diff --git a/.changelog/16257.txt b/.changelog/16257.txt new file mode 100644 index 00000000000..8e98530c421 --- /dev/null +++ b/.changelog/16257.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: Fix issue where mesh gateways would use the wrong address when contacting a remote peer with the same datacenter name. +``` diff --git a/.changelog/16263.txt b/.changelog/16263.txt new file mode 100644 index 00000000000..a8cd3f9043a --- /dev/null +++ b/.changelog/16263.txt @@ -0,0 +1,4 @@ +```release-note:security +Upgrade to use Go 1.20.1. +This resolves vulnerabilities [CVE-2022-41724](https://go.dev/issue/58001) in `crypto/tls` and [CVE-2022-41723](https://go.dev/issue/57855) in `net/http`. +``` diff --git a/.changelog/16274.txt b/.changelog/16274.txt new file mode 100644 index 00000000000..983d33b1959 --- /dev/null +++ b/.changelog/16274.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: Bump Envoy 1.22.5 to 1.22.7, 1.23.2 to 1.23.4, 1.24.0 to 1.24.2, add 1.25.1, remove 1.21.5 +``` diff --git a/.changelog/16284.txt b/.changelog/16284.txt new file mode 100644 index 00000000000..23dd2aa6fef --- /dev/null +++ b/.changelog/16284.txt @@ -0,0 +1,3 @@ +```release-note:feature +cli: adds new CLI commands `consul troubleshoot upstreams` and `consul troubleshoot proxy` to troubleshoot Consul's service mesh configuration and network issues. +``` \ No newline at end of file diff --git a/.changelog/16288.txt b/.changelog/16288.txt new file mode 100644 index 00000000000..5e2820ec27d --- /dev/null +++ b/.changelog/16288.txt @@ -0,0 +1,8 @@ +```release-note:deprecation +cli: Deprecate the `-merge-policies` and `-merge-roles` flags from the `consul token update` command in favor of: `-append-policy-id`, `-append-policy-name`, `-append-role-name`, and `-append-role-id`. +``` + +```release-note:improvement +cli: added `-append-policy-id`, `-append-policy-name`, `-append-role-name`, and `-append-role-id` flags to the `consul token update` command. +These flags allow updates to a token's policies/roles without having to override them completely. +``` \ No newline at end of file diff --git a/.changelog/16301.txt b/.changelog/16301.txt new file mode 100644 index 00000000000..e1dc5deb1c5 --- /dev/null +++ b/.changelog/16301.txt @@ -0,0 +1,3 @@ +```release-note:bug +agent configuration: Fix issue of using unix socket when https is used. +``` diff --git a/.changelog/16339.txt b/.changelog/16339.txt new file mode 100644 index 00000000000..cf44f010aff --- /dev/null +++ b/.changelog/16339.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: Fix bug where services were incorrectly imported as connect-enabled. +``` diff --git a/.changelog/16358.txt b/.changelog/16358.txt new file mode 100644 index 00000000000..91fcfe4505c --- /dev/null +++ b/.changelog/16358.txt @@ -0,0 +1,3 @@ +```release-note:improvement +container: Upgrade container image to use to Alpine 3.17. +``` diff --git a/.changelog/16369.txt b/.changelog/16369.txt new file mode 100644 index 00000000000..1ae86968c40 --- /dev/null +++ b/.changelog/16369.txt @@ -0,0 +1,3 @@ +```release-note:feature +**API Gateway (Beta)** This version adds support for API gateway on VMs. API gateway provides a highly-configurable ingress for requests coming into a Consul network. For more information, refer to the [API gateway](https://developer.hashicorp.com/consul/docs/connect/gateways/api-gateway) documentation. +``` diff --git a/.changelog/16444.txt b/.changelog/16444.txt new file mode 100644 index 00000000000..542f0560fec --- /dev/null +++ b/.changelog/16444.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fix issue with lists and filters not rendering properly +``` diff --git a/.changelog/16445.txt b/.changelog/16445.txt new file mode 100644 index 00000000000..19745c6df99 --- /dev/null +++ b/.changelog/16445.txt @@ -0,0 +1,3 @@ +```release-note:bug +cli: ensure acl token read -self works +``` diff --git a/.changelog/16485.txt b/.changelog/16485.txt new file mode 100644 index 00000000000..7e1938b00ea --- /dev/null +++ b/.changelog/16485.txt @@ -0,0 +1,3 @@ +```release-note:bug +cli: fix panic read non-existent acl policy +``` diff --git a/.changelog/16495.txt b/.changelog/16495.txt new file mode 100644 index 00000000000..4b8ee933ed0 --- /dev/null +++ b/.changelog/16495.txt @@ -0,0 +1,3 @@ +```release-note:improvement +mesh: Add ServiceResolver RequestTimeout for route timeouts to make request timeouts configurable +``` diff --git a/.changelog/16497.txt b/.changelog/16497.txt new file mode 100644 index 00000000000..3aa3633ac3a --- /dev/null +++ b/.changelog/16497.txt @@ -0,0 +1,3 @@ +```release-note:bug +proxycfg: ensure that an irrecoverable error in proxycfg closes the xds session and triggers a replacement proxycfg watcher +``` diff --git a/.changelog/16498.txt b/.changelog/16498.txt new file mode 100644 index 00000000000..cdb045d67c9 --- /dev/null +++ b/.changelog/16498.txt @@ -0,0 +1,3 @@ +```release-note:bug +proxycfg: fix a bug where terminating gateways were not cleaning up deleted service resolvers for their referenced services +``` diff --git a/.changelog/16499.txt b/.changelog/16499.txt new file mode 100644 index 00000000000..4bd50db47e8 --- /dev/null +++ b/.changelog/16499.txt @@ -0,0 +1,3 @@ +```release-note:bug +mesh: Fix resolution of service resolvers with subsets for external upstreams +``` diff --git a/.changelog/16506.txt b/.changelog/16506.txt new file mode 100644 index 00000000000..2560c247466 --- /dev/null +++ b/.changelog/16506.txt @@ -0,0 +1,8 @@ +```release-note:deprecation +cli: Deprecate the `-merge-node-identites` and `-merge-service-identities` flags from the `consul token update` command in favor of: `-append-node-identity` and `-append-service-identity`. +``` + +```release-note:improvement +cli: added `-append-service-identity` and `-append-node-identity` flags to the `consul token update` command. +These flags allow updates to a token's node identities/service identities without having to override them. +``` \ No newline at end of file diff --git a/.changelog/16508.txt b/.changelog/16508.txt new file mode 100644 index 00000000000..4732ed553c6 --- /dev/null +++ b/.changelog/16508.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: support filtering API gateways in the ui and displaying their documentation links +``` diff --git a/.changelog/16512.txt b/.changelog/16512.txt new file mode 100644 index 00000000000..288ff8aa45e --- /dev/null +++ b/.changelog/16512.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateways: fix HTTPRoute bug where service weights could be less than or equal to 0 and result in a downstream envoy protocol error +``` \ No newline at end of file diff --git a/.changelog/16530.txt b/.changelog/16530.txt new file mode 100644 index 00000000000..38d98036ab9 --- /dev/null +++ b/.changelog/16530.txt @@ -0,0 +1,7 @@ +```release-note:bug +cli: Fixes an issue with `consul connect envoy` where a log to STDOUT could malform JSON when used with `-bootstrap`. +``` + +```release-note:bug +cli: Fixes an issue with `consul connect envoy` where grpc-disabled agents were not error-handled correctly. +``` diff --git a/.changelog/16531.txt b/.changelog/16531.txt new file mode 100644 index 00000000000..71f83ad2acc --- /dev/null +++ b/.changelog/16531.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateways: fix HTTPRoute bug where services with a weight not divisible by 10000 are never registered properly +``` \ No newline at end of file diff --git a/.changelog/16570.txt b/.changelog/16570.txt new file mode 100644 index 00000000000..ad07cda81c0 --- /dev/null +++ b/.changelog/16570.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: Fixes a bug that can lead to peering service deletes impacting the state of local services +``` diff --git a/.changelog/16574.txt b/.changelog/16574.txt new file mode 100644 index 00000000000..78bfc334984 --- /dev/null +++ b/.changelog/16574.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: fix rendering issues on Overview and empty-states by addressing isHTMLSafe errors +``` diff --git a/.changelog/16585.txt b/.changelog/16585.txt new file mode 100644 index 00000000000..11e2959cfbd --- /dev/null +++ b/.changelog/16585.txt @@ -0,0 +1,3 @@ +```release-note:feature +xds: Allow for configuring connect proxies to send service mesh telemetry to an HCP metrics collection service. +``` \ No newline at end of file diff --git a/.changelog/16592.txt b/.changelog/16592.txt new file mode 100644 index 00000000000..ba37d69015f --- /dev/null +++ b/.changelog/16592.txt @@ -0,0 +1,3 @@ +```release-note:bug +ca: Fixes a bug where updating Vault CA Provider config would cause TLS issues in the service mesh +``` diff --git a/.changelog/16647.txt b/.changelog/16647.txt new file mode 100644 index 00000000000..cbe38b3ed28 --- /dev/null +++ b/.changelog/16647.txt @@ -0,0 +1,3 @@ +```release-note:bug +raft_logstore: Fixes a bug where restoring a snapshot when using the experimental WAL storage backend causes a panic. +``` diff --git a/.changelog/16649.txt b/.changelog/16649.txt new file mode 100644 index 00000000000..e510558ff90 --- /dev/null +++ b/.changelog/16649.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateways: Adds validation to ensure the API Gateway has a listener defined when created +``` \ No newline at end of file diff --git a/.changelog/16651.txt b/.changelog/16651.txt new file mode 100644 index 00000000000..c297cca489d --- /dev/null +++ b/.changelog/16651.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateway: **(Enterprise only)** Fix bug where namespace/partition would fail to unmarshal. +``` diff --git a/.changelog/16660.txt b/.changelog/16660.txt new file mode 100644 index 00000000000..f8971862165 --- /dev/null +++ b/.changelog/16660.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: fix PUT token request with adding missed AccessorID property to requestBody +``` \ No newline at end of file diff --git a/.changelog/16661.txt b/.changelog/16661.txt new file mode 100644 index 00000000000..41336502116 --- /dev/null +++ b/.changelog/16661.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateways: Fixes a bug API gateways using HTTP listeners were taking upwards of 15 seconds to get configured over xDS. +``` diff --git a/.changelog/16675.txt b/.changelog/16675.txt new file mode 100644 index 00000000000..f72eedc61c8 --- /dev/null +++ b/.changelog/16675.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: Fixes a bug where the importing partition was not added to peered failover targets, which causes issues when the importing partition is a non-default partition. +``` diff --git a/.changelog/16700.txt b/.changelog/16700.txt new file mode 100644 index 00000000000..82da5936ddb --- /dev/null +++ b/.changelog/16700.txt @@ -0,0 +1,3 @@ +```release-note:bug +audit-logging: (Enterprise only) Fix a bug where `/agent/monitor` and `/agent/metrics` endpoints return a `Streaming not supported` error when audit logs are enabled. This also fixes the delay receiving logs when running `consul monitor` against an agent with audit logs enabled. +``` diff --git a/.changelog/16729.txt b/.changelog/16729.txt new file mode 100644 index 00000000000..36c6e1aeabb --- /dev/null +++ b/.changelog/16729.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: Fix issue resulting in prepared query failover to cluster peers never un-failing over. +``` diff --git a/.changelog/16776.txt b/.changelog/16776.txt new file mode 100644 index 00000000000..0159aee8589 --- /dev/null +++ b/.changelog/16776.txt @@ -0,0 +1,3 @@ +```release-note:improvement +peering: allow re-establishing terminated peering from new token without deleting existing peering first. +``` \ No newline at end of file diff --git a/.changelog/16781.txt b/.changelog/16781.txt new file mode 100644 index 00000000000..708a91d40c8 --- /dev/null +++ b/.changelog/16781.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateway: **(Enterprise only)** Fix bug where namespace/partition would fail to unmarshal for TCPServices. +``` diff --git a/.changelog/16789.txt b/.changelog/16789.txt new file mode 100644 index 00000000000..ed25e11bbb6 --- /dev/null +++ b/.changelog/16789.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateway: **(Enterprise only)** Fix bug where parent refs and service refs for a route in the same namespace as the route would fallback to the default namespace if the namespace was not specified in the configuration rather than falling back to the routes namespace. +``` diff --git a/.changelog/16818.txt b/.changelog/16818.txt new file mode 100644 index 00000000000..665c11034cc --- /dev/null +++ b/.changelog/16818.txt @@ -0,0 +1,3 @@ +```release-note:bug +cache: revert cache refactor which could cause blocking queries to never return +``` diff --git a/.changelog/_16677.txt b/.changelog/_16677.txt new file mode 100644 index 00000000000..0bf621f09ac --- /dev/null +++ b/.changelog/_16677.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateway: **(Enterprise only)** Fix bug where routes defined in a different namespace than a gateway would fail to register. [[GH-16677](https://github.com/hashicorp/consul/pull/16677)]. +``` diff --git a/.changelog/_4696.txt b/.changelog/_4696.txt new file mode 100644 index 00000000000..951896fd66f --- /dev/null +++ b/.changelog/_4696.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: **(Consul Enterprise only)** Fix issue where connect-enabled services with peer upstreams incorrectly required `service:write` access in the `default` namespace to query data, which was too restrictive. Now having `service:write` to any namespace is sufficient to query the peering data. +``` diff --git a/.changelog/_4832.txt b/.changelog/_4832.txt new file mode 100644 index 00000000000..b4576871554 --- /dev/null +++ b/.changelog/_4832.txt @@ -0,0 +1,3 @@ +```release-note:bug +peering: **(Consul Enterprise only)** Fix issue where resolvers, routers, and splitters referencing peer targets may not work correctly for non-default partitions and namespaces. Enterprise customers leveraging peering are encouraged to upgrade both servers and agents to avoid this problem. +``` diff --git a/.circleci/config.yml b/.circleci/config.yml index dae806a625b..20bcf317403 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,10 +6,6 @@ parameters: type: string default: "" description: "Commit to run load tests against" - trigger-load-test: - type: boolean - default: false - description: "Boolean whether to run the load test workflow" references: paths: @@ -21,12 +17,12 @@ references: GIT_COMMITTER_NAME: circleci-consul S3_ARTIFACT_BUCKET: consul-dev-artifacts-v2 BASH_ENV: .circleci/bash_env.sh - GO_VERSION: 1.19.4 + GO_VERSION: 1.20.1 envoy-versions: &supported_envoy_versions - - &default_envoy_version "1.21.5" - - "1.22.5" - - "1.23.2" - - "1.24.0" + - &default_envoy_version "1.22.7" + - "1.23.4" + - "1.24.2" + - "1.25.1" nomad-versions: &supported_nomad_versions - &default_nomad_version "1.3.3" - "1.2.10" @@ -39,7 +35,7 @@ references: images: # When updating the Go version, remember to also update the versions in the # workflows section for go-test-lib jobs. - go: &GOLANG_IMAGE docker.mirror.hashicorp.services/cimg/go:1.19.4 + go: &GOLANG_IMAGE docker.mirror.hashicorp.services/cimg/go:1.20.1 ember: &EMBER_IMAGE docker.mirror.hashicorp.services/circleci/node:14-browsers ubuntu: &UBUNTU_CI_IMAGE ubuntu-2004:202201-02 cache: @@ -613,7 +609,7 @@ jobs: - run: *notify-slack-failure nomad-integration-test: &NOMAD_TESTS docker: - - image: docker.mirror.hashicorp.services/cimg/go:1.19 + - image: docker.mirror.hashicorp.services/cimg/go:1.20 parameters: nomad-version: type: enum @@ -992,78 +988,6 @@ jobs: path: *TEST_RESULTS_DIR - run: *notify-slack-failure - # Run load tests against a commit - load-test: - docker: - - image: hashicorp/terraform:latest - environment: - AWS_DEFAULT_REGION: us-east-2 - BUCKET: consul-ci-load-tests - BASH_ENV: /etc/profile - shell: /bin/sh -leo pipefail - steps: - - checkout - - run: apk add jq curl bash - - run: - name: export load-test credentials - command: | - echo "export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_LOAD_TEST" >> $BASH_ENV - echo "export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_LOAD_TEST" >> $BASH_ENV - - run: - name: export role arn - command: | - echo "export TF_VAR_role_arn=$ROLE_ARN_LOAD_TEST" >> $BASH_ENV - - run: - name: setup TF_VARs - command: | - # if pipeline.parameters.commit="" it was not triggered/set through the API - # so we use the latest commit from _this_ branch. This is the case for nightly tests. - if [ "<< pipeline.parameters.commit >>" = "" ]; then - LOCAL_COMMIT_SHA=$(git rev-parse HEAD) - else - LOCAL_COMMIT_SHA="<< pipeline.parameters.commit >>" - fi - echo "export LOCAL_COMMIT_SHA=${LOCAL_COMMIT_SHA}" >> $BASH_ENV - git checkout ${LOCAL_COMMIT_SHA} - - short_ref=$(git rev-parse --short ${LOCAL_COMMIT_SHA}) - echo "export TF_VAR_ami_owners=$LOAD_TEST_AMI_OWNERS" >> $BASH_ENV - echo "export TF_VAR_vpc_name=$short_ref" >> $BASH_ENV - echo "export TF_VAR_cluster_name=$short_ref" >> $BASH_ENV - echo "export TF_VAR_consul_download_url=https://${S3_ARTIFACT_BUCKET}.s3.${AWS_DEFAULT_REGION}.amazonaws.com/${S3_ARTIFACT_PATH}/${LOCAL_COMMIT_SHA}.zip" >> $BASH_ENV - - run: - name: wait for dev build from test-integrations workflow - command: | - echo "curl-ing https://${S3_ARTIFACT_BUCKET}.s3.${AWS_DEFAULT_REGION}.amazonaws.com/${S3_ARTIFACT_PATH}/${LOCAL_COMMIT_SHA}.zip" - until [ $SECONDS -ge 300 ] && exit 1; do - curl -o /dev/null --fail --silent "https://${S3_ARTIFACT_BUCKET}.s3.${AWS_DEFAULT_REGION}.amazonaws.com/${S3_ARTIFACT_PATH}/${LOCAL_COMMIT_SHA}.zip" && exit - echo -n "." - sleep 2 - done - - run: - working_directory: .circleci/terraform/load-test - name: terraform init - command: | - short_ref=$(git rev-parse --short HEAD) - echo "Testing commit id: $short_ref" - terraform init \ - -backend-config="bucket=${BUCKET}" \ - -backend-config="key=${LOCAL_COMMIT_SHA}" \ - -backend-config="region=${AWS_DEFAULT_REGION}" \ - -backend-config="role_arn=${ROLE_ARN_LOAD_TEST}" - - run: - working_directory: .circleci/terraform/load-test - name: run terraform apply - command: | - terraform apply -auto-approve - - run: - working_directory: .circleci/terraform/load-test - when: always - name: terraform destroy - command: | - for i in $(seq 1 5); do terraform destroy -auto-approve && s=0 && break || s=$? && sleep 20; done; (exit $s) - - run: *notify-slack-failure - # The noop job is a used as a very fast job in the verify-ci workflow because every workflow # requires at least one job. It does nothing. noop: @@ -1080,7 +1004,6 @@ workflows: jobs: [noop] go-tests: - unless: << pipeline.parameters.trigger-load-test >> jobs: - check-go-mod: &filter-ignore-non-go-branches filters: @@ -1110,40 +1033,39 @@ workflows: - go-test-lib: name: "go-test-envoyextensions" path: envoyextensions - go-version: "1.19" + go-version: "1.20" requires: [dev-build] <<: *filter-ignore-non-go-branches - go-test-lib: name: "go-test-troubleshoot" path: troubleshoot - go-version: "1.19" + go-version: "1.20" requires: [dev-build] <<: *filter-ignore-non-go-branches - go-test-lib: - name: "go-test-api go1.18" + name: "go-test-api go1.19" path: api - go-version: "1.18" + go-version: "1.19" requires: [dev-build] - go-test-lib: - name: "go-test-api go1.19" + name: "go-test-api go1.20" path: api - go-version: "1.19" + go-version: "1.20" requires: [dev-build] - go-test-lib: - name: "go-test-sdk go1.18" + name: "go-test-sdk go1.19" path: sdk - go-version: "1.18" + go-version: "1.19" <<: *filter-ignore-non-go-branches - go-test-lib: - name: "go-test-sdk go1.19" + name: "go-test-sdk go1.20" path: sdk - go-version: "1.19" + go-version: "1.20" <<: *filter-ignore-non-go-branches - go-test-race: *filter-ignore-non-go-branches - go-test-32bit: *filter-ignore-non-go-branches - noop build-distros: - unless: << pipeline.parameters.trigger-load-test >> jobs: - check-go-mod: *filter-ignore-non-go-branches - build-386: &require-check-go-mod @@ -1175,7 +1097,6 @@ workflows: context: consul-ci - noop test-integrations: - unless: << pipeline.parameters.trigger-load-test >> jobs: - dev-build: *filter-ignore-non-go-branches - dev-upload-s3: &dev-upload @@ -1212,7 +1133,6 @@ workflows: - dev-build - noop frontend: - unless: << pipeline.parameters.trigger-load-test >> jobs: - frontend-cache: filters: @@ -1245,19 +1165,3 @@ workflows: requires: - ember-build-ent - noop - - load-test: - when: << pipeline.parameters.trigger-load-test >> - jobs: - - load-test - - nightly-jobs: - triggers: - - schedule: - cron: "0 4 * * *" # 4AM UTC <> 12AM EST <> 9PM PST should have no impact - filters: - branches: - only: - - main - jobs: - - load-test diff --git a/.github/scripts/changelog_checker.sh b/.github/scripts/changelog_checker.sh new file mode 100755 index 00000000000..e46030da1e1 --- /dev/null +++ b/.github/scripts/changelog_checker.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -euo pipefail + +# check if there is a diff in the .changelog directory +# for PRs against the main branch, the changelog file name should match the PR number +if [ "$GITHUB_BASE_REF" = "$GITHUB_DEFAULT_BRANCH" ]; then + enforce_matching_pull_request_number="matching this PR number " + changelog_file_path=".changelog/(_)?$PR_NUMBER.txt" +else + changelog_file_path=".changelog/[_0-9]*.txt" +fi + +changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/main")" | egrep "${changelog_file_path}") + +# If we do not find a file in .changelog/, we fail the check +if [ -z "$changelog_files" ]; then + # Fail status check when no .changelog entry was found on the PR + echo "Did not find a .changelog entry ${enforce_matching_pull_request_number}and the 'pr/no-changelog' label was not applied. Reference - https://github.com/hashicorp/consul/pull/8387" + exit 1 +else + echo "Found .changelog entry in PR!" +fi diff --git a/.github/scripts/get_runner_classes.sh b/.github/scripts/get_runner_classes.sh new file mode 100755 index 00000000000..b722ba76878 --- /dev/null +++ b/.github/scripts/get_runner_classes.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# +# This script generates tag-sets that can be used as runs-on: values to select runners. + +set -euo pipefail + +case "$GITHUB_REPOSITORY" in + *-enterprise) + # shellcheck disable=SC2129 + echo "compute-small=['self-hosted', 'linux', 'small']" >> "$GITHUB_OUTPUT" + echo "compute-medium=['self-hosted', 'linux', 'medium']" >> "$GITHUB_OUTPUT" + echo "compute-large=['self-hosted', 'linux', 'large']" >> "$GITHUB_OUTPUT" + # m5d.8xlarge is equivalent to our xl custom runner in OSS + echo "compute-xl=['self-hosted', 'ondemand', 'linux', 'type=m5d.8xlarge']" >> "$GITHUB_OUTPUT" + ;; + *) + # shellcheck disable=SC2129 + echo "compute-small=['custom-linux-s-consul-latest']" >> "$GITHUB_OUTPUT" + echo "compute-medium=['custom-linux-m-consul-latest']" >> "$GITHUB_OUTPUT" + echo "compute-large=['custom-linux-l-consul-latest']" >> "$GITHUB_OUTPUT" + echo "compute-xl=['custom-linux-xl-consul-latest']" >> "$GITHUB_OUTPUT" + ;; +esac diff --git a/.github/scripts/notify_slack.sh b/.github/scripts/notify_slack.sh new file mode 100755 index 00000000000..b3dcdb210dc --- /dev/null +++ b/.github/scripts/notify_slack.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -uo pipefail + +# This script is used in GitHub Actions pipelines to notify Slack of a job failure. + +if [[ $GITHUB_REF_NAME == "main" ]]; then + GITHUB_ENDPOINT="https://github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}" + GITHUB_ACTIONS_ENDPOINT="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + COMMIT_MESSAGE=$(git log -1 --pretty=%B | head -n1) + SHORT_REF=$(git rev-parse --short "${GITHUB_SHA}") + curl -X POST -H 'Content-type: application/json' \ + --data \ + "{ \ + \"attachments\": [ \ + { \ + \"fallback\": \"GitHub Actions workflow failed!\", \ + \"text\": \"❌ Failed: \`${GITHUB_ACTOR}\`'s <${GITHUB_ACTIONS_ENDPOINT}|${GITHUB_JOB}> job failed for commit <${GITHUB_ENDPOINT}|${SHORT_REF}> on \`${GITHUB_REF_NAME}\`!\n\n- <${COMMIT_MESSAGE}\", \ + \"footer\": \"${GITHUB_REPOSITORY}\", \ + \"ts\": \"$(date +%s)\", \ + \"color\": \"danger\" \ + } \ + ] \ + }" "${FEED_CONSUL_GH_URL}" +else + echo "Not posting slack failure notifications for non-main branch" +fi diff --git a/.github/scripts/rerun_fails_report.sh b/.github/scripts/rerun_fails_report.sh new file mode 100755 index 00000000000..ac6b7cf2ff9 --- /dev/null +++ b/.github/scripts/rerun_fails_report.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# +# Add a comment on the github PR if there were any rerun tests. +# +set -eu -o pipefail + +report_filename="${1?report filename is required}" +if [ ! -s "$report_filename" ]; then + echo "gotestsum rerun report file is empty or missing" + exit 0 +fi + +function report { + echo ":repeat: gotestsum re-ran some tests in https://github.com/hashicorp/consul/actions/run/$GITHUB_RUN_ID" + echo + echo '```' + cat "$report_filename" + echo '```' +} + +report diff --git a/.github/scripts/set_test_package_matrix.sh b/.github/scripts/set_test_package_matrix.sh new file mode 100755 index 00000000000..a9a21c747e5 --- /dev/null +++ b/.github/scripts/set_test_package_matrix.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +export RUNNER_COUNT=$1 + +# set matrix var to list of unique packages containing tests +matrix="$(go list -json="ImportPath,TestGoFiles" ./... | jq --compact-output '. | select(.TestGoFiles != null) | .ImportPath' | jq --slurp --compact-output '.' | jq --argjson runnercount $RUNNER_COUNT -cM '[_nwise(length / $runnercount | floor)]'))" + +echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/backport-checker.yml b/.github/workflows/backport-checker.yml new file mode 100644 index 00000000000..c9af23ea971 --- /dev/null +++ b/.github/workflows/backport-checker.yml @@ -0,0 +1,32 @@ +# This workflow checks that there is either a 'pr/no-backport' label applied to a PR +# or there is a backport/* label indicating a backport has been set + +name: Backport Checker + +on: + pull_request: + types: [opened, synchronize, labeled] + # Runs on PRs to main and all release branches + branches: + - main + - release/* + +jobs: + # checks that a backport label is present for a PR + backport-check: + # If there's a `pr/no-backport` label we ignore this check. Also, we ignore PRs created by the bot assigned to `backport-assistant` + if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-backport') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" + runs-on: ubuntu-latest + + steps: + - name: Check for Backport Label + run: | + labels="${{join(github.event.pull_request.labels.*.name, ', ') }}" + if [[ "$labels" =~ .*"backport/".* ]]; then + echo "Found backport label!" + exit 0 + fi + # Fail status check when no backport label was found on the PR + echo "Did not find a backport label matching the pattern 'backport/*' and the 'pr/no-backport' label was not applied. Reference - https://github.com/hashicorp/consul/pull/16567" + exit 1 + diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml new file mode 100644 index 00000000000..57d23d52f58 --- /dev/null +++ b/.github/workflows/build-artifacts.yml @@ -0,0 +1,123 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# This workflow builds a dev binary and distributes a Docker image on every push to the main branch. +name: build-artifacts + +on: + push: + branches: + - main + +permissions: + contents: read + +env: + GOPRIVATE: github.com/hashicorp + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + compute-small: ${{ steps.setup-outputs.outputs.compute-small }} + compute-medium: ${{ steps.setup-outputs.outputs.compute-medium }} + compute-large: ${{ steps.setup-outputs.outputs.compute-large }} + compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + - id: setup-outputs + name: Setup outputs + run: ./.github/scripts/get_runner_classes.sh + + dev-build-push: + needs: setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} + permissions: + id-token: write # NOTE: this permission is explicitly required for Vault auth. + contents: read + steps: + # NOTE: ENT specific step as we store secrets in Vault. + - name: Authenticate to Vault + if: ${{ endsWith(github.repository, '-enterprise') }} + id: vault-auth + run: vault-auth + + # NOTE: ENT specific step as we store secrets in Vault. + - name: Fetch Secrets + if: ${{ 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 }}/dockerhub username | DOCKERHUB_USERNAME; + kv/data/github/${{ github.repository }}/dockerhub token | DOCKERHUB_TOKEN; + + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + + - name: Build dev binary + run: make dev + + - name: Set env vars + run: | + echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + 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@f03ac48505955848960e80bbb68046aa35c7b9e7 # pin@v2.4.1 + + # NOTE: conditional specific logic as we store secrets in Vault in ENT and use GHA secrets in OSS. + - name: Login to Docker Hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2.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@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # pin@v4.0.0 + with: + context: ./bin + file: ./build-support/docker/Consul-Dev.dockerfile + labels: COMMIT_SHA=${{ github.sha }},GITHUB_BUILD_URL=${{ env.GITHUB_BUILD_URL }} + push: true + tags: | + hashicorpdev/${{ github.event.repository.name }}:${{ env.SHORT_SHA }} + hashicorpdev/${{ github.event.repository.name }}:latest + + # This is job is required for branch protection as a required gihub check + # because GitHub actions show up as checks at the job level and not the + # workflow level. This is currently a feature request: + # https://github.com/orgs/community/discussions/12395 + # + # This job must: + # - be placed after the fanout of a workflow so that everything fans back in + # to this job. + # - "need" any job that is part of the fan out / fan in + # - implement the if logic because we have conditional jobs + # (go-test-enteprise) that this job needs and this would potentially get + # skipped if a previous job got skipped. So we use the if clause to make + # sure it does not get skipped. + + build-artifacts-success: + needs: + - setup + - dev-build-push + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + if: | + (always() && ! cancelled()) && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') + steps: + - run: echo "build-artifacts succeeded" diff --git a/.github/workflows/build-distros.yml b/.github/workflows/build-distros.yml new file mode 100644 index 00000000000..c8036a75055 --- /dev/null +++ b/.github/workflows/build-distros.yml @@ -0,0 +1,143 @@ +# NOTE: this workflow builds Consul binaries on multiple architectures for PRs. +# It is aimed at checking new commits don't introduce any breaking build changes. +name: build-distros + +on: + pull_request: + push: + branches: + # Push events on the main branch + - main + - release/** + +permissions: + contents: read + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + compute-small: ${{ steps.setup-outputs.outputs.compute-small }} + compute-medium: ${{ steps.setup-outputs.outputs.compute-medium }} + compute-large: ${{ steps.setup-outputs.outputs.compute-large }} + compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + - id: setup-outputs + name: Setup outputs + run: ./.github/scripts/get_runner_classes.sh + + check-go-mod: + needs: + - setup + uses: ./.github/workflows/reusable-check-go-mod.yml + with: + runs-on: ${{ needs.setup.outputs.compute-medium }} + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + build-386: + needs: + - setup + - check-go-mod + env: + XC_OS: "freebsd linux windows" + runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - name: Build + run: | + for os in $XC_OS; do + GOOS="$os" GOARCH=386 CGO_ENABLED=0 go build + done + + build-amd64: + needs: + - setup + - check-go-mod + env: + XC_OS: "darwin freebsd linux solaris windows" + runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - name: Build + run: | + for os in $XC_OS; do + GOOS="$os" GOARCH=amd64 CGO_ENABLED=0 go build + done + + build-arm: + needs: + - setup + - check-go-mod + runs-on: ${{ fromJSON(needs.setup.outputs.compute-medium) }} + env: + CGO_ENABLED: 1 + GOOS: linux + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: | + sudo apt-get update --allow-releaseinfo-change-suite --allow-releaseinfo-change-version && sudo apt-get install -y gcc-arm-linux-gnueabi gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu + + - run: CC=arm-linux-gnueabi-gcc GOARCH=arm GOARM=5 go build + - run: CC=arm-linux-gnueabihf-gcc GOARCH=arm GOARM=6 go build + - run: CC=aarch64-linux-gnu-gcc GOARCH=arm64 go build + + # This is job is required for branch protection as a required gihub check + # because GitHub actions show up as checks at the job level and not the + # workflow level. This is currently a feature request: + # https://github.com/orgs/community/discussions/12395 + # + # This job must: + # - be placed after the fanout of a workflow so that everything fans back in + # to this job. + # - "need" any job that is part of the fan out / fan in + # - implement the if logic because we have conditional jobs + # (go-test-enteprise) that this job needs and this would potentially get + # skipped if a previous job got skipped. So we use the if clause to make + # sure it does not get skipped. + + build-distros-success: + needs: + - setup + - check-go-mod + - build-386 + - build-amd64 + - build-arm + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + if: | + (always() && ! cancelled()) && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') + steps: + - run: echo "build-distros succeeded" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20f316c12e3..8c49acbdd8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: pre-version: ${{ steps.set-product-version.outputs.prerelease-product-version }} shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: set product version id: set-product-version uses: hashicorp/actions-set-product-version@v1 @@ -60,7 +60,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: 'Checkout directory' - uses: actions/checkout@v3 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -68,7 +68,7 @@ jobs: version: ${{ needs.set-product-version.outputs.product-version }} product: ${{ env.PKG_NAME }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@3.1.2 with: name: metadata.json path: ${{ steps.generate-metadata-file.outputs.filepath }} @@ -79,23 +79,23 @@ jobs: strategy: matrix: include: - - {go: "1.19.4", goos: "linux", goarch: "386"} - - {go: "1.19.4", goos: "linux", goarch: "amd64"} - - {go: "1.19.4", goos: "linux", goarch: "arm"} - - {go: "1.19.4", goos: "linux", goarch: "arm64"} - - {go: "1.19.4", goos: "freebsd", goarch: "386"} - - {go: "1.19.4", goos: "freebsd", goarch: "amd64"} - - {go: "1.19.4", goos: "windows", goarch: "386"} - - {go: "1.19.4", goos: "windows", goarch: "amd64"} - - {go: "1.19.4", goos: "solaris", goarch: "amd64"} + - {go: "1.20.1", goos: "linux", goarch: "386"} + - {go: "1.20.1", goos: "linux", goarch: "amd64"} + - {go: "1.20.1", goos: "linux", goarch: "arm"} + - {go: "1.20.1", goos: "linux", goarch: "arm64"} + - {go: "1.20.1", goos: "freebsd", goarch: "386"} + - {go: "1.20.1", goos: "freebsd", goarch: "amd64"} + - {go: "1.20.1", goos: "windows", goarch: "386"} + - {go: "1.20.1", goos: "windows", goarch: "amd64"} + - {go: "1.20.1", goos: "solaris", goarch: "amd64"} fail-fast: true name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Setup with node and yarn - uses: actions/setup-node@v3 + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3.6.0 with: node-version: '14' cache: 'yarn' @@ -157,13 +157,13 @@ jobs: echo "RPM_PACKAGE=$(basename out/*.rpm)" >> $GITHUB_ENV echo "DEB_PACKAGE=$(basename out/*.deb)" >> $GITHUB_ENV - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@3.1.2 if: ${{ matrix.goos == 'linux' }} with: name: ${{ env.RPM_PACKAGE }} path: out/${{ env.RPM_PACKAGE }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@3.1.2 if: ${{ matrix.goos == 'linux' }} with: name: ${{ env.DEB_PACKAGE }} @@ -176,15 +176,15 @@ jobs: matrix: goos: [ darwin ] goarch: [ "amd64", "arm64" ] - go: [ "1.19.4" ] + go: [ "1.20.1" ] fail-fast: true name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Setup with node and yarn - uses: actions/setup-node@v3 + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3.6.0 with: node-version: '14' cache: 'yarn' @@ -232,7 +232,7 @@ jobs: version: ${{needs.set-product-version.outputs.product-version}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 # 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 @@ -266,7 +266,7 @@ jobs: version: ${{needs.set-product-version.outputs.product-version}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - uses: hashicorp/actions-docker-build@v1 with: version: ${{env.version}} @@ -286,7 +286,7 @@ jobs: version: ${{needs.set-product-version.outputs.product-version}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 # 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 @@ -323,15 +323,15 @@ jobs: name: Verify ${{ matrix.arch }} linux binary steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Download ${{ matrix.arch }} zip - uses: actions/download-artifact@v3 + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # pin@v3.0.2 with: name: ${{ env.zip_name }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2.1.0 if: ${{ matrix.arch == 'arm' || matrix.arch == 'arm64' }} with: # this should be a comma-separated string as opposed to an array @@ -353,10 +353,10 @@ jobs: name: Verify amd64 darwin binary steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Download amd64 darwin zip - uses: actions/download-artifact@v3 + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # pin@v3.0.2 with: name: ${{ env.zip_name }} @@ -380,7 +380,7 @@ jobs: name: Verify ${{ matrix.arch }} debian package steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Set package version run: | @@ -391,12 +391,12 @@ jobs: echo "pkg_name=consul_${{ env.pkg_version }}-1_${{ matrix.arch }}.deb" >> $GITHUB_ENV - name: Download workflow artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # pin@v3.0.2 with: name: ${{ env.pkg_name }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2.1.0 with: platforms: all @@ -417,7 +417,7 @@ jobs: name: Verify ${{ matrix.arch }} rpm steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 - name: Set package version run: | @@ -428,12 +428,12 @@ jobs: echo "pkg_name=consul-${{ env.pkg_version }}-1.${{ matrix.arch }}.rpm" >> $GITHUB_ENV - name: Download workflow artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # pin@v3.0.2 with: name: ${{ env.pkg_name }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2.1.0 with: platforms: all diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index bb13255d297..c81eb8a7a04 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -15,7 +15,7 @@ jobs: # checks that a .changelog entry is present for a PR changelog-check: # If there a `pr/no-changelog` label we ignore this check. Also, we ignore PRs created by the bot assigned to `backport-assistant` - if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-changelog') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" + if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-changelog') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" runs-on: ubuntu-latest steps: @@ -24,23 +24,8 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches - name: Check for changelog entry in diff - run: | - # check if there is a diff in the .changelog directory - # for PRs against the main branch, the changelog file name should match the PR number - if [ "${{ github.event.pull_request.base.ref }}" = "${{ github.event.repository.default_branch }}" ]; then - enforce_matching_pull_request_number="matching this PR number " - changelog_file_path=".changelog/(_)?${{ github.event.pull_request.number }}.txt" - else - changelog_file_path=".changelog/[_0-9]*.txt" - fi - - changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/main")" | egrep ${changelog_file_path}) - - # If we do not find a file in .changelog/, we fail the check - if [ -z "$changelog_files" ]; then - # Fail status check when no .changelog entry was found on the PR - echo "Did not find a .changelog entry ${enforce_matching_pull_request_number}and the 'pr/no-changelog' label was not applied. Reference - https://github.com/hashicorp/consul/pull/8387" - exit 1 - else - echo "Found .changelog entry in PR!" - fi + run: ./.github/scripts/changelog_checker.sh + env: + GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref }} + GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 00000000000..b6451a378c3 --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,134 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +name: frontend + +on: + push: + branches: + - main + - ui/** + - backport/ui/** + +permissions: + contents: read + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + compute-small: ${{ steps.setup-outputs.outputs.compute-small }} + compute-medium: ${{ steps.setup-outputs.outputs.compute-medium }} + compute-large: ${{ steps.setup-outputs.outputs.compute-large }} + compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + - id: setup-outputs + name: Setup outputs + run: ./.github/scripts/get_runner_classes.sh + + workspace-tests: + needs: setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + defaults: + run: + working-directory: ui + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3.6.0 + with: + node-version: '16' + + - name: Install Yarn + run: npm install -g yarn + + # Install dependencies. + - name: install yarn packages + working-directory: ui + run: make deps + + - run: make test-workspace + + node-tests: + needs: setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3.6.0 + with: + node-version: '16' + + - name: Install Yarn + run: npm install -g yarn + + # Install dependencies. + - name: install yarn packages + working-directory: ui + run: make deps + + - run: make test-node + working-directory: ui/packages/consul-ui + + ember-build-test: + needs: setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} + env: + EMBER_TEST_REPORT: test-results/report-oss.xml # outputs test report for CircleCI test summary + EMBER_TEST_PARALLEL: true # enables test parallelization with ember-exam + CONSUL_NSPACES_ENABLED: ${{ endsWith(github.repository, '-enterprise') && 1 || 0 }} # NOTE: this should be 1 in ENT. + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + + - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3.6.0 + with: + node-version: '16' + + - name: Install Yarn + run: npm install -g yarn + + - name: Install Chrome + uses: browser-actions/setup-chrome@29abc1a83d1d71557708563b4bc962d0f983a376 # pin@v1.2.1 + + # Install dependencies. + - name: install yarn packages + working-directory: ui + run: make deps + + - working-directory: ui/packages/consul-ui + run: | + make build-ci + node_modules/.bin/ember exam --path dist --silent -r xunit + + - working-directory: ui/packages/consul-ui + run: make test-coverage-ci + + # This is job is required for branch protection as a required gihub check + # because GitHub actions show up as checks at the job level and not the + # workflow level. This is currently a feature request: + # https://github.com/orgs/community/discussions/12395 + # + # This job must: + # - be placed after the fanout of a workflow so that everything fans back in + # to this job. + # - "need" any job that is part of the fan out / fan in + # - implement the if logic because we have conditional jobs + # (go-test-enteprise) that this job needs and this would potentially get + # skipped if a previous job got skipped. So we use the if clause to make + # sure it does not get skipped. + + frontend-success: + needs: + - setup + - workspace-tests + - node-tests + - ember-build-test + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + if: | + (always() && ! cancelled()) && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') + steps: + - run: echo "frontend succeeded" diff --git a/.github/workflows/go-tests.yml b/.github/workflows/go-tests.yml new file mode 100644 index 00000000000..4d8f7b04bc4 --- /dev/null +++ b/.github/workflows/go-tests.yml @@ -0,0 +1,415 @@ +name: go-tests + +on: + pull_request: + branches-ignore: + - stable-website + - 'docs/**' + - 'ui/**' + - 'mktg-**' # Digital Team Terraform-generated branches' prefix + - 'backport/docs/**' + - 'backport/ui/**' + - 'backport/mktg-**' + push: + branches: + # Push events on the main branch + - main + - release/** + +permissions: + contents: read + +env: + TEST_RESULTS: /tmp/test-results + GOTESTSUM_VERSION: 1.8.2 + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + compute-small: ${{ steps.setup-outputs.outputs.compute-small }} + compute-medium: ${{ steps.setup-outputs.outputs.compute-medium }} + compute-large: ${{ steps.setup-outputs.outputs.compute-large }} + compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + - id: setup-outputs + name: Setup outputs + run: ./.github/scripts/get_runner_classes.sh + + check-go-mod: + needs: + - setup + uses: ./.github/workflows/reusable-check-go-mod.yml + with: + runs-on: ${{ needs.setup.outputs.compute-small }} + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + check-generated-protobuf: + needs: + - setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: make proto-tools + name: Install protobuf + - run: make proto-format + name: "Protobuf Format" + - run: make --always-make proto + - run: | + if ! git diff --exit-code; then + echo "Generated code was not updated correctly" + exit 1 + fi + - run: make proto-lint + name: "Protobuf Lint" + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh + check-generated-deep-copy: + needs: + - setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: make codegen-tools + name: Install deep-copy + - run: make --always-make deep-copy + - run: | + if ! git diff --exit-code; then + echo "Generated code was not updated correctly" + exit 1 + fi + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh + + lint-enums: + needs: + - setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: go install github.com/reillywatson/enumcover/cmd/enumcover@master && enumcover ./... + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh + + lint-container-test-deps: + needs: + - setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: make lint-container-test-deps + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh + + lint-consul-retry: + needs: + - setup + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: go install github.com/hashicorp/lint-consul-retry@master && lint-consul-retry + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh + + lint: + needs: + - setup + uses: ./.github/workflows/reusable-lint.yml + with: + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + lint-32bit: + needs: + - setup + uses: ./.github/workflows/reusable-lint.yml + with: + go-arch: "386" + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + # create a development build + dev-build: + needs: + - setup + uses: ./.github/workflows/reusable-dev-build.yml + with: + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + dev-build-arm64: + # only run on enterprise because GHA does not have arm64 runners in OSS + if: ${{ endsWith(github.repository, '-enterprise') }} + needs: + - setup + uses: ./.github/workflows/reusable-dev-build.yml + with: + uploaded-binary-name: 'consul-bin-arm64' + runs-on: ${{ needs.setup.outputs.compute-xl }} + go-arch: "arm64" + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + go-test-arm64: + # only run on enterprise because GHA does not have arm64 runners in OSS + if: ${{ endsWith(github.repository, '-enterprise') }} + needs: + - setup + - dev-build-arm64 + uses: ./.github/workflows/reusable-unit-split.yml + with: + directory: . + uploaded-binary-name: 'consul-bin-arm64' + runner-count: 12 + runs-on: "['self-hosted', 'ondemand', 'os=macos-arm', 'arm64']" + go-test-flags: 'if ! [[ "$GITHUB_REF_NAME" =~ ^main$|^release/ ]]; then export GO_TEST_FLAGS="-short"; fi' + repository-name: ${{ github.repository }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-oss: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit-split.yml + with: + directory: . + runner-count: 12 + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-enterprise: + if: ${{ endsWith(github.repository, '-enterprise') }} + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit-split.yml + with: + directory: . + runner-count: 12 + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-race: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: . + go-test-flags: 'GO_TEST_FLAGS="-race -gcflags=all=-d=checkptr=0"' + package-names-command: "go list ./... | grep -E -v '^github.com/hashicorp/consul/agent(/consul|/local|/routine-leak-checker)?$' | grep -E -v '^github.com/hashicorp/consul(/command|/connect|/snapshot)'" + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-32bit: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: . + go-arch: "386" + go-test-flags: 'export GO_TEST_FLAGS="-short"' + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-envoyextensions: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: envoyextensions + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-troubleshoot: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: troubleshoot + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-api-1-19: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: api + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-api-1-20: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: api + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-sdk-1-19: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: sdk + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + go-test-sdk-1-20: + needs: + - setup + - dev-build + uses: ./.github/workflows/reusable-unit.yml + with: + directory: sdk + runs-on: ${{ needs.setup.outputs.compute-xl }} + repository-name: ${{ github.repository }} + go-tags: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + consul-license: ${{secrets.CONSUL_LICENSE}} + + noop: + runs-on: ubuntu-latest + steps: + - run: "echo ok" + + # This is job is required for branch protection as a required gihub check + # because GitHub actions show up as checks at the job level and not the + # workflow level. This is currently a feature request: + # https://github.com/orgs/community/discussions/12395 + # + # This job must: + # - be placed after the fanout of a workflow so that everything fans back in + # to this job. + # - "need" any job that is part of the fan out / fan in + # - implement the if logic because we have conditional jobs + # (go-test-enteprise) that this job needs and this would potentially get + # skipped if a previous job got skipped. So we use the if clause to make + # sure it does not get skipped. + + go-tests-success: + needs: + - setup + - check-generated-deep-copy + - check-generated-protobuf + - check-go-mod + - lint-consul-retry + - lint-container-test-deps + - lint-enums + - lint + - lint-32bit + # - go-test-arm64 + - go-test-enterprise + - go-test-oss + - go-test-race + - go-test-envoyextensions + - go-test-troubleshoot + - go-test-api-1-19 + - go-test-api-1-20 + - go-test-sdk-1-19 + - go-test-sdk-1-20 + - go-test-32bit + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + if: | + (always() && ! cancelled()) && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') + steps: + - run: echo "go-tests succeeded" diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 17b7d26b1ff..be8eb77865b 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -41,6 +41,12 @@ jobs: if [[ -n ${ROLE} ]]; then echo "Actor ${{ github.actor }} is a ${TEAM} team member" echo "MESSAGE=true" >> $GITHUB_OUTPUT + elif [[ "${{ contains(github.actor, 'hc-github-team-consul-core') }}" == "true" ]]; then + echo "Actor ${{ github.actor }} is a ${TEAM} team member" + echo "MESSAGE=true" >> $GITHUB_OUTPUT + elif [[ "${{ contains(github.actor, 'dependabot') }}" == "true" ]]; then + echo "Actor ${{ github.actor }} is a ${TEAM} team member" + echo "MESSAGE=true" >> $GITHUB_OUTPUT else echo "Actor ${{ github.actor }} is NOT a ${TEAM} team member" echo "MESSAGE=false" >> $GITHUB_OUTPUT diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml deleted file mode 100644 index ab7d793e794..00000000000 --- a/.github/workflows/load-test.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Load Test - -on: - pull_request: - branches: - - main - types: [labeled] - workflow_dispatch: {} - -jobs: - trigger-load-test: - if: ${{ github.event.label.name == 'pr/load-test' }} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Trigger CircleCI Load Test Pipeline - run: | - # build json payload to trigger CircleCI Load Test Pipeline - # This only runs the load test pipeline on the 'main' branch - jsonData=$(jq --null-input -r --arg commit ${{ github.event.pull_request.head.sha }} \ - '{branch:"main", parameters: {"commit": $commit, "trigger-load-test": true}}') - echo "Passing JSON data to CircleCI API: $jsonData" - load_test_pipeline_id=$(curl -X POST \ - -H "Circle-Token: ${{ secrets.CIRCLE_TOKEN }}" \ - -H "Content-Type: application/json" \ - -d "$jsonData" \ - "https://circleci.com/api/v2/project/gh/${GITHUB_REPOSITORY}/pipeline" | jq -r '.id') - echo "LOAD_TEST_PIPELINE_ID=$load_test_pipeline_id" >> $GITHUB_ENV - - name: Post Load Test URL to PR - env: - PR_COMMENT_URL: ${{ github.event.pull_request.comments_url }} - run: | - echo "LOAD_TEST_PIPELINE_ID is: $LOAD_TEST_PIPELINE_ID" - # get load-test workflow - workflow= - # wait up to a minute for load-test workflow to start - until [ $SECONDS -ge 60 ] && exit 1; do - workflow=$(curl -s -X GET \ - -H "Circle-Token: ${{ secrets.CIRCLE_TOKEN }}" \ - -H "Content-Type: application/json" \ - "https://circleci.com/api/v2/pipeline/${LOAD_TEST_PIPELINE_ID}/workflow" | jq '.items[] | select(.name=="load-test")') - # if we found a workflow we exit - if [ -n "$workflow" ]; then - break - fi - echo -n "." - sleep 5 - done - echo "$workflow" - # get pipeline number - pipeline_number=$(echo "$workflow" | jq -r '.pipeline_number') - # get workflow id - workflow_id=$(echo "$workflow" | jq -r '.id') - # get project slug - project_slug=$(echo "$workflow" | jq -r '.project_slug') - # build load test URL - load_test_url="https://app.circleci.com/pipelines/${project_slug}/${pipeline_number}/workflows/${workflow_id}" - # comment URL to pull request - curl -X POST \ - -H "Authorization: token ${{ secrets.PR_COMMENT_TOKEN }}" \ - -H "Content-Type: application/json" \ - -d "{\"body\": \"Load Test Pipeline Started at: $load_test_url\"}" \ - "$PR_COMMENT_URL" diff --git a/.github/workflows/reusable-check-go-mod.yml b/.github/workflows/reusable-check-go-mod.yml new file mode 100644 index 00000000000..2078b0c3217 --- /dev/null +++ b/.github/workflows/reusable-check-go-mod.yml @@ -0,0 +1,38 @@ +name: check-go-mod + +on: + workflow_call: + inputs: + runs-on: + description: An expression indicating which kind of runners to use. + required: true + type: string + repository-name: + required: true + type: string + secrets: + elevated-github-token: + required: true +jobs: + check-go-mod: + runs-on: ${{ fromJSON(inputs.runs-on) }} + + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: go mod tidy + - run: | + if [[ -n $(git status -s) ]]; then + echo "Git directory has changes" + git status -s + exit 1 + fi + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh diff --git a/.github/workflows/reusable-dev-build.yml b/.github/workflows/reusable-dev-build.yml new file mode 100644 index 00000000000..36ce160bce9 --- /dev/null +++ b/.github/workflows/reusable-dev-build.yml @@ -0,0 +1,47 @@ +name: reusable-dev-build + +on: + workflow_call: + inputs: + uploaded-binary-name: + required: false + type: string + default: "consul-bin" + runs-on: + description: An expression indicating which kind of runners to use. + required: true + type: string + repository-name: + required: true + type: string + go-arch: + required: false + type: string + default: "" + secrets: + elevated-github-token: + required: true +jobs: + build: + runs-on: ${{ fromJSON(inputs.runs-on) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - name: Build + env: + GOARCH: ${{ inputs.goarch }} + run: make dev + # save dev build to pass to downstream jobs + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@v3.1.2 + with: + name: ${{inputs.uploaded-binary-name}} + path: ./bin/consul + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh diff --git a/.github/workflows/reusable-lint.yml b/.github/workflows/reusable-lint.yml new file mode 100644 index 00000000000..82650fd5e90 --- /dev/null +++ b/.github/workflows/reusable-lint.yml @@ -0,0 +1,56 @@ +name: reusable-lint + +on: + workflow_call: + inputs: + go-arch: + required: false + type: string + default: "" + runs-on: + description: An expression indicating which kind of runners to use. + required: true + type: string + repository-name: + required: true + type: string + secrets: + elevated-github-token: + required: true +env: + GOTAGS: "${{ github.event.repository.name == 'consul-enterprise' && 'consulent consulprem consuldev' || '' }}" + GOARCH: ${{inputs.go-arch}} + +jobs: + lint: + runs-on: ${{ fromJSON(inputs.runs-on) }} + strategy: + matrix: + directory: + - "" + - "api" + - "sdk" + - "envoyextensions" + - "troubleshoot" + - "test/integration/consul-container" + fail-fast: true + name: lint ${{ matrix.directory }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - run: go env + - name: lint-${{ matrix.directory }} + uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # pin@v3.4.0 + with: + working-directory: ${{ matrix.directory }} + version: v1.51 + args: --build-tags="${{ env.GOTAGS }}" -v + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh diff --git a/.github/workflows/reusable-unit-split.yml b/.github/workflows/reusable-unit-split.yml new file mode 100644 index 00000000000..0131582b0be --- /dev/null +++ b/.github/workflows/reusable-unit-split.yml @@ -0,0 +1,144 @@ +name: reusable-unit-split + +on: + workflow_call: + inputs: + directory: + required: true + type: string + runs-on: + description: An expression indicating which kind of runners to use. + required: true + type: string + go-arch: + required: false + type: string + default: "" + uploaded-binary-name: + required: false + type: string + default: "consul-bin" + args: + required: false + type: string + default: "" + runner-count: + required: false + type: number + default: 1 + go-test-flags: + required: false + type: string + default: "" + repository-name: + required: true + type: string + go-tags: + required: false + type: string + default: "" + secrets: + elevated-github-token: + required: true + consul-license: + required: true +env: + TEST_RESULTS: /tmp/test-results + GOTESTSUM_VERSION: 1.8.2 + GOARCH: ${{inputs.go-arch}} + TOTAL_RUNNERS: ${{inputs.runner-count}} + CONSUL_LICENSE: ${{secrets.consul-license}} + GOTAGS: ${{ inputs.go-tags}} + +jobs: + set-test-package-matrix: + runs-on: ubuntu-latest + outputs: + package-matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + - id: set-matrix + run: ./.github/scripts/set_test_package_matrix.sh ${{env.TOTAL_RUNNERS}} + + go-test: + runs-on: ${{ fromJSON(inputs.runs-on) }} + name: "go-test" + needs: + - set-test-package-matrix + strategy: + fail-fast: false + matrix: + package: ${{ fromJson(needs.set-test-package-matrix.outputs.package-matrix) }} + steps: + - name: ulimit + run: | + echo "Soft limits" + ulimit -Sa + echo "Hard limits" + ulimit -Ha + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + cache: true + - run: mkdir -p ${{env.TEST_RESULTS}} + - name: go mod download + working-directory: ${{inputs.directory}} + run: go mod download + - name: Download consul + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # pin@v3.0.2 + with: + name: ${{inputs.uploaded-binary-name}} + path: ${{inputs.directory}} + - name: Display downloaded file + run: ls -ld consul + working-directory: ${{inputs.directory}} + - run: echo "$GITHUB_WORKSPACE/${{inputs.directory}}" >> $GITHUB_PATH + - name: Make sure consul is executable + run: chmod +x $GITHUB_WORKSPACE/${{inputs.directory}}/consul + - run: go env + - name: Run tests + working-directory: ${{inputs.directory}} + run: | + # separate the list + PACKAGE_NAMES="${{ join(matrix.package, ' ') }}" + # PACKAGE_NAMES="${{ matrix.package }}" + + ${{inputs.go-test-flags}} + + # some tests expect this umask, and arm images have a different default + umask 0022 + + go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ + --format=short-verbose \ + --jsonfile /tmp/jsonfile/go-test.log \ + --debug \ + --rerun-fails=3 \ + --rerun-fails-max-failures=40 \ + --rerun-fails-report=/tmp/gotestsum-rerun-fails \ + --packages="$PACKAGE_NAMES" \ + --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- \ + -tags="${{env.GOTAGS}}" -p 2 \ + ${GO_TEST_FLAGS-} \ + -cover -coverprofile=coverage.txt + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@v3.1.2 + with: + name: test-results + path: ${{env.TEST_RESULTS}} + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@v3.1.2 + with: + name: jsonfile + path: /tmp/jsonfile + - name: "Re-run fails report" + run: | + .github/scripts/rerun_fails_report.sh /tmp/gotestsum-rerun-fails + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh diff --git a/.github/workflows/reusable-unit.yml b/.github/workflows/reusable-unit.yml new file mode 100644 index 00000000000..fde340bd5ed --- /dev/null +++ b/.github/workflows/reusable-unit.yml @@ -0,0 +1,112 @@ +name: reusable-unit + +on: + workflow_call: + inputs: + directory: + required: true + type: string + runs-on: + description: An expression indicating which kind of runners to use. + required: true + type: string + go-arch: + required: false + type: string + default: "" + uploaded-binary-name: + required: false + type: string + default: "consul-bin" + package-names-command: + required: false + type: string + default: 'go list -tags "$GOTAGS" ./...' + go-test-flags: + required: false + type: string + default: "" + repository-name: + required: true + type: string + go-tags: + required: false + type: string + default: "" + secrets: + elevated-github-token: + required: true + consul-license: + required: true +env: + TEST_RESULTS: /tmp/test-results + GOTESTSUM_VERSION: 1.8.2 + GOARCH: ${{inputs.go-arch}} + CONSUL_LICENSE: ${{secrets.consul-license}} + GOTAGS: ${{ inputs.go-tags}} + +jobs: + go-test: + runs-on: ${{ fromJSON(inputs.runs-on) }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0 + # 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@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3.5.0 + with: + go-version-file: 'go.mod' + cache: true + - run: mkdir -p ${{env.TEST_RESULTS}} + - name: go mod download + working-directory: ${{inputs.directory}} + run: go mod download + - name: Download consul + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # pin@v3.0.2 + with: + name: ${{inputs.uploaded-binary-name}} + path: ${{inputs.directory}} + - name: Display downloaded file + run: ls -ld consul + working-directory: ${{inputs.directory}} + - run: echo "$GITHUB_WORKSPACE/${{inputs.directory}}" >> $GITHUB_PATH + - name: Make sure consul is executable + run: chmod +x $GITHUB_WORKSPACE/${{inputs.directory}}/consul + - run: go env + - name: Run tests + working-directory: ${{inputs.directory}} + run: | + PACKAGE_NAMES=$(${{inputs.package-names-command}}) + + # some tests expect this umask, and arm images have a different default + umask 0022 + + ${{inputs.go-test-flags}} + + go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ + --format=short-verbose \ + --jsonfile /tmp/jsonfile/go-test.log \ + --debug \ + --rerun-fails=3 \ + --rerun-fails-max-failures=40 \ + --rerun-fails-report=/tmp/gotestsum-rerun-fails \ + --packages="$PACKAGE_NAMES" \ + --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- \ + -tags="${{env.GOTAGS}}" \ + ${GO_TEST_FLAGS-} \ + -cover -coverprofile=coverage.txt + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@v3.1.2 + with: + name: test-results + path: ${{env.TEST_RESULTS}} + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # pin@v3.1.2 + with: + name: jsonfile + path: /tmp/jsonfile + - name: "Re-run fails report" + run: | + .github/scripts/rerun_fails_report.sh /tmp/gotestsum-rerun-fails + - name: Notify Slack + if: ${{ failure() }} + run: .github/scripts/notify_slack.sh diff --git a/.github/workflows/verify-ci.yml b/.github/workflows/verify-ci.yml new file mode 100644 index 00000000000..4bd4536add1 --- /dev/null +++ b/.github/workflows/verify-ci.yml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# verify-ci is a no-op workflow that must run on every PR. It is used in a +# branch protection rule to detect when CI workflows are not running. +name: verify-ci + +permissions: + contents: read + +on: + pull_request: + push: + branches: + # Push events on the main branch + - main + - release/** + +jobs: + verify-ci-success: + runs-on: ubuntu-latest + steps: + - run: echo "verify-ci succeeded" diff --git a/.release/ci.hcl b/.release/ci.hcl index 084450dd4cd..25f64e4c6b7 100644 --- a/.release/ci.hcl +++ b/.release/ci.hcl @@ -38,6 +38,41 @@ event "prepare" { } } +## These are promotion and post-publish events +## they should be added to the end of the file after the verify event stanza. + +event "trigger-staging" { +// This event is dispatched by the bob trigger-promotion command +// and is required - do not delete. +} + +event "promote-staging" { + depends = ["trigger-staging"] + action "promote-staging" { + organization = "hashicorp" + repository = "crt-workflows-common" + workflow = "promote-staging" + config = "release-metadata.hcl" + } + + notification { + on = "always" + } +} + +event "promote-staging-docker" { + depends = ["promote-staging"] + action "promote-staging-docker" { + organization = "hashicorp" + repository = "crt-workflows-common" + workflow = "promote-staging-docker" + } + + notification { + on = "always" + } +} + event "trigger-production" { // This event is dispatched by the bob trigger-promotion command // and is required - do not delete. diff --git a/CHANGELOG.md b/CHANGELOG.md index 975050ecf3e..5b5259a054e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,145 @@ +## 1.15.2 (March 30, 2023) + +FEATURES: + +* xds: Allow for configuring connect proxies to send service mesh telemetry to an HCP metrics collection service. [[GH-16585](https://github.com/hashicorp/consul/issues/16585)] + +BUG FIXES: + +* audit-logging: (Enterprise only) Fix a bug where `/agent/monitor` and `/agent/metrics` endpoints return a `Streaming not supported` error when audit logs are enabled. This also fixes the delay receiving logs when running `consul monitor` against an agent with audit logs enabled. [[GH-16700](https://github.com/hashicorp/consul/issues/16700)] +* ca: Fixes a bug where updating Vault CA Provider config would cause TLS issues in the service mesh [[GH-16592](https://github.com/hashicorp/consul/issues/16592)] +* cache: revert cache refactor which could cause blocking queries to never return [[GH-16818](https://github.com/hashicorp/consul/issues/16818)] +* gateway: **(Enterprise only)** Fix bug where namespace/partition would fail to unmarshal for TCPServices. [[GH-16781](https://github.com/hashicorp/consul/issues/16781)] +* gateway: **(Enterprise only)** Fix bug where namespace/partition would fail to unmarshal. [[GH-16651](https://github.com/hashicorp/consul/issues/16651)] +* gateway: **(Enterprise only)** Fix bug where parent refs and service refs for a route in the same namespace as the route would fallback to the default namespace if the namespace was not specified in the configuration rather than falling back to the routes namespace. [[GH-16789](https://github.com/hashicorp/consul/issues/16789)] +* gateway: **(Enterprise only)** Fix bug where routes defined in a different namespace than a gateway would fail to register. [[GH-16677](https://github.com/hashicorp/consul/pull/16677)]. +* gateways: Adds validation to ensure the API Gateway has a listener defined when created [[GH-16649](https://github.com/hashicorp/consul/issues/16649)] +* gateways: Fixes a bug API gateways using HTTP listeners were taking upwards of 15 seconds to get configured over xDS. [[GH-16661](https://github.com/hashicorp/consul/issues/16661)] +* peering: **(Consul Enterprise only)** Fix issue where connect-enabled services with peer upstreams incorrectly required `service:write` access in the `default` namespace to query data, which was too restrictive. Now having `service:write` to any namespace is sufficient to query the peering data. +* peering: **(Consul Enterprise only)** Fix issue where resolvers, routers, and splitters referencing peer targets may not work correctly for non-default partitions and namespaces. Enterprise customers leveraging peering are encouraged to upgrade both servers and agents to avoid this problem. +* peering: Fix issue resulting in prepared query failover to cluster peers never un-failing over. [[GH-16729](https://github.com/hashicorp/consul/issues/16729)] +* peering: Fixes a bug that can lead to peering service deletes impacting the state of local services [[GH-16570](https://github.com/hashicorp/consul/issues/16570)] +* peering: Fixes a bug where the importing partition was not added to peered failover targets, which causes issues when the importing partition is a non-default partition. [[GH-16675](https://github.com/hashicorp/consul/issues/16675)] +* raft_logstore: Fixes a bug where restoring a snapshot when using the experimental WAL storage backend causes a panic. [[GH-16647](https://github.com/hashicorp/consul/issues/16647)] +* ui: fix PUT token request with adding missed AccessorID property to requestBody [[GH-16660](https://github.com/hashicorp/consul/issues/16660)] +* ui: fix rendering issues on Overview and empty-states by addressing isHTMLSafe errors [[GH-16574](https://github.com/hashicorp/consul/issues/16574)] + +## 1.15.1 (March 7, 2023) + +IMPROVEMENTS: + +* cli: added `-append-policy-id`, `-append-policy-name`, `-append-role-name`, and `-append-role-id` flags to the `consul token update` command. +These flags allow updates to a token's policies/roles without having to override them completely. [[GH-16288](https://github.com/hashicorp/consul/issues/16288)] +* cli: added `-append-service-identity` and `-append-node-identity` flags to the `consul token update` command. +These flags allow updates to a token's node identities/service identities without having to override them. [[GH-16506](https://github.com/hashicorp/consul/issues/16506)] +* connect: Bump Envoy 1.22.5 to 1.22.7, 1.23.2 to 1.23.4, 1.24.0 to 1.24.2, add 1.25.1, remove 1.21.5 [[GH-16274](https://github.com/hashicorp/consul/issues/16274)] +* mesh: Add ServiceResolver RequestTimeout for route timeouts to make request timeouts configurable [[GH-16495](https://github.com/hashicorp/consul/issues/16495)] +* ui: support filtering API gateways in the ui and displaying their documentation links [[GH-16508](https://github.com/hashicorp/consul/issues/16508)] + +DEPRECATIONS: + +* cli: Deprecate the `-merge-node-identites` and `-merge-service-identities` flags from the `consul token update` command in favor of: `-append-node-identity` and `-append-service-identity`. [[GH-16506](https://github.com/hashicorp/consul/issues/16506)] +* cli: Deprecate the `-merge-policies` and `-merge-roles` flags from the `consul token update` command in favor of: `-append-policy-id`, `-append-policy-name`, `-append-role-name`, and `-append-role-id`. [[GH-16288](https://github.com/hashicorp/consul/issues/16288)] + +BUG FIXES: + +* cli: Fixes an issue with `consul connect envoy` where a log to STDOUT could malform JSON when used with `-bootstrap`. [[GH-16530](https://github.com/hashicorp/consul/issues/16530)] +* cli: Fixes an issue with `consul connect envoy` where grpc-disabled agents were not error-handled correctly. [[GH-16530](https://github.com/hashicorp/consul/issues/16530)] +* cli: ensure acl token read -self works [[GH-16445](https://github.com/hashicorp/consul/issues/16445)] +* cli: fix panic read non-existent acl policy [[GH-16485](https://github.com/hashicorp/consul/issues/16485)] +* gateways: fix HTTPRoute bug where service weights could be less than or equal to 0 and result in a downstream envoy protocol error [[GH-16512](https://github.com/hashicorp/consul/issues/16512)] +* gateways: fix HTTPRoute bug where services with a weight not divisible by 10000 are never registered properly [[GH-16531](https://github.com/hashicorp/consul/issues/16531)] +* mesh: Fix resolution of service resolvers with subsets for external upstreams [[GH-16499](https://github.com/hashicorp/consul/issues/16499)] +* proxycfg: ensure that an irrecoverable error in proxycfg closes the xds session and triggers a replacement proxycfg watcher [[GH-16497](https://github.com/hashicorp/consul/issues/16497)] +* proxycfg: fix a bug where terminating gateways were not cleaning up deleted service resolvers for their referenced services [[GH-16498](https://github.com/hashicorp/consul/issues/16498)] +* ui: Fix issue with lists and filters not rendering properly [[GH-16444](https://github.com/hashicorp/consul/issues/16444)] + +## 1.15.0 (February 23, 2023) + +KNOWN ISSUES: + +* connect: An issue with leaf certificate rotation can cause some service instances to lose their ability to communicate in the mesh after 72 hours (LeafCertTTL). This issue is not consistently reproducible. We are working to address this issue in an upcoming patch release. To err on the side of caution, service mesh deployments should not upgrade to Consul v1.15 at this time. Refer to [[GH-16779](https://github.com/hashicorp/consul/issues/16779)] for the latest information. + +BREAKING CHANGES: + +* acl errors: Delete and get requests now return descriptive errors when the specified resource cannot be found. Other ACL request errors provide more information about when a resource is missing. Add error for when the ACL system has not been bootstrapped. + + Delete Token/Policy/AuthMethod/Role/BindingRule endpoints now return 404 when the resource cannot be found. + - New error formats: "Requested * does not exist: ACL not found", "* not found in namespace $NAMESPACE: ACL not found" + + Read Token/Policy/Role endpoints now return 404 when the resource cannot be found. + - New error format: "Cannot find * to delete" + + Logout now returns a 401 error when the supplied token cannot be found + - New error format: "Supplied token does not exist" + + Token Self endpoint now returns 404 when the token cannot be found. + - New error format: "Supplied token does not exist" [[GH-16105](https://github.com/hashicorp/consul/issues/16105)] +* acl: remove all acl migration functionality and references to the legacy acl system. [[GH-15947](https://github.com/hashicorp/consul/issues/15947)] +* acl: remove all functionality and references for legacy acl policies. [[GH-15922](https://github.com/hashicorp/consul/issues/15922)] +* config: Deprecate `-join`, `-join-wan`, `start_join`, and `start_join_wan`. +These options are now aliases of `-retry-join`, `-retry-join-wan`, `retry_join`, and `retry_join_wan`, respectively. [[GH-15598](https://github.com/hashicorp/consul/issues/15598)] +* connect: Add `peer` field to service-defaults upstream overrides. The addition of this field makes it possible to apply upstream overrides only to peer services. Prior to this change, overrides would be applied based on matching the `namespace` and `name` fields only, which means users could not have different configuration for local versus peer services. With this change, peer upstreams are only affected if the `peer` field matches the destination peer name. [[GH-15956](https://github.com/hashicorp/consul/issues/15956)] +* connect: Consul will now error and exit when using the `consul connect envoy` command if the Envoy version is incompatible. To ignore this check use flag `--ignore-envoy-compatibility` [[GH-15818](https://github.com/hashicorp/consul/issues/15818)] +* extensions: Refactor Lambda integration to get configured with the Envoy extensions field on service-defaults configuration entries. [[GH-15817](https://github.com/hashicorp/consul/issues/15817)] +* ingress-gateway: upstream cluster will have empty outlier_detection if passive health check is unspecified [[GH-15614](https://github.com/hashicorp/consul/issues/15614)] +* xds: Remove the `connect.enable_serverless_plugin` agent configuration option. Now +Lambda integration is enabled by default. [[GH-15710](https://github.com/hashicorp/consul/issues/15710)] + +SECURITY: + +* Upgrade to use Go 1.20.1. +This resolves vulnerabilities [CVE-2022-41724](https://go.dev/issue/58001) in `crypto/tls` and [CVE-2022-41723](https://go.dev/issue/57855) in `net/http`. [[GH-16263](https://github.com/hashicorp/consul/issues/16263)] + +FEATURES: + +* **API Gateway (Beta)** This version adds support for API gateway on VMs. API gateway provides a highly-configurable ingress for requests coming into a Consul network. For more information, refer to the [API gateway](https://developer.hashicorp.com/consul/docs/connect/gateways/api-gateway) documentation. [[GH-16369](https://github.com/hashicorp/consul/issues/16369)] +* acl: Add new `acl.tokens.config_file_registration` config field which specifies the token used +to register services and checks that are defined in config files. [[GH-15828](https://github.com/hashicorp/consul/issues/15828)] +* acl: anonymous token is logged as 'anonymous token' instead of its accessor ID [[GH-15884](https://github.com/hashicorp/consul/issues/15884)] +* cli: adds new CLI commands `consul troubleshoot upstreams` and `consul troubleshoot proxy` to troubleshoot Consul's service mesh configuration and network issues. [[GH-16284](https://github.com/hashicorp/consul/issues/16284)] +* command: Adds the `operator usage instances` subcommand for displaying total services, connect service instances and billable service instances in the local datacenter or globally. [[GH-16205](https://github.com/hashicorp/consul/issues/16205)] +* config-entry(ingress-gateway): support outlier detection (passive health check) for upstream cluster [[GH-15614](https://github.com/hashicorp/consul/issues/15614)] +* connect: adds support for Envoy [access logging](https://developer.hashicorp.com/consul/docs/connect/observability/access-logs). Access logging can be enabled using the [`proxy-defaults`](https://developer.hashicorp.com/consul/docs/connect/config-entries/proxy-defaults#accesslogs) config entry. [[GH-15864](https://github.com/hashicorp/consul/issues/15864)] +* xds: Add a built-in Envoy extension that inserts Lua HTTP filters. [[GH-15906](https://github.com/hashicorp/consul/issues/15906)] +* xds: Insert originator service identity into Envoy's dynamic metadata under the `consul` namespace. [[GH-15906](https://github.com/hashicorp/consul/issues/15906)] + +IMPROVEMENTS: + +* connect: for early awareness of Envoy incompatibilities, when using the `consul connect envoy` command the Envoy version will now be checked for compatibility. If incompatible Consul will error and exit. [[GH-15818](https://github.com/hashicorp/consul/issues/15818)] +* grpc: client agents will switch server on error, and automatically retry on `RESOURCE_EXHAUSTED` responses [[GH-15892](https://github.com/hashicorp/consul/issues/15892)] +* raft: add an operator api endpoint and a command to initiate raft leadership transfer. [[GH-14132](https://github.com/hashicorp/consul/issues/14132)] +* acl: Added option to allow for an operator-generated bootstrap token to be passed to the `acl bootstrap` command. [[GH-14437](https://github.com/hashicorp/consul/issues/14437)] +* agent: Give better error when client specifies wrong datacenter when auto-encrypt is enabled. [[GH-14832](https://github.com/hashicorp/consul/issues/14832)] +* api: updated the go module directive to 1.18. [[GH-15297](https://github.com/hashicorp/consul/issues/15297)] +* ca: support Vault agent auto-auth config for Vault CA provider using AWS/GCP authentication. [[GH-15970](https://github.com/hashicorp/consul/issues/15970)] +* cli: always use name "global" for proxy-defaults config entries [[GH-14833](https://github.com/hashicorp/consul/issues/14833)] +* cli: connect envoy command errors if grpc ports are not open [[GH-15794](https://github.com/hashicorp/consul/issues/15794)] +* client: add support for RemoveEmptyTags in Prepared Queries templates. [[GH-14244](https://github.com/hashicorp/consul/issues/14244)] +* connect: Warn if ACLs are enabled but a token is not provided to envoy [[GH-15967](https://github.com/hashicorp/consul/issues/15967)] +* container: Upgrade container image to use to Alpine 3.17. [[GH-16358](https://github.com/hashicorp/consul/issues/16358)] +* dns: support RFC 2782 SRV lookups for prepared queries using format `_._tcp.query[.].`. [[GH-14465](https://github.com/hashicorp/consul/issues/14465)] +* ingress-gateways: Don't log error when gateway is registered without a config entry [[GH-15001](https://github.com/hashicorp/consul/issues/15001)] +* licensing: **(Enterprise Only)** Consul Enterprise non-terminating production licenses do not degrade or terminate Consul upon expiration. They will only fail when trying to upgrade to a newer version of Consul. Evaluation licenses still terminate. +* raft: Added experimental `wal` backend for log storage. [[GH-16176](https://github.com/hashicorp/consul/issues/16176)] +* sdk: updated the go module directive to 1.18. [[GH-15297](https://github.com/hashicorp/consul/issues/15297)] +* telemetry: Added a `consul.xds.server.streamsUnauthenticated` metric to track +the number of active xDS streams handled by the server that are unauthenticated +because ACLs are not enabled or ACL tokens were missing. [[GH-15967](https://github.com/hashicorp/consul/issues/15967)] +* ui: Update sidebar width to 280px [[GH-16204](https://github.com/hashicorp/consul/issues/16204)] +* ui: update Ember version to 3.27; [[GH-16227](https://github.com/hashicorp/consul/issues/16227)] + +DEPRECATIONS: + +* acl: Deprecate the `token` query parameter and warn when it is used for authentication. [[GH-16009](https://github.com/hashicorp/consul/issues/16009)] +* cli: The `-id` flag on acl token operations has been changed to `-accessor-id` for clarity in documentation. The `-id` flag will continue to work, but operators should use `-accessor-id` in the future. [[GH-16044](https://github.com/hashicorp/consul/issues/16044)] + +BUG FIXES: + +* agent configuration: Fix issue of using unix socket when https is used. [[GH-16301](https://github.com/hashicorp/consul/issues/16301)] +* cache: refactor agent cache fetching to prevent unnecessary fetches on error [[GH-14956](https://github.com/hashicorp/consul/issues/14956)] +* cli: fatal error if config file does not have HCL or JSON extension, instead of warn and skip [[GH-15107](https://github.com/hashicorp/consul/issues/15107)] +* cli: fix ACL token processing unexpected precedence [[GH-15274](https://github.com/hashicorp/consul/issues/15274)] +* peering: Fix bug where services were incorrectly imported as connect-enabled. [[GH-16339](https://github.com/hashicorp/consul/issues/16339)] +* peering: Fix issue where mesh gateways would use the wrong address when contacting a remote peer with the same datacenter name. [[GH-16257](https://github.com/hashicorp/consul/issues/16257)] +* peering: Fix issue where secondary wan-federated datacenters could not be used as peering acceptors. [[GH-16230](https://github.com/hashicorp/consul/issues/16230)] + ## 1.14.4 (January 26, 2023) BREAKING CHANGES: diff --git a/Dockerfile b/Dockerfile index 4882f1b46d9..45bef496c23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,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.15 as official +FROM docker.mirror.hashicorp.services/alpine:3.17 as official # This is the release of Consul to pull in. ARG VERSION @@ -109,7 +109,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.15 as default +FROM docker.mirror.hashicorp.services/alpine:3.17 as default ARG PRODUCT_VERSION ARG BIN_NAME diff --git a/GNUmakefile b/GNUmakefile index f1cebb68955..ee765693f4a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -7,11 +7,11 @@ SHELL = bash # These version variables can either be a valid string for "go install @" # or the string @DEV to imply use what is currently installed locally. ### -GOLANGCI_LINT_VERSION='v1.50.1' -MOCKERY_VERSION='v2.15.0' +GOLANGCI_LINT_VERSION='v1.51.1' +MOCKERY_VERSION='v2.20.0' BUF_VERSION='v1.4.0' PROTOC_GEN_GO_GRPC_VERSION="v1.2.0" -MOG_VERSION='v0.3.0' +MOG_VERSION='v0.4.0' PROTOC_GO_INJECT_TAG_VERSION='v1.3.0' PROTOC_GEN_GO_BINARY_VERSION="v0.1.0" DEEP_COPY_VERSION='bc3f5aa5735d8a54961580a3a24422c308c831c2' diff --git a/agent/agent.go b/agent/agent.go index bff47a0fcf2..9c85f065ec2 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -721,11 +721,12 @@ func (a *Agent) Start(ctx context.Context) error { go localproxycfg.Sync( &lib.StopChannelContext{StopCh: a.shutdownCh}, localproxycfg.SyncConfig{ - Manager: a.proxyConfig, - State: a.State, - Logger: a.proxyConfig.Logger.Named("agent-state"), - Tokens: a.baseDeps.Tokens, - NodeName: a.config.NodeName, + Manager: a.proxyConfig, + State: a.State, + Logger: a.proxyConfig.Logger.Named("agent-state"), + Tokens: a.baseDeps.Tokens, + NodeName: a.config.NodeName, + ResyncFrequency: a.config.LocalProxyConfigResyncInterval, }, ) @@ -1051,7 +1052,8 @@ func (a *Agent) listenHTTP() ([]apiServer, error) { for _, l := range listeners { var tlscfg *tls.Config _, isTCP := l.(*tcpKeepAliveListener) - if isTCP && proto == "https" { + isUnix := l.Addr().Network() == "unix" + if (isTCP || isUnix) && proto == "https" { tlscfg = a.tlsConfigurator.IncomingHTTPSConfig() l = tls.NewListener(l, tlscfg) } diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index c9cfbee45cb..2c5ee9f3727 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -186,7 +186,7 @@ func TestAgent_Services_ExternalConnectProxy(t *testing.T) { Port: 5000, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "db", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, } a.State.AddServiceWithChecks(srv1, nil, "", false) @@ -226,7 +226,7 @@ func TestAgent_Services_Sidecar(t *testing.T) { LocallyRegisteredAsSidecar: true, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "db", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), Mode: structs.ProxyModeTransparent, TransparentProxy: structs.TransparentProxyConfig{ OutboundListenerPort: 10101, diff --git a/agent/agent_test.go b/agent/agent_test.go index d6dd2cc5fcf..55b161b5248 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -4,12 +4,13 @@ import ( "bytes" "context" "crypto/md5" + "crypto/rand" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/json" "fmt" - "math/rand" + mathrand "math/rand" "net" "net/http" "net/http/httptest" @@ -752,7 +753,7 @@ func testAgent_AddServices_AliasUpdateCheckNotReverted(t *testing.T, extraHCL st func test_createAlias(t *testing.T, agent *TestAgent, chk *structs.CheckType, expectedResult string) func(r *retry.R) { t.Helper() - serviceNum := rand.Int() + serviceNum := mathrand.Int() srv := &structs.NodeService{ Service: fmt.Sprintf("serviceAlias-%d", serviceNum), Tags: []string{"tag1"}, @@ -4818,19 +4819,19 @@ services { deadlineCh := time.After(10 * time.Second) start := time.Now() +LOOP: for { select { case evt := <-ch: // We may receive several notifications of an error until we get the // first successful reply. require.Equal(t, "foo", evt.CorrelationID) - if evt.Err == nil { - require.NoError(t, evt.Err) - require.NotNil(t, evt.Result) - t.Logf("took %s to get first success", time.Since(start)) - return + if evt.Err != nil { + break LOOP } - t.Logf("saw error: %v", evt.Err) + require.NoError(t, evt.Err) + require.NotNil(t, evt.Result) + t.Logf("took %s to get first success", time.Since(start)) case <-deadlineCh: t.Fatal("did not get notified successfully") } diff --git a/agent/cache/cache.go b/agent/cache/cache.go index 55b1654af26..ea537cc9e6f 100644 --- a/agent/cache/cache.go +++ b/agent/cache/cache.go @@ -84,8 +84,8 @@ var Counters = []prometheus.CounterDefinition{ // Constants related to refresh backoff. We probably don't ever need to // make these configurable knobs since they primarily exist to lower load. const ( - DefaultCacheRefreshBackoffMin = 3 // 3 attempts before backing off - DefaultCacheRefreshMaxWait = 1 * time.Minute // maximum backoff wait time + CacheRefreshBackoffMin = 3 // 3 attempts before backing off + CacheRefreshMaxWait = 1 * time.Minute // maximum backoff wait time // The following constants are default values for the cache entry // rate limiter settings. @@ -138,7 +138,10 @@ type Cache struct { entriesLock sync.RWMutex entries map[string]cacheEntry entriesExpiryHeap *ttlcache.ExpiryHeap - lastGoroutineID uint64 + + fetchLock sync.Mutex + lastFetchID uint64 + fetchHandles map[string]fetchHandle // stopped is used as an atomic flag to signal that the Cache has been // discarded so background fetches and expiry processing should stop. @@ -151,6 +154,11 @@ type Cache struct { rateLimitCancel context.CancelFunc } +type fetchHandle struct { + id uint64 + stopCh chan struct{} +} + // typeEntry is a single type that is registered with a Cache. type typeEntry struct { // Name that was used to register the Type @@ -196,13 +204,6 @@ type Options struct { EntryFetchMaxBurst int // EntryFetchRate represents the max calls/sec for a single cache entry EntryFetchRate rate.Limit - - // CacheRefreshBackoffMin is the number of attempts to wait before backing off. - // Mostly configurable just for testing. - CacheRefreshBackoffMin uint - // CacheRefreshMaxWait is the maximum backoff wait time. - // Mostly configurable just for testing. - CacheRefreshMaxWait time.Duration } // Equal return true if both options are equivalent @@ -218,12 +219,6 @@ func applyDefaultValuesOnOptions(options Options) Options { if options.EntryFetchMaxBurst == 0 { options.EntryFetchMaxBurst = DefaultEntryFetchMaxBurst } - if options.CacheRefreshBackoffMin == 0 { - options.CacheRefreshBackoffMin = DefaultCacheRefreshBackoffMin - } - if options.CacheRefreshMaxWait == 0 { - options.CacheRefreshMaxWait = DefaultCacheRefreshMaxWait - } if options.Logger == nil { options.Logger = hclog.New(nil) } @@ -239,6 +234,7 @@ func New(options Options) *Cache { types: make(map[string]typeEntry), entries: make(map[string]cacheEntry), entriesExpiryHeap: ttlcache.NewExpiryHeap(), + fetchHandles: make(map[string]fetchHandle), stopCh: make(chan struct{}), options: options, rateLimitContext: ctx, @@ -408,23 +404,11 @@ func (c *Cache) getEntryLocked( // Check if re-validate is requested. If so the first time round the // loop is not a hit but subsequent ones should be treated normally. if !tEntry.Opts.Refresh && info.MustRevalidate { - // It is important to note that this block ONLY applies when we are not - // in indefinite refresh mode (where the underlying goroutine will - // continue to re-query for data). - // - // In this mode goroutines have a 1:1 relationship to RPCs that get - // executed, and importantly they DO NOT SLEEP after executing. - // - // This means that a running goroutine for this cache entry extremely - // strongly implies that the RPC has not yet completed, which is why - // this check works for the revalidation-avoidance optimization here. - if entry.GoroutineID != 0 { - // There is an active goroutine performing a blocking query for - // this data, which has not returned. - // - // We can logically deduce that the contents of the cache are - // actually current, and we can simply return this while leaving - // the blocking query alone. + if entry.Fetching { + // There is an active blocking query for this data, which has not + // returned. We can logically deduce that the contents of the cache + // are actually current, and we can simply return this while + // leaving the blocking query alone. return true, true, entry } return true, false, entry @@ -554,7 +538,7 @@ RETRY_GET: // At this point, we know we either don't have a value at all or the // value we have is too old. We need to wait for new data. - waiterCh := c.fetch(key, r) + waiterCh := c.fetch(key, r, true, 0, false) // No longer our first time through first = false @@ -581,36 +565,46 @@ func makeEntryKey(t, dc, peerName, token, key string) string { return fmt.Sprintf("%s/%s/%s/%s", t, dc, token, key) } -// fetch triggers a new background fetch for the given Request. If a background -// fetch is already running or a goroutine to manage that still exists for a -// matching Request, the waiter channel for that request is returned. The -// effect of this is that there is only ever one blocking query and goroutine -// for any matching requests. -func (c *Cache) fetch(key string, r getOptions) <-chan struct{} { +// fetch triggers a new background fetch for the given Request. If a +// background fetch is already running for a matching Request, the waiter +// channel for that request is returned. The effect of this is that there +// is only ever one blocking query for any matching requests. +// +// If allowNew is true then the fetch should create the cache entry +// if it doesn't exist. If this is false, then fetch will do nothing +// if the entry doesn't exist. This latter case is to support refreshing. +func (c *Cache) fetch(key string, r getOptions, allowNew bool, attempt uint, ignoreExisting bool) <-chan struct{} { + // We acquire a write lock because we may have to set Fetching to true. c.entriesLock.Lock() defer c.entriesLock.Unlock() - ok, entryValid, entry := c.getEntryLocked(r.TypeEntry, key, r.Info) - switch { - case ok && entryValid: - // This handles the case where a fetch succeeded after checking for its - // existence in getWithIndex. This ensures that we don't miss updates. + // This handles the case where a fetch succeeded after checking for its existence in + // getWithIndex. This ensures that we don't miss updates. + if ok && entryValid && !ignoreExisting { ch := make(chan struct{}) close(ch) return ch + } - case ok && entry.GoroutineID != 0: - // If we already have an entry and there's a goroutine to keep it - // refreshed then don't spawn another one to do the same work. - // - // Return the currently active waiter. + // If we aren't allowing new values and we don't have an existing value, + // return immediately. We return an immediately-closed channel so nothing + // blocks. + if !ok && !allowNew { + ch := make(chan struct{}) + close(ch) + return ch + } + + // If we already have an entry and it is actively fetching, then return + // the currently active waiter. + if ok && entry.Fetching { return entry.Waiter + } - case !ok: - // If we don't have an entry, then create it. The entry must be marked - // as invalid so that it isn't returned as a valid value for a zero - // index. + // If we don't have an entry, then create it. The entry must be marked + // as invalid so that it isn't returned as a valid value for a zero index. + if !ok { entry = cacheEntry{ Valid: false, Waiter: make(chan struct{}), @@ -621,100 +615,27 @@ func (c *Cache) fetch(key string, r getOptions) <-chan struct{} { } } - // Assign each background fetching goroutine a unique ID and fingerprint - // the cache entry with the same ID. This way if the cache entry is ever - // cleaned up due to expiry and later recreated the old goroutine can - // detect that and terminate rather than leak and do double work. - c.lastGoroutineID++ - entry.GoroutineID = c.lastGoroutineID + // Set that we're fetching to true, which makes it so that future + // identical calls to fetch will return the same waiter rather than + // perform multiple fetches. + entry.Fetching = true c.entries[key] = entry metrics.SetGauge([]string{"consul", "cache", "entries_count"}, float32(len(c.entries))) metrics.SetGauge([]string{"cache", "entries_count"}, float32(len(c.entries))) - // The actual Fetch must be performed in a goroutine. - go c.launchBackgroundFetcher(entry.GoroutineID, key, r) - - return entry.Waiter -} - -func (c *Cache) launchBackgroundFetcher(goroutineID uint64, key string, r getOptions) { - defer func() { - c.entriesLock.Lock() - defer c.entriesLock.Unlock() - entry, ok := c.entries[key] - if ok && entry.GoroutineID == goroutineID { - entry.GoroutineID = 0 - c.entries[key] = entry - } - }() - - var attempt uint - for { - shouldStop, shouldBackoff := c.runBackgroundFetcherOnce(goroutineID, key, r) - if shouldStop { - return - } - - if shouldBackoff { - attempt++ - } else { - attempt = 0 - } - // If we're over the attempt minimum, start an exponential backoff. - wait := backOffWait(c.options, attempt) - - // If we have a timer, wait for it - wait += r.TypeEntry.Opts.RefreshTimer - - select { - case <-time.After(wait): - case <-c.stopCh: - return // Check if cache was stopped - } - - // Trigger. - r.Info.MustRevalidate = false - r.Info.MinIndex = 0 - - // We acquire a write lock because we may have to set Fetching to true. - c.entriesLock.Lock() - - entry, ok := c.entries[key] - if !ok || entry.GoroutineID != goroutineID { - // If we don't have an existing entry, return immediately. - // - // Also if we already have an entry and it is actively fetching, then - // return immediately. - // - // If we've somehow lost control of the entry, also return. - c.entriesLock.Unlock() - return - } - - c.entries[key] = entry - metrics.SetGauge([]string{"consul", "cache", "entries_count"}, float32(len(c.entries))) - metrics.SetGauge([]string{"cache", "entries_count"}, float32(len(c.entries))) - c.entriesLock.Unlock() - } -} - -func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOptions) (shouldStop, shouldBackoff bool) { - // Freshly re-read this, rather than relying upon the caller to fetch it - // and pass it in. - c.entriesLock.RLock() - entry, ok := c.entries[key] - c.entriesLock.RUnlock() + tEntry := r.TypeEntry - if !ok || entry.GoroutineID != goroutineID { - // If we don't have an existing entry, return immediately. - // - // Also if something weird has happened to orphan this goroutine, also - // return immediately. - return true, false - } + // The actual Fetch must be performed in a goroutine. Ensure that we only + // have one in-flight at a time, but don't use a deferred + // context.WithCancel style termination so that these things outlive their + // requester. + // + // By the time we get here the system WANTS to make a replacement fetcher, so + // we terminate the prior one and replace it. + handle := c.getOrReplaceFetchHandle(key) + go func(handle fetchHandle) { + defer c.deleteFetchHandle(key, handle.id) - tEntry := r.TypeEntry - { // NOTE: this indentation is here to facilitate the PR review diff only // If we have background refresh and currently are in "disconnected" state, // waiting for a response might mean we mark our results as stale for up to // 10 minutes (max blocking timeout) after connection is restored. To reduce @@ -728,7 +649,7 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp c.entriesLock.Lock() defer c.entriesLock.Unlock() entry, ok := c.entries[key] - if !ok || entry.RefreshLostContact.IsZero() || entry.GoroutineID != goroutineID { + if !ok || entry.RefreshLostContact.IsZero() { return } entry.RefreshLostContact = time.Time{} @@ -752,15 +673,12 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp Index: entry.Index, } } - if err := entry.FetchRateLimiter.Wait(c.rateLimitContext); err != nil { if connectedTimer != nil { connectedTimer.Stop() } entry.Error = fmt.Errorf("rateLimitContext canceled: %s", err.Error()) - // NOTE: this can only happen when the entire cache is being - // shutdown and isn't something that can happen normally. - return true, false + return } // Start building the new entry by blocking on the fetch. result, err := r.Fetch(fOpts) @@ -768,8 +686,17 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp connectedTimer.Stop() } + // If we were stopped while waiting on a blocking query now would be a + // good time to detect that. + select { + case <-handle.stopCh: + return + default: + } + // Copy the existing entry to start. newEntry := entry + newEntry.Fetching = false // Importantly, always reset the Error. Having both Error and a Value that // are non-nil is allowed in the cache entry but it indicates that the Error @@ -825,7 +752,7 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp if result.Index > 0 { // Reset the attempts counter so we don't have any backoff - shouldBackoff = false + attempt = 0 } else { // Result having a zero index is an implicit error case. There was no // actual error but it implies the RPC found in index (nothing written @@ -840,7 +767,7 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp // state it can be considered a bug in the RPC implementation (to ever // return a zero index) however since it can happen this is a safety net // for the future. - shouldBackoff = true + attempt++ } // If we have refresh active, this successful response means cache is now @@ -860,7 +787,7 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp metrics.IncrCounterWithLabels([]string{"cache", tEntry.Name, "fetch_error"}, 1, labels) // Increment attempt counter - shouldBackoff = true + attempt++ // If we are refreshing and just failed, updated the lost contact time as // our cache will be stale until we get successfully reconnected. We only @@ -877,7 +804,7 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp // Set our entry c.entriesLock.Lock() - if currEntry, ok := c.entries[key]; !ok || currEntry.GoroutineID != goroutineID { + if _, ok := c.entries[key]; !ok { // This entry was evicted during our fetch. DON'T re-insert it or fall // through to the refresh loop below otherwise it will live forever! In // theory there should not be any Get calls waiting on entry.Waiter since @@ -890,7 +817,7 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp // Trigger any waiters that are around. close(entry.Waiter) - return true, false + return } // If this is a new entry (not in the heap yet), then setup the @@ -915,22 +842,79 @@ func (c *Cache) runBackgroundFetcherOnce(goroutineID uint64, key string, r getOp // request back up again shortly but in the general case this prevents // spamming the logs with tons of ACL not found errors for days. if tEntry.Opts.Refresh && !preventRefresh { - return false, shouldBackoff + // Check if cache was stopped + if atomic.LoadUint32(&c.stopped) == 1 { + return + } + + // If we're over the attempt minimum, start an exponential backoff. + wait := backOffWait(attempt) + + // If we have a timer, wait for it + wait += tEntry.Opts.RefreshTimer + + select { + case <-time.After(wait): + case <-handle.stopCh: + return + } + + // Trigger. The "allowNew" field is false because in the time we were + // waiting to refresh we may have expired and got evicted. If that + // happened, we don't want to create a new entry. + r.Info.MustRevalidate = false + r.Info.MinIndex = 0 + c.fetch(key, r, false, attempt, true) } + }(handle) + + return entry.Waiter +} + +func (c *Cache) getOrReplaceFetchHandle(key string) fetchHandle { + c.fetchLock.Lock() + defer c.fetchLock.Unlock() + + if prevHandle, ok := c.fetchHandles[key]; ok { + close(prevHandle.stopCh) } - return true, false + c.lastFetchID++ + + handle := fetchHandle{ + id: c.lastFetchID, + stopCh: make(chan struct{}), + } + + c.fetchHandles[key] = handle + + return handle +} + +func (c *Cache) deleteFetchHandle(key string, fetchID uint64) { + c.fetchLock.Lock() + defer c.fetchLock.Unlock() + + // Only remove a fetchHandle if it's YOUR fetchHandle. + handle, ok := c.fetchHandles[key] + if !ok { + return + } + + if handle.id == fetchID { + delete(c.fetchHandles, key) + } } -func backOffWait(opts Options, failures uint) time.Duration { - if failures > opts.CacheRefreshBackoffMin { - shift := failures - opts.CacheRefreshBackoffMin - waitTime := opts.CacheRefreshMaxWait +func backOffWait(failures uint) time.Duration { + if failures > CacheRefreshBackoffMin { + shift := failures - CacheRefreshBackoffMin + waitTime := CacheRefreshMaxWait if shift < 31 { waitTime = (1 << shift) * time.Second } - if waitTime > opts.CacheRefreshMaxWait { - waitTime = opts.CacheRefreshMaxWait + if waitTime > CacheRefreshMaxWait { + waitTime = CacheRefreshMaxWait } return waitTime + lib.RandomStagger(waitTime) } diff --git a/agent/cache/cache_test.go b/agent/cache/cache_test.go index 98b04ee9a48..6f8805be06d 100644 --- a/agent/cache/cache_test.go +++ b/agent/cache/cache_test.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/lib/ttlcache" "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/sdk/testutil/retry" ) // Test a basic Get with no indexes (and therefore no blocking queries). @@ -1750,12 +1751,22 @@ func TestCache_RefreshLifeCycle(t *testing.T) { require.NoError(t, err) require.Equal(t, true, result) + waitUntilFetching := func(expectValue bool) { + retry.Run(t, func(t *retry.R) { + c.entriesLock.Lock() + defer c.entriesLock.Unlock() + entry, ok := c.entries[key] + require.True(t, ok) + if expectValue { + require.True(t, entry.Fetching) + } else { + require.False(t, entry.Fetching) + } + }) + } + // ensure that the entry is fetching again - c.entriesLock.Lock() - entry, ok := c.entries[key] - require.True(t, ok) - require.True(t, entry.GoroutineID > 0) - c.entriesLock.Unlock() + waitUntilFetching(true) requestChan := make(chan error) @@ -1789,11 +1800,7 @@ func TestCache_RefreshLifeCycle(t *testing.T) { } // ensure that the entry is fetching again - c.entriesLock.Lock() - entry, ok = c.entries[key] - require.True(t, ok) - require.True(t, entry.GoroutineID > 0) - c.entriesLock.Unlock() + waitUntilFetching(true) // background a call that will wait for a newer version - will result in an acl not found error go getError(5) @@ -1814,11 +1821,7 @@ func TestCache_RefreshLifeCycle(t *testing.T) { // ensure that the ACL not found error killed off the background refresh // but didn't remove it from the cache - c.entriesLock.Lock() - entry, ok = c.entries[key] - require.True(t, ok) - require.False(t, entry.GoroutineID > 0) - c.entriesLock.Unlock() + waitUntilFetching(false) } type fakeType struct { diff --git a/agent/cache/entry.go b/agent/cache/entry.go index 7130381dea4..0c71e944371 100644 --- a/agent/cache/entry.go +++ b/agent/cache/entry.go @@ -26,9 +26,9 @@ type cacheEntry struct { Index uint64 // Metadata that is used for internal accounting - Valid bool // True if the Value is set - GoroutineID uint64 // Nonzero if a fetch goroutine is running. - Waiter chan struct{} // Closed when this entry is invalidated + Valid bool // True if the Value is set + Fetching bool // True if a fetch is already active + Waiter chan struct{} // Closed when this entry is invalidated // Expiry contains information about the expiration of this // entry. This is a pointer as its shared as a value in the diff --git a/agent/cache/watch.go b/agent/cache/watch.go index d87bca38a54..abd247c4f1c 100644 --- a/agent/cache/watch.go +++ b/agent/cache/watch.go @@ -137,7 +137,7 @@ func (c *Cache) notifyBlockingQuery(ctx context.Context, r getOptions, correlati failures = 0 } else { failures++ - wait = backOffWait(c.options, failures) + wait = backOffWait(failures) c.options.Logger. With("error", err). @@ -224,7 +224,7 @@ func (c *Cache) notifyPollingQuery(ctx context.Context, r getOptions, correlatio // as this would eliminate the single-flighting of these requests in the cache and // the efficiencies gained by it. if failures > 0 { - wait = backOffWait(c.options, failures) + wait = backOffWait(failures) } else { // Calculate when the cached data's Age will get too stale and // need to be re-queried. When the data's Age already exceeds the diff --git a/agent/config/builder.go b/agent/config/builder.go index f682bf7b142..5d697b027ee 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -1091,6 +1091,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) { Watches: c.Watches, XDSUpdateRateLimit: limitVal(c.XDS.UpdateMaxPerSecond), AutoReloadConfigCoalesceInterval: 1 * time.Second, + LocalProxyConfigResyncInterval: 30 * time.Second, } rt.TLS, err = b.buildTLSConfig(rt, c.TLS) diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 627c1e56440..b0d9cf436e5 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -1475,6 +1475,10 @@ type RuntimeConfig struct { // AutoReloadConfigCoalesceInterval Coalesce Interval for auto reload config AutoReloadConfigCoalesceInterval time.Duration + // LocalProxyConfigResyncInterval is not a user-configurable value and exists + // here so that tests can use a smaller value. + LocalProxyConfigResyncInterval time.Duration + EnterpriseRuntimeConfig } diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 2954dee6700..af1f5085bb5 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -5995,12 +5995,13 @@ func TestLoad_FullConfig(t *testing.T) { nodeEntMeta := structs.NodeEnterpriseMetaInDefaultPartition() expected := &RuntimeConfig{ // non-user configurable values - AEInterval: time.Minute, - CheckDeregisterIntervalMin: time.Minute, - CheckReapInterval: 30 * time.Second, - SegmentNameLimit: 64, - SyncCoordinateIntervalMin: 15 * time.Second, - SyncCoordinateRateTarget: 64, + AEInterval: time.Minute, + CheckDeregisterIntervalMin: time.Minute, + CheckReapInterval: 30 * time.Second, + SegmentNameLimit: 64, + SyncCoordinateIntervalMin: 15 * time.Second, + SyncCoordinateRateTarget: 64, + LocalProxyConfigResyncInterval: 30 * time.Second, Revision: "JNtPSav3", Version: "R909Hblt", diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index 75d216fabdb..fff09fa343f 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -79,8 +79,6 @@ "BootstrapExpect": 0, "BuildDate": "2019-11-20 05:00:00 +0000 UTC", "Cache": { - "CacheRefreshBackoffMin": 0, - "CacheRefreshMaxWait": "0s", "EntryFetchMaxBurst": 42, "EntryFetchRate": 0.334, "Logger": null @@ -233,6 +231,7 @@ "KVMaxValueSize": 1234567800000000, "LeaveDrainTime": "0s", "LeaveOnTerm": false, + "LocalProxyConfigResyncInterval": "0s", "Logging": { "EnableSyslog": false, "LogFilePath": "", diff --git a/agent/consul/auto_config_endpoint_test.go b/agent/consul/auto_config_endpoint_test.go index ac9ea4128dd..1f0f8e18a1c 100644 --- a/agent/consul/auto_config_endpoint_test.go +++ b/agent/consul/auto_config_endpoint_test.go @@ -3,12 +3,11 @@ package consul import ( "bytes" "crypto" - crand "crypto/rand" + "crypto/rand" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" - "math/rand" "net" "net/url" "os" @@ -884,7 +883,7 @@ func TestAutoConfig_parseAutoConfigCSR(t *testing.T) { // customizations to allow for better unit testing. createCSR := func(tmpl *x509.CertificateRequest, privateKey crypto.Signer) (string, error) { connect.HackSANExtensionForCSR(tmpl) - bs, err := x509.CreateCertificateRequest(crand.Reader, tmpl, privateKey) + bs, err := x509.CreateCertificateRequest(rand.Reader, tmpl, privateKey) require.NoError(t, err) var csrBuf bytes.Buffer err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs}) diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index 7af98bc06a1..212c3d8fbd3 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -719,10 +719,21 @@ func (c *compiler) newTarget(opts structs.DiscoveryTargetOpts) *structs.Discover } else { // Don't allow Peer and Datacenter. opts.Datacenter = "" - // Peer and Partition cannot both be set. - opts.Partition = acl.PartitionOrDefault("") + // Since discovery targets (for peering) are ONLY used to query the catalog, and + // not to generate the SNI it is more correct to switch this to the calling-side + // of the peering's partition as that matches where the replicated data is stored + // in the catalog. This is done to simplify the usage of peer targets in both + // the xds and proxycfg packages. + // + // The peer info data attached to service instances will have the embedded opaque + // SNI/SAN information generated by the remote side and that will have the + // OTHER partition properly specified. + opts.Partition = acl.PartitionOrDefault(c.evaluateInPartition) // Default to "default" rather than c.evaluateInNamespace. - opts.Namespace = acl.PartitionOrDefault(opts.Namespace) + // Note that the namespace is not swapped out, because it should + // always match the value in the remote cluster (and shouldn't + // have been changed anywhere). + opts.Namespace = acl.NamespaceOrDefault(opts.Namespace) } t := structs.NewDiscoveryTarget(opts) @@ -978,6 +989,7 @@ RESOLVE_AGAIN: Default: resolver.IsDefault(), Target: target.ID, ConnectTimeout: connectTimeout, + RequestTimeout: resolver.RequestTimeout, }, LoadBalancer: resolver.LoadBalancer, } diff --git a/agent/consul/discoverychain/gateway.go b/agent/consul/discoverychain/gateway.go index cd582f1ec02..e834b7a1d49 100644 --- a/agent/consul/discoverychain/gateway.go +++ b/agent/consul/discoverychain/gateway.go @@ -5,6 +5,7 @@ import ( "hash/crc32" "sort" "strconv" + "strings" "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" @@ -17,6 +18,7 @@ type GatewayChainSynthesizer struct { trustDomain string suffix string gateway *structs.APIGatewayConfigEntry + hostname string matchesByHostname map[string][]hostnameMatch tcpRoutes []structs.TCPRouteConfigEntry } @@ -44,17 +46,17 @@ func (l *GatewayChainSynthesizer) AddTCPRoute(route structs.TCPRouteConfigEntry) l.tcpRoutes = append(l.tcpRoutes, route) } +// SetHostname sets the base hostname for a listener that this is being synthesized for +func (l *GatewayChainSynthesizer) SetHostname(hostname string) { + l.hostname = hostname +} + // AddHTTPRoute takes a new route and flattens its rule matches out per hostname. // This is required since a single route can specify multiple hostnames, and a // single hostname can be specified in multiple routes. Routing for a given // hostname must behave based on the aggregate of all rules that apply to it. func (l *GatewayChainSynthesizer) AddHTTPRoute(route structs.HTTPRouteConfigEntry) { - hostnames := route.Hostnames - if len(route.Hostnames) == 0 { - // add a wildcard if there are no explicit hostnames set - hostnames = append(hostnames, "*") - } - + hostnames := route.FilteredHostnames(l.hostname) for _, host := range hostnames { matches, ok := l.matchesByHostname[host] if !ok { @@ -125,6 +127,46 @@ func (l *GatewayChainSynthesizer) Synthesize(chains ...*structs.CompiledDiscover if err != nil { return nil, nil, err } + + node := compiled.Nodes[compiled.StartNode] + if node.IsRouter() { + resolverPrefix := structs.DiscoveryGraphNodeTypeResolver + ":" + node.Name + + // clean out the clusters that will get added for the router + for name := range compiled.Nodes { + if strings.HasPrefix(name, resolverPrefix) { + delete(compiled.Nodes, name) + } + } + + // clean out the route rules that'll get added for the router + filtered := []*structs.DiscoveryRoute{} + for _, route := range node.Routes { + if strings.HasPrefix(route.NextNode, resolverPrefix) { + continue + } + filtered = append(filtered, route) + } + node.Routes = filtered + } + compiled.Nodes[compiled.StartNode] = node + + // fix up the nodes for the terminal targets to either be a splitter or resolver if there is no splitter present + for name, node := range compiled.Nodes { + switch node.Type { + // we should only have these two types + case structs.DiscoveryGraphNodeTypeRouter: + for i, route := range node.Routes { + node.Routes[i].NextNode = targetForResolverNode(route.NextNode, chains) + } + case structs.DiscoveryGraphNodeTypeSplitter: + for i, split := range node.Splits { + node.Splits[i].NextNode = targetForResolverNode(split.NextNode, chains) + } + } + compiled.Nodes[name] = node + } + for _, c := range chains { for id, target := range c.Targets { compiled.Targets[id] = target @@ -176,6 +218,27 @@ func (l *GatewayChainSynthesizer) consolidateHTTPRoutes() []structs.HTTPRouteCon return routes } +func targetForResolverNode(nodeName string, chains []*structs.CompiledDiscoveryChain) string { + resolverPrefix := structs.DiscoveryGraphNodeTypeResolver + ":" + splitterPrefix := structs.DiscoveryGraphNodeTypeSplitter + ":" + + if !strings.HasPrefix(nodeName, resolverPrefix) { + return nodeName + } + + splitterName := splitterPrefix + strings.TrimPrefix(nodeName, resolverPrefix) + + for _, c := range chains { + for name, node := range c.Nodes { + if node.IsSplitter() && strings.HasPrefix(splitterName, name) { + return name + } + } + } + + return nodeName +} + func hostsKey(hosts ...string) string { sort.Strings(hosts) hostsHash := crc32.NewIEEE() diff --git a/agent/consul/discoverychain/gateway_httproute.go b/agent/consul/discoverychain/gateway_httproute.go index aaaec12e6b1..30968c9a4d5 100644 --- a/agent/consul/discoverychain/gateway_httproute.go +++ b/agent/consul/discoverychain/gateway_httproute.go @@ -77,15 +77,14 @@ func httpRouteToDiscoveryChain(route structs.HTTPRouteConfigEntry) (*structs.Ser for idx, rule := range route.Rules { modifier := httpRouteFiltersToServiceRouteHeaderModifier(rule.Filters.Headers) - prefixRewrite := httpRouteFiltersToDestinationPrefixRewrite(rule.Filters.URLRewrites) + prefixRewrite := httpRouteFiltersToDestinationPrefixRewrite(rule.Filters.URLRewrite) var destination structs.ServiceRouteDestination if len(rule.Services) == 1 { - // TODO open question: is there a use case where someone might want to set the rewrite to ""? service := rule.Services[0] - servicePrefixRewrite := httpRouteFiltersToDestinationPrefixRewrite(service.Filters.URLRewrites) - if servicePrefixRewrite == "" { + servicePrefixRewrite := httpRouteFiltersToDestinationPrefixRewrite(service.Filters.URLRewrite) + if service.Filters.URLRewrite == nil { servicePrefixRewrite = prefixRewrite } serviceModifier := httpRouteFiltersToServiceRouteHeaderModifier(service.Filters.Headers) @@ -176,13 +175,11 @@ func httpRouteToDiscoveryChain(route structs.HTTPRouteConfigEntry) (*structs.Ser return router, splitters, defaults } -func httpRouteFiltersToDestinationPrefixRewrite(rewrites []structs.URLRewrite) string { - for _, rewrite := range rewrites { - if rewrite.Path != "" { - return rewrite.Path - } +func httpRouteFiltersToDestinationPrefixRewrite(rewrite *structs.URLRewrite) string { + if rewrite == nil { + return "" } - return "" + return rewrite.Path } // httpRouteFiltersToServiceRouteHeaderModifier will consolidate a list of HTTP filters diff --git a/agent/consul/discoverychain/gateway_test.go b/agent/consul/discoverychain/gateway_test.go index 1d6ec78d24c..57d236afdc8 100644 --- a/agent/consul/discoverychain/gateway_test.go +++ b/agent/consul/discoverychain/gateway_test.go @@ -3,6 +3,7 @@ package discoverychain import ( "testing" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" "github.com/stretchr/testify/require" ) @@ -46,7 +47,7 @@ func TestGatewayChainSynthesizer_AddHTTPRoute(t *testing.T) { route structs.HTTPRouteConfigEntry expectedMatchesByHostname map[string][]hostnameMatch }{ - "no hostanames": { + "no hostnames": { route: structs.HTTPRouteConfigEntry{ Kind: structs.HTTPRoute, Name: "route", @@ -459,6 +460,7 @@ func TestGatewayChainSynthesizer_AddHTTPRoute(t *testing.T) { gatewayChainSynthesizer := NewGatewayChainSynthesizer(datacenter, "domain", "suffix", gateway) + gatewayChainSynthesizer.SetHostname("*") gatewayChainSynthesizer.AddHTTPRoute(tc.route) require.Equal(t, tc.expectedMatchesByHostname, gatewayChainSynthesizer.matchesByHostname) @@ -537,15 +539,6 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) { Protocol: "http", StartNode: "router:gateway-suffix-9b9265b.default.default", Nodes: map[string]*structs.DiscoveryGraphNode{ - "resolver:gateway-suffix-9b9265b.default.default.dc1": { - Type: "resolver", - Name: "gateway-suffix-9b9265b.default.default.dc1", - Resolver: &structs.DiscoveryResolver{ - Target: "gateway-suffix-9b9265b.default.default.dc1", - Default: true, - ConnectTimeout: 5000000000, - }, - }, "router:gateway-suffix-9b9265b.default.default": { Type: "router", Name: "gateway-suffix-9b9265b.default.default", @@ -567,20 +560,6 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) { }, }, NextNode: "resolver:foo.default.default.dc1", - }, { - Definition: &structs.ServiceRoute{ - Match: &structs.ServiceRouteMatch{ - HTTP: &structs.ServiceRouteHTTPMatch{ - PathPrefix: "/", - }, - }, - Destination: &structs.ServiceRouteDestination{ - Service: "gateway-suffix-9b9265b", - Partition: "default", - Namespace: "default", - }, - }, - NextNode: "resolver:gateway-suffix-9b9265b.default.default.dc1", }}, }, "resolver:foo.default.default.dc1": { @@ -621,6 +600,8 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { + tc.synthesizer.SetHostname("*") + for _, tcpRoute := range tc.tcpRoutes { tc.synthesizer.AddTCPRoute(*tcpRoute) } @@ -637,3 +618,233 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) { }) } } + +func TestGatewayChainSynthesizer_ComplexChain(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + synthesizer *GatewayChainSynthesizer + route *structs.HTTPRouteConfigEntry + entries []structs.ConfigEntry + expectedDiscoveryChain *structs.CompiledDiscoveryChain + }{ + "HTTP-Route with nested splitters": { + synthesizer: NewGatewayChainSynthesizer("dc1", "domain", "suffix", &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + }), + route: &structs.HTTPRouteConfigEntry{ + Kind: structs.HTTPRoute, + Name: "test", + Rules: []structs.HTTPRouteRule{{ + Services: []structs.HTTPService{{ + Name: "splitter-one", + }}, + }}, + }, + entries: []structs.ConfigEntry{ + &structs.ServiceSplitterConfigEntry{ + Kind: structs.ServiceSplitter, + Name: "splitter-one", + Splits: []structs.ServiceSplit{{ + Service: "service-one", + Weight: 50, + }, { + Service: "splitter-two", + Weight: 50, + }}, + }, + &structs.ServiceSplitterConfigEntry{ + Kind: structs.ServiceSplitter, + Name: "splitter-two", + Splits: []structs.ServiceSplit{{ + Service: "service-two", + Weight: 50, + }, { + Service: "service-three", + Weight: 50, + }}, + }, + &structs.ProxyConfigEntry{ + Kind: structs.ProxyConfigGlobal, + Name: "global", + Config: map[string]interface{}{ + "protocol": "http", + }, + }, + }, + expectedDiscoveryChain: &structs.CompiledDiscoveryChain{ + ServiceName: "gateway-suffix-9b9265b", + Namespace: "default", + Partition: "default", + Datacenter: "dc1", + Protocol: "http", + StartNode: "router:gateway-suffix-9b9265b.default.default", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:service-one.default.default.dc1": { + Type: "resolver", + Name: "service-one.default.default.dc1", + Resolver: &structs.DiscoveryResolver{ + Target: "service-one.default.default.dc1", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + "resolver:service-three.default.default.dc1": { + Type: "resolver", + Name: "service-three.default.default.dc1", + Resolver: &structs.DiscoveryResolver{ + Target: "service-three.default.default.dc1", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + "resolver:service-two.default.default.dc1": { + Type: "resolver", + Name: "service-two.default.default.dc1", + Resolver: &structs.DiscoveryResolver{ + Target: "service-two.default.default.dc1", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + "resolver:splitter-one.default.default.dc1": { + Type: "resolver", + Name: "splitter-one.default.default.dc1", + Resolver: &structs.DiscoveryResolver{ + Target: "splitter-one.default.default.dc1", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + "router:gateway-suffix-9b9265b.default.default": { + Type: "router", + Name: "gateway-suffix-9b9265b.default.default", + Routes: []*structs.DiscoveryRoute{{ + Definition: &structs.ServiceRoute{ + Match: &structs.ServiceRouteMatch{ + HTTP: &structs.ServiceRouteHTTPMatch{ + PathPrefix: "/", + }, + }, + Destination: &structs.ServiceRouteDestination{ + Service: "splitter-one", + Partition: "default", + Namespace: "default", + RequestHeaders: &structs.HTTPHeaderModifiers{ + Add: make(map[string]string), + Set: make(map[string]string), + }, + }, + }, + NextNode: "splitter:splitter-one.default.default", + }}, + }, + "splitter:splitter-one.default.default": { + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "splitter-one.default.default", + Splits: []*structs.DiscoverySplit{{ + Definition: &structs.ServiceSplit{ + Weight: 50, + Service: "service-one", + }, + Weight: 50, + NextNode: "resolver:service-one.default.default.dc1", + }, { + Definition: &structs.ServiceSplit{ + Weight: 50, + Service: "service-two", + }, + Weight: 25, + NextNode: "resolver:service-two.default.default.dc1", + }, { + Definition: &structs.ServiceSplit{ + Weight: 50, + Service: "service-three", + }, + Weight: 25, + NextNode: "resolver:service-three.default.default.dc1", + }}, + }, + }, Targets: map[string]*structs.DiscoveryTarget{ + "gateway-suffix-9b9265b.default.default.dc1": { + ID: "gateway-suffix-9b9265b.default.default.dc1", + Service: "gateway-suffix-9b9265b", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "gateway-suffix-9b9265b.default.dc1.internal.domain", + Name: "gateway-suffix-9b9265b.default.dc1.internal.domain", + }, + "service-one.default.default.dc1": { + ID: "service-one.default.default.dc1", + Service: "service-one", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "service-one.default.dc1.internal.domain", + Name: "service-one.default.dc1.internal.domain", + }, + "service-three.default.default.dc1": { + ID: "service-three.default.default.dc1", + Service: "service-three", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "service-three.default.dc1.internal.domain", + Name: "service-three.default.dc1.internal.domain", + }, + "service-two.default.default.dc1": { + ID: "service-two.default.default.dc1", + Service: "service-two", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "service-two.default.dc1.internal.domain", + Name: "service-two.default.dc1.internal.domain", + }, + "splitter-one.default.default.dc1": { + ID: "splitter-one.default.default.dc1", + Service: "splitter-one", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "splitter-one.default.dc1.internal.domain", + Name: "splitter-one.default.dc1.internal.domain", + }, + }}, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + service := tc.entries[0] + entries := configentry.NewDiscoveryChainSet() + entries.AddEntries(tc.entries...) + compiled, err := Compile(CompileRequest{ + ServiceName: service.GetName(), + EvaluateInNamespace: service.GetEnterpriseMeta().NamespaceOrDefault(), + EvaluateInPartition: service.GetEnterpriseMeta().PartitionOrDefault(), + EvaluateInDatacenter: "dc1", + EvaluateInTrustDomain: "domain", + Entries: entries, + }) + require.NoError(t, err) + + tc.synthesizer.SetHostname("*") + tc.synthesizer.AddHTTPRoute(*tc.route) + + chains := []*structs.CompiledDiscoveryChain{compiled} + _, discoveryChains, err := tc.synthesizer.Synthesize(chains...) + + require.NoError(t, err) + require.Len(t, discoveryChains, 1) + require.Equal(t, tc.expectedDiscoveryChain, discoveryChains[0]) + }) + } +} diff --git a/agent/consul/gateways/controller_gateways.go b/agent/consul/gateways/controller_gateways.go index 53d8c9a888c..cfc5a25ba7e 100644 --- a/agent/consul/gateways/controller_gateways.go +++ b/agent/consul/gateways/controller_gateways.go @@ -71,7 +71,7 @@ func (r *apiGatewayReconciler) Reconcile(ctx context.Context, req controller.Req func reconcileEntry[T structs.ControlledConfigEntry](store *state.Store, logger hclog.Logger, ctx context.Context, req controller.Request, reconciler func(ctx context.Context, req controller.Request, store *state.Store, entry T) error, cleaner func(ctx context.Context, req controller.Request, store *state.Store) error) error { _, entry, err := store.ConfigEntry(nil, req.Kind, req.Name, req.Meta) if err != nil { - requestLogger(logger, req).Error("error fetching config entry for reconciliation request", "error", err) + requestLogger(logger, req).Warn("error fetching config entry for reconciliation request", "error", err) return err } @@ -87,12 +87,12 @@ func reconcileEntry[T structs.ControlledConfigEntry](store *state.Store, logger func (r *apiGatewayReconciler) enqueueCertificateReferencedGateways(store *state.Store, _ context.Context, req controller.Request) error { logger := certificateRequestLogger(r.logger, req) - logger.Debug("certificate changed, enqueueing dependent gateways") - defer logger.Debug("finished enqueuing gateways") + logger.Trace("certificate changed, enqueueing dependent gateways") + defer logger.Trace("finished enqueuing gateways") _, entries, err := store.ConfigEntriesByKind(nil, structs.APIGateway, acl.WildcardEnterpriseMeta()) if err != nil { - logger.Error("error retrieving api gateways", "error", err) + logger.Warn("error retrieving api gateways", "error", err) return err } @@ -127,12 +127,12 @@ func (r *apiGatewayReconciler) enqueueCertificateReferencedGateways(store *state func (r *apiGatewayReconciler) cleanupBoundGateway(_ context.Context, req controller.Request, store *state.Store) error { logger := gatewayRequestLogger(r.logger, req) - logger.Debug("cleaning up bound gateway") - defer logger.Debug("finished cleaning up bound gateway") + logger.Trace("cleaning up bound gateway") + defer logger.Trace("finished cleaning up bound gateway") routes, err := retrieveAllRoutesFromStore(store) if err != nil { - logger.Error("error retrieving routes", "error", err) + logger.Warn("error retrieving routes", "error", err) return err } @@ -141,9 +141,9 @@ func (r *apiGatewayReconciler) cleanupBoundGateway(_ context.Context, req contro for _, modifiedRoute := range removeGateway(resource, routes...) { routeLogger := routeLogger(logger, modifiedRoute) - routeLogger.Debug("persisting route status") + routeLogger.Trace("persisting route status") if err := r.updater.Update(modifiedRoute); err != nil { - routeLogger.Error("error removing gateway from route", "error", err) + routeLogger.Warn("error removing gateway from route", "error", err) return err } } @@ -156,20 +156,20 @@ func (r *apiGatewayReconciler) cleanupBoundGateway(_ context.Context, req contro func (r *apiGatewayReconciler) reconcileBoundGateway(_ context.Context, req controller.Request, store *state.Store, bound *structs.BoundAPIGatewayConfigEntry) error { logger := gatewayRequestLogger(r.logger, req) - logger.Debug("reconciling bound gateway") - defer logger.Debug("finished reconciling bound gateway") + logger.Trace("reconciling bound gateway") + defer logger.Trace("finished reconciling bound gateway") _, gateway, err := store.ConfigEntry(nil, structs.APIGateway, req.Name, req.Meta) if err != nil { - logger.Error("error retrieving api gateway", "error", err) + logger.Warn("error retrieving api gateway", "error", err) return err } if gateway == nil { // delete the bound gateway - logger.Debug("deleting bound api gateway") + logger.Trace("deleting bound api gateway") if err := r.updater.Delete(bound); err != nil { - logger.Error("error deleting bound api gateway", "error", err) + logger.Warn("error deleting bound api gateway", "error", err) return err } } @@ -183,18 +183,18 @@ func (r *apiGatewayReconciler) reconcileBoundGateway(_ context.Context, req cont func (r *apiGatewayReconciler) cleanupGateway(_ context.Context, req controller.Request, store *state.Store) error { logger := gatewayRequestLogger(r.logger, req) - logger.Debug("cleaning up deleted gateway") - defer logger.Debug("finished cleaning up deleted gateway") + logger.Trace("cleaning up deleted gateway") + defer logger.Trace("finished cleaning up deleted gateway") _, bound, err := store.ConfigEntry(nil, structs.BoundAPIGateway, req.Name, req.Meta) if err != nil { - logger.Error("error retrieving bound api gateway", "error", err) + logger.Warn("error retrieving bound api gateway", "error", err) return err } - logger.Debug("deleting bound api gateway") + logger.Trace("deleting bound api gateway") if err := r.updater.Delete(bound); err != nil { - logger.Error("error deleting bound api gateway", "error", err) + logger.Warn("error deleting bound api gateway", "error", err) return err } @@ -212,8 +212,8 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle logger := gatewayRequestLogger(r.logger, req) - logger.Debug("started reconciling gateway") - defer logger.Debug("finished reconciling gateway") + logger.Trace("started reconciling gateway") + defer logger.Trace("finished reconciling gateway") updater := structs.NewStatusUpdater(gateway) // we clear out the initial status conditions since we're doing a full update @@ -222,21 +222,21 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle routes, err := retrieveAllRoutesFromStore(store) if err != nil { - logger.Error("error retrieving routes", "error", err) + logger.Warn("error retrieving routes", "error", err) return err } // construct the tuple we'll be working on to update state _, bound, err := store.ConfigEntry(nil, structs.BoundAPIGateway, req.Name, req.Meta) if err != nil { - logger.Error("error retrieving bound api gateway", "error", err) + logger.Warn("error retrieving bound api gateway", "error", err) return err } meta := newGatewayMeta(gateway, bound) certificateErrors, err := meta.checkCertificates(store) if err != nil { - logger.Error("error checking gateway certificates", "error", err) + logger.Warn("error checking gateway certificates", "error", err) return err } @@ -286,9 +286,9 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle // now check if we need to update the gateway status if modifiedGateway, shouldUpdate := updater.UpdateEntry(); shouldUpdate { - logger.Debug("persisting gateway status") + logger.Trace("persisting gateway status") if err := r.updater.UpdateWithStatus(modifiedGateway); err != nil { - logger.Error("error persisting gateway status", "error", err) + logger.Warn("error persisting gateway status", "error", err) return err } } @@ -296,18 +296,18 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle // next update route statuses for _, modifiedRoute := range updatedRoutes { routeLogger := routeLogger(logger, modifiedRoute) - routeLogger.Debug("persisting route status") + routeLogger.Trace("persisting route status") if err := r.updater.UpdateWithStatus(modifiedRoute); err != nil { - routeLogger.Error("error persisting route status", "error", err) + routeLogger.Warn("error persisting route status", "error", err) return err } } // now update the bound state if it changed if bound == nil || !bound.(*structs.BoundAPIGatewayConfigEntry).IsSame(meta.BoundGateway) { - logger.Debug("persisting bound api gateway") + logger.Trace("persisting bound api gateway") if err := r.updater.Update(meta.BoundGateway); err != nil { - logger.Error("error persisting bound api gateway", "error", err) + logger.Warn("error persisting bound api gateway", "error", err) return err } } @@ -320,20 +320,20 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle func (r *apiGatewayReconciler) cleanupRoute(_ context.Context, req controller.Request, store *state.Store) error { logger := routeRequestLogger(r.logger, req) - logger.Debug("cleaning up route") - defer logger.Debug("finished cleaning up route") + logger.Trace("cleaning up route") + defer logger.Trace("finished cleaning up route") meta, err := getAllGatewayMeta(store) if err != nil { - logger.Error("error retrieving gateways", "error", err) + logger.Warn("error retrieving gateways", "error", err) return err } for _, modifiedGateway := range removeRoute(requestToResourceRef(req), meta...) { gatewayLogger := gatewayLogger(logger, modifiedGateway.BoundGateway) - gatewayLogger.Debug("persisting bound gateway state") + gatewayLogger.Trace("persisting bound gateway state") if err := r.updater.Update(modifiedGateway.BoundGateway); err != nil { - gatewayLogger.Error("error updating bound api gateway", "error", err) + gatewayLogger.Warn("error updating bound api gateway", "error", err) return err } } @@ -355,12 +355,12 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. logger := routeRequestLogger(r.logger, req) - logger.Debug("reconciling route") - defer logger.Debug("finished reconciling route") + logger.Trace("reconciling route") + defer logger.Trace("finished reconciling route") meta, err := getAllGatewayMeta(store) if err != nil { - logger.Error("error retrieving gateways", "error", err) + logger.Warn("error retrieving gateways", "error", err) return err } @@ -378,9 +378,9 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. modifiedGateway, shouldUpdate := gateway.checkConflicts() if shouldUpdate { gatewayLogger := gatewayLogger(logger, modifiedGateway) - gatewayLogger.Debug("persisting gateway status") + gatewayLogger.Trace("persisting gateway status") if err := r.updater.UpdateWithStatus(modifiedGateway); err != nil { - gatewayLogger.Error("error persisting gateway", "error", err) + gatewayLogger.Warn("error persisting gateway", "error", err) return err } } @@ -388,9 +388,9 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. // next update the route status if modifiedRoute, shouldUpdate := updater.UpdateEntry(); shouldUpdate { - r.logger.Debug("persisting route status") + r.logger.Trace("persisting route status") if err := r.updater.UpdateWithStatus(modifiedRoute); err != nil { - r.logger.Error("error persisting route", "error", err) + r.logger.Warn("error persisting route", "error", err) return err } } @@ -398,9 +398,9 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. // now update all of the bound gateways that have been modified for _, bound := range modifiedGateways { gatewayLogger := gatewayLogger(logger, bound) - gatewayLogger.Debug("persisting bound api gateway") + gatewayLogger.Trace("persisting bound api gateway") if err := r.updater.Update(bound); err != nil { - gatewayLogger.Error("error persisting bound api gateway", "error", err) + gatewayLogger.Warn("error persisting bound api gateway", "error", err) return err } } @@ -409,11 +409,10 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. } var triggerOnce sync.Once - validTargets := true for _, service := range route.GetServiceNames() { _, chainSet, err := store.ReadDiscoveryChainConfigEntries(ws, service.Name, pointerTo(service.EnterpriseMeta)) if err != nil { - logger.Error("error reading discovery chain", "error", err) + logger.Warn("error reading discovery chain", "error", err) return err } @@ -422,11 +421,6 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. r.controller.AddTrigger(req, ws.WatchCtx) }) - if chainSet.IsEmpty() { - updater.SetCondition(conditions.routeInvalidDiscoveryChain(errServiceDoesNotExist)) - continue - } - // make sure that we can actually compile a discovery chain based on this route // the main check is to make sure that all of the protocols align chain, err := discoverychain.Compile(discoverychain.CompileRequest{ @@ -438,28 +432,16 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. Entries: chainSet, }) if err != nil { - // we only really need to return the first error for an invalid - // discovery chain, but we still want to set watches on everything in the - // store - if validTargets { - updater.SetCondition(conditions.routeInvalidDiscoveryChain(err)) - validTargets = false - } + updater.SetCondition(conditions.routeInvalidDiscoveryChain(err)) continue } if chain.Protocol != string(route.GetProtocol()) { - if validTargets { - updater.SetCondition(conditions.routeInvalidDiscoveryChain(errInvalidProtocol)) - validTargets = false - } + updater.SetCondition(conditions.routeInvalidDiscoveryChain(errInvalidProtocol)) continue } - // this makes sure we don't override an already set status - if validTargets { - updater.SetCondition(conditions.routeAccepted()) - } + updater.SetCondition(conditions.routeAccepted()) } // if we have no upstream targets, then set the route as invalid @@ -467,21 +449,10 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. // we'll do it here too just in case if len(route.GetServiceNames()) == 0 { updater.SetCondition(conditions.routeNoUpstreams()) - validTargets = false - } - - if !validTargets { - // we return early, but need to make sure we're removed from all referencing - // gateways and our status is updated properly - updated := []*structs.BoundAPIGatewayConfigEntry{} - for _, modifiedGateway := range removeRoute(requestToResourceRef(req), meta...) { - updated = append(updated, modifiedGateway.BoundGateway) - } - return finalize(updated) } // the route is valid, attempt to bind it to all gateways - r.logger.Debug("binding routes to gateway") + r.logger.Trace("binding routes to gateway") modifiedGateways, boundRefs, bindErrors := bindRoutesToGateways(route, meta...) // set the status of the references that are bound @@ -575,6 +546,8 @@ type gatewayMeta struct { // the map values are pointers so that we can update them directly // and have the changes propagate back to the container gateways. boundListeners map[string]*structs.BoundAPIGatewayListener + + generator *gatewayConditionGenerator } // getAllGatewayMeta returns a pre-constructed list of all valid gateway and state @@ -701,6 +674,25 @@ func (g *gatewayMeta) bindRoute(listener *structs.APIGatewayListener, bound *str return false, nil } + // check to make sure we're not binding to an invalid gateway + if !g.Gateway.Status.MatchesConditionStatus(g.generator.gatewayAccepted()) { + return false, fmt.Errorf("failed to bind route to gateway %s: gateway has not been accepted", g.Gateway.Name) + } + + // check to make sure we're not binding to an invalid route + status := route.GetStatus() + if !status.MatchesConditionStatus(g.generator.routeAccepted()) { + return false, fmt.Errorf("failed to bind route to gateway %s: route has not been accepted", g.Gateway.Name) + } + + if route, ok := route.(*structs.HTTPRouteConfigEntry); ok { + // check our hostnames + hostnames := route.FilteredHostnames(listener.GetHostname()) + if len(hostnames) == 0 { + return false, fmt.Errorf("failed to bind route to gateway %s: listener %s is does not have any hostnames that match the route", g.Gateway.Name, listener.Name) + } + } + if listener.Protocol == route.GetProtocol() && bound.BindRoute(structs.ResourceReference{ Kind: route.GetKind(), Name: route.GetName(), @@ -801,6 +793,8 @@ func (g *gatewayMeta) setConflicts(updater *structs.StatusUpdater) { // initialize sets up the listener maps that we use for quickly indexing the listeners in our binding logic func (g *gatewayMeta) initialize() *gatewayMeta { + g.generator = newGatewayConditionGenerator() + // set up the maps for fast access g.boundListeners = make(map[string]*structs.BoundAPIGatewayListener, len(g.BoundGateway.Listeners)) for i, listener := range g.BoundGateway.Listeners { diff --git a/agent/consul/gateways/controller_gateways_test.go b/agent/consul/gateways/controller_gateways_test.go index 0c47809c763..805a5738ce6 100644 --- a/agent/consul/gateways/controller_gateways_test.go +++ b/agent/consul/gateways/controller_gateways_test.go @@ -49,6 +49,11 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, route: &structs.TCPRouteConfigEntry{ @@ -61,6 +66,11 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { SectionName: "Listener", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{ Kind: structs.BoundAPIGateway, @@ -116,6 +126,11 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, route: &structs.TCPRouteConfigEntry{ @@ -127,6 +142,11 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { Name: "Gateway", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{ Kind: structs.BoundAPIGateway, @@ -417,6 +437,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -431,6 +456,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -521,6 +551,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, { @@ -541,6 +576,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -560,6 +600,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -624,6 +669,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -638,6 +688,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener 2", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -691,6 +746,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -705,6 +765,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -770,6 +835,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -784,6 +854,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -843,6 +918,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, { @@ -871,6 +951,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -890,6 +975,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener 2", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -968,6 +1058,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -982,6 +1077,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener 2", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -1027,6 +1127,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, { @@ -1047,6 +1152,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -1061,6 +1171,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener 1", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.TCPRouteConfigEntry{ Name: "TCP Route 2", @@ -1072,6 +1187,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener 2", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -1128,6 +1248,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolHTTP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -1142,6 +1267,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "Listener", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{}, @@ -1181,6 +1311,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -1195,6 +1330,11 @@ func TestBindRoutesToGateways(t *testing.T) { SectionName: "", }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ @@ -1289,6 +1429,11 @@ func TestBindRoutesToGateways(t *testing.T) { Protocol: structs.ListenerProtocolTCP, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, }, }, @@ -1302,6 +1447,11 @@ func TestBindRoutesToGateways(t *testing.T) { Kind: structs.APIGateway, }, }, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{}, @@ -1430,7 +1580,7 @@ func TestAPIGatewayController(t *testing.T) { }, }, }, - "tcp-route-no-gateways-invalid-targets": { + "tcp-route-not-accepted-bind": { requests: []controller.Request{{ Kind: structs.TCPRoute, Name: "tcp-route", @@ -1444,6 +1594,27 @@ func TestAPIGatewayController(t *testing.T) { Services: []structs.TCPService{{ Name: "tcp-upstream", }}, + Parents: []structs.ResourceReference{{ + Name: "api-gateway", + EnterpriseMeta: *defaultMeta, + }}, + }, + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "api-gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{{ + Name: "listener", + Port: 80, + }}, + }, + &structs.BoundAPIGatewayConfigEntry{ + Kind: structs.BoundAPIGateway, + Name: "api-gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.BoundAPIGatewayListener{{ + Name: "listener", + }}, }, }, finalEntries: []structs.ConfigEntry{ @@ -1453,10 +1624,41 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeInvalidDiscoveryChain(errServiceDoesNotExist), + conditions.routeAccepted(), + conditions.routeUnbound(structs.ResourceReference{ + Name: "api-gateway", + EnterpriseMeta: *defaultMeta, + }, errors.New("failed to bind route to gateway api-gateway: gateway has not been accepted")), + }, + }, + }, + &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "api-gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.APIGatewayListener{{ + Name: "listener", + Port: 80, + }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + Kind: structs.APIGateway, + Name: "api-gateway", + SectionName: "listener", + EnterpriseMeta: *defaultMeta, + }), }, }, }, + &structs.BoundAPIGatewayConfigEntry{ + Kind: structs.BoundAPIGateway, + Name: "api-gateway", + EnterpriseMeta: *defaultMeta, + Listeners: []structs.BoundAPIGatewayListener{{ + Name: "listener", + }}, + }, }, }, "tcp-route-no-gateways-invalid-targets-bad-protocol": { @@ -1748,6 +1950,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, @@ -1763,6 +1970,11 @@ func TestAPIGatewayController(t *testing.T) { Protocol: structs.ListenerProtocolTCP, Port: 22, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().gatewayAccepted(), + }, + }, }, &structs.BoundAPIGatewayConfigEntry{ Kind: structs.BoundAPIGateway, @@ -1840,6 +2052,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, @@ -1931,6 +2148,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.TCPRouteConfigEntry{ Kind: structs.TCPRoute, @@ -1944,6 +2166,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, @@ -2061,6 +2288,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.HTTPRouteConfigEntry{ Kind: structs.HTTPRoute, @@ -2076,6 +2308,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, @@ -2174,6 +2411,14 @@ func TestAPIGatewayController(t *testing.T) { Kind: structs.TCPRoute, Name: "tcp-route", Meta: acl.DefaultEnterpriseMeta(), + }, { + Kind: structs.TCPRoute, + Name: "tcp-route", + Meta: acl.DefaultEnterpriseMeta(), + }, { + Kind: structs.HTTPRoute, + Name: "http-route", + Meta: acl.DefaultEnterpriseMeta(), }, { Kind: structs.HTTPRoute, Name: "http-route", @@ -2327,6 +2572,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.TCPRouteConfigEntry{ Kind: structs.TCPRoute, @@ -2340,6 +2590,11 @@ func TestAPIGatewayController(t *testing.T) { Name: "gateway", EnterpriseMeta: *defaultMeta, }}, + Status: structs.Status{ + Conditions: []structs.Condition{ + newGatewayConditionGenerator().routeAccepted(), + }, + }, }, &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, diff --git a/agent/consul/internal_endpoint_test.go b/agent/consul/internal_endpoint_test.go index e0aa941b90e..181de4ed82c 100644 --- a/agent/consul/internal_endpoint_test.go +++ b/agent/consul/internal_endpoint_test.go @@ -1,9 +1,9 @@ package consul import ( + "crypto/rand" "encoding/base64" "fmt" - "math/rand" "os" "strings" "testing" diff --git a/agent/consul/leader_connect_ca.go b/agent/consul/leader_connect_ca.go index 008289c947e..abb92f54b6b 100644 --- a/agent/consul/leader_connect_ca.go +++ b/agent/consul/leader_connect_ca.go @@ -12,7 +12,7 @@ import ( "time" "github.com/hashicorp/go-hclog" - uuid "github.com/hashicorp/go-uuid" + "github.com/hashicorp/go-uuid" "golang.org/x/time/rate" "github.com/hashicorp/consul/acl" @@ -272,7 +272,7 @@ func newCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error) { ExternalTrustDomain: clusterID, NotBefore: primaryCert.NotBefore, NotAfter: primaryCert.NotAfter, - RootCert: pemValue, + RootCert: lib.EnsureTrailingNewline(pemValue), PrivateKeyType: keyType, PrivateKeyBits: keyBits, Active: true, @@ -887,6 +887,23 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C return err } + // TODO: https://github.com/hashicorp/consul/issues/12386 + intermediate, err := newProvider.ActiveIntermediate() + if err != nil { + return fmt.Errorf("error fetching active intermediate: %w", err) + } + if intermediate == "" { + intermediate, err = newProvider.GenerateIntermediate() + if err != nil { + return fmt.Errorf("error generating intermediate: %w", err) + } + } + if intermediate != newRootPEM { + if err := setLeafSigningCert(newActiveRoot, intermediate); err != nil { + return err + } + } + // See if the provider needs to persist any state along with the config pState, err := newProvider.State() if err != nil { @@ -970,19 +987,9 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C } // Add the cross signed cert to the new CA's intermediates (to be attached - // to leaf certs). - newActiveRoot.IntermediateCerts = []string{xcCert} - } - } - - // TODO: https://github.com/hashicorp/consul/issues/12386 - intermediate, err := newProvider.GenerateIntermediate() - if err != nil { - return err - } - if intermediate != newRootPEM { - if err := setLeafSigningCert(newActiveRoot, intermediate); err != nil { - return err + // to leaf certs). We do not want it to be the last cert if there are any + // existing intermediate certs so we push to the front. + newActiveRoot.IntermediateCerts = append([]string{xcCert}, newActiveRoot.IntermediateCerts...) } } diff --git a/agent/consul/leader_connect_ca_test.go b/agent/consul/leader_connect_ca_test.go index 7e84a87b19b..1e4e4d2af96 100644 --- a/agent/consul/leader_connect_ca_test.go +++ b/agent/consul/leader_connect_ca_test.go @@ -25,7 +25,7 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" - ca "github.com/hashicorp/consul/agent/connect/ca" + "github.com/hashicorp/consul/agent/connect/ca" "github.com/hashicorp/consul/agent/consul/fsm" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" @@ -612,39 +612,72 @@ func TestCAManager_UpdateConfiguration_Vault_Primary(t *testing.T) { _, origRoot, err := s1.fsm.State().CARootActive(nil) require.NoError(t, err) require.Len(t, origRoot.IntermediateCerts, 1) + origRoot.CreateIndex = s1.caManager.providerRoot.CreateIndex + origRoot.ModifyIndex = s1.caManager.providerRoot.ModifyIndex + require.Equal(t, s1.caManager.providerRoot, origRoot) cert, err := connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(origRoot)) require.NoError(t, err) require.Equal(t, connect.HexString(cert.SubjectKeyId), origRoot.SigningKeyID) - vaultToken2 := ca.CreateVaultTokenWithAttrs(t, vault.Client(), &ca.VaultTokenAttributes{ - RootPath: "pki-root-2", - IntermediatePath: "pki-intermediate-2", - ConsulManaged: true, + t.Run("update config without changing root", func(t *testing.T) { + err = s1.caManager.UpdateConfiguration(&structs.CARequest{ + Config: &structs.CAConfiguration{ + Provider: "vault", + Config: map[string]interface{}{ + "Address": vault.Addr, + "Token": vaultToken, + "RootPKIPath": "pki-root/", + "IntermediatePKIPath": "pki-intermediate/", + "CSRMaxPerSecond": 100, + }, + }, + }) + require.NoError(t, err) + _, sameRoot, err := s1.fsm.State().CARootActive(nil) + require.NoError(t, err) + require.Len(t, sameRoot.IntermediateCerts, 1) + sameRoot.CreateIndex = s1.caManager.providerRoot.CreateIndex + sameRoot.ModifyIndex = s1.caManager.providerRoot.ModifyIndex + + cert, err := connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(sameRoot)) + require.NoError(t, err) + require.Equal(t, connect.HexString(cert.SubjectKeyId), sameRoot.SigningKeyID) + + require.Equal(t, origRoot, sameRoot) + require.Equal(t, sameRoot, s1.caManager.providerRoot) }) - err = s1.caManager.UpdateConfiguration(&structs.CARequest{ - Config: &structs.CAConfiguration{ - Provider: "vault", - Config: map[string]interface{}{ - "Address": vault.Addr, - "Token": vaultToken2, - "RootPKIPath": "pki-root-2/", - "IntermediatePKIPath": "pki-intermediate-2/", + t.Run("update config and change root", func(t *testing.T) { + vaultToken2 := ca.CreateVaultTokenWithAttrs(t, vault.Client(), &ca.VaultTokenAttributes{ + RootPath: "pki-root-2", + IntermediatePath: "pki-intermediate-2", + ConsulManaged: true, + }) + + err = s1.caManager.UpdateConfiguration(&structs.CARequest{ + Config: &structs.CAConfiguration{ + Provider: "vault", + Config: map[string]interface{}{ + "Address": vault.Addr, + "Token": vaultToken2, + "RootPKIPath": "pki-root-2/", + "IntermediatePKIPath": "pki-intermediate-2/", + }, }, - }, - }) - require.NoError(t, err) + }) + require.NoError(t, err) - _, newRoot, err := s1.fsm.State().CARootActive(nil) - require.NoError(t, err) - require.Len(t, newRoot.IntermediateCerts, 2, - "expected one cross-sign cert and one local leaf sign cert") - require.NotEqual(t, origRoot.ID, newRoot.ID) + _, newRoot, err := s1.fsm.State().CARootActive(nil) + require.NoError(t, err) + require.Len(t, newRoot.IntermediateCerts, 2, + "expected one cross-sign cert and one local leaf sign cert") + require.NotEqual(t, origRoot.ID, newRoot.ID) - cert, err = connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(newRoot)) - require.NoError(t, err) - require.Equal(t, connect.HexString(cert.SubjectKeyId), newRoot.SigningKeyID) + cert, err = connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(newRoot)) + require.NoError(t, err) + require.Equal(t, connect.HexString(cert.SubjectKeyId), newRoot.SigningKeyID) + }) } func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) { diff --git a/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index 4be6326c095..757fc87eebc 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -468,6 +468,30 @@ func TestLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T) { require.NoError(r, err) require.Equal(r, pbpeering.PeeringState_TERMINATED, peering.State) }) + + // Re-establishing a peering terminated by the acceptor should be possible + // without needing to delete the terminated peering first. + ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + req = pbpeering.GenerateTokenRequest{ + PeerName: "my-peer-dialer", + } + resp, err = peeringClient.GenerateToken(ctx, &req) + require.NoError(t, err) + + tokenJSON, err = base64.StdEncoding.DecodeString(resp.PeeringToken) + require.NoError(t, err) + + token = structs.PeeringToken{} + require.NoError(t, json.Unmarshal(tokenJSON, &token)) + + establishReq = pbpeering.EstablishRequest{ + PeerName: "my-peer-acceptor", + PeeringToken: resp.PeeringToken, + } + _, err = dialerClient.Establish(ctx, &establishReq) + require.NoError(t, err) } func TestLeader_PeeringSync_FailsForTLSError(t *testing.T) { @@ -478,7 +502,7 @@ func TestLeader_PeeringSync_FailsForTLSError(t *testing.T) { t.Run("server-name-validation", func(t *testing.T) { testLeader_PeeringSync_failsForTLSError(t, func(token *structs.PeeringToken) { token.ServerName = "wrong.name" - }, `transport: authentication handshake failed: x509: certificate is valid for server.dc1.peering.11111111-2222-3333-4444-555555555555.consul, not wrong.name`) + }, `transport: authentication handshake failed: tls: failed to verify certificate: x509: certificate is valid for server.dc1.peering.11111111-2222-3333-4444-555555555555.consul, not wrong.name`) }) t.Run("bad-ca-roots", func(t *testing.T) { wrongRoot, err := os.ReadFile("../../test/client_certs/rootca.crt") @@ -486,7 +510,7 @@ func TestLeader_PeeringSync_FailsForTLSError(t *testing.T) { testLeader_PeeringSync_failsForTLSError(t, func(token *structs.PeeringToken) { token.CA = []string{string(wrongRoot)} - }, `transport: authentication handshake failed: x509: certificate signed by unknown authority`) + }, `transport: authentication handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority`) }) } diff --git a/agent/consul/peering_backend.go b/agent/consul/peering_backend.go index 7c0b7c2e542..aa18cfd04ae 100644 --- a/agent/consul/peering_backend.go +++ b/agent/consul/peering_backend.go @@ -147,8 +147,11 @@ func (b *PeeringBackend) fetchPeerServerAddresses(ws memdb.WatchSet, peerID stri if err != nil { return nil, fmt.Errorf("failed to fetch peer %q: %w", peerID, err) } - if !peering.IsActive() { - return nil, fmt.Errorf("there is no active peering for %q", peerID) + if peering == nil { + return nil, fmt.Errorf("unknown peering %q", peerID) + } + if peering.DeletedAt != nil && !structs.IsZeroProtoTime(peering.DeletedAt) { + return nil, fmt.Errorf("peering %q was deleted", peerID) } return bufferFromAddresses(peering.GetAddressesToDial()) } diff --git a/agent/consul/peering_backend_test.go b/agent/consul/peering_backend_test.go index b7a725409bc..48580663189 100644 --- a/agent/consul/peering_backend_test.go +++ b/agent/consul/peering_backend_test.go @@ -253,7 +253,7 @@ func TestPeeringBackend_GetDialAddresses(t *testing.T) { }, peerID: acceptorPeerID, expect: expectation{ - err: fmt.Sprintf(`there is no active peering for %q`, acceptorPeerID), + err: fmt.Sprintf(`unknown peering %q`, acceptorPeerID), }, }, { @@ -384,6 +384,25 @@ func TestPeeringBackend_GetDialAddresses(t *testing.T) { gatewayAddrs: []string{"5.6.7.8:8443", "6.7.8.9:8443"}, }, }, + { + name: "addresses are returned if the peering is marked as terminated", + setup: func(store *state.Store) { + require.NoError(t, store.PeeringWrite(5, &pbpeering.PeeringWriteRequest{ + Peering: &pbpeering.Peering{ + Name: "dialer", + ID: dialerPeerID, + PeerServerAddresses: []string{"1.2.3.4:8502", "2.3.4.5:8503"}, + State: pbpeering.PeeringState_TERMINATED, + }, + })) + }, + peerID: dialerPeerID, + expect: expectation{ + // Gateways come first, and we use their LAN addresses since this is for outbound communication. + addrs: []string{"5.6.7.8:8443", "6.7.8.9:8443", "1.2.3.4:8502", "2.3.4.5:8503"}, + gatewayAddrs: []string{"5.6.7.8:8443", "6.7.8.9:8443"}, + }, + }, { name: "addresses are not returned if the peering is deleted", setup: func(store *state.Store) { @@ -401,7 +420,7 @@ func TestPeeringBackend_GetDialAddresses(t *testing.T) { }, peerID: dialerPeerID, expect: expectation{ - err: fmt.Sprintf(`there is no active peering for %q`, dialerPeerID), + err: fmt.Sprintf(`peering %q was deleted`, dialerPeerID), }, }, } diff --git a/agent/consul/prepared_query_endpoint.go b/agent/consul/prepared_query_endpoint.go index ffa4b5e5091..54ef82bafdd 100644 --- a/agent/consul/prepared_query_endpoint.go +++ b/agent/consul/prepared_query_endpoint.go @@ -468,7 +468,7 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest, // by the query setup. if len(reply.Nodes) == 0 { wrapper := &queryServerWrapper{srv: p.srv, executeRemote: p.ExecuteRemote} - if err := queryFailover(wrapper, query, args, reply); err != nil { + if err := queryFailover(wrapper, *query, args, reply); err != nil { return err } } @@ -707,7 +707,7 @@ func (q *queryServerWrapper) GetOtherDatacentersByDistance() ([]string, error) { // queryFailover runs an algorithm to determine which DCs to try and then calls // them to try to locate alternative services. -func queryFailover(q queryServer, query *structs.PreparedQuery, +func queryFailover(q queryServer, query structs.PreparedQuery, args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { @@ -789,7 +789,7 @@ func queryFailover(q queryServer, query *structs.PreparedQuery, // the remote query as well. remote := &structs.PreparedQueryExecuteRemoteRequest{ Datacenter: dc, - Query: *query, + Query: query, Limit: args.Limit, QueryOptions: args.QueryOptions, Connect: args.Connect, diff --git a/agent/consul/prepared_query_endpoint_test.go b/agent/consul/prepared_query_endpoint_test.go index 418710b8b50..ca459ddfd89 100644 --- a/agent/consul/prepared_query_endpoint_test.go +++ b/agent/consul/prepared_query_endpoint_test.go @@ -2092,16 +2092,16 @@ func TestPreparedQuery_Execute(t *testing.T) { require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Apply", &query, &query.Query.ID)) // Update the health of a node to mark it critical. - setHealth := func(t *testing.T, codec rpc.ClientCodec, dc string, node string, health string) { + setHealth := func(t *testing.T, codec rpc.ClientCodec, dc string, i int, health string) { t.Helper() req := structs.RegisterRequest{ Datacenter: dc, - Node: node, + Node: fmt.Sprintf("node%d", i), Address: "127.0.0.1", Service: &structs.NodeService{ Service: "foo", Port: 8000, - Tags: []string{"dc1", "tag1"}, + Tags: []string{dc, fmt.Sprintf("tag%d", i)}, }, Check: &structs.HealthCheck{ Name: "failing", @@ -2113,7 +2113,7 @@ func TestPreparedQuery_Execute(t *testing.T) { var reply struct{} require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply)) } - setHealth(t, codec1, "dc1", "node1", api.HealthCritical) + setHealth(t, codec1, "dc1", 1, api.HealthCritical) // The failing node should be filtered. t.Run("failing node filtered", func(t *testing.T) { @@ -2133,7 +2133,7 @@ func TestPreparedQuery_Execute(t *testing.T) { }) // Upgrade it to a warning and re-query, should be 10 nodes again. - setHealth(t, codec1, "dc1", "node1", api.HealthWarning) + setHealth(t, codec1, "dc1", 1, api.HealthWarning) t.Run("warning nodes are included", func(t *testing.T) { req := structs.PreparedQueryExecuteRequest{ Datacenter: "dc1", @@ -2303,7 +2303,7 @@ func TestPreparedQuery_Execute(t *testing.T) { // Now fail everything in dc1 and we should get an empty list back. for i := 0; i < 10; i++ { - setHealth(t, codec1, "dc1", fmt.Sprintf("node%d", i+1), api.HealthCritical) + setHealth(t, codec1, "dc1", i+1, api.HealthCritical) } t.Run("everything is failing so should get empty list", func(t *testing.T) { req := structs.PreparedQueryExecuteRequest{ @@ -2474,7 +2474,7 @@ func TestPreparedQuery_Execute(t *testing.T) { // Set all checks in dc2 as critical for i := 0; i < 10; i++ { - setHealth(t, codec2, "dc2", fmt.Sprintf("node%d", i+1), api.HealthCritical) + setHealth(t, codec2, "dc2", i+1, api.HealthCritical) } // Now we should see 9 nodes from dc3 (we have the tag filter still) @@ -2493,6 +2493,31 @@ func TestPreparedQuery_Execute(t *testing.T) { } expectFailoverPeerNodes(t, &query, &reply, 9) }) + + // Set all checks in dc1 as passing + for i := 0; i < 10; i++ { + setHealth(t, codec1, "dc1", i+1, api.HealthPassing) + } + + // Nothing is healthy so nothing is returned + t.Run("un-failing over", func(t *testing.T) { + retry.Run(t, func(r *retry.R) { + req := structs.PreparedQueryExecuteRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: execToken}, + } + + var reply structs.PreparedQueryExecuteResponse + require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply)) + + for _, node := range reply.Nodes { + assert.NotEqual(t, "node3", node.Node.Node) + } + + expectNodes(t, &query, &reply, 9) + }) + }) } func TestPreparedQuery_Execute_ForwardLeader(t *testing.T) { @@ -2982,7 +3007,7 @@ func (m *mockQueryServer) ExecuteRemote(args *structs.PreparedQueryExecuteRemote func TestPreparedQuery_queryFailover(t *testing.T) { t.Parallel() - query := &structs.PreparedQuery{ + query := structs.PreparedQuery{ Name: "test", Service: structs.ServiceQuery{ Failover: structs.QueryFailoverOptions{ diff --git a/agent/consul/server_log_verification.go b/agent/consul/server_log_verification.go index 0c7e63e3a12..cb95b9aeeee 100644 --- a/agent/consul/server_log_verification.go +++ b/agent/consul/server_log_verification.go @@ -62,12 +62,12 @@ func makeLogVerifyReportFn(logger hclog.Logger) verifier.ReportFn { if r.WrittenSum > 0 && r.WrittenSum != r.ExpectedSum { // The failure occurred before the follower wrote to the log so it // must be corrupted in flight from the leader! - l2.Info("verification checksum FAILED: in-flight corruption", + l2.Error("verification checksum FAILED: in-flight corruption", "followerWriteChecksum", fmt.Sprintf("%08x", r.WrittenSum), "readChecksum", fmt.Sprintf("%08x", r.ReadSum), ) } else { - l2.Info("verification checksum FAILED: storage corruption", + l2.Error("verification checksum FAILED: storage corruption", "followerWriteChecksum", fmt.Sprintf("%08x", r.WrittenSum), "readChecksum", fmt.Sprintf("%08x", r.ReadSum), ) diff --git a/agent/consul/state/acl_test.go b/agent/consul/state/acl_test.go index 5e01514730f..fc00728282a 100644 --- a/agent/consul/state/acl_test.go +++ b/agent/consul/state/acl_test.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto/pbacl" ) @@ -3570,7 +3569,6 @@ func TestStateStore_ACLPolicies_Snapshot_Restore(t *testing.T) { } func TestTokenPoliciesIndex(t *testing.T) { - lib.SeedMathRand() idIndex := &memdb.IndexSchema{ Name: "id", diff --git a/agent/consul/state/catalog.go b/agent/consul/state/catalog.go index 4d645013680..077fbde79a8 100644 --- a/agent/consul/state/catalog.go +++ b/agent/consul/state/catalog.go @@ -900,12 +900,17 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool return fmt.Errorf("failed updating gateway mapping: %s", err) } + if svc.PeerName == "" && sn.Name != "" { + if err := upsertKindServiceName(tx, idx, structs.ServiceKindConnectEnabled, sn); err != nil { + return fmt.Errorf("failed to persist service name as connect-enabled: %v", err) + } + } + + // Update the virtual IP for the service supported, err := virtualIPsSupported(tx, nil) if err != nil { return err } - - // Update the virtual IP for the service if supported { psn := structs.PeeredServiceName{Peer: svc.PeerName, ServiceName: sn} vip, err := assignServiceVirtualIP(tx, idx, psn) @@ -1181,7 +1186,7 @@ func serviceListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, unique := make(map[structs.ServiceName]struct{}) for service := services.Next(); service != nil; service = services.Next() { svc := service.(*structs.ServiceNode) - unique[svc.CompoundServiceName()] = struct{}{} + unique[svc.CompoundServiceName().ServiceName] = struct{}{} } results := make(structs.ServiceList, 0, len(unique)) @@ -1915,17 +1920,17 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st return fmt.Errorf("failed updating service-kind indexes: %w", err) } // Update the node indexes as the service information is included in node catalog queries. - if err := catalogUpdateNodesIndexes(tx, idx, entMeta, peerName); err != nil { + if err := catalogUpdateNodesIndexes(tx, idx, entMeta, svc.PeerName); err != nil { return fmt.Errorf("failed updating nodes indexes: %w", err) } - if err := catalogUpdateNodeIndexes(tx, idx, nodeName, entMeta, peerName); err != nil { + if err := catalogUpdateNodeIndexes(tx, idx, nodeName, entMeta, svc.PeerName); err != nil { return fmt.Errorf("failed updating node indexes: %w", err) } - name := svc.CompoundServiceName() + psn := svc.CompoundServiceName() if err := cleanupMeshTopology(tx, idx, svc); err != nil { - return fmt.Errorf("failed to clean up mesh-topology associations for %q: %v", name.String(), err) + return fmt.Errorf("failed to clean up mesh-topology associations for %q: %v", psn.String(), err) } q := Query{ @@ -1952,22 +1957,42 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st if err := catalogUpdateServiceExtinctionIndex(tx, idx, entMeta, svc.PeerName); err != nil { return err } - psn := structs.PeeredServiceName{Peer: svc.PeerName, ServiceName: name} if err := freeServiceVirtualIP(tx, idx, psn, nil); err != nil { - return fmt.Errorf("failed to clean up virtual IP for %q: %v", name.String(), err) + return fmt.Errorf("failed to clean up virtual IP for %q: %v", psn.String(), err) } - if err := cleanupKindServiceName(tx, idx, svc.CompoundServiceName(), svc.ServiceKind); err != nil { - return fmt.Errorf("failed to persist service name: %v", err) + + if svc.PeerName == "" { + if err := cleanupKindServiceName(tx, idx, psn.ServiceName, svc.ServiceKind); err != nil { + return fmt.Errorf("failed to persist service name: %v", err) + } } } } else { return fmt.Errorf("Could not find any service %s: %s", svc.ServiceName, err) } + // Cleanup ConnectEnabled for this service if none exist. + if svc.PeerName == "" && (svc.ServiceKind == structs.ServiceKindConnectProxy || svc.ServiceConnect.Native) { + service := svc.ServiceName + if svc.ServiceKind == structs.ServiceKindConnectProxy { + service = svc.ServiceProxy.DestinationServiceName + } + sn := structs.ServiceName{Name: service, EnterpriseMeta: svc.EnterpriseMeta} + connectEnabled, err := serviceHasConnectEnabledInstances(tx, sn.Name, &sn.EnterpriseMeta) + if err != nil { + return fmt.Errorf("failed to search for connect instances for service %q: %w", sn.Name, err) + } + if !connectEnabled { + if err := cleanupKindServiceName(tx, idx, sn, structs.ServiceKindConnectEnabled); err != nil { + return fmt.Errorf("failed to cleanup connect-enabled service name: %v", err) + } + } + } + if svc.PeerName == "" { sn := structs.ServiceName{Name: svc.ServiceName, EnterpriseMeta: svc.EnterpriseMeta} if err := cleanupGatewayWildcards(tx, idx, sn, false); err != nil { - return fmt.Errorf("failed to clean up gateway-service associations for %q: %v", name.String(), err) + return fmt.Errorf("failed to clean up gateway-service associations for %q: %v", psn.String(), err) } } @@ -2672,7 +2697,7 @@ func (s *Store) CheckIngressServiceNodes(ws memdb.WatchSet, serviceName string, // De-dup services to lookup names := make(map[structs.ServiceName]struct{}) for _, n := range nodes { - names[n.CompoundServiceName()] = struct{}{} + names[n.CompoundServiceName().ServiceName] = struct{}{} } var results structs.CheckServiceNodes @@ -3634,7 +3659,7 @@ func updateGatewayNamespace(tx WriteTxn, idx uint64, service *structs.GatewaySer continue } - existing, err := tx.First(tableGatewayServices, indexID, service.Gateway, sn.CompoundServiceName(), service.Port) + existing, err := tx.First(tableGatewayServices, indexID, service.Gateway, sn.CompoundServiceName().ServiceName, service.Port) if err != nil { return fmt.Errorf("gateway service lookup failed: %s", err) } @@ -3731,6 +3756,27 @@ func serviceHasConnectInstances(tx WriteTxn, serviceName string, entMeta *acl.En return hasConnectInstance, hasNonConnectInstance, nil } +// serviceHasConnectEnabledInstances returns whether the given service name +// has a corresponding connect-proxy or connect-native instance. +// This function is mostly a clone of `serviceHasConnectInstances`, but it has +// an early return to improve performance and returns true if at least one +// connect-native instance exists. +func serviceHasConnectEnabledInstances(tx WriteTxn, serviceName string, entMeta *acl.EnterpriseMeta) (bool, error) { + query := Query{ + Value: serviceName, + EnterpriseMeta: *entMeta, + } + + svc, err := tx.First(tableServices, indexConnect, query) + if err != nil { + return false, fmt.Errorf("failed service lookup: %w", err) + } + if svc != nil { + return true, nil + } + return false, nil +} + // updateGatewayService associates services with gateways after an eligible event // ie. Registering a service in a namespace targeted by a gateway func updateGatewayService(tx WriteTxn, idx uint64, mapping *structs.GatewayService) error { @@ -4567,7 +4613,10 @@ func updateMeshTopology(tx WriteTxn, idx uint64, node string, svc *structs.NodeS // cleanupMeshTopology removes a service from the mesh topology table // This is only safe to call when there are no more known instances of this proxy func cleanupMeshTopology(tx WriteTxn, idx uint64, service *structs.ServiceNode) error { - // TODO(peering): make this peering aware? + if service.PeerName != "" { + return nil + } + if service.ServiceKind != structs.ServiceKindConnectProxy { return nil } diff --git a/agent/consul/state/catalog_test.go b/agent/consul/state/catalog_test.go index d354b9b094e..b5976bbd2b0 100644 --- a/agent/consul/state/catalog_test.go +++ b/agent/consul/state/catalog_test.go @@ -2577,20 +2577,49 @@ func TestStateStore_DeleteService(t *testing.T) { testRegisterService(t, s, 2, "node1", "service1") testRegisterCheck(t, s, 3, "node1", "service1", "check1", api.HealthPassing) - // Delete the service. + // register a node with a service on a cluster peer. + testRegisterNodeOpts(t, s, 4, "node1", func(n *structs.Node) error { + n.PeerName = "cluster-01" + return nil + }) + testRegisterServiceOpts(t, s, 5, "node1", "service1", func(service *structs.NodeService) { + service.PeerName = "cluster-01" + }) + + wsPeer := memdb.NewWatchSet() + _, ns, err := s.NodeServices(wsPeer, "node1", nil, "cluster-01") + require.Len(t, ns.Services, 1) + require.NoError(t, err) + ws := memdb.NewWatchSet() - _, _, err := s.NodeServices(ws, "node1", nil, "") + _, ns, err = s.NodeServices(ws, "node1", nil, "") + require.Len(t, ns.Services, 1) require.NoError(t, err) - if err := s.DeleteService(4, "node1", "service1", nil, ""); err != nil { - t.Fatalf("err: %s", err) + + { + // Delete the peered service. + err = s.DeleteService(6, "node1", "service1", nil, "cluster-01") + require.NoError(t, err) + require.True(t, watchFired(wsPeer)) + _, kindServiceNames, err := s.ServiceNamesOfKind(nil, structs.ServiceKindTypical) + require.NoError(t, err) + require.Len(t, kindServiceNames, 1) + require.Equal(t, "service1", kindServiceNames[0].Service.Name) } - if !watchFired(ws) { - t.Fatalf("bad") + + { + // Delete the service. + err = s.DeleteService(6, "node1", "service1", nil, "") + require.NoError(t, err) + require.True(t, watchFired(ws)) + _, kindServiceNames, err := s.ServiceNamesOfKind(nil, structs.ServiceKindTypical) + require.NoError(t, err) + require.Len(t, kindServiceNames, 0) } // Service doesn't exist. ws = memdb.NewWatchSet() - _, ns, err := s.NodeServices(ws, "node1", nil, "") + _, ns, err = s.NodeServices(ws, "node1", nil, "") if err != nil || ns == nil || len(ns.Services) != 0 { t.Fatalf("bad: %#v (err: %#v)", ns, err) } @@ -2605,15 +2634,15 @@ func TestStateStore_DeleteService(t *testing.T) { } // Index tables were updated. - assert.Equal(t, uint64(4), catalogChecksMaxIndex(tx, nil, "")) - assert.Equal(t, uint64(4), catalogServicesMaxIndex(tx, nil, "")) + assert.Equal(t, uint64(6), catalogChecksMaxIndex(tx, nil, "")) + assert.Equal(t, uint64(6), catalogServicesMaxIndex(tx, nil, "")) // Deleting a nonexistent service should be idempotent and not return an // error, nor fire a watch. - if err := s.DeleteService(5, "node1", "service1", nil, ""); err != nil { + if err := s.DeleteService(6, "node1", "service1", nil, ""); err != nil { t.Fatalf("err: %s", err) } - assert.Equal(t, uint64(4), catalogServicesMaxIndex(tx, nil, "")) + assert.Equal(t, uint64(6), catalogServicesMaxIndex(tx, nil, "")) if watchFired(ws) { t.Fatalf("bad") } @@ -8664,7 +8693,7 @@ func TestStateStore_EnsureService_ServiceNames(t *testing.T) { }, } - var idx uint64 + var idx, connectEnabledIdx uint64 testRegisterNode(t, s, idx, "node1") for _, svc := range services { @@ -8678,8 +8707,29 @@ func TestStateStore_EnsureService_ServiceNames(t *testing.T) { require.Len(t, gotNames, 1) require.Equal(t, svc.CompoundServiceName(), gotNames[0].Service) require.Equal(t, svc.Kind, gotNames[0].Kind) + if svc.Kind == structs.ServiceKindConnectProxy { + connectEnabledIdx = idx + } } + // A ConnectEnabled service should exist if a corresponding ConnectProxy or ConnectNative service exists. + verifyConnectEnabled := func(expectIdx uint64) { + gotIdx, gotNames, err := s.ServiceNamesOfKind(nil, structs.ServiceKindConnectEnabled) + require.NoError(t, err) + require.Equal(t, expectIdx, gotIdx) + require.Equal(t, []*KindServiceName{ + { + Kind: structs.ServiceKindConnectEnabled, + Service: structs.NewServiceName("foo", entMeta), + RaftIndex: structs.RaftIndex{ + CreateIndex: connectEnabledIdx, + ModifyIndex: connectEnabledIdx, + }, + }, + }, gotNames) + } + verifyConnectEnabled(connectEnabledIdx) + // Register another ingress gateway and there should be two names under the kind index newIngress := structs.NodeService{ Kind: structs.ServiceKindIngressGateway, @@ -8749,6 +8799,38 @@ func TestStateStore_EnsureService_ServiceNames(t *testing.T) { require.NoError(t, err) require.Equal(t, idx, gotIdx) require.Empty(t, got) + + // A ConnectEnabled entry should not be removed until all corresponding services are removed. + { + verifyConnectEnabled(connectEnabledIdx) + // Add a connect-native service. + idx++ + require.NoError(t, s.EnsureService(idx, "node1", &structs.NodeService{ + Kind: structs.ServiceKindTypical, + ID: "foo", + Service: "foo", + Address: "5.5.5.5", + Port: 5555, + EnterpriseMeta: *entMeta, + Connect: structs.ServiceConnect{ + Native: true, + }, + })) + verifyConnectEnabled(connectEnabledIdx) + + // Delete the proxy. This should not clean up the entry, because we still have a + // connect-native service registered. + idx++ + require.NoError(t, s.DeleteService(idx, "node1", "connect-proxy", entMeta, "")) + verifyConnectEnabled(connectEnabledIdx) + + // Remove the connect-native service to clear out the connect-enabled entry. + require.NoError(t, s.DeleteService(idx, "node1", "foo", entMeta, "")) + gotIdx, gotNames, err := s.ServiceNamesOfKind(nil, structs.ServiceKindConnectEnabled) + require.NoError(t, err) + require.Equal(t, idx, gotIdx) + require.Empty(t, gotNames) + } } func assertMaxIndexes(t *testing.T, tx ReadTxn, expect map[string]uint64, skip ...string) { diff --git a/agent/consul/state/peering.go b/agent/consul/state/peering.go index 32e60450044..3fafb98cba7 100644 --- a/agent/consul/state/peering.go +++ b/agent/consul/state/peering.go @@ -770,88 +770,181 @@ func exportedServicesForPeerTxn( maxIdx := peering.ModifyIndex entMeta := structs.NodeEnterpriseMetaInPartition(peering.Partition) - idx, conf, err := getExportedServicesConfigEntryTxn(tx, ws, nil, entMeta) + idx, exportConf, err := getExportedServicesConfigEntryTxn(tx, ws, nil, entMeta) if err != nil { return 0, nil, fmt.Errorf("failed to fetch exported-services config entry: %w", err) } if idx > maxIdx { maxIdx = idx } - if conf == nil { + if exportConf == nil { return maxIdx, &structs.ExportedServiceList{}, nil } var ( - normalSet = make(map[structs.ServiceName]struct{}) - discoSet = make(map[structs.ServiceName]struct{}) + // exportedServices will contain the listing of all service names that are being exported + // and will need to be queried for connect / discovery chain information. + exportedServices = make(map[structs.ServiceName]struct{}) + + // exportedConnectServices will contain the listing of all connect service names that are being exported. + exportedConnectServices = make(map[structs.ServiceName]struct{}) + + // namespaceConnectServices provides a listing of all connect service names for a particular partition+namespace pair. + namespaceConnectServices = make(map[acl.EnterpriseMeta]map[string]struct{}) + + // namespaceDiscoChains provides a listing of all disco chain names for a particular partition+namespace pair. + namespaceDiscoChains = make(map[acl.EnterpriseMeta]map[string]struct{}) ) - // At least one of the following should be true for a name for it to - // replicate: - // - // - are a discovery chain by definition (service-router, service-splitter, service-resolver) - // - have an explicit sidecar kind=connect-proxy - // - use connect native mode + // Helper function for inserting data and auto-creating maps. + insertEntry := func(m map[acl.EnterpriseMeta]map[string]struct{}, entMeta acl.EnterpriseMeta, name string) { + names, ok := m[entMeta] + if !ok { + names = make(map[string]struct{}) + m[entMeta] = names + } + names[name] = struct{}{} + } - for _, svc := range conf.Services { + // Build the set of all services that will be exported. + // Any possible namespace wildcards or "consul" services should be removed by this step. + for _, svc := range exportConf.Services { // Prevent exporting the "consul" service. if svc.Name == structs.ConsulServiceName { continue } - svcMeta := acl.NewEnterpriseMetaWithPartition(entMeta.PartitionOrDefault(), svc.Namespace) + svcEntMeta := acl.NewEnterpriseMetaWithPartition(entMeta.PartitionOrDefault(), svc.Namespace) + svcName := structs.NewServiceName(svc.Name, &svcEntMeta) - sawPeer := false + peerFound := false for _, consumer := range svc.Consumers { - name := structs.NewServiceName(svc.Name, &svcMeta) - - if _, ok := normalSet[name]; ok { - // Service was covered by a wildcard that was already accounted for - continue + if consumer.Peer == peering.Name { + peerFound = true + break } - if consumer.Peer != peering.Name { - continue + } + // Only look for more information if the matching peer was found. + if !peerFound { + continue + } + + // If this isn't a wildcard, we can simply add it to the list of services to watch and move to the next entry. + if svc.Name != structs.WildcardSpecifier { + exportedServices[svcName] = struct{}{} + continue + } + + // If all services in the namespace are exported by the wildcard, query those service names. + idx, typicalServices, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindTypical, svcEntMeta) + if err != nil { + return 0, nil, fmt.Errorf("failed to get typical service names: %w", err) + } + if idx > maxIdx { + maxIdx = idx + } + for _, sn := range typicalServices { + // Prevent exporting the "consul" service. + if sn.Service.Name != structs.ConsulServiceName { + exportedServices[sn.Service] = struct{}{} } - sawPeer = true + } - if svc.Name != structs.WildcardSpecifier { - normalSet[name] = struct{}{} + // List all config entries of kind service-resolver, service-router, service-splitter, because they + // will be exported as connect services. + idx, discoChains, err := listDiscoveryChainNamesTxn(tx, ws, nil, svcEntMeta) + if err != nil { + return 0, nil, fmt.Errorf("failed to get discovery chain names: %w", err) + } + if idx > maxIdx { + maxIdx = idx + } + for _, sn := range discoChains { + // Prevent exporting the "consul" service. + if sn.Name != structs.ConsulServiceName { + exportedConnectServices[sn] = struct{}{} + insertEntry(namespaceDiscoChains, svcEntMeta, sn.Name) } } + } - // If the target peer is a consumer, and all services in the namespace are exported, query those service names. - if sawPeer && svc.Name == structs.WildcardSpecifier { - idx, typicalServices, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindTypical, svcMeta) + // At least one of the following should be true for a name to replicate it as a *connect* service: + // - are a discovery chain by definition (service-router, service-splitter, service-resolver) + // - have an explicit sidecar kind=connect-proxy + // - use connect native mode + // - are registered with a terminating gateway + populateConnectService := func(sn structs.ServiceName) error { + // Load all disco-chains in this namespace if we haven't already. + if _, ok := namespaceDiscoChains[sn.EnterpriseMeta]; !ok { + // Check to see if we have a discovery chain with the same name. + idx, chains, err := listDiscoveryChainNamesTxn(tx, ws, nil, sn.EnterpriseMeta) if err != nil { - return 0, nil, fmt.Errorf("failed to get typical service names: %w", err) + return fmt.Errorf("failed to get connect services: %w", err) } if idx > maxIdx { maxIdx = idx } - for _, s := range typicalServices { - // Prevent exporting the "consul" service. - if s.Service.Name == structs.ConsulServiceName { - continue - } - normalSet[s.Service] = struct{}{} + for _, sn := range chains { + insertEntry(namespaceDiscoChains, sn.EnterpriseMeta, sn.Name) } + } + // Check to see if we have the connect service. + if _, ok := namespaceDiscoChains[sn.EnterpriseMeta][sn.Name]; ok { + exportedConnectServices[sn] = struct{}{} + // Do not early return because we have multiple watches that should be established. + } - // list all config entries of kind service-resolver, service-router, service-splitter? - idx, discoChains, err := listDiscoveryChainNamesTxn(tx, ws, nil, svcMeta) + // Load all services in this namespace if we haven't already. + if _, ok := namespaceConnectServices[sn.EnterpriseMeta]; !ok { + // This is more efficient than querying the service instance table. + idx, connectServices, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindConnectEnabled, sn.EnterpriseMeta) if err != nil { - return 0, nil, fmt.Errorf("failed to get discovery chain names: %w", err) + return fmt.Errorf("failed to get connect services: %w", err) } if idx > maxIdx { maxIdx = idx } - for _, sn := range discoChains { - discoSet[sn] = struct{}{} + for _, ksn := range connectServices { + insertEntry(namespaceConnectServices, sn.EnterpriseMeta, ksn.Service.Name) } } + // Check to see if we have the connect service. + if _, ok := namespaceConnectServices[sn.EnterpriseMeta][sn.Name]; ok { + exportedConnectServices[sn] = struct{}{} + // Do not early return because we have multiple watches that should be established. + } + + // Check if the service is exposed via terminating gateways. + svcGateways, err := tx.Get(tableGatewayServices, indexService, sn) + if err != nil { + return fmt.Errorf("failed gateway lookup for %q: %w", sn.Name, err) + } + ws.Add(svcGateways.WatchCh()) + for svc := svcGateways.Next(); svc != nil; svc = svcGateways.Next() { + gs, ok := svc.(*structs.GatewayService) + if !ok { + return fmt.Errorf("failed converting to GatewayService for %q", sn.Name) + } + if gs.GatewayKind == structs.ServiceKindTerminatingGateway { + exportedConnectServices[sn] = struct{}{} + break + } + } + + return nil } - normal := maps.SliceOfKeys(normalSet) - disco := maps.SliceOfKeys(discoSet) + // Perform queries and check if each service is connect-enabled. + for sn := range exportedServices { + // Do not query for data if we already know it's a connect service. + if _, ok := exportedConnectServices[sn]; ok { + continue + } + if err := populateConnectService(sn); err != nil { + return 0, nil, err + } + } + // Fetch the protocol / targets for connect services. chainInfo := make(map[structs.ServiceName]structs.ExportedDiscoveryChainInfo) populateChainInfo := func(svc structs.ServiceName) error { if _, ok := chainInfo[svc]; ok { @@ -899,21 +992,17 @@ func exportedServicesForPeerTxn( return nil } - for _, svc := range normal { - if err := populateChainInfo(svc); err != nil { - return 0, nil, err - } - } - for _, svc := range disco { + for svc := range exportedConnectServices { if err := populateChainInfo(svc); err != nil { return 0, nil, err } } - structs.ServiceList(normal).Sort() + sortedServices := maps.SliceOfKeys(exportedServices) + structs.ServiceList(sortedServices).Sort() list := &structs.ExportedServiceList{ - Services: normal, + Services: sortedServices, DiscoChains: chainInfo, } diff --git a/agent/consul/state/peering_test.go b/agent/consul/state/peering_test.go index 2c2caaab9a9..81933500d2f 100644 --- a/agent/consul/state/peering_test.go +++ b/agent/consul/state/peering_test.go @@ -1908,18 +1908,28 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { }, }, { + // Should be exported as both a normal and disco chain (resolver). Name: "mysql", Consumers: []structs.ServiceConsumer{ {Peer: "my-peering"}, }, }, { + // Should be exported as both a normal and disco chain (connect-proxy). Name: "redis", Consumers: []structs.ServiceConsumer{ {Peer: "my-peering"}, }, }, { + // Should only be exported as a normal service. + Name: "prometheus", + Consumers: []structs.ServiceConsumer{ + {Peer: "my-peering"}, + }, + }, + { + // Should not be exported (different peer consumer) Name: "mongo", Consumers: []structs.ServiceConsumer{ {Peer: "my-other-peering"}, @@ -1932,12 +1942,37 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { require.True(t, watchFired(ws)) ws = memdb.NewWatchSet() + // Register extra things so that disco chain entries appear. + lastIdx++ + require.NoError(t, s.EnsureNode(lastIdx, &structs.Node{ + Node: "node1", Address: "10.0.0.1", + })) + lastIdx++ + require.NoError(t, s.EnsureService(lastIdx, "node1", &structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + ID: "redis-sidecar-proxy", + Service: "redis-sidecar-proxy", + Port: 5005, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "redis", + }, + })) + ensureConfigEntry(t, &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "mysql", + EnterpriseMeta: *defaultEntMeta, + }) + expect := &structs.ExportedServiceList{ Services: []structs.ServiceName{ { Name: "mysql", EnterpriseMeta: *defaultEntMeta, }, + { + Name: "prometheus", + EnterpriseMeta: *defaultEntMeta, + }, { Name: "redis", EnterpriseMeta: *defaultEntMeta, @@ -1998,17 +2033,21 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { ws = memdb.NewWatchSet() expect := &structs.ExportedServiceList{ + // Only "billing" shows up, because there are no other service instances running, + // and "consul" is never exported. Services: []structs.ServiceName{ { Name: "billing", EnterpriseMeta: *defaultEntMeta, }, }, + // Only "mysql" appears because there it has a service resolver. + // "redis" does not appear, because it's a sidecar proxy without a corresponding service, so the wildcard doesn't find it. DiscoChains: map[structs.ServiceName]structs.ExportedDiscoveryChainInfo{ - newSN("billing"): { + newSN("mysql"): { Protocol: "tcp", TCPTargets: []*structs.DiscoveryTarget{ - newTarget("billing", "", "dc1"), + newTarget("mysql", "", "dc1"), }, }, }, @@ -2025,13 +2064,17 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { ID: "payments", Service: "payments", Port: 5000, })) - // The proxy will be ignored. + // The proxy will cause "payments" to be output in the disco chains. It will NOT be output + // in the normal services list. lastIdx++ require.NoError(t, s.EnsureService(lastIdx, "foo", &structs.NodeService{ Kind: structs.ServiceKindConnectProxy, ID: "payments-proxy", Service: "payments-proxy", Port: 5000, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "payments", + }, })) lastIdx++ // The consul service should never be exported. @@ -2099,10 +2142,11 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { }, DiscoChains: map[structs.ServiceName]structs.ExportedDiscoveryChainInfo{ // NOTE: no consul-redirect here - newSN("billing"): { + // NOTE: no billing here, because it does not have a proxy. + newSN("payments"): { Protocol: "http", }, - newSN("payments"): { + newSN("mysql"): { Protocol: "http", }, newSN("resolver"): { @@ -2129,6 +2173,9 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { lastIdx++ require.NoError(t, s.DeleteConfigEntry(lastIdx, structs.ServiceSplitter, "splitter", nil)) + lastIdx++ + require.NoError(t, s.DeleteConfigEntry(lastIdx, structs.ServiceResolver, "mysql", nil)) + require.True(t, watchFired(ws)) ws = memdb.NewWatchSet() @@ -2160,6 +2207,51 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { require.Equal(t, expect, got) }) + testutil.RunStep(t, "terminating gateway services are exported", func(t *testing.T) { + lastIdx++ + require.NoError(t, s.EnsureService(lastIdx, "foo", &structs.NodeService{ + ID: "term-svc", Service: "term-svc", Port: 6000, + })) + lastIdx++ + require.NoError(t, s.EnsureService(lastIdx, "foo", &structs.NodeService{ + Kind: structs.ServiceKindTerminatingGateway, + Service: "some-terminating-gateway", + ID: "some-terminating-gateway", + Port: 9000, + })) + lastIdx++ + require.NoError(t, s.EnsureConfigEntry(lastIdx, &structs.TerminatingGatewayConfigEntry{ + Kind: structs.TerminatingGateway, + Name: "some-terminating-gateway", + Services: []structs.LinkedService{{Name: "term-svc"}}, + })) + + expect := &structs.ExportedServiceList{ + Services: []structs.ServiceName{ + newSN("payments"), + newSN("term-svc"), + }, + DiscoChains: map[structs.ServiceName]structs.ExportedDiscoveryChainInfo{ + newSN("payments"): { + Protocol: "http", + }, + newSN("resolver"): { + Protocol: "http", + }, + newSN("router"): { + Protocol: "http", + }, + newSN("term-svc"): { + Protocol: "http", + }, + }, + } + idx, got, err := s.ExportedServicesForPeer(ws, id, "dc1") + require.NoError(t, err) + require.Equal(t, lastIdx, idx) + require.Equal(t, expect, got) + }) + testutil.RunStep(t, "deleting the config entry clears exported services", func(t *testing.T) { expect := &structs.ExportedServiceList{} diff --git a/agent/consul/state/usage.go b/agent/consul/state/usage.go index 5e3d7ce9cb3..9db30e5b2d1 100644 --- a/agent/consul/state/usage.go +++ b/agent/consul/state/usage.go @@ -126,11 +126,11 @@ func updateUsage(tx WriteTxn, changes Changes) error { // changed, in order to compare it with the finished memdb state. // Make sure to account for the fact that services can change their names. if serviceNameChanged(change) { - serviceNameChanges[svc.CompoundServiceName()] += 1 + serviceNameChanges[svc.CompoundServiceName().ServiceName] += 1 before := change.Before.(*structs.ServiceNode) - serviceNameChanges[before.CompoundServiceName()] -= 1 + serviceNameChanges[before.CompoundServiceName().ServiceName] -= 1 } else { - serviceNameChanges[svc.CompoundServiceName()] += delta + serviceNameChanges[svc.CompoundServiceName().ServiceName] += delta } case "kvs": diff --git a/agent/coordinate_endpoint_test.go b/agent/coordinate_endpoint_test.go index 4782ae72d0f..71492d909c4 100644 --- a/agent/coordinate_endpoint_test.go +++ b/agent/coordinate_endpoint_test.go @@ -40,9 +40,9 @@ func TestCoordinate_Disabled_Response(t *testing.T) { req, _ := http.NewRequest("PUT", "/should/not/care", nil) resp := httptest.NewRecorder() obj, err := tt(resp, req) - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 401 { - t.Fatalf("expected status 401 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 401 { + t.Fatalf("expected status 401 but got %d", httpErr.StatusCode) } } else { t.Fatalf("expected HTTP error but got %v", err) diff --git a/agent/dns_test.go b/agent/dns_test.go index 501564c66c2..1cc74213890 100644 --- a/agent/dns_test.go +++ b/agent/dns_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/serf/coordinate" "github.com/miekg/dns" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/consul" @@ -4883,21 +4884,26 @@ func TestDNS_TCP_and_UDP_Truncate(t *testing.T) { services := []string{"normal", "truncated"} for index, service := range services { numServices := (index * 5000) + 2 + var eg errgroup.Group for i := 1; i < numServices; i++ { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("%s-%d.acme.com", service, i), - Address: fmt.Sprintf("127.%d.%d.%d", 0, (i / 255), i%255), - Service: &structs.NodeService{ - Service: service, - Port: 8000, - }, - } + 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{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); 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) } // Register an equivalent prepared query. diff --git a/agent/envoyextensions/builtin/http/localratelimit/copied.go b/agent/envoyextensions/builtin/http/localratelimit/copied.go deleted file mode 100644 index ec1d4988eb7..00000000000 --- a/agent/envoyextensions/builtin/http/localratelimit/copied.go +++ /dev/null @@ -1,58 +0,0 @@ -package localratelimit - -import ( - envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" - envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" - envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" - - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" -) - -// This is copied from xds and not put into the shared package because I'm not -// convinced it should be shared. - -func makeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) { - if tlsContext == nil { - return nil, nil - } - return makeTransportSocket("tls", tlsContext) -} - -func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) { - any, err := anypb.New(config) - if err != nil { - return nil, err - } - return &envoy_core_v3.TransportSocket{ - Name: name, - ConfigType: &envoy_core_v3.TransportSocket_TypedConfig{ - TypedConfig: any, - }, - }, nil -} - -func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) { - any, err := anypb.New(cfg) - if err != nil { - return nil, err - } - - return &envoy_http_v3.HttpFilter{ - Name: name, - ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{TypedConfig: any}, - }, nil -} - -func makeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) { - any, err := anypb.New(cfg) - if err != nil { - return nil, err - } - - return &envoy_listener_v3.Filter{ - Name: name, - ConfigType: &envoy_listener_v3.Filter_TypedConfig{TypedConfig: any}, - }, nil -} diff --git a/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go b/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go deleted file mode 100644 index 3ffea6f1ff8..00000000000 --- a/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go +++ /dev/null @@ -1,198 +0,0 @@ -package localratelimit - -import ( - "errors" - "fmt" - "time" - - envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" - envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" - envoy_ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3" - envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" - envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" - envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" - "github.com/golang/protobuf/ptypes/wrappers" - "github.com/hashicorp/go-multierror" - "github.com/mitchellh/mapstructure" - "google.golang.org/protobuf/types/known/durationpb" - - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/envoyextensions/extensioncommon" -) - -type ratelimit struct { - ProxyType string - - // Token bucket of the rate limit - MaxTokens *int - TokensPerFill *int - FillInterval *int - - // Percent of requests to be rate limited - FilterEnabled *uint32 - FilterEnforced *uint32 -} - -var _ extensioncommon.BasicExtension = (*ratelimit)(nil) - -// Constructor follows a specific function signature required for the extension registration. -func Constructor(ext api.EnvoyExtension) (extensioncommon.EnvoyExtender, error) { - var r ratelimit - if name := ext.Name; name != api.BuiltinLocalRatelimitExtension { - return nil, fmt.Errorf("expected extension name 'ratelimit' but got %q", name) - } - - if err := r.fromArguments(ext.Arguments); err != nil { - return nil, err - } - - return &extensioncommon.BasicEnvoyExtender{ - Extension: &r, - }, nil -} - -func (r *ratelimit) fromArguments(args map[string]interface{}) error { - if err := mapstructure.Decode(args, r); err != nil { - return fmt.Errorf("error decoding extension arguments: %v", err) - } - return r.validate() -} - -func (r *ratelimit) validate() error { - var resultErr error - - // NOTE: Envoy requires FillInterval value must be greater than 0. - // If unset, it is considered as 0. - if r.FillInterval == nil { - resultErr = multierror.Append(resultErr, fmt.Errorf("FillInterval(in second) is missing")) - } else if *r.FillInterval <= 0 { - resultErr = multierror.Append(resultErr, fmt.Errorf("FillInterval(in second) must be greater than 0, got %d", *r.FillInterval)) - } - - // NOTE: Envoy requires MaxToken value must be greater than 0. - // If unset, it is considered as 0. - if r.MaxTokens == nil { - resultErr = multierror.Append(resultErr, fmt.Errorf("MaxTokens is missing")) - } else if *r.MaxTokens <= 0 { - resultErr = multierror.Append(resultErr, fmt.Errorf("MaxTokens must be greater than 0, got %d", r.MaxTokens)) - } - - // TokensPerFill is allowed to unset. In this case, envoy - // uses its default value, which is 1. - if r.TokensPerFill != nil && *r.TokensPerFill <= 0 { - resultErr = multierror.Append(resultErr, fmt.Errorf("TokensPerFill must be greater than 0, got %d", *r.TokensPerFill)) - } - - if err := validateProxyType(r.ProxyType); err != nil { - resultErr = multierror.Append(resultErr, err) - } - - return resultErr -} - -// CanApply determines if the extension can apply to the given extension configuration. -func (p *ratelimit) CanApply(config *extensioncommon.RuntimeConfig) bool { - // rate limit is only applied to the service itself since the limit is - // aggregated from all downstream connections. - return string(config.Kind) == p.ProxyType && !config.IsUpstream() -} - -// PatchRoute does nothing. -func (p ratelimit) PatchRoute(_ *extensioncommon.RuntimeConfig, route *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) { - return route, false, nil -} - -// PatchCluster does nothing. -func (p ratelimit) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) { - return c, false, nil -} - -// PatchFilter inserts a http local rate_limit filter at the head of -// envoy.filters.network.http_connection_manager filters -func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) { - if filter.Name != "envoy.filters.network.http_connection_manager" { - return filter, false, nil - } - if typedConfig := filter.GetTypedConfig(); typedConfig == nil { - return filter, false, errors.New("error getting typed config for http filter") - } - - config := envoy_resource_v3.GetHTTPConnectionManager(filter) - if config == nil { - return filter, false, errors.New("error unmarshalling filter") - } - - tokenBucket := envoy_type_v3.TokenBucket{} - - if p.TokensPerFill != nil { - tokenBucket.TokensPerFill = &wrappers.UInt32Value{ - Value: uint32(*p.TokensPerFill), - } - } - if p.MaxTokens != nil { - tokenBucket.MaxTokens = uint32(*p.MaxTokens) - } - - if p.FillInterval != nil { - tokenBucket.FillInterval = durationpb.New(time.Duration(*p.FillInterval) * time.Second) - } - - var FilterEnabledDefault *envoy_core_v3.RuntimeFractionalPercent - if p.FilterEnabled != nil { - FilterEnabledDefault = &envoy_core_v3.RuntimeFractionalPercent{ - DefaultValue: &envoy_type_v3.FractionalPercent{ - Numerator: *p.FilterEnabled, - Denominator: envoy_type_v3.FractionalPercent_HUNDRED, - }, - } - } - - var FilterEnforcedDefault *envoy_core_v3.RuntimeFractionalPercent - if p.FilterEnforced != nil { - FilterEnforcedDefault = &envoy_core_v3.RuntimeFractionalPercent{ - DefaultValue: &envoy_type_v3.FractionalPercent{ - Numerator: *p.FilterEnforced, - Denominator: envoy_type_v3.FractionalPercent_HUNDRED, - }, - } - } - - ratelimitHttpFilter, err := makeEnvoyHTTPFilter( - "envoy.filters.http.local_ratelimit", - &envoy_ratelimit.LocalRateLimit{ - TokenBucket: &tokenBucket, - StatPrefix: "local_ratelimit", - FilterEnabled: FilterEnabledDefault, - FilterEnforced: FilterEnforcedDefault, - }, - ) - - if err != nil { - return filter, false, err - } - - changedFilters := make([]*envoy_http_v3.HttpFilter, 0, len(config.HttpFilters)+1) - - // The ratelimitHttpFilter is inserted as the first element of the http - // filter chain. - changedFilters = append(changedFilters, ratelimitHttpFilter) - changedFilters = append(changedFilters, config.HttpFilters...) - config.HttpFilters = changedFilters - - newFilter, err := makeFilter("envoy.filters.network.http_connection_manager", config) - if err != nil { - return filter, false, errors.New("error making new filter") - } - - return newFilter, true, nil -} - -func validateProxyType(t string) error { - if t != "connect-proxy" { - return fmt.Errorf("unexpected ProxyType %q", t) - } - - return nil -} diff --git a/agent/envoyextensions/builtin/http/localratelimit/ratelimit_test.go b/agent/envoyextensions/builtin/http/localratelimit/ratelimit_test.go deleted file mode 100644 index 5c68b1f51ea..00000000000 --- a/agent/envoyextensions/builtin/http/localratelimit/ratelimit_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package localratelimit - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/envoyextensions/extensioncommon" -) - -func TestConstructor(t *testing.T) { - makeArguments := func(overrides map[string]interface{}) map[string]interface{} { - m := map[string]interface{}{ - "ProxyType": "connect-proxy", - } - - for k, v := range overrides { - m[k] = v - } - - return m - } - - cases := map[string]struct { - extensionName string - arguments map[string]interface{} - expected ratelimit - ok bool - expectedErrMsg string - }{ - "with no arguments": { - arguments: nil, - ok: false, - }, - "with an invalid name": { - arguments: makeArguments(map[string]interface{}{}), - extensionName: "bad", - ok: false, - }, - "MaxToken is missing": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 30, - "TokensPerFill": 5, - }), - expectedErrMsg: "MaxTokens is missing", - ok: false, - }, - "MaxTokens <= 0": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 30, - "TokensPerFill": 5, - "MaxTokens": 0, - }), - expectedErrMsg: "MaxTokens must be greater than 0", - ok: false, - }, - "FillInterval is missing": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "TokensPerFill": 5, - "MaxTokens": 10, - }), - expectedErrMsg: "FillInterval(in second) is missing", - ok: false, - }, - "FillInterval <= 0": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 0, - "TokensPerFill": 5, - "MaxTokens": 10, - }), - expectedErrMsg: "FillInterval(in second) must be greater than 0", - ok: false, - }, - "TokensPerFill <= 0": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 30, - "TokensPerFill": 0, - "MaxTokens": 10, - }), - expectedErrMsg: "TokensPerFill must be greater than 0", - ok: false, - }, - "FilterEnabled < 0": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 30, - "TokensPerFill": 5, - "MaxTokens": 10, - "FilterEnabled": -1, - }), - expectedErrMsg: "cannot parse 'FilterEnabled', -1 overflows uint", - ok: false, - }, - "FilterEnforced < 0": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 30, - "TokensPerFill": 5, - "MaxTokens": 10, - "FilterEnforced": -1, - }), - expectedErrMsg: "cannot parse 'FilterEnforced', -1 overflows uint", - ok: false, - }, - "valid everything": { - arguments: makeArguments(map[string]interface{}{ - "ProxyType": "connect-proxy", - "FillInterval": 30, - "MaxTokens": 20, - "TokensPerFill": 5, - }), - expected: ratelimit{ - ProxyType: "connect-proxy", - MaxTokens: intPointer(20), - FillInterval: intPointer(30), - TokensPerFill: intPointer(5), - }, - ok: true, - }, - } - - for n, tc := range cases { - t.Run(n, func(t *testing.T) { - - extensionName := api.BuiltinLocalRatelimitExtension - if tc.extensionName != "" { - extensionName = tc.extensionName - } - - svc := api.CompoundServiceName{Name: "svc"} - ext := extensioncommon.RuntimeConfig{ - ServiceName: svc, - EnvoyExtension: api.EnvoyExtension{ - Name: extensionName, - Arguments: tc.arguments, - }, - } - - e, err := Constructor(ext.EnvoyExtension) - - if tc.ok { - require.NoError(t, err) - require.Equal(t, &extensioncommon.BasicEnvoyExtender{Extension: &tc.expected}, e) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expectedErrMsg) - } - }) - } -} - -func intPointer(i int) *int { - return &i -} diff --git a/agent/envoyextensions/registered_extensions.go b/agent/envoyextensions/registered_extensions.go index fed8d3c59e8..c765df7c838 100644 --- a/agent/envoyextensions/registered_extensions.go +++ b/agent/envoyextensions/registered_extensions.go @@ -4,7 +4,6 @@ import ( "fmt" awslambda "github.com/hashicorp/consul/agent/envoyextensions/builtin/aws-lambda" - "github.com/hashicorp/consul/agent/envoyextensions/builtin/http/localratelimit" "github.com/hashicorp/consul/agent/envoyextensions/builtin/lua" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/envoyextensions/extensioncommon" @@ -14,9 +13,8 @@ import ( type extensionConstructor func(api.EnvoyExtension) (extensioncommon.EnvoyExtender, error) var extensionConstructors = map[string]extensionConstructor{ - api.BuiltinLuaExtension: lua.Constructor, - api.BuiltinAWSLambdaExtension: awslambda.Constructor, - api.BuiltinLocalRatelimitExtension: localratelimit.Constructor, + api.BuiltinLuaExtension: lua.Constructor, + api.BuiltinAWSLambdaExtension: awslambda.Constructor, } // ConstructExtension attempts to lookup and build an extension from the registry with the diff --git a/agent/grpc-external/limiter/limiter_test.go b/agent/grpc-external/limiter/limiter_test.go index cef6a4d4171..7f5b9654a0a 100644 --- a/agent/grpc-external/limiter/limiter_test.go +++ b/agent/grpc-external/limiter/limiter_test.go @@ -8,12 +8,8 @@ import ( "time" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/lib" ) -func init() { lib.SeedMathRand() } - func TestSessionLimiter(t *testing.T) { lim := NewSessionLimiter() diff --git a/agent/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index b21f35bccef..ed5809e4554 100644 --- a/agent/grpc-external/services/peerstream/stream_test.go +++ b/agent/grpc-external/services/peerstream/stream_test.go @@ -844,6 +844,13 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { Node: &structs.Node{Node: "foo", Address: "10.0.0.1"}, Service: &structs.NodeService{ID: "mysql-1", Service: "mysql", Port: 5000}, } + mysqlSidecar := &structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + Service: "mysql-sidecar-proxy", + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "mysql", + }, + } lastIdx++ require.NoError(t, store.EnsureNode(lastIdx, mysql.Node)) @@ -851,6 +858,9 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { lastIdx++ require.NoError(t, store.EnsureService(lastIdx, "foo", mysql.Service)) + lastIdx++ + require.NoError(t, store.EnsureService(lastIdx, "foo", mysqlSidecar)) + mongoSvcDefaults := &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, Name: "mongo", @@ -870,6 +880,24 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { mysqlProxySN = structs.NewServiceName("mysql-sidecar-proxy", nil).String() ) + testutil.RunStep(t, "initial stream data is received", func(t *testing.T) { + expectReplEvents(t, client, + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, msg.GetResponse().ResourceURL) + // Roots tested in TestStreamResources_Server_CARootUpdates + }, + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) + require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var exportedServices pbpeerstream.ExportedServiceList + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) + require.ElementsMatch(t, []string{}, exportedServices.Services) + }, + ) + }) + testutil.RunStep(t, "exporting mysql leads to an UPSERT event", func(t *testing.T) { entry := &structs.ExportedServicesConfigEntry{ Name: "default", @@ -895,10 +923,6 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { require.NoError(t, store.EnsureConfigEntry(lastIdx, entry)) expectReplEvents(t, client, - func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { - require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, msg.GetResponse().ResourceURL) - // Roots tested in TestStreamResources_Server_CARootUpdates - }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { // no mongo instances exist require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) @@ -909,16 +933,6 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&nodes)) require.Len(t, nodes.Nodes, 0) }, - func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { - // proxies can't export because no mesh gateway exists yet - require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) - require.Equal(t, mongoProxySN, msg.GetResponse().ResourceID) - require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) - - var nodes pbpeerstream.ExportedService - require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&nodes)) - require.Len(t, nodes.Nodes, 0) - }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) require.Equal(t, mysqlSN, msg.GetResponse().ResourceID) @@ -938,17 +952,6 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&nodes)) require.Len(t, nodes.Nodes, 0) }, - // This event happens because this is the first test case and there are - // no exported services when replication is initially set up. - func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { - require.Equal(t, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) - require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) - require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) - - var exportedServices pbpeerstream.ExportedServiceList - require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) - require.ElementsMatch(t, []string{}, exportedServices.Services) - }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) @@ -978,7 +981,7 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { expectReplEvents(t, client, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) - require.Equal(t, mongoProxySN, msg.GetResponse().ResourceID) + require.Equal(t, mysqlProxySN, msg.GetResponse().ResourceID) require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) var nodes pbpeerstream.ExportedService @@ -986,16 +989,26 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { require.Len(t, nodes.Nodes, 1) pm := nodes.Nodes[0].Service.Connect.PeerMeta - require.Equal(t, "grpc", pm.Protocol) + require.Equal(t, "tcp", pm.Protocol) spiffeIDs := []string{ - "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/mongo", + "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/mysql", "spiffe://11111111-2222-3333-4444-555555555555.consul/gateway/mesh/dc/dc1", } require.Equal(t, spiffeIDs, pm.SpiffeID) }, + ) + }) + + testutil.RunStep(t, "register service resolver to send proxy updates", func(t *testing.T) { + lastIdx++ + require.NoError(t, store.EnsureConfigEntry(lastIdx, &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "mongo", + })) + expectReplEvents(t, client, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) - require.Equal(t, mysqlProxySN, msg.GetResponse().ResourceID) + require.Equal(t, mongoProxySN, msg.GetResponse().ResourceID) require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) var nodes pbpeerstream.ExportedService @@ -1003,9 +1016,9 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { require.Len(t, nodes.Nodes, 1) pm := nodes.Nodes[0].Service.Connect.PeerMeta - require.Equal(t, "tcp", pm.Protocol) + require.Equal(t, "grpc", pm.Protocol) spiffeIDs := []string{ - "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/mysql", + "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/mongo", "spiffe://11111111-2222-3333-4444-555555555555.consul/gateway/mesh/dc/dc1", } require.Equal(t, spiffeIDs, pm.SpiffeID) @@ -1239,8 +1252,8 @@ func TestStreamResources_Server_DisconnectsOnHeartbeatTimeout(t *testing.T) { }) testutil.RunStep(t, "stream is disconnected due to heartbeat timeout", func(t *testing.T) { - disconnectTime := ptr(it.FutureNow(1)) retry.Run(t, func(r *retry.R) { + disconnectTime := ptr(it.StaticNow()) status, ok := srv.StreamStatus(testPeerID) require.True(r, ok) require.False(r, status.Connected) @@ -1410,7 +1423,7 @@ func makeClient(t *testing.T, srv *testServer, peerID string) *MockClient { }, })) - // Receive a services and roots subscription request pair from server + // Receive ExportedService, ExportedServiceList, and PeeringTrustBundle subscription requests from server receivedSub1, err := client.Recv() require.NoError(t, err) receivedSub2, err := client.Recv() diff --git a/agent/grpc-external/services/peerstream/subscription_manager.go b/agent/grpc-external/services/peerstream/subscription_manager.go index 0f9174dd859..dea33a551fc 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager.go +++ b/agent/grpc-external/services/peerstream/subscription_manager.go @@ -143,7 +143,7 @@ func (m *subscriptionManager) handleEvent(ctx context.Context, state *subscripti pending := &pendingPayload{} m.syncNormalServices(ctx, state, evt.Services) if m.config.ConnectEnabled { - m.syncDiscoveryChains(state, pending, evt.ListAllDiscoveryChains()) + m.syncDiscoveryChains(state, pending, evt.DiscoChains) } err := pending.Add( diff --git a/agent/grpc-external/services/peerstream/subscription_manager_test.go b/agent/grpc-external/services/peerstream/subscription_manager_test.go index 6d7a41979ce..c7b77edec96 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager_test.go +++ b/agent/grpc-external/services/peerstream/subscription_manager_test.go @@ -472,15 +472,40 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { Node: &structs.Node{Node: "foo", Address: "10.0.0.1"}, Service: &structs.NodeService{ID: "mysql-1", Service: "mysql", Port: 5000}, } + mysqlSidecar := structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + Service: "mysql-sidecar-proxy", + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "mysql", + }, + } backend.ensureNode(t, mysql.Node) backend.ensureService(t, "foo", mysql.Service) + backend.ensureService(t, "foo", &mysqlSidecar) mongo := &structs.CheckServiceNode{ - Node: &structs.Node{Node: "zip", Address: "10.0.0.3"}, - Service: &structs.NodeService{ID: "mongo-1", Service: "mongo", Port: 5000}, + Node: &structs.Node{Node: "zip", Address: "10.0.0.3"}, + Service: &structs.NodeService{ + ID: "mongo-1", + Service: "mongo", + Port: 5000, + }, + } + mongoSidecar := structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + Service: "mongo-sidecar-proxy", + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "mongo", + }, } backend.ensureNode(t, mongo.Node) backend.ensureService(t, "zip", mongo.Service) + backend.ensureService(t, "zip", &mongoSidecar) + + backend.ensureConfigEntry(t, &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "chain", + }) var ( mysqlCorrID = subExportedService + structs.NewServiceName("mysql", nil).String() diff --git a/agent/grpc-external/services/peerstream/testing.go b/agent/grpc-external/services/peerstream/testing.go index 4f0297a6c52..5eb575c06aa 100644 --- a/agent/grpc-external/services/peerstream/testing.go +++ b/agent/grpc-external/services/peerstream/testing.go @@ -150,6 +150,16 @@ func (t *incrementalTime) Now() time.Time { return t.base.Add(dur) } +// StaticNow returns the current internal clock without advancing it. +func (t *incrementalTime) StaticNow() time.Time { + t.mu.Lock() + defer t.mu.Unlock() + + dur := time.Duration(t.next) * time.Second + + return t.base.Add(dur) +} + // FutureNow will return a given future value of the Now() function. // The numerical argument indicates which future Now value you wanted. The // value must be > 0. diff --git a/agent/http_test.go b/agent/http_test.go index 39963be0417..7f95e38ce76 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/httptest" "net/netip" + "net/url" "os" "path/filepath" "runtime" @@ -140,6 +141,95 @@ func TestHTTPServer_UnixSocket_FileExists(t *testing.T) { } } +func TestHTTPSServer_UnixSocket(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + if runtime.GOOS == "windows" { + t.SkipNow() + } + + tempDir := testutil.TempDir(t, "consul") + socket := filepath.Join(tempDir, "test.sock") + + a := StartTestAgent(t, TestAgent{ + UseHTTPS: true, + HCL: ` + addresses { + https = "unix://` + socket + `" + } + unix_sockets { + mode = "0777" + } + tls { + defaults { + ca_file = "../test/client_certs/rootca.crt" + cert_file = "../test/client_certs/server.crt" + key_file = "../test/client_certs/server.key" + } + } + `, + }) + defer a.Shutdown() + + // Ensure the socket was created + if _, err := os.Stat(socket); err != nil { + t.Fatalf("err: %s", err) + } + + // Ensure the mode was set properly + fi, err := os.Stat(socket) + if err != nil { + t.Fatalf("err: %s", err) + } + if fi.Mode().String() != "Srwxrwxrwx" { + t.Fatalf("bad permissions: %s", fi.Mode()) + } + + // Make an HTTP/2-enabled client, using the API helpers to set + // up TLS to be as normal as possible for Consul. + tlscfg := &api.TLSConfig{ + Address: "consul.test", + KeyFile: "../test/client_certs/client.key", + CertFile: "../test/client_certs/client.crt", + CAFile: "../test/client_certs/rootca.crt", + } + tlsccfg, err := api.SetupTLSConfig(tlscfg) + if err != nil { + t.Fatalf("err: %v", err) + } + transport := api.DefaultConfig().Transport + transport.TLSHandshakeTimeout = 30 * time.Second + transport.TLSClientConfig = tlsccfg + if err := http2.ConfigureTransport(transport); err != nil { + t.Fatalf("err: %v", err) + } + transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socket) + } + client := &http.Client{Transport: transport} + + u, err := url.Parse("https://unix" + socket) + if err != nil { + t.Fatalf("err: %s", err) + } + u.Path = "/v1/agent/self" + u.Scheme = "https" + resp, err := client.Get(u.String()) + if err != nil { + t.Fatalf("err: %s", err) + } + defer resp.Body.Close() + + if body, err := io.ReadAll(resp.Body); err != nil || len(body) == 0 { + t.Fatalf("bad: %s %v", body, err) + } else if !strings.Contains(string(body), "NodeName") { + t.Fatalf("NodeName not found in results: %s", string(body)) + } +} + func TestSetupHTTPServer_HTTP2(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -151,9 +241,13 @@ func TestSetupHTTPServer_HTTP2(t *testing.T) { a := StartTestAgent(t, TestAgent{ UseHTTPS: true, HCL: ` - key_file = "../test/client_certs/server.key" - cert_file = "../test/client_certs/server.crt" - ca_file = "../test/client_certs/rootca.crt" + tls { + defaults { + ca_file = "../test/client_certs/rootca.crt" + cert_file = "../test/client_certs/server.crt" + key_file = "../test/client_certs/server.key" + } + } `, }) defer a.Shutdown() diff --git a/agent/metrics_test.go b/agent/metrics_test.go index 1f649dd07a5..6f75a4d233b 100644 --- a/agent/metrics_test.go +++ b/agent/metrics_test.go @@ -432,3 +432,193 @@ func TestHTTPHandlers_AgentMetrics_CACertExpiry_Prometheus(t *testing.T) { }) } + +func TestHTTPHandlers_AgentMetrics_WAL_Prometheus(t *testing.T) { + skipIfShortTesting(t) + // This test cannot use t.Parallel() since we modify global state, ie the global metrics instance + + t.Run("client agent emits nothing", func(t *testing.T) { + hcl := ` + server = false + telemetry = { + prometheus_retention_time = "5s", + disable_hostname = true + metrics_prefix = "agent_4" + } + raft_logstore { + backend = "wal" + } + bootstrap = false + ` + + a := StartTestAgent(t, TestAgent{HCL: hcl}) + defer a.Shutdown() + + respRec := httptest.NewRecorder() + recordPromMetrics(t, a, respRec) + + require.NotContains(t, respRec.Body.String(), "agent_4_raft_wal") + }) + + t.Run("server with WAL enabled emits WAL metrics", func(t *testing.T) { + hcl := ` + server = true + bootstrap = true + telemetry = { + prometheus_retention_time = "5s", + disable_hostname = true + metrics_prefix = "agent_5" + } + connect { + enabled = true + } + raft_logstore { + backend = "wal" + } + ` + + a := StartTestAgent(t, TestAgent{HCL: hcl}) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + respRec := httptest.NewRecorder() + recordPromMetrics(t, a, respRec) + + out := respRec.Body.String() + require.Contains(t, out, "agent_5_raft_wal_head_truncations") + require.Contains(t, out, "agent_5_raft_wal_last_segment_age_seconds") + require.Contains(t, out, "agent_5_raft_wal_log_appends") + require.Contains(t, out, "agent_5_raft_wal_log_entries_read") + require.Contains(t, out, "agent_5_raft_wal_log_entries_written") + require.Contains(t, out, "agent_5_raft_wal_log_entry_bytes_read") + require.Contains(t, out, "agent_5_raft_wal_log_entry_bytes_written") + require.Contains(t, out, "agent_5_raft_wal_segment_rotations") + require.Contains(t, out, "agent_5_raft_wal_stable_gets") + require.Contains(t, out, "agent_5_raft_wal_stable_sets") + require.Contains(t, out, "agent_5_raft_wal_tail_truncations") + }) + + t.Run("server without WAL enabled emits no WAL metrics", func(t *testing.T) { + hcl := ` + server = true + bootstrap = true + telemetry = { + prometheus_retention_time = "5s", + disable_hostname = true + metrics_prefix = "agent_6" + } + connect { + enabled = true + } + raft_logstore { + backend = "boltdb" + } + ` + + a := StartTestAgent(t, TestAgent{HCL: hcl}) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + respRec := httptest.NewRecorder() + recordPromMetrics(t, a, respRec) + + require.NotContains(t, respRec.Body.String(), "agent_6_raft_wal") + }) + +} + +func TestHTTPHandlers_AgentMetrics_LogVerifier_Prometheus(t *testing.T) { + skipIfShortTesting(t) + // This test cannot use t.Parallel() since we modify global state, ie the global metrics instance + + t.Run("client agent emits nothing", func(t *testing.T) { + hcl := ` + server = false + telemetry = { + prometheus_retention_time = "5s", + disable_hostname = true + metrics_prefix = "agent_4" + } + raft_logstore { + verification { + enabled = true + interval = "1s" + } + } + bootstrap = false + ` + + a := StartTestAgent(t, TestAgent{HCL: hcl}) + defer a.Shutdown() + + respRec := httptest.NewRecorder() + recordPromMetrics(t, a, respRec) + + require.NotContains(t, respRec.Body.String(), "agent_4_raft_logstore_verifier") + }) + + t.Run("server with verifier enabled emits all metrics", func(t *testing.T) { + hcl := ` + server = true + bootstrap = true + telemetry = { + prometheus_retention_time = "5s", + disable_hostname = true + metrics_prefix = "agent_5" + } + connect { + enabled = true + } + raft_logstore { + verification { + enabled = true + interval = "1s" + } + } + ` + + a := StartTestAgent(t, TestAgent{HCL: hcl}) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + respRec := httptest.NewRecorder() + recordPromMetrics(t, a, respRec) + + out := respRec.Body.String() + require.Contains(t, out, "agent_5_raft_logstore_verifier_checkpoints_written") + require.Contains(t, out, "agent_5_raft_logstore_verifier_dropped_reports") + require.Contains(t, out, "agent_5_raft_logstore_verifier_ranges_verified") + require.Contains(t, out, "agent_5_raft_logstore_verifier_read_checksum_failures") + require.Contains(t, out, "agent_5_raft_logstore_verifier_write_checksum_failures") + }) + + t.Run("server with verifier disabled emits no extra metrics", func(t *testing.T) { + hcl := ` + server = true + bootstrap = true + telemetry = { + prometheus_retention_time = "5s", + disable_hostname = true + metrics_prefix = "agent_6" + } + connect { + enabled = true + } + raft_logstore { + verification { + enabled = false + } + } + ` + + a := StartTestAgent(t, TestAgent{HCL: hcl}) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + respRec := httptest.NewRecorder() + recordPromMetrics(t, a, respRec) + + require.NotContains(t, respRec.Body.String(), "agent_6_raft_logstore_verifier") + }) + +} diff --git a/agent/prepared_query_endpoint_test.go b/agent/prepared_query_endpoint_test.go index 33240fd77ae..09012bc2a06 100644 --- a/agent/prepared_query_endpoint_test.go +++ b/agent/prepared_query_endpoint_test.go @@ -13,9 +13,10 @@ import ( "github.com/hashicorp/consul/testrpc" + "github.com/stretchr/testify/require" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/types" - "github.com/stretchr/testify/require" ) // MockPreparedQuery is a fake endpoint that we inject into the Consul server @@ -628,9 +629,9 @@ func TestPreparedQuery_Execute(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/query/not-there/execute", body) resp := httptest.NewRecorder() _, err := a.srv.PreparedQuerySpecific(resp, req) - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 404 { - t.Fatalf("expected status 404 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 404 { + t.Fatalf("expected status 404 but got %d", httpErr.StatusCode) } } else { t.Fatalf("expected HTTP error but got %v", err) @@ -768,9 +769,9 @@ func TestPreparedQuery_Explain(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/query/not-there/explain", body) resp := httptest.NewRecorder() _, err := a.srv.PreparedQuerySpecific(resp, req) - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 404 { - t.Fatalf("expected status 404 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 404 { + t.Fatalf("expected status 404 but got %d", httpErr.StatusCode) } } else { t.Fatalf("expected HTTP error but got %v", err) @@ -862,9 +863,9 @@ func TestPreparedQuery_Get(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/query/f004177f-2c28-83b7-4229-eacc25fe55d1", body) resp := httptest.NewRecorder() _, err := a.srv.PreparedQuerySpecific(resp, req) - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 404 { - t.Fatalf("expected status 404 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 404 { + t.Fatalf("expected status 404 but got %d", httpErr.StatusCode) } } else { t.Fatalf("expected HTTP error but got %v", err) diff --git a/agent/proxycfg-glue/exported_peered_services_test.go b/agent/proxycfg-glue/exported_peered_services_test.go index 8ba7390fbf5..77341e51577 100644 --- a/agent/proxycfg-glue/exported_peered_services_test.go +++ b/agent/proxycfg-glue/exported_peered_services_test.go @@ -49,6 +49,14 @@ func TestServerExportedPeeredServices(t *testing.T) { }, })) + // Create resolvers for each of the services so that they are guaranteed to be replicated by the peer stream. + for _, s := range []string{"web", "api", "db"} { + require.NoError(t, store.EnsureConfigEntry(0, &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: s, + })) + } + authz := policyAuthorizer(t, ` service "web" { policy = "read" } service "api" { policy = "read" } diff --git a/agent/proxycfg-glue/trust_bundle.go b/agent/proxycfg-glue/trust_bundle.go index 4d6cbc4d239..05d2619fa69 100644 --- a/agent/proxycfg-glue/trust_bundle.go +++ b/agent/proxycfg-glue/trust_bundle.go @@ -33,12 +33,14 @@ type serverTrustBundle struct { } func (s *serverTrustBundle) Notify(ctx context.Context, req *cachetype.TrustBundleReadRequest, correlationID string, ch chan<- proxycfg.UpdateEvent) error { - entMeta := structs.NodeEnterpriseMetaInPartition(req.Request.Partition) + // Having the ability to write a service in ANY (at least one) namespace should be + // sufficient for reading the trust bundle, which is why we use a wildcard. + entMeta := acl.NewEnterpriseMetaWithPartition(req.Request.Partition, acl.WildcardName) return watch.ServerLocalNotify(ctx, correlationID, s.deps.GetStore, func(ws memdb.WatchSet, store Store) (uint64, *pbpeering.TrustBundleReadResponse, error) { var authzCtx acl.AuthorizerContext - authz, err := s.deps.ACLResolver.ResolveTokenAndDefaultMeta(req.Token, entMeta, &authzCtx) + authz, err := s.deps.ACLResolver.ResolveTokenAndDefaultMeta(req.Token, &entMeta, &authzCtx) if err != nil { return 0, nil, err } diff --git a/agent/proxycfg-sources/local/sync.go b/agent/proxycfg-sources/local/sync.go index c6cee8c61d1..5702d2f3684 100644 --- a/agent/proxycfg-sources/local/sync.go +++ b/agent/proxycfg-sources/local/sync.go @@ -2,6 +2,7 @@ package local import ( "context" + "time" "github.com/hashicorp/go-hclog" @@ -11,6 +12,8 @@ import ( "github.com/hashicorp/consul/agent/token" ) +const resyncFrequency = 30 * time.Second + const source proxycfg.ProxySource = "local" // SyncConfig contains the dependencies required by Sync. @@ -30,6 +33,10 @@ type SyncConfig struct { // Logger will be used to write log messages. Logger hclog.Logger + + // ResyncFrequency is how often to do a resync and recreate any terminated + // watches. + ResyncFrequency time.Duration } // Sync watches the agent's local state and registers/deregisters services with @@ -50,12 +57,19 @@ func Sync(ctx context.Context, cfg SyncConfig) { cfg.State.Notify(stateCh) defer cfg.State.StopNotify(stateCh) + var resyncCh <-chan time.Time for { sync(cfg) + if resyncCh == nil && cfg.ResyncFrequency > 0 { + resyncCh = time.After(cfg.ResyncFrequency) + } + select { case <-stateCh: // Wait for a state change. + case <-resyncCh: + resyncCh = nil case <-ctx.Done(): return } diff --git a/agent/proxycfg/api_gateway.go b/agent/proxycfg/api_gateway.go index 28a6c423006..ad0d5f203dc 100644 --- a/agent/proxycfg/api_gateway.go +++ b/agent/proxycfg/api_gateway.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/consul/acl" cachetype "github.com/hashicorp/consul/agent/cache-types" "github.com/hashicorp/consul/agent/proxycfg/internal/watch" "github.com/hashicorp/consul/agent/structs" @@ -45,13 +46,13 @@ func (h *handlerAPIGateway) initialize(ctx context.Context) (ConfigSnapshot, err } // Watch the api-gateway's config entry - err = h.subscribeToConfigEntry(ctx, structs.APIGateway, h.service, gatewayConfigWatchID) + err = h.subscribeToConfigEntry(ctx, structs.APIGateway, h.service, h.proxyID.EnterpriseMeta, gatewayConfigWatchID) if err != nil { return snap, err } // Watch the bound-api-gateway's config entry - err = h.subscribeToConfigEntry(ctx, structs.BoundAPIGateway, h.service, gatewayConfigWatchID) + err = h.subscribeToConfigEntry(ctx, structs.BoundAPIGateway, h.service, h.proxyID.EnterpriseMeta, gatewayConfigWatchID) if err != nil { return snap, err } @@ -73,19 +74,20 @@ func (h *handlerAPIGateway) initialize(ctx context.Context) (ConfigSnapshot, err snap.APIGateway.WatchedDiscoveryChains = make(map[UpstreamID]context.CancelFunc) snap.APIGateway.WatchedGateways = make(map[UpstreamID]map[string]context.CancelFunc) snap.APIGateway.WatchedGatewayEndpoints = make(map[UpstreamID]map[string]structs.CheckServiceNodes) + snap.APIGateway.WatchedLocalGWEndpoints = watch.NewMap[string, structs.CheckServiceNodes]() snap.APIGateway.WatchedUpstreams = make(map[UpstreamID]map[string]context.CancelFunc) snap.APIGateway.WatchedUpstreamEndpoints = make(map[UpstreamID]map[string]structs.CheckServiceNodes) return snap, nil } -func (h *handlerAPIGateway) subscribeToConfigEntry(ctx context.Context, kind, name, watchID string) error { +func (h *handlerAPIGateway) subscribeToConfigEntry(ctx context.Context, kind, name string, entMeta acl.EnterpriseMeta, watchID string) error { return h.dataSources.ConfigEntry.Notify(ctx, &structs.ConfigEntryQuery{ Kind: kind, Name: name, Datacenter: h.source.Datacenter, QueryOptions: structs.QueryOptions{Token: h.token}, - EnterpriseMeta: h.proxyID.EnterpriseMeta, + EnterpriseMeta: entMeta, }, watchID, h.ch) } @@ -171,7 +173,7 @@ func (h *handlerAPIGateway) handleGatewayConfigUpdate(ctx context.Context, u Upd return fmt.Errorf("unexpected route kind on gateway: %s", ref.Kind) } - err := h.subscribeToConfigEntry(ctx, ref.Kind, ref.Name, routeConfigWatchID) + err := h.subscribeToConfigEntry(ctx, ref.Kind, ref.Name, ref.EnterpriseMeta, routeConfigWatchID) if err != nil { // TODO May want to continue return err @@ -184,7 +186,7 @@ func (h *handlerAPIGateway) handleGatewayConfigUpdate(ctx context.Context, u Upd seenRefs[ref] = struct{}{} snap.APIGateway.Certificates.InitWatch(ref, cancel) - err := h.subscribeToConfigEntry(ctx, ref.Kind, ref.Name, inlineCertificateConfigWatchID) + err := h.subscribeToConfigEntry(ctx, ref.Kind, ref.Name, ref.EnterpriseMeta, inlineCertificateConfigWatchID) if err != nil { // TODO May want to continue return err @@ -390,7 +392,7 @@ func (h *handlerAPIGateway) handleRouteConfigUpdate(ctx context.Context, u Updat snap.APIGateway.Upstreams.set(ref, listener, set) } snap.APIGateway.UpstreamsSet.set(ref, seenUpstreamIDs) - //snap.APIGateway.Hosts = TODO + // snap.APIGateway.Hosts = TODO snap.APIGateway.AreHostsSet = true // Stop watching any upstreams and discovery chains that have become irrelevant diff --git a/agent/proxycfg/connect_proxy.go b/agent/proxycfg/connect_proxy.go index 81b41db6940..813f5bdc960 100644 --- a/agent/proxycfg/connect_proxy.go +++ b/agent/proxycfg/connect_proxy.go @@ -3,12 +3,16 @@ package proxycfg import ( "context" "fmt" + "path" "strings" + "github.com/hashicorp/consul/acl" cachetype "github.com/hashicorp/consul/agent/cache-types" "github.com/hashicorp/consul/agent/proxycfg/internal/watch" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto/pbpeering" + "github.com/mitchellh/mapstructure" ) type handlerConnectProxy struct { @@ -103,6 +107,10 @@ func (s *handlerConnectProxy) initialize(ctx context.Context) (ConfigSnapshot, e return snap, err } + if err := s.maybeInitializeHCPMetricsWatches(ctx, snap); err != nil { + return snap, fmt.Errorf("failed to initialize HCP metrics watches: %w", err) + } + if s.proxyCfg.Mode == structs.ProxyModeTransparent { // When in transparent proxy we will infer upstreams from intentions with this source err := s.dataSources.IntentionUpstreams.Notify(ctx, &structs.ServiceSpecificRequest{ @@ -614,3 +622,66 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s } return nil } + +// hcpMetricsConfig represents the basic opaque config values for pushing telemetry to HCP. +type hcpMetricsConfig struct { + // HCPMetricsBindSocketDir is a string that configures the directory for a + // unix socket where Envoy will forward metrics. These metrics get pushed to + // the HCP Metrics collector to show service mesh metrics on HCP. + HCPMetricsBindSocketDir string `mapstructure:"envoy_hcp_metrics_bind_socket_dir"` +} + +func parseHCPMetricsConfig(m map[string]interface{}) (hcpMetricsConfig, error) { + var cfg hcpMetricsConfig + err := mapstructure.WeakDecode(m, &cfg) + + if err != nil { + return cfg, fmt.Errorf("failed to decode: %w", err) + } + + return cfg, nil +} + +// maybeInitializeHCPMetricsWatches will initialize a synthetic upstream and discovery chain +// watch for the HCP metrics collector, if metrics collection is enabled on the proxy registration. +func (s *handlerConnectProxy) maybeInitializeHCPMetricsWatches(ctx context.Context, snap ConfigSnapshot) error { + hcpCfg, err := parseHCPMetricsConfig(s.proxyCfg.Config) + if err != nil { + s.logger.Error("failed to parse connect.proxy.config", "error", err) + } + + if hcpCfg.HCPMetricsBindSocketDir == "" { + // Metrics collection is not enabled, return early. + return nil + } + + // The path includes the proxy ID so that when multiple proxies are on the same host + // they each have a distinct path to send their metrics. + sock := fmt.Sprintf("%s_%s.sock", s.proxyID.NamespaceOrDefault(), s.proxyID.ID) + path := path.Join(hcpCfg.HCPMetricsBindSocketDir, sock) + + upstream := structs.Upstream{ + DestinationNamespace: acl.DefaultNamespaceName, + DestinationPartition: s.proxyID.PartitionOrDefault(), + DestinationName: api.HCPMetricsCollectorName, + LocalBindSocketPath: path, + Config: map[string]interface{}{ + "protocol": "grpc", + }, + } + uid := NewUpstreamID(&upstream) + snap.ConnectProxy.UpstreamConfig[uid] = &upstream + + err = s.dataSources.CompiledDiscoveryChain.Notify(ctx, &structs.DiscoveryChainRequest{ + Datacenter: s.source.Datacenter, + QueryOptions: structs.QueryOptions{Token: s.token}, + Name: upstream.DestinationName, + EvaluateInDatacenter: s.source.Datacenter, + EvaluateInNamespace: uid.NamespaceOrDefault(), + EvaluateInPartition: uid.PartitionOrDefault(), + }, "discovery-chain:"+uid.String(), s.ch) + if err != nil { + return fmt.Errorf("failed to watch discovery chain for %s: %v", uid.String(), err) + } + return nil +} diff --git a/agent/proxycfg/ingress_gateway.go b/agent/proxycfg/ingress_gateway.go index b6c1cd6e029..f0f75d8107c 100644 --- a/agent/proxycfg/ingress_gateway.go +++ b/agent/proxycfg/ingress_gateway.go @@ -67,6 +67,7 @@ func (s *handlerIngressGateway) initialize(ctx context.Context) (ConfigSnapshot, snap.IngressGateway.WatchedUpstreamEndpoints = make(map[UpstreamID]map[string]structs.CheckServiceNodes) snap.IngressGateway.WatchedGateways = make(map[UpstreamID]map[string]context.CancelFunc) snap.IngressGateway.WatchedGatewayEndpoints = make(map[UpstreamID]map[string]structs.CheckServiceNodes) + snap.IngressGateway.WatchedLocalGWEndpoints = watch.NewMap[string, structs.CheckServiceNodes]() snap.IngressGateway.Listeners = make(map[IngressListenerKey]structs.IngressListener) snap.IngressGateway.UpstreamPeerTrustBundles = watch.NewMap[string, *pbpeering.PeeringTrustBundle]() snap.IngressGateway.PeerUpstreamEndpoints = watch.NewMap[UpstreamID, structs.CheckServiceNodes]() diff --git a/agent/proxycfg/manager.go b/agent/proxycfg/manager.go index eb5df5a8552..d21ff4f1ea5 100644 --- a/agent/proxycfg/manager.go +++ b/agent/proxycfg/manager.go @@ -2,6 +2,7 @@ package proxycfg import ( "errors" + "runtime/debug" "sync" "github.com/hashicorp/go-hclog" @@ -142,8 +143,22 @@ func (m *Manager) Register(id ProxyID, ns *structs.NodeService, source ProxySour m.mu.Lock() defer m.mu.Unlock() + defer func() { + if r := recover(); r != nil { + m.Logger.Error("unexpected panic during service manager registration", + "node", id.NodeName, + "service", id.ServiceID, + "message", r, + "stacktrace", string(debug.Stack()), + ) + } + }() + return m.register(id, ns, source, token, overwrite) +} + +func (m *Manager) register(id ProxyID, ns *structs.NodeService, source ProxySource, token string, overwrite bool) error { state, ok := m.proxies[id] - if ok { + if ok && !state.stoppedRunning() { if state.source != source && !overwrite { // Registered by a different source, leave as-is. return nil diff --git a/agent/proxycfg/manager_test.go b/agent/proxycfg/manager_test.go index 7d946ce823f..cad98796f73 100644 --- a/agent/proxycfg/manager_test.go +++ b/agent/proxycfg/manager_test.go @@ -95,7 +95,7 @@ func TestManager_BasicLifecycle(t *testing.T) { }) } - upstreams := structs.TestUpstreams(t) + upstreams := structs.TestUpstreams(t, false) for i := range upstreams { upstreams[i].DestinationNamespace = structs.IntentionDefaultNamespace upstreams[i].DestinationPartition = api.PartitionDefaultName diff --git a/agent/proxycfg/proxycfg.deepcopy.go b/agent/proxycfg/proxycfg.deepcopy.go index 4c0318f67bf..f1772ae72ed 100644 --- a/agent/proxycfg/proxycfg.deepcopy.go +++ b/agent/proxycfg/proxycfg.deepcopy.go @@ -310,6 +310,23 @@ func (o *configSnapshotAPIGateway) DeepCopy() *configSnapshotAPIGateway { cp.Listeners[k2] = cp_Listeners_v2 } } + if o.ListenerCertificates != nil { + cp.ListenerCertificates = make(map[IngressListenerKey][]structs.InlineCertificateConfigEntry, len(o.ListenerCertificates)) + for k2, v2 := range o.ListenerCertificates { + var cp_ListenerCertificates_v2 []structs.InlineCertificateConfigEntry + if v2 != nil { + cp_ListenerCertificates_v2 = make([]structs.InlineCertificateConfigEntry, len(v2)) + copy(cp_ListenerCertificates_v2, v2) + for i3 := range v2 { + { + retV := v2[i3].DeepCopy() + cp_ListenerCertificates_v2[i3] = *retV + } + } + } + cp.ListenerCertificates[k2] = cp_ListenerCertificates_v2 + } + } if o.BoundListeners != nil { cp.BoundListeners = make(map[string]structs.BoundAPIGatewayListener, len(o.BoundListeners)) for k2, v2 := range o.BoundListeners { diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 13c85400562..f60e62319e5 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -734,6 +734,8 @@ type configSnapshotAPIGateway struct { // Listeners is the original listener config from the api-gateway config // entry to save us trying to pass fields through Upstreams Listeners map[string]structs.APIGatewayListener + // this acts as an intermediary for inlining certificates + ListenerCertificates map[IngressListenerKey][]structs.InlineCertificateConfigEntry BoundListeners map[string]structs.BoundAPIGatewayListener } @@ -751,6 +753,9 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI watchedUpstreamEndpoints := make(map[UpstreamID]map[string]structs.CheckServiceNodes) watchedGatewayEndpoints := make(map[UpstreamID]map[string]structs.CheckServiceNodes) + // reset the cached certificates + c.ListenerCertificates = make(map[IngressListenerKey][]structs.InlineCertificateConfigEntry) + for name, listener := range c.Listeners { boundListener, ok := c.BoundListeners[name] if !ok { @@ -769,7 +774,7 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI } // Create a synthesized discovery chain for each service. - services, upstreams, compiled, err := c.synthesizeChains(datacenter, listener.Protocol, listener.Port, listener.Name, boundListener) + services, upstreams, compiled, err := c.synthesizeChains(datacenter, listener, boundListener) if err != nil { return configSnapshotIngressGateway{}, err } @@ -802,17 +807,18 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI watchedGatewayEndpoints[id] = gatewayEndpoints } + key := IngressListenerKey{ + Port: listener.Port, + Protocol: string(listener.Protocol), + } + // Configure TLS for the ingress listener - tls, err := c.toIngressTLS() + tls, err := c.toIngressTLS(key, listener, boundListener) if err != nil { return configSnapshotIngressGateway{}, err } - ingressListener.TLS = tls - key := IngressListenerKey{ - Port: listener.Port, - Protocol: string(listener.Protocol), - } + ingressListener.TLS = tls ingressListeners[key] = ingressListener ingressUpstreams[key] = upstreams } @@ -830,7 +836,7 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI }, nil } -func (c *configSnapshotAPIGateway) synthesizeChains(datacenter string, protocol structs.APIGatewayListenerProtocol, port int, name string, boundListener structs.BoundAPIGatewayListener) ([]structs.IngressService, structs.Upstreams, []*structs.CompiledDiscoveryChain, error) { +func (c *configSnapshotAPIGateway) synthesizeChains(datacenter string, listener structs.APIGatewayListener, boundListener structs.BoundAPIGatewayListener) ([]structs.IngressService, structs.Upstreams, []*structs.CompiledDiscoveryChain, error) { chains := []*structs.CompiledDiscoveryChain{} trustDomain := "" @@ -846,12 +852,13 @@ DOMAIN_LOOP: } } - synthesizer := discoverychain.NewGatewayChainSynthesizer(datacenter, trustDomain, name, c.GatewayConfig) + synthesizer := discoverychain.NewGatewayChainSynthesizer(datacenter, trustDomain, listener.Name, c.GatewayConfig) + synthesizer.SetHostname(listener.GetHostname()) for _, routeRef := range boundListener.Routes { switch routeRef.Kind { case structs.HTTPRoute: route, ok := c.HTTPRoutes.Get(routeRef) - if !ok || protocol != structs.ListenerProtocolHTTP { + if !ok || listener.Protocol != structs.ListenerProtocolHTTP { continue } synthesizer.AddHTTPRoute(*route) @@ -863,7 +870,7 @@ DOMAIN_LOOP: } case structs.TCPRoute: route, ok := c.TCPRoutes.Get(routeRef) - if !ok || protocol != structs.ListenerProtocolTCP { + if !ok || listener.Protocol != structs.ListenerProtocolTCP { continue } synthesizer.AddTCPRoute(*route) @@ -895,9 +902,9 @@ DOMAIN_LOOP: DestinationNamespace: service.NamespaceOrDefault(), DestinationPartition: service.PartitionOrDefault(), IngressHosts: service.Hosts, - LocalBindPort: port, + LocalBindPort: listener.Port, Config: map[string]interface{}{ - "protocol": string(protocol), + "protocol": string(listener.Protocol), }, }) } @@ -905,9 +912,25 @@ DOMAIN_LOOP: return services, upstreams, compiled, err } -func (c *configSnapshotAPIGateway) toIngressTLS() (*structs.GatewayTLSConfig, error) { - // TODO (t-eckert) this is dependent on future SDS work. - return &structs.GatewayTLSConfig{}, nil +func (c *configSnapshotAPIGateway) toIngressTLS(key IngressListenerKey, listener structs.APIGatewayListener, bound structs.BoundAPIGatewayListener) (*structs.GatewayTLSConfig, error) { + if len(listener.TLS.Certificates) == 0 { + return nil, nil + } + + for _, certRef := range bound.Certificates { + cert, ok := c.Certificates.Get(certRef) + if !ok { + continue + } + c.ListenerCertificates[key] = append(c.ListenerCertificates[key], *cert) + } + + return &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: listener.TLS.MinVersion, + TLSMaxVersion: listener.TLS.MaxVersion, + CipherSuites: listener.TLS.CipherSuites, + }, nil } type configSnapshotIngressGateway struct { diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index 57964d54fe1..2347b04cc53 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "reflect" + "runtime/debug" "sync/atomic" "time" @@ -82,10 +83,20 @@ type state struct { ch chan UpdateEvent snapCh chan ConfigSnapshot reqCh chan chan *ConfigSnapshot + doneCh chan struct{} rateLimiter *rate.Limiter } +func (s *state) stoppedRunning() bool { + select { + case <-s.doneCh: + return true + default: + return false + } +} + // failed returns whether run exited because a data source is in an // irrecoverable state. func (s *state) failed() bool { @@ -181,6 +192,7 @@ func newState(id ProxyID, ns *structs.NodeService, source ProxySource, token str ch: ch, snapCh: make(chan ConfigSnapshot, 1), reqCh: make(chan chan *ConfigSnapshot, 1), + doneCh: make(chan struct{}), rateLimiter: rateLimiter, }, nil } @@ -264,6 +276,9 @@ func (s *state) Watch() (<-chan ConfigSnapshot, error) { // Close discards the state and stops any long-running watches. func (s *state) Close(failed bool) error { + if s.stoppedRunning() { + return nil + } if s.cancel != nil { s.cancel() } @@ -298,6 +313,24 @@ func newConfigSnapshotFromServiceInstance(s serviceInstance, config stateConfig) } func (s *state) run(ctx context.Context, snap *ConfigSnapshot) { + // Add a recover here so than any panics do not make their way up + // into the server / agent. + defer func() { + if r := recover(); r != nil { + s.logger.Error("unexpected panic while running proxycfg", + "node", s.serviceInstance.proxyID.NodeName, + "service", s.serviceInstance.proxyID.ServiceID, + "message", r, + "stacktrace", string(debug.Stack())) + } + }() + s.unsafeRun(ctx, snap) +} + +func (s *state) unsafeRun(ctx context.Context, snap *ConfigSnapshot) { + // Closing the done channel signals that this entire state is no longer + // going to be updated. + defer close(s.doneCh) // Close the channel we return from Watch when we stop so consumers can stop // watching and clean up their goroutines. It's important we do this here and // not in Close since this routine sends on this chan and so might panic if it @@ -413,9 +446,20 @@ func (s *state) run(ctx context.Context, snap *ConfigSnapshot) { func (s *state) CurrentSnapshot() *ConfigSnapshot { // Make a chan for the response to be sent on ch := make(chan *ConfigSnapshot, 1) - s.reqCh <- ch + + select { + case <-s.doneCh: + return nil + case s.reqCh <- ch: + } + // Wait for the response - return <-ch + select { + case <-s.doneCh: + return nil + case resp := <-ch: + return resp + } } // Changed returns whether or not the passed NodeService has had any of the diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index 894f2bd4795..b83edb4af46 100644 --- a/agent/proxycfg/state_test.go +++ b/agent/proxycfg/state_test.go @@ -15,6 +15,7 @@ import ( cachetype "github.com/hashicorp/consul/agent/cache-types" "github.com/hashicorp/consul/agent/consul/discoverychain" "github.com/hashicorp/consul/agent/structs" + apimod "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto/pbpeering" "github.com/hashicorp/consul/proto/prototest" "github.com/hashicorp/consul/sdk/testutil" @@ -455,16 +456,18 @@ func TestState_WatchesAndUpdates(t *testing.T) { // Used to account for differences in OSS/ent implementations of ServiceID.String() var ( - db = structs.NewServiceName("db", nil) - billing = structs.NewServiceName("billing", nil) - api = structs.NewServiceName("api", nil) - apiA = structs.NewServiceName("api-a", nil) - - apiUID = NewUpstreamIDFromServiceName(api) - dbUID = NewUpstreamIDFromServiceName(db) - pqUID = UpstreamIDFromString("prepared_query:query") - extApiUID = NewUpstreamIDFromServiceName(apiA) - extDBUID = NewUpstreamIDFromServiceName(db) + db = structs.NewServiceName("db", nil) + billing = structs.NewServiceName("billing", nil) + api = structs.NewServiceName("api", nil) + apiA = structs.NewServiceName("api-a", nil) + hcpCollector = structs.NewServiceName(apimod.HCPMetricsCollectorName, nil) + + apiUID = NewUpstreamIDFromServiceName(api) + dbUID = NewUpstreamIDFromServiceName(db) + pqUID = UpstreamIDFromString("prepared_query:query") + extApiUID = NewUpstreamIDFromServiceName(apiA) + extDBUID = NewUpstreamIDFromServiceName(db) + hcpCollectorUID = NewUpstreamIDFromServiceName(hcpCollector) ) // TODO(peering): NewUpstreamIDFromServiceName should take a PeerName extApiUID.Peer = "peer-a" @@ -2093,6 +2096,28 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.Equal(t, dbResolver.Entry, snap.TerminatingGateway.ServiceResolvers[db]) }, }, + { + requiredWatches: map[string]verifyWatchRequest{ + "service-resolver:" + db.String(): genVerifyResolverWatch("db", "dc1", structs.ServiceResolver), + }, + events: []UpdateEvent{ + { + CorrelationID: "service-resolver:" + db.String(), + Result: &structs.ConfigEntryResponse{ + Entry: nil, + }, + Err: nil, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid(), "gateway with service list is valid") + // Finally ensure we cleaned up the resolver + require.Equal(t, []structs.ServiceName{db}, snap.TerminatingGateway.ValidServices()) + + require.False(t, snap.TerminatingGateway.ServiceResolversSet[db]) + require.Nil(t, snap.TerminatingGateway.ServiceResolvers[db]) + }, + }, { events: []UpdateEvent{ { @@ -3601,6 +3626,164 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, }, }, + "hcp-metrics": { + ns: structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + ID: "web-sidecar-proxy", + Service: "web-sidecar-proxy", + Address: "10.0.1.1", + Port: 443, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "web", + Config: map[string]interface{}{ + "envoy_hcp_metrics_bind_socket_dir": "/tmp/consul/hcp-metrics/", + }, + }, + }, + sourceDC: "dc1", + stages: []verificationStage{ + { + requiredWatches: map[string]verifyWatchRequest{ + fmt.Sprintf("discovery-chain:%s", hcpCollectorUID.String()): genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{ + Name: hcpCollector.Name, + EvaluateInDatacenter: "dc1", + EvaluateInNamespace: "default", + EvaluateInPartition: "default", + Datacenter: "dc1", + QueryOptions: structs.QueryOptions{ + Token: aclToken, + }, + }), + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.False(t, snap.Valid(), "should not be valid") + + require.Len(t, snap.ConnectProxy.DiscoveryChain, 0, "%+v", snap.ConnectProxy.DiscoveryChain) + require.Len(t, snap.ConnectProxy.WatchedDiscoveryChains, 0, "%+v", snap.ConnectProxy.WatchedDiscoveryChains) + require.Len(t, snap.ConnectProxy.WatchedUpstreams, 0, "%+v", snap.ConnectProxy.WatchedUpstreams) + require.Len(t, snap.ConnectProxy.WatchedUpstreamEndpoints, 0, "%+v", snap.ConnectProxy.WatchedUpstreamEndpoints) + }, + }, + { + events: []UpdateEvent{ + rootWatchEvent(), + { + CorrelationID: peeringTrustBundlesWatchID, + Result: peerTrustBundles, + }, + { + CorrelationID: leafWatchID, + Result: issuedCert, + Err: nil, + }, + { + CorrelationID: intentionsWatchID, + Result: TestIntentions(), + Err: nil, + }, + { + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{}, + }, + { + CorrelationID: fmt.Sprintf("discovery-chain:%s", hcpCollectorUID.String()), + Result: &structs.DiscoveryChainResponse{ + Chain: discoverychain.TestCompileConfigEntries(t, hcpCollector.Name, "default", "default", "dc1", "trustdomain.consul", nil), + }, + Err: nil, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid()) + require.Equal(t, indexedRoots, snap.Roots) + require.Equal(t, issuedCert, snap.ConnectProxy.Leaf) + + // An event was received with the HCP collector's discovery chain, which sets up some bookkeeping in the snapshot. + require.Len(t, snap.ConnectProxy.DiscoveryChain, 1, "%+v", snap.ConnectProxy.DiscoveryChain) + require.Contains(t, snap.ConnectProxy.DiscoveryChain, hcpCollectorUID) + + require.Len(t, snap.ConnectProxy.WatchedUpstreams, 1, "%+v", snap.ConnectProxy.WatchedUpstreams) + require.Len(t, snap.ConnectProxy.WatchedUpstreamEndpoints, 1, "%+v", snap.ConnectProxy.WatchedUpstreamEndpoints) + require.Contains(t, snap.ConnectProxy.WatchedUpstreamEndpoints, hcpCollectorUID) + + expectUpstream := structs.Upstream{ + DestinationNamespace: "default", + DestinationPartition: "default", + DestinationName: apimod.HCPMetricsCollectorName, + LocalBindSocketPath: "/tmp/consul/hcp-metrics/default_web-sidecar-proxy.sock", + Config: map[string]interface{}{ + "protocol": "grpc", + }, + } + uid := NewUpstreamID(&expectUpstream) + + require.Contains(t, snap.ConnectProxy.UpstreamConfig, uid) + require.Equal(t, &expectUpstream, snap.ConnectProxy.UpstreamConfig[uid]) + + // No endpoints have arrived yet. + require.Len(t, snap.ConnectProxy.WatchedUpstreamEndpoints[hcpCollectorUID], 0, "%+v", snap.ConnectProxy.WatchedUpstreamEndpoints) + }, + }, + { + requiredWatches: map[string]verifyWatchRequest{ + fmt.Sprintf("upstream-target:%s.default.default.dc1:", apimod.HCPMetricsCollectorName) + hcpCollectorUID.String(): genVerifyServiceSpecificRequest(apimod.HCPMetricsCollectorName, "", "dc1", true), + }, + events: []UpdateEvent{ + { + CorrelationID: fmt.Sprintf("upstream-target:%s.default.default.dc1:", apimod.HCPMetricsCollectorName) + hcpCollectorUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Node: "node1", + Address: "10.0.0.1", + }, + Service: &structs.NodeService{ + ID: apimod.HCPMetricsCollectorName, + Service: apimod.HCPMetricsCollectorName, + Port: 8080, + }, + }, + }, + }, + Err: nil, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid()) + require.Equal(t, indexedRoots, snap.Roots) + require.Equal(t, issuedCert, snap.ConnectProxy.Leaf) + + // Discovery chain for the HCP collector should still be stored in the snapshot. + require.Len(t, snap.ConnectProxy.DiscoveryChain, 1, "%+v", snap.ConnectProxy.DiscoveryChain) + require.Contains(t, snap.ConnectProxy.DiscoveryChain, hcpCollectorUID) + + require.Len(t, snap.ConnectProxy.WatchedUpstreams, 1, "%+v", snap.ConnectProxy.WatchedUpstreams) + require.Len(t, snap.ConnectProxy.WatchedUpstreamEndpoints, 1, "%+v", snap.ConnectProxy.WatchedUpstreamEndpoints) + require.Contains(t, snap.ConnectProxy.WatchedUpstreamEndpoints, hcpCollectorUID) + + // An endpoint arrived for the HCP collector, so it should be present in the snapshot. + require.Len(t, snap.ConnectProxy.WatchedUpstreamEndpoints[hcpCollectorUID], 1, "%+v", snap.ConnectProxy.WatchedUpstreamEndpoints) + + nodes := structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Node: "node1", + Address: "10.0.0.1", + }, + Service: &structs.NodeService{ + ID: apimod.HCPMetricsCollectorName, + Service: apimod.HCPMetricsCollectorName, + Port: 8080, + }, + }, + } + target := fmt.Sprintf("%s.default.default.dc1", apimod.HCPMetricsCollectorName) + require.Equal(t, nodes, snap.ConnectProxy.WatchedUpstreamEndpoints[hcpCollectorUID][target]) + }, + }, + }, + }, } for name, tc := range cases { diff --git a/agent/proxycfg/terminating_gateway.go b/agent/proxycfg/terminating_gateway.go index cb371ae2bf0..483c79f91dd 100644 --- a/agent/proxycfg/terminating_gateway.go +++ b/agent/proxycfg/terminating_gateway.go @@ -354,8 +354,13 @@ func (s *handlerTerminatingGateway) handleUpdate(ctx context.Context, u UpdateEv // There should only ever be one entry for a service resolver within a namespace if resolver, ok := resp.Entry.(*structs.ServiceResolverConfigEntry); ok { snap.TerminatingGateway.ServiceResolvers[sn] = resolver + snap.TerminatingGateway.ServiceResolversSet[sn] = true + } else { + // we likely have a deleted service resolver, and our cast is a nil + // cast, so clear this out + delete(snap.TerminatingGateway.ServiceResolvers, sn) + snap.TerminatingGateway.ServiceResolversSet[sn] = false } - snap.TerminatingGateway.ServiceResolversSet[sn] = true case strings.HasPrefix(u.CorrelationID, serviceIntentionsIDPrefix): resp, ok := u.Result.(structs.Intentions) diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index 8fbb247e084..7c4b85b9712 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -552,7 +552,7 @@ func TestGatewayServiceGroupBarDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "bar", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -573,7 +573,7 @@ func TestGatewayServiceGroupBarDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "bar", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -594,7 +594,7 @@ func TestGatewayServiceGroupBarDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "bar", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -620,7 +620,7 @@ func TestGatewayServiceGroupFooDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "foo", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -641,7 +641,7 @@ func TestGatewayServiceGroupFooDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "foo", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -662,7 +662,7 @@ func TestGatewayServiceGroupFooDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "foo", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -683,7 +683,7 @@ func TestGatewayServiceGroupFooDC1(t testing.T) structs.CheckServiceNodes { }, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "foo", - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, Checks: structs.HealthChecks{ diff --git a/agent/proxycfg/testing_api_gateway.go b/agent/proxycfg/testing_api_gateway.go new file mode 100644 index 00000000000..80a50e2ceba --- /dev/null +++ b/agent/proxycfg/testing_api_gateway.go @@ -0,0 +1,157 @@ +package proxycfg + +import ( + "fmt" + + "github.com/hashicorp/consul/agent/connect" + "github.com/hashicorp/consul/agent/consul/discoverychain" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/consul/agent/structs" +) + +func TestConfigSnapshotAPIGateway( + t testing.T, + variation string, + nsFn func(ns *structs.NodeService), + configFn func(entry *structs.APIGatewayConfigEntry, boundEntry *structs.BoundAPIGatewayConfigEntry), + routes []structs.BoundRoute, + certificates []structs.InlineCertificateConfigEntry, + extraUpdates []UpdateEvent, + additionalEntries ...structs.ConfigEntry, +) *ConfigSnapshot { + roots, placeholderLeaf := TestCerts(t) + + entry := &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "api-gateway", + } + boundEntry := &structs.BoundAPIGatewayConfigEntry{ + Kind: structs.BoundAPIGateway, + Name: "api-gateway", + } + + if configFn != nil { + configFn(entry, boundEntry) + } + + baseEvents := []UpdateEvent{ + { + CorrelationID: rootsWatchID, + Result: roots, + }, + { + CorrelationID: leafWatchID, + Result: placeholderLeaf, + }, + { + CorrelationID: gatewayConfigWatchID, + Result: &structs.ConfigEntryResponse{ + Entry: entry, + }, + }, + { + CorrelationID: gatewayConfigWatchID, + Result: &structs.ConfigEntryResponse{ + Entry: boundEntry, + }, + }, + } + + for _, route := range routes { + // Add the watch event for the route. + watch := UpdateEvent{ + CorrelationID: routeConfigWatchID, + Result: &structs.ConfigEntryResponse{ + Entry: route, + }, + } + baseEvents = append(baseEvents, watch) + + // Add the watch event for the discovery chain. + entries := []structs.ConfigEntry{ + &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": route.GetProtocol(), + }, + }, + &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "api-gateway", + }, + } + + // Add a discovery chain watch event for each service. + for _, serviceName := range route.GetServiceNames() { + discoChain := UpdateEvent{ + CorrelationID: fmt.Sprintf("discovery-chain:%s", UpstreamIDString("", "", serviceName.Name, &serviceName.EnterpriseMeta, "")), + Result: &structs.DiscoveryChainResponse{ + Chain: discoverychain.TestCompileConfigEntries(t, serviceName.Name, "default", "default", "dc1", connect.TestClusterID+".consul", nil, entries...), + }, + } + baseEvents = append(baseEvents, discoChain) + } + } + + for _, certificate := range certificates { + inlineCertificate := certificate + baseEvents = append(baseEvents, UpdateEvent{ + CorrelationID: inlineCertificateConfigWatchID, + Result: &structs.ConfigEntryResponse{ + Entry: &inlineCertificate, + }, + }) + } + + upstreams := structs.TestUpstreams(t, false) + + baseEvents = testSpliceEvents(baseEvents, setupTestVariationConfigEntriesAndSnapshot( + t, variation, false, upstreams, additionalEntries..., + )) + + return testConfigSnapshotFixture(t, &structs.NodeService{ + Kind: structs.ServiceKindAPIGateway, + Service: "api-gateway", + Address: "1.2.3.4", + Meta: nil, + TaggedAddresses: nil, + }, nsFn, nil, testSpliceEvents(baseEvents, extraUpdates)) +} + +// TestConfigSnapshotAPIGateway_NilConfigEntry is used to test when +// the update event for the config entry returns nil +// since this always happens on the first watch if it doesn't exist. +func TestConfigSnapshotAPIGateway_NilConfigEntry( + t testing.T, +) *ConfigSnapshot { + roots, _ := TestCerts(t) + + baseEvents := []UpdateEvent{ + { + CorrelationID: rootsWatchID, + Result: roots, + }, + { + CorrelationID: gatewayConfigWatchID, + Result: &structs.ConfigEntryResponse{ + Entry: nil, // The first watch on a config entry will return nil if the config entry doesn't exist. + }, + }, + { + CorrelationID: gatewayConfigWatchID, + Result: &structs.ConfigEntryResponse{ + Entry: nil, // The first watch on a config entry will return nil if the config entry doesn't exist. + }, + }, + } + + return testConfigSnapshotFixture(t, &structs.NodeService{ + Kind: structs.ServiceKindAPIGateway, + Service: "api-gateway", + Address: "1.2.3.4", + Meta: nil, + TaggedAddresses: nil, + }, nil, nil, testSpliceEvents(baseEvents, nil)) +} diff --git a/agent/proxycfg/testing_connect_proxy.go b/agent/proxycfg/testing_connect_proxy.go index 74ac5cb8670..5624a36e647 100644 --- a/agent/proxycfg/testing_connect_proxy.go +++ b/agent/proxycfg/testing_connect_proxy.go @@ -1,14 +1,17 @@ package proxycfg import ( + "fmt" "time" "github.com/mitchellh/go-testing-interface" "github.com/stretchr/testify/assert" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/discoverychain" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/types" ) @@ -21,7 +24,7 @@ func TestConfigSnapshot(t testing.T, nsFn func(ns *structs.NodeService), extraUp assert.True(t, dbChain.Default) var ( - upstreams = structs.TestUpstreams(t) + upstreams = structs.TestUpstreams(t, false) dbUpstream = upstreams[0] geoUpstream = upstreams[1] @@ -91,19 +94,25 @@ func TestConfigSnapshot(t testing.T, nsFn func(ns *structs.NodeService), extraUp func TestConfigSnapshotDiscoveryChain( t testing.T, variation string, + enterprise bool, nsFn func(ns *structs.NodeService), extraUpdates []UpdateEvent, additionalEntries ...structs.ConfigEntry, ) *ConfigSnapshot { roots, leaf := TestCerts(t) + var entMeta acl.EnterpriseMeta + if enterprise { + entMeta = acl.NewEnterpriseMetaWithPartition("ap1", "ns1") + } + var ( - upstreams = structs.TestUpstreams(t) + upstreams = structs.TestUpstreams(t, enterprise) geoUpstream = upstreams[1] geoUID = NewUpstreamID(&geoUpstream) - webSN = structs.ServiceIDString("web", nil) + webSN = structs.ServiceIDString("web", &entMeta) ) baseEvents := testSpliceEvents([]UpdateEvent{ @@ -136,7 +145,7 @@ func TestConfigSnapshotDiscoveryChain( }, }, }, setupTestVariationConfigEntriesAndSnapshot( - t, variation, upstreams, additionalEntries..., + t, variation, enterprise, upstreams, additionalEntries..., )) return testConfigSnapshotFixture(t, &structs.NodeService{ @@ -155,6 +164,7 @@ func TestConfigSnapshotDiscoveryChain( }, Meta: nil, TaggedAddresses: nil, + EnterpriseMeta: entMeta, }, nsFn, nil, testSpliceEvents(baseEvents, extraUpdates)) } @@ -288,3 +298,51 @@ func TestConfigSnapshotGRPCExposeHTTP1(t testing.T) *ConfigSnapshot { }, }) } + +// TestConfigSnapshotDiscoveryChain returns a fully populated snapshot using a discovery chain +func TestConfigSnapshotHCPMetrics(t testing.T) *ConfigSnapshot { + // DiscoveryChain without an UpstreamConfig should yield a + // filter chain when in transparent proxy mode + var ( + collector = structs.NewServiceName(api.HCPMetricsCollectorName, nil) + collectorUID = NewUpstreamIDFromServiceName(collector) + collectorChain = discoverychain.TestCompileConfigEntries(t, api.HCPMetricsCollectorName, "default", "default", "dc1", connect.TestClusterID+".consul", nil) + ) + + return TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Config = map[string]interface{}{ + "envoy_hcp_metrics_bind_socket_dir": "/tmp/consul/hcp-metrics", + } + }, []UpdateEvent{ + { + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: nil, + }, + }, + { + CorrelationID: "discovery-chain:" + collectorUID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: collectorChain, + }, + }, + { + CorrelationID: fmt.Sprintf("upstream-target:%s.default.default.dc1:", api.HCPMetricsCollectorName) + collectorUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: []structs.CheckServiceNode{ + { + Node: &structs.Node{ + Address: "8.8.8.8", + Datacenter: "dc1", + }, + Service: &structs.NodeService{ + Service: api.HCPMetricsCollectorName, + Address: "9.9.9.9", + Port: 9090, + }, + }, + }, + }, + }, + }) +} diff --git a/agent/proxycfg/testing_ingress_gateway.go b/agent/proxycfg/testing_ingress_gateway.go index f2f5ab67f77..5e981b9e579 100644 --- a/agent/proxycfg/testing_ingress_gateway.go +++ b/agent/proxycfg/testing_ingress_gateway.go @@ -84,11 +84,11 @@ func TestConfigSnapshotIngressGateway( }, }}) - upstreams := structs.TestUpstreams(t) + upstreams := structs.TestUpstreams(t, false) upstreams = structs.Upstreams{upstreams[0]} // just keep 'db' baseEvents = testSpliceEvents(baseEvents, setupTestVariationConfigEntriesAndSnapshot( - t, variation, upstreams, additionalEntries..., + t, variation, false, upstreams, additionalEntries..., )) } @@ -700,11 +700,13 @@ func TestConfigSnapshotIngress_HTTPMultipleServices(t testing.T) *ConfigSnapshot Kind: structs.ServiceResolver, Name: "foo", ConnectTimeout: 22 * time.Second, + RequestTimeout: 22 * time.Second, }, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "bar", ConnectTimeout: 22 * time.Second, + RequestTimeout: 22 * time.Second, }, } @@ -855,11 +857,13 @@ func TestConfigSnapshotIngress_GRPCMultipleServices(t testing.T) *ConfigSnapshot Kind: structs.ServiceResolver, Name: "foo", ConnectTimeout: 22 * time.Second, + RequestTimeout: 22 * time.Second, }, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "bar", ConnectTimeout: 22 * time.Second, + RequestTimeout: 22 * time.Second, }, } @@ -1213,12 +1217,14 @@ func TestConfigSnapshotIngressGatewayWithChain( Name: "web", EnterpriseMeta: *webEntMeta, ConnectTimeout: 22 * time.Second, + RequestTimeout: 22 * time.Second, }, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "foo", EnterpriseMeta: *fooEntMeta, ConnectTimeout: 22 * time.Second, + RequestTimeout: 22 * time.Second, }, } diff --git a/agent/proxycfg/testing_mesh_gateway.go b/agent/proxycfg/testing_mesh_gateway.go index 039807ed619..87d4f33082a 100644 --- a/agent/proxycfg/testing_mesh_gateway.go +++ b/agent/proxycfg/testing_mesh_gateway.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/discoverychain" "github.com/hashicorp/consul/agent/structs" @@ -182,6 +183,7 @@ func TestConfigSnapshotMeshGateway(t testing.T, variant string, nsFn func(ns *st Kind: structs.ServiceResolver, Name: "bar", ConnectTimeout: 10 * time.Second, + RequestTimeout: 10 * time.Second, Subsets: map[string]structs.ServiceResolverSubset{ "v1": { Filter: "Service.Meta.Version == 1", @@ -473,6 +475,8 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( discoChains = make(map[structs.ServiceName]*structs.CompiledDiscoveryChain) endpoints = make(map[structs.ServiceName]structs.CheckServiceNodes) entries []structs.ConfigEntry + // This portion of the test is not currently enterprise-aware, but we need this to satisfy a function call. + entMeta = *acl.DefaultEnterpriseMeta() ) switch variant { @@ -659,8 +663,8 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( CorrelationID: "peering-connect-service:peer-a:db", Result: &structs.IndexedCheckServiceNodes{ Nodes: structs.CheckServiceNodes{ - structs.TestCheckNodeServiceWithNameInPeer(t, "db", "peer-a", "10.40.1.1", false), - structs.TestCheckNodeServiceWithNameInPeer(t, "db", "peer-a", "10.40.1.2", false), + structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc1", "peer-a", "10.40.1.1", false, entMeta), + structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc1", "peer-a", "10.40.1.2", false, entMeta), }, }, }, @@ -668,8 +672,8 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( CorrelationID: "peering-connect-service:peer-b:alt", Result: &structs.IndexedCheckServiceNodes{ Nodes: structs.CheckServiceNodes{ - structs.TestCheckNodeServiceWithNameInPeer(t, "alt", "peer-b", "10.40.2.1", false), - structs.TestCheckNodeServiceWithNameInPeer(t, "alt", "peer-b", "10.40.2.2", true), + structs.TestCheckNodeServiceWithNameInPeer(t, "alt", "remote-dc", "peer-b", "10.40.2.1", false, entMeta), + structs.TestCheckNodeServiceWithNameInPeer(t, "alt", "remote-dc", "peer-b", "10.40.2.2", true, entMeta), }, }, }, @@ -687,6 +691,7 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( Kind: structs.ServiceResolver, Name: "db", ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, }, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, diff --git a/agent/proxycfg/testing_upstreams.go b/agent/proxycfg/testing_upstreams.go index ed2e65d8d67..8077bbb12eb 100644 --- a/agent/proxycfg/testing_upstreams.go +++ b/agent/proxycfg/testing_upstreams.go @@ -5,6 +5,7 @@ import ( "github.com/mitchellh/go-testing-interface" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/discoverychain" "github.com/hashicorp/consul/agent/structs" @@ -14,6 +15,7 @@ import ( func setupTestVariationConfigEntriesAndSnapshot( t testing.T, variation string, + enterprise bool, upstreams structs.Upstreams, additionalEntries ...structs.ConfigEntry, ) []UpdateEvent { @@ -23,7 +25,7 @@ func setupTestVariationConfigEntriesAndSnapshot( dbUID = NewUpstreamID(&dbUpstream) ) - dbChain := setupTestVariationDiscoveryChain(t, variation, additionalEntries...) + dbChain := setupTestVariationDiscoveryChain(t, variation, enterprise, dbUID.EnterpriseMeta, additionalEntries...) nodes := TestUpstreamNodes(t, "db") if variation == "register-to-terminating-gateway" { @@ -46,29 +48,42 @@ func setupTestVariationConfigEntriesAndSnapshot( }, } + dbOpts := structs.DiscoveryTargetOpts{ + Service: dbUID.Name, + Namespace: dbUID.NamespaceOrDefault(), + Partition: dbUID.PartitionOrDefault(), + Datacenter: "dc1", + } + dbChainID := structs.ChainID(dbOpts) + makeChainID := func(opts structs.DiscoveryTargetOpts) string { + return structs.ChainID(structs.MergeDiscoveryTargetOpts(dbOpts, opts)) + } + switch variation { case "default": case "simple-with-overrides": case "simple": case "external-sni": case "failover": + chainID := makeChainID(structs.DiscoveryTargetOpts{Service: "fail"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:fail.default.default.dc1:" + dbUID.String(), + CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesAlternate(t), }, }) case "failover-through-remote-gateway-triggered": events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc1:" + dbUID.String(), + CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesInStatus(t, "critical"), }, }) fallthrough case "failover-through-remote-gateway": + chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc2:" + dbUID.String(), + CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesDC2(t), }, @@ -91,10 +106,17 @@ func setupTestVariationConfigEntriesAndSnapshot( }, }, }) + uid := UpstreamID{ + Name: "db", + Peer: "cluster-01", + } + if enterprise { + uid.EnterpriseMeta = acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), "ns9") + } events = append(events, UpdateEvent{ - CorrelationID: "upstream-peer:db?peer=cluster-01", + CorrelationID: "upstream-peer:" + uid.String(), Result: &structs.IndexedCheckServiceNodes{ - Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "cluster-01", "10.40.1.1", false)}, + Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc2", "cluster-01", "10.40.1.1", false, uid.EnterpriseMeta)}, }, }) case "redirect-to-cluster-peer": @@ -109,83 +131,95 @@ func setupTestVariationConfigEntriesAndSnapshot( }, }, }) + uid := UpstreamID{ + Name: "db", + Peer: "cluster-01", + } + if enterprise { + uid.EnterpriseMeta = acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), "ns9") + } events = append(events, UpdateEvent{ - CorrelationID: "upstream-peer:db?peer=cluster-01", + CorrelationID: "upstream-peer:" + uid.String(), Result: &structs.IndexedCheckServiceNodes{ - Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "cluster-01", "10.40.1.1", false)}, + Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc2", "cluster-01", "10.40.1.1", false, uid.EnterpriseMeta)}, }, }) case "failover-through-double-remote-gateway-triggered": + chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc1:" + dbUID.String(), + CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesInStatus(t, "critical"), }, - }) - events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc2:" + dbUID.String(), - Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestUpstreamNodesInStatusDC2(t, "critical"), - }, - }) + }, + UpdateEvent{ + CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodesInStatusDC2(t, "critical"), + }, + }) fallthrough case "failover-through-double-remote-gateway": + chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc3"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc3:" + dbUID.String(), + CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesDC2(t), }, - }) - events = append(events, UpdateEvent{ - CorrelationID: "mesh-gateway:dc2:" + dbUID.String(), - Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestGatewayNodesDC2(t), - }, - }) - events = append(events, UpdateEvent{ - CorrelationID: "mesh-gateway:dc3:" + dbUID.String(), - Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestGatewayNodesDC3(t), + }, + UpdateEvent{ + CorrelationID: "mesh-gateway:dc2:" + dbUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestGatewayNodesDC2(t), + }, }, - }) + UpdateEvent{ + CorrelationID: "mesh-gateway:dc3:" + dbUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestGatewayNodesDC3(t), + }, + }) case "failover-through-local-gateway-triggered": events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc1:" + dbUID.String(), + CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesInStatus(t, "critical"), }, }) fallthrough case "failover-through-local-gateway": + chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc2:" + dbUID.String(), + CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesDC2(t), }, - }) - events = append(events, UpdateEvent{ - CorrelationID: "mesh-gateway:dc1:" + dbUID.String(), - Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestGatewayNodesDC1(t), - }, - }) + }, + UpdateEvent{ + CorrelationID: "mesh-gateway:dc1:" + dbUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestGatewayNodesDC1(t), + }, + }) case "failover-through-double-local-gateway-triggered": + db2ChainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc1:" + dbUID.String(), + CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesInStatus(t, "critical"), }, - }) - events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc2:" + dbUID.String(), - Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestUpstreamNodesInStatusDC2(t, "critical"), - }, - }) + }, + UpdateEvent{ + CorrelationID: "upstream-target:" + db2ChainID + ":" + dbUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodesInStatusDC2(t, "critical"), + }, + }) fallthrough case "failover-through-double-local-gateway": + db3ChainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc3"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:db.default.default.dc3:" + dbUID.String(), + CorrelationID: "upstream-target:" + db3ChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesDC2(t), }, @@ -197,14 +231,16 @@ func setupTestVariationConfigEntriesAndSnapshot( }, }) case "splitter-with-resolver-redirect-multidc": + v1ChainID := makeChainID(structs.DiscoveryTargetOpts{ServiceSubset: "v1"}) + v2ChainID := makeChainID(structs.DiscoveryTargetOpts{ServiceSubset: "v2", Datacenter: "dc2"}) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:v1.db.default.default.dc1:" + dbUID.String(), + CorrelationID: "upstream-target:" + v1ChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodes(t, "db"), }, }) events = append(events, UpdateEvent{ - CorrelationID: "upstream-target:v2.db.default.default.dc2:" + dbUID.String(), + CorrelationID: "upstream-target:" + v2ChainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesDC2(t), }, @@ -225,6 +261,8 @@ func setupTestVariationConfigEntriesAndSnapshot( func setupTestVariationDiscoveryChain( t testing.T, variation string, + enterprise bool, + entMeta acl.EnterpriseMeta, additionalEntries ...structs.ConfigEntry, ) *structs.CompiledDiscoveryChain { // Compile a chain. @@ -249,20 +287,25 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, }, ) case "external-sni": entries = append(entries, &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "db", - ExternalSNI: "db.some.other.service.mesh", + Kind: structs.ServiceDefaults, + Name: "db", + EnterpriseMeta: entMeta, + ExternalSNI: "db.some.other.service.mesh", }, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, }, ) case "failover": @@ -270,7 +313,9 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, Failover: map[string]structs.ServiceResolverFailover{ "*": { Service: "fail", @@ -283,8 +328,9 @@ func setupTestVariationDiscoveryChain( case "failover-through-remote-gateway": entries = append(entries, &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "db", + Kind: structs.ServiceDefaults, + Name: "db", + EnterpriseMeta: entMeta, MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, }, @@ -292,7 +338,9 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, Failover: map[string]structs.ServiceResolverFailover{ "*": { Datacenters: []string{"dc2"}, @@ -301,29 +349,44 @@ func setupTestVariationDiscoveryChain( }, ) case "failover-to-cluster-peer": + target := structs.ServiceResolverFailoverTarget{ + Peer: "cluster-01", + } + + if enterprise { + target.Namespace = "ns9" + } + entries = append(entries, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, Failover: map[string]structs.ServiceResolverFailover{ "*": { - Targets: []structs.ServiceResolverFailoverTarget{ - {Peer: "cluster-01"}, - }, + Targets: []structs.ServiceResolverFailoverTarget{target}, }, }, }, ) case "redirect-to-cluster-peer": + redirect := &structs.ServiceResolverRedirect{ + Peer: "cluster-01", + } + if enterprise { + redirect.Namespace = "ns9" + } + entries = append(entries, &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, - Redirect: &structs.ServiceResolverRedirect{ - Peer: "cluster-01", - }, + RequestTimeout: 33 * time.Second, + Redirect: redirect, }, ) case "failover-through-double-remote-gateway-triggered": @@ -331,8 +394,9 @@ func setupTestVariationDiscoveryChain( case "failover-through-double-remote-gateway": entries = append(entries, &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "db", + Kind: structs.ServiceDefaults, + Name: "db", + EnterpriseMeta: entMeta, MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, }, @@ -340,7 +404,9 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, Failover: map[string]structs.ServiceResolverFailover{ "*": { Datacenters: []string{"dc2", "dc3"}, @@ -353,8 +419,9 @@ func setupTestVariationDiscoveryChain( case "failover-through-local-gateway": entries = append(entries, &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "db", + Kind: structs.ServiceDefaults, + Name: "db", + EnterpriseMeta: entMeta, MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeLocal, }, @@ -362,7 +429,9 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, Failover: map[string]structs.ServiceResolverFailover{ "*": { Datacenters: []string{"dc2"}, @@ -375,8 +444,9 @@ func setupTestVariationDiscoveryChain( case "failover-through-double-local-gateway": entries = append(entries, &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "db", + Kind: structs.ServiceDefaults, + Name: "db", + EnterpriseMeta: entMeta, MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeLocal, }, @@ -384,7 +454,9 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, Failover: map[string]structs.ServiceResolverFailover{ "*": { Datacenters: []string{"dc2", "dc3"}, @@ -393,25 +465,29 @@ func setupTestVariationDiscoveryChain( }, ) case "splitter-with-resolver-redirect-multidc": + em := acl.NewEnterpriseMetaWithPartition(entMeta.PartitionOrDefault(), acl.NamespaceOrDefault("")) entries = append(entries, &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + EnterpriseMeta: em, Config: map[string]interface{}{ "protocol": "http", }, }, &structs.ServiceSplitterConfigEntry{ - Kind: structs.ServiceResolver, - Name: "db", + Kind: structs.ServiceResolver, + Name: "db", + EnterpriseMeta: entMeta, Splits: []structs.ServiceSplit{ {Weight: 50, Service: "db-dc1"}, {Weight: 50, Service: "db-dc2"}, }, }, &structs.ServiceResolverConfigEntry{ - Kind: structs.ServiceResolver, - Name: "db-dc1", + Kind: structs.ServiceResolver, + Name: "db-dc1", + EnterpriseMeta: entMeta, Redirect: &structs.ServiceResolverRedirect{ Service: "db", ServiceSubset: "v1", @@ -419,8 +495,9 @@ func setupTestVariationDiscoveryChain( }, }, &structs.ServiceResolverConfigEntry{ - Kind: structs.ServiceResolver, - Name: "db-dc2", + Kind: structs.ServiceResolver, + Name: "db-dc2", + EnterpriseMeta: entMeta, Redirect: &structs.ServiceResolverRedirect{ Service: "db", ServiceSubset: "v2", @@ -428,8 +505,9 @@ func setupTestVariationDiscoveryChain( }, }, &structs.ServiceResolverConfigEntry{ - Kind: structs.ServiceResolver, - Name: "db", + Kind: structs.ServiceResolver, + Name: "db", + EnterpriseMeta: entMeta, Subsets: map[string]structs.ServiceResolverSubset{ "v1": { Filter: "Service.Meta.version == v1", @@ -445,18 +523,22 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, }, &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + EnterpriseMeta: entMeta, Config: map[string]interface{}{ "protocol": "http", }, }, &structs.ServiceSplitterConfigEntry{ - Kind: structs.ServiceSplitter, - Name: "db", + Kind: structs.ServiceSplitter, + Name: "db", + EnterpriseMeta: entMeta, Splits: []structs.ServiceSplit{ { Weight: 95.5, @@ -496,18 +578,22 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, }, &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + EnterpriseMeta: entMeta, Config: map[string]interface{}{ "protocol": "grpc", }, }, &structs.ServiceRouterConfigEntry{ - Kind: structs.ServiceRouter, - Name: "db", + Kind: structs.ServiceRouter, + Name: "db", + EnterpriseMeta: entMeta, Routes: []structs.ServiceRoute{ { Match: &structs.ServiceRouteMatch{ @@ -527,18 +613,22 @@ func setupTestVariationDiscoveryChain( &structs.ServiceResolverConfigEntry{ Kind: structs.ServiceResolver, Name: "db", + EnterpriseMeta: entMeta, ConnectTimeout: 33 * time.Second, + RequestTimeout: 33 * time.Second, }, &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + EnterpriseMeta: entMeta, Config: map[string]interface{}{ "protocol": "http", }, }, &structs.ServiceSplitterConfigEntry{ - Kind: structs.ServiceSplitter, - Name: "split-3-ways", + Kind: structs.ServiceSplitter, + Name: "split-3-ways", + EnterpriseMeta: entMeta, Splits: []structs.ServiceSplit{ {Weight: 95.5, Service: "big-side"}, {Weight: 4, Service: "goldilocks-side"}, @@ -546,8 +636,9 @@ func setupTestVariationDiscoveryChain( }, }, &structs.ServiceRouterConfigEntry{ - Kind: structs.ServiceRouter, - Name: "db", + Kind: structs.ServiceRouter, + Name: "db", + EnterpriseMeta: entMeta, Routes: []structs.ServiceRoute{ { Match: httpMatch(&structs.ServiceRouteHTTPMatch{ @@ -778,23 +869,26 @@ func setupTestVariationDiscoveryChain( case "lb-resolver": entries = append(entries, &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + EnterpriseMeta: entMeta, Config: map[string]interface{}{ "protocol": "http", }, }, &structs.ServiceSplitterConfigEntry{ - Kind: structs.ServiceSplitter, - Name: "db", + Kind: structs.ServiceSplitter, + Name: "db", + EnterpriseMeta: entMeta, Splits: []structs.ServiceSplit{ {Weight: 95.5, Service: "something-else"}, {Weight: 4.5, Service: "db"}, }, }, &structs.ServiceResolverConfigEntry{ - Kind: structs.ServiceResolver, - Name: "db", + Kind: structs.ServiceResolver, + Name: "db", + EnterpriseMeta: entMeta, LoadBalancer: &structs.LoadBalancer{ Policy: "ring_hash", RingHashConfig: &structs.RingHashConfig{ @@ -833,7 +927,7 @@ func setupTestVariationDiscoveryChain( entries = append(entries, additionalEntries...) } - return discoverychain.TestCompileConfigEntries(t, "db", "default", "default", "dc1", connect.TestClusterID+".consul", compileSetup, entries...) + return discoverychain.TestCompileConfigEntries(t, "db", entMeta.NamespaceOrDefault(), entMeta.PartitionOrDefault(), "dc1", connect.TestClusterID+".consul", compileSetup, entries...) } func httpMatch(http *structs.ServiceRouteHTTPMatch) *structs.ServiceRouteMatch { diff --git a/agent/proxycfg/upstreams.go b/agent/proxycfg/upstreams.go index 367eeda62cc..b42b5e286e2 100644 --- a/agent/proxycfg/upstreams.go +++ b/agent/proxycfg/upstreams.go @@ -314,7 +314,7 @@ func (s *handlerUpstreams) resetWatchesFromChain( } opts := targetWatchOpts{upstreamID: uid} - opts.fromChainTarget(chain, target) + opts.fromChainTarget(target) err := s.watchUpstreamTarget(ctx, snap, opts) if err != nil { @@ -432,30 +432,13 @@ type targetWatchOpts struct { entMeta *acl.EnterpriseMeta } -func (o *targetWatchOpts) fromChainTarget(c *structs.CompiledDiscoveryChain, t *structs.DiscoveryTarget) { +func (o *targetWatchOpts) fromChainTarget(t *structs.DiscoveryTarget) { o.chainID = t.ID o.service = t.Service o.filter = t.Subset.Filter o.datacenter = t.Datacenter o.peer = t.Peer o.entMeta = t.GetEnterpriseMetadata() - - // The peer-targets in a discovery chain intentionally clear out - // the partition field, since we don't know the remote service's partition. - // Therefore, we must query with the chain's local partition / DC, or else - // the services will not be found. - // - // Note that the namespace is not swapped out, because it should - // always match the value in the remote datacenter (and shouldn't - // have been changed anywhere). - if o.peer != "" { - o.datacenter = "" - // Clone the enterprise meta so it's not modified when we swap the partition. - var em acl.EnterpriseMeta - em.Merge(o.entMeta) - em.OverridePartition(c.Partition) - o.entMeta = &em - } } func (s *handlerUpstreams) watchUpstreamTarget(ctx context.Context, snap *ConfigSnapshotUpstreams, opts targetWatchOpts) error { diff --git a/agent/proxycfg_test.go b/agent/proxycfg_test.go new file mode 100644 index 00000000000..11f9b8fe795 --- /dev/null +++ b/agent/proxycfg_test.go @@ -0,0 +1,138 @@ +package agent + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "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" + "github.com/hashicorp/consul/testrpc" +) + +func TestAgent_local_proxycfg(t *testing.T) { + a := NewTestAgent(t, TestACLConfig()) + defer a.Shutdown() + + testrpc.WaitForLeader(t, a.RPC, "dc1") + + token := generateUUID() + + svc := &structs.NodeService{ + ID: "db", + Service: "db", + Port: 5000, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + require.NoError(t, a.State.AddServiceWithChecks(svc, nil, token, true)) + + proxy := &structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + ID: "db-sidecar-proxy", + Service: "db-sidecar-proxy", + Port: 5000, + // Set this internal state that we expect sidecar registrations to have. + LocallyRegisteredAsSidecar: true, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "db", + Upstreams: structs.TestUpstreams(t, false), + }, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + require.NoError(t, a.State.AddServiceWithChecks(proxy, nil, token, true)) + + // This is a little gross, but this gives us the layered pair of + // local/catalog sources for now. + cfg := a.xdsServer.CfgSrc + + var ( + timer = time.After(100 * time.Millisecond) + timerFired = false + finalTimer <-chan time.Time + ) + + var ( + firstTime = true + ch <-chan *proxycfg.ConfigSnapshot + stc limiter.SessionTerminatedChan + cancel proxycfg.CancelFunc + ) + defer func() { + if cancel != nil { + cancel() + } + }() + for { + if ch == nil { + // Sign up for a stream of config snapshots, in the same manner as the xds server. + sid := proxy.CompoundServiceID() + + if firstTime { + firstTime = false + } else { + t.Logf("re-creating watch") + } + + // 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(sid, a.config.NodeName, token) + require.NoError(t, err) + } + select { + case <-stc: + t.Fatal("session unexpectedly terminated") + case snap, ok := <-ch: + if !ok { + t.Logf("channel is closed") + cancel() + ch, stc, cancel = nil, nil, nil + continue + } + require.NotNil(t, snap) + if !timerFired { + t.Fatal("should not have gotten snapshot until after we manifested the token") + } + return + case <-timer: + timerFired = true + finalTimer = time.After(1 * time.Second) + + // This simulates the eventual consistency of a token + // showing up on a server after it's creation by + // pre-creating the UUID and later using that as the + // initial SecretID for a real token. + gotToken := testWriteToken(t, a, &api.ACLToken{ + AccessorID: generateUUID(), + SecretID: token, + Description: "my token", + ServiceIdentities: []*api.ACLServiceIdentity{{ + ServiceName: "db", + }}, + }) + require.Equal(t, token, gotToken) + case <-finalTimer: + t.Fatal("did not receive a snapshot after the token manifested") + } + } + +} + +func testWriteToken(t *testing.T, a *TestAgent, tok *api.ACLToken) string { + req, _ := http.NewRequest("PUT", "/v1/acl/token", jsonReader(tok)) + req.Header.Add("X-Consul-Token", "root") + resp := httptest.NewRecorder() + a.srv.h.ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code) + + dec := json.NewDecoder(resp.Body) + aclResp := &structs.ACLToken{} + require.NoError(t, dec.Decode(aclResp)) + return aclResp.SecretID +} diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index 9a6b1adaa5d..e1a289bed5b 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -920,9 +920,12 @@ func (s *Server) TrustBundleRead(ctx context.Context, req *pbpeering.TrustBundle defer metrics.MeasureSince([]string{"peering", "trust_bundle_read"}, time.Now()) + // Having the ability to write a service in ANY (at least one) namespace should be + // sufficient for reading the trust bundle, which is why we use a wildcard. + entMeta := acl.NewEnterpriseMetaWithPartition(req.Partition, acl.WildcardName) + entMeta.Normalize() var authzCtx acl.AuthorizerContext - entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, &entMeta, &authzCtx) if err != nil { return nil, err } @@ -933,7 +936,7 @@ func (s *Server) TrustBundleRead(ctx context.Context, req *pbpeering.TrustBundle idx, trustBundle, err := s.Backend.Store().PeeringTrustBundleRead(nil, state.Query{ Value: req.Name, - EnterpriseMeta: *entMeta, + EnterpriseMeta: entMeta, }) if err != nil { return nil, fmt.Errorf("failed to read trust bundle for peer %s: %w", req.Name, err) diff --git a/agent/rpcclient/health/view.go b/agent/rpcclient/health/view.go index fd19cb4a001..ce32e801c33 100644 --- a/agent/rpcclient/health/view.go +++ b/agent/rpcclient/health/view.go @@ -50,8 +50,10 @@ func NewHealthView(req structs.ServiceSpecificRequest) (*HealthView, error) { return nil, err } return &HealthView{ - state: make(map[string]structs.CheckServiceNode), - filter: fe, + state: make(map[string]structs.CheckServiceNode), + filter: fe, + connect: req.Connect, + kind: req.ServiceKind, }, nil } @@ -61,8 +63,10 @@ func NewHealthView(req structs.ServiceSpecificRequest) (*HealthView, error) { // (IndexedCheckServiceNodes) and update it in place for each event - that // involves re-sorting each time etc. though. type HealthView struct { - state map[string]structs.CheckServiceNode - filter filterEvaluator + connect bool + kind structs.ServiceKind + state map[string]structs.CheckServiceNode + filter filterEvaluator } // Update implements View @@ -84,6 +88,13 @@ func (s *HealthView) Update(events []*pbsubscribe.Event) error { if csn == nil { return errors.New("check service node was unexpectedly nil") } + + // check if we intentionally need to skip the filter + if s.skipFilter(csn) { + s.state[id] = *csn + continue + } + passed, err := s.filter.Evaluate(*csn) if err != nil { return err @@ -100,6 +111,11 @@ func (s *HealthView) Update(events []*pbsubscribe.Event) error { return nil } +func (s *HealthView) skipFilter(csn *structs.CheckServiceNode) bool { + // we only do this for connect-enabled services that need to be routed through a terminating gateway + return s.kind == "" && s.connect && csn.Service.Kind == structs.ServiceKindTerminatingGateway +} + type filterEvaluator interface { Evaluate(datum interface{}) (bool, error) } diff --git a/agent/rpcclient/health/view_test.go b/agent/rpcclient/health/view_test.go index 8fcb50da339..f1d2cd0869d 100644 --- a/agent/rpcclient/health/view_test.go +++ b/agent/rpcclient/health/view_test.go @@ -941,3 +941,39 @@ func TestNewFilterEvaluator(t *testing.T) { }) } } + +func TestHealthView_SkipFilteringTerminatingGateways(t *testing.T) { + view, err := NewHealthView(structs.ServiceSpecificRequest{ + ServiceName: "name", + Connect: true, + QueryOptions: structs.QueryOptions{ + Filter: "Service.Meta.version == \"v1\"", + }, + }) + require.NoError(t, err) + + err = view.Update([]*pbsubscribe.Event{{ + Index: 1, + Payload: &pbsubscribe.Event_ServiceHealth{ + ServiceHealth: &pbsubscribe.ServiceHealthUpdate{ + Op: pbsubscribe.CatalogOp_Register, + CheckServiceNode: &pbservice.CheckServiceNode{ + Service: &pbservice.NodeService{ + Kind: structs.TerminatingGateway, + Service: "name", + Address: "127.0.0.1", + Port: 8443, + }, + }, + }, + }, + }}) + require.NoError(t, err) + + node, ok := (view.Result(1)).(*structs.IndexedCheckServiceNodes) + require.True(t, ok) + + require.Len(t, node.Nodes, 1) + require.Equal(t, "127.0.0.1", node.Nodes[0].Service.Address) + require.Equal(t, 8443, node.Nodes[0].Service.Port) +} diff --git a/agent/setup.go b/agent/setup.go index 01d7b7593f6..8dc5e5e18c0 100644 --- a/agent/setup.go +++ b/agent/setup.go @@ -9,6 +9,8 @@ import ( "github.com/armon/go-metrics/prometheus" "github.com/hashicorp/go-hclog" + wal "github.com/hashicorp/raft-wal" + "github.com/hashicorp/raft-wal/verifier" "google.golang.org/grpc/grpclog" autoconf "github.com/hashicorp/consul/agent/auto-config" @@ -89,7 +91,7 @@ func NewBaseDeps(configLoader ConfigLoader, logOut io.Writer, providedLogger hcl } isServer := result.RuntimeConfig.ServerMode - gauges, counters, summaries := getPrometheusDefs(cfg.Telemetry, isServer) + gauges, counters, summaries := getPrometheusDefs(cfg, isServer) cfg.Telemetry.PrometheusOpts.GaugeDefinitions = gauges cfg.Telemetry.PrometheusOpts.CounterDefinitions = counters cfg.Telemetry.PrometheusOpts.SummaryDefinitions = summaries @@ -226,7 +228,7 @@ func newConnPool(config *config.RuntimeConfig, logger hclog.Logger, tls *tlsutil // getPrometheusDefs reaches into every slice of prometheus defs we've defined in each part of the agent, and appends // all of our slices into one nice slice of definitions per metric type for the Consul agent to pass to go-metrics. -func getPrometheusDefs(cfg lib.TelemetryConfig, isServer bool) ([]prometheus.GaugeDefinition, []prometheus.CounterDefinition, []prometheus.SummaryDefinition) { +func getPrometheusDefs(cfg *config.RuntimeConfig, isServer bool) ([]prometheus.GaugeDefinition, []prometheus.CounterDefinition, []prometheus.SummaryDefinition) { // TODO: "raft..." metrics come from the raft lib and we should migrate these to a telemetry // package within. In the mean time, we're going to define a few here because they're key to monitoring Consul. raftGauges := []prometheus.GaugeDefinition{ @@ -272,6 +274,29 @@ func getPrometheusDefs(cfg lib.TelemetryConfig, isServer bool) ([]prometheus.Gau ) } + if isServer && cfg.RaftLogStoreConfig.Verification.Enabled { + verifierGauges := make([]prometheus.GaugeDefinition, 0) + for _, d := range verifier.MetricDefinitions.Gauges { + verifierGauges = append(verifierGauges, prometheus.GaugeDefinition{ + Name: []string{"raft", "logstore", "verifier", d.Name}, + Help: d.Desc, + }) + } + gauges = append(gauges, verifierGauges) + } + + if isServer && cfg.RaftLogStoreConfig.Backend == consul.LogStoreBackendWAL { + + walGauges := make([]prometheus.GaugeDefinition, 0) + for _, d := range wal.MetricDefinitions.Gauges { + walGauges = append(walGauges, prometheus.GaugeDefinition{ + Name: []string{"raft", "wal", d.Name}, + Help: d.Desc, + }) + } + gauges = append(gauges, walGauges) + } + // Flatten definitions // NOTE(kit): Do we actually want to create a set here so we can ensure definition names are unique? var gaugeDefs []prometheus.GaugeDefinition @@ -280,7 +305,7 @@ func getPrometheusDefs(cfg lib.TelemetryConfig, isServer bool) ([]prometheus.Gau // TODO(kit): Prepending the service to each definition should be handled by go-metrics var withService []prometheus.GaugeDefinition for _, gauge := range g { - gauge.Name = append([]string{cfg.MetricsPrefix}, gauge.Name...) + gauge.Name = append([]string{cfg.Telemetry.MetricsPrefix}, gauge.Name...) withService = append(withService, gauge) } gaugeDefs = append(gaugeDefs, withService...) @@ -316,6 +341,32 @@ func getPrometheusDefs(cfg lib.TelemetryConfig, isServer bool) ([]prometheus.Gau raftCounters, rate.Counters, } + + // For some unknown reason, we seem to add the raft counters above without + // checking if this is a server like we do above for some of the summaries + // above. We should probably fix that but I want to not change behavior right + // now. If we are a server, add summaries for WAL and verifier metrics. + if isServer && cfg.RaftLogStoreConfig.Verification.Enabled { + verifierCounters := make([]prometheus.CounterDefinition, 0) + for _, d := range verifier.MetricDefinitions.Counters { + verifierCounters = append(verifierCounters, prometheus.CounterDefinition{ + Name: []string{"raft", "logstore", "verifier", d.Name}, + Help: d.Desc, + }) + } + counters = append(counters, verifierCounters) + } + if isServer && cfg.RaftLogStoreConfig.Backend == consul.LogStoreBackendWAL { + walCounters := make([]prometheus.CounterDefinition, 0) + for _, d := range wal.MetricDefinitions.Counters { + walCounters = append(walCounters, prometheus.CounterDefinition{ + Name: []string{"raft", "wal", d.Name}, + Help: d.Desc, + }) + } + counters = append(counters, walCounters) + } + // Flatten definitions // NOTE(kit): Do we actually want to create a set here so we can ensure definition names are unique? var counterDefs []prometheus.CounterDefinition @@ -323,7 +374,7 @@ func getPrometheusDefs(cfg lib.TelemetryConfig, isServer bool) ([]prometheus.Gau // TODO(kit): Prepending the service to each definition should be handled by go-metrics var withService []prometheus.CounterDefinition for _, counter := range c { - counter.Name = append([]string{cfg.MetricsPrefix}, counter.Name...) + counter.Name = append([]string{cfg.Telemetry.MetricsPrefix}, counter.Name...) withService = append(withService, counter) } counterDefs = append(counterDefs, withService...) @@ -377,7 +428,7 @@ func getPrometheusDefs(cfg lib.TelemetryConfig, isServer bool) ([]prometheus.Gau // TODO(kit): Prepending the service to each definition should be handled by go-metrics var withService []prometheus.SummaryDefinition for _, summary := range s { - summary.Name = append([]string{cfg.MetricsPrefix}, summary.Name...) + summary.Name = append([]string{cfg.Telemetry.MetricsPrefix}, summary.Name...) withService = append(withService, summary) } summaryDefs = append(summaryDefs, withService...) diff --git a/agent/sidecar_service_test.go b/agent/sidecar_service_test.go index 3b4a018a52d..37854298e62 100644 --- a/agent/sidecar_service_test.go +++ b/agent/sidecar_service_test.go @@ -93,7 +93,7 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { LocalServiceAddress: "127.0.127.0", LocalServicePort: 9999, Config: map[string]interface{}{"baz": "qux"}, - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), }, }, }, @@ -118,7 +118,7 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { LocalServiceAddress: "127.0.127.0", LocalServicePort: 9999, Config: map[string]interface{}{"baz": "qux"}, - Upstreams: structs.TestAddDefaultsToUpstreams(t, structs.TestUpstreams(t), + Upstreams: structs.TestAddDefaultsToUpstreams(t, structs.TestUpstreams(t, false), *structs.DefaultEnterpriseMetaInDefaultPartition()), }, }, diff --git a/agent/structs/config_entry_discoverychain.go b/agent/structs/config_entry_discoverychain.go index 2444e90101c..9f80acf682b 100644 --- a/agent/structs/config_entry_discoverychain.go +++ b/agent/structs/config_entry_discoverychain.go @@ -857,6 +857,11 @@ type ServiceResolverConfigEntry struct { // to this service. ConnectTimeout time.Duration `json:",omitempty" alias:"connect_timeout"` + // RequestTimeout is the timeout for an HTTP request to complete before + // the connection is automatically terminated. If unspecified, defaults + // to 15 seconds. + RequestTimeout time.Duration `json:",omitempty" alias:"request_timeout"` + // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. LoadBalancer *LoadBalancer `json:",omitempty" alias:"load_balancer"` @@ -870,14 +875,19 @@ func (e *ServiceResolverConfigEntry) MarshalJSON() ([]byte, error) { type Alias ServiceResolverConfigEntry exported := &struct { ConnectTimeout string `json:",omitempty"` + RequestTimeout string `json:",omitempty"` *Alias }{ ConnectTimeout: e.ConnectTimeout.String(), + RequestTimeout: e.RequestTimeout.String(), Alias: (*Alias)(e), } if e.ConnectTimeout == 0 { exported.ConnectTimeout = "" } + if e.RequestTimeout == 0 { + exported.RequestTimeout = "" + } return json.Marshal(exported) } @@ -886,6 +896,7 @@ func (e *ServiceResolverConfigEntry) UnmarshalJSON(data []byte) error { type Alias ServiceResolverConfigEntry aux := &struct { ConnectTimeout string + RequestTimeout string *Alias }{ Alias: (*Alias)(e), @@ -899,6 +910,11 @@ func (e *ServiceResolverConfigEntry) UnmarshalJSON(data []byte) error { return err } } + if aux.RequestTimeout != "" { + if e.RequestTimeout, err = time.ParseDuration(aux.RequestTimeout); err != nil { + return err + } + } return nil } @@ -919,6 +935,7 @@ func (e *ServiceResolverConfigEntry) IsDefault() bool { e.Redirect == nil && len(e.Failover) == 0 && e.ConnectTimeout == 0 && + e.RequestTimeout == 0 && e.LoadBalancer == nil } @@ -1117,6 +1134,10 @@ func (e *ServiceResolverConfigEntry) Validate() error { return fmt.Errorf("Bad ConnectTimeout '%s', must be >= 0", e.ConnectTimeout) } + if e.RequestTimeout < 0 { + return fmt.Errorf("Bad RequestTimeout '%s', must be >= 0", e.RequestTimeout) + } + if e.LoadBalancer != nil { lb := e.LoadBalancer diff --git a/agent/structs/config_entry_discoverychain_test.go b/agent/structs/config_entry_discoverychain_test.go index b16f48f63b4..db4ac425ebe 100644 --- a/agent/structs/config_entry_discoverychain_test.go +++ b/agent/structs/config_entry_discoverychain_test.go @@ -410,8 +410,17 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) { }, }, { - name: "api-gateway", - entry: &APIGatewayConfigEntry{Name: "test"}, + name: "api-gateway", + entry: &APIGatewayConfigEntry{ + Name: "test", + Listeners: []APIGatewayListener{ + { + Name: "test", + Port: 100, + Protocol: "http", + }, + }, + }, expectACLs: []testACL{ { name: "no-authz", diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index 83bbcfb1595..32fd966f600 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -2,6 +2,7 @@ package structs import ( "fmt" + "regexp" "sort" "strings" @@ -713,6 +714,18 @@ type APIGatewayConfigEntry struct { RaftIndex } +func (e *APIGatewayConfigEntry) GetKind() string { return APIGateway } +func (e *APIGatewayConfigEntry) GetName() string { return e.Name } +func (e *APIGatewayConfigEntry) GetMeta() map[string]string { return e.Meta } +func (e *APIGatewayConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex } +func (e *APIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { return &e.EnterpriseMeta } + +var _ ControlledConfigEntry = (*APIGatewayConfigEntry)(nil) + +func (e *APIGatewayConfigEntry) GetStatus() Status { return e.Status } +func (e *APIGatewayConfigEntry) SetStatus(status Status) { e.Status = status } +func (e *APIGatewayConfigEntry) DefaultStatus() Status { return Status{} } + func (e *APIGatewayConfigEntry) ListenerIsReady(name string) bool { for _, condition := range e.Status.Conditions { if !condition.Resource.IsSame(&ResourceReference{ @@ -732,34 +745,34 @@ func (e *APIGatewayConfigEntry) ListenerIsReady(name string) bool { return true } -func (e *APIGatewayConfigEntry) GetKind() string { - return APIGateway -} - -func (e *APIGatewayConfigEntry) GetName() string { - if e == nil { - return "" - } - return e.Name -} - -func (e *APIGatewayConfigEntry) GetMeta() map[string]string { - if e == nil { - return nil - } - return e.Meta -} - func (e *APIGatewayConfigEntry) Normalize() error { for i, listener := range e.Listeners { protocol := strings.ToLower(string(listener.Protocol)) listener.Protocol = APIGatewayListenerProtocol(protocol) e.Listeners[i] = listener + + for i, cert := range listener.TLS.Certificates { + if cert.Kind == "" { + cert.Kind = InlineCertificate + } + cert.EnterpriseMeta.Merge(e.GetEnterpriseMeta()) + cert.EnterpriseMeta.Normalize() + + listener.TLS.Certificates[i] = cert + } } + return nil } func (e *APIGatewayConfigEntry) Validate() error { + if err := validateConfigEntryMeta(e.Meta); err != nil { + return err + } + + if len(e.Listeners) == 0 { + return fmt.Errorf("api gateway must have at least one listener") + } if err := e.validateListenerNames(); err != nil { return err } @@ -770,9 +783,14 @@ func (e *APIGatewayConfigEntry) Validate() error { return e.validateListeners() } +var listenerNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) + func (e *APIGatewayConfigEntry) validateListenerNames() error { listeners := make(map[string]struct{}) for _, listener := range e.Listeners { + if len(listener.Name) < 1 || !listenerNameRegex.MatchString(listener.Name) { + return fmt.Errorf("listener name %q is invalid, must be at least 1 character and contain only letters, numbers, or dashes", listener.Name) + } if _, found := listeners[listener.Name]; found { return fmt.Errorf("found multiple listeners with the name %q", listener.Name) } @@ -843,34 +861,6 @@ func (e *APIGatewayConfigEntry) CanWrite(authz acl.Authorizer) error { return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext) } -func (e *APIGatewayConfigEntry) GetRaftIndex() *RaftIndex { - if e == nil { - return &RaftIndex{} - } - return &e.RaftIndex -} - -func (e *APIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { - if e == nil { - return nil - } - return &e.EnterpriseMeta -} - -var _ ControlledConfigEntry = (*APIGatewayConfigEntry)(nil) - -func (e *APIGatewayConfigEntry) GetStatus() Status { - return e.Status -} - -func (e *APIGatewayConfigEntry) SetStatus(status Status) { - e.Status = status -} - -func (e *APIGatewayConfigEntry) DefaultStatus() Status { - return Status{} -} - // APIGatewayListenerProtocol is the protocol that an APIGateway listener uses type APIGatewayListenerProtocol string @@ -881,9 +871,8 @@ const ( // APIGatewayListener represents an individual listener for an APIGateway type APIGatewayListener struct { - // Name is the optional name of the listener in a given gateway. This is - // optional but must be unique within a gateway; therefore, if a gateway - // has more than a single listener, all but one must specify a Name. + // Name is the name of the listener in a given gateway. This must be + // unique within a gateway. Name string // Hostname is the host name that a listener should be bound to. If // unspecified, the listener accepts requests for all hostnames. @@ -897,6 +886,13 @@ type APIGatewayListener struct { TLS APIGatewayTLSConfiguration } +func (l APIGatewayListener) GetHostname() string { + if l.Hostname != "" { + return l.Hostname + } + return "*" +} + // APIGatewayTLSConfiguration specifies the configuration of a listener’s // TLS settings. type APIGatewayTLSConfiguration struct { @@ -984,25 +980,26 @@ func (e *BoundAPIGatewayConfigEntry) IsInitializedForGateway(gateway *APIGateway return true } -func (e *BoundAPIGatewayConfigEntry) GetKind() string { - return BoundAPIGateway -} +func (e *BoundAPIGatewayConfigEntry) GetKind() string { return BoundAPIGateway } +func (e *BoundAPIGatewayConfigEntry) GetName() string { return e.Name } +func (e *BoundAPIGatewayConfigEntry) GetMeta() map[string]string { return e.Meta } +func (e *BoundAPIGatewayConfigEntry) Normalize() error { + for i, listener := range e.Listeners { + for j, route := range listener.Routes { + route.EnterpriseMeta.Merge(&e.EnterpriseMeta) + route.EnterpriseMeta.Normalize() -func (e *BoundAPIGatewayConfigEntry) GetName() string { - if e == nil { - return "" - } - return e.Name -} + listener.Routes[j] = route + } + for j, cert := range listener.Certificates { + cert.EnterpriseMeta.Merge(&e.EnterpriseMeta) + cert.EnterpriseMeta.Normalize() -func (e *BoundAPIGatewayConfigEntry) GetMeta() map[string]string { - if e == nil { - return nil - } - return e.Meta -} + listener.Certificates[j] = cert + } -func (e *BoundAPIGatewayConfigEntry) Normalize() error { + e.Listeners[i] = listener + } return nil } @@ -1141,8 +1138,6 @@ func (l *BoundAPIGatewayListener) UnbindRoute(route ResourceReference) bool { return false } -func (e *BoundAPIGatewayConfigEntry) GetStatus() Status { - return Status{} -} +func (e *BoundAPIGatewayConfigEntry) GetStatus() Status { return Status{} } func (e *BoundAPIGatewayConfigEntry) SetStatus(status Status) {} func (e *BoundAPIGatewayConfigEntry) DefaultStatus() Status { return Status{} } diff --git a/agent/structs/config_entry_gateways_test.go b/agent/structs/config_entry_gateways_test.go index dd1f624ecad..1302cb2ad7a 100644 --- a/agent/structs/config_entry_gateways_test.go +++ b/agent/structs/config_entry_gateways_test.go @@ -1126,6 +1126,13 @@ func TestGatewayService_Addresses(t *testing.T) { func TestAPIGateway_Listeners(t *testing.T) { cases := map[string]configEntryTestcase{ + "no listeners defined": { + entry: &APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "api-gw-one", + }, + validateErr: "api gateway must have at least one listener", + }, "listener name conflict": { entry: &APIGatewayConfigEntry{ Kind: "api-gateway", @@ -1143,12 +1150,40 @@ func TestAPIGateway_Listeners(t *testing.T) { }, validateErr: "multiple listeners with the name", }, + "empty listener name": { + entry: &APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "api-gw-one", + Listeners: []APIGatewayListener{ + { + Port: 80, + Protocol: "tcp", + }, + }, + }, + validateErr: "listener name \"\" is invalid, must be at least 1 character and contain only letters, numbers, or dashes", + }, + "invalid listener name": { + entry: &APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "api-gw-one", + Listeners: []APIGatewayListener{ + { + Port: 80, + Protocol: "tcp", + Name: "/", + }, + }, + }, + validateErr: "listener name \"/\" is invalid, must be at least 1 character and contain only letters, numbers, or dashes", + }, "merged listener protocol conflict": { entry: &APIGatewayConfigEntry{ Kind: "api-gateway", Name: "api-gw-two", Listeners: []APIGatewayListener{ { + Name: "listener-one", Port: 80, Protocol: ListenerProtocolHTTP, }, @@ -1167,6 +1202,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-three", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", }, @@ -1185,6 +1221,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-four", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("UDP"), @@ -1199,6 +1236,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-five", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("tcp"), @@ -1213,6 +1251,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-six", Listeners: []APIGatewayListener{ { + Name: "listener", Port: -1, Protocol: APIGatewayListenerProtocol("tcp"), }, @@ -1226,6 +1265,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-seven", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "*.*.host.one", Protocol: APIGatewayListenerProtocol("http"), @@ -1240,6 +1280,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-eight", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("http"), @@ -1259,6 +1300,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-nine", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("http"), diff --git a/agent/structs/config_entry_inline_certificate.go b/agent/structs/config_entry_inline_certificate.go index 7cfc71a6b2c..bdbcbc6ae05 100644 --- a/agent/structs/config_entry_inline_certificate.go +++ b/agent/structs/config_entry_inline_certificate.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/hashicorp/consul/acl" + "github.com/miekg/dns" ) // InlineCertificateConfigEntry manages the configuration for an inline certificate @@ -29,19 +30,20 @@ type InlineCertificateConfigEntry struct { RaftIndex } -func (e *InlineCertificateConfigEntry) GetKind() string { - return InlineCertificate -} - -func (e *InlineCertificateConfigEntry) GetName() string { - return e.Name -} - -func (e *InlineCertificateConfigEntry) Normalize() error { - return nil +func (e *InlineCertificateConfigEntry) GetKind() string { return InlineCertificate } +func (e *InlineCertificateConfigEntry) GetName() string { return e.Name } +func (e *InlineCertificateConfigEntry) Normalize() error { return nil } +func (e *InlineCertificateConfigEntry) GetMeta() map[string]string { return e.Meta } +func (e *InlineCertificateConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { + return &e.EnterpriseMeta } +func (e *InlineCertificateConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex } func (e *InlineCertificateConfigEntry) Validate() error { + if err := validateConfigEntryMeta(e.Meta); err != nil { + return err + } + privateKeyBlock, _ := pem.Decode([]byte(e.PrivateKey)) if privateKeyBlock == nil { return errors.New("failed to parse private key PEM") @@ -64,9 +66,45 @@ func (e *InlineCertificateConfigEntry) Validate() error { return err } + // validate that each host referenced in the CN, DNSSans, and IPSans + // are valid hostnames + hosts, err := e.Hosts() + if err != nil { + return err + } + for _, host := range hosts { + if _, ok := dns.IsDomainName(host); !ok { + return fmt.Errorf("host %q must be a valid DNS hostname", host) + } + } + return nil } +func (e *InlineCertificateConfigEntry) Hosts() ([]string, error) { + certificateBlock, _ := pem.Decode([]byte(e.Certificate)) + if certificateBlock == nil { + return nil, errors.New("failed to parse certificate PEM") + } + + certificate, err := x509.ParseCertificate(certificateBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %w", err) + } + + hosts := []string{certificate.Subject.CommonName} + + for _, name := range certificate.DNSNames { + hosts = append(hosts, name) + } + + for _, ip := range certificate.IPAddresses { + hosts = append(hosts, ip.String()) + } + + return hosts, nil +} + func (e *InlineCertificateConfigEntry) CanRead(authz acl.Authorizer) error { var authzContext acl.AuthorizerContext e.FillAuthzContext(&authzContext) @@ -78,24 +116,3 @@ func (e *InlineCertificateConfigEntry) CanWrite(authz acl.Authorizer) error { e.FillAuthzContext(&authzContext) return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext) } - -func (e *InlineCertificateConfigEntry) GetMeta() map[string]string { - if e == nil { - return nil - } - return e.Meta -} - -func (e *InlineCertificateConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { - if e == nil { - return nil - } - return &e.EnterpriseMeta -} - -func (e *InlineCertificateConfigEntry) GetRaftIndex() *RaftIndex { - if e == nil { - return &RaftIndex{} - } - return &e.RaftIndex -} diff --git a/agent/structs/config_entry_inline_certificate_test.go b/agent/structs/config_entry_inline_certificate_test.go index 3e9d84e4f4b..db46ca92a7d 100644 --- a/agent/structs/config_entry_inline_certificate_test.go +++ b/agent/structs/config_entry_inline_certificate_test.go @@ -5,6 +5,73 @@ import "testing" const ( // generated via openssl req -x509 -sha256 -days 1825 -newkey rsa:2048 -keyout private.key -out certificate.crt validPrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg +5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F +eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga +9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv +OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz +9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67 +yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7 +OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs +qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo +Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5 +LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6 +Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg +llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn +gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ +eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA +hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn +orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN +JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC +2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s +1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn +IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS +2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq +wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe +RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK ++QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ== +-----END RSA PRIVATE KEY-----` + validCertificate = `-----BEGIN CERTIFICATE----- +MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD +VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx +NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM +AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc +MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq +oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P +BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4 +GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH +bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6 +8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF +AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N +L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc +U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi +SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq +MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq +J3TrQE3YVrL4D9xnklT86WDnZKApJg== +-----END CERTIFICATE-----` + mismatchedCertificate = `-----BEGIN CERTIFICATE----- +MIIDQjCCAioCCQC2H6+PYz23xDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQwwCgYD +VQQLDANGb28xHTAbBgNVBAMMFG90aGVyLmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx +NzAyMTM0OVoXDTI4MDIxNjAyMTM0OVowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM +AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRm9vMR0w +GwYDVQQDDBRvdGhlci5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAO0IH/dzmWJaTPVL32xQVHivrnQk38vskW0ymILYuaismUMJ +0+xrcaTcVljU+3nKhmSW9wcYSFY02GcGWAdcw8x8xO801cna020T+DIWiYaljXT3 +agrbYfULF9q+ihT6IL1D2mFa0AW1x6Bk1XAmZRSTpRBhp7iFNnCXGRK8sSSr95ge +DxaRyj/2F8t6kG+ANPkRBiPd2rRgsYQjuTLuZYBvseeJygnSF8ty1QMg6koz7kdN +bPon3Q5GFH71WNwzm9G3DWjMIu+dhpHz7rsbCnhwLB5lh1jsZBYkAMt3kiyY0g4I +ReuiVWesMe+AMG/DQZvZ5mE252QFJ92dLTeo5RcCAwEAATANBgkqhkiG9w0BAQsF +AAOCAQEAijm6blixjl+pMRAj7EajoPjU+GqhooZayJrvdwvofwcPxQYpkPuh7Uc6 +l2z494b75cRzMw7wS+iW/ad8NYrfw1JwHMsUfncxs5LDO5GsKl9Krg/39goDl3wC +ywTcl00y+FMYfldNPjKDLunENmn+yPa2pKuBVQ0yOKALp+oUeJFVzRNPV5fohlBi +HjypkO0KaVmCG6P01cqCgVkNzxnX9qQYP3YXX1yt5iOcI7QcoOa5WnRhOuD8WqJ1 +v3AZGYNvKyXf9E5nD0y2Cmz6t1awjFjzMlXMx6AdHrjWqxtHhYQ1xz4P4NfzK27m +cCtURSzXMgcrSeZLepBfdICf+0/0+Q== +-----END CERTIFICATE-----` + emptyCNPrivateKey = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ httBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo NvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC @@ -31,7 +98,7 @@ T0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj nZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX kHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/ -----END RSA PRIVATE KEY-----` - validCertificate = `-----BEGIN CERTIFICATE----- + emptyCNCertificate = `-----BEGIN CERTIFICATE----- MIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV UzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB @@ -46,26 +113,12 @@ RahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK NtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO qwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww AAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r ------END CERTIFICATE-----` - mismatchedCertificate = `-----BEGIN CERTIFICATE----- -MIICljCCAX4CCQC49bq8e0QgLDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV -UzAeFw0yMjEyMjAxNzUyMzJaFw0yNzEyMTkxNzUyMzJaMA0xCzAJBgNVBAYTAlVT -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7Are9ulVDY0IqaG5Pt/ -OVuS0kmDhgVUfQBM5JDGRfIsu1ebn68kn5JGCTQ+nC8nU9QXRJS7vG6As5GWm08W -FpkOyIbHLjOhWtYCYzQ+0R+sSSoMnczgl8l6wIUIkR3Vpoy6QUsSZbvo4/xDi3Uk -1CF+JMTM2oFDLD8PNrNzW/txRyTugK36W1G1ofUhvP6EHsTjmVcZwBcLOKToov6L -Ai758MLztl1/X/90DNdZwuHC9fGIgx52Ojz3+XIocXFttr+J8xZglMCtqL4n40bh -5b1DE+hC3NHQmA+7Chc99z28baj2cU1woNk/TO+ewqpyvj+WPWwGOQt3U63ZoPaw -yQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCMF3JlrDdcSv2KYrxEp1tWB/GglI8a -JiSvrf3hePaRz59099bg4DoHzTn0ptOcOPOO9epDPbCJrUqLuPlwvrQRvll6GaW1 -y3TcbnE1AbwTAjbOTgpLhvuj6IVlyNNLoKbjZqs4A8N8i6UkQ7Y8qg77lwxD3QoH -pWLwGZKJifKPa7ObVWmKj727kbU59nA2Hx+Y4qa/MyiPWxJM9Y0JsFGxSBxp4kmQ -q4ikzSWaPv/TvtV+d4mO1H44aggdNMCYIQd/5BXQzG40l+ecHnBueJyG312ax/Zp -NsYUAKQT864cGlxrnWVgT4sW/tsl9Qen7g9iAdeBAPvLO7cQjAjtc7KZ -----END CERTIFICATE-----` ) func TestInlineCertificate(t *testing.T) { + t.Parallel() + cases := map[string]configEntryTestcase{ "invalid private key": { entry: &InlineCertificateConfigEntry{ @@ -101,6 +154,15 @@ func TestInlineCertificate(t *testing.T) { Certificate: validCertificate, }, }, + "empty cn certificate": { + entry: &InlineCertificateConfigEntry{ + Kind: InlineCertificate, + Name: "cert-five", + PrivateKey: emptyCNPrivateKey, + Certificate: emptyCNCertificate, + }, + validateErr: "host \"\" must be a valid DNS hostname", + }, } testConfigEntryNormalizeAndValidate(t, cases) } diff --git a/agent/structs/config_entry_routes.go b/agent/structs/config_entry_routes.go index 7c959d79b0b..ca092c07d19 100644 --- a/agent/structs/config_entry_routes.go +++ b/agent/structs/config_entry_routes.go @@ -2,6 +2,9 @@ package structs import ( "fmt" + "strings" + + "github.com/miekg/dns" "github.com/hashicorp/consul/acl" ) @@ -40,54 +43,247 @@ type HTTPRouteConfigEntry struct { RaftIndex } +func (e *HTTPRouteConfigEntry) GetKind() string { return HTTPRoute } +func (e *HTTPRouteConfigEntry) GetName() string { return e.Name } +func (e *HTTPRouteConfigEntry) GetMeta() map[string]string { return e.Meta } +func (e *HTTPRouteConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { return &e.EnterpriseMeta } +func (e *HTTPRouteConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex } + +var _ ControlledConfigEntry = (*HTTPRouteConfigEntry)(nil) + +func (e *HTTPRouteConfigEntry) GetStatus() Status { return e.Status } +func (e *HTTPRouteConfigEntry) SetStatus(status Status) { e.Status = status } +func (e *HTTPRouteConfigEntry) DefaultStatus() Status { return Status{} } + +var _ BoundRoute = (*HTTPRouteConfigEntry)(nil) + +func (e *HTTPRouteConfigEntry) GetParents() []ResourceReference { return e.Parents } +func (e *HTTPRouteConfigEntry) GetProtocol() APIGatewayListenerProtocol { return ListenerProtocolHTTP } + +func (e *HTTPRouteConfigEntry) GetServiceNames() []ServiceName { + services := []ServiceName{} + for _, service := range e.GetServices() { + services = append(services, NewServiceName(service.Name, &service.EnterpriseMeta)) + } + return services +} + func (e *HTTPRouteConfigEntry) GetServices() []HTTPService { targets := []HTTPService{} for _, rule := range e.Rules { - for _, service := range rule.Services { - targets = append(targets, service) - } + targets = append(targets, rule.Services...) } return targets } -func (e *HTTPRouteConfigEntry) GetServiceNames() []ServiceName { - services := []ServiceName{} - for _, service := range e.GetServices() { - services = append(services, NewServiceName(service.Name, &service.EnterpriseMeta)) +func (e *HTTPRouteConfigEntry) Normalize() error { + for i, parent := range e.Parents { + if parent.Kind == "" { + parent.Kind = APIGateway + } + parent.EnterpriseMeta.Merge(e.GetEnterpriseMeta()) + parent.EnterpriseMeta.Normalize() + e.Parents[i] = parent } - return services + + for i, rule := range e.Rules { + for j, match := range rule.Matches { + rule.Matches[j] = normalizeHTTPMatch(match) + } + + for j, service := range rule.Services { + rule.Services[j] = e.normalizeHTTPService(service) + } + e.Rules[i] = rule + } + + return nil } -func (e *HTTPRouteConfigEntry) GetKind() string { - return HTTPRoute +func (e *HTTPRouteConfigEntry) normalizeHTTPService(service HTTPService) HTTPService { + service.EnterpriseMeta.Merge(e.GetEnterpriseMeta()) + service.EnterpriseMeta.Normalize() + if service.Weight <= 0 { + service.Weight = 1 + } + return service } -func (e *HTTPRouteConfigEntry) GetName() string { - if e == nil { - return "" +func normalizeHTTPMatch(match HTTPMatch) HTTPMatch { + method := string(match.Method) + method = strings.ToUpper(method) + match.Method = HTTPMatchMethod(method) + + pathMatch := match.Path.Match + if string(pathMatch) == "" { + match.Path.Match = HTTPPathMatchPrefix + match.Path.Value = "/" } - return e.Name + + return match } -func (e *HTTPRouteConfigEntry) GetParents() []ResourceReference { - if e == nil { - return []ResourceReference{} +func (e *HTTPRouteConfigEntry) Validate() error { + for _, host := range e.Hostnames { + // validate that each host referenced in a valid dns name and has + // no wildcards in it + if _, ok := dns.IsDomainName(host); !ok { + return fmt.Errorf("host %q must be a valid DNS hostname", host) + } + + if strings.ContainsRune(host, '*') { + return fmt.Errorf("host %q must not be a wildcard", host) + } + } + + validParentKinds := map[string]bool{ + APIGateway: true, } - return e.Parents + + for _, parent := range e.Parents { + if !validParentKinds[parent.Kind] { + return fmt.Errorf("unsupported parent kind: %q, must be 'api-gateway'", parent.Kind) + } + } + + if err := validateConfigEntryMeta(e.Meta); err != nil { + return err + } + + for i, rule := range e.Rules { + if err := validateRule(rule); err != nil { + return fmt.Errorf("Rule[%d], %w", i, err) + } + } + + return nil } -func (e *HTTPRouteConfigEntry) GetProtocol() APIGatewayListenerProtocol { - return ListenerProtocolHTTP +func validateRule(rule HTTPRouteRule) error { + if err := validateFilters(rule.Filters); err != nil { + return err + } + + for i, match := range rule.Matches { + if err := validateMatch(match); err != nil { + return fmt.Errorf("Match[%d], %w", i, err) + } + } + + for i, service := range rule.Services { + if err := validateHTTPService(service); err != nil { + return fmt.Errorf("Service[%d], %w", i, err) + } + } + + return nil } -func (e *HTTPRouteConfigEntry) Normalize() error { +func validateMatch(match HTTPMatch) error { + if match.Method != HTTPMatchMethodAll { + if !isValidHTTPMethod(string(match.Method)) { + return fmt.Errorf("Method contains an invalid method %q", match.Method) + } + } + + for i, query := range match.Query { + if err := validateHTTPQueryMatch(query); err != nil { + return fmt.Errorf("Query[%d], %w", i, err) + } + } + + for i, header := range match.Headers { + if err := validateHTTPHeaderMatch(header); err != nil { + return fmt.Errorf("Headers[%d], %w", i, err) + } + } + + if err := validateHTTPPathMatch(match.Path); err != nil { + return fmt.Errorf("Path, %w", err) + } + return nil } -func (e *HTTPRouteConfigEntry) Validate() error { +func validateHTTPService(service HTTPService) error { + return validateFilters(service.Filters) +} + +func validateFilters(filter HTTPFilters) error { + for i, header := range filter.Headers { + if err := validateHeaderFilter(header); err != nil { + return fmt.Errorf("HTTPFilters, Headers[%d], %w", i, err) + } + } + + if err := validateURLRewrite(filter.URLRewrite); err != nil { + return fmt.Errorf("HTTPFilters, URLRewrite, %w", err) + } + + return nil +} + +func validateURLRewrite(rewrite *URLRewrite) error { + // TODO: we don't really have validation of the actual params + // passed as "PrefixRewrite" in our discoverychain config + // entries, figure out if we should have something here return nil } +func validateHeaderFilter(filter HTTPHeaderFilter) error { + // TODO: we don't really have validation of the values + // passed as header modifiers in our current discoverychain + // config entries, figure out if we need to + return nil +} + +func validateHTTPQueryMatch(query HTTPQueryMatch) error { + if query.Name == "" { + return fmt.Errorf("missing required Name field") + } + + switch query.Match { + case HTTPQueryMatchExact, + HTTPQueryMatchPresent, + HTTPQueryMatchRegularExpression: + return nil + default: + return fmt.Errorf("match type should be one of present, exact, or regex") + } +} + +func validateHTTPHeaderMatch(header HTTPHeaderMatch) error { + if header.Name == "" { + return fmt.Errorf("missing required Name field") + } + + switch header.Match { + case HTTPHeaderMatchExact, + HTTPHeaderMatchPrefix, + HTTPHeaderMatchRegularExpression, + HTTPHeaderMatchSuffix, + HTTPHeaderMatchPresent: + return nil + default: + return fmt.Errorf("match type should be one of present, exact, prefix, suffix, or regex") + } +} + +func validateHTTPPathMatch(path HTTPPathMatch) error { + switch path.Match { + case HTTPPathMatchExact, + HTTPPathMatchPrefix: + if !strings.HasPrefix(path.Value, "/") { + return fmt.Errorf("%s type match doesn't start with '/': %q", path.Match, path.Value) + } + fallthrough + case HTTPPathMatchRegularExpression: + return nil + default: + return fmt.Errorf("match type should be one of exact, prefix, or regex") + } +} + func (e *HTTPRouteConfigEntry) CanRead(authz acl.Authorizer) error { var authzContext acl.AuthorizerContext e.FillAuthzContext(&authzContext) @@ -100,25 +296,29 @@ func (e *HTTPRouteConfigEntry) CanWrite(authz acl.Authorizer) error { return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext) } -func (e *HTTPRouteConfigEntry) GetMeta() map[string]string { - if e == nil { - return nil +func (e *HTTPRouteConfigEntry) FilteredHostnames(listenerHostname string) []string { + if len(e.Hostnames) == 0 { + // we have no hostnames specified here, so treat it like a wildcard + return []string{listenerHostname} } - return e.Meta -} -func (e *HTTPRouteConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { - if e == nil { - return nil - } - return &e.EnterpriseMeta -} + wildcardHostname := strings.ContainsRune(listenerHostname, '*') || listenerHostname == "*" + listenerHostname = strings.TrimPrefix(strings.TrimPrefix(listenerHostname, "*"), ".") + + hostnames := []string{} + for _, hostname := range e.Hostnames { + if wildcardHostname { + if strings.HasSuffix(hostname, listenerHostname) { + hostnames = append(hostnames, hostname) + } + continue + } -func (e *HTTPRouteConfigEntry) GetRaftIndex() *RaftIndex { - if e == nil { - return &RaftIndex{} + if hostname == listenerHostname { + hostnames = append(hostnames, hostname) + } } - return &e.RaftIndex + return hostnames } // HTTPMatch specifies the criteria that should be @@ -206,8 +406,8 @@ type HTTPQueryMatch struct { // HTTPFilters specifies a list of filters used to modify a request // before it is routed to an upstream. type HTTPFilters struct { - Headers []HTTPHeaderFilter - URLRewrites []URLRewrite + Headers []HTTPHeaderFilter + URLRewrite *URLRewrite } // HTTPHeaderFilter specifies how HTTP headers should be modified. @@ -246,27 +446,13 @@ type HTTPService struct { // to routing it to the upstream service Filters HTTPFilters - acl.EnterpriseMeta + acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` } func (s HTTPService) ServiceName() ServiceName { return NewServiceName(s.Name, &s.EnterpriseMeta) } -var _ ControlledConfigEntry = (*HTTPRouteConfigEntry)(nil) - -func (e *HTTPRouteConfigEntry) GetStatus() Status { - return e.Status -} - -func (e *HTTPRouteConfigEntry) SetStatus(status Status) { - e.Status = status -} - -func (e *HTTPRouteConfigEntry) DefaultStatus() Status { - return Status{} -} - // TCPRouteConfigEntry manages the configuration for a TCP route // with the given name. type TCPRouteConfigEntry struct { @@ -291,9 +477,22 @@ type TCPRouteConfigEntry struct { RaftIndex } -func (e *TCPRouteConfigEntry) GetServices() []TCPService { - return e.Services -} +func (e *TCPRouteConfigEntry) GetKind() string { return TCPRoute } +func (e *TCPRouteConfigEntry) GetName() string { return e.Name } +func (e *TCPRouteConfigEntry) GetMeta() map[string]string { return e.Meta } +func (e *TCPRouteConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex } +func (e *TCPRouteConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { return &e.EnterpriseMeta } + +var _ ControlledConfigEntry = (*TCPRouteConfigEntry)(nil) + +func (e *TCPRouteConfigEntry) GetStatus() Status { return e.Status } +func (e *TCPRouteConfigEntry) SetStatus(status Status) { e.Status = status } +func (e *TCPRouteConfigEntry) DefaultStatus() Status { return Status{} } + +var _ BoundRoute = (*TCPRouteConfigEntry)(nil) + +func (e *TCPRouteConfigEntry) GetParents() []ResourceReference { return e.Parents } +func (e *TCPRouteConfigEntry) GetProtocol() APIGatewayListenerProtocol { return ListenerProtocolTCP } func (e *TCPRouteConfigEntry) GetServiceNames() []ServiceName { services := []ServiceName{} @@ -303,42 +502,24 @@ func (e *TCPRouteConfigEntry) GetServiceNames() []ServiceName { return services } -func (e *TCPRouteConfigEntry) GetKind() string { - return TCPRoute -} - -func (e *TCPRouteConfigEntry) GetName() string { - if e == nil { - return "" - } - return e.Name -} - -func (e *TCPRouteConfigEntry) GetParents() []ResourceReference { - if e == nil { - return []ResourceReference{} - } - return e.Parents -} - -func (e *TCPRouteConfigEntry) GetProtocol() APIGatewayListenerProtocol { - return ListenerProtocolTCP -} - -func (e *TCPRouteConfigEntry) GetMeta() map[string]string { - if e == nil { - return nil - } - return e.Meta -} +func (e *TCPRouteConfigEntry) GetServices() []TCPService { return e.Services } func (e *TCPRouteConfigEntry) Normalize() error { for i, parent := range e.Parents { if parent.Kind == "" { parent.Kind = APIGateway - e.Parents[i] = parent } + parent.EnterpriseMeta.Merge(e.GetEnterpriseMeta()) + parent.EnterpriseMeta.Normalize() + e.Parents[i] = parent + } + + for i, service := range e.Services { + service.EnterpriseMeta.Merge(e.GetEnterpriseMeta()) + service.EnterpriseMeta.Normalize() + e.Services[i] = service } + return nil } @@ -355,6 +536,11 @@ func (e *TCPRouteConfigEntry) Validate() error { return fmt.Errorf("unsupported parent kind: %q, must be 'api-gateway'", parent.Kind) } } + + if err := validateConfigEntryMeta(e.Meta); err != nil { + return err + } + return nil } @@ -370,42 +556,11 @@ func (e *TCPRouteConfigEntry) CanWrite(authz acl.Authorizer) error { return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext) } -func (e *TCPRouteConfigEntry) GetRaftIndex() *RaftIndex { - if e == nil { - return &RaftIndex{} - } - return &e.RaftIndex -} - -func (e *TCPRouteConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { - if e == nil { - return nil - } - return &e.EnterpriseMeta -} - -var _ ControlledConfigEntry = (*TCPRouteConfigEntry)(nil) - -func (e *TCPRouteConfigEntry) GetStatus() Status { - return e.Status -} - -func (e *TCPRouteConfigEntry) SetStatus(status Status) { - e.Status = status -} - -func (e *TCPRouteConfigEntry) DefaultStatus() Status { - return Status{} -} - // TCPService is a service reference for a TCPRoute type TCPService struct { Name string - // Weight specifies the proportion of requests forwarded to the referenced service. - // This is computed as weight/(sum of all weights in the list of services). - Weight int - acl.EnterpriseMeta + acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` } func (s TCPService) ServiceName() ServiceName { diff --git a/agent/structs/config_entry_routes_test.go b/agent/structs/config_entry_routes_test.go index dc89ca9e090..37c20390a31 100644 --- a/agent/structs/config_entry_routes_test.go +++ b/agent/structs/config_entry_routes_test.go @@ -3,10 +3,13 @@ package structs import ( "testing" + "github.com/hashicorp/consul/acl" "github.com/stretchr/testify/require" ) func TestTCPRoute(t *testing.T) { + t.Parallel() + cases := map[string]configEntryTestcase{ "multiple services": { entry: &TCPRouteConfigEntry{ @@ -34,8 +37,9 @@ func TestTCPRoute(t *testing.T) { normalizeOnly: true, check: func(t *testing.T, entry ConfigEntry) { expectedParent := ResourceReference{ - Kind: APIGateway, - Name: "gateway", + Kind: APIGateway, + Name: "gateway", + EnterpriseMeta: *acl.DefaultEnterpriseMeta(), } route := entry.(*TCPRouteConfigEntry) require.Len(t, route.Parents, 1) @@ -56,3 +60,230 @@ func TestTCPRoute(t *testing.T) { } testConfigEntryNormalizeAndValidate(t, cases) } + +func TestHTTPRoute(t *testing.T) { + t.Parallel() + + cases := map[string]configEntryTestcase{ + "normalize parent kind": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-one", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + }, + normalizeOnly: true, + check: func(t *testing.T, entry ConfigEntry) { + expectedParent := ResourceReference{ + Kind: APIGateway, + Name: "gateway", + EnterpriseMeta: *acl.DefaultEnterpriseMeta(), + } + route := entry.(*HTTPRouteConfigEntry) + require.Len(t, route.Parents, 1) + require.Equal(t, expectedParent, route.Parents[0]) + }, + }, + "invalid parent kind": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Kind: "route", + Name: "gateway", + }}, + }, + validateErr: "unsupported parent kind", + }, + "wildcard hostnames": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Hostnames: []string{"*"}, + }, + validateErr: "host \"*\" must not be a wildcard", + }, + "wildcard subdomain": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Hostnames: []string{"*.consul.example"}, + }, + validateErr: "host \"*.consul.example\" must not be a wildcard", + }, + "valid dns hostname": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Hostnames: []string{"...not legal"}, + }, + validateErr: "host \"...not legal\" must be a valid DNS hostname", + }, + "rule matches invalid header match type": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Headers: []HTTPHeaderMatch{{ + Match: HTTPHeaderMatchType("foo"), + Name: "foo", + }}, + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Headers[0], match type should be one of present, exact, prefix, suffix, or regex", + }, + "rule matches invalid header match name": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Headers: []HTTPHeaderMatch{{ + Match: HTTPHeaderMatchPresent, + }}, + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Headers[0], missing required Name field", + }, + "rule matches invalid query match type": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Query: []HTTPQueryMatch{{ + Match: HTTPQueryMatchType("foo"), + Name: "foo", + }}, + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Query[0], match type should be one of present, exact, or regex", + }, + "rule matches invalid query match name": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Query: []HTTPQueryMatch{{ + Match: HTTPQueryMatchPresent, + }}, + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Query[0], missing required Name field", + }, + "rule matches invalid path match type": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Path: HTTPPathMatch{ + Match: HTTPPathMatchType("foo"), + }, + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Path, match type should be one of exact, prefix, or regex", + }, + "rule matches invalid path match prefix": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Path: HTTPPathMatch{ + Match: HTTPPathMatchPrefix, + }, + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Path, prefix type match doesn't start with '/': \"\"", + }, + "rule matches invalid method": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Method: HTTPMatchMethod("foo"), + }}, + }}, + }, + validateErr: "Rule[0], Match[0], Method contains an invalid method \"FOO\"", + }, + "rule normalizes method casing and path matches": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-two", + Parents: []ResourceReference{{ + Name: "gateway", + }}, + Rules: []HTTPRouteRule{{ + Matches: []HTTPMatch{{ + Method: HTTPMatchMethod("trace"), + }}, + }}, + }, + }, + "rule normalizes service weight": { + entry: &HTTPRouteConfigEntry{ + Kind: HTTPRoute, + Name: "route-one", + Rules: []HTTPRouteRule{{ + Services: []HTTPService{ + { + Name: "test", + Weight: 0, + }, + { + Name: "test2", + Weight: -1, + }}, + }}, + }, + check: func(t *testing.T, entry ConfigEntry) { + route := entry.(*HTTPRouteConfigEntry) + require.Equal(t, 1, route.Rules[0].Services[0].Weight) + require.Equal(t, 1, route.Rules[0].Services[1].Weight) + }, + }, + } + testConfigEntryNormalizeAndValidate(t, cases) +} diff --git a/agent/structs/config_entry_status.go b/agent/structs/config_entry_status.go index faafd34d7b7..2a56ba08a01 100644 --- a/agent/structs/config_entry_status.go +++ b/agent/structs/config_entry_status.go @@ -1,6 +1,7 @@ package structs import ( + "fmt" "sort" "time" @@ -21,7 +22,11 @@ type ResourceReference struct { // unused, this should be blank. SectionName string - acl.EnterpriseMeta + acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` +} + +func (r *ResourceReference) String() string { + return fmt.Sprintf("%s:%s/%s/%s/%s", r.Kind, r.PartitionOrDefault(), r.NamespaceOrDefault(), r.Name, r.SectionName) } func (r *ResourceReference) IsSame(other *ResourceReference) bool { @@ -45,6 +50,16 @@ type Status struct { Conditions []Condition } +func (s *Status) MatchesConditionStatus(condition Condition) bool { + for _, c := range s.Conditions { + if c.IsCondition(&condition) && + c.Status == condition.Status { + return true + } + } + return false +} + func (s Status) SameConditions(other Status) bool { if len(s.Conditions) != len(other.Conditions) { return false diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index 6de1cd36c46..75ae6c25dbb 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -59,7 +59,7 @@ type CompiledDiscoveryChain struct { // ID returns an ID that encodes the service, namespace, partition, and datacenter. // This ID allows us to compare a discovery chain target to the chain upstream itself. func (c *CompiledDiscoveryChain) ID() string { - return chainID(DiscoveryTargetOpts{ + return ChainID(DiscoveryTargetOpts{ Service: c.ServiceName, Namespace: c.Namespace, Partition: c.Partition, @@ -116,6 +116,7 @@ func (s *DiscoveryGraphNode) MapKey() string { type DiscoveryResolver struct { Default bool `json:",omitempty"` ConnectTimeout time.Duration `json:",omitempty"` + RequestTimeout time.Duration `json:",omitempty"` Target string `json:",omitempty"` Failover *DiscoveryFailover `json:",omitempty"` } @@ -259,6 +260,34 @@ type DiscoveryTargetOpts struct { Peer string } +func MergeDiscoveryTargetOpts(o1 DiscoveryTargetOpts, o2 DiscoveryTargetOpts) DiscoveryTargetOpts { + if o2.Service != "" { + o1.Service = o2.Service + } + + if o2.ServiceSubset != "" { + o1.ServiceSubset = o2.ServiceSubset + } + + if o2.Namespace != "" { + o1.Namespace = o2.Namespace + } + + if o2.Partition != "" { + o1.Partition = o2.Partition + } + + if o2.Datacenter != "" { + o1.Datacenter = o2.Datacenter + } + + if o2.Peer != "" { + o1.Peer = o2.Peer + } + + return o1 +} + func NewDiscoveryTarget(opts DiscoveryTargetOpts) *DiscoveryTarget { t := &DiscoveryTarget{ Service: opts.Service, @@ -283,10 +312,10 @@ func (t *DiscoveryTarget) ToDiscoveryTargetOpts() DiscoveryTargetOpts { } } -func chainID(opts DiscoveryTargetOpts) string { +func ChainID(opts DiscoveryTargetOpts) string { // NOTE: this format is similar to the SNI syntax for simplicity if opts.Peer != "" { - return fmt.Sprintf("%s.%s.default.external.%s", opts.Service, opts.Namespace, opts.Peer) + return fmt.Sprintf("%s.%s.%s.external.%s", opts.Service, opts.Namespace, opts.Partition, opts.Peer) } if opts.ServiceSubset == "" { return fmt.Sprintf("%s.%s.%s.%s", opts.Service, opts.Namespace, opts.Partition, opts.Datacenter) @@ -295,7 +324,7 @@ func chainID(opts DiscoveryTargetOpts) string { } func (t *DiscoveryTarget) setID() { - t.ID = chainID(t.ToDiscoveryTargetOpts()) + t.ID = ChainID(t.ToDiscoveryTargetOpts()) } func (t *DiscoveryTarget) String() string { diff --git a/agent/structs/peering.go b/agent/structs/peering.go index 52d2f32a378..714a442e8f7 100644 --- a/agent/structs/peering.go +++ b/agent/structs/peering.go @@ -62,8 +62,7 @@ func (i ExportedDiscoveryChainInfo) Equal(o ExportedDiscoveryChainInfo) bool { return true } -// ListAllDiscoveryChains returns all discovery chains (union of Services and -// DiscoChains). +// ListAllDiscoveryChains returns all discovery chains (union of Services and DiscoChains). func (list *ExportedServiceList) ListAllDiscoveryChains() map[ServiceName]ExportedDiscoveryChainInfo { chainsByName := make(map[ServiceName]ExportedDiscoveryChainInfo) if list == nil { diff --git a/agent/structs/structs.deepcopy.go b/agent/structs/structs.deepcopy.go index d52585771e7..43f94674c5f 100644 --- a/agent/structs/structs.deepcopy.go +++ b/agent/structs/structs.deepcopy.go @@ -329,9 +329,9 @@ func (o *HTTPRouteConfigEntry) DeepCopy() *HTTPRouteConfigEntry { } } } - if o.Rules[i2].Filters.URLRewrites != nil { - cp.Rules[i2].Filters.URLRewrites = make([]URLRewrite, len(o.Rules[i2].Filters.URLRewrites)) - copy(cp.Rules[i2].Filters.URLRewrites, o.Rules[i2].Filters.URLRewrites) + if o.Rules[i2].Filters.URLRewrite != nil { + cp.Rules[i2].Filters.URLRewrite = new(URLRewrite) + *cp.Rules[i2].Filters.URLRewrite = *o.Rules[i2].Filters.URLRewrite } if o.Rules[i2].Matches != nil { cp.Rules[i2].Matches = make([]HTTPMatch, len(o.Rules[i2].Matches)) @@ -373,9 +373,9 @@ func (o *HTTPRouteConfigEntry) DeepCopy() *HTTPRouteConfigEntry { } } } - if o.Rules[i2].Services[i4].Filters.URLRewrites != nil { - cp.Rules[i2].Services[i4].Filters.URLRewrites = make([]URLRewrite, len(o.Rules[i2].Services[i4].Filters.URLRewrites)) - copy(cp.Rules[i2].Services[i4].Filters.URLRewrites, o.Rules[i2].Services[i4].Filters.URLRewrites) + if o.Rules[i2].Services[i4].Filters.URLRewrite != nil { + cp.Rules[i2].Services[i4].Filters.URLRewrite = new(URLRewrite) + *cp.Rules[i2].Services[i4].Filters.URLRewrite = *o.Rules[i2].Services[i4].Filters.URLRewrite } } } diff --git a/agent/structs/structs.go b/agent/structs/structs.go index 0a95da695ae..117de854234 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -1153,7 +1153,7 @@ func (sn *ServiceNode) CompoundServiceID() ServiceID { } } -func (sn *ServiceNode) CompoundServiceName() ServiceName { +func (sn *ServiceNode) CompoundServiceName() PeeredServiceName { name := sn.ServiceName if name == "" { name = sn.ServiceID @@ -1163,10 +1163,14 @@ func (sn *ServiceNode) CompoundServiceName() ServiceName { entMeta := sn.EnterpriseMeta entMeta.Normalize() - return ServiceName{ - Name: name, - EnterpriseMeta: entMeta, + return PeeredServiceName{ + ServiceName: ServiceName{ + Name: name, + EnterpriseMeta: entMeta, + }, + Peer: sn.PeerName, } + } // Weights represent the weight used by DNS for a given status @@ -1235,6 +1239,12 @@ const ( // This service allows external traffic to exit the mesh through a terminating gateway // based on centralized configuration. ServiceKindDestination ServiceKind = "destination" + + // ServiceKindConnectEnabled is used to indicate whether a service is either + // connect-native or if the service has a corresponding sidecar. It is used for + // internal query purposes and should not be exposed to users as a valid Kind + // option. + ServiceKindConnectEnabled ServiceKind = "connect-enabled" ) // Type to hold a address and port of a service diff --git a/agent/structs/testing_catalog.go b/agent/structs/testing_catalog.go index 11316905117..ff36545b66a 100644 --- a/agent/structs/testing_catalog.go +++ b/agent/structs/testing_catalog.go @@ -1,6 +1,9 @@ package structs import ( + "fmt" + + "github.com/hashicorp/consul/acl" "github.com/mitchellh/go-testing-interface" ) @@ -55,23 +58,38 @@ func TestNodeServiceWithName(t testing.T, name string) *NodeService { const peerTrustDomain = "1c053652-8512-4373-90cf-5a7f6263a994.consul" -func TestCheckNodeServiceWithNameInPeer(t testing.T, name, peer, ip string, useHostname bool) CheckServiceNode { +func TestCheckNodeServiceWithNameInPeer(t testing.T, name, dc, peer, ip string, useHostname bool, remoteEntMeta acl.EnterpriseMeta) CheckServiceNode { + + // Non-default partitions have a different spiffe format. + spiffe := fmt.Sprintf("spiffe://%s/ns/default/dc/%s/svc/%s", peerTrustDomain, dc, name) + if !remoteEntMeta.InDefaultPartition() { + spiffe = fmt.Sprintf("spiffe://%s/ap/%s/ns/%s/dc/%s/svc/%s", + peerTrustDomain, remoteEntMeta.PartitionOrDefault(), remoteEntMeta.NamespaceOrDefault(), dc, name) + } service := &NodeService{ - Kind: ServiceKindTypical, - Service: name, - Port: 8080, + Kind: ServiceKindTypical, + Service: name, + // We should not see this port number appear in most xds golden tests, + // because the WAN addr should typically be used. + Port: 9090, PeerName: peer, Connect: ServiceConnect{ PeerMeta: &PeeringServiceMeta{ SNI: []string{ - name + ".default.default." + peer + ".external." + peerTrustDomain, - }, - SpiffeID: []string{ - "spiffe://" + peerTrustDomain + "/ns/default/dc/" + peer + "-dc/svc/" + name, + fmt.Sprintf("%s.%s.%s.%s.external.%s", + name, remoteEntMeta.NamespaceOrDefault(), remoteEntMeta.PartitionOrDefault(), peer, peerTrustDomain), }, + SpiffeID: []string{spiffe}, Protocol: "tcp", }, }, + // This value should typically be seen in golden file output, since this is a peered service. + TaggedAddresses: map[string]ServiceAddress{ + TaggedAddressWAN: { + Address: ip, + Port: 8080, + }, + }, } if useHostname { @@ -89,10 +107,12 @@ func TestCheckNodeServiceWithNameInPeer(t testing.T, name, peer, ip string, useH return CheckServiceNode{ Node: &Node{ - ID: "test1", - Node: "test1", - Address: ip, - Datacenter: "cloud-dc", + ID: "test1", + Node: "test1", + // We should not see this address appear in most xds golden tests, + // because the WAN addr should typically be used. + Address: "1.23.45.67", + Datacenter: dc, }, Service: service, } diff --git a/agent/structs/testing_connect_proxy_config.go b/agent/structs/testing_connect_proxy_config.go index fdee3f6937d..0021612e9e8 100644 --- a/agent/structs/testing_connect_proxy_config.go +++ b/agent/structs/testing_connect_proxy_config.go @@ -11,37 +11,46 @@ import ( func TestConnectProxyConfig(t testing.T) ConnectProxyConfig { return ConnectProxyConfig{ DestinationServiceName: "web", - Upstreams: TestUpstreams(t), + Upstreams: TestUpstreams(t, false), } } // TestUpstreams returns a set of upstreams to be used in tests exercising most // important configuration patterns. -func TestUpstreams(t testing.T) Upstreams { - return Upstreams{ - { - // We rely on this one having default type in a few tests... - DestinationName: "db", - LocalBindPort: 9191, - Config: map[string]interface{}{ - // Float because this is how it is decoded by JSON decoder so this - // enables the value returned to be compared directly to a decoded JSON - // response without spurious type loss. - "connect_timeout_ms": float64(1000), - }, - }, - { - DestinationType: UpstreamDestTypePreparedQuery, - DestinationName: "geo-cache", - LocalBindPort: 8181, - LocalBindAddress: "127.10.10.10", - }, - { - DestinationName: "upstream_socket", - LocalBindSocketPath: "/tmp/upstream.sock", - LocalBindSocketMode: "0700", +func TestUpstreams(t testing.T, enterprise bool) Upstreams { + db := Upstream{ + // We rely on this one having default type in a few tests... + DestinationName: "db", + LocalBindPort: 9191, + Config: map[string]interface{}{ + // Float because this is how it is decoded by JSON decoder so this + // enables the value returned to be compared directly to a decoded JSON + // response without spurious type loss. + "connect_timeout_ms": float64(1000), }, } + + geoCache := Upstream{ + DestinationType: UpstreamDestTypePreparedQuery, + DestinationName: "geo-cache", + LocalBindPort: 8181, + LocalBindAddress: "127.10.10.10", + } + + if enterprise { + db.DestinationNamespace = "foo" + db.DestinationPartition = "bar" + + geoCache.DestinationNamespace = "baz" + geoCache.DestinationPartition = "qux" + } + + return Upstreams{db, geoCache, { + DestinationName: "upstream_socket", + LocalBindSocketPath: "/tmp/upstream.sock", + LocalBindSocketMode: "0700", + }, + } } // TestAddDefaultsToUpstreams takes an array of upstreams (such as that from diff --git a/agent/testagent.go b/agent/testagent.go index db08be40a4b..f81b56314f9 100644 --- a/agent/testagent.go +++ b/agent/testagent.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "fmt" "io" - "math/rand" "net" "net/http/httptest" "path/filepath" @@ -32,10 +31,6 @@ import ( "github.com/hashicorp/consul/tlsutil" ) -func init() { - rand.Seed(time.Now().UnixNano()) // seed random number generator -} - // TestAgent encapsulates an Agent with a default configuration and // startup procedure suitable for testing. It panics if there are errors // during creation or startup instead of returning errors. It manages a @@ -216,9 +211,9 @@ func (a *TestAgent) Start(t *testing.T) error { } else { result.RuntimeConfig.Telemetry.Disable = true } - // Lower the maximum backoff period of a cache refresh just for - // tests see #14956 for more. - result.RuntimeConfig.Cache.CacheRefreshMaxWait = 1 * time.Second + + // Lower the resync interval for tests. + result.RuntimeConfig.LocalProxyConfigResyncInterval = 250 * time.Millisecond } return result, err } diff --git a/agent/txn_endpoint_test.go b/agent/txn_endpoint_test.go index 90e5359955c..ce94b5c3e63 100644 --- a/agent/txn_endpoint_test.go +++ b/agent/txn_endpoint_test.go @@ -67,9 +67,9 @@ func TestTxnEndpoint_Bad_Size_Item(t *testing.T) { t.Fatalf("err: %v", err) } } else { - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 413 { - t.Fatalf("expected 413 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 413 { + t.Fatalf("expected 413 but got %d", httpErr.StatusCode) } } else { t.Fatalf("excected HTTP error but got %v", err) @@ -150,9 +150,9 @@ func TestTxnEndpoint_Bad_Size_Net(t *testing.T) { t.Fatalf("err: %v", err) } } else { - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 413 { - t.Fatalf("expected 413 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 413 { + t.Fatalf("expected 413 but got %d", httpErr.StatusCode) } } else { t.Fatalf("excected HTTP error but got %v", err) @@ -220,9 +220,9 @@ func TestTxnEndpoint_Bad_Size_Ops(t *testing.T) { resp := httptest.NewRecorder() _, err := a.srv.Txn(resp, req) - if err, ok := err.(HTTPError); ok { - if err.StatusCode != 413 { - t.Fatalf("expected 413 but got %d", err.StatusCode) + if httpErr, ok := err.(HTTPError); ok { + if httpErr.StatusCode != 413 { + t.Fatalf("expected 413 but got %d", httpErr.StatusCode) } } else { t.Fatalf("expected HTTP error but got %v", err) diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index 7ee9a5577dc..2779fd2da78 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -976,7 +976,7 @@ func (s *ResourceGenerator) makeUpstreamClusterForPeerService( // entire cluster. outlierDetection.MaxEjectionPercent = &wrapperspb.UInt32Value{Value: 100} - s.Logger.Trace("generating cluster for", "cluster", clusterName) + s.Logger.Trace("generating cluster for", "cluster", clusterName, "uid", uid) if c == nil { c = &envoy_cluster_v3.Cluster{ Name: clusterName, @@ -1044,10 +1044,13 @@ func (s *ResourceGenerator) makeUpstreamClusterForPeerService( makeTLSParametersFromProxyTLSConfig(cfgSnap.MeshConfigTLSOutgoing()), ) err = injectSANMatcher(commonTLSContext, peerMeta.SpiffeID...) + s.Logger.Trace("injecting SAN matcher rules for cluster %q with SPIFFE IDs: %+v", clusterName, peerMeta.SpiffeID) + if err != nil { return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", clusterName, err) } + s.Logger.Trace("injecting TLS context for cluster %q with SNI: %+v", clusterName, peerMeta.PrimarySNI()) tlsContext := &envoy_tls_v3.UpstreamTlsContext{ CommonTlsContext: commonTLSContext, Sni: peerMeta.PrimarySNI(), diff --git a/agent/xds/clusters_test.go b/agent/xds/clusters_test.go index e2269dc7ffa..094b9ceb182 100644 --- a/agent/xds/clusters_test.go +++ b/agent/xds/clusters_test.go @@ -21,6 +21,112 @@ import ( "github.com/hashicorp/consul/types" ) +type clusterTestCase struct { + name string + create func(t testinf.T) *proxycfg.ConfigSnapshot + overrideGoldenName string +} + +func makeClusterDiscoChainTests(enterprise bool) []clusterTestCase { + return []clusterTestCase{ + { + name: "custom-upstream-default-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", enterprise, func(ns *structs.NodeService) { + ns.Proxy.Upstreams[0].Config["envoy_cluster_json"] = + customAppClusterJSON(t, customClusterJSONOptions{ + Name: "myservice", + }) + }, nil) + }, + }, + { + name: "connect-proxy-with-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-chain-external-sni", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-chain-and-overrides", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-chain-and-failover", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway-triggered", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway-triggered", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway-triggered", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway-triggered", enterprise, nil, nil) + }, + }, + { + name: "splitter-with-resolver-redirect", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-lb-in-resolver", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "lb-resolver", enterprise, nil, nil) + }, + }, + } +} + func TestClustersFromSnapshot(t *testing.T) { // TODO: we should move all of these to TestAllResourcesFromSnapshot // eventually to test all of the xDS types at once with the same input, @@ -29,11 +135,7 @@ func TestClustersFromSnapshot(t *testing.T) { t.Skip("too slow for testing.Short") } - tests := []struct { - name string - create func(t testinf.T) *proxycfg.ConfigSnapshot - overrideGoldenName string - }{ + tests := []clusterTestCase{ { name: "connect-proxy-with-tls-outgoing-min-version-auto", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -135,17 +237,6 @@ func TestClustersFromSnapshot(t *testing.T) { }, nil) }, }, - { - name: "custom-upstream-default-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", func(ns *structs.NodeService) { - ns.Proxy.Upstreams[0].Config["envoy_cluster_json"] = - customAppClusterJSON(t, customClusterJSONOptions{ - Name: "myservice", - }) - }, nil) - }, - }, { name: "custom-upstream-ignores-tls", overrideGoldenName: "custom-upstream", // should be the same @@ -245,90 +336,6 @@ func TestClustersFromSnapshot(t *testing.T) { }, nil) }, }, - { - name: "connect-proxy-with-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil) - }, - }, - { - name: "connect-proxy-with-chain-external-sni", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", nil, nil) - }, - }, - { - name: "connect-proxy-with-chain-and-overrides", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", nil, nil) - }, - }, - { - name: "connect-proxy-with-chain-and-failover", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway-triggered", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway-triggered", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway-triggered", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway-triggered", nil, nil) - }, - }, - { - name: "splitter-with-resolver-redirect", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", nil, nil) - }, - }, - { - name: "connect-proxy-lb-in-resolver", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "lb-resolver", nil, nil) - }, - }, { name: "expose-paths-local-app-paths", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -767,6 +774,8 @@ func TestClustersFromSnapshot(t *testing.T) { }, } + tests = append(tests, makeClusterDiscoChainTests(false)...) + latestEnvoyVersion := xdscommon.EnvoyVersions[0] for _, envoyVersion := range xdscommon.EnvoyVersions { sf, err := xdscommon.DetermineSupportedProxyFeaturesFromString(envoyVersion) diff --git a/agent/xds/delta.go b/agent/xds/delta.go index 5a1ef17d244..62a725b8f1b 100644 --- a/agent/xds/delta.go +++ b/agent/xds/delta.go @@ -466,20 +466,21 @@ func (s *Server) applyEnvoyExtensions(resources *xdscommon.IndexedResources, cfg return nil } +// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#eventual-consistency-considerations var xDSUpdateOrder = []xDSUpdateOperation{ - // TODO Update comments + // 1. SDS updates (if any) can be pushed here with no harm. {TypeUrl: xdscommon.SecretType, Upsert: true}, - // 1. CDS updates (if any) must always be pushed first. + // 2. CDS updates (if any) must always be pushed before the following types. {TypeUrl: xdscommon.ClusterType, Upsert: true}, - // 2. EDS updates (if any) must arrive after CDS updates for the respective clusters. + // 3. EDS updates (if any) must arrive after CDS updates for the respective clusters. {TypeUrl: xdscommon.EndpointType, Upsert: true}, - // 3. LDS updates must arrive after corresponding CDS/EDS updates. + // 4. LDS updates must arrive after corresponding CDS/EDS updates. {TypeUrl: xdscommon.ListenerType, Upsert: true, Remove: true}, - // 4. RDS updates related to the newly added listeners must arrive after CDS/EDS/LDS updates. + // 5. RDS updates related to the newly added listeners must arrive after CDS/EDS/LDS updates. {TypeUrl: xdscommon.RouteType, Upsert: true, Remove: true}, - // 5. (NOT IMPLEMENTED YET IN CONSUL) VHDS updates (if any) related to the newly added RouteConfigurations must arrive after RDS updates. + // 6. (NOT IMPLEMENTED YET IN CONSUL) VHDS updates (if any) related to the newly added RouteConfigurations must arrive after RDS updates. // {}, - // 6. Stale CDS clusters, related EDS endpoints (ones no longer being referenced) and SDS secrets can then be removed. + // 7. Stale CDS clusters, related EDS endpoints (ones no longer being referenced) and SDS secrets can then be removed. {TypeUrl: xdscommon.ClusterType, Remove: true}, {TypeUrl: xdscommon.EndpointType, Remove: true}, {TypeUrl: xdscommon.SecretType, Remove: true}, diff --git a/agent/xds/delta_envoy_extender_oss_test.go b/agent/xds/delta_envoy_extender_oss_test.go index f6791ba6543..3a8d1f17614 100644 --- a/agent/xds/delta_envoy_extender_oss_test.go +++ b/agent/xds/delta_envoy_extender_oss_test.go @@ -92,7 +92,7 @@ end`, { name: "lambda-connect-proxy", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLambdaServiceDefaults(false)) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, makeLambdaServiceDefaults(false)) }, }, { @@ -107,13 +107,13 @@ end`, { name: "lambda-connect-proxy-with-terminating-gateway-upstream", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, makeLambdaServiceDefaults(false)) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", false, nil, nil, makeLambdaServiceDefaults(false)) }, }, { name: "lambda-connect-proxy-opposite-meta", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLambdaServiceDefaults(true)) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, makeLambdaServiceDefaults(true)) }, }, { @@ -129,13 +129,13 @@ end`, { name: "lua-outbound-applies-to-upstreams", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLuaServiceDefaults(false)) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, makeLuaServiceDefaults(false)) }, }, { name: "lua-inbound-doesnt-applies-to-upstreams", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLuaServiceDefaults(true)) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, makeLuaServiceDefaults(true)) }, }, { @@ -183,7 +183,7 @@ end`, { name: "lua-connect-proxy-with-terminating-gateway-upstream", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, makeLambdaServiceDefaults(false)) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", false, nil, nil, makeLambdaServiceDefaults(false)) }, }, { @@ -205,28 +205,7 @@ end`, }, } } - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nsFunc, nil, makeLambdaServiceDefaults(true)) - }, - }, - { - name: "http-local-ratelimit-applyto-filter", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) { - ns.Proxy.Config["protocol"] = "http" - ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{ - { - Name: api.BuiltinLocalRatelimitExtension, - Arguments: map[string]interface{}{ - "ProxyType": "connect-proxy", - "MaxTokens": 3, - "TokensPerFill": 2, - "FillInterval": 10, - "FilterEnabled": 100, - "FilterEnforced": 100, - }, - }, - } - }, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nsFunc, nil, makeLambdaServiceDefaults(true)) }, }, } diff --git a/agent/xds/delta_test.go b/agent/xds/delta_test.go index 23d60198bbf..a61050f8457 100644 --- a/agent/xds/delta_test.go +++ b/agent/xds/delta_test.go @@ -10,7 +10,6 @@ import ( "github.com/armon/go-metrics" envoy_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" - "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" rpcstatus "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" @@ -21,8 +20,10 @@ import ( "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" "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/version" ) @@ -1057,19 +1058,23 @@ func TestServer_DeltaAggregatedResources_v3_ACLEnforcement(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // aclResolve may be called in a goroutine even after a + // testcase tt returns. Capture the variable as tc so the + // values don't swap in the next iteration. + tc := tt aclResolve := func(id string) (acl.Authorizer, error) { - if !tt.defaultDeny { + if !tc.defaultDeny { // Allow all return acl.RootAuthorizer("allow"), nil } - if tt.acl == "" { + if tc.acl == "" { // No token and defaultDeny is denied return acl.RootAuthorizer("deny"), nil } // Ensure the correct token was passed - require.Equal(t, tt.token, id) + require.Equal(t, tc.token, id) // Parse the ACL and enforce it - policy, err := acl.NewPolicyFromSource(tt.acl, nil, nil) + policy, err := acl.NewPolicyFromSource(tc.acl, nil, nil) require.NoError(t, err) return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) } @@ -1095,13 +1100,15 @@ func TestServer_DeltaAggregatedResources_v3_ACLEnforcement(t *testing.T) { // If there is no token, check that we increment the gauge if tt.token == "" { - data := scenario.sink.Data() - require.Len(t, data, 1) - - item := data[0] - val, ok := item.Gauges["consul.xds.test.xds.server.streamsUnauthenticated"] - require.True(t, ok) - require.Equal(t, float32(1), val.Value) + retry.Run(t, func(r *retry.R) { + data := scenario.sink.Data() + require.Len(r, data, 1) + + item := data[0] + val, ok := item.Gauges["consul.xds.test.xds.server.streamsUnauthenticated"] + require.True(r, ok) + require.Equal(r, float32(1), val.Value) + }) } if !tt.wantDenied { @@ -1138,13 +1145,15 @@ func TestServer_DeltaAggregatedResources_v3_ACLEnforcement(t *testing.T) { // If there is no token, check that we decrement the gauge if tt.token == "" { - data := scenario.sink.Data() - require.Len(t, data, 1) - - item := data[0] - val, ok := item.Gauges["consul.xds.test.xds.server.streamsUnauthenticated"] - require.True(t, ok) - require.Equal(t, float32(0), val.Value) + retry.Run(t, func(r *retry.R) { + data := scenario.sink.Data() + require.Len(r, data, 1) + + item := data[0] + val, ok := item.Gauges["consul.xds.test.xds.server.streamsUnauthenticated"] + require.True(r, ok) + require.Equal(r, float32(0), val.Value) + }) } }) } diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index dcdbb4d2f5d..d117f8dd829 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -449,7 +449,9 @@ func (s *ResourceGenerator) makeEndpointsForOutgoingPeeredServices( la := makeLoadAssignment( clusterName, groups, - cfgSnap.Locality, + // Use an empty key here so that it never matches. This will force the mesh gateway to always + // reference the remote mesh gateway's wan addr. + proxycfg.GatewayKey{}, ) resources = append(resources, la) } diff --git a/agent/xds/endpoints_test.go b/agent/xds/endpoints_test.go index e43865495a7..31215adba93 100644 --- a/agent/xds/endpoints_test.go +++ b/agent/xds/endpoints_test.go @@ -217,136 +217,143 @@ func Test_makeLoadAssignment(t *testing.T) { } } -func TestEndpointsFromSnapshot(t *testing.T) { - // TODO: we should move all of these to TestAllResourcesFromSnapshot - // eventually to test all of the xDS types at once with the same input, - // just as it would be triggered by our xDS server. - if testing.Short() { - t.Skip("too slow for testing.Short") - } +type endpointTestCase struct { + name string + create func(t testinf.T) *proxycfg.ConfigSnapshot + overrideGoldenName string +} - tests := []struct { - name string - create func(t testinf.T) *proxycfg.ConfigSnapshot - overrideGoldenName string - }{ +func makeEndpointDiscoChainTests(enterprise bool) []endpointTestCase { + return []endpointTestCase{ { - name: "mesh-gateway", + name: "connect-proxy-with-chain", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotMeshGateway(t, "default", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil) }, }, { - name: "mesh-gateway-using-federation-states", + name: "connect-proxy-with-chain-external-sni", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotMeshGateway(t, "federation-states", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", enterprise, nil, nil) }, }, { - name: "mesh-gateway-newer-information-in-federation-states", + name: "connect-proxy-with-chain-and-overrides", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotMeshGateway(t, "newer-info-in-federation-states", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", enterprise, nil, nil) }, }, { - name: "mesh-gateway-older-information-in-federation-states", + name: "connect-proxy-with-chain-and-failover", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotMeshGateway(t, "older-info-in-federation-states", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", enterprise, nil, nil) }, }, { - name: "mesh-gateway-no-services", + name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotMeshGateway(t, "no-services", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-chain", + name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway-triggered", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-chain-external-sni", + name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-chain-and-overrides", + name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway-triggered", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-chain-and-failover", + name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", + name: "connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway-triggered", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered", + name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway-triggered", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway", + name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway-triggered", enterprise, nil, nil) }, }, { - name: "connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered", + name: "connect-proxy-with-default-chain-and-custom-cluster", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-remote-gateway-triggered", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", enterprise, func(ns *structs.NodeService) { + ns.Proxy.Upstreams[0].Config["envoy_cluster_json"] = + customAppClusterJSON(t, customClusterJSONOptions{ + Name: "myservice", + }) + }, nil) }, }, { - name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", + name: "splitter-with-resolver-redirect", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", enterprise, nil, nil) }, }, + } +} + +func TestEndpointsFromSnapshot(t *testing.T) { + // TODO: we should move all of these to TestAllResourcesFromSnapshot + // eventually to test all of the xDS types at once with the same input, + // just as it would be triggered by our xDS server. + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + tests := []endpointTestCase{ { - name: "connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered", + name: "mesh-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway-triggered", nil, nil) + return proxycfg.TestConfigSnapshotMeshGateway(t, "default", nil, nil) }, }, { - name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway", + name: "mesh-gateway-using-federation-states", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway", nil, nil) + return proxycfg.TestConfigSnapshotMeshGateway(t, "federation-states", nil, nil) }, }, { - name: "connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered", + name: "mesh-gateway-newer-information-in-federation-states", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-double-local-gateway-triggered", nil, nil) + return proxycfg.TestConfigSnapshotMeshGateway(t, "newer-info-in-federation-states", nil, nil) }, }, { - name: "connect-proxy-with-default-chain-and-custom-cluster", + name: "mesh-gateway-older-information-in-federation-states", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", func(ns *structs.NodeService) { - ns.Proxy.Upstreams[0].Config["envoy_cluster_json"] = - customAppClusterJSON(t, customClusterJSONOptions{ - Name: "myservice", - }) - }, nil) + return proxycfg.TestConfigSnapshotMeshGateway(t, "older-info-in-federation-states", nil, nil) }, }, { - name: "splitter-with-resolver-redirect", + name: "mesh-gateway-no-services", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", nil, nil) + return proxycfg.TestConfigSnapshotMeshGateway(t, "no-services", nil, nil) }, }, { @@ -498,6 +505,8 @@ func TestEndpointsFromSnapshot(t *testing.T) { }, } + tests = append(tests, makeEndpointDiscoChainTests(false)...) + latestEnvoyVersion := xdscommon.EnvoyVersions[0] for _, envoyVersion := range xdscommon.EnvoyVersions { sf, err := xdscommon.DetermineSupportedProxyFeaturesFromString(envoyVersion) diff --git a/agent/xds/extensionruntime/runtime_config_oss_test.go b/agent/xds/extensionruntime/runtime_config_oss_test.go index 62ce5f812c1..cc004c788de 100644 --- a/agent/xds/extensionruntime/runtime_config_oss_test.go +++ b/agent/xds/extensionruntime/runtime_config_oss_test.go @@ -130,11 +130,11 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) { } // Setup a snapshot where the db upstream is on a connect proxy. - snapConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, serviceDefaults) + snapConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, serviceDefaults) // Setup a snapshot where the db upstream is on a terminating gateway. - snapTermGw := proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, serviceDefaults) + snapTermGw := proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", false, nil, nil, serviceDefaults) // Setup a snapshot with the local service web has extensions. - snapWebConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", func(ns *structs.NodeService) { + snapWebConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, func(ns *structs.NodeService) { ns.Proxy.EnvoyExtensions = envoyExtensions }, nil) diff --git a/agent/xds/golden_test.go b/agent/xds/golden_test.go index 420285203e0..c056aa7b3a0 100644 --- a/agent/xds/golden_test.go +++ b/agent/xds/golden_test.go @@ -1,6 +1,7 @@ package xds import ( + "encoding/json" "flag" "fmt" "os" @@ -8,6 +9,7 @@ import ( "testing" "github.com/hashicorp/go-version" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" @@ -83,12 +85,16 @@ func golden(t *testing.T, name, subname, latestSubname, got string) string { golden := filepath.Join("testdata", name+suffix) - // Always load the latest golden file if configured to do so. - latestExpected := "" - if latestSubname != "" && subname != latestSubname { - latestGolden := filepath.Join("testdata", fmt.Sprintf("%s.%s.golden", name, latestSubname)) - raw, err := os.ReadFile(latestGolden) - require.NoError(t, err, "%q %q %q", name, subname, latestSubname) + var latestGoldenPath, latestExpected string + isLatest := subname == latestSubname + // Include latestSubname in the latest golden path if it exists. + if latestSubname == "" { + latestGoldenPath = filepath.Join("testdata", fmt.Sprintf("%s.golden", name)) + } else { + latestGoldenPath = filepath.Join("testdata", fmt.Sprintf("%s.%s.golden", name, latestSubname)) + } + + if raw, err := os.ReadFile(latestGoldenPath); err == nil { latestExpected = string(raw) } @@ -97,8 +103,14 @@ func golden(t *testing.T, name, subname, latestSubname, got string) string { // // To trim down PRs, we only create per-version golden files if they differ // from the latest version. + if *update && got != "" { - if latestExpected == got { + var gotInterface, latestExpectedInterface interface{} + json.Unmarshal([]byte(got), &gotInterface) + json.Unmarshal([]byte(latestExpected), &latestExpectedInterface) + + // Remove non-latest golden files if they are the same as the latest one. + if !isLatest && assert.ObjectsAreEqualValues(gotInterface, latestExpectedInterface) { // In update mode we erase a golden file if it is identical to // the golden file corresponding to the latest version of // envoy. @@ -109,7 +121,12 @@ func golden(t *testing.T, name, subname, latestSubname, got string) string { return got } - require.NoError(t, os.WriteFile(golden, []byte(got), 0644)) + // We use require.JSONEq to compare values and ObjectsAreEqualValues is used + // internally by that function to compare the string JSON values. This only + // writes updates the golden file when they actually change. + if !assert.ObjectsAreEqualValues(gotInterface, latestExpectedInterface) { + require.NoError(t, os.WriteFile(golden, []byte(got), 0644)) + } return got } diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index 291e7151764..0c31c423849 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -2328,6 +2328,9 @@ func makeHTTPInspectorListenerFilter() (*envoy_listener_v3.ListenerFilter, error } func makeSNIFilterChainMatch(sniMatches ...string) *envoy_listener_v3.FilterChainMatch { + if sniMatches == nil { + return nil + } return &envoy_listener_v3.FilterChainMatch{ ServerNames: sniMatches, } diff --git a/agent/xds/listeners_ingress.go b/agent/xds/listeners_ingress.go index 6083529849d..40eb24230e0 100644 --- a/agent/xds/listeners_ingress.go +++ b/agent/xds/listeners_ingress.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/types" ) @@ -25,6 +26,15 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap return nil, fmt.Errorf("no listener config found for listener on proto/port %s/%d", listenerKey.Protocol, listenerKey.Port) } + var isAPIGatewayWithTLS bool + var certs []structs.InlineCertificateConfigEntry + if cfgSnap.APIGateway.ListenerCertificates != nil { + certs = cfgSnap.APIGateway.ListenerCertificates[listenerKey] + } + if certs != nil { + isAPIGatewayWithTLS = true + } + tlsContext, err := makeDownstreamTLSContextFromSnapshotListenerConfig(cfgSnap, listenerCfg) if err != nil { return nil, err @@ -72,6 +82,7 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap logger: s.Logger, } l := makeListener(opts) + filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{ accessLogs: &cfgSnap.Proxy.AccessLogs, routeName: uid.EnvoyID(), @@ -87,8 +98,30 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap l.FilterChains = []*envoy_listener_v3.FilterChain{ filterChain, } - resources = append(resources, l) + if isAPIGatewayWithTLS { + // construct SNI filter chains + l.FilterChains, err = makeInlineOverrideFilterChains(cfgSnap, cfgSnap.IngressGateway.TLSConfig, listenerKey, listenerFilterOpts{ + useRDS: useRDS, + protocol: listenerKey.Protocol, + routeName: listenerKey.RouteName(), + cluster: clusterName, + statPrefix: "ingress_upstream_", + accessLogs: &cfgSnap.Proxy.AccessLogs, + logger: s.Logger, + }, certs) + if err != nil { + return nil, err + } + // add the tls inspector to do SNI introspection + tlsInspector, err := makeTLSInspectorListenerFilter() + if err != nil { + return nil, err + } + l.ListenerFilters = []*envoy_listener_v3.ListenerFilter{tlsInspector} + } + + resources = append(resources, l) } else { // If multiple upstreams share this port, make a special listener for the protocol. listenerOpts := makeListenerOpts{ @@ -121,6 +154,13 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap return nil, err } + if isAPIGatewayWithTLS { + sniFilterChains, err = makeInlineOverrideFilterChains(cfgSnap, cfgSnap.IngressGateway.TLSConfig, listenerKey, filterOpts, certs) + if err != nil { + return nil, err + } + } + // If there are any sni filter chains, we need a TLS inspector filter! if len(sniFilterChains) > 0 { tlsInspector, err := makeTLSInspectorListenerFilter() @@ -134,7 +174,7 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap // See if there are other services that didn't have specific SNI-matching // filter chains. If so add a default filterchain to serve them. - if len(sniFilterChains) < len(upstreams) { + if len(sniFilterChains) < len(upstreams) && !isAPIGatewayWithTLS { defaultFilter, err := makeListenerFilter(filterOpts) if err != nil { return nil, err @@ -379,10 +419,133 @@ func makeSDSOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot, return chains, nil } +// when we have multiple certificates on a single listener, we need +// to duplicate the filter chains with multiple TLS contexts +func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot, + tlsCfg structs.GatewayTLSConfig, + listenerKey proxycfg.IngressListenerKey, + filterOpts listenerFilterOpts, + certs []structs.InlineCertificateConfigEntry) ([]*envoy_listener_v3.FilterChain, error) { + + listenerCfg, ok := cfgSnap.IngressGateway.Listeners[listenerKey] + if !ok { + return nil, fmt.Errorf("no listener config found for listener on port %d", listenerKey.Port) + } + + var chains []*envoy_listener_v3.FilterChain + + constructChain := func(name string, hosts []string, tlsContext *envoy_tls_v3.CommonTlsContext) error { + filterOpts.filterName = name + filter, err := makeListenerFilter(filterOpts) + if err != nil { + return err + } + + // Configure alpn protocols on TLSContext + tlsContext.AlpnProtocols = getAlpnProtocols(listenerCfg.Protocol) + transportSocket, err := makeDownstreamTLSTransportSocket(&envoy_tls_v3.DownstreamTlsContext{ + CommonTlsContext: tlsContext, + RequireClientCertificate: &wrapperspb.BoolValue{Value: false}, + }) + if err != nil { + return err + } + + chains = append(chains, &envoy_listener_v3.FilterChain{ + FilterChainMatch: makeSNIFilterChainMatch(hosts...), + Filters: []*envoy_listener_v3.Filter{ + filter, + }, + TransportSocket: transportSocket, + }) + + return nil + } + + multipleCerts := len(certs) > 1 + + allCertHosts := map[string]struct{}{} + overlappingHosts := map[string]struct{}{} + + if multipleCerts { + // we only need to prune out overlapping hosts if we have more than + // one certificate + for _, cert := range certs { + hosts, err := cert.Hosts() + if err != nil { + return nil, fmt.Errorf("unable to parse hosts from x509 certificate: %v", hosts) + } + for _, host := range hosts { + if _, ok := allCertHosts[host]; ok { + overlappingHosts[host] = struct{}{} + } + allCertHosts[host] = struct{}{} + } + } + } + + for _, cert := range certs { + var hosts []string + + // if we only have one cert, we just use it for all ingress + if multipleCerts { + // otherwise, we need an SNI per cert and to fallback to our ingress + // gateway certificate signed by our Consul CA + certHosts, err := cert.Hosts() + if err != nil { + return nil, fmt.Errorf("unable to parse hosts from x509 certificate: %v", hosts) + } + // filter out any overlapping hosts so we don't have collisions in our filter chains + for _, host := range certHosts { + if _, ok := overlappingHosts[host]; !ok { + hosts = append(hosts, host) + } + } + + if len(hosts) == 0 { + // all of our hosts are overlapping, so we just skip this filter and it'll be + // handled by the default filter chain + continue + } + } + + if err := constructChain(cert.Name, hosts, makeInlineTLSContextFromGatewayTLSConfig(tlsCfg, cert)); err != nil { + return nil, err + } + } + + if multipleCerts { + // if we have more than one cert, add a default handler that uses the leaf cert from connect + if err := constructChain("default", nil, makeCommonTLSContext(cfgSnap.Leaf(), cfgSnap.RootPEMs(), makeTLSParametersFromGatewayTLSConfig(tlsCfg))); err != nil { + return nil, err + } + } + + return chains, nil +} + func makeTLSParametersFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *envoy_tls_v3.TlsParameters { return makeTLSParametersFromTLSConfig(tlsCfg.TLSMinVersion, tlsCfg.TLSMaxVersion, tlsCfg.CipherSuites) } +func makeInlineTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig, cert structs.InlineCertificateConfigEntry) *envoy_tls_v3.CommonTlsContext { + return &envoy_tls_v3.CommonTlsContext{ + TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg), + TlsCertificates: []*envoy_tls_v3.TlsCertificate{{ + CertificateChain: &envoy_core_v3.DataSource{ + Specifier: &envoy_core_v3.DataSource_InlineString{ + InlineString: lib.EnsureTrailingNewline(cert.Certificate), + }, + }, + PrivateKey: &envoy_core_v3.DataSource{ + Specifier: &envoy_core_v3.DataSource_InlineString{ + InlineString: lib.EnsureTrailingNewline(cert.PrivateKey), + }, + }, + }}, + } +} + func makeCommonTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *envoy_tls_v3.CommonTlsContext { return &envoy_tls_v3.CommonTlsContext{ TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg), @@ -396,6 +559,7 @@ func makeCommonTLSContextFromGatewayServiceTLSConfig(tlsCfg structs.GatewayServi TlsCertificateSdsSecretConfigs: makeTLSCertificateSdsSecretConfigsFromSDS(*tlsCfg.SDS), } } + func makeTLSCertificateSdsSecretConfigsFromSDS(sdsCfg structs.GatewayTLSSDSConfig) []*envoy_tls_v3.SdsSecretConfig { return []*envoy_tls_v3.SdsSecretConfig{ { diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index fffe3dc342d..a69a74a2b1c 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -21,6 +21,121 @@ import ( "github.com/hashicorp/consul/types" ) +type listenerTestCase struct { + name string + create func(t testinf.T) *proxycfg.ConfigSnapshot + // Setup is called before the test starts. It is passed the snapshot from + // TestConfigSnapshot and is allowed to modify it in any way to setup the + // test input. + overrideGoldenName string + generatorSetup func(*ResourceGenerator) +} + +func makeListenerDiscoChainTests(enterprise bool) []listenerTestCase { + return []listenerTestCase{ + { + name: "custom-upstream-ignored-with-disco-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", enterprise, func(ns *structs.NodeService) { + for i := range ns.Proxy.Upstreams { + if ns.Proxy.Upstreams[i].DestinationName != "db" { + continue // only tweak the db upstream + } + if ns.Proxy.Upstreams[i].Config == nil { + ns.Proxy.Upstreams[i].Config = map[string]interface{}{} + } + + uid := proxycfg.NewUpstreamID(&ns.Proxy.Upstreams[i]) + + ns.Proxy.Upstreams[i].Config["envoy_listener_json"] = + customListenerJSON(t, customListenerJSONOptions{ + Name: uid.EnvoyID() + ":custom-upstream", + }) + } + }, nil) + }, + }, + { + name: "splitter-with-resolver-redirect", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-http-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil, + &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, + ) + }, + }, + { + name: "connect-proxy-with-http2-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil, + &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http2", + }, + }, + ) + }, + }, + { + name: "connect-proxy-with-grpc-chain", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil, + &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "grpc", + }, + }, + ) + }, + }, + { + name: "connect-proxy-with-chain-external-sni", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-chain-and-overrides", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", enterprise, nil, nil) + }, + }, + { + name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", enterprise, nil, nil) + }, + }, + } +} + func TestListenersFromSnapshot(t *testing.T) { // TODO: we should move all of these to TestAllResourcesFromSnapshot // eventually to test all of the xDS types at once with the same input, @@ -29,16 +144,7 @@ func TestListenersFromSnapshot(t *testing.T) { t.Skip("too slow for testing.Short") } - tests := []struct { - name string - create func(t testinf.T) *proxycfg.ConfigSnapshot - // Setup is called before the test starts. It is passed the snapshot from - // TestConfigSnapshot and is allowed to modify it in any way to setup the - // test input. - setup func(snap *proxycfg.ConfigSnapshot) - overrideGoldenName string - generatorSetup func(*ResourceGenerator) - }{ + tests := []listenerTestCase{ { name: "connect-proxy-with-tls-outgoing-min-version-auto", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -326,106 +432,6 @@ func TestListenersFromSnapshot(t *testing.T) { }, nil) }, }, - { - name: "custom-upstream-ignored-with-disco-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", func(ns *structs.NodeService) { - for i := range ns.Proxy.Upstreams { - if ns.Proxy.Upstreams[i].DestinationName != "db" { - continue // only tweak the db upstream - } - if ns.Proxy.Upstreams[i].Config == nil { - ns.Proxy.Upstreams[i].Config = map[string]interface{}{} - } - - uid := proxycfg.NewUpstreamID(&ns.Proxy.Upstreams[i]) - - ns.Proxy.Upstreams[i].Config["envoy_listener_json"] = - customListenerJSON(t, customListenerJSONOptions{ - Name: uid.EnvoyID() + ":custom-upstream", - }) - } - }, nil) - }, - }, - { - name: "splitter-with-resolver-redirect", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil) - }, - }, - { - name: "connect-proxy-with-http-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil, - &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, - ) - }, - }, - { - name: "connect-proxy-with-http2-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil, - &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http2", - }, - }, - ) - }, - }, - { - name: "connect-proxy-with-grpc-chain", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil, - &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "grpc", - }, - }, - ) - }, - }, - { - name: "connect-proxy-with-chain-external-sni", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", nil, nil) - }, - }, - { - name: "connect-proxy-with-chain-and-overrides", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", nil, nil) - }, - }, - { - name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", - create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", nil, nil) - }, - }, { name: "connect-proxy-upstream-defaults", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -527,6 +533,210 @@ func TestListenersFromSnapshot(t *testing.T) { }, nil) }, }, + { + name: "api-gateway", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, nil, nil, nil, nil) + }, + }, + { + name: "api-gateway-nil-config-entry", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway_NilConfigEntry(t) + }, + }, + { + name: "api-gateway-tcp-listener", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener", + Protocol: structs.ListenerProtocolTCP, + Port: 8080, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener", + }, + } + }, nil, nil, nil) + }, + }, + { + name: "api-gateway-tcp-listener-with-tcp-route", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener", + Protocol: structs.ListenerProtocolTCP, + Port: 8080, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener", + Routes: []structs.ResourceReference{ + { + Name: "tcp-route", + Kind: structs.TCPRoute, + }, + }, + }, + } + + }, []structs.BoundRoute{ + &structs.TCPRouteConfigEntry{ + Name: "tcp-route", + Kind: structs.TCPRoute, + Parents: []structs.ResourceReference{ + { + Kind: structs.APIGateway, + Name: "api-gateway", + }, + }, + Services: []structs.TCPService{ + {Name: "tcp-service"}, + }, + }, + }, nil, nil) + }, + }, + { + name: "api-gateway-http-listener", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener", + Protocol: structs.ListenerProtocolHTTP, + Port: 8080, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener", + Routes: []structs.ResourceReference{}, + }, + } + }, nil, nil, nil) + }, + }, + { + name: "api-gateway-http-listener-with-http-route", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener", + Protocol: structs.ListenerProtocolHTTP, + Port: 8080, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener", + Routes: []structs.ResourceReference{ + { + Name: "http-route", + Kind: structs.HTTPRoute, + }, + }, + }, + } + }, []structs.BoundRoute{ + &structs.HTTPRouteConfigEntry{ + Name: "http-route", + Kind: structs.HTTPRoute, + Parents: []structs.ResourceReference{ + { + Kind: structs.APIGateway, + Name: "api-gateway", + }, + }, + Rules: []structs.HTTPRouteRule{ + { + Services: []structs.HTTPService{ + {Name: "http-service"}, + }, + }, + }, + }, + }, nil, nil) + }, + }, + { + name: "api-gateway-tcp-listener-with-tcp-and-http-route", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener-tcp", + Protocol: structs.ListenerProtocolTCP, + Port: 8080, + }, + { + Name: "listener-http", + Protocol: structs.ListenerProtocolHTTP, + Port: 8081, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener-tcp", + Routes: []structs.ResourceReference{ + { + Name: "tcp-route", + Kind: structs.TCPRoute, + }, + }, + }, + { + Name: "listener-http", + Routes: []structs.ResourceReference{ + { + Name: "http-route", + Kind: structs.HTTPRoute, + }, + }, + }, + } + }, []structs.BoundRoute{ + &structs.TCPRouteConfigEntry{ + Name: "tcp-route", + Kind: structs.TCPRoute, + Parents: []structs.ResourceReference{ + { + Kind: structs.APIGateway, + Name: "api-gateway", + }, + }, + Services: []structs.TCPService{ + {Name: "tcp-service"}, + }, + }, + &structs.HTTPRouteConfigEntry{ + Name: "http-route", + Kind: structs.HTTPRoute, + Parents: []structs.ResourceReference{ + { + Kind: structs.APIGateway, + Name: "api-gateway", + }, + }, + Rules: []structs.HTTPRouteRule{ + { + Services: []structs.HTTPService{ + {Name: "http-service"}, + }, + }, + }, + }, + }, nil, nil) + }, + }, { name: "ingress-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -888,6 +1098,8 @@ func TestListenersFromSnapshot(t *testing.T) { }, } + tests = append(tests, makeListenerDiscoChainTests(false)...) + latestEnvoyVersion := xdscommon.EnvoyVersions[0] for _, envoyVersion := range xdscommon.EnvoyVersions { sf, err := xdscommon.DetermineSupportedProxyFeaturesFromString(envoyVersion) @@ -906,10 +1118,6 @@ func TestListenersFromSnapshot(t *testing.T) { // golder files for every test case and so not be any use! testcommon.SetupTLSRootsAndLeaf(t, snap) - if tt.setup != nil { - tt.setup(snap) - } - // Need server just for logger dependency g := NewResourceGenerator(testutil.Logger(t), nil, false) g.ProxyFeatures = sf diff --git a/agent/xds/resources.go b/agent/xds/resources.go index 6bd6709343c..cab494da4ca 100644 --- a/agent/xds/resources.go +++ b/agent/xds/resources.go @@ -35,8 +35,7 @@ func NewResourceGenerator( func (g *ResourceGenerator) AllResourcesFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot) (map[string][]proto.Message, error) { all := make(map[string][]proto.Message) - // TODO Add xdscommon.SecretType - for _, typeUrl := range []string{xdscommon.ListenerType, xdscommon.RouteType, xdscommon.ClusterType, xdscommon.EndpointType} { + for _, typeUrl := range []string{xdscommon.ListenerType, xdscommon.RouteType, xdscommon.ClusterType, xdscommon.EndpointType, xdscommon.SecretType} { res, err := g.resourcesFromSnapshot(typeUrl, cfgSnap) if err != nil { return nil, fmt.Errorf("failed to generate xDS resources for %q: %v", typeUrl, err) diff --git a/agent/xds/resources_test.go b/agent/xds/resources_test.go index ad3c1b396c2..1f9b99274bd 100644 --- a/agent/xds/resources_test.go +++ b/agent/xds/resources_test.go @@ -9,6 +9,10 @@ import ( envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + + "github.com/hashicorp/consul/agent/connect" + "github.com/hashicorp/consul/agent/consul/discoverychain" "github.com/hashicorp/consul/agent/xds/testcommon" "github.com/hashicorp/consul/envoyextensions/xdscommon" @@ -25,6 +29,7 @@ var testTypeUrlToPrettyName = map[string]string{ xdscommon.RouteType: "routes", xdscommon.ClusterType: "clusters", xdscommon.EndpointType: "endpoints", + xdscommon.SecretType: "secrets", } type goldenTestCase struct { @@ -60,7 +65,7 @@ func TestAllResourcesFromSnapshot(t *testing.T) { // We need to replace the TLS certs with deterministic ones to make golden // files workable. Note we don't update these otherwise they'd change - // golder files for every test case and so not be any use! + // golden files for every test case and so not be any use! testcommon.SetupTLSRootsAndLeaf(t, snap) if tt.setup != nil { @@ -82,6 +87,7 @@ func TestAllResourcesFromSnapshot(t *testing.T) { xdscommon.RouteType, xdscommon.ClusterType, xdscommon.EndpointType, + xdscommon.SecretType, } require.Len(t, resources, len(typeUrls)) @@ -101,6 +107,8 @@ func TestAllResourcesFromSnapshot(t *testing.T) { return items[i].(*envoy_cluster_v3.Cluster).Name < items[j].(*envoy_cluster_v3.Cluster).Name case xdscommon.EndpointType: return items[i].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName < items[j].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName + case xdscommon.SecretType: + return items[i].(*envoy_tls_v3.Secret).Name < items[j].(*envoy_tls_v3.Secret).Name default: panic("not possible") } @@ -160,11 +168,16 @@ func TestAllResourcesFromSnapshot(t *testing.T) { name: "local-mesh-gateway-with-peered-upstreams", create: proxycfg.TestConfigSnapshotPeeringLocalMeshGateway, }, + { + name: "hcp-metrics", + create: proxycfg.TestConfigSnapshotHCPMetrics, + }, } tests = append(tests, getConnectProxyTransparentProxyGoldenTestCases()...) tests = append(tests, getMeshGatewayPeeringGoldenTestCases()...) - tests = append(tests, getTrafficControlPeeringGoldenTestCases()...) + tests = append(tests, getTrafficControlPeeringGoldenTestCases(false)...) tests = append(tests, getEnterpriseGoldenTestCases()...) + tests = append(tests, getAPIGatewayGoldenTestCases(t)...) latestEnvoyVersion := xdscommon.EnvoyVersions[0] for _, envoyVersion := range xdscommon.EnvoyVersions { @@ -240,18 +253,172 @@ func getMeshGatewayPeeringGoldenTestCases() []goldenTestCase { } } -func getTrafficControlPeeringGoldenTestCases() []goldenTestCase { - return []goldenTestCase{ +func getTrafficControlPeeringGoldenTestCases(enterprise bool) []goldenTestCase { + cases := []goldenTestCase{ { name: "connect-proxy-with-chain-and-failover-to-cluster-peer", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-to-cluster-peer", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-to-cluster-peer", enterprise, nil, nil) }, }, { name: "connect-proxy-with-chain-and-redirect-to-cluster-peer", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "redirect-to-cluster-peer", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "redirect-to-cluster-peer", enterprise, nil, nil) + }, + }, + } + + if enterprise { + for i := range cases { + cases[i].name = "enterprise-" + cases[i].name + } + } + + return cases +} + +const ( + gatewayTestPrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ +httBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo +NvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC +yYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409g9X5VU88/Bmmrz4cMyxce86Kc2ug +5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftrXOvuCbO5IBRHMOBHiHTZ4rtGuhMa +Ir21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+WmQIDAQABAoIBACYvceUzp2MK4gYA +GWPOP2uKbBdM0l+hHeNV0WAM+dHMfmMuL4pkT36ucqt0ySOLjw6rQyOZG5nmA6t9 +sv0g4ae2eCMlyDIeNi1Yavu4Wt6YX4cTXbQKThm83C6W2X9THKbauBbxD621bsDK +7PhiGPN60yPue7YwFQAPqqD4YaK+s22HFIzk9gwM/rkvAUNwRv7SyHMiFe4Igc1C +Eev7iHWzvj5Heoz6XfF+XNF9DU+TieSUAdjd56VyUb8XL4+uBTOhHwLiXvAmfaMR +HvpcxeKnYZusS6NaOxcUHiJnsLNWrxmJj9WEGgQzuLxcLjTe4vVmELVZD8t3QUKj +PAxu8tUCgYEA7KIWVn9dfVpokReorFym+J8FzLwSktP9RZYEMonJo00i8aii3K9s +u/aSwRWQSCzmON1ZcxZzWhwQF9usz6kGCk//9+4hlVW90GtNK0RD+j7sp4aT2JI8 +9eLEjTG+xSXa7XWe98QncjjL9lu/yrRncSTxHs13q/XP198nn2aYuQ8CgYEA2Dnt +sRBzv0fFEvzzFv7G/5f85mouN38TUYvxNRTjBLCXl9DeKjDkOVZ2b6qlfQnYXIru +H+W+v+AZEb6fySXc8FRab7lkgTMrwE+aeI4rkW7asVwtclv01QJ5wMnyT84AgDD/ +Dgt/RThFaHgtU9TW5GOZveL+l9fVPn7vKFdTJdcCgYEArJ99zjHxwJ1whNAOk1av +09UmRPm6TvRo4heTDk8oEoIWCNatoHI0z1YMLuENNSnT9Q280FFDayvnrY/qnD7A +kktT/sjwJOG8q8trKzIMqQS4XWm2dxoPcIyyOBJfCbEY6XuRsUuePxwh5qF942EB +yS9a2s6nC4Ix0lgPrqAIr48CgYBgS/Q6riwOXSU8nqCYdiEkBYlhCJrKpnJxF9T1 +ofa0yPzKZP/8ZEfP7VzTwHjxJehQ1qLUW9pG08P2biH1UEKEWdzo8vT6wVJT1F/k +HtTycR8+a+Hlk2SHVRHqNUYQGpuIe8mrdJ1as4Pd0d/F/P0zO9Rlh+mAsGPM8HUM +T0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj +nZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX +kHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/ +-----END RSA PRIVATE KEY-----` + gatewayTestCertificate = `-----BEGIN CERTIFICATE----- +MIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV +UzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB +ptqOdam2ch4BHQGhNhX/MrDwwuZQhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2 +jQlhqTodElkbsd5vWY8R/bxJWQSoNvVE12TlzECxGpJEiHt4W0r8pGffk+rvplji +UyCfnT1kGF3znOSjK1hRMTn6RKWCyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409 +g9X5VU88/Bmmrz4cMyxce86Kc2ug5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftr +XOvuCbO5IBRHMOBHiHTZ4rtGuhMaIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+W +mQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBfCqoUIdPf/HGSbOorPyZWbyizNtHJ +GL7x9cAeIYxpI5Y/WcO1o5v94lvrgm3FNfJoGKbV66+JxOge731FrfMpHplhar1Z +RahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK +NtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO +qwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww +AAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r +-----END CERTIFICATE-----` +) + +func getAPIGatewayGoldenTestCases(t *testing.T) []goldenTestCase { + t.Helper() + + service := structs.NewServiceName("service", nil) + serviceUID := proxycfg.NewUpstreamIDFromServiceName(service) + serviceChain := discoverychain.TestCompileConfigEntries(t, "service", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + + return []goldenTestCase{ + { + name: "api-gateway-with-tcp-route-and-inline-certificate", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener", + Protocol: structs.ListenerProtocolTCP, + Port: 8080, + TLS: structs.APIGatewayTLSConfiguration{ + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "certificate", + }}, + }, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener", + Certificates: []structs.ResourceReference{{ + Kind: structs.InlineCertificate, + Name: "certificate", + }}, + Routes: []structs.ResourceReference{{ + Kind: structs.TCPRoute, + Name: "route", + }}, + }, + } + }, []structs.BoundRoute{ + &structs.TCPRouteConfigEntry{ + Kind: structs.TCPRoute, + Name: "route", + Services: []structs.TCPService{{ + Name: "service", + }}, + }, + }, []structs.InlineCertificateConfigEntry{{ + Kind: structs.InlineCertificate, + Name: "certificate", + PrivateKey: gatewayTestPrivateKey, + Certificate: gatewayTestCertificate, + }}, nil) + }, + }, + { + name: "api-gateway-with-http-route-and-inline-certificate", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) { + entry.Listeners = []structs.APIGatewayListener{ + { + Name: "listener", + Protocol: structs.ListenerProtocolHTTP, + Port: 8080, + }, + } + bound.Listeners = []structs.BoundAPIGatewayListener{ + { + Name: "listener", + Routes: []structs.ResourceReference{{ + Kind: structs.HTTPRoute, + Name: "route", + }}, + }, + } + }, []structs.BoundRoute{ + &structs.HTTPRouteConfigEntry{ + Kind: structs.HTTPRoute, + Name: "route", + Rules: []structs.HTTPRouteRule{{ + Services: []structs.HTTPService{{ + Name: "service", + }}, + }}, + }, + }, nil, []proxycfg.UpdateEvent{{ + CorrelationID: "discovery-chain:" + serviceUID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: serviceChain, + }, + }, { + CorrelationID: "upstream-target:" + serviceChain.ID() + ":" + serviceUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: proxycfg.TestUpstreamNodes(t, "service"), + }, + }}) }, }, } diff --git a/agent/xds/routes.go b/agent/xds/routes.go index 46d8d2c0a8e..9a02d96ea5c 100644 --- a/agent/xds/routes.go +++ b/agent/xds/routes.go @@ -218,7 +218,7 @@ func (s *ResourceGenerator) makeRoutes( if resolver.LoadBalancer != nil { lb = resolver.LoadBalancer } - route, err := makeNamedDefaultRouteWithLB(clusterName, lb, autoHostRewrite) + route, err := makeNamedDefaultRouteWithLB(clusterName, lb, resolver.RequestTimeout, autoHostRewrite) if err != nil { s.Logger.Error("failed to make route", "cluster", clusterName, "error", err) return nil, err @@ -228,7 +228,7 @@ func (s *ResourceGenerator) makeRoutes( // If there is a service-resolver for this service then also setup routes for each subset for name := range resolver.Subsets { clusterName = connect.ServiceSNI(svc.Name, name, svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) - route, err := makeNamedDefaultRouteWithLB(clusterName, lb, true) + route, err := makeNamedDefaultRouteWithLB(clusterName, lb, resolver.RequestTimeout, true) if err != nil { s.Logger.Error("failed to make route", "cluster", clusterName, "error", err) return nil, err @@ -282,7 +282,7 @@ func (s *ResourceGenerator) routesForMeshGateway(cfgSnap *proxycfg.ConfigSnapsho return resources, nil } -func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, autoHostRewrite bool) (*envoy_route_v3.RouteConfiguration, error) { +func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, timeout time.Duration, autoHostRewrite bool) (*envoy_route_v3.RouteConfiguration, error) { action := makeRouteActionFromName(clusterName) if err := injectLBToRouteAction(lb, action.Route); err != nil { @@ -296,6 +296,10 @@ func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, a } } + if timeout != 0 { + action.Route.Timeout = durationpb.New(timeout) + } + return &envoy_route_v3.RouteConfiguration{ Name: clusterName, VirtualHosts: []*envoy_route_v3.VirtualHost{ @@ -637,6 +641,9 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain( return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err) } + if startNode.Resolver.RequestTimeout > 0 { + routeAction.Route.Timeout = durationpb.New(startNode.Resolver.RequestTimeout) + } defaultRoute := &envoy_route_v3.Route{ Match: makeDefaultRouteMatch(), Action: routeAction, @@ -856,6 +863,7 @@ func (s *ResourceGenerator) makeRouteActionForSplitter( forMeshGateway bool, ) (*envoy_route_v3.Route_Route, error) { clusters := make([]*envoy_route_v3.WeightedCluster_ClusterWeight, 0, len(splits)) + totalWeight := 0 for _, split := range splits { nextNode := chain.Nodes[split.NextNode] @@ -871,8 +879,10 @@ func (s *ResourceGenerator) makeRouteActionForSplitter( // The smallest representable weight is 1/10000 or .01% but envoy // deals with integers so scale everything up by 100x. + weight := int(split.Weight * 100) + totalWeight += weight cw := &envoy_route_v3.WeightedCluster_ClusterWeight{ - Weight: makeUint32Value(int(split.Weight * 100)), + Weight: makeUint32Value(weight), Name: targetOptions.clusterName, } if err := injectHeaderManipToWeightedCluster(split.Definition, cw); err != nil { @@ -886,12 +896,19 @@ func (s *ResourceGenerator) makeRouteActionForSplitter( return nil, fmt.Errorf("number of clusters in splitter must be > 0; got %d", len(clusters)) } + envoyWeightScale := 10000 + if envoyWeightScale < totalWeight { + clusters[0].Weight.Value += uint32(totalWeight - envoyWeightScale) + } else { + clusters[0].Weight.Value += uint32(envoyWeightScale - totalWeight) + } + return &envoy_route_v3.Route_Route{ Route: &envoy_route_v3.RouteAction{ ClusterSpecifier: &envoy_route_v3.RouteAction_WeightedClusters{ WeightedClusters: &envoy_route_v3.WeightedCluster{ Clusters: clusters, - TotalWeight: makeUint32Value(10000), // scaled up 100% + TotalWeight: makeUint32Value(envoyWeightScale), // scaled up 100% }, }, }, diff --git a/agent/xds/routes_test.go b/agent/xds/routes_test.go index cec0f650c7a..c68e54c6d0a 100644 --- a/agent/xds/routes_test.go +++ b/agent/xds/routes_test.go @@ -19,67 +19,74 @@ import ( "github.com/hashicorp/consul/sdk/testutil" ) -func TestRoutesFromSnapshot(t *testing.T) { - // TODO: we should move all of these to TestAllResourcesFromSnapshot - // eventually to test all of the xDS types at once with the same input, - // just as it would be triggered by our xDS server. - if testing.Short() { - t.Skip("too slow for testing.Short") - } +type routeTestCase struct { + name string + create func(t testinf.T) *proxycfg.ConfigSnapshot + overrideGoldenName string +} - tests := []struct { - name string - create func(t testinf.T) *proxycfg.ConfigSnapshot - overrideGoldenName string - }{ +func makeRouteDiscoChainTests(enterprise bool) []routeTestCase { + return []routeTestCase{ { name: "connect-proxy-with-chain", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil) }, }, { name: "connect-proxy-with-chain-external-sni", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", enterprise, nil, nil) }, }, { name: "connect-proxy-with-chain-and-overrides", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple-with-overrides", enterprise, nil, nil) }, }, { name: "splitter-with-resolver-redirect", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", enterprise, nil, nil) }, }, { name: "connect-proxy-with-chain-and-splitter", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "chain-and-splitter", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "chain-and-splitter", enterprise, nil, nil) }, }, { name: "connect-proxy-with-grpc-router", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "grpc-router", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "grpc-router", enterprise, nil, nil) }, }, { name: "connect-proxy-with-chain-and-router", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "chain-and-router", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "chain-and-router", enterprise, nil, nil) }, }, { name: "connect-proxy-lb-in-resolver", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "lb-resolver", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "lb-resolver", enterprise, nil, nil) }, }, + } +} + +func TestRoutesFromSnapshot(t *testing.T) { + // TODO: we should move all of these to TestAllResourcesFromSnapshot + // eventually to test all of the xDS types at once with the same input, + // just as it would be triggered by our xDS server. + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + tests := []routeTestCase{ // TODO(rb): test match stanza skipped for grpc // Start ingress gateway test cases { @@ -188,6 +195,8 @@ func TestRoutesFromSnapshot(t *testing.T) { }, } + tests = append(tests, makeRouteDiscoChainTests(false)...) + latestEnvoyVersion := xdscommon.EnvoyVersions[0] for _, envoyVersion := range xdscommon.EnvoyVersions { sf, err := xdscommon.DetermineSupportedProxyFeaturesFromString(envoyVersion) diff --git a/agent/xds/secrets.go b/agent/xds/secrets.go index 5547b6d2037..a1ef50e44ca 100644 --- a/agent/xds/secrets.go +++ b/agent/xds/secrets.go @@ -21,18 +21,10 @@ func (s *ResourceGenerator) secretsFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot case structs.ServiceKindConnectProxy, structs.ServiceKindTerminatingGateway, structs.ServiceKindMeshGateway, - structs.ServiceKindIngressGateway: + structs.ServiceKindIngressGateway, + structs.ServiceKindAPIGateway: return nil, nil - // Only API gateways utilize secrets - case structs.ServiceKindAPIGateway: - return s.secretsFromSnapshotAPIGateway(cfgSnap) default: return nil, fmt.Errorf("Invalid service kind: %v", cfgSnap.Kind) } } - -func (s *ResourceGenerator) secretsFromSnapshotAPIGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { - var res []proto.Message - // TODO - return res, nil -} diff --git a/agent/xds/testcommon/testcommon.go b/agent/xds/testcommon/testcommon.go index c310d0980a6..ba75cf9ae97 100644 --- a/agent/xds/testcommon/testcommon.go +++ b/agent/xds/testcommon/testcommon.go @@ -22,6 +22,9 @@ func SetupTLSRootsAndLeaf(t *testing.T, snap *proxycfg.ConfigSnapshot) { case structs.ServiceKindMeshGateway: snap.MeshGateway.Leaf.CertPEM = loadTestResource(t, "test-leaf-cert") snap.MeshGateway.Leaf.PrivateKeyPEM = loadTestResource(t, "test-leaf-key") + case structs.ServiceKindAPIGateway: + snap.APIGateway.Leaf.CertPEM = loadTestResource(t, "test-leaf-cert") + snap.APIGateway.Leaf.PrivateKeyPEM = loadTestResource(t, "test-leaf-key") } } if snap.Roots != nil { diff --git a/agent/xds/testdata/builtin_extension/clusters/http-local-ratelimit-applyto-filter.latest.golden b/agent/xds/testdata/builtin_extension/clusters/http-local-ratelimit-applyto-filter.latest.golden deleted file mode 100644 index 6f67c341ddf..00000000000 --- a/agent/xds/testdata/builtin_extension/clusters/http-local-ratelimit-applyto-filter.latest.golden +++ /dev/null @@ -1,127 +0,0 @@ -{ - "versionInfo": "00000001", - "resources": [ - { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": {}, - "resourceApiVersion": "V3" - } - }, - "connectTimeout": "5s", - "circuitBreakers": {}, - "outlierDetection": {}, - "commonLbConfig": { - "healthyPanicThreshold": {} - }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": {}, - "tlsCertificates": [ - { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" - }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" - } - } - ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" - }, - "matchSubjectAltNames": [ - { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" - } - ] - } - }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" - } - } - }, - { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": {}, - "resourceApiVersion": "V3" - } - }, - "connectTimeout": "5s", - "circuitBreakers": {}, - "outlierDetection": {}, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": {}, - "tlsCertificates": [ - { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" - }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" - } - } - ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" - }, - "matchSubjectAltNames": [ - { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target" - }, - { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target" - } - ] - } - }, - "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" - } - } - }, - { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "local_app", - "type": "STATIC", - "connectTimeout": "5s", - "loadAssignment": { - "clusterName": "local_app", - "endpoints": [ - { - "lbEndpoints": [ - { - "endpoint": { - "address": { - "socketAddress": { - "address": "127.0.0.1", - "portValue": 8080 - } - } - } - } - ] - } - ] - } - } - ], - "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "nonce": "00000001" -} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/endpoints/http-local-ratelimit-applyto-filter.latest.golden b/agent/xds/testdata/builtin_extension/endpoints/http-local-ratelimit-applyto-filter.latest.golden deleted file mode 100644 index e8e6b94a1a9..00000000000 --- a/agent/xds/testdata/builtin_extension/endpoints/http-local-ratelimit-applyto-filter.latest.golden +++ /dev/null @@ -1,75 +0,0 @@ -{ - "versionInfo": "00000001", - "resources": [ - { - "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "endpoints": [ - { - "lbEndpoints": [ - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.10.1.1", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 - }, - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.10.1.2", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 - } - ] - } - ] - }, - { - "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", - "endpoints": [ - { - "lbEndpoints": [ - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.10.1.1", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 - }, - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.20.1.2", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 - } - ] - } - ] - } - ], - "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "nonce": "00000001" -} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/listeners/http-local-ratelimit-applyto-filter.latest.golden b/agent/xds/testdata/builtin_extension/listeners/http-local-ratelimit-applyto-filter.latest.golden deleted file mode 100644 index 29c336296e4..00000000000 --- a/agent/xds/testdata/builtin_extension/listeners/http-local-ratelimit-applyto-filter.latest.golden +++ /dev/null @@ -1,256 +0,0 @@ -{ - "versionInfo": "00000001", - "resources": [ - { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "db:127.0.0.1:9191", - "address": { - "socketAddress": { - "address": "127.0.0.1", - "portValue": 9191 - } - }, - "filterChains": [ - { - "filters": [ - { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "upstream.db.default.default.dc1", - "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" - } - } - ] - } - ], - "trafficDirection": "OUTBOUND" - }, - { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "prepared_query:geo-cache:127.10.10.10:8181", - "address": { - "socketAddress": { - "address": "127.10.10.10", - "portValue": 8181 - } - }, - "filterChains": [ - { - "filters": [ - { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "upstream.prepared_query_geo-cache", - "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" - } - } - ] - } - ], - "trafficDirection": "OUTBOUND" - }, - { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "public_listener:0.0.0.0:9999", - "address": { - "socketAddress": { - "address": "0.0.0.0", - "portValue": 9999 - } - }, - "filterChains": [ - { - "filters": [ - { - "name": "envoy.filters.network.http_connection_manager", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "statPrefix": "public_listener", - "routeConfig": { - "name": "public_listener", - "virtualHosts": [ - { - "name": "public_listener", - "domains": [ - "*" - ], - "routes": [ - { - "match": { - "prefix": "/" - }, - "route": { - "cluster": "local_app" - } - } - ] - } - ] - }, - "httpFilters": [ - { - "name": "envoy.filters.http.local_ratelimit", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "statPrefix": "local_ratelimit", - "tokenBucket": { - "maxTokens": 3, - "tokensPerFill": 2, - "fillInterval": "10s" - }, - "filterEnabled": { - "defaultValue": { - "numerator": 100 - } - }, - "filterEnforced": { - "defaultValue": { - "numerator": 100 - } - } - } - }, - { - "name": "envoy.filters.http.rbac", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", - "rules": {} - } - }, - { - "name": "envoy.filters.http.header_to_metadata", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config", - "requestRules": [ - { - "header": "x-forwarded-client-cert", - "onHeaderPresent": { - "metadataNamespace": "consul", - "key": "trust-domain", - "regexValueRewrite": { - "pattern": { - "googleRe2": {}, - "regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*" - }, - "substitution": "\\1" - } - } - }, - { - "header": "x-forwarded-client-cert", - "onHeaderPresent": { - "metadataNamespace": "consul", - "key": "partition", - "regexValueRewrite": { - "pattern": { - "googleRe2": {}, - "regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*" - }, - "substitution": "\\2" - } - } - }, - { - "header": "x-forwarded-client-cert", - "onHeaderPresent": { - "metadataNamespace": "consul", - "key": "namespace", - "regexValueRewrite": { - "pattern": { - "googleRe2": {}, - "regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*" - }, - "substitution": "\\3" - } - } - }, - { - "header": "x-forwarded-client-cert", - "onHeaderPresent": { - "metadataNamespace": "consul", - "key": "datacenter", - "regexValueRewrite": { - "pattern": { - "googleRe2": {}, - "regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*" - }, - "substitution": "\\4" - } - } - }, - { - "header": "x-forwarded-client-cert", - "onHeaderPresent": { - "metadataNamespace": "consul", - "key": "service", - "regexValueRewrite": { - "pattern": { - "googleRe2": {}, - "regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*" - }, - "substitution": "\\5" - } - } - } - ] - } - }, - { - "name": "envoy.filters.http.router", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" - } - } - ], - "tracing": { - "randomSampling": {} - }, - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "subject": true, - "cert": true, - "chain": true, - "dns": true, - "uri": true - } - } - } - ], - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", - "commonTlsContext": { - "tlsParams": {}, - "tlsCertificates": [ - { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" - }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" - } - } - ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" - } - }, - "alpnProtocols": [ - "http/1.1" - ] - }, - "requireClientCertificate": true - } - } - } - ], - "trafficDirection": "INBOUND" - } - ], - "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", - "nonce": "00000001" -} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/routes/http-local-ratelimit-applyto-filter.latest.golden b/agent/xds/testdata/builtin_extension/routes/http-local-ratelimit-applyto-filter.latest.golden deleted file mode 100644 index d0810938103..00000000000 --- a/agent/xds/testdata/builtin_extension/routes/http-local-ratelimit-applyto-filter.latest.golden +++ /dev/null @@ -1,5 +0,0 @@ -{ - "versionInfo": "00000001", - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" -} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/api-gateway-with-http-route-and-inline-certificate.latest.golden b/agent/xds/testdata/clusters/api-gateway-with-http-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..e20479dfd1c --- /dev/null +++ b/agent/xds/testdata/clusters/api-gateway-with-http-route-and-inline-certificate.latest.golden @@ -0,0 +1,55 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": {}, + "outlierDetection": {}, + "commonLbConfig": { + "healthyPanicThreshold": {} + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/service" + } + ] + } + }, + "sni": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/api-gateway-with-tcp-route-and-inline-certificate.latest.golden b/agent/xds/testdata/clusters/api-gateway-with-tcp-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..e20479dfd1c --- /dev/null +++ b/agent/xds/testdata/clusters/api-gateway-with-tcp-route-and-inline-certificate.latest.golden @@ -0,0 +1,55 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": {}, + "outlierDetection": {}, + "commonLbConfig": { + "healthyPanicThreshold": {} + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/service" + } + ] + } + }, + "sni": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden index 78d23803d12..aed47665204 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden @@ -1,209 +1,183 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "clusterType": { - "name": "envoy.clusters.aggregate", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig", - "clusters": [ + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterType": { + "name": "envoy.clusters.aggregate", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig", + "clusters": [ "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "failover-target~db.default.cluster-01.external.peer1.domain" ] } }, - "connectTimeout": "33s", - "lbPolicy": "CLUSTER_PROVIDED" + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "failover-target~db.default.cluster-01.external.peer1.domain", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.cluster-01.external.peer1.domain", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "1s", - "circuitBreakers": { - - }, - "outlierDetection": { - "maxEjectionPercent": 100 + "connectTimeout": "1s", + "circuitBreakers": {}, + "outlierDetection": { + "maxEjectionPercent": 100 }, - "commonLbConfig": { - "healthyPanicThreshold": { - - } + "commonLbConfig": { + "healthyPanicThreshold": {} }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "peer1-root-1\n" + "validationContext": { + "trustedCa": { + "inlineString": "peer1-root-1\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/db" + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/dc2/svc/db" } ] } }, - "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" } } }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "altStatName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "33s", - "circuitBreakers": { - - }, - "outlierDetection": { - + "connectTimeout": "33s", + "circuitBreakers": {}, + "outlierDetection": {}, + "commonLbConfig": { + "healthyPanicThreshold": {} }, - "commonLbConfig": { - "healthyPanicThreshold": { - - } - }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "5s", - "circuitBreakers": { - - }, - "outlierDetection": { - - }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "connectTimeout": "5s", + "circuitBreakers": {}, + "outlierDetection": {}, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target" + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target" }, { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target" + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target" } ] } }, - "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" } } }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "local_app", - "type": "STATIC", - "connectTimeout": "5s", - "loadAssignment": { - "clusterName": "local_app", - "endpoints": [ + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "local_app", + "type": "STATIC", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "local_app", + "endpoints": [ { - "lbEndpoints": [ + "lbEndpoints": [ { - "endpoint": { - "address": { - "socketAddress": { - "address": "127.0.0.1", - "portValue": 8080 + "endpoint": { + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 8080 } } } @@ -214,6 +188,6 @@ } } ], - "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden index a797d7e8914..70b94debf20 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -1,134 +1,118 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "db.default.cluster-01.external.peer1.domain", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.cluster-01.external.peer1.domain", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "1s", - "circuitBreakers": { - - }, - "outlierDetection": { - "maxEjectionPercent": 100 + "connectTimeout": "1s", + "circuitBreakers": {}, + "outlierDetection": { + "maxEjectionPercent": 100 }, - "commonLbConfig": { - "healthyPanicThreshold": { - - } + "commonLbConfig": { + "healthyPanicThreshold": {} }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "peer1-root-1\n" + "validationContext": { + "trustedCa": { + "inlineString": "peer1-root-1\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/db" + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/dc2/svc/db" } ] } }, - "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" } } }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "5s", - "circuitBreakers": { - - }, - "outlierDetection": { - - }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "connectTimeout": "5s", + "circuitBreakers": {}, + "outlierDetection": {}, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target" + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target" }, { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target" + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target" } ] } }, - "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" } } }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "local_app", - "type": "STATIC", - "connectTimeout": "5s", - "loadAssignment": { - "clusterName": "local_app", - "endpoints": [ + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "local_app", + "type": "STATIC", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "local_app", + "endpoints": [ { - "lbEndpoints": [ + "lbEndpoints": [ { - "endpoint": { - "address": { - "socketAddress": { - "address": "127.0.0.1", - "portValue": 8080 + "endpoint": { + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 8080 } } } @@ -139,6 +123,6 @@ } } ], - "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/clusters/hcp-metrics.latest.golden b/agent/xds/testdata/clusters/hcp-metrics.latest.golden new file mode 100644 index 00000000000..441763f1a92 --- /dev/null +++ b/agent/xds/testdata/clusters/hcp-metrics.latest.golden @@ -0,0 +1,183 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": {}, + "outlierDetection": {}, + "commonLbConfig": { + "healthyPanicThreshold": {} + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" + } + ] + } + }, + "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": {}, + "outlierDetection": {}, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target" + }, + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target" + } + ] + } + }, + "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "hcp-metrics-collector.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "hcp-metrics-collector.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": {}, + "typedExtensionProtocolOptions": { + "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": { + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions", + "explicitHttpConfig": { + "http2ProtocolOptions": {} + } + } + }, + "outlierDetection": {}, + "commonLbConfig": { + "healthyPanicThreshold": {} + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/hcp-metrics-collector" + } + ] + } + }, + "sni": "hcp-metrics-collector.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "local_app", + "type": "STATIC", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "local_app", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 8080 + } + } + } + } + ] + } + ] + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden index f0f79211fa8..fbdfa6eede9 100644 --- a/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden @@ -1,142 +1,122 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "clusterType": { - "name": "envoy.clusters.aggregate", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig", - "clusters": [ + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterType": { + "name": "envoy.clusters.aggregate", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig", + "clusters": [ "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "failover-target~db.default.cluster-01.external.peer1.domain" ] } }, - "connectTimeout": "33s", - "lbPolicy": "CLUSTER_PROVIDED", - "outlierDetection": { - - } + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED", + "outlierDetection": {} }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "failover-target~db.default.cluster-01.external.peer1.domain", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.cluster-01.external.peer1.domain", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "33s", - "circuitBreakers": { - + "connectTimeout": "33s", + "circuitBreakers": {}, + "outlierDetection": { + "maxEjectionPercent": 100 }, - "outlierDetection": { - "maxEjectionPercent": 100 - }, - "commonLbConfig": { - "healthyPanicThreshold": { - - } + "commonLbConfig": { + "healthyPanicThreshold": {} }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "peer1-root-1\n" + "validationContext": { + "trustedCa": { + "inlineString": "peer1-root-1\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/db" + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/dc2/svc/db" } ] } }, - "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" } } }, { - "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "name": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "altStatName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "type": "EDS", - "edsClusterConfig": { - "edsConfig": { - "ads": { - - }, - "resourceApiVersion": "V3" + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": {}, + "resourceApiVersion": "V3" } }, - "connectTimeout": "33s", - "circuitBreakers": { - - }, - "outlierDetection": { - - }, - "commonLbConfig": { - "healthyPanicThreshold": { - - } + "connectTimeout": "33s", + "circuitBreakers": {}, + "outlierDetection": {}, + "commonLbConfig": { + "healthyPanicThreshold": {} }, - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" }, - "matchSubjectAltNames": [ + "matchSubjectAltNames": [ { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } } ], - "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/api-gateway-with-http-route-and-inline-certificate.latest.golden b/agent/xds/testdata/endpoints/api-gateway-with-http-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..18adccd10c8 --- /dev/null +++ b/agent/xds/testdata/endpoints/api-gateway-with-http-route-and-inline-certificate.latest.golden @@ -0,0 +1,41 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/api-gateway-with-tcp-route-and-inline-certificate.latest.golden b/agent/xds/testdata/endpoints/api-gateway-with-tcp-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..47b46bca225 --- /dev/null +++ b/agent/xds/testdata/endpoints/api-gateway-with-tcp-route-and-inline-certificate.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden index 8cb6ce20a06..e55cdc39f7a 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -1,63 +1,63 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.cluster-01.external.peer1.domain", - "endpoints": [ + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "db.default.cluster-01.external.peer1.domain", + "endpoints": [ { - "lbEndpoints": [ + "lbEndpoints": [ { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.40.1.1", - "portValue": 8080 + "endpoint": { + "address": { + "socketAddress": { + "address": "10.40.1.1", + "portValue": 8080 } } }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 } ] } ] }, { - "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", - "endpoints": [ + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ { - "lbEndpoints": [ + "lbEndpoints": [ { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.10.1.1", - "portValue": 8080 + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 } } }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 }, { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.20.1.2", - "portValue": 8080 + "endpoint": { + "address": { + "socketAddress": { + "address": "10.20.1.2", + "portValue": 8080 } } }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 } ] } ] } ], - "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/hcp-metrics.latest.golden b/agent/xds/testdata/endpoints/hcp-metrics.latest.golden new file mode 100644 index 00000000000..a19ac95f210 --- /dev/null +++ b/agent/xds/testdata/endpoints/hcp-metrics.latest.golden @@ -0,0 +1,97 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.20.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "hcp-metrics-collector.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "9.9.9.9", + "portValue": 9090 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-http-listener-with-http-route.latest.golden b/agent/xds/testdata/listeners/api-gateway-http-listener-with-http-route.latest.golden new file mode 100644 index 00000000000..e9bee988de9 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-http-listener-with-http-route.latest.golden @@ -0,0 +1,49 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": {}, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": {} + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-http-listener.latest.golden b/agent/xds/testdata/listeners/api-gateway-http-listener.latest.golden new file mode 100644 index 00000000000..d2d839adf95 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-http-listener.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-nil-config-entry.latest.golden b/agent/xds/testdata/listeners/api-gateway-nil-config-entry.latest.golden new file mode 100644 index 00000000000..d2d839adf95 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-nil-config-entry.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-and-http-route.latest.golden b/agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-and-http-route.latest.golden new file mode 100644 index 00000000000..9e136780767 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-and-http-route.latest.golden @@ -0,0 +1,74 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": {}, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": {} + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "tcp-service:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.tcp-service.default.default.dc1", + "cluster": "tcp-service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-route.latest.golden b/agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-route.latest.golden new file mode 100644 index 00000000000..ffcb5830b9a --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-route.latest.golden @@ -0,0 +1,32 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "tcp-service:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.tcp-service.default.default.dc1", + "cluster": "tcp-service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-tcp-listener.latest.golden b/agent/xds/testdata/listeners/api-gateway-tcp-listener.latest.golden new file mode 100644 index 00000000000..d2d839adf95 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-tcp-listener.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-with-http-route-and-inline-certificate.latest.golden b/agent/xds/testdata/listeners/api-gateway-with-http-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..e9bee988de9 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-with-http-route-and-inline-certificate.latest.golden @@ -0,0 +1,49 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": {}, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": {} + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway-with-tcp-route-and-inline-certificate.latest.golden b/agent/xds/testdata/listeners/api-gateway-with-tcp-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..3bfbb71f067 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway-with-tcp-route-and-inline-certificate.latest.golden @@ -0,0 +1,60 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "service:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "ingress_upstream_certificate", + "cluster": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV\nUzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB\nptqOdam2ch4BHQGhNhX/MrDwwuZQhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2\njQlhqTodElkbsd5vWY8R/bxJWQSoNvVE12TlzECxGpJEiHt4W0r8pGffk+rvplji\nUyCfnT1kGF3znOSjK1hRMTn6RKWCyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409\ng9X5VU88/Bmmrz4cMyxce86Kc2ug5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftr\nXOvuCbO5IBRHMOBHiHTZ4rtGuhMaIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+W\nmQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBfCqoUIdPf/HGSbOorPyZWbyizNtHJ\nGL7x9cAeIYxpI5Y/WcO1o5v94lvrgm3FNfJoGKbV66+JxOge731FrfMpHplhar1Z\nRahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK\nNtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO\nqwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww\nAAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ\nhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo\nNvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC\nyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409g9X5VU88/Bmmrz4cMyxce86Kc2ug\n5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftrXOvuCbO5IBRHMOBHiHTZ4rtGuhMa\nIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+WmQIDAQABAoIBACYvceUzp2MK4gYA\nGWPOP2uKbBdM0l+hHeNV0WAM+dHMfmMuL4pkT36ucqt0ySOLjw6rQyOZG5nmA6t9\nsv0g4ae2eCMlyDIeNi1Yavu4Wt6YX4cTXbQKThm83C6W2X9THKbauBbxD621bsDK\n7PhiGPN60yPue7YwFQAPqqD4YaK+s22HFIzk9gwM/rkvAUNwRv7SyHMiFe4Igc1C\nEev7iHWzvj5Heoz6XfF+XNF9DU+TieSUAdjd56VyUb8XL4+uBTOhHwLiXvAmfaMR\nHvpcxeKnYZusS6NaOxcUHiJnsLNWrxmJj9WEGgQzuLxcLjTe4vVmELVZD8t3QUKj\nPAxu8tUCgYEA7KIWVn9dfVpokReorFym+J8FzLwSktP9RZYEMonJo00i8aii3K9s\nu/aSwRWQSCzmON1ZcxZzWhwQF9usz6kGCk//9+4hlVW90GtNK0RD+j7sp4aT2JI8\n9eLEjTG+xSXa7XWe98QncjjL9lu/yrRncSTxHs13q/XP198nn2aYuQ8CgYEA2Dnt\nsRBzv0fFEvzzFv7G/5f85mouN38TUYvxNRTjBLCXl9DeKjDkOVZ2b6qlfQnYXIru\nH+W+v+AZEb6fySXc8FRab7lkgTMrwE+aeI4rkW7asVwtclv01QJ5wMnyT84AgDD/\nDgt/RThFaHgtU9TW5GOZveL+l9fVPn7vKFdTJdcCgYEArJ99zjHxwJ1whNAOk1av\n09UmRPm6TvRo4heTDk8oEoIWCNatoHI0z1YMLuENNSnT9Q280FFDayvnrY/qnD7A\nkktT/sjwJOG8q8trKzIMqQS4XWm2dxoPcIyyOBJfCbEY6XuRsUuePxwh5qF942EB\nyS9a2s6nC4Ix0lgPrqAIr48CgYBgS/Q6riwOXSU8nqCYdiEkBYlhCJrKpnJxF9T1\nofa0yPzKZP/8ZEfP7VzTwHjxJehQ1qLUW9pG08P2biH1UEKEWdzo8vT6wVJT1F/k\nHtTycR8+a+Hlk2SHVRHqNUYQGpuIe8mrdJ1as4Pd0d/F/P0zO9Rlh+mAsGPM8HUM\nT0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj\nnZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX\nkHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/\n-----END RSA PRIVATE KEY-----\n" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/api-gateway.latest.golden b/agent/xds/testdata/listeners/api-gateway.latest.golden new file mode 100644 index 00000000000..d2d839adf95 --- /dev/null +++ b/agent/xds/testdata/listeners/api-gateway.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/listeners/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden index 57d50f71c36..5fdd2e351c0 100644 --- a/agent/xds/testdata/listeners/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/listeners/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden @@ -1,119 +1,115 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "db:127.0.0.1:9191", - "address": { - "socketAddress": { - "address": "127.0.0.1", - "portValue": 9191 + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 } }, - "filterChains": [ + "filterChains": [ { - "filters": [ + "filters": [ { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "upstream.db.default.default.dc1", - "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } ] } ], - "trafficDirection": "OUTBOUND" + "trafficDirection": "OUTBOUND" }, { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "prepared_query:geo-cache:127.10.10.10:8181", - "address": { - "socketAddress": { - "address": "127.10.10.10", - "portValue": 8181 + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 } }, - "filterChains": [ + "filterChains": [ { - "filters": [ + "filters": [ { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "upstream.prepared_query_geo-cache", - "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" } } ] } ], - "trafficDirection": "OUTBOUND" + "trafficDirection": "OUTBOUND" }, { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "public_listener:0.0.0.0:9999", - "address": { - "socketAddress": { - "address": "0.0.0.0", - "portValue": 9999 + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 } }, - "filterChains": [ + "filterChains": [ { - "filters": [ + "filters": [ { - "name": "envoy.filters.network.rbac", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", - "rules": { - - }, - "statPrefix": "connect_authz" + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" } }, { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "public_listener", - "cluster": "local_app" + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" } } ], - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" } } }, - "requireClientCertificate": true + "requireClientCertificate": true } } } ], - "trafficDirection": "INBOUND" + "trafficDirection": "INBOUND" } ], - "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/listeners/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/listeners/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden index e061148c008..02d749a2c5c 100644 --- a/agent/xds/testdata/listeners/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/listeners/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -1,119 +1,115 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "db:127.0.0.1:9191", - "address": { - "socketAddress": { - "address": "127.0.0.1", - "portValue": 9191 + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 } }, - "filterChains": [ + "filterChains": [ { - "filters": [ + "filters": [ { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "upstream.db.default.default.dc1", - "cluster": "db.default.cluster-01.external.peer1.domain" + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.cluster-01.external.peer1.domain" } } ] } ], - "trafficDirection": "OUTBOUND" + "trafficDirection": "OUTBOUND" }, { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "prepared_query:geo-cache:127.10.10.10:8181", - "address": { - "socketAddress": { - "address": "127.10.10.10", - "portValue": 8181 + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 } }, - "filterChains": [ + "filterChains": [ { - "filters": [ + "filters": [ { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "upstream.prepared_query_geo-cache", - "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" } } ] } ], - "trafficDirection": "OUTBOUND" + "trafficDirection": "OUTBOUND" }, { - "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", - "name": "public_listener:0.0.0.0:9999", - "address": { - "socketAddress": { - "address": "0.0.0.0", - "portValue": 9999 + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 } }, - "filterChains": [ + "filterChains": [ { - "filters": [ + "filters": [ { - "name": "envoy.filters.network.rbac", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", - "rules": { - - }, - "statPrefix": "connect_authz" + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" } }, { - "name": "envoy.filters.network.tcp_proxy", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "statPrefix": "public_listener", - "cluster": "local_app" + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" } } ], - "transportSocket": { - "name": "tls", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", - "commonTlsContext": { - "tlsParams": { - - }, - "tlsCertificates": [ + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ { - "certificateChain": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" }, - "privateKey": { - "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" } } ], - "validationContext": { - "trustedCa": { - "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" } } }, - "requireClientCertificate": true + "requireClientCertificate": true } } } ], - "trafficDirection": "INBOUND" + "trafficDirection": "INBOUND" } ], - "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/listeners/hcp-metrics.latest.golden b/agent/xds/testdata/listeners/hcp-metrics.latest.golden new file mode 100644 index 00000000000..d89036a935c --- /dev/null +++ b/agent/xds/testdata/listeners/hcp-metrics.latest.golden @@ -0,0 +1,184 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "hcp-metrics-collector:/tmp/consul/hcp-metrics/default_web-sidecar-proxy.sock", + "address": { + "pipe": { + "path": "/tmp/consul/hcp-metrics/default_web-sidecar-proxy.sock" + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "upstream.hcp-metrics-collector.default.default.dc1", + "routeConfig": { + "name": "hcp-metrics-collector", + "virtualHosts": [ + { + "name": "hcp-metrics-collector.default.default.dc1", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "hcp-metrics-collector.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ] + }, + "httpFilters": [ + { + "name": "envoy.filters.http.grpc_stats", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig", + "statsForAllMethods": true + } + }, + { + "name": "envoy.filters.http.grpc_http1_bridge", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config" + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": {} + }, + "http2ProtocolOptions": {} + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": {}, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/api-gateway-with-http-route-and-inline-certificate.latest.golden b/agent/xds/testdata/routes/api-gateway-with-http-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..6abc6f2946b --- /dev/null +++ b/agent/xds/testdata/routes/api-gateway-with-http-route-and-inline-certificate.latest.golden @@ -0,0 +1,31 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "8080", + "virtualHosts": [ + { + "name": "api-gateway-listener-9b9265b", + "domains": [ + "*", + "*:8080" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/api-gateway-with-tcp-route-and-inline-certificate.latest.golden b/agent/xds/testdata/routes/api-gateway-with-tcp-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..306f5220e7b --- /dev/null +++ b/agent/xds/testdata/routes/api-gateway-with-tcp-route-and-inline-certificate.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/routes/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden index 547b923b0d6..095330c6840 100644 --- a/agent/xds/testdata/routes/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/routes/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden @@ -1,30 +1,31 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "db", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "db", + "virtualHosts": [ { - "name": "db", - "domains": [ + "name": "db", + "domains": [ "*" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "33s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain-and-overrides.latest.golden b/agent/xds/testdata/routes/connect-proxy-with-chain-and-overrides.latest.golden index f5b65496101..c40cfe9e90e 100644 --- a/agent/xds/testdata/routes/connect-proxy-with-chain-and-overrides.latest.golden +++ b/agent/xds/testdata/routes/connect-proxy-with-chain-and-overrides.latest.golden @@ -16,7 +16,8 @@ "prefix": "/" }, "route": { - "cluster": "78ebd528~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "cluster": "78ebd528~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "33s" } } ] diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/routes/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden index e63a643ef7d..cf29d6729a0 100644 --- a/agent/xds/testdata/routes/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/routes/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -1,30 +1,31 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "db", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "db", + "virtualHosts": [ { - "name": "db", - "domains": [ + "name": "db", + "domains": [ "*" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "db.default.cluster-01.external.peer1.domain" + "route": { + "cluster": "db.default.cluster-01.external.peer1.domain", + "timeout": "33s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.latest.golden b/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.latest.golden index 547b923b0d6..095330c6840 100644 --- a/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.latest.golden +++ b/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.latest.golden @@ -1,30 +1,31 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "db", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "db", + "virtualHosts": [ { - "name": "db", - "domains": [ + "name": "db", + "domains": [ "*" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "33s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain.latest.golden b/agent/xds/testdata/routes/connect-proxy-with-chain.latest.golden index 547b923b0d6..095330c6840 100644 --- a/agent/xds/testdata/routes/connect-proxy-with-chain.latest.golden +++ b/agent/xds/testdata/routes/connect-proxy-with-chain.latest.golden @@ -1,30 +1,31 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "db", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "db", + "virtualHosts": [ { - "name": "db", - "domains": [ + "name": "db", + "domains": [ "*" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "33s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/hcp-metrics.latest.golden b/agent/xds/testdata/routes/hcp-metrics.latest.golden new file mode 100644 index 00000000000..306f5220e7b --- /dev/null +++ b/agent/xds/testdata/routes/hcp-metrics.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-grpc-multiple-services.latest.golden b/agent/xds/testdata/routes/ingress-grpc-multiple-services.latest.golden index 2822f903d7c..006bf5157b0 100644 --- a/agent/xds/testdata/routes/ingress-grpc-multiple-services.latest.golden +++ b/agent/xds/testdata/routes/ingress-grpc-multiple-services.latest.golden @@ -1,50 +1,52 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "8080", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "8080", + "virtualHosts": [ { - "name": "foo", - "domains": [ + "name": "foo", + "domains": [ "test1.example.com", "test2.example.com", "test2.example.com:8080", "test1.example.com:8080" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] }, { - "name": "bar", - "domains": [ + "name": "bar", + "domains": [ "bar.ingress.*", "bar.ingress.*:8080" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-http-multiple-services.latest.golden b/agent/xds/testdata/routes/ingress-http-multiple-services.latest.golden index 4e7cfc422dc..507c66a46d5 100644 --- a/agent/xds/testdata/routes/ingress-http-multiple-services.latest.golden +++ b/agent/xds/testdata/routes/ingress-http-multiple-services.latest.golden @@ -60,7 +60,8 @@ "prefix": "/" }, "route": { - "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] @@ -77,7 +78,8 @@ "prefix": "/" }, "route": { - "cluster": "bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "cluster": "bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] diff --git a/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.latest.golden b/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.latest.golden index 3c251942ec1..cedfc99f655 100644 --- a/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.latest.golden +++ b/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.latest.golden @@ -1,48 +1,50 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "9191", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191", + "virtualHosts": [ { - "name": "web", - "domains": [ + "name": "web", + "domains": [ "web.ingress.*", "web.ingress.*:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] }, { - "name": "foo", - "domains": [ + "name": "foo", + "domains": [ "foo.ingress.*", "foo.ingress.*:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-listener-level.latest.golden b/agent/xds/testdata/routes/ingress-with-sds-listener-level.latest.golden index 0dc02c50569..3f2631217da 100644 --- a/agent/xds/testdata/routes/ingress-with-sds-listener-level.latest.golden +++ b/agent/xds/testdata/routes/ingress-with-sds-listener-level.latest.golden @@ -1,48 +1,50 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "9191", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191", + "virtualHosts": [ { - "name": "web", - "domains": [ + "name": "web", + "domains": [ "www.example.com", "www.example.com:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] }, { - "name": "foo", - "domains": [ + "name": "foo", + "domains": [ "foo.example.com", "foo.example.com:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.latest.golden b/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.latest.golden index 44ab48e588b..7539ad4feb1 100644 --- a/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.latest.golden +++ b/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.latest.golden @@ -1,55 +1,57 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "9191", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191", + "virtualHosts": [ { - "name": "foo", - "domains": [ + "name": "foo", + "domains": [ "foo.example.com", "foo.example.com:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true }, { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "9191_web", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191_web", + "virtualHosts": [ { - "name": "web", - "domains": [ + "name": "web", + "domains": [ "www.example.com", "www.example.com:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-service-level.latest.golden b/agent/xds/testdata/routes/ingress-with-sds-service-level.latest.golden index 1e207d6efca..6009fac7231 100644 --- a/agent/xds/testdata/routes/ingress-with-sds-service-level.latest.golden +++ b/agent/xds/testdata/routes/ingress-with-sds-service-level.latest.golden @@ -1,55 +1,57 @@ { - "versionInfo": "00000001", - "resources": [ + "versionInfo": "00000001", + "resources": [ { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "9191_foo", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191_foo", + "virtualHosts": [ { - "name": "foo", - "domains": [ + "name": "foo", + "domains": [ "foo.example.com", "foo.example.com:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true }, { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "9191_web", - "virtualHosts": [ + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191_web", + "virtualHosts": [ { - "name": "web", - "domains": [ + "name": "web", + "domains": [ "www.example.com", "www.example.com:9191" ], - "routes": [ + "routes": [ { - "match": { - "prefix": "/" + "match": { + "prefix": "/" }, - "route": { - "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "timeout": "22s" } } ] } ], - "validateClusters": true + "validateClusters": true } ], - "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "nonce": "00000001" + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" } \ No newline at end of file diff --git a/agent/xds/testdata/secrets/api-gateway-with-http-route-and-inline-certificate.latest.golden b/agent/xds/testdata/secrets/api-gateway-with-http-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..95612291de7 --- /dev/null +++ b/agent/xds/testdata/secrets/api-gateway-with-http-route-and-inline-certificate.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/api-gateway-with-tcp-route-and-inline-certificate.latest.golden b/agent/xds/testdata/secrets/api-gateway-with-tcp-route-and-inline-certificate.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/api-gateway-with-tcp-route-and-inline-certificate.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/connect-proxy-exported-to-peers.latest.golden b/agent/xds/testdata/secrets/connect-proxy-exported-to-peers.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/connect-proxy-exported-to-peers.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/secrets/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/secrets/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/connect-proxy-with-peered-upstreams.latest.golden b/agent/xds/testdata/secrets/connect-proxy-with-peered-upstreams.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/connect-proxy-with-peered-upstreams.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/defaults.latest.golden b/agent/xds/testdata/secrets/defaults.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/defaults.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/hcp-metrics.latest.golden b/agent/xds/testdata/secrets/hcp-metrics.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/hcp-metrics.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/local-mesh-gateway-with-peered-upstreams.latest.golden b/agent/xds/testdata/secrets/local-mesh-gateway-with-peered-upstreams.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/local-mesh-gateway-with-peered-upstreams.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/mesh-gateway-peering-control-plane.latest.golden b/agent/xds/testdata/secrets/mesh-gateway-peering-control-plane.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/mesh-gateway-peering-control-plane.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services-http-with-router.latest.golden b/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services-http-with-router.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services-http-with-router.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services-http.latest.golden b/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services-http.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services-http.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services.latest.golden b/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/mesh-gateway-with-exported-peered-services.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/mesh-gateway-with-imported-peered-services.latest.golden b/agent/xds/testdata/secrets/mesh-gateway-with-imported-peered-services.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/mesh-gateway-with-imported-peered-services.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden b/agent/xds/testdata/secrets/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/transparent-proxy-destination-http.latest.golden b/agent/xds/testdata/secrets/transparent-proxy-destination-http.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/transparent-proxy-destination-http.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/transparent-proxy-destination.latest.golden b/agent/xds/testdata/secrets/transparent-proxy-destination.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/transparent-proxy-destination.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/transparent-proxy-terminating-gateway-destinations-only.latest.golden b/agent/xds/testdata/secrets/transparent-proxy-terminating-gateway-destinations-only.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/transparent-proxy-terminating-gateway-destinations-only.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/transparent-proxy-with-peered-upstreams.latest.golden b/agent/xds/testdata/secrets/transparent-proxy-with-peered-upstreams.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/transparent-proxy-with-peered-upstreams.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/secrets/transparent-proxy.latest.golden b/agent/xds/testdata/secrets/transparent-proxy.latest.golden new file mode 100644 index 00000000000..e6c25e165c6 --- /dev/null +++ b/agent/xds/testdata/secrets/transparent-proxy.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testing.go b/agent/xds/testing.go index 967a96e6acd..76cc53f49fd 100644 --- a/agent/xds/testing.go +++ b/agent/xds/testing.go @@ -85,6 +85,8 @@ type TestEnvoy struct { EnvoyVersion string deltaStream *TestADSDeltaStream // Incremental v3 + + closed bool } // NewTestEnvoy creates a TestEnvoy instance. @@ -225,9 +227,9 @@ func (e *TestEnvoy) Close() error { defer e.mu.Unlock() // unblock the recv chans to simulate recv errors when client disconnects - if e.deltaStream != nil && e.deltaStream.recvCh != nil { + if !e.closed && e.deltaStream.recvCh != nil { close(e.deltaStream.recvCh) - e.deltaStream = nil + e.closed = true } if e.cancel != nil { e.cancel() diff --git a/agent/xds/validateupstream-test/validateupstream_test.go b/agent/xds/validateupstream-test/validateupstream_test.go index c78b34d7aa0..c85fe49b0f0 100644 --- a/agent/xds/validateupstream-test/validateupstream_test.go +++ b/agent/xds/validateupstream-test/validateupstream_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/consul/agent/xds/testcommon" "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/consul/troubleshoot/proxy" + troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy" testinf "github.com/mitchellh/go-testing-interface" "github.com/stretchr/testify/require" ) @@ -43,35 +43,35 @@ func TestValidateUpstreams(t *testing.T) { { name: "tcp-success", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil) }, }, { name: "tcp-missing-listener", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil) }, patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources { delete(ir.Index[xdscommon.ListenerType], listenerName) return ir }, - err: "no listener for upstream \"db\"", + err: "No listener for upstream \"db\"", }, { name: "tcp-missing-cluster", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil) }, patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources { delete(ir.Index[xdscommon.ClusterType], sni) return ir }, - err: "no cluster \"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul\" for upstream \"db\"", + err: "No cluster \"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul\" for upstream \"db\"", }, { name: "http-success", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, httpServiceDefaults) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, httpServiceDefaults) }, }, { @@ -79,7 +79,7 @@ func TestValidateUpstreams(t *testing.T) { // RDS, Envoy's Route Discovery Service, is only used for HTTP services with a customized discovery chain, so we // need to use the test snapshot and add L7 config entries. create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, []proxycfg.UpdateEvent{ + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, []proxycfg.UpdateEvent{ // The events ensure there are endpoints for the v1 and v2 subsets. { CorrelationID: "upstream-target:v1.db.default.default.dc1:" + dbUID.String(), @@ -104,7 +104,7 @@ func TestValidateUpstreams(t *testing.T) { // RDS, Envoy's Route Discovery Service, is only used for HTTP services with a customized discovery chain, so we // need to use the test snapshot and add L7 config entries. create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, []proxycfg.UpdateEvent{ + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, []proxycfg.UpdateEvent{ // The events ensure there are endpoints for the v1 and v2 subsets. { CorrelationID: "upstream-target:v1.db.default.default.dc1:" + dbUID.String(), @@ -124,24 +124,24 @@ func TestValidateUpstreams(t *testing.T) { delete(ir.Index[xdscommon.RouteType], "db") return ir }, - err: "no route for upstream \"db\"", + err: "No route for upstream \"db\"", }, { name: "redirect", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "redirect-to-cluster-peer", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "redirect-to-cluster-peer", false, nil, nil) }, }, { name: "failover", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", false, nil, nil) }, }, { name: "failover-to-cluster-peer", create: func(t testinf.T) *proxycfg.ConfigSnapshot { - return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-to-cluster-peer", nil, nil) + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-to-cluster-peer", false, nil, nil) }, }, { @@ -170,7 +170,7 @@ func TestValidateUpstreams(t *testing.T) { delete(ir.Index[xdscommon.ClusterType], sni) return ir }, - err: "no cluster \"google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul\" for upstream \"240.0.0.1\"", + err: "No cluster \"google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul\" for upstream \"240.0.0.1\"", }, { name: "tproxy-http-redirect-success", @@ -230,7 +230,9 @@ func TestValidateUpstreams(t *testing.T) { var outputErrors string for _, msgError := range messages.Errors() { outputErrors += msgError.Message - outputErrors += msgError.PossibleActions + for _, action := range msgError.PossibleActions { + outputErrors += action + } } if len(tt.err) == 0 { require.True(t, messages.Success()) diff --git a/agent/xds/xds_protocol_helpers_test.go b/agent/xds/xds_protocol_helpers_test.go index 8c4481515c8..b71c8282e8f 100644 --- a/agent/xds/xds_protocol_helpers_test.go +++ b/agent/xds/xds_protocol_helpers_test.go @@ -47,7 +47,7 @@ func newTestSnapshot( dbServiceProtocol string, additionalEntries ...structs.ConfigEntry, ) *proxycfg.ConfigSnapshot { - snap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, additionalEntries...) + snap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, additionalEntries...) snap.ConnectProxy.PreparedQueryEndpoints = map[proxycfg.UpstreamID]structs.CheckServiceNodes{ UID("prepared_query:geo-cache"): proxycfg.TestPreparedQueryNodes(t, "geo-cache"), } @@ -166,9 +166,6 @@ func newTestServerDeltaScenario( ) *testServerScenario { mgr := newTestManager(t) envoy := NewTestEnvoy(t, proxyID, token) - t.Cleanup(func() { - envoy.Close() - }) sink := metrics.NewInmemSink(1*time.Minute, 1*time.Minute) cfg := metrics.DefaultConfig("consul.xds.test") @@ -177,6 +174,7 @@ func newTestServerDeltaScenario( metrics.NewGlobal(cfg, sink) t.Cleanup(func() { + envoy.Close() sink := &metrics.BlackholeSink{} metrics.NewGlobal(cfg, sink) }) diff --git a/api/config_entry.go b/api/config_entry.go index 39b7727c89a..4e9682ee6f8 100644 --- a/api/config_entry.go +++ b/api/config_entry.go @@ -33,9 +33,8 @@ const ( ) const ( - BuiltinAWSLambdaExtension string = "builtin/aws/lambda" - BuiltinLuaExtension string = "builtin/lua" - BuiltinLocalRatelimitExtension string = "builtin/http/localratelimit" + BuiltinAWSLambdaExtension string = "builtin/aws/lambda" + BuiltinLuaExtension string = "builtin/lua" ) type ConfigEntry interface { diff --git a/api/config_entry_discoverychain.go b/api/config_entry_discoverychain.go index ff92125dcdc..acc05e13e1f 100644 --- a/api/config_entry_discoverychain.go +++ b/api/config_entry_discoverychain.go @@ -167,6 +167,7 @@ type ServiceResolverConfigEntry struct { Redirect *ServiceResolverRedirect `json:",omitempty"` Failover map[string]ServiceResolverFailover `json:",omitempty"` ConnectTimeout time.Duration `json:",omitempty" alias:"connect_timeout"` + RequestTimeout time.Duration `json:",omitempty" alias:"request_timeout"` // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 209c7ae0df9..05e43832c1f 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -268,9 +268,8 @@ func (g *APIGatewayConfigEntry) GetModifyIndex() uint64 { return g.ModifyInd // APIGatewayListener represents an individual listener for an APIGateway type APIGatewayListener struct { - // Name is the optional name of the listener in a given gateway. This is - // optional, however, it must be unique. Therefore, if a gateway has more - // than a single listener, all but one must specify a Name. + // Name is the name of the listener in a given gateway. This must be + // unique within a gateway. Name string // Hostname is the host name that a listener should be bound to, if // unspecified, the listener accepts requests for all hostnames. diff --git a/api/config_entry_inline_certificate.go b/api/config_entry_inline_certificate.go index d2aa5115eac..bbf12ccaaf6 100644 --- a/api/config_entry_inline_certificate.go +++ b/api/config_entry_inline_certificate.go @@ -12,7 +12,7 @@ type InlineCertificateConfigEntry struct { // Certificate is the public certificate component of an x509 key pair encoded in raw PEM format. Certificate string // PrivateKey is the private key component of an x509 key pair encoded in raw PEM format. - PrivateKey string + PrivateKey string `alias:"private_key"` Meta map[string]string `json:",omitempty"` @@ -34,42 +34,10 @@ type InlineCertificateConfigEntry struct { Namespace string `json:",omitempty"` } -func (a *InlineCertificateConfigEntry) GetKind() string { - return InlineCertificate -} - -func (a *InlineCertificateConfigEntry) GetName() string { - if a != nil { - return "" - } - return a.Name -} - -func (a *InlineCertificateConfigEntry) GetPartition() string { - if a != nil { - return "" - } - return a.Partition -} - -func (a *InlineCertificateConfigEntry) GetNamespace() string { - if a != nil { - return "" - } - return a.GetNamespace() -} - -func (a *InlineCertificateConfigEntry) GetMeta() map[string]string { - if a != nil { - return nil - } - return a.GetMeta() -} - -func (a *InlineCertificateConfigEntry) GetCreateIndex() uint64 { - return a.CreateIndex -} - -func (a *InlineCertificateConfigEntry) GetModifyIndex() uint64 { - return a.ModifyIndex -} +func (a *InlineCertificateConfigEntry) GetKind() string { return InlineCertificate } +func (a *InlineCertificateConfigEntry) GetName() string { return a.Name } +func (a *InlineCertificateConfigEntry) GetPartition() string { return a.Partition } +func (a *InlineCertificateConfigEntry) GetNamespace() string { return a.Namespace } +func (a *InlineCertificateConfigEntry) GetMeta() map[string]string { return a.Meta } +func (a *InlineCertificateConfigEntry) GetCreateIndex() uint64 { return a.CreateIndex } +func (a *InlineCertificateConfigEntry) GetModifyIndex() uint64 { return a.ModifyIndex } diff --git a/api/config_entry_inline_certificate_test.go b/api/config_entry_inline_certificate_test.go new file mode 100644 index 00000000000..78771fcc78c --- /dev/null +++ b/api/config_entry_inline_certificate_test.go @@ -0,0 +1,126 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + // generated via openssl req -x509 -sha256 -days 1825 -newkey rsa:2048 -keyout private.key -out certificate.crt + validPrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg +5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F +eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga +9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv +OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz +9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67 +yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7 +OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs +qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo +Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5 +LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6 +Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg +llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn +gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ +eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA +hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn +orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN +JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC +2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s +1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn +IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS +2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq +wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe +RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK ++QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ== +-----END RSA PRIVATE KEY-----` + validCertificate = `-----BEGIN CERTIFICATE----- +MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD +VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx +NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM +AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc +MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq +oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P +BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4 +GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH +bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6 +8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF +AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N +L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc +U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi +SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq +MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq +J3TrQE3YVrL4D9xnklT86WDnZKApJg== +-----END CERTIFICATE-----` +) + +func TestAPI_ConfigEntries_InlineCertificate(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + configEntries := c.ConfigEntries() + + cert1 := &InlineCertificateConfigEntry{ + Kind: InlineCertificate, + Name: "cert1", + Meta: map[string]string{"foo": "bar"}, + Certificate: validCertificate, + PrivateKey: validPrivateKey, + } + + // set it + _, wm, err := configEntries.Set(cert1, nil) + require.NoError(t, err) + assert.NotNil(t, wm) + + // get it + entry, qm, err := configEntries.Get(InlineCertificate, "cert1", nil) + require.NoError(t, err) + require.NotNil(t, qm) + assert.NotEqual(t, 0, qm.RequestTime) + + readCert, ok := entry.(*InlineCertificateConfigEntry) + require.True(t, ok) + assert.Equal(t, cert1.Kind, readCert.Kind) + assert.Equal(t, cert1.Name, readCert.Name) + assert.Equal(t, cert1.Meta, readCert.Meta) + assert.Equal(t, cert1.Meta, readCert.GetMeta()) + + // update it + cert1.Meta["bar"] = "baz" + written, wm, err := configEntries.CAS(cert1, readCert.ModifyIndex, nil) + require.NoError(t, err) + require.NotNil(t, wm) + assert.NotEqual(t, 0, wm.RequestTime) + assert.True(t, written) + + // list it + entries, qm, err := configEntries.List(InlineCertificate, nil) + require.NoError(t, err) + require.NotNil(t, qm) + assert.NotEqual(t, 0, qm.RequestTime) + + require.Len(t, entries, 1) + assert.Equal(t, cert1.Kind, entries[0].GetKind()) + assert.Equal(t, cert1.Name, entries[0].GetName()) + + readCert, ok = entries[0].(*InlineCertificateConfigEntry) + require.True(t, ok) + assert.Equal(t, cert1.Certificate, readCert.Certificate) + assert.Equal(t, cert1.Meta, readCert.Meta) + + // delete it + wm, err = configEntries.Delete(InlineCertificate, cert1.Name, nil) + require.NoError(t, err) + require.NotNil(t, wm) + assert.NotEqual(t, 0, wm.RequestTime) + + // try to get it + _, _, err = configEntries.Get(InlineCertificate, cert1.Name, nil) + assert.Error(t, err) +} diff --git a/api/config_entry_routes.go b/api/config_entry_routes.go index 8561c02eaf7..2edf9b23a4c 100644 --- a/api/config_entry_routes.go +++ b/api/config_entry_routes.go @@ -49,9 +49,6 @@ func (a *TCPRouteConfigEntry) GetModifyIndex() uint64 { return a.ModifyIndex // TCPService is a service reference for a TCPRoute type TCPService struct { Name string - // Weight specifies the proportion of requests forwarded to the referenced service. - // This is computed as weight/(sum of all weights in the list of services). - Weight int // Partition is the partition the config entry is associated with. // Partitioning is a Consul Enterprise feature. @@ -195,8 +192,8 @@ type HTTPQueryMatch struct { // HTTPFilters specifies a list of filters used to modify a request // before it is routed to an upstream. type HTTPFilters struct { - Headers []HTTPHeaderFilter - URLRewrites []URLRewrite + Headers []HTTPHeaderFilter + URLRewrite *URLRewrite } // HTTPHeaderFilter specifies how HTTP headers should be modified. diff --git a/api/connect.go b/api/connect.go index a40d1e2321a..a5298d81369 100644 --- a/api/connect.go +++ b/api/connect.go @@ -1,5 +1,8 @@ package api +// HCPMetricsCollectorName is the service name for the HCP Metrics Collector +const HCPMetricsCollectorName string = "hcp-metrics-collector" + // Connect can be used to work with endpoints related to Connect, the // feature for securely connecting services within Consul. type Connect struct { diff --git a/api/go.mod b/api/go.mod index 20c8e80814b..c8f6faa19cd 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,12 +1,12 @@ module github.com/hashicorp/consul/api -go 1.18 +go 1.19 -replace github.com/hashicorp/consul/sdk => ../sdk +//replace github.com/hashicorp/consul/sdk => ../sdk require ( github.com/google/go-cmp v0.5.7 - github.com/hashicorp/consul/sdk v0.13.0 + github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-hclog v0.12.0 github.com/hashicorp/go-rootcerts v1.0.2 diff --git a/api/go.sum b/api/go.sum index 043db4cd1ef..bb2233c5d20 100644 --- a/api/go.sum +++ b/api/go.sum @@ -15,6 +15,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCy github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= +github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= diff --git a/api/operator_license.go b/api/operator_license.go index 14c548b1a35..74eed3baa4d 100644 --- a/api/operator_license.go +++ b/api/operator_license.go @@ -30,6 +30,9 @@ type License struct { // no longer be used in any capacity TerminationTime time.Time `json:"termination_time"` + // Whether the license will ignore termination + IgnoreTermination bool `json:"ignore_termination"` + // The product the license is valid for Product string `json:"product"` diff --git a/build-support/docker/Build-Go.dockerfile b/build-support/docker/Build-Go.dockerfile index cd578b451b7..543344ea3f4 100644 --- a/build-support/docker/Build-Go.dockerfile +++ b/build-support/docker/Build-Go.dockerfile @@ -1,4 +1,4 @@ -ARG GOLANG_VERSION=1.19.2 +ARG GOLANG_VERSION=1.20.1 FROM golang:${GOLANG_VERSION} WORKDIR /consul diff --git a/command/acl/policy/read/policy_read.go b/command/acl/policy/read/policy_read.go index 455f5e5f7d6..57e9397b2a4 100644 --- a/command/acl/policy/read/policy_read.go +++ b/command/acl/policy/read/policy_read.go @@ -92,6 +92,11 @@ func (c *cmd) Run(args []string) int { return 1 } + if pol == nil { + c.UI.Error(fmt.Sprintf("Error policy not found: %s", c.policyName)) + return 1 + } + formatter, err := policy.NewFormatter(c.format, c.showMeta) if err != nil { c.UI.Error(err.Error()) diff --git a/command/acl/policy/read/policy_read_test.go b/command/acl/policy/read/policy_read_test.go index af5bf1c843f..bd8d99dd837 100644 --- a/command/acl/policy/read/policy_read_test.go +++ b/command/acl/policy/read/policy_read_test.go @@ -82,6 +82,17 @@ func TestPolicyReadCommand(t *testing.T) { output = ui.OutputWriter.String() assert.Contains(t, output, fmt.Sprintf("test-policy")) assert.Contains(t, output, policy.ID) + + // Test querying non-existent policy + argsName = []string{ + "-http-addr=" + a.HTTPAddr(), + "-token=root", + "-name=test-policy-not-exist", + } + + cmd = New(ui) + code = cmd.Run(argsName) + assert.Equal(t, code, 1) } func TestPolicyReadCommand_JSON(t *testing.T) { diff --git a/command/acl/token/read/token_read.go b/command/acl/token/read/token_read.go index 79ee10f4f70..0554ccaccb5 100644 --- a/command/acl/token/read/token_read.go +++ b/command/acl/token/read/token_read.go @@ -67,17 +67,6 @@ func (c *cmd) Run(args []string) int { return 1 } - tokenAccessor := c.tokenAccessorID - if tokenAccessor == "" { - if c.tokenID == "" { - c.UI.Error("Must specify the -accessor-id parameter") - return 1 - } else { - tokenAccessor = c.tokenID - c.UI.Warn("Use the -accessor-id parameter to specify token by Accessor ID") - } - } - client, err := c.http.APIClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) @@ -87,6 +76,17 @@ func (c *cmd) Run(args []string) int { var t *api.ACLToken var expanded *api.ACLTokenExpanded if !c.self { + tokenAccessor := c.tokenAccessorID + if tokenAccessor == "" { + if c.tokenID == "" { + c.UI.Error("Must specify the -accessor-id parameter") + return 1 + } else { + tokenAccessor = c.tokenID + c.UI.Warn("Use the -accessor-id parameter to specify token by Accessor ID") + } + } + tok, err := acl.GetTokenAccessorIDFromPartial(client, tokenAccessor) if err != nil { c.UI.Error(fmt.Sprintf("Error determining token ID: %v", err)) diff --git a/command/acl/token/read/token_read_test.go b/command/acl/token/read/token_read_test.go index 505b15b02fa..7988f9772ac 100644 --- a/command/acl/token/read/token_read_test.go +++ b/command/acl/token/read/token_read_test.go @@ -116,3 +116,50 @@ func TestTokenReadCommand_JSON(t *testing.T) { err = json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput) require.NoError(t, err, "token unmarshalling error") } + +func TestTokenReadCommand_Self(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + a := agent.NewTestAgent(t, ` + primary_datacenter = "dc1" + acl { + enabled = true + tokens { + initial_management = "root" + } + }`) + + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + ui := cli.NewMockUi() + cmd := New(ui) + + // Create a token + client := a.Client() + + token, _, err := client.ACL().TokenCreate( + &api.ACLToken{Description: "test"}, + &api.WriteOptions{Token: "root"}, + ) + assert.NoError(t, err) + + args := []string{ + "-http-addr=" + a.HTTPAddr(), + "-token=" + token.SecretID, + "-self", + } + + code := cmd.Run(args) + assert.Equal(t, code, 0) + assert.Empty(t, ui.ErrorWriter.String()) + + output := ui.OutputWriter.String() + assert.Contains(t, output, fmt.Sprintf("test")) + assert.Contains(t, output, token.AccessorID) + assert.Contains(t, output, token.SecretID) +} diff --git a/command/acl/token/update/token_update.go b/command/acl/token/update/token_update.go index 4f168e927b8..519a3da8d4e 100644 --- a/command/acl/token/update/token_update.go +++ b/command/acl/token/update/token_update.go @@ -25,54 +25,67 @@ type cmd struct { http *flags.HTTPFlags help string - tokenAccessorID string - policyIDs []string - policyNames []string - roleIDs []string - roleNames []string - serviceIdents []string - nodeIdents []string - description string - mergePolicies bool - mergeRoles bool + tokenAccessorID string + policyIDs []string + appendPolicyIDs []string + policyNames []string + appendPolicyNames []string + roleIDs []string + appendRoleIDs []string + roleNames []string + appendRoleNames []string + serviceIdents []string + nodeIdents []string + appendNodeIdents []string + appendServiceIdents []string + description string + showMeta bool + format string + + // DEPRECATED mergeServiceIdents bool mergeNodeIdents bool - showMeta bool - format string - - tokenID string // DEPRECATED + mergeRoles bool + mergePolicies bool + tokenID string } func (c *cmd) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that token metadata such "+ "as the content hash and raft indices should be shown for each entry") - c.flags.BoolVar(&c.mergePolicies, "merge-policies", false, "Merge the new policies "+ - "with the existing policies") - c.flags.BoolVar(&c.mergeRoles, "merge-roles", false, "Merge the new roles "+ - "with the existing roles") - c.flags.BoolVar(&c.mergeServiceIdents, "merge-service-identities", false, "Merge the new service identities "+ - "with the existing service identities") - c.flags.BoolVar(&c.mergeNodeIdents, "merge-node-identities", false, "Merge the new node identities "+ - "with the existing node identities") c.flags.StringVar(&c.tokenAccessorID, "accessor-id", "", "The Accessor ID of the token to update. "+ "It may be specified as a unique ID prefix but will error if the prefix "+ "matches multiple token Accessor IDs") c.flags.StringVar(&c.description, "description", "", "A description of the token") c.flags.Var((*flags.AppendSliceValue)(&c.policyIDs), "policy-id", "ID of a "+ - "policy to use for this token. May be specified multiple times") + "policy to use for this token. Overwrites existing policies. May be specified multiple times") + c.flags.Var((*flags.AppendSliceValue)(&c.appendPolicyIDs), "append-policy-id", "ID of a "+ + "policy to use for this token. The token retains existing policies. May be specified multiple times") c.flags.Var((*flags.AppendSliceValue)(&c.policyNames), "policy-name", "Name of a "+ - "policy to use for this token. May be specified multiple times") + "policy to use for this token. Overwrites existing policies. May be specified multiple times") + c.flags.Var((*flags.AppendSliceValue)(&c.appendPolicyNames), "append-policy-name", "Name of a "+ + "policy to add to this token. The token retains existing policies. May be specified multiple times") c.flags.Var((*flags.AppendSliceValue)(&c.roleIDs), "role-id", "ID of a "+ - "role to use for this token. May be specified multiple times") + "role to use for this token. Overwrites existing roles. May be specified multiple times") c.flags.Var((*flags.AppendSliceValue)(&c.roleNames), "role-name", "Name of a "+ - "role to use for this token. May be specified multiple times") + "role to use for this token. Overwrites existing roles. May be specified multiple times") + c.flags.Var((*flags.AppendSliceValue)(&c.appendRoleIDs), "append-role-id", "ID of a "+ + "role to add to this token. The token retains existing roles. May be specified multiple times") + c.flags.Var((*flags.AppendSliceValue)(&c.appendRoleNames), "append-role-name", "Name of a "+ + "role to add to this token. The token retains existing roles. May be specified multiple times") c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+ "service identity to use for this token. May be specified multiple times. Format is "+ "the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...") + c.flags.Var((*flags.AppendSliceValue)(&c.appendServiceIdents), "append-service-identity", "Name of a "+ + "service identity to use for this token. This token retains existing service identities. May be specified"+ + "multiple times. Format is the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...") c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+ "node identity to use for this token. May be specified multiple times. Format is "+ "NODENAME:DATACENTER") + c.flags.Var((*flags.AppendSliceValue)(&c.appendNodeIdents), "append-node-identity", "Name of a "+ + "node identity to use for this token. This token retains existing node identities. May be "+ + "specified multiple times. Format is NODENAME:DATACENTER") c.flags.StringVar( &c.format, "format", @@ -87,8 +100,15 @@ func (c *cmd) init() { c.help = flags.Usage(help, c.flags) // Deprecations - c.flags.StringVar(&c.tokenID, "id", "", - "DEPRECATED. Use -accessor-id instead.") + c.flags.StringVar(&c.tokenID, "id", "", "DEPRECATED. Use -accessor-id instead.") + c.flags.BoolVar(&c.mergePolicies, "merge-policies", false, "DEPRECATED. "+ + "Use -append-policy-id or -append-policy-name instead.") + c.flags.BoolVar(&c.mergeRoles, "merge-roles", false, "DEPRECATED. "+ + "Use -append-role-id or -append-role-name instead.") + c.flags.BoolVar(&c.mergeServiceIdents, "merge-service-identities", false, "DEPRECATED. "+ + "Use -append-service-identity instead.") + c.flags.BoolVar(&c.mergeNodeIdents, "merge-node-identities", false, "DEPRECATED. "+ + "Use -append-node-identity instead.") } func (c *cmd) Run(args []string) int { @@ -135,19 +155,47 @@ func (c *cmd) Run(args []string) int { t.Description = c.description } + hasAppendServiceFields := len(c.appendServiceIdents) > 0 + hasServiceFields := len(c.serviceIdents) > 0 + if hasAppendServiceFields && hasServiceFields { + c.UI.Error("Cannot combine the use of service-identity flag with append-service-identity. " + + "To set or overwrite existing service identities, use -service-identity. " + + "To append to existing service identities, use -append-service-identity.") + return 1 + } + parsedServiceIdents, err := acl.ExtractServiceIdentities(c.serviceIdents) + if hasAppendServiceFields { + parsedServiceIdents, err = acl.ExtractServiceIdentities(c.appendServiceIdents) + } if err != nil { c.UI.Error(err.Error()) return 1 } + hasAppendNodeFields := len(c.appendNodeIdents) > 0 + hasNodeFields := len(c.nodeIdents) > 0 + + if hasAppendNodeFields && hasNodeFields { + c.UI.Error("Cannot combine the use of node-identity flag with append-node-identity. " + + "To set or overwrite existing node identities, use -node-identity. " + + "To append to existing node identities, use -append-node-identity.") + return 1 + } + parsedNodeIdents, err := acl.ExtractNodeIdentities(c.nodeIdents) + if hasAppendNodeFields { + parsedNodeIdents, err = acl.ExtractNodeIdentities(c.appendNodeIdents) + } if err != nil { c.UI.Error(err.Error()) return 1 } if c.mergePolicies { + c.UI.Warn("merge-policies is deprecated and will be removed in a future Consul version. " + + "Use `append-policy-name` or `append-policy-id` instead.") + for _, policyName := range c.policyNames { found := false for _, link := range t.Policies { @@ -184,15 +232,33 @@ func (c *cmd) Run(args []string) int { } } } else { - t.Policies = nil - for _, policyName := range c.policyNames { + hasAddPolicyFields := len(c.appendPolicyNames) > 0 || len(c.appendPolicyIDs) > 0 + hasPolicyFields := len(c.policyIDs) > 0 || len(c.policyNames) > 0 + + if hasPolicyFields && hasAddPolicyFields { + c.UI.Error("Cannot combine the use of policy-id/policy-name flags with append- variants. " + + "To set or overwrite existing policies, use -policy-id or -policy-name. " + + "To append to existing policies, use -append-policy-id or -append-policy-name.") + return 1 + } + + policyIDs := c.appendPolicyIDs + policyNames := c.appendPolicyNames + + if hasPolicyFields { + policyIDs = c.policyIDs + policyNames = c.policyNames + t.Policies = nil + } + + for _, policyName := range policyNames { // We could resolve names to IDs here but there isn't any reason why its would be better // than allowing the agent to do it. t.Policies = append(t.Policies, &api.ACLTokenPolicyLink{Name: policyName}) } - for _, policyID := range c.policyIDs { + for _, policyID := range policyIDs { policyID, err := acl.GetPolicyIDFromPartial(client, policyID) if err != nil { c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err)) @@ -203,6 +269,9 @@ func (c *cmd) Run(args []string) int { } if c.mergeRoles { + c.UI.Warn("merge-roles is deprecated and will be removed in a future Consul version. " + + "Use `append-role-name` or `append-role-id` instead.") + for _, roleName := range c.roleNames { found := false for _, link := range t.Roles { @@ -239,15 +308,32 @@ func (c *cmd) Run(args []string) int { } } } else { - t.Roles = nil + hasAddRoleFields := len(c.appendRoleNames) > 0 || len(c.appendRoleIDs) > 0 + hasRoleFields := len(c.roleIDs) > 0 || len(c.roleNames) > 0 - for _, roleName := range c.roleNames { + if hasRoleFields && hasAddRoleFields { + c.UI.Error("Cannot combine the use of role-id/role-name flags with append- variants. " + + "To set or overwrite existing roles, use -role-id or -role-name. " + + "To append to existing roles, use -append-role-id or -append-role-name.") + return 1 + } + + roleNames := c.appendRoleNames + roleIDs := c.appendRoleIDs + + if hasRoleFields { + roleNames = c.roleNames + roleIDs = c.roleIDs + t.Roles = nil + } + + for _, roleName := range roleNames { // We could resolve names to IDs here but there isn't any reason why its would be better // than allowing the agent to do it. t.Roles = append(t.Roles, &api.ACLTokenRoleLink{Name: roleName}) } - for _, roleID := range c.roleIDs { + for _, roleID := range roleIDs { roleID, err := acl.GetRoleIDFromPartial(client, roleID) if err != nil { c.UI.Error(fmt.Sprintf("Error resolving role ID %s: %v", roleID, err)) @@ -257,7 +343,7 @@ func (c *cmd) Run(args []string) int { } } - if c.mergeServiceIdents { + if c.mergeServiceIdents || hasAppendServiceFields { for _, svcid := range parsedServiceIdents { found := -1 for i, link := range t.ServiceIdentities { @@ -277,7 +363,7 @@ func (c *cmd) Run(args []string) int { t.ServiceIdentities = parsedServiceIdents } - if c.mergeNodeIdents { + if c.mergeNodeIdents || hasAppendNodeFields { for _, nodeid := range parsedNodeIdents { found := false for _, link := range t.NodeIdentities { diff --git a/command/acl/token/update/token_update_test.go b/command/acl/token/update/token_update_test.go index 541d1e22539..011e916f4fb 100644 --- a/command/acl/token/update/token_update_test.go +++ b/command/acl/token/update/token_update_test.go @@ -22,6 +22,13 @@ func TestTokenUpdateCommand_noTabs(t *testing.T) { } } +func create_token(t *testing.T, client *api.Client, aclToken *api.ACLToken, writeOptions *api.WriteOptions) *api.ACLToken { + token, _, err := client.ACL().TokenCreate(aclToken, writeOptions) + require.NoError(t, err) + + return token +} + func TestTokenUpdateCommand(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -50,13 +57,6 @@ func TestTokenUpdateCommand(t *testing.T) { ) require.NoError(t, err) - // create a token - token, _, err := client.ACL().TokenCreate( - &api.ACLToken{Description: "test"}, - &api.WriteOptions{Token: "root"}, - ) - require.NoError(t, err) - run := func(t *testing.T, args []string) *api.ACLToken { ui := cli.NewMockUi() cmd := New(ui) @@ -72,7 +72,9 @@ func TestTokenUpdateCommand(t *testing.T) { // update with node identity t.Run("node-identity", func(t *testing.T) { - token := run(t, []string{ + token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"}) + + responseToken := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-accessor-id=" + token.AccessorID, "-token=root", @@ -80,13 +82,19 @@ func TestTokenUpdateCommand(t *testing.T) { "-description=test token", }) - require.Len(t, token.NodeIdentities, 1) - require.Equal(t, "foo", token.NodeIdentities[0].NodeName) - require.Equal(t, "bar", token.NodeIdentities[0].Datacenter) + require.Len(t, responseToken.NodeIdentities, 1) + require.Equal(t, "foo", responseToken.NodeIdentities[0].NodeName) + require.Equal(t, "bar", responseToken.NodeIdentities[0].Datacenter) }) t.Run("node-identity-merge", func(t *testing.T) { - token := run(t, []string{ + token := create_token(t, + client, + &api.ACLToken{Description: "test", NodeIdentities: []*api.ACLNodeIdentity{{NodeName: "foo", Datacenter: "bar"}}}, + &api.WriteOptions{Token: "root"}, + ) + + responseToken := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-accessor-id=" + token.AccessorID, "-token=root", @@ -95,7 +103,7 @@ func TestTokenUpdateCommand(t *testing.T) { "-merge-node-identities", }) - require.Len(t, token.NodeIdentities, 2) + require.Len(t, responseToken.NodeIdentities, 2) expected := []*api.ACLNodeIdentity{ { NodeName: "foo", @@ -106,12 +114,14 @@ func TestTokenUpdateCommand(t *testing.T) { Datacenter: "baz", }, } - require.ElementsMatch(t, expected, token.NodeIdentities) + require.ElementsMatch(t, expected, responseToken.NodeIdentities) }) // update with policy by name t.Run("policy-name", func(t *testing.T) { - token := run(t, []string{ + token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"}) + + responseToken := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-accessor-id=" + token.AccessorID, "-token=root", @@ -119,12 +129,14 @@ func TestTokenUpdateCommand(t *testing.T) { "-description=test token", }) - require.Len(t, token.Policies, 1) + require.Len(t, responseToken.Policies, 1) }) // update with policy by id t.Run("policy-id", func(t *testing.T) { - token := run(t, []string{ + token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"}) + + responseToken := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-accessor-id=" + token.AccessorID, "-token=root", @@ -132,23 +144,41 @@ func TestTokenUpdateCommand(t *testing.T) { "-description=test token", }) - require.Len(t, token.Policies, 1) + require.Len(t, responseToken.Policies, 1) + }) + + // update with service-identity + t.Run("service-identity", func(t *testing.T) { + token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"}) + + responseToken := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-accessor-id=" + token.AccessorID, + "-token=root", + "-service-identity=service:datapalace", + "-description=test token", + }) + + require.Len(t, responseToken.ServiceIdentities, 1) + require.Equal(t, "service", responseToken.ServiceIdentities[0].ServiceName) }) // update with no description shouldn't delete the current description t.Run("merge-description", func(t *testing.T) { - token := run(t, []string{ + token := create_token(t, client, &api.ACLToken{Description: "test token"}, &api.WriteOptions{Token: "root"}) + + responseToken := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-accessor-id=" + token.AccessorID, "-token=root", "-policy-name=" + policy.Name, }) - require.Equal(t, "test token", token.Description) + require.Equal(t, "test token", responseToken.Description) }) } -func TestTokenUpdateCommand_JSON(t *testing.T) { +func TestTokenUpdateCommandWithAppend(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -167,8 +197,6 @@ func TestTokenUpdateCommand_JSON(t *testing.T) { defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") - ui := cli.NewMockUi() - // Create a policy client := a.Client() @@ -178,13 +206,142 @@ func TestTokenUpdateCommand_JSON(t *testing.T) { ) require.NoError(t, err) - // create a token - token, _, err := client.ACL().TokenCreate( - &api.ACLToken{Description: "test"}, + //secondary policy + secondPolicy, _, policyErr := client.ACL().PolicyCreate( + &api.ACLPolicy{Name: "secondary-policy"}, + &api.WriteOptions{Token: "root"}, + ) + require.NoError(t, policyErr) + + run := func(t *testing.T, args []string) *api.ACLToken { + ui := cli.NewMockUi() + cmd := New(ui) + + code := cmd.Run(append(args, "-format=json")) + require.Equal(t, 0, code) + require.Empty(t, ui.ErrorWriter.String()) + + var token api.ACLToken + require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &token)) + return &token + } + + // update with append-policy-name + t.Run("append-policy-name", func(t *testing.T) { + token := create_token(t, client, + &api.ACLToken{Description: "test", Policies: []*api.ACLTokenPolicyLink{{Name: policy.Name}}}, + &api.WriteOptions{Token: "root"}, + ) + + responseToken := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-accessor-id=" + token.AccessorID, + "-token=root", + "-append-policy-name=" + secondPolicy.Name, + "-description=test token", + }) + + require.Len(t, responseToken.Policies, 2) + }) + + // update with append-policy-id + t.Run("append-policy-id", func(t *testing.T) { + token := create_token(t, client, + &api.ACLToken{Description: "test", Policies: []*api.ACLTokenPolicyLink{{Name: policy.Name}}}, + &api.WriteOptions{Token: "root"}, + ) + + responseToken := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-accessor-id=" + token.AccessorID, + "-token=root", + "-append-policy-id=" + secondPolicy.ID, + "-description=test token", + }) + + require.Len(t, responseToken.Policies, 2) + }) + + // update with append-node-identity + t.Run("append-node-identity", func(t *testing.T) { + token := create_token(t, client, + &api.ACLToken{ + Description: "test", + Policies: []*api.ACLTokenPolicyLink{{Name: policy.Name}}, + NodeIdentities: []*api.ACLNodeIdentity{{NodeName: "namenode", Datacenter: "somewhere"}}, + }, + &api.WriteOptions{Token: "root"}, + ) + + responseToken := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-accessor-id=" + token.AccessorID, + "-token=root", + "-append-node-identity=third:node", + "-description=test token", + }) + + require.Len(t, responseToken.NodeIdentities, 2) + require.Equal(t, "third", responseToken.NodeIdentities[1].NodeName) + require.Equal(t, "node", responseToken.NodeIdentities[1].Datacenter) + }) + + // update with append-service-identity + t.Run("append-service-identity", func(t *testing.T) { + token := create_token(t, client, + &api.ACLToken{ + Description: "test", + Policies: []*api.ACLTokenPolicyLink{{Name: policy.Name}}, + ServiceIdentities: []*api.ACLServiceIdentity{{ServiceName: "service"}}, + }, + &api.WriteOptions{Token: "root"}, + ) + + responseToken := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-accessor-id=" + token.AccessorID, + "-token=root", + "-append-service-identity=web", + "-description=test token", + }) + + require.Len(t, responseToken.ServiceIdentities, 2) + require.Equal(t, "web", responseToken.ServiceIdentities[1].ServiceName) + }) +} + +func TestTokenUpdateCommand_JSON(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + a := agent.NewTestAgent(t, ` + primary_datacenter = "dc1" + acl { + enabled = true + tokens { + initial_management = "root" + } + }`) + + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + ui := cli.NewMockUi() + + // Create a policy + client := a.Client() + + policy, _, err := client.ACL().PolicyCreate( + &api.ACLPolicy{Name: "test-policy"}, &api.WriteOptions{Token: "root"}, ) require.NoError(t, err) + token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"}) + t.Run("update with policy by name", func(t *testing.T) { cmd := New(ui) args := []string{ diff --git a/command/connect/envoy/bootstrap_config.go b/command/connect/envoy/bootstrap_config.go index 23427ad0af6..e88d83e6a0d 100644 --- a/command/connect/envoy/bootstrap_config.go +++ b/command/connect/envoy/bootstrap_config.go @@ -7,9 +7,11 @@ import ( "net" "net/url" "os" + "path" "strings" "text/template" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/api" ) @@ -49,6 +51,11 @@ type BootstrapConfig struct { // stats_config.stats_tags can be made by overriding envoy_stats_config_json. StatsTags []string `mapstructure:"envoy_stats_tags"` + // HCPMetricsBindSocketDir is a string that configures the directory for a + // unix socket where Envoy will forward metrics. These metrics get pushed to + // the HCP Metrics collector to show service mesh metrics on HCP. + HCPMetricsBindSocketDir string `mapstructure:"envoy_hcp_metrics_bind_socket_dir"` + // PrometheusBindAddr configures an : on which the Envoy will listen // and expose a single /metrics HTTP endpoint for Prometheus to scrape. It // does this by proxying that URL to the internal admin server's prometheus @@ -238,6 +245,11 @@ func (c *BootstrapConfig) ConfigureArgs(args *BootstrapTplArgs, omitDeprecatedTa args.StatsFlushInterval = c.StatsFlushInterval } + // Setup HCP Metrics if needed. This MUST happen after the Static*JSON is set above + if c.HCPMetricsBindSocketDir != "" { + appendHCPMetricsConfig(args, c.HCPMetricsBindSocketDir) + } + return nil } @@ -271,7 +283,7 @@ func (c *BootstrapConfig) generateStatsSinks(args *BootstrapTplArgs) error { } if len(stats_sinks) > 0 { - args.StatsSinksJSON = "[\n" + strings.Join(stats_sinks, ",\n") + "\n]" + args.StatsSinksJSON = strings.Join(stats_sinks, ",\n") } return nil } @@ -796,6 +808,58 @@ func (c *BootstrapConfig) generateListenerConfig(args *BootstrapTplArgs, bindAdd return nil } +// appendHCPMetricsConfig generates config to enable a socket at path: /_.sock +// or /.sock, if namespace is empty. +func appendHCPMetricsConfig(args *BootstrapTplArgs, hcpMetricsBindSocketDir string) { + // Normalize namespace to "default". This ensures we match the namespace behaviour in proxycfg package, + // where a dynamic listener will be created at the same socket path via xDS. + sock := fmt.Sprintf("%s_%s.sock", acl.NamespaceOrDefault(args.Namespace), args.ProxyID) + path := path.Join(hcpMetricsBindSocketDir, sock) + + if args.StatsSinksJSON != "" { + args.StatsSinksJSON += ",\n" + } + args.StatsSinksJSON += `{ + "name": "envoy.stat_sinks.metrics_service", + "typed_config": { + "@type": "type.googleapis.com/envoy.config.metrics.v3.MetricsServiceConfig", + "transport_api_version": "V3", + "grpc_service": { + "envoy_grpc": { + "cluster_name": "hcp_metrics_collector" + } + } + } + }` + + if args.StaticClustersJSON != "" { + args.StaticClustersJSON += ",\n" + } + args.StaticClustersJSON += fmt.Sprintf(`{ + "name": "hcp_metrics_collector", + "type": "STATIC", + "http2_protocol_options": {}, + "loadAssignment": { + "clusterName": "hcp_metrics_collector", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "pipe": { + "path": "%s" + } + } + } + } + ] + } + ] + } + }`, path) +} + func containsSelfAdminCluster(clustersJSON string) (bool, error) { clusterNames := []struct { Name string diff --git a/command/connect/envoy/bootstrap_config_test.go b/command/connect/envoy/bootstrap_config_test.go index 9e8038ae036..e5d9548e655 100644 --- a/command/connect/envoy/bootstrap_config_test.go +++ b/command/connect/envoy/bootstrap_config_test.go @@ -513,6 +513,56 @@ const ( } ] }` + + expectedStatsdSink = `{ + "name": "envoy.stat_sinks.statsd", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.metrics.v3.StatsdSink", + "address": { + "socket_address": { + "address": "127.0.0.1", + "port_value": 9125 + } + } + } +}` + + expectedHCPMetricsStatsSink = `{ + "name": "envoy.stat_sinks.metrics_service", + "typed_config": { + "@type": "type.googleapis.com/envoy.config.metrics.v3.MetricsServiceConfig", + "transport_api_version": "V3", + "grpc_service": { + "envoy_grpc": { + "cluster_name": "hcp_metrics_collector" + } + } + } + }` + + expectedHCPMetricsCluster = `{ + "name": "hcp_metrics_collector", + "type": "STATIC", + "http2_protocol_options": {}, + "loadAssignment": { + "clusterName": "hcp_metrics_collector", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "pipe": { + "path": "/tmp/consul/hcp-metrics/default_web-sidecar-proxy.sock" + } + } + } + } + ] + } + ] + } + }` ) func TestBootstrapConfig_ConfigureArgs(t *testing.T) { @@ -557,33 +607,71 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { }, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.custom_exciting_sink", "config": { "foo": "bar" } - }]`, + }`, }, }, { - name: "simple-statsd-sink", + name: "hcp-metrics-sink", + baseArgs: BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + }, input: BootstrapConfig{ - StatsdURL: "udp://127.0.0.1:9125", + HCPMetricsBindSocketDir: "/tmp/consul/hcp-metrics", }, wantArgs: BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ - "name": "envoy.stat_sinks.statsd", - "typedConfig": { - "@type": "type.googleapis.com/envoy.config.metrics.v3.StatsdSink", - "address": { - "socket_address": { - "address": "127.0.0.1", - "port_value": 9125 + StatsSinksJSON: `{ + "name": "envoy.stat_sinks.metrics_service", + "typed_config": { + "@type": "type.googleapis.com/envoy.config.metrics.v3.MetricsServiceConfig", + "transport_api_version": "V3", + "grpc_service": { + "envoy_grpc": { + "cluster_name": "hcp_metrics_collector" + } + } + } + }`, + StaticClustersJSON: `{ + "name": "hcp_metrics_collector", + "type": "STATIC", + "http2_protocol_options": {}, + "loadAssignment": { + "clusterName": "hcp_metrics_collector", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "pipe": { + "path": "/tmp/consul/hcp-metrics/default_web-sidecar-proxy.sock" + } + } + } } + ] } + ] } - }]`, + }`, + }, + wantErr: false, + }, + { + name: "simple-statsd-sink", + input: BootstrapConfig{ + StatsdURL: "udp://127.0.0.1:9125", + }, + wantArgs: BootstrapTplArgs{ + StatsConfigJSON: defaultStatsConfigJSON, + StatsSinksJSON: expectedStatsdSink, }, wantErr: false, }, @@ -600,7 +688,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { }, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.stat_sinks.statsd", "typedConfig": { "@type": "type.googleapis.com/envoy.config.metrics.v3.StatsdSink", @@ -617,7 +705,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { "config": { "foo": "bar" } - }]`, + }`, }, wantErr: false, }, @@ -629,7 +717,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { env: []string{"MY_STATSD_URL=udp://127.0.0.1:9125"}, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.stat_sinks.statsd", "typedConfig": { "@type": "type.googleapis.com/envoy.config.metrics.v3.StatsdSink", @@ -640,7 +728,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { } } } - }]`, + }`, }, wantErr: false, }, @@ -652,7 +740,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { env: []string{"HOST_IP=127.0.0.1"}, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.stat_sinks.statsd", "typedConfig": { "@type": "type.googleapis.com/envoy.config.metrics.v3.StatsdSink", @@ -663,7 +751,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { } } } - }]`, + }`, }, wantErr: false, }, @@ -685,7 +773,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { }, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.stat_sinks.dog_statsd", "typedConfig": { "@type": "type.googleapis.com/envoy.config.metrics.v3.DogStatsdSink", @@ -696,7 +784,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { } } } - }]`, + }`, }, wantErr: false, }, @@ -707,7 +795,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { }, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.stat_sinks.dog_statsd", "typedConfig": { "@type": "type.googleapis.com/envoy.config.metrics.v3.DogStatsdSink", @@ -717,7 +805,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { } } } - }]`, + }`, }, wantErr: false, }, @@ -730,7 +818,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { env: []string{"MY_STATSD_URL=udp://127.0.0.1:9125"}, wantArgs: BootstrapTplArgs{ StatsConfigJSON: defaultStatsConfigJSON, - StatsSinksJSON: `[{ + StatsSinksJSON: `{ "name": "envoy.stat_sinks.dog_statsd", "typedConfig": { "@type": "type.googleapis.com/envoy.config.metrics.v3.DogStatsdSink", @@ -741,7 +829,7 @@ func TestBootstrapConfig_ConfigureArgs(t *testing.T) { } } } - }]`, + }`, }, wantErr: false, }, @@ -1539,3 +1627,65 @@ func TestConsulTagSpecifiers(t *testing.T) { }) } } + +func TestAppendHCPMetrics(t *testing.T) { + tests := map[string]struct { + inputArgs *BootstrapTplArgs + bindSocketDir string + wantArgs *BootstrapTplArgs + }{ + "dir-without-trailing-slash": { + inputArgs: &BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + }, + bindSocketDir: "/tmp/consul/hcp-metrics", + wantArgs: &BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + StatsSinksJSON: expectedHCPMetricsStatsSink, + StaticClustersJSON: expectedHCPMetricsCluster, + }, + }, + "dir-with-trailing-slash": { + inputArgs: &BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + }, + bindSocketDir: "/tmp/consul/hcp-metrics", + wantArgs: &BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + StatsSinksJSON: expectedHCPMetricsStatsSink, + StaticClustersJSON: expectedHCPMetricsCluster, + }, + }, + "append-clusters-and-stats-sink": { + inputArgs: &BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + StatsSinksJSON: expectedStatsdSink, + StaticClustersJSON: expectedSelfAdminCluster, + }, + bindSocketDir: "/tmp/consul/hcp-metrics", + wantArgs: &BootstrapTplArgs{ + ProxyID: "web-sidecar-proxy", + StatsSinksJSON: expectedStatsdSink + ",\n" + expectedHCPMetricsStatsSink, + StaticClustersJSON: expectedSelfAdminCluster + ",\n" + expectedHCPMetricsCluster, + }, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + appendHCPMetricsConfig(tt.inputArgs, tt.bindSocketDir) + + // Some of our JSON strings are comma separated objects to be + // insertedinto an array which is not valid JSON on it's own so wrap + // them all in an array. For simple values this is still valid JSON + // too. + wantStatsSink := "[" + tt.wantArgs.StatsSinksJSON + "]" + gotStatsSink := "[" + tt.inputArgs.StatsSinksJSON + "]" + require.JSONEq(t, wantStatsSink, gotStatsSink, "field StatsSinksJSON should be equivalent JSON") + + wantClusters := "[" + tt.wantArgs.StaticClustersJSON + "]" + gotClusters := "[" + tt.inputArgs.StaticClustersJSON + "]" + require.JSONEq(t, wantClusters, gotClusters, "field StaticClustersJSON should be equivalent JSON") + }) + } +} diff --git a/command/connect/envoy/bootstrap_tpl.go b/command/connect/envoy/bootstrap_tpl.go index 7ed75304bca..9e264fe9351 100644 --- a/command/connect/envoy/bootstrap_tpl.go +++ b/command/connect/envoy/bootstrap_tpl.go @@ -262,7 +262,9 @@ const bootstrapTemplate = `{ {{- end }} }, {{- if .StatsSinksJSON }} - "stats_sinks": {{ .StatsSinksJSON }}, + "stats_sinks": [ + {{ .StatsSinksJSON }} + ], {{- end }} {{- if .StatsConfigJSON }} "stats_config": {{ .StatsConfigJSON }}, diff --git a/command/connect/envoy/envoy.go b/command/connect/envoy/envoy.go index c265c0ba9cd..0864ecc1997 100644 --- a/command/connect/envoy/envoy.go +++ b/command/connect/envoy/envoy.go @@ -11,7 +11,6 @@ import ( "strings" "time" - "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-version" "github.com/mitchellh/cli" @@ -22,6 +21,7 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/xds" "github.com/hashicorp/consul/agent/xds/accesslogs" + "github.com/hashicorp/consul/api" proxyCmd "github.com/hashicorp/consul/command/connect/proxy" "github.com/hashicorp/consul/command/flags" "github.com/hashicorp/consul/envoyextensions/xdscommon" @@ -503,15 +503,15 @@ func (c *cmd) run(args []string) int { return 1 } - ok, err := checkEnvoyVersionCompatibility(v, xdscommon.UnsupportedEnvoyVersions) + ec, err := checkEnvoyVersionCompatibility(v, xdscommon.UnsupportedEnvoyVersions) if err != nil { c.UI.Warn("There was an error checking the compatibility of the envoy version: " + err.Error()) - } else if !ok { + } else if !ec.isCompatible { c.UI.Error(fmt.Sprintf("Envoy version %s is not supported. If there is a reason you need to use "+ "this version of envoy use the ignore-envoy-compatibility flag. Using an unsupported version of Envoy "+ "is not recommended and your experience may vary. For more information on compatibility "+ - "see https://developer.hashicorp.com/consul/docs/connect/proxies/envoy#envoy-and-consul-client-agent", v)) + "see https://developer.hashicorp.com/consul/docs/connect/proxies/envoy#envoy-and-consul-client-agent", ec.versionIncompatible)) return 1 } } @@ -810,8 +810,7 @@ func (c *cmd) xdsAddress() (GRPC, error) { port, protocol, err := c.lookupXDSPort() if err != nil { if strings.Contains(err.Error(), "Permission denied") { - // Token did not have agent:read. Log and proceed with defaults. - c.UI.Info(fmt.Sprintf("Could not query /v1/agent/self for xDS ports: %s", err)) + // Token did not have agent:read. Suppress and proceed with defaults. } else { // If not a permission denied error, gRPC is explicitly disabled // or something went fatally wrong. @@ -822,7 +821,7 @@ func (c *cmd) xdsAddress() (GRPC, error) { // This is the dev mode default and recommended production setting if // enabled. port = 8502 - c.UI.Info("-grpc-addr not provided and unable to discover a gRPC address for xDS. Defaulting to localhost:8502") + c.UI.Warn("-grpc-addr not provided and unable to discover a gRPC address for xDS. Defaulting to localhost:8502") } addr = fmt.Sprintf("%vlocalhost:%v", protocol, port) } @@ -887,9 +886,12 @@ func (c *cmd) lookupXDSPort() (int, string, error) { var resp response if err := mapstructure.Decode(self, &resp); err == nil { - if resp.XDS.Ports.TLS < 0 && resp.XDS.Ports.Plaintext < 0 { - return 0, "", fmt.Errorf("agent has grpc disabled") - } + // When we get rid of the 1.10 compatibility code below we can uncomment + // this check: + // + // if resp.XDS.Ports.TLS <= 0 && resp.XDS.Ports.Plaintext <= 0 { + // return 0, "", fmt.Errorf("agent has grpc disabled") + // } if resp.XDS.Ports.TLS > 0 { return resp.XDS.Ports.TLS, "https://", nil } @@ -898,9 +900,12 @@ func (c *cmd) lookupXDSPort() (int, string, error) { } } - // If above TLS and Plaintext ports are both 0, fallback to - // old API for the case where a new consul CLI is being used - // with an older API version. + // If above TLS and Plaintext ports are both 0, it could mean + // gRPC is disabled on the agent or we are using an older API. + // In either case, fallback to reading from the DebugConfig. + // + // Next major version we should get rid of this below code. + // It exists for compatibility reasons for 1.10 and below. cfg, ok := self["DebugConfig"] if !ok { return 0, "", fmt.Errorf("unexpected agent response: no debug config") @@ -914,6 +919,12 @@ func (c *cmd) lookupXDSPort() (int, string, error) { return 0, "", fmt.Errorf("invalid grpc port in agent response") } + // This works for both <1.10 and later but we should prefer + // reading from resp.XDS instead. + if portN < 0 { + return 0, "", fmt.Errorf("agent has grpc disabled") + } + return int(portN), "", nil } @@ -976,34 +987,73 @@ Usage: consul connect envoy [options] [-- pass-through options] ` ) -func checkEnvoyVersionCompatibility(envoyVersion string, unsupportedList []string) (bool, error) { - // Now compare the versions to the list of supported versions +type envoyCompat struct { + isCompatible bool + versionIncompatible string +} + +func checkEnvoyVersionCompatibility(envoyVersion string, unsupportedList []string) (envoyCompat, error) { v, err := version.NewVersion(envoyVersion) if err != nil { - return false, err + return envoyCompat{}, err } var cs strings.Builder - // Add one to the max minor version so that we accept all patches + // If there is a list of unsupported versions, build the constraint string, + // this will detect exactly unsupported versions + if len(unsupportedList) > 0 { + for i, s := range unsupportedList { + if i == 0 { + cs.WriteString(fmt.Sprintf("!= %s", s)) + } else { + cs.WriteString(fmt.Sprintf(", != %s", s)) + } + } + + constraints, err := version.NewConstraint(cs.String()) + if err != nil { + return envoyCompat{}, err + } + + if c := constraints.Check(v); !c { + return envoyCompat{ + isCompatible: c, + versionIncompatible: envoyVersion, + }, nil + } + } + + // Next build the constraint string using the bounds, make sure that we are less than but not equal to + // maxSupported since we will add 1. Need to add one to the max minor version so that we accept all patches splitS := strings.Split(xdscommon.GetMaxEnvoyMinorVersion(), ".") minor, err := strconv.Atoi(splitS[1]) if err != nil { - return false, err + return envoyCompat{}, err } minor++ maxSupported := fmt.Sprintf("%s.%d", splitS[0], minor) - // Build the constraint string, make sure that we are less than but not equal to maxSupported since we added 1 + cs.Reset() cs.WriteString(fmt.Sprintf(">= %s, < %s", xdscommon.GetMinEnvoyMinorVersion(), maxSupported)) - for _, s := range unsupportedList { - cs.WriteString(fmt.Sprintf(", != %s", s)) - } - constraints, err := version.NewConstraint(cs.String()) if err != nil { - return false, err + return envoyCompat{}, err + } + + if c := constraints.Check(v); !c { + return envoyCompat{ + isCompatible: c, + versionIncompatible: replacePatchVersionWithX(envoyVersion), + }, nil } - return constraints.Check(v), nil + return envoyCompat{isCompatible: true}, nil +} + +func replacePatchVersionWithX(version string) string { + // Strip off the patch and append x to convey that the constraint is on the minor version and not the patch + // itself + a := strings.Split(version, ".") + return fmt.Sprintf("%s.%s.x", a[0], a[1]) } diff --git a/command/connect/envoy/envoy_test.go b/command/connect/envoy/envoy_test.go index 223eb2e130c..8366d65974e 100644 --- a/command/connect/envoy/envoy_test.go +++ b/command/connect/envoy/envoy_test.go @@ -13,7 +13,6 @@ import ( "strings" "testing" - "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,6 +21,7 @@ import ( "github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/agent/xds" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/consul/sdk/testutil" ) @@ -123,6 +123,7 @@ type generateConfigTestCase struct { NamespacesEnabled bool XDSPorts agent.GRPCPorts // used to mock an agent's configured gRPC ports. Plaintext defaults to 8502 and TLS defaults to 8503. AgentSelf110 bool // fake the agent API from versions v1.10 and earlier + GRPCDisabled bool WantArgs BootstrapTplArgs WantErr string WantWarn string @@ -146,13 +147,10 @@ func TestGenerateConfig(t *testing.T) { WantErr: "'-node-name' requires '-proxy-id'", }, { - Name: "gRPC disabled", - Flags: []string{"-proxy-id", "test-proxy"}, - XDSPorts: agent.GRPCPorts{ - Plaintext: -1, - TLS: -1, - }, - WantErr: "agent has grpc disabled", + Name: "gRPC disabled", + Flags: []string{"-proxy-id", "test-proxy"}, + GRPCDisabled: true, + WantErr: "agent has grpc disabled", }, { Name: "defaults", @@ -197,6 +195,29 @@ func TestGenerateConfig(t *testing.T) { PrometheusScrapePath: "/metrics", }, }, + { + Name: "hcp-metrics", + Flags: []string{"-proxy-id", "test-proxy"}, + ProxyConfig: map[string]interface{}{ + "envoy_hcp_metrics_bind_socket_dir": "/tmp/consul/hcp-metrics", + }, + WantArgs: BootstrapTplArgs{ + ProxyCluster: "test-proxy", + ProxyID: "test-proxy", + // We don't know this til after the lookup so it will be empty in the + // initial args call we are testing here. + ProxySourceService: "", + GRPC: GRPC{ + AgentAddress: "127.0.0.1", + AgentPort: "8502", + }, + AdminAccessLogPath: "/dev/null", + AdminBindAddress: "127.0.0.1", + AdminBindPort: "19000", + LocalAgentClusterName: xds.LocalAgentClusterName, + PrometheusScrapePath: "/metrics", + }, + }, { Name: "prometheus-metrics", Flags: []string{"-proxy-id", "test-proxy", @@ -1387,7 +1408,7 @@ func testMockAgent(tc generateConfigTestCase) http.HandlerFunc { case strings.Contains(r.URL.Path, "/agent/service"): testMockAgentProxyConfig(tc.ProxyConfig, tc.NamespacesEnabled)(w, r) case strings.Contains(r.URL.Path, "/agent/self"): - testMockAgentSelf(tc.XDSPorts, tc.AgentSelf110)(w, r) + testMockAgentSelf(tc.XDSPorts, tc.AgentSelf110, tc.GRPCDisabled)(w, r) case strings.Contains(r.URL.Path, "/catalog/node-services"): testMockCatalogNodeServiceList()(w, r) case strings.Contains(r.URL.Path, "/config/proxy-defaults/global"): @@ -1658,7 +1679,11 @@ func TestEnvoyCommand_canBindInternal(t *testing.T) { // testMockAgentSelf returns an empty /v1/agent/self response except GRPC // port is filled in to match the given wantXDSPort argument. -func testMockAgentSelf(wantXDSPorts agent.GRPCPorts, agentSelf110 bool) http.HandlerFunc { +func testMockAgentSelf( + wantXDSPorts agent.GRPCPorts, + agentSelf110 bool, + grpcDisabled bool, +) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { resp := agent.Self{ Config: map[string]interface{}{ @@ -1670,6 +1695,12 @@ func testMockAgentSelf(wantXDSPorts agent.GRPCPorts, agentSelf110 bool) http.Han resp.DebugConfig = map[string]interface{}{ "GRPCPort": wantXDSPorts.Plaintext, } + } else if grpcDisabled { + resp.DebugConfig = map[string]interface{}{ + "GRPCPort": -1, + } + // the real agent does not populate XDS if grpc or + // grpc-tls ports are < 0 } else { resp.XDS = &agent.XDSSelf{ // The deprecated Port field should default to TLS if it's available. @@ -1696,50 +1727,65 @@ func TestCheckEnvoyVersionCompatibility(t *testing.T) { name string envoyVersion string unsupportedList []string - expectedSupport bool + expectedCompat envoyCompat isErrorExpected bool }{ { name: "supported-using-proxy-support-defined", envoyVersion: xdscommon.EnvoyVersions[1], unsupportedList: xdscommon.UnsupportedEnvoyVersions, - expectedSupport: true, + expectedCompat: envoyCompat{ + isCompatible: true, + }, }, { name: "supported-at-max", envoyVersion: xdscommon.GetMaxEnvoyMinorVersion(), unsupportedList: xdscommon.UnsupportedEnvoyVersions, - expectedSupport: true, + expectedCompat: envoyCompat{ + isCompatible: true, + }, }, { name: "supported-patch-higher", envoyVersion: addNPatchVersion(xdscommon.EnvoyVersions[0], 1), unsupportedList: xdscommon.UnsupportedEnvoyVersions, - expectedSupport: true, + expectedCompat: envoyCompat{ + isCompatible: true, + }, }, { name: "not-supported-minor-higher", envoyVersion: addNMinorVersion(xdscommon.EnvoyVersions[0], 1), unsupportedList: xdscommon.UnsupportedEnvoyVersions, - expectedSupport: false, + expectedCompat: envoyCompat{ + isCompatible: false, + versionIncompatible: replacePatchVersionWithX(addNMinorVersion(xdscommon.EnvoyVersions[0], 1)), + }, }, { name: "not-supported-minor-lower", envoyVersion: addNMinorVersion(xdscommon.EnvoyVersions[len(xdscommon.EnvoyVersions)-1], -1), unsupportedList: xdscommon.UnsupportedEnvoyVersions, - expectedSupport: false, + expectedCompat: envoyCompat{ + isCompatible: false, + versionIncompatible: replacePatchVersionWithX(addNMinorVersion(xdscommon.EnvoyVersions[len(xdscommon.EnvoyVersions)-1], -1)), + }, }, { name: "not-supported-explicitly-unsupported-version", envoyVersion: addNPatchVersion(xdscommon.EnvoyVersions[0], 1), unsupportedList: []string{"1.23.1", addNPatchVersion(xdscommon.EnvoyVersions[0], 1)}, - expectedSupport: false, + expectedCompat: envoyCompat{ + isCompatible: false, + versionIncompatible: addNPatchVersion(xdscommon.EnvoyVersions[0], 1), + }, }, { name: "error-bad-input", envoyVersion: "1.abc.3", unsupportedList: xdscommon.UnsupportedEnvoyVersions, - expectedSupport: false, + expectedCompat: envoyCompat{}, isErrorExpected: true, }, } @@ -1752,7 +1798,7 @@ func TestCheckEnvoyVersionCompatibility(t *testing.T) { } else { assert.NoError(t, err) } - assert.Equal(t, tc.expectedSupport, actual) + assert.Equal(t, tc.expectedCompat, actual) }) } } diff --git a/command/connect/envoy/testdata/hcp-metrics.golden b/command/connect/envoy/testdata/hcp-metrics.golden new file mode 100644 index 00000000000..563d662e443 --- /dev/null +++ b/command/connect/envoy/testdata/hcp-metrics.golden @@ -0,0 +1,247 @@ +{ + "admin": { + "access_log_path": "/dev/null", + "address": { + "socket_address": { + "address": "127.0.0.1", + "port_value": 19000 + } + } + }, + "node": { + "cluster": "test", + "id": "test-proxy", + "metadata": { + "namespace": "default", + "partition": "default" + } + }, + "layered_runtime": { + "layers": [ + { + "name": "base", + "static_layer": { + "re2.max_program_size.error_level": 1048576 + } + } + ] + }, + "static_resources": { + "clusters": [ + { + "name": "local_agent", + "ignore_health_on_host_removal": false, + "connect_timeout": "1s", + "type": "STATIC", + "http2_protocol_options": {}, + "loadAssignment": { + "clusterName": "local_agent", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socket_address": { + "address": "127.0.0.1", + "port_value": 8502 + } + } + } + } + ] + } + ] + } + }, + { + "name": "hcp_metrics_collector", + "type": "STATIC", + "http2_protocol_options": {}, + "loadAssignment": { + "clusterName": "hcp_metrics_collector", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "pipe": { + "path": "/tmp/consul/hcp-metrics/default_test-proxy.sock" + } + } + } + } + ] + } + ] + } + } + ] + }, + "stats_sinks": [ + { + "name": "envoy.stat_sinks.metrics_service", + "typed_config": { + "@type": "type.googleapis.com/envoy.config.metrics.v3.MetricsServiceConfig", + "transport_api_version": "V3", + "grpc_service": { + "envoy_grpc": { + "cluster_name": "hcp_metrics_collector" + } + } + } + } + ], + "stats_config": { + "stats_tags": [ + { + "regex": "^cluster\\.(?:passthrough~)?((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.custom_hash" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.service_subset" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.service" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.namespace" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:([^.]+)\\.)?[^.]+\\.internal[^.]*\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.partition" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.datacenter" + }, + { + "regex": "^cluster\\.([^.]+\\.(?:[^.]+\\.)?([^.]+)\\.external\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.peer" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.routing_type" + }, + { + "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", + "tag_name": "consul.destination.trust_domain" + }, + { + "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.destination.target" + }, + { + "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", + "tag_name": "consul.destination.full_target" + }, + { + "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.(([^.]+)(?:\\.[^.]+)?(?:\\.[^.]+)?\\.[^.]+\\.)", + "tag_name": "consul.upstream.service" + }, + { + "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.[^.]+)?\\.([^.]+)\\.)", + "tag_name": "consul.upstream.datacenter" + }, + { + "regex": "^(?:tcp|http)\\.upstream_peered\\.([^.]+(?:\\.[^.]+)?\\.([^.]+)\\.)", + "tag_name": "consul.upstream.peer" + }, + { + "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.([^.]+(?:\\.([^.]+))?(?:\\.[^.]+)?\\.[^.]+\\.)", + "tag_name": "consul.upstream.namespace" + }, + { + "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.([^.]+))?\\.[^.]+\\.)", + "tag_name": "consul.upstream.partition" + }, + { + "regex": "^cluster\\.((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.custom_hash" + }, + { + "regex": "^cluster\\.((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.service_subset" + }, + { + "regex": "^cluster\\.((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.service" + }, + { + "regex": "^cluster\\.((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.namespace" + }, + { + "regex": "^cluster\\.((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", + "tag_name": "consul.datacenter" + }, + { + "regex": "^cluster\\.((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", + "tag_name": "consul.routing_type" + }, + { + "regex": "^cluster\\.((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", + "tag_name": "consul.trust_domain" + }, + { + "regex": "^cluster\\.(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", + "tag_name": "consul.target" + }, + { + "regex": "^cluster\\.(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", + "tag_name": "consul.full_target" + }, + { + "tag_name": "local_cluster", + "fixed_value": "test" + }, + { + "tag_name": "consul.source.service", + "fixed_value": "test" + }, + { + "tag_name": "consul.source.namespace", + "fixed_value": "default" + }, + { + "tag_name": "consul.source.partition", + "fixed_value": "default" + }, + { + "tag_name": "consul.source.datacenter", + "fixed_value": "dc1" + } + ], + "use_all_default_tags": true + }, + "dynamic_resources": { + "lds_config": { + "ads": {}, + "resource_api_version": "V3" + }, + "cds_config": { + "ads": {}, + "resource_api_version": "V3" + }, + "ads_config": { + "api_type": "DELTA_GRPC", + "transport_api_version": "V3", + "grpc_services": { + "initial_metadata": [ + { + "key": "x-consul-token", + "value": "" + } + ], + "envoy_grpc": { + "cluster_name": "local_agent" + } + } + } + } +} + diff --git a/command/debug/debug.go b/command/debug/debug.go index 017f42b77a2..dd03286d68f 100644 --- a/command/debug/debug.go +++ b/command/debug/debug.go @@ -270,7 +270,8 @@ func (c *cmd) prepare() (version string, err error) { // If none are specified we will collect information from // all by default if len(c.capture) == 0 { - c.capture = defaultTargets + c.capture = make([]string, len(defaultTargets)) + copy(c.capture, defaultTargets) } // If EnableDebug is not true, skip collecting pprof diff --git a/command/members/members_test.go b/command/members/members_test.go index cc4a21742ae..c9a2d42b77d 100644 --- a/command/members/members_test.go +++ b/command/members/members_test.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/consul/agent" consulapi "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/lib" ) // TODO(partitions): split these tests @@ -206,8 +205,6 @@ func zip(t *testing.T, k, v []string) map[string]string { } func TestSortByMemberNamePartitionAndSegment(t *testing.T) { - lib.SeedMathRand() - // For the test data we'll give them names that would sort them backwards // if we only sorted by name. newData := func() []*consulapi.AgentMember { diff --git a/command/operator/usage/instances/usage_instances.go b/command/operator/usage/instances/usage_instances.go index df997022ae0..c1c94caa69b 100644 --- a/command/operator/usage/instances/usage_instances.go +++ b/command/operator/usage/instances/usage_instances.go @@ -33,8 +33,10 @@ type cmd struct { func (c *cmd) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) - c.flags.BoolVar(&c.onlyBillable, "billable", false, "Display only billable service info.") - c.flags.BoolVar(&c.onlyConnect, "connect", false, "Display only Connect service info.") + c.flags.BoolVar(&c.onlyBillable, "billable", false, "Display only billable service info. "+ + "Cannot be used with -connect.") + c.flags.BoolVar(&c.onlyConnect, "connect", false, "Display only Connect service info."+ + "Cannot be used with -billable.") c.flags.BoolVar(&c.allDatacenters, "all-datacenters", false, "Display service counts from "+ "all datacenters.") @@ -54,6 +56,11 @@ func (c *cmd) Run(args []string) int { return 1 } + if c.onlyBillable && c.onlyConnect { + c.UI.Error("Cannot specify both -billable and -connect flags") + return 1 + } + // Create and test the HTTP client client, err := c.http.APIClient() if err != nil { @@ -219,22 +226,22 @@ func (c *cmd) Help() string { const ( synopsis = "Display service instance usage information" help = ` -Usage: consul usage instances [options] +Usage: consul operator usage instances [options] Retrieves usage information about the number of services registered in a given datacenter. By default, the datacenter of the local agent is queried. To retrieve the service usage data: - $ consul usage instances + $ consul operator usage instances To show only billable service instance counts: - $ consul usage instances -billable + $ consul operator usage instances -billable To show only connect service instance counts: - $ consul usage instances -connect + $ consul operator usage instances -connect For a full list of options and examples, please see the Consul documentation. ` diff --git a/command/operator/usage/instances/usage_instances_test.go b/command/operator/usage/instances/usage_instances_test.go index 7aabf030e27..0f41b79aa0b 100644 --- a/command/operator/usage/instances/usage_instances_test.go +++ b/command/operator/usage/instances/usage_instances_test.go @@ -1,6 +1,7 @@ package instances import ( + "errors" "testing" "github.com/hashicorp/consul/agent" @@ -36,15 +37,40 @@ func TestUsageInstancesCommand(t *testing.T) { t.Fatal(err) } - ui := cli.NewMockUi() - c := New(ui) - args := []string{ - "-http-addr=" + a.HTTPAddr(), + cases := []struct { + name string + extraArgs []string + output string + err error + }{ + { + name: "basic output", + output: "Billable Service Instances Total: 2", + }, + { + name: "billable and connect flags together are invalid", + extraArgs: []string{"-billable", "-connect"}, + err: errors.New("Cannot specify both -billable and -connect"), + }, } - code := c.Run(args) - if code != 0 { - t.Fatalf("bad exit code %d: %s", code, ui.ErrorWriter.String()) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ui := cli.NewMockUi() + c := New(ui) + args := []string{ + "-http-addr=" + a.HTTPAddr(), + } + args = append(args, tc.extraArgs...) + + code := c.Run(args) + if tc.err != nil { + require.Equal(t, 1, code) + require.Contains(t, ui.ErrorWriter.String(), tc.err.Error()) + } else { + require.Equal(t, 0, code) + require.Contains(t, ui.OutputWriter.String(), tc.output) + } + }) } - output := ui.OutputWriter.String() - require.Contains(t, output, "Billable Service Instances Total: 2") } diff --git a/command/services/config_test.go b/command/services/config_test.go index 1647b929342..b54a793aa47 100644 --- a/command/services/config_test.go +++ b/command/services/config_test.go @@ -137,7 +137,7 @@ func TestStructsToAgentService(t *testing.T) { DestinationServiceName: "web", LocalServiceAddress: "127.0.0.1", LocalServicePort: 8181, - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), Mode: structs.ProxyModeTransparent, Config: map[string]interface{}{ "foo": "bar", @@ -154,7 +154,7 @@ func TestStructsToAgentService(t *testing.T) { DestinationServiceName: "web", LocalServiceAddress: "127.0.0.1", LocalServicePort: 8181, - Upstreams: structs.TestUpstreams(t).ToAPI(), + Upstreams: structs.TestUpstreams(t, false).ToAPI(), Mode: api.ProxyModeTransparent, Config: map[string]interface{}{ "foo": "bar", @@ -174,7 +174,7 @@ func TestStructsToAgentService(t *testing.T) { DestinationServiceName: "web", LocalServiceAddress: "127.0.0.1", LocalServicePort: 8181, - Upstreams: structs.TestUpstreams(t), + Upstreams: structs.TestUpstreams(t, false), Mode: structs.ProxyModeTransparent, TransparentProxy: structs.TransparentProxyConfig{ OutboundListenerPort: 808, @@ -201,7 +201,7 @@ func TestStructsToAgentService(t *testing.T) { DestinationServiceName: "web", LocalServiceAddress: "127.0.0.1", LocalServicePort: 8181, - Upstreams: structs.TestUpstreams(t).ToAPI(), + Upstreams: structs.TestUpstreams(t, false).ToAPI(), Mode: api.ProxyModeTransparent, TransparentProxy: &api.TransparentProxyConfig{ OutboundListenerPort: 808, diff --git a/command/troubleshoot/proxy/troubleshoot_proxy.go b/command/troubleshoot/proxy/troubleshoot_proxy.go index 8950424a786..57d982dea0d 100644 --- a/command/troubleshoot/proxy/troubleshoot_proxy.go +++ b/command/troubleshoot/proxy/troubleshoot_proxy.go @@ -77,12 +77,12 @@ func (c *cmd) Run(args []string) int { t, err := troubleshoot.NewTroubleshoot(adminBindIP, adminPort) if err != nil { - c.UI.Error("error generating troubleshoot client: " + err.Error()) + c.UI.Error("Error generating troubleshoot client: " + err.Error()) return 1 } messages, err := t.RunAllTests(c.upstreamEnvoyID, c.upstreamIP) if err != nil { - c.UI.Error("error running the tests: " + err.Error()) + c.UI.Error("Error running the tests: " + err.Error()) return 1 } @@ -92,11 +92,16 @@ func (c *cmd) Run(args []string) int { c.UI.SuccessOutput(o.Message) } else { c.UI.ErrorOutput(o.Message) - if o.PossibleActions != "" { - c.UI.UnchangedOutput(o.PossibleActions) + for _, action := range o.PossibleActions { + c.UI.UnchangedOutput("-> " + action) } } } + if messages.Success() { + c.UI.UnchangedOutput("If you are still experiencing issues, you can:") + c.UI.UnchangedOutput("-> Check intentions to ensure the upstream allows traffic from this source") + c.UI.UnchangedOutput("-> If using transparent proxy, ensure DNS resolution is to the same IP you have verified here") + } return 0 } @@ -114,14 +119,15 @@ const ( Usage: consul troubleshoot proxy [options] Connects to local envoy proxy and troubleshoots service mesh communication issues. - Requires an upstream service envoy identifier. + Requires an upstream service identifier. When debugging explicitly configured upstreams, + use -upstream-envoy-id, when debugging transparent proxy upstreams use -upstream-ip. Examples: (explicit upstreams only) $ consul troubleshoot proxy -upstream-envoy-id foo (transparent proxy only) - $ consul troubleshoot proxy -upstream-ip + $ consul troubleshoot proxy -upstream-ip 240.0.0.1 - where 'foo' is the upstream envoy identifier which + where 'foo' is the upstream envoy identifier and '240.0.0.1' is an upstream ip which can be obtained by running: $ consul troubleshoot upstreams [options] ` diff --git a/command/troubleshoot/upstreams/troubleshoot_upstreams.go b/command/troubleshoot/upstreams/troubleshoot_upstreams.go index 1249f5ddc56..8f941699ce0 100644 --- a/command/troubleshoot/upstreams/troubleshoot_upstreams.go +++ b/command/troubleshoot/upstreams/troubleshoot_upstreams.go @@ -77,24 +77,24 @@ func (c *cmd) Run(args []string) int { return 1 } - c.UI.Output(fmt.Sprintf("==> Upstreams (explicit upstreams only) (%v)", len(envoyIDs))) + c.UI.HeaderOutput(fmt.Sprintf("Upstreams (explicit upstreams only) (%v)", len(envoyIDs))) for _, u := range envoyIDs { - c.UI.Output(u) + c.UI.UnchangedOutput(u) } - c.UI.Output(fmt.Sprintf("\n==> Upstream IPs (transparent proxy only) (%v)", len(upstreamIPs))) + c.UI.HeaderOutput(fmt.Sprintf("Upstream IPs (transparent proxy only) (%v)", len(upstreamIPs))) tbl := cli.NewTable("IPs ", "Virtual ", "Cluster Names") for _, u := range upstreamIPs { tbl.AddRow([]string{formatIPs(u.IPs), strconv.FormatBool(u.IsVirtual), formatClusterNames(u.ClusterNames)}, []string{}) } c.UI.Table(tbl) - c.UI.Output("\nIf you don't see your upstream address or cluster for a transparent proxy upstream:") - c.UI.Output("- Check intentions: Tproxy upstreams are configured based on intentions, make sure you " + + c.UI.UnchangedOutput("\nIf you cannot find the upstream address or cluster for a transparent proxy upstream:") + c.UI.UnchangedOutput("-> Check intentions: Transparent proxy upstreams are configured based on intentions. Make sure you " + "have configured intentions to allow traffic to your upstream.") - c.UI.Output("- You can also check that the right cluster is being dialed by running a DNS lookup " + - "for the upstream you are dialing (i.e dig backend.svc.consul). If the address you get from that is missing " + - "from the Upstream IPs your proxy may be misconfigured.") + c.UI.UnchangedOutput("-> To check that the right cluster is being dialed, run a DNS lookup " + + "for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing " + + "from the upstream IPs, it means that your proxy may be misconfigured.") return 0 } diff --git a/docs/README.md b/docs/README.md index 0c24ff8f992..8f0be780011 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,6 +38,7 @@ Also see the [FAQ](./faq.md). ## Other Docs 1. [Integration Tests](../test/integration/connect/envoy/README.md) +1. [Upgrade Tests](../test/integration/consul-container/test/upgrade/README.md) ## Important Directories diff --git a/envoyextensions/go.mod b/envoyextensions/go.mod index f96560804c9..a62847f7132 100644 --- a/envoyextensions/go.mod +++ b/envoyextensions/go.mod @@ -2,12 +2,12 @@ module github.com/hashicorp/consul/envoyextensions go 1.19 -replace github.com/hashicorp/consul/api => ../api +//replace github.com/hashicorp/consul/api => ../api require ( github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 - github.com/hashicorp/consul/api v1.10.1-0.20230209203402-db2bd404bf72 - github.com/hashicorp/consul/sdk v0.13.0 + github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-hclog v1.2.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.2.1 diff --git a/envoyextensions/go.sum b/envoyextensions/go.sum index bf542e8f1dc..95881e29255 100644 --- a/envoyextensions/go.sum +++ b/envoyextensions/go.sum @@ -58,13 +58,14 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= -github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= +github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= @@ -95,10 +96,7 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -108,7 +106,6 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -124,7 +121,6 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -179,7 +175,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -191,7 +186,6 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -236,10 +230,8 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/envoyextensions/xdscommon/envoy_versioning_test.go b/envoyextensions/xdscommon/envoy_versioning_test.go index 833e3014ebe..e20a2ca8cee 100644 --- a/envoyextensions/xdscommon/envoy_versioning_test.go +++ b/envoyextensions/xdscommon/envoy_versioning_test.go @@ -121,6 +121,7 @@ func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { "1.18.6": {expectErr: "Envoy 1.18.6 " + errTooOld}, "1.19.5": {expectErr: "Envoy 1.19.5 " + errTooOld}, "1.20.7": {expectErr: "Envoy 1.20.7 " + errTooOld}, + "1.21.5": {expectErr: "Envoy 1.21.5 " + errTooOld}, } // Insert a bunch of valid versions. @@ -135,10 +136,10 @@ func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { } */ for _, v := range []string{ - "1.21.0", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", "1.22.0", "1.22.1", "1.22.2", "1.22.3", "1.22.4", "1.22.5", - "1.23.0", "1.23.1", "1.23.2", - "1.24.0", + "1.23.0", "1.23.1", "1.23.2", "1.23.3", "1.23.4", + "1.24.0", "1.24.1", "1.24.2", + "1.25.0", "1.25.1", } { cases[v] = testcase{expect: SupportedProxyFeatures{}} } diff --git a/envoyextensions/xdscommon/proxysupport.go b/envoyextensions/xdscommon/proxysupport.go index 963e0dba0c2..bedc0608bfd 100644 --- a/envoyextensions/xdscommon/proxysupport.go +++ b/envoyextensions/xdscommon/proxysupport.go @@ -9,10 +9,10 @@ import "strings" // // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions var EnvoyVersions = []string{ - "1.24.0", - "1.23.2", + "1.25.1", + "1.24.2", + "1.23.4", "1.22.5", - "1.21.5", } // UnsupportedEnvoyVersions lists any unsupported Envoy versions (mainly minor versions) that fall diff --git a/go.mod b/go.mod index 5a38efd9dc8..0a525cbc8e5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul -go 1.19 +go 1.20 replace ( github.com/hashicorp/consul/api => ./api @@ -18,7 +18,7 @@ exclude ( require ( github.com/NYTimes/gziphandler v1.0.1 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e - github.com/armon/go-metrics v0.3.10 + github.com/armon/go-metrics v0.4.1 github.com/armon/go-radix v1.0.0 github.com/aws/aws-sdk-go v1.42.34 github.com/coredns/coredns v1.6.6 @@ -29,7 +29,6 @@ require ( github.com/fsnotify/fsnotify v1.5.1 github.com/go-openapi/runtime v0.24.1 github.com/go-openapi/strfmt v0.21.3 - github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.8 github.com/google/gofuzz v1.2.0 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 @@ -37,11 +36,11 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706 github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69 - github.com/hashicorp/consul/api v1.18.0 - github.com/hashicorp/consul/envoyextensions v0.0.0-20230209212012-3b9c56956132 + github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/envoyextensions v0.1.2 github.com/hashicorp/consul/proto-public v0.2.1 - github.com/hashicorp/consul/sdk v0.13.0 - github.com/hashicorp/consul/troubleshoot v0.0.0-00010101000000-000000000000 + github.com/hashicorp/consul/sdk v0.13.1 + github.com/hashicorp/consul/troubleshoot v0.1.2 github.com/hashicorp/go-bexpr v0.1.2 github.com/hashicorp/go-checkpoint v0.5.0 github.com/hashicorp/go-cleanhttp v0.5.2 @@ -63,10 +62,10 @@ require ( github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 github.com/hashicorp/memberlist v0.5.0 - github.com/hashicorp/raft v1.3.11 + github.com/hashicorp/raft v1.4.0 github.com/hashicorp/raft-autopilot v0.1.6 github.com/hashicorp/raft-boltdb/v2 v2.2.2 - github.com/hashicorp/raft-wal v0.2.4 + github.com/hashicorp/raft-wal v0.3.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.2 github.com/hashicorp/vault/api/auth/gcp v0.3.0 @@ -90,7 +89,7 @@ require ( github.com/rboyer/safeio v0.2.1 github.com/ryanuber/columnize v2.1.2+incompatible github.com/shirou/gopsutil/v3 v3.22.8 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.2 go.etcd.io/bbolt v1.3.6 go.uber.org/goleak v1.1.10 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d @@ -158,6 +157,7 @@ require ( github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.0 // indirect github.com/google/go-querystring v1.0.0 // indirect @@ -213,7 +213,7 @@ require ( github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.4.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect diff --git a/go.sum b/go.sum index 7234a8e0275..461cd0136ff 100644 --- a/go.sum +++ b/go.sum @@ -142,8 +142,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -607,8 +607,9 @@ github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0 h1:kBpVVl1sl3MaSrs97e0+pDQhSrq github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0/go.mod h1:6pdNz0vo0mF0GvhwDG56O3N18qBrAz/XRIcfINfTbwo= github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft v1.3.11 h1:p3v6gf6l3S797NnK5av3HcczOC1T5CLoaRvg0g9ys4A= github.com/hashicorp/raft v1.3.11/go.mod h1:J8naEwc6XaaCfts7+28whSeRvCqTd6e20BlCU3LtEO4= +github.com/hashicorp/raft v1.4.0 h1:tn28S/AWv0BtRQgwZv/1NELu8sCvI0FixqL8C8MYKeY= +github.com/hashicorp/raft v1.4.0/go.mod h1:nz64BIjXphDLATfKGG5RzHtNUPioLeKFsXEm88yTVew= github.com/hashicorp/raft-autopilot v0.1.6 h1:C1q3RNF2FfXNZfHWbvVAu0QixaQK8K5pX4O5lh+9z4I= github.com/hashicorp/raft-autopilot v0.1.6/go.mod h1:Af4jZBwaNOI+tXfIqIdbcAnh/UyyqIMj/pOISIfhArw= github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= @@ -616,8 +617,8 @@ github.com/hashicorp/raft-boltdb v0.0.0-20210409134258-03c10cc3d4ea/go.mod h1:qR github.com/hashicorp/raft-boltdb v0.0.0-20220329195025-15018e9b97e0 h1:CO8dBMLH6dvE1jTn/30ZZw3iuPsNfajshWoJTnVc5cc= github.com/hashicorp/raft-boltdb/v2 v2.2.2 h1:rlkPtOllgIcKLxVT4nutqlTH2NRFn+tO1wwZk/4Dxqw= github.com/hashicorp/raft-boltdb/v2 v2.2.2/go.mod h1:N8YgaZgNJLpZC+h+by7vDu5rzsRgONThTEeUS3zWbfY= -github.com/hashicorp/raft-wal v0.2.4 h1:Ke0ytMj8XyOVKQqFDmmgs/6hqkTJg0b/GO2a2XQBZ6A= -github.com/hashicorp/raft-wal v0.2.4/go.mod h1:JQ/4RbnKFi5Q/4rA73CekaYtHCJhU7qM7AQ4X5Y6q4M= +github.com/hashicorp/raft-wal v0.3.0 h1:Mi6RPoRbsxIIYZryI+bSTXHD97Ua6rIYO51ibYV9bkY= +github.com/hashicorp/raft-wal v0.3.0/go.mod h1:A6vP5o8hGOs1LHfC1Okh9xPwWDcmb6Vvuz/QyqUXlOE= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= @@ -697,8 +698,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -921,8 +922,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -977,8 +978,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -987,8 +989,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 h1:8fDzz4GuVg4skjY2B0nMN7h6uN61EDVkuLyI2+qGHhI= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= diff --git a/lib/rand.go b/lib/rand.go deleted file mode 100644 index 22aa4f3544b..00000000000 --- a/lib/rand.go +++ /dev/null @@ -1,34 +0,0 @@ -package lib - -import ( - crand "crypto/rand" - "math" - "math/big" - "math/rand" - "sync" - "time" -) - -var ( - once sync.Once - - // SeededSecurely is set to true if a cryptographically secure seed - // was used to initialize rand. When false, the start time is used - // as a seed. - SeededSecurely bool -) - -// SeedMathRand provides weak, but guaranteed seeding, which is better than -// running with Go's default seed of 1. A call to SeedMathRand() is expected -// to be called via init(), but never a second time. -func SeedMathRand() { - once.Do(func() { - n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) - if err != nil { - rand.Seed(time.Now().UTC().UnixNano()) - return - } - rand.Seed(n.Int64()) - SeededSecurely = true - }) -} diff --git a/lib/telemetry.go b/lib/telemetry.go index b5815ff64e6..d07dea96d54 100644 --- a/lib/telemetry.go +++ b/lib/telemetry.go @@ -14,6 +14,7 @@ import ( "github.com/armon/go-metrics/prometheus" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" + prometheuscore "github.com/prometheus/client_golang/prometheus" "github.com/hashicorp/consul/lib/retry" ) @@ -258,7 +259,7 @@ func dogstatdSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, err return sink, nil } -func prometheusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { +func prometheusSink(cfg TelemetryConfig, _ string) (metrics.MetricSink, error) { if cfg.PrometheusOpts.Expiration.Nanoseconds() < 1 { return nil, nil @@ -266,12 +267,19 @@ func prometheusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, e sink, err := prometheus.NewPrometheusSinkFrom(cfg.PrometheusOpts) if err != nil { + // During testing we may try to register the same metrics collector + // multiple times in a single run (e.g. a metrics test fails and + // we attempt a retry), resulting in an AlreadyRegisteredError. + // Suppress this and move on. + if errors.As(err, &prometheuscore.AlreadyRegisteredError{}) { + return nil, nil + } return nil, err } return sink, nil } -func circonusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { +func circonusSink(cfg TelemetryConfig, _ string) (metrics.MetricSink, error) { token := cfg.CirconusAPIToken url := cfg.CirconusSubmissionURL if token == "" && url == "" { @@ -337,7 +345,6 @@ func configureSinks(cfg TelemetryConfig, memSink metrics.MetricSink) (metrics.Fa addSink(statsdSink) addSink(dogstatdSink) addSink(circonusSink) - addSink(circonusSink) addSink(prometheusSink) if len(sinks) > 0 { diff --git a/main.go b/main.go index 5138f8c2219..804635060a8 100644 --- a/main.go +++ b/main.go @@ -11,14 +11,9 @@ import ( "github.com/hashicorp/consul/command" "github.com/hashicorp/consul/command/cli" "github.com/hashicorp/consul/command/version" - "github.com/hashicorp/consul/lib" _ "github.com/hashicorp/consul/service_os" ) -func init() { - lib.SeedMathRand() -} - func main() { os.Exit(realMain()) } diff --git a/proto-public/pbdns/mock_DNSServiceClient.go b/proto-public/pbdns/mock_DNSServiceClient.go index 24906ab8547..d9fffda65ae 100644 --- a/proto-public/pbdns/mock_DNSServiceClient.go +++ b/proto-public/pbdns/mock_DNSServiceClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.15.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package pbdns @@ -27,6 +27,10 @@ func (_m *MockDNSServiceClient) Query(ctx context.Context, in *QueryRequest, opt ret := _m.Called(_ca...) var r0 *QueryResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *QueryRequest, ...grpc.CallOption) (*QueryResponse, error)); ok { + return rf(ctx, in, opts...) + } if rf, ok := ret.Get(0).(func(context.Context, *QueryRequest, ...grpc.CallOption) *QueryResponse); ok { r0 = rf(ctx, in, opts...) } else { @@ -35,7 +39,6 @@ func (_m *MockDNSServiceClient) Query(ctx context.Context, in *QueryRequest, opt } } - var r1 error if rf, ok := ret.Get(1).(func(context.Context, *QueryRequest, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { diff --git a/proto-public/pbdns/mock_DNSServiceServer.go b/proto-public/pbdns/mock_DNSServiceServer.go index e9bd338daf1..e78c7d4c304 100644 --- a/proto-public/pbdns/mock_DNSServiceServer.go +++ b/proto-public/pbdns/mock_DNSServiceServer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.15.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package pbdns @@ -18,6 +18,10 @@ func (_m *MockDNSServiceServer) Query(_a0 context.Context, _a1 *QueryRequest) (* ret := _m.Called(_a0, _a1) var r0 *QueryResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *QueryRequest) (*QueryResponse, error)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(context.Context, *QueryRequest) *QueryResponse); ok { r0 = rf(_a0, _a1) } else { @@ -26,7 +30,6 @@ func (_m *MockDNSServiceServer) Query(_a0 context.Context, _a1 *QueryRequest) (* } } - var r1 error if rf, ok := ret.Get(1).(func(context.Context, *QueryRequest) error); ok { r1 = rf(_a0, _a1) } else { diff --git a/proto-public/pbdns/mock_UnsafeDNSServiceServer.go b/proto-public/pbdns/mock_UnsafeDNSServiceServer.go index 0a6c47c2cb7..43a9e1e461a 100644 --- a/proto-public/pbdns/mock_UnsafeDNSServiceServer.go +++ b/proto-public/pbdns/mock_UnsafeDNSServiceServer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.15.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package pbdns diff --git a/proto/pbconfigentry/config_entry.gen.go b/proto/pbconfigentry/config_entry.gen.go index b5ae3861092..00846d3be7c 100644 --- a/proto/pbconfigentry/config_entry.gen.go +++ b/proto/pbconfigentry/config_entry.gen.go @@ -364,13 +364,10 @@ func HTTPFiltersToStructs(s *HTTPFilters, t *structs.HTTPFilters) { } } } - { - t.URLRewrites = make([]structs.URLRewrite, len(s.URLRewrites)) - for i := range s.URLRewrites { - if s.URLRewrites[i] != nil { - URLRewriteToStructs(s.URLRewrites[i], &t.URLRewrites[i]) - } - } + if s.URLRewrite != nil { + var x structs.URLRewrite + URLRewriteToStructs(s.URLRewrite, &x) + t.URLRewrite = &x } } func HTTPFiltersFromStructs(t *structs.HTTPFilters, s *HTTPFilters) { @@ -387,15 +384,10 @@ func HTTPFiltersFromStructs(t *structs.HTTPFilters, s *HTTPFilters) { } } } - { - s.URLRewrites = make([]*URLRewrite, len(t.URLRewrites)) - for i := range t.URLRewrites { - { - var x URLRewrite - URLRewriteFromStructs(&t.URLRewrites[i], &x) - s.URLRewrites[i] = &x - } - } + if t.URLRewrite != nil { + var x URLRewrite + URLRewriteFromStructs(t.URLRewrite, &x) + s.URLRewrite = &x } } func HTTPHeaderFilterToStructs(s *HTTPHeaderFilter, t *structs.HTTPHeaderFilter) { @@ -1351,6 +1343,7 @@ func ServiceResolverToStructs(s *ServiceResolver, t *structs.ServiceResolverConf } } t.ConnectTimeout = structs.DurationFromProto(s.ConnectTimeout) + t.RequestTimeout = structs.DurationFromProto(s.RequestTimeout) if s.LoadBalancer != nil { var x structs.LoadBalancer LoadBalancerToStructs(s.LoadBalancer, &x) @@ -1393,6 +1386,7 @@ func ServiceResolverFromStructs(t *structs.ServiceResolverConfigEntry, s *Servic } } s.ConnectTimeout = structs.DurationToProto(t.ConnectTimeout) + s.RequestTimeout = structs.DurationToProto(t.RequestTimeout) if t.LoadBalancer != nil { var x LoadBalancer LoadBalancerFromStructs(t.LoadBalancer, &x) @@ -1635,7 +1629,6 @@ func TCPServiceToStructs(s *TCPService, t *structs.TCPService) { return } t.Name = s.Name - t.Weight = int(s.Weight) t.EnterpriseMeta = enterpriseMetaToStructs(s.EnterpriseMeta) } func TCPServiceFromStructs(t *structs.TCPService, s *TCPService) { @@ -1643,7 +1636,6 @@ func TCPServiceFromStructs(t *structs.TCPService, s *TCPService) { return } s.Name = t.Name - s.Weight = int32(t.Weight) s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta) } func TransparentProxyConfigToStructs(s *TransparentProxyConfig, t *structs.TransparentProxyConfig) { diff --git a/proto/pbconfigentry/config_entry.go b/proto/pbconfigentry/config_entry.go index c570f9d35c6..4b36134794d 100644 --- a/proto/pbconfigentry/config_entry.go +++ b/proto/pbconfigentry/config_entry.go @@ -81,6 +81,14 @@ func ConfigEntryToStructs(s *ConfigEntry) structs.ConfigEntry { pbcommon.RaftIndexToStructs(s.RaftIndex, &target.RaftIndex) pbcommon.EnterpriseMetaToStructs(s.EnterpriseMeta, &target.EnterpriseMeta) return &target + case Kind_KindInlineCertificate: + var target structs.InlineCertificateConfigEntry + target.Name = s.Name + + InlineCertificateToStructs(s.GetInlineCertificate(), &target) + pbcommon.RaftIndexToStructs(s.RaftIndex, &target.RaftIndex) + pbcommon.EnterpriseMetaToStructs(s.EnterpriseMeta, &target.EnterpriseMeta) + return &target case Kind_KindServiceDefaults: var target structs.ServiceConfigEntry target.Name = s.Name @@ -177,6 +185,14 @@ func ConfigEntryFromStructs(s structs.ConfigEntry) *ConfigEntry { configEntry.Entry = &ConfigEntry_HTTPRoute{ HTTPRoute: &route, } + case *structs.InlineCertificateConfigEntry: + var cert InlineCertificate + InlineCertificateFromStructs(v, &cert) + + configEntry.Kind = Kind_KindInlineCertificate + configEntry.Entry = &ConfigEntry_InlineCertificate{ + InlineCertificate: &cert, + } default: panic(fmt.Sprintf("unable to convert %T to proto", s)) } diff --git a/proto/pbconfigentry/config_entry.pb.go b/proto/pbconfigentry/config_entry.pb.go index c929a21d5c5..442def1c817 100644 --- a/proto/pbconfigentry/config_entry.pb.go +++ b/proto/pbconfigentry/config_entry.pb.go @@ -575,6 +575,7 @@ type ConfigEntry struct { // *ConfigEntry_BoundAPIGateway // *ConfigEntry_TCPRoute // *ConfigEntry_HTTPRoute + // *ConfigEntry_InlineCertificate Entry isConfigEntry_Entry `protobuf_oneof:"Entry"` } @@ -708,6 +709,13 @@ func (x *ConfigEntry) GetHTTPRoute() *HTTPRoute { return nil } +func (x *ConfigEntry) GetInlineCertificate() *InlineCertificate { + if x, ok := x.GetEntry().(*ConfigEntry_InlineCertificate); ok { + return x.InlineCertificate + } + return nil +} + type isConfigEntry_Entry interface { isConfigEntry_Entry() } @@ -748,6 +756,10 @@ type ConfigEntry_HTTPRoute struct { HTTPRoute *HTTPRoute `protobuf:"bytes,13,opt,name=HTTPRoute,proto3,oneof"` } +type ConfigEntry_InlineCertificate struct { + InlineCertificate *InlineCertificate `protobuf:"bytes,14,opt,name=InlineCertificate,proto3,oneof"` +} + func (*ConfigEntry_MeshConfig) isConfigEntry_Entry() {} func (*ConfigEntry_ServiceResolver) isConfigEntry_Entry() {} @@ -766,6 +778,8 @@ func (*ConfigEntry_TCPRoute) isConfigEntry_Entry() {} func (*ConfigEntry_HTTPRoute) isConfigEntry_Entry() {} +func (*ConfigEntry_InlineCertificate) isConfigEntry_Entry() {} + // mog annotation: // // target=github.com/hashicorp/consul/agent/structs.MeshConfigEntry @@ -1157,6 +1171,8 @@ type ServiceResolver struct { ConnectTimeout *durationpb.Duration `protobuf:"bytes,5,opt,name=ConnectTimeout,proto3" json:"ConnectTimeout,omitempty"` LoadBalancer *LoadBalancer `protobuf:"bytes,6,opt,name=LoadBalancer,proto3" json:"LoadBalancer,omitempty"` Meta map[string]string `protobuf:"bytes,7,rep,name=Meta,proto3" json:"Meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto + RequestTimeout *durationpb.Duration `protobuf:"bytes,8,opt,name=RequestTimeout,proto3" json:"RequestTimeout,omitempty"` } func (x *ServiceResolver) Reset() { @@ -1240,6 +1256,13 @@ func (x *ServiceResolver) GetMeta() map[string]string { return nil } +func (x *ServiceResolver) GetRequestTimeout() *durationpb.Duration { + if x != nil { + return x.RequestTimeout + } + return nil +} + // mog annotation: // // target=github.com/hashicorp/consul/agent/structs.ServiceResolverSubset @@ -4902,8 +4925,8 @@ type HTTPFilters struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Headers []*HTTPHeaderFilter `protobuf:"bytes,1,rep,name=Headers,proto3" json:"Headers,omitempty"` - URLRewrites []*URLRewrite `protobuf:"bytes,2,rep,name=URLRewrites,proto3" json:"URLRewrites,omitempty"` + Headers []*HTTPHeaderFilter `protobuf:"bytes,1,rep,name=Headers,proto3" json:"Headers,omitempty"` + URLRewrite *URLRewrite `protobuf:"bytes,2,opt,name=URLRewrite,proto3" json:"URLRewrite,omitempty"` } func (x *HTTPFilters) Reset() { @@ -4945,9 +4968,9 @@ func (x *HTTPFilters) GetHeaders() []*HTTPHeaderFilter { return nil } -func (x *HTTPFilters) GetURLRewrites() []*URLRewrite { +func (x *HTTPFilters) GetURLRewrite() *URLRewrite { if x != nil { - return x.URLRewrites + return x.URLRewrite } return nil } @@ -5238,10 +5261,8 @@ type TCPService struct { unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` - // mog: func-to=int func-from=int32 - Weight int32 `protobuf:"varint,2,opt,name=Weight,proto3" json:"Weight,omitempty"` // mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs - EnterpriseMeta *pbcommon.EnterpriseMeta `protobuf:"bytes,4,opt,name=EnterpriseMeta,proto3" json:"EnterpriseMeta,omitempty"` + EnterpriseMeta *pbcommon.EnterpriseMeta `protobuf:"bytes,2,opt,name=EnterpriseMeta,proto3" json:"EnterpriseMeta,omitempty"` } func (x *TCPService) Reset() { @@ -5283,13 +5304,6 @@ func (x *TCPService) GetName() string { return "" } -func (x *TCPService) GetWeight() int32 { - if x != nil { - return x.Weight - } - return 0 -} - func (x *TCPService) GetEnterpriseMeta() *pbcommon.EnterpriseMeta { if x != nil { return x.EnterpriseMeta @@ -5310,7 +5324,7 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd2, 0x08, + 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbc, 0x09, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, @@ -5379,1065 +5393,1074 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x00, 0x52, 0x09, - 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x22, 0xec, 0x03, 0x0a, 0x0a, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x6d, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, - 0x50, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, - 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, - 0x12, 0x46, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x12, 0x49, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, - 0x65, 0x73, 0x68, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x48, - 0x54, 0x54, 0x50, 0x12, 0x4f, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, - 0x4d, 0x65, 0x74, 0x61, 0x12, 0x52, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x68, 0x0a, 0x11, 0x49, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x11, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xec, 0x03, 0x0a, + 0x0a, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x6d, 0x0a, 0x10, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x50, 0x0a, 0x1a, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, - 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x32, 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x68, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x4d, - 0x65, 0x73, 0x68, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, - 0x6e, 0x6c, 0x79, 0x22, 0xc9, 0x01, 0x0a, 0x0d, 0x4d, 0x65, 0x73, 0x68, 0x54, 0x4c, 0x53, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5b, 0x0a, 0x08, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x65, + 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x46, 0x0a, 0x03, 0x54, 0x4c, + 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, - 0x4d, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x54, - 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x69, - 0x6e, 0x67, 0x12, 0x5b, 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, - 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x4c, 0x53, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x4f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x22, - 0x8a, 0x01, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, - 0x54, 0x4c, 0x53, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, - 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x69, 0x70, 0x68, - 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, - 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x54, 0x0a, 0x0e, - 0x4d, 0x65, 0x73, 0x68, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, - 0x0a, 0x1c, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x58, 0x46, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x65, 0x64, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x58, 0x46, - 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, - 0x72, 0x74, 0x22, 0x4d, 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, - 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x17, 0x50, 0x65, 0x65, 0x72, 0x54, - 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x50, 0x65, 0x65, 0x72, 0x54, 0x68, - 0x72, 0x6f, 0x75, 0x67, 0x68, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x73, 0x22, 0xf6, 0x06, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x12, 0x5d, 0x0a, 0x07, 0x53, - 0x75, 0x62, 0x73, 0x65, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x07, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x73, 0x12, 0x5a, 0x0a, 0x08, 0x52, 0x65, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x72, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x08, 0x52, 0x65, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x60, 0x0a, 0x08, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, - 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, - 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, - 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x57, 0x0a, 0x0c, 0x4c, - 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x52, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x72, 0x12, 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x78, 0x0a, 0x0c, 0x53, 0x75, - 0x62, 0x73, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x52, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, - 0x76, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x7b, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x54, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, - 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x51, 0x0a, 0x15, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x53, 0x75, 0x62, - 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x4f, - 0x6e, 0x6c, 0x79, 0x50, 0x61, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x4f, 0x6e, 0x6c, 0x79, 0x50, 0x61, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x22, 0xc9, 0x01, - 0x0a, 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, - 0x72, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, - 0x62, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, - 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, - 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x22, 0xf9, 0x01, 0x0a, 0x17, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, 0x69, - 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x75, 0x62, 0x73, 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, - 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, - 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x5e, 0x0a, 0x07, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, - 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x73, 0x22, 0xcf, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, - 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, - 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x22, 0xc7, 0x02, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x12, 0x5d, 0x0a, 0x0e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x69, 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0c, 0x48, 0x61, - 0x73, 0x68, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x52, 0x0c, 0x48, 0x61, 0x73, 0x68, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, - 0x73, 0x22, 0x64, 0x0a, 0x0e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a, 0x0f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x52, 0x69, - 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x4d, 0x69, - 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x52, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, - 0x0f, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x52, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x52, - 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x36, 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, - 0x0b, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0b, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, - 0xd3, 0x01, 0x0a, 0x0a, 0x48, 0x61, 0x73, 0x68, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, - 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0c, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, - 0x08, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x65, 0x72, - 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x54, 0x65, 0x72, - 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x69, 0x0a, 0x0c, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x2b, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x12, 0x0a, 0x04, - 0x50, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, - 0x22, 0x98, 0x03, 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x12, 0x49, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x4d, 0x65, 0x73, 0x68, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, + 0x4c, 0x53, 0x12, 0x49, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x12, 0x54, - 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x65, 0x72, 0x73, 0x12, 0x53, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, - 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x57, 0x0a, 0x08, 0x44, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x68, 0x61, + 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x48, 0x54, 0x54, + 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x48, 0x54, 0x54, 0x50, 0x12, 0x4f, 0x0a, + 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4d, + 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x52, + 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4d, + 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8f, 0x02, 0x0a, 0x14, - 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x4d, 0x61, - 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x12, - 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x15, - 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x4d, 0x61, 0x78, - 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x73, 0x12, 0x69, 0x0a, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x50, 0x0a, 0x1a, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, + 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, 0x14, 0x4d, 0x65, 0x73, + 0x68, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x6e, 0x6c, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x4d, 0x65, 0x73, 0x68, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xc9, 0x01, + 0x0a, 0x0d, 0x4d, 0x65, 0x73, 0x68, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x5b, 0x0a, 0x08, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x44, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x08, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x5b, 0x0a, 0x08, + 0x4f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, - 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xea, 0x01, - 0x0a, 0x10, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x4c, 0x0a, 0x03, - 0x53, 0x44, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x53, 0x44, 0x53, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, - 0x53, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, - 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x69, - 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x5b, 0x0a, 0x13, 0x47, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x0f, 0x49, 0x6e, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x50, - 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x51, 0x0a, 0x08, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x52, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x49, - 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x68, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x08, 0x4f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x8a, 0x01, 0x0a, 0x18, 0x4d, 0x65, + 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x4c, 0x53, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x69, 0x6e, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, + 0x4c, 0x53, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, + 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, + 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x54, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x68, 0x48, 0x54, + 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x1c, 0x53, 0x61, 0x6e, 0x69, + 0x74, 0x69, 0x7a, 0x65, 0x58, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, + 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x58, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x65, 0x64, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x22, 0x4d, 0x0a, 0x11, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x38, 0x0a, 0x17, 0x50, 0x65, 0x65, 0x72, 0x54, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, + 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x17, 0x50, 0x65, 0x65, 0x72, 0x54, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x4d, + 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x22, 0xb9, 0x07, 0x0a, 0x0f, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x12, + 0x24, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, + 0x75, 0x62, 0x73, 0x65, 0x74, 0x12, 0x5d, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x2e, 0x53, + 0x75, 0x62, 0x73, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x53, 0x75, 0x62, + 0x73, 0x65, 0x74, 0x73, 0x12, 0x5a, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x12, 0x60, 0x0a, 0x08, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x6f, + 0x76, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, + 0x65, 0x72, 0x12, 0x41, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x57, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x22, 0xb7, 0x06, 0x0a, 0x0e, 0x49, 0x6e, - 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x03, 0x20, + 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, + 0x52, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12, 0x54, + 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x6c, 0x76, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, + 0x4d, 0x65, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x78, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, 0x65, + 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x52, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, + 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x1a, 0x7b, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x54, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x12, 0x62, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x0e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x64, 0x0a, 0x0f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, 0x69, 0x6c, 0x6f, + 0x76, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x37, + 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x51, 0x0a, 0x15, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x6e, 0x6c, 0x79, + 0x50, 0x61, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x4f, + 0x6e, 0x6c, 0x79, 0x50, 0x61, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x22, 0xc9, 0x01, 0x0a, 0x17, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, + 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x22, 0xf9, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, + 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0d, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, + 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, + 0x72, 0x73, 0x12, 0x5e, 0x0a, 0x07, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, 0x69, 0x6c, 0x6f, + 0x76, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x07, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x73, 0x22, 0xcf, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x46, 0x61, 0x69, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x24, + 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, + 0x62, 0x73, 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x50, 0x65, 0x65, 0x72, 0x22, 0xc7, 0x02, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x5d, 0x0a, + 0x0e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, - 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x73, 0x52, 0x0f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x73, 0x12, 0x53, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, - 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, + 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x69, + 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x52, 0x69, + 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x69, 0x0a, 0x12, + 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0c, 0x48, 0x61, 0x73, 0x68, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x0c, 0x48, 0x61, 0x73, 0x68, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x22, 0x64, + 0x0a, 0x0e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x28, 0x0a, 0x0f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x52, 0x69, 0x6e, 0x67, 0x53, + 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, + 0x75, 0x6d, 0x52, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x4d, 0x61, + 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x52, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0f, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x52, 0x69, 0x6e, 0x67, + 0x53, 0x69, 0x7a, 0x65, 0x22, 0x36, 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x68, + 0x6f, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0b, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd3, 0x01, 0x0a, + 0x0a, 0x48, 0x61, 0x73, 0x68, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x43, 0x6f, + 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x22, 0x69, 0x0a, 0x0c, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x03, + 0x54, 0x54, 0x4c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, + 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x22, 0x98, 0x03, + 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x12, 0x49, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x12, 0x54, 0x0a, 0x09, 0x4c, + 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x69, + 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, + 0x73, 0x12, 0x53, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3f, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x57, 0x0a, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x1a, + 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8f, 0x02, 0x0a, 0x14, 0x49, 0x6e, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, + 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x69, 0x0a, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, - 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x67, 0x0a, 0x17, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, - 0x0a, 0x03, 0x53, 0x44, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, - 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x53, 0x44, 0x53, 0x22, 0xcb, 0x02, 0x0a, - 0x13, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x41, 0x64, - 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x41, 0x64, 0x64, 0x12, 0x55, 0x0a, 0x03, 0x53, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x18, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x4c, 0x0a, 0x03, 0x53, 0x44, 0x53, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x03, 0x53, 0x44, 0x53, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x69, + 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x54, 0x4c, 0x53, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, + 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x4c, 0x53, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, + 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, + 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x5b, 0x0a, 0x13, 0x47, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, + 0x0a, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x0f, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x51, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x52, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x03, 0x54, + 0x4c, 0x53, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x73, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x53, - 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x1a, 0x36, 0x0a, 0x08, 0x41, 0x64, - 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x1a, 0x36, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x11, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x50, 0x0a, 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x42, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x22, 0xb7, 0x06, 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x48, 0x6f, + 0x73, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, - 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0xa6, 0x06, 0x0a, 0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x06, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0b, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x03, 0x54, 0x4c, 0x53, 0x12, 0x62, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x64, 0x0a, 0x0f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x0f, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x53, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, + 0x4d, 0x65, 0x74, 0x61, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, + 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x26, + 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x69, 0x0a, 0x12, + 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x67, 0x0a, 0x17, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, 0x0a, 0x03, 0x53, + 0x44, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x53, 0x44, 0x53, 0x22, 0xcb, 0x02, 0x0a, 0x13, 0x48, 0x54, + 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x73, 0x12, 0x55, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x03, 0x41, 0x64, 0x64, 0x12, 0x55, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, + 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x73, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x53, 0x65, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x1a, 0x36, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, + 0x36, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x50, 0x0a, + 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, + 0x56, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0xa6, 0x06, 0x0a, 0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x65, 0x63, 0x65, 0x64, + 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x50, 0x72, 0x65, 0x63, + 0x65, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, + 0x49, 0x44, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, + 0x49, 0x44, 0x12, 0x4e, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x65, - 0x63, 0x65, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x50, - 0x72, 0x65, 0x63, 0x65, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x65, 0x67, - 0x61, 0x63, 0x79, 0x49, 0x44, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4c, 0x65, 0x67, - 0x61, 0x63, 0x79, 0x49, 0x44, 0x12, 0x4e, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x46, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x12, - 0x46, 0x0a, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, - 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x4c, - 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, - 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, - 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, - 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x1a, 0x3d, 0x0a, - 0x0f, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x01, 0x0a, - 0x13, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, + 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x46, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x10, + 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, 0x61, + 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, + 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x04, 0x48, 0x54, 0x54, 0x50, 0x22, 0xed, 0x01, 0x0a, 0x17, 0x49, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, - 0x63, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, - 0x69, 0x78, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, - 0x12, 0x5c, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x44, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, - 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x18, - 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x1d, 0x49, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, 0x14, - 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, - 0x65, 0x67, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x22, 0xb6, 0x08, 0x0a, - 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x44, 0x0a, 0x04, - 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, - 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x4d, 0x6f, - 0x64, 0x65, 0x12, 0x69, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, - 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x5a, 0x0a, - 0x0b, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x47, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x4d, 0x65, - 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x4b, 0x0a, 0x06, 0x45, 0x78, 0x70, - 0x6f, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x1a, 0x3d, 0x0a, 0x0f, 0x4c, 0x65, + 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x49, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x52, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x04, 0x48, 0x54, 0x54, 0x50, 0x22, 0xed, 0x01, 0x0a, 0x17, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, + 0x1e, 0x0a, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, + 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x5c, 0x0a, + 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x1d, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x50, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x50, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x52, + 0x65, 0x67, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x65, 0x67, 0x65, + 0x78, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x22, 0xb6, 0x08, 0x0a, 0x0f, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1a, 0x0a, + 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x44, 0x0a, 0x04, 0x4d, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, + 0x69, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, + 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, - 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x53, 0x4e, 0x49, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x45, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x4e, 0x49, 0x12, 0x64, 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x3c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, + 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x5a, 0x0a, 0x0b, 0x4d, 0x65, + 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x4d, 0x65, 0x73, 0x68, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x4b, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x45, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x45, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, + 0x4e, 0x49, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x53, 0x4e, 0x49, 0x12, 0x64, 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x55, 0x70, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5a, 0x0a, 0x0b, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, - 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5a, - 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x44, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, - 0x78, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x49, 0x6e, + 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x34, 0x0a, 0x15, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x19, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x19, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x54, 0x0a, 0x04, 0x4d, 0x65, - 0x74, 0x61, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, - 0x12, 0x5a, 0x0a, 0x0f, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x76, - 0x6f, 0x79, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x45, 0x6e, 0x76, - 0x6f, 0x79, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x37, 0x0a, 0x09, - 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x74, 0x0a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, - 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x32, 0x0a, 0x14, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x4f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, - 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x44, 0x69, 0x61, 0x6c, 0x65, 0x64, 0x44, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x44, 0x69, 0x61, - 0x6c, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x22, 0x5f, 0x0a, 0x11, 0x4d, - 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x4a, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, + 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x4d, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x15, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x19, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, + 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x5a, 0x0a, + 0x0f, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x45, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x45, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, + 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x74, 0x0a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, 0x14, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, + 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x4f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, + 0x12, 0x26, 0x0a, 0x0e, 0x44, 0x69, 0x61, 0x6c, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x44, 0x69, 0x61, 0x6c, 0x65, 0x64, + 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x22, 0x5f, 0x0a, 0x11, 0x4d, 0x65, 0x73, 0x68, + 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4a, 0x0a, + 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x6f, 0x0a, 0x0c, 0x45, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x73, 0x12, 0x47, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, + 0x61, 0x74, 0x68, 0x52, 0x05, 0x50, 0x61, 0x74, 0x68, 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x0a, 0x45, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0c, 0x4c, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x50, 0x6f, + 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, + 0x61, 0x74, 0x68, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x73, 0x65, 0x64, 0x46, 0x72, 0x6f, + 0x6d, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x50, 0x61, + 0x72, 0x73, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xbf, 0x01, + 0x0a, 0x15, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x53, 0x0a, 0x09, 0x4f, 0x76, 0x65, 0x72, 0x72, + 0x69, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x09, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x08, + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x6f, 0x0a, 0x0c, - 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x73, 0x12, 0x47, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x45, 0x78, 0x70, 0x6f, - 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x50, 0x61, 0x74, 0x68, 0x73, 0x22, 0xb0, 0x01, - 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, - 0x68, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x4c, 0x6f, 0x63, - 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x73, 0x65, 0x64, - 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0f, 0x50, 0x61, 0x72, 0x73, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x53, 0x0a, 0x09, 0x4f, 0x76, - 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x22, + 0x8a, 0x05, 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x12, 0x2c, 0x0a, 0x11, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, + 0x72, 0x4a, 0x53, 0x4f, 0x4e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x45, 0x6e, 0x76, + 0x6f, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x4a, 0x53, 0x4f, 0x4e, 0x12, 0x2a, + 0x0a, 0x10, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4a, 0x53, + 0x4f, 0x4e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4a, 0x53, 0x4f, 0x4e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x2a, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x4d, 0x73, 0x12, 0x4d, 0x0a, 0x06, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x06, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x73, 0x12, 0x69, 0x0a, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x12, - 0x51, 0x0a, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, + 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x5a, 0x0a, 0x0b, + 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x73, 0x22, 0x8a, 0x05, 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, - 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x4d, 0x65, 0x73, + 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x3e, 0x0a, 0x1a, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x22, 0x9e, 0x01, 0x0a, + 0x0e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, + 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, + 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0xa7, 0x01, + 0x0a, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x12, 0x35, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x4d, + 0x61, 0x78, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0b, 0x4d, 0x61, 0x78, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x12, 0x38, 0x0a, + 0x17, 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x76, 0x65, 0x35, 0x78, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x17, + 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x75, + 0x74, 0x69, 0x76, 0x65, 0x35, 0x78, 0x78, 0x22, 0x45, 0x0a, 0x11, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, + 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xb6, + 0x02, 0x0a, 0x0a, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x4f, 0x0a, + 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x2e, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x4d, + 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x57, + 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x11, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x4c, 0x69, 0x73, 0x74, - 0x65, 0x6e, 0x65, 0x72, 0x4a, 0x53, 0x4f, 0x4e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, - 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x4a, 0x53, 0x4f, - 0x4e, 0x12, 0x2a, 0x0a, 0x10, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x4a, 0x53, 0x4f, 0x4e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x45, 0x6e, 0x76, - 0x6f, 0x79, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4a, 0x53, 0x4f, 0x4e, 0x12, 0x1a, 0x0a, - 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x2a, 0x0a, 0x10, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x4d, 0x0a, 0x06, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x4c, 0x69, + 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x45, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x37, + 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5a, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x50, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x06, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x73, 0x12, 0x69, 0x0a, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x12, 0x50, 0x61, 0x73, - 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, - 0x5a, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x73, - 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, - 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x3e, 0x0a, 0x1a, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x1a, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x50, - 0x65, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x22, - 0x9e, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x69, 0x6d, 0x69, - 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x4d, 0x61, 0x78, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x4d, 0x61, - 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, - 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, - 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, - 0x22, 0xa7, 0x01, 0x0a, 0x12, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x35, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x20, - 0x0a, 0x0b, 0x4d, 0x61, 0x78, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x4d, 0x61, 0x78, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, - 0x12, 0x38, 0x0a, 0x17, 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, - 0x73, 0x65, 0x63, 0x75, 0x74, 0x69, 0x76, 0x65, 0x35, 0x78, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x17, 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, - 0x65, 0x63, 0x75, 0x74, 0x69, 0x76, 0x65, 0x35, 0x78, 0x78, 0x22, 0x45, 0x0a, 0x11, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x1c, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x6f, 0x72, - 0x74, 0x22, 0xb6, 0x02, 0x0a, 0x0a, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x12, 0x4f, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, + 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x22, 0x8b, 0x02, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x54, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x4c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x12, 0x4c, + 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, + 0x65, 0x22, 0x8c, 0x02, 0x0a, 0x12, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x5d, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, - 0x61, 0x12, 0x57, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x41, 0x50, 0x49, - 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, - 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x45, 0x0a, 0x06, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, - 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5a, 0x0a, 0x06, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x50, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x53, 0x0a, 0x03, 0x54, + 0x4c, 0x53, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x43, 0x6f, 0x6e, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8b, 0x02, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x54, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x08, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x4c, 0x61, 0x73, 0x74, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x12, 0x4c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8c, 0x02, 0x0a, 0x12, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x4e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, - 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, - 0x5d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x53, - 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, - 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, - 0x54, 0x4c, 0x53, 0x22, 0xde, 0x01, 0x0a, 0x1a, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x52, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, - 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, - 0x69, 0x74, 0x65, 0x73, 0x22, 0xb7, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4b, 0x69, - 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, - 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, - 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x22, 0xfe, - 0x01, 0x0a, 0x0f, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x12, 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, - 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x5c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, - 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x4c, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0xdd, 0x01, 0x0a, 0x17, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x4e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x2e, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x54, 0x4c, 0x53, + 0x22, 0xde, 0x01, 0x0a, 0x1a, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, + 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x50, 0x0a, - 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x0c, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x0a, + 0x0a, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x4d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x4d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, + 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, + 0x73, 0x22, 0xb7, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, + 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x22, 0xfe, 0x01, 0x0a, 0x0f, + 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, + 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, - 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, - 0xe6, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x56, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x6c, 0x69, - 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x20, 0x0a, - 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, - 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x1a, - 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x99, 0x03, 0x0a, 0x09, 0x48, 0x54, 0x54, - 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x4e, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, - 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x52, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x52, 0x07, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x4a, 0x0a, 0x05, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, - 0x05, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, - 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf9, 0x01, 0x0a, 0x0d, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x4c, 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, - 0x48, 0x54, 0x54, 0x50, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x52, 0x07, 0x46, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x73, 0x12, 0x4a, 0x0a, 0x07, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, - 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x07, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, - 0x12, 0x4e, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x5c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, + 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x65, 0x72, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xdd, 0x01, 0x0a, + 0x17, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5c, 0x0a, 0x0c, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x22, 0xc4, 0x02, 0x0a, 0x09, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x50, - 0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, - 0x12, 0x4e, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, - 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x48, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x4b, 0x0a, 0x05, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, - 0x52, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x22, 0x8d, 0x01, 0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x50, 0x0a, 0x05, 0x4d, - 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0c, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x50, 0x0a, 0x06, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, - 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, - 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x12, 0x0a, - 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x75, 0x0a, 0x0d, 0x48, 0x54, 0x54, 0x50, 0x50, - 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x4e, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, - 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8b, - 0x01, 0x0a, 0x0e, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x12, 0x4f, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0xe6, 0x01, 0x0a, + 0x11, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x12, 0x56, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x42, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x4d, 0x61, 0x74, - 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xb5, 0x01, 0x0a, - 0x0b, 0x48, 0x54, 0x54, 0x50, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x51, 0x0a, 0x07, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, + 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x37, 0x0a, 0x09, + 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x99, 0x03, 0x0a, 0x09, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x12, 0x4e, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, + 0x65, 0x74, 0x61, 0x12, 0x52, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x07, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x4a, 0x0a, 0x05, 0x52, 0x75, 0x6c, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, + 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x52, 0x75, + 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x12, 0x45, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0xf9, 0x01, 0x0a, 0x0d, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, + 0x75, 0x6c, 0x65, 0x12, 0x4c, 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, + 0x50, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x52, 0x07, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x73, 0x12, 0x4a, 0x0a, 0x07, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x07, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x4e, 0x0a, + 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x32, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x52, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0xc4, 0x02, + 0x0a, 0x09, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x50, 0x0a, 0x07, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x4e, 0x0a, + 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, - 0x53, 0x0a, 0x0b, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x52, 0x4c, - 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x52, 0x0b, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x73, 0x22, 0x20, 0x0a, 0x0a, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x22, 0xc2, 0x02, 0x0a, 0x10, 0x48, 0x54, 0x54, 0x50, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x03, 0x41, - 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x41, 0x64, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x52, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x48, 0x0a, + 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x4b, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, + 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x05, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x22, 0x8d, 0x01, 0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x50, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x75, 0x0a, 0x0d, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x4e, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, - 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x65, - 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x53, 0x65, 0x74, 0x1a, 0x36, 0x0a, 0x08, 0x41, - 0x64, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x36, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe1, 0x01, 0x0a, 0x0b, - 0x48, 0x54, 0x54, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x4c, 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x2e, 0x48, 0x54, 0x54, 0x50, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x52, 0x07, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, - 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, + 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x0e, + 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x4f, + 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x22, - 0xfc, 0x02, 0x0a, 0x08, 0x54, 0x43, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x4d, 0x0a, 0x04, - 0x4d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xb3, 0x01, 0x0a, 0x0b, 0x48, 0x54, + 0x54, 0x50, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x51, 0x0a, 0x07, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, - 0x72, 0x79, 0x2e, 0x54, 0x43, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x52, 0x0a, 0x07, 0x50, - 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x4d, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x54, 0x43, 0x50, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x52, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x45, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x52, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x51, 0x0a, 0x0a, + 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x52, 0x0a, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, + 0x20, 0x0a, 0x0a, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, + 0x68, 0x22, 0xc2, 0x02, 0x0a, 0x10, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x41, 0x64, 0x64, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x41, 0x64, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x12, 0x52, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x03, 0x53, 0x65, 0x74, 0x1a, 0x36, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x92, - 0x01, 0x0a, 0x0a, 0x54, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x06, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, - 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x2a, 0xfd, 0x01, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, - 0x4b, 0x69, 0x6e, 0x64, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x12, 0x0a, - 0x0e, 0x4b, 0x69, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10, - 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x4b, 0x69, - 0x6e, 0x64, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x10, 0x04, 0x12, 0x17, 0x0a, - 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x73, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x4b, 0x69, 0x6e, 0x64, 0x49, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x10, - 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x4b, 0x69, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x10, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x42, 0x6f, 0x75, - 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x10, 0x08, 0x12, 0x11, - 0x0a, 0x0d, 0x4b, 0x69, 0x6e, 0x64, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x10, - 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x4b, 0x69, 0x6e, 0x64, 0x54, 0x43, 0x50, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x10, 0x0a, 0x2a, 0x26, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x10, 0x01, 0x2a, 0x21, 0x0a, 0x13, 0x49, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x10, 0x00, 0x2a, 0x50, - 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x50, - 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, - 0x00, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x50, - 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, - 0x2a, 0x7b, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, - 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x00, 0x12, - 0x17, 0x0a, 0x13, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x6f, - 0x64, 0x65, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x68, - 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, - 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x10, 0x03, 0x2a, 0x4f, 0x0a, - 0x1a, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x14, 0x4c, - 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x48, - 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, - 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x54, 0x43, 0x50, 0x10, 0x01, 0x2a, 0x92, - 0x02, 0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x48, 0x54, - 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x10, - 0x02, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x47, 0x65, 0x74, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x54, - 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x48, 0x65, 0x61, 0x64, - 0x10, 0x04, 0x12, 0x1a, 0x0a, 0x16, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x10, 0x05, 0x12, 0x18, - 0x0a, 0x14, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x50, 0x61, 0x74, 0x63, 0x68, 0x10, 0x06, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x54, 0x50, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x6f, 0x73, 0x74, 0x10, - 0x07, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x50, 0x75, 0x74, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x54, - 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x54, 0x72, 0x61, 0x63, - 0x65, 0x10, 0x09, 0x2a, 0xa7, 0x01, 0x0a, 0x13, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x48, - 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, - 0x61, 0x63, 0x74, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x10, 0x01, - 0x12, 0x1a, 0x0a, 0x16, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, - 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x10, 0x04, 0x2a, 0x68, 0x0a, - 0x11, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, - 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x2a, 0x6d, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, - 0x13, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x45, - 0x78, 0x61, 0x63, 0x74, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x10, - 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x42, 0xa6, 0x02, 0x0a, 0x29, 0x63, 0x6f, 0x6d, 0x2e, 0x68, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x36, + 0x0a, 0x08, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe1, 0x01, 0x0a, 0x0b, 0x48, 0x54, 0x54, 0x50, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x57, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x57, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x12, 0x4c, 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x52, 0x07, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, + 0x12, 0x58, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, + 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x22, 0xfc, 0x02, 0x0a, 0x08, 0x54, + 0x43, 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x4d, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x54, 0x43, + 0x50, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x52, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x52, 0x07, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x08, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x42, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x43, - 0xaa, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xca, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0xe2, 0x02, 0x31, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x28, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x54, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, + 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x06, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, + 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7a, 0x0a, 0x0a, 0x54, 0x43, 0x50, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x45, + 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x2a, 0xfd, 0x01, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, + 0x0a, 0x0b, 0x4b, 0x69, 0x6e, 0x64, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, + 0x12, 0x0a, 0x0e, 0x4b, 0x69, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, + 0x4b, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x10, 0x04, 0x12, + 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x4b, 0x69, 0x6e, 0x64, + 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x4b, 0x69, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x10, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x42, + 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x10, 0x08, + 0x12, 0x11, 0x0a, 0x0d, 0x4b, 0x69, 0x6e, 0x64, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x10, 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x4b, 0x69, 0x6e, 0x64, 0x54, 0x43, 0x50, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x10, 0x0a, 0x2a, 0x26, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, + 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, + 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x10, 0x01, 0x2a, 0x21, 0x0a, + 0x13, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x10, 0x00, + 0x2a, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, + 0x10, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x13, 0x0a, + 0x0f, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x10, 0x02, 0x2a, 0x7b, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, + 0x00, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x65, + 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x68, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x10, 0x03, 0x2a, + 0x4f, 0x0a, 0x1a, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x18, 0x0a, + 0x14, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x65, + 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x54, 0x43, 0x50, 0x10, 0x01, + 0x2a, 0x92, 0x02, 0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, + 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x47, 0x65, 0x74, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, + 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x48, 0x65, + 0x61, 0x64, 0x10, 0x04, 0x12, 0x1a, 0x0a, 0x16, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x10, 0x05, + 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x50, 0x61, 0x74, 0x63, 0x68, 0x10, 0x06, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, + 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x6f, 0x73, + 0x74, 0x10, 0x07, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x75, 0x74, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x48, + 0x54, 0x54, 0x50, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x10, 0x09, 0x2a, 0xa7, 0x01, 0x0a, 0x13, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, + 0x14, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x10, 0x02, 0x12, 0x24, + 0x0a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x10, 0x04, 0x2a, + 0x68, 0x0a, 0x11, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, + 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x61, 0x74, + 0x68, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x45, 0x78, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x2a, 0x6d, 0x0a, 0x12, 0x48, 0x54, 0x54, + 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x17, 0x0a, 0x13, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x54, 0x54, 0x50, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x74, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x48, 0x54, 0x54, 0x50, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x45, 0x78, 0x70, 0x72, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x42, 0xa6, 0x02, 0x0a, 0x29, 0x63, 0x6f, 0x6d, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xa2, 0x02, 0x04, 0x48, 0x43, + 0x49, 0x43, 0xaa, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xca, 0x02, 0x25, 0x48, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0xe2, 0x02, 0x31, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x28, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, + 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -6563,118 +6586,120 @@ var file_proto_pbconfigentry_config_entry_proto_depIdxs = []int32{ 56, // 9: hashicorp.consul.internal.configentry.ConfigEntry.BoundAPIGateway:type_name -> hashicorp.consul.internal.configentry.BoundAPIGateway 69, // 10: hashicorp.consul.internal.configentry.ConfigEntry.TCPRoute:type_name -> hashicorp.consul.internal.configentry.TCPRoute 59, // 11: hashicorp.consul.internal.configentry.ConfigEntry.HTTPRoute:type_name -> hashicorp.consul.internal.configentry.HTTPRoute - 12, // 12: hashicorp.consul.internal.configentry.MeshConfig.TransparentProxy:type_name -> hashicorp.consul.internal.configentry.TransparentProxyMeshConfig - 13, // 13: hashicorp.consul.internal.configentry.MeshConfig.TLS:type_name -> hashicorp.consul.internal.configentry.MeshTLSConfig - 15, // 14: hashicorp.consul.internal.configentry.MeshConfig.HTTP:type_name -> hashicorp.consul.internal.configentry.MeshHTTPConfig - 71, // 15: hashicorp.consul.internal.configentry.MeshConfig.Meta:type_name -> hashicorp.consul.internal.configentry.MeshConfig.MetaEntry - 16, // 16: hashicorp.consul.internal.configentry.MeshConfig.Peering:type_name -> hashicorp.consul.internal.configentry.PeeringMeshConfig - 14, // 17: hashicorp.consul.internal.configentry.MeshTLSConfig.Incoming:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig - 14, // 18: hashicorp.consul.internal.configentry.MeshTLSConfig.Outgoing:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig - 72, // 19: hashicorp.consul.internal.configentry.ServiceResolver.Subsets:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry - 19, // 20: hashicorp.consul.internal.configentry.ServiceResolver.Redirect:type_name -> hashicorp.consul.internal.configentry.ServiceResolverRedirect - 73, // 21: hashicorp.consul.internal.configentry.ServiceResolver.Failover:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry - 91, // 22: hashicorp.consul.internal.configentry.ServiceResolver.ConnectTimeout:type_name -> google.protobuf.Duration - 22, // 23: hashicorp.consul.internal.configentry.ServiceResolver.LoadBalancer:type_name -> hashicorp.consul.internal.configentry.LoadBalancer - 74, // 24: hashicorp.consul.internal.configentry.ServiceResolver.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry - 21, // 25: hashicorp.consul.internal.configentry.ServiceResolverFailover.Targets:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailoverTarget - 23, // 26: hashicorp.consul.internal.configentry.LoadBalancer.RingHashConfig:type_name -> hashicorp.consul.internal.configentry.RingHashConfig - 24, // 27: hashicorp.consul.internal.configentry.LoadBalancer.LeastRequestConfig:type_name -> hashicorp.consul.internal.configentry.LeastRequestConfig - 25, // 28: hashicorp.consul.internal.configentry.LoadBalancer.HashPolicies:type_name -> hashicorp.consul.internal.configentry.HashPolicy - 26, // 29: hashicorp.consul.internal.configentry.HashPolicy.CookieConfig:type_name -> hashicorp.consul.internal.configentry.CookieConfig - 91, // 30: hashicorp.consul.internal.configentry.CookieConfig.TTL:type_name -> google.protobuf.Duration - 29, // 31: hashicorp.consul.internal.configentry.IngressGateway.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig - 31, // 32: hashicorp.consul.internal.configentry.IngressGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.IngressListener - 75, // 33: hashicorp.consul.internal.configentry.IngressGateway.Meta:type_name -> hashicorp.consul.internal.configentry.IngressGateway.MetaEntry - 28, // 34: hashicorp.consul.internal.configentry.IngressGateway.Defaults:type_name -> hashicorp.consul.internal.configentry.IngressServiceConfig - 48, // 35: hashicorp.consul.internal.configentry.IngressServiceConfig.PassiveHealthCheck:type_name -> hashicorp.consul.internal.configentry.PassiveHealthCheck - 30, // 36: hashicorp.consul.internal.configentry.GatewayTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - 32, // 37: hashicorp.consul.internal.configentry.IngressListener.Services:type_name -> hashicorp.consul.internal.configentry.IngressService - 29, // 38: hashicorp.consul.internal.configentry.IngressListener.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig - 33, // 39: hashicorp.consul.internal.configentry.IngressService.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayServiceTLSConfig - 34, // 40: hashicorp.consul.internal.configentry.IngressService.RequestHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers - 34, // 41: hashicorp.consul.internal.configentry.IngressService.ResponseHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers - 76, // 42: hashicorp.consul.internal.configentry.IngressService.Meta:type_name -> hashicorp.consul.internal.configentry.IngressService.MetaEntry - 89, // 43: hashicorp.consul.internal.configentry.IngressService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 48, // 44: hashicorp.consul.internal.configentry.IngressService.PassiveHealthCheck:type_name -> hashicorp.consul.internal.configentry.PassiveHealthCheck - 30, // 45: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - 77, // 46: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry - 78, // 47: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry - 36, // 48: hashicorp.consul.internal.configentry.ServiceIntentions.Sources:type_name -> hashicorp.consul.internal.configentry.SourceIntention - 79, // 49: hashicorp.consul.internal.configentry.ServiceIntentions.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry - 1, // 50: hashicorp.consul.internal.configentry.SourceIntention.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction - 37, // 51: hashicorp.consul.internal.configentry.SourceIntention.Permissions:type_name -> hashicorp.consul.internal.configentry.IntentionPermission - 2, // 52: hashicorp.consul.internal.configentry.SourceIntention.Type:type_name -> hashicorp.consul.internal.configentry.IntentionSourceType - 80, // 53: hashicorp.consul.internal.configentry.SourceIntention.LegacyMeta:type_name -> hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry - 92, // 54: hashicorp.consul.internal.configentry.SourceIntention.LegacyCreateTime:type_name -> google.protobuf.Timestamp - 92, // 55: hashicorp.consul.internal.configentry.SourceIntention.LegacyUpdateTime:type_name -> google.protobuf.Timestamp - 89, // 56: hashicorp.consul.internal.configentry.SourceIntention.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 1, // 57: hashicorp.consul.internal.configentry.IntentionPermission.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction - 38, // 58: hashicorp.consul.internal.configentry.IntentionPermission.HTTP:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPPermission - 39, // 59: hashicorp.consul.internal.configentry.IntentionHTTPPermission.Header:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission - 3, // 60: hashicorp.consul.internal.configentry.ServiceDefaults.Mode:type_name -> hashicorp.consul.internal.configentry.ProxyMode - 41, // 61: hashicorp.consul.internal.configentry.ServiceDefaults.TransparentProxy:type_name -> hashicorp.consul.internal.configentry.TransparentProxyConfig - 42, // 62: hashicorp.consul.internal.configentry.ServiceDefaults.MeshGateway:type_name -> hashicorp.consul.internal.configentry.MeshGatewayConfig - 43, // 63: hashicorp.consul.internal.configentry.ServiceDefaults.Expose:type_name -> hashicorp.consul.internal.configentry.ExposeConfig - 45, // 64: hashicorp.consul.internal.configentry.ServiceDefaults.UpstreamConfig:type_name -> hashicorp.consul.internal.configentry.UpstreamConfiguration - 49, // 65: hashicorp.consul.internal.configentry.ServiceDefaults.Destination:type_name -> hashicorp.consul.internal.configentry.DestinationConfig - 81, // 66: hashicorp.consul.internal.configentry.ServiceDefaults.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceDefaults.MetaEntry - 93, // 67: hashicorp.consul.internal.configentry.ServiceDefaults.EnvoyExtensions:type_name -> hashicorp.consul.internal.common.EnvoyExtension - 4, // 68: hashicorp.consul.internal.configentry.MeshGatewayConfig.Mode:type_name -> hashicorp.consul.internal.configentry.MeshGatewayMode - 44, // 69: hashicorp.consul.internal.configentry.ExposeConfig.Paths:type_name -> hashicorp.consul.internal.configentry.ExposePath - 46, // 70: hashicorp.consul.internal.configentry.UpstreamConfiguration.Overrides:type_name -> hashicorp.consul.internal.configentry.UpstreamConfig - 46, // 71: hashicorp.consul.internal.configentry.UpstreamConfiguration.Defaults:type_name -> hashicorp.consul.internal.configentry.UpstreamConfig - 89, // 72: hashicorp.consul.internal.configentry.UpstreamConfig.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 47, // 73: hashicorp.consul.internal.configentry.UpstreamConfig.Limits:type_name -> hashicorp.consul.internal.configentry.UpstreamLimits - 48, // 74: hashicorp.consul.internal.configentry.UpstreamConfig.PassiveHealthCheck:type_name -> hashicorp.consul.internal.configentry.PassiveHealthCheck - 42, // 75: hashicorp.consul.internal.configentry.UpstreamConfig.MeshGateway:type_name -> hashicorp.consul.internal.configentry.MeshGatewayConfig - 91, // 76: hashicorp.consul.internal.configentry.PassiveHealthCheck.Interval:type_name -> google.protobuf.Duration - 82, // 77: hashicorp.consul.internal.configentry.APIGateway.Meta:type_name -> hashicorp.consul.internal.configentry.APIGateway.MetaEntry - 53, // 78: hashicorp.consul.internal.configentry.APIGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.APIGatewayListener - 51, // 79: hashicorp.consul.internal.configentry.APIGateway.Status:type_name -> hashicorp.consul.internal.configentry.Status - 52, // 80: hashicorp.consul.internal.configentry.Status.Conditions:type_name -> hashicorp.consul.internal.configentry.Condition - 55, // 81: hashicorp.consul.internal.configentry.Condition.Resource:type_name -> hashicorp.consul.internal.configentry.ResourceReference - 92, // 82: hashicorp.consul.internal.configentry.Condition.LastTransitionTime:type_name -> google.protobuf.Timestamp - 5, // 83: hashicorp.consul.internal.configentry.APIGatewayListener.Protocol:type_name -> hashicorp.consul.internal.configentry.APIGatewayListenerProtocol - 54, // 84: hashicorp.consul.internal.configentry.APIGatewayListener.TLS:type_name -> hashicorp.consul.internal.configentry.APIGatewayTLSConfiguration - 55, // 85: hashicorp.consul.internal.configentry.APIGatewayTLSConfiguration.Certificates:type_name -> hashicorp.consul.internal.configentry.ResourceReference - 89, // 86: hashicorp.consul.internal.configentry.ResourceReference.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 83, // 87: hashicorp.consul.internal.configentry.BoundAPIGateway.Meta:type_name -> hashicorp.consul.internal.configentry.BoundAPIGateway.MetaEntry - 57, // 88: hashicorp.consul.internal.configentry.BoundAPIGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.BoundAPIGatewayListener - 55, // 89: hashicorp.consul.internal.configentry.BoundAPIGatewayListener.Certificates:type_name -> hashicorp.consul.internal.configentry.ResourceReference - 55, // 90: hashicorp.consul.internal.configentry.BoundAPIGatewayListener.Routes:type_name -> hashicorp.consul.internal.configentry.ResourceReference - 84, // 91: hashicorp.consul.internal.configentry.InlineCertificate.Meta:type_name -> hashicorp.consul.internal.configentry.InlineCertificate.MetaEntry - 85, // 92: hashicorp.consul.internal.configentry.HTTPRoute.Meta:type_name -> hashicorp.consul.internal.configentry.HTTPRoute.MetaEntry - 55, // 93: hashicorp.consul.internal.configentry.HTTPRoute.Parents:type_name -> hashicorp.consul.internal.configentry.ResourceReference - 60, // 94: hashicorp.consul.internal.configentry.HTTPRoute.Rules:type_name -> hashicorp.consul.internal.configentry.HTTPRouteRule - 51, // 95: hashicorp.consul.internal.configentry.HTTPRoute.Status:type_name -> hashicorp.consul.internal.configentry.Status - 65, // 96: hashicorp.consul.internal.configentry.HTTPRouteRule.Filters:type_name -> hashicorp.consul.internal.configentry.HTTPFilters - 61, // 97: hashicorp.consul.internal.configentry.HTTPRouteRule.Matches:type_name -> hashicorp.consul.internal.configentry.HTTPMatch - 68, // 98: hashicorp.consul.internal.configentry.HTTPRouteRule.Services:type_name -> hashicorp.consul.internal.configentry.HTTPService - 62, // 99: hashicorp.consul.internal.configentry.HTTPMatch.Headers:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderMatch - 6, // 100: hashicorp.consul.internal.configentry.HTTPMatch.Method:type_name -> hashicorp.consul.internal.configentry.HTTPMatchMethod - 63, // 101: hashicorp.consul.internal.configentry.HTTPMatch.Path:type_name -> hashicorp.consul.internal.configentry.HTTPPathMatch - 64, // 102: hashicorp.consul.internal.configentry.HTTPMatch.Query:type_name -> hashicorp.consul.internal.configentry.HTTPQueryMatch - 7, // 103: hashicorp.consul.internal.configentry.HTTPHeaderMatch.Match:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderMatchType - 8, // 104: hashicorp.consul.internal.configentry.HTTPPathMatch.Match:type_name -> hashicorp.consul.internal.configentry.HTTPPathMatchType - 9, // 105: hashicorp.consul.internal.configentry.HTTPQueryMatch.Match:type_name -> hashicorp.consul.internal.configentry.HTTPQueryMatchType - 67, // 106: hashicorp.consul.internal.configentry.HTTPFilters.Headers:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderFilter - 66, // 107: hashicorp.consul.internal.configentry.HTTPFilters.URLRewrites:type_name -> hashicorp.consul.internal.configentry.URLRewrite - 86, // 108: hashicorp.consul.internal.configentry.HTTPHeaderFilter.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderFilter.AddEntry - 87, // 109: hashicorp.consul.internal.configentry.HTTPHeaderFilter.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderFilter.SetEntry - 65, // 110: hashicorp.consul.internal.configentry.HTTPService.Filters:type_name -> hashicorp.consul.internal.configentry.HTTPFilters - 89, // 111: hashicorp.consul.internal.configentry.HTTPService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 88, // 112: hashicorp.consul.internal.configentry.TCPRoute.Meta:type_name -> hashicorp.consul.internal.configentry.TCPRoute.MetaEntry - 55, // 113: hashicorp.consul.internal.configentry.TCPRoute.Parents:type_name -> hashicorp.consul.internal.configentry.ResourceReference - 70, // 114: hashicorp.consul.internal.configentry.TCPRoute.Services:type_name -> hashicorp.consul.internal.configentry.TCPService - 51, // 115: hashicorp.consul.internal.configentry.TCPRoute.Status:type_name -> hashicorp.consul.internal.configentry.Status - 89, // 116: hashicorp.consul.internal.configentry.TCPService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 18, // 117: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverSubset - 20, // 118: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailover - 119, // [119:119] is the sub-list for method output_type - 119, // [119:119] is the sub-list for method input_type - 119, // [119:119] is the sub-list for extension type_name - 119, // [119:119] is the sub-list for extension extendee - 0, // [0:119] is the sub-list for field type_name + 58, // 12: hashicorp.consul.internal.configentry.ConfigEntry.InlineCertificate:type_name -> hashicorp.consul.internal.configentry.InlineCertificate + 12, // 13: hashicorp.consul.internal.configentry.MeshConfig.TransparentProxy:type_name -> hashicorp.consul.internal.configentry.TransparentProxyMeshConfig + 13, // 14: hashicorp.consul.internal.configentry.MeshConfig.TLS:type_name -> hashicorp.consul.internal.configentry.MeshTLSConfig + 15, // 15: hashicorp.consul.internal.configentry.MeshConfig.HTTP:type_name -> hashicorp.consul.internal.configentry.MeshHTTPConfig + 71, // 16: hashicorp.consul.internal.configentry.MeshConfig.Meta:type_name -> hashicorp.consul.internal.configentry.MeshConfig.MetaEntry + 16, // 17: hashicorp.consul.internal.configentry.MeshConfig.Peering:type_name -> hashicorp.consul.internal.configentry.PeeringMeshConfig + 14, // 18: hashicorp.consul.internal.configentry.MeshTLSConfig.Incoming:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig + 14, // 19: hashicorp.consul.internal.configentry.MeshTLSConfig.Outgoing:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig + 72, // 20: hashicorp.consul.internal.configentry.ServiceResolver.Subsets:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry + 19, // 21: hashicorp.consul.internal.configentry.ServiceResolver.Redirect:type_name -> hashicorp.consul.internal.configentry.ServiceResolverRedirect + 73, // 22: hashicorp.consul.internal.configentry.ServiceResolver.Failover:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry + 91, // 23: hashicorp.consul.internal.configentry.ServiceResolver.ConnectTimeout:type_name -> google.protobuf.Duration + 22, // 24: hashicorp.consul.internal.configentry.ServiceResolver.LoadBalancer:type_name -> hashicorp.consul.internal.configentry.LoadBalancer + 74, // 25: hashicorp.consul.internal.configentry.ServiceResolver.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry + 91, // 26: hashicorp.consul.internal.configentry.ServiceResolver.RequestTimeout:type_name -> google.protobuf.Duration + 21, // 27: hashicorp.consul.internal.configentry.ServiceResolverFailover.Targets:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailoverTarget + 23, // 28: hashicorp.consul.internal.configentry.LoadBalancer.RingHashConfig:type_name -> hashicorp.consul.internal.configentry.RingHashConfig + 24, // 29: hashicorp.consul.internal.configentry.LoadBalancer.LeastRequestConfig:type_name -> hashicorp.consul.internal.configentry.LeastRequestConfig + 25, // 30: hashicorp.consul.internal.configentry.LoadBalancer.HashPolicies:type_name -> hashicorp.consul.internal.configentry.HashPolicy + 26, // 31: hashicorp.consul.internal.configentry.HashPolicy.CookieConfig:type_name -> hashicorp.consul.internal.configentry.CookieConfig + 91, // 32: hashicorp.consul.internal.configentry.CookieConfig.TTL:type_name -> google.protobuf.Duration + 29, // 33: hashicorp.consul.internal.configentry.IngressGateway.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig + 31, // 34: hashicorp.consul.internal.configentry.IngressGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.IngressListener + 75, // 35: hashicorp.consul.internal.configentry.IngressGateway.Meta:type_name -> hashicorp.consul.internal.configentry.IngressGateway.MetaEntry + 28, // 36: hashicorp.consul.internal.configentry.IngressGateway.Defaults:type_name -> hashicorp.consul.internal.configentry.IngressServiceConfig + 48, // 37: hashicorp.consul.internal.configentry.IngressServiceConfig.PassiveHealthCheck:type_name -> hashicorp.consul.internal.configentry.PassiveHealthCheck + 30, // 38: hashicorp.consul.internal.configentry.GatewayTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + 32, // 39: hashicorp.consul.internal.configentry.IngressListener.Services:type_name -> hashicorp.consul.internal.configentry.IngressService + 29, // 40: hashicorp.consul.internal.configentry.IngressListener.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig + 33, // 41: hashicorp.consul.internal.configentry.IngressService.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayServiceTLSConfig + 34, // 42: hashicorp.consul.internal.configentry.IngressService.RequestHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers + 34, // 43: hashicorp.consul.internal.configentry.IngressService.ResponseHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers + 76, // 44: hashicorp.consul.internal.configentry.IngressService.Meta:type_name -> hashicorp.consul.internal.configentry.IngressService.MetaEntry + 89, // 45: hashicorp.consul.internal.configentry.IngressService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 48, // 46: hashicorp.consul.internal.configentry.IngressService.PassiveHealthCheck:type_name -> hashicorp.consul.internal.configentry.PassiveHealthCheck + 30, // 47: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + 77, // 48: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry + 78, // 49: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry + 36, // 50: hashicorp.consul.internal.configentry.ServiceIntentions.Sources:type_name -> hashicorp.consul.internal.configentry.SourceIntention + 79, // 51: hashicorp.consul.internal.configentry.ServiceIntentions.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry + 1, // 52: hashicorp.consul.internal.configentry.SourceIntention.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction + 37, // 53: hashicorp.consul.internal.configentry.SourceIntention.Permissions:type_name -> hashicorp.consul.internal.configentry.IntentionPermission + 2, // 54: hashicorp.consul.internal.configentry.SourceIntention.Type:type_name -> hashicorp.consul.internal.configentry.IntentionSourceType + 80, // 55: hashicorp.consul.internal.configentry.SourceIntention.LegacyMeta:type_name -> hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry + 92, // 56: hashicorp.consul.internal.configentry.SourceIntention.LegacyCreateTime:type_name -> google.protobuf.Timestamp + 92, // 57: hashicorp.consul.internal.configentry.SourceIntention.LegacyUpdateTime:type_name -> google.protobuf.Timestamp + 89, // 58: hashicorp.consul.internal.configentry.SourceIntention.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 1, // 59: hashicorp.consul.internal.configentry.IntentionPermission.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction + 38, // 60: hashicorp.consul.internal.configentry.IntentionPermission.HTTP:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPPermission + 39, // 61: hashicorp.consul.internal.configentry.IntentionHTTPPermission.Header:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission + 3, // 62: hashicorp.consul.internal.configentry.ServiceDefaults.Mode:type_name -> hashicorp.consul.internal.configentry.ProxyMode + 41, // 63: hashicorp.consul.internal.configentry.ServiceDefaults.TransparentProxy:type_name -> hashicorp.consul.internal.configentry.TransparentProxyConfig + 42, // 64: hashicorp.consul.internal.configentry.ServiceDefaults.MeshGateway:type_name -> hashicorp.consul.internal.configentry.MeshGatewayConfig + 43, // 65: hashicorp.consul.internal.configentry.ServiceDefaults.Expose:type_name -> hashicorp.consul.internal.configentry.ExposeConfig + 45, // 66: hashicorp.consul.internal.configentry.ServiceDefaults.UpstreamConfig:type_name -> hashicorp.consul.internal.configentry.UpstreamConfiguration + 49, // 67: hashicorp.consul.internal.configentry.ServiceDefaults.Destination:type_name -> hashicorp.consul.internal.configentry.DestinationConfig + 81, // 68: hashicorp.consul.internal.configentry.ServiceDefaults.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceDefaults.MetaEntry + 93, // 69: hashicorp.consul.internal.configentry.ServiceDefaults.EnvoyExtensions:type_name -> hashicorp.consul.internal.common.EnvoyExtension + 4, // 70: hashicorp.consul.internal.configentry.MeshGatewayConfig.Mode:type_name -> hashicorp.consul.internal.configentry.MeshGatewayMode + 44, // 71: hashicorp.consul.internal.configentry.ExposeConfig.Paths:type_name -> hashicorp.consul.internal.configentry.ExposePath + 46, // 72: hashicorp.consul.internal.configentry.UpstreamConfiguration.Overrides:type_name -> hashicorp.consul.internal.configentry.UpstreamConfig + 46, // 73: hashicorp.consul.internal.configentry.UpstreamConfiguration.Defaults:type_name -> hashicorp.consul.internal.configentry.UpstreamConfig + 89, // 74: hashicorp.consul.internal.configentry.UpstreamConfig.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 47, // 75: hashicorp.consul.internal.configentry.UpstreamConfig.Limits:type_name -> hashicorp.consul.internal.configentry.UpstreamLimits + 48, // 76: hashicorp.consul.internal.configentry.UpstreamConfig.PassiveHealthCheck:type_name -> hashicorp.consul.internal.configentry.PassiveHealthCheck + 42, // 77: hashicorp.consul.internal.configentry.UpstreamConfig.MeshGateway:type_name -> hashicorp.consul.internal.configentry.MeshGatewayConfig + 91, // 78: hashicorp.consul.internal.configentry.PassiveHealthCheck.Interval:type_name -> google.protobuf.Duration + 82, // 79: hashicorp.consul.internal.configentry.APIGateway.Meta:type_name -> hashicorp.consul.internal.configentry.APIGateway.MetaEntry + 53, // 80: hashicorp.consul.internal.configentry.APIGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.APIGatewayListener + 51, // 81: hashicorp.consul.internal.configentry.APIGateway.Status:type_name -> hashicorp.consul.internal.configentry.Status + 52, // 82: hashicorp.consul.internal.configentry.Status.Conditions:type_name -> hashicorp.consul.internal.configentry.Condition + 55, // 83: hashicorp.consul.internal.configentry.Condition.Resource:type_name -> hashicorp.consul.internal.configentry.ResourceReference + 92, // 84: hashicorp.consul.internal.configentry.Condition.LastTransitionTime:type_name -> google.protobuf.Timestamp + 5, // 85: hashicorp.consul.internal.configentry.APIGatewayListener.Protocol:type_name -> hashicorp.consul.internal.configentry.APIGatewayListenerProtocol + 54, // 86: hashicorp.consul.internal.configentry.APIGatewayListener.TLS:type_name -> hashicorp.consul.internal.configentry.APIGatewayTLSConfiguration + 55, // 87: hashicorp.consul.internal.configentry.APIGatewayTLSConfiguration.Certificates:type_name -> hashicorp.consul.internal.configentry.ResourceReference + 89, // 88: hashicorp.consul.internal.configentry.ResourceReference.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 83, // 89: hashicorp.consul.internal.configentry.BoundAPIGateway.Meta:type_name -> hashicorp.consul.internal.configentry.BoundAPIGateway.MetaEntry + 57, // 90: hashicorp.consul.internal.configentry.BoundAPIGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.BoundAPIGatewayListener + 55, // 91: hashicorp.consul.internal.configentry.BoundAPIGatewayListener.Certificates:type_name -> hashicorp.consul.internal.configentry.ResourceReference + 55, // 92: hashicorp.consul.internal.configentry.BoundAPIGatewayListener.Routes:type_name -> hashicorp.consul.internal.configentry.ResourceReference + 84, // 93: hashicorp.consul.internal.configentry.InlineCertificate.Meta:type_name -> hashicorp.consul.internal.configentry.InlineCertificate.MetaEntry + 85, // 94: hashicorp.consul.internal.configentry.HTTPRoute.Meta:type_name -> hashicorp.consul.internal.configentry.HTTPRoute.MetaEntry + 55, // 95: hashicorp.consul.internal.configentry.HTTPRoute.Parents:type_name -> hashicorp.consul.internal.configentry.ResourceReference + 60, // 96: hashicorp.consul.internal.configentry.HTTPRoute.Rules:type_name -> hashicorp.consul.internal.configentry.HTTPRouteRule + 51, // 97: hashicorp.consul.internal.configentry.HTTPRoute.Status:type_name -> hashicorp.consul.internal.configentry.Status + 65, // 98: hashicorp.consul.internal.configentry.HTTPRouteRule.Filters:type_name -> hashicorp.consul.internal.configentry.HTTPFilters + 61, // 99: hashicorp.consul.internal.configentry.HTTPRouteRule.Matches:type_name -> hashicorp.consul.internal.configentry.HTTPMatch + 68, // 100: hashicorp.consul.internal.configentry.HTTPRouteRule.Services:type_name -> hashicorp.consul.internal.configentry.HTTPService + 62, // 101: hashicorp.consul.internal.configentry.HTTPMatch.Headers:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderMatch + 6, // 102: hashicorp.consul.internal.configentry.HTTPMatch.Method:type_name -> hashicorp.consul.internal.configentry.HTTPMatchMethod + 63, // 103: hashicorp.consul.internal.configentry.HTTPMatch.Path:type_name -> hashicorp.consul.internal.configentry.HTTPPathMatch + 64, // 104: hashicorp.consul.internal.configentry.HTTPMatch.Query:type_name -> hashicorp.consul.internal.configentry.HTTPQueryMatch + 7, // 105: hashicorp.consul.internal.configentry.HTTPHeaderMatch.Match:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderMatchType + 8, // 106: hashicorp.consul.internal.configentry.HTTPPathMatch.Match:type_name -> hashicorp.consul.internal.configentry.HTTPPathMatchType + 9, // 107: hashicorp.consul.internal.configentry.HTTPQueryMatch.Match:type_name -> hashicorp.consul.internal.configentry.HTTPQueryMatchType + 67, // 108: hashicorp.consul.internal.configentry.HTTPFilters.Headers:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderFilter + 66, // 109: hashicorp.consul.internal.configentry.HTTPFilters.URLRewrite:type_name -> hashicorp.consul.internal.configentry.URLRewrite + 86, // 110: hashicorp.consul.internal.configentry.HTTPHeaderFilter.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderFilter.AddEntry + 87, // 111: hashicorp.consul.internal.configentry.HTTPHeaderFilter.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderFilter.SetEntry + 65, // 112: hashicorp.consul.internal.configentry.HTTPService.Filters:type_name -> hashicorp.consul.internal.configentry.HTTPFilters + 89, // 113: hashicorp.consul.internal.configentry.HTTPService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 88, // 114: hashicorp.consul.internal.configentry.TCPRoute.Meta:type_name -> hashicorp.consul.internal.configentry.TCPRoute.MetaEntry + 55, // 115: hashicorp.consul.internal.configentry.TCPRoute.Parents:type_name -> hashicorp.consul.internal.configentry.ResourceReference + 70, // 116: hashicorp.consul.internal.configentry.TCPRoute.Services:type_name -> hashicorp.consul.internal.configentry.TCPService + 51, // 117: hashicorp.consul.internal.configentry.TCPRoute.Status:type_name -> hashicorp.consul.internal.configentry.Status + 89, // 118: hashicorp.consul.internal.configentry.TCPService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 18, // 119: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverSubset + 20, // 120: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailover + 121, // [121:121] is the sub-list for method output_type + 121, // [121:121] is the sub-list for method input_type + 121, // [121:121] is the sub-list for extension type_name + 121, // [121:121] is the sub-list for extension extendee + 0, // [0:121] is the sub-list for field type_name } func init() { file_proto_pbconfigentry_config_entry_proto_init() } @@ -7426,6 +7451,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { (*ConfigEntry_BoundAPIGateway)(nil), (*ConfigEntry_TCPRoute)(nil), (*ConfigEntry_HTTPRoute)(nil), + (*ConfigEntry_InlineCertificate)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/proto/pbconfigentry/config_entry.proto b/proto/pbconfigentry/config_entry.proto index 53d69a64bc7..476842fb671 100644 --- a/proto/pbconfigentry/config_entry.proto +++ b/proto/pbconfigentry/config_entry.proto @@ -37,6 +37,7 @@ message ConfigEntry { BoundAPIGateway BoundAPIGateway = 11; TCPRoute TCPRoute = 12; HTTPRoute HTTPRoute = 13; + InlineCertificate InlineCertificate = 14; } } @@ -120,6 +121,8 @@ message ServiceResolver { google.protobuf.Duration ConnectTimeout = 5; LoadBalancer LoadBalancer = 6; map Meta = 7; + // mog: func-to=structs.DurationFromProto func-from=structs.DurationToProto + google.protobuf.Duration RequestTimeout = 8; } // mog annotation: @@ -805,7 +808,7 @@ message HTTPQueryMatch { // name=Structs message HTTPFilters { repeated HTTPHeaderFilter Headers = 1; - repeated URLRewrite URLRewrites = 2; + URLRewrite URLRewrite = 2; } // mog annotation: @@ -862,8 +865,6 @@ message TCPRoute { // name=Structs message TCPService { string Name = 1; - // mog: func-to=int func-from=int32 - int32 Weight = 2; // mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs - common.EnterpriseMeta EnterpriseMeta = 4; + common.EnterpriseMeta EnterpriseMeta = 2; } diff --git a/sdk/freeport/freeport.go b/sdk/freeport/freeport.go index 6eda1d4279b..c51ef981539 100644 --- a/sdk/freeport/freeport.go +++ b/sdk/freeport/freeport.go @@ -76,6 +76,9 @@ var ( // total is the total number of available ports in the block for use. total int + // seededRand is a random generator that is pre-seeded from the current time. + seededRand *rand.Rand + // stopCh is used to signal to background goroutines to terminate. Only // really exists for the safety of reset() during unit tests. stopCh chan struct{} @@ -114,7 +117,7 @@ func initialize() { panic("freeport: block size too big or too many blocks requested") } - rand.Seed(time.Now().UnixNano()) + seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) // This is compatible with go 1.19 but unnecessary in >= go1.20 firstPort, lockLn = alloc() condNotEmpty = sync.NewCond(&mu) @@ -256,7 +259,7 @@ func adjustMaxBlocks() (int, error) { // be automatically released when the application terminates. func alloc() (int, net.Listener) { for i := 0; i < attempts; i++ { - block := int(rand.Int31n(int32(effectiveMaxBlocks))) + block := int(seededRand.Int31n(int32(effectiveMaxBlocks))) firstPort := lowPort + block*blockSize ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", firstPort)) if err != nil { diff --git a/sdk/go.mod b/sdk/go.mod index b7c2eb01426..8a04b9e1035 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul/sdk -go 1.18 +go 1.19 require ( github.com/hashicorp/go-cleanhttp v0.5.1 diff --git a/test/integration/connect/envoy/Dockerfile-tcpdump b/test/integration/connect/envoy/Dockerfile-tcpdump index 03116e8f5a2..658cd30a233 100644 --- a/test/integration/connect/envoy/Dockerfile-tcpdump +++ b/test/integration/connect/envoy/Dockerfile-tcpdump @@ -1,7 +1,7 @@ -FROM alpine:3.12 +FROM alpine:3.17 RUN apk add --no-cache tcpdump VOLUME [ "/data" ] CMD [ "-w", "/data/all.pcap" ] -ENTRYPOINT [ "/usr/sbin/tcpdump" ] +ENTRYPOINT [ "/usr/bin/tcpdump" ] diff --git a/test/integration/connect/envoy/case-api-gateway-http-hostnames/capture.sh b/test/integration/connect/envoy/case-api-gateway-http-hostnames/capture.sh new file mode 100644 index 00000000000..8ba0e0ddabc --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-hostnames/capture.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 api-gateway primary || true \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-hostnames/service_gateway.hcl b/test/integration/connect/envoy/case-api-gateway-http-hostnames/service_gateway.hcl new file mode 100644 index 00000000000..486c25c59e5 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-hostnames/service_gateway.hcl @@ -0,0 +1,4 @@ +services { + name = "api-gateway" + kind = "api-gateway" +} diff --git a/test/integration/connect/envoy/case-api-gateway-http-hostnames/setup.sh b/test/integration/connect/envoy/case-api-gateway-http-hostnames/setup.sh new file mode 100644 index 00000000000..f4963a2a24f --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-hostnames/setup.sh @@ -0,0 +1,156 @@ +#!/bin/bash + +set -euo pipefail + +upsert_config_entry primary ' +kind = "api-gateway" +name = "api-gateway" +listeners = [ + { + name = "listener-one" + port = 9999 + protocol = "http" + hostname = "*.consul.example" + }, + { + name = "listener-two" + port = 9998 + protocol = "http" + hostname = "foo.bar.baz" + }, + { + name = "listener-three" + port = 9997 + protocol = "http" + hostname = "*.consul.example" + }, + { + name = "listener-four" + port = 9996 + protocol = "http" + hostname = "*.consul.example" + }, + { + name = "listener-five" + port = 9995 + protocol = "http" + hostname = "foo.bar.baz" + } +] +' + +upsert_config_entry primary ' +Kind = "proxy-defaults" +Name = "global" +Config { + protocol = "http" +} +' + +upsert_config_entry primary ' +kind = "http-route" +name = "api-gateway-route-one" +hostnames = ["test.consul.example"] +rules = [ + { + services = [ + { + name = "s1" + } + ] + } +] +parents = [ + { + name = "api-gateway" + sectionName = "listener-one" + }, +] +' + +upsert_config_entry primary ' +kind = "http-route" +name = "api-gateway-route-two" +hostnames = ["foo.bar.baz"] +rules = [ + { + services = [ + { + name = "s1" + } + ] + } +] +parents = [ + { + name = "api-gateway" + sectionName = "listener-two" + }, +] +' + +upsert_config_entry primary ' +kind = "http-route" +name = "api-gateway-route-three" +hostnames = ["foo.bar.baz"] +rules = [ + { + services = [ + { + name = "s1" + } + ] + } +] +parents = [ + { + name = "api-gateway" + sectionName = "listener-three" + }, +] +' + +upsert_config_entry primary ' +kind = "http-route" +name = "api-gateway-route-four" +rules = [ + { + services = [ + { + name = "s1" + } + ] + } +] +parents = [ + { + name = "api-gateway" + sectionName = "listener-four" + }, +] +' + +upsert_config_entry primary ' +kind = "http-route" +name = "api-gateway-route-five" +rules = [ + { + services = [ + { + name = "s1" + } + ] + } +] +parents = [ + { + name = "api-gateway" + sectionName = "listener-five" + }, +] +' + +register_services primary + +gen_envoy_bootstrap api-gateway 20000 primary true +gen_envoy_bootstrap s1 19000 \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-hostnames/vars.sh b/test/integration/connect/envoy/case-api-gateway-http-hostnames/vars.sh new file mode 100644 index 00000000000..38a47d85278 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-hostnames/vars.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES api-gateway-primary" diff --git a/test/integration/connect/envoy/case-api-gateway-http-hostnames/verify.bats b/test/integration/connect/envoy/case-api-gateway-http-hostnames/verify.bats new file mode 100644 index 00000000000..ba109ea6f9d --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-hostnames/verify.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats + +load helpers + +@test "api gateway proxy admin is up on :20000" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "api gateway should have be accepted and not conflicted" { + assert_config_entry_status Accepted True Accepted primary api-gateway api-gateway + assert_config_entry_status Conflicted False NoConflict primary api-gateway api-gateway +} + +@test "api gateway should be bound to route one" { + assert_config_entry_status Bound True Bound primary http-route api-gateway-route-one + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 +} + +@test "api gateway should be bound to route two" { + assert_config_entry_status Bound True Bound primary http-route api-gateway-route-two +} + +@test "api gateway should be unbound to route three" { + assert_config_entry_status Bound False FailedToBind primary http-route api-gateway-route-three +} + +@test "api gateway should be bound to route four" { + assert_config_entry_status Bound True Bound primary http-route api-gateway-route-four +} + +@test "api gateway should be bound to route five" { + assert_config_entry_status Bound True Bound primary http-route api-gateway-route-five +} + +@test "api gateway should be able to connect to s1 via route one with the proper host" { + run retry_long curl -H "Host: test.consul.example" -s -f -d hello localhost:9999 + [ "$status" -eq 0 ] + [[ "$output" == *"hello"* ]] +} + +@test "api gateway should not be able to connect to s1 via route one with a mismatched host" { + run retry_default sh -c "curl -H \"Host: foo.consul.example\" -sI -o /dev/null -w \"%{http_code}\" localhost:9999 | grep 404" + [ "$status" -eq 0 ] + [[ "$output" == "404" ]] +} + +@test "api gateway should be able to connect to s1 via route two with the proper host" { + run retry_long curl -H "Host: foo.bar.baz" -s -f -d hello localhost:9998 + [ "$status" -eq 0 ] + [[ "$output" == *"hello"* ]] +} + +@test "api gateway should be able to connect to s1 via route four with any subdomain of the listener host" { + run retry_long curl -H "Host: test.consul.example" -s -f -d hello localhost:9996 + [ "$status" -eq 0 ] + [[ "$output" == *"hello"* ]] + run retry_long curl -H "Host: foo.consul.example" -s -f -d hello localhost:9996 + [ "$status" -eq 0 ] + [[ "$output" == *"hello"* ]] +} + +@test "api gateway should be able to connect to s1 via route five with the proper host" { + run retry_long curl -H "Host: foo.bar.baz" -s -f -d hello localhost:9995 + [ "$status" -eq 0 ] + [[ "$output" == *"hello"* ]] +} \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-simple/setup.sh b/test/integration/connect/envoy/case-api-gateway-http-simple/setup.sh index 8d0513553de..6dab478e203 100644 --- a/test/integration/connect/envoy/case-api-gateway-http-simple/setup.sh +++ b/test/integration/connect/envoy/case-api-gateway-http-simple/setup.sh @@ -35,6 +35,10 @@ rules = [ services = [ { name = "s1" + }, + { + name = "s2" + weight = 2 } ] } diff --git a/test/integration/connect/envoy/case-api-gateway-http-simple/verify.bats b/test/integration/connect/envoy/case-api-gateway-http-simple/verify.bats index c7378e55bfe..72686b3c4f2 100644 --- a/test/integration/connect/envoy/case-api-gateway-http-simple/verify.bats +++ b/test/integration/connect/envoy/case-api-gateway-http-simple/verify.bats @@ -22,9 +22,9 @@ load helpers } @test "api gateway should be able to connect to s1 via configured port" { - run retry_long curl -s -f -d hello localhost:9999 + run retry_long curl -s -d hello localhost:9999 [ "$status" -eq 0 ] - [[ "$output" == *"hello"* ]] + [[ ! -z "$output" ]] } @test "api gateway should get an intentions error connecting to s2 via configured port" { diff --git a/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/capture.sh b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/capture.sh new file mode 100644 index 00000000000..8ba0e0ddabc --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/capture.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 api-gateway primary || true \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/service_gateway.hcl b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/service_gateway.hcl new file mode 100644 index 00000000000..486c25c59e5 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/service_gateway.hcl @@ -0,0 +1,4 @@ +services { + name = "api-gateway" + kind = "api-gateway" +} diff --git a/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/service_s3.hcl b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/service_s3.hcl new file mode 100644 index 00000000000..2f6d05e0feb --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/service_s3.hcl @@ -0,0 +1,9 @@ +services { + id = "s3" + name = "s3" + port = 8182 + + connect { + sidecar_service {} + } +} diff --git a/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/setup.sh b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/setup.sh new file mode 100644 index 00000000000..47916da173c --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/setup.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +set -euo pipefail + +upsert_config_entry primary ' +kind = "api-gateway" +name = "api-gateway" +listeners = [ + { + name = "listener-one" + port = 9999 + protocol = "http" + } +] +' + +upsert_config_entry primary ' +Kind = "proxy-defaults" +Name = "global" +Config { + protocol = "http" +} +' + +upsert_config_entry primary ' +kind = "http-route" +name = "api-gateway-route-one" +rules = [ + { + services = [ + { + name = "splitter-one" + } + ] + } +] +parents = [ + { + name = "api-gateway" + sectionName = "listener-one" + } +] +' + +upsert_config_entry primary ' +kind = "service-splitter" +name = "splitter-one" +splits = [ + { + weight = 50, + service = "s1" + }, + { + weight = 50, + service = "splitter-two" + }, +] +' + +upsert_config_entry primary ' +kind = "service-splitter" +name = "splitter-two" +splits = [ + { + weight = 50, + service = "s2" + }, + { + weight = 50, + service = "s3" + }, +] +' + +register_services primary + +gen_envoy_bootstrap api-gateway 20000 primary true +gen_envoy_bootstrap s1 19000 +gen_envoy_bootstrap s2 19001 +gen_envoy_bootstrap s3 19002 \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/vars.sh b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/vars.sh new file mode 100644 index 00000000000..38a47d85278 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/vars.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES api-gateway-primary" diff --git a/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/verify.bats b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/verify.bats new file mode 100644 index 00000000000..50d447516b1 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-splitter-targets/verify.bats @@ -0,0 +1,23 @@ +#!/usr/bin/env bats + +load helpers + +@test "api gateway proxy admin is up on :20000" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "api gateway should be accepted and not conflicted" { + assert_config_entry_status Accepted True Accepted primary api-gateway api-gateway + assert_config_entry_status Conflicted False NoConflict primary api-gateway api-gateway +} + +@test "api gateway should have healthy endpoints for s1" { + assert_config_entry_status Bound True Bound primary http-route api-gateway-route-one + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 +} + +@test "api gateway should be able to connect to s1, s2, and s3 via configured port" { + run retry_default assert_expected_fortio_name_pattern ^FORTIO_NAME=s1$ + run retry_default assert_expected_fortio_name_pattern ^FORTIO_NAME=s2$ + run retry_default assert_expected_fortio_name_pattern ^FORTIO_NAME=s3$ +} \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/capture.sh b/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/capture.sh new file mode 100644 index 00000000000..8ba0e0ddabc --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/capture.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 api-gateway primary || true \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/service_gateway.hcl b/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/service_gateway.hcl new file mode 100644 index 00000000000..486c25c59e5 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/service_gateway.hcl @@ -0,0 +1,4 @@ +services { + name = "api-gateway" + kind = "api-gateway" +} diff --git a/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/setup.sh b/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/setup.sh new file mode 100644 index 00000000000..2b2cbd160fa --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-http-tls-overlapping-hosts/setup.sh @@ -0,0 +1,287 @@ +#!/bin/bash + +set -euo pipefail + +upsert_config_entry primary ' +kind = "api-gateway" +name = "api-gateway" +listeners = [ + { + name = "listener-one" + port = 9999 + protocol = "http" + tls = { + certificates = [ + { + kind = "inline-certificate" + name = "host-consul-example" + } + ] + } + }, + { + name = "listener-two" + port = 9998 + protocol = "http" + tls = { + certificates = [ + { + kind = "inline-certificate" + name = "host-consul-example" + }, + { + kind = "inline-certificate" + name = "also-host-consul-example" + }, + { + kind = "inline-certificate" + name = "other-consul-example" + } + ] + } + } +] +' + +upsert_config_entry primary ' +kind = "inline-certificate" +name = "host-consul-example" +private_key = </dev/null) + + echo "WANT CN: ${CN} (SNI: ${SERVER_NAME})" + echo "GOT CERT:" + echo "$CERT" + + echo "$CERT" | grep "CN = ${CN}" +} + function assert_envoy_version { local ADMINPORT=$1 run retry_default curl -f -s localhost:$ADMINPORT/server_info @@ -347,6 +361,39 @@ function get_upstream_endpoint { | select(.name|startswith(\"${CLUSTER_NAME}\"))" } +function get_upstream_endpoint_port { + local HOSTPORT=$1 + local CLUSTER_NAME=$2 + local PORT_VALUE=$3 + run curl -s -f "http://${HOSTPORT}/clusters?format=json" + [ "$status" -eq 0 ] + echo "$output" | jq --raw-output " +.cluster_statuses[] +| select(.name|startswith(\"${CLUSTER_NAME}\")) +| [.host_statuses[].address.socket_address.port_value] +| [select(.[] == ${PORT_VALUE})] +| length" +} + +function assert_upstream_has_endpoint_port_once { + local HOSTPORT=$1 + local CLUSTER_NAME=$2 + local PORT_VALUE=$3 + + GOT_COUNT=$(get_upstream_endpoint_port $HOSTPORT $CLUSTER_NAME $PORT_VALUE) + + [ "$GOT_COUNT" -eq 1 ] +} + +function assert_upstream_has_endpoint_port { + local HOSTPORT=$1 + local CLUSTER_NAME=$2 + local PORT_VALUE=$3 + + run retry_long assert_upstream_has_endpoint_port_once $HOSTPORT $CLUSTER_NAME $PORT_VALUE + [ "$status" -eq 0 ] +} + function get_upstream_endpoint_in_status_count { local HOSTPORT=$1 local CLUSTER_NAME=$2 @@ -369,15 +416,27 @@ function assert_upstream_has_endpoints_in_status_once { GOT_COUNT=$(get_upstream_endpoint_in_status_count $HOSTPORT $CLUSTER_NAME $HEALTH_STATUS) + echo "GOT: $GOT_COUNT" [ "$GOT_COUNT" -eq $EXPECT_COUNT ] } +function assert_upstream_missing_once { + local HOSTPORT=$1 + local CLUSTER_NAME=$2 + + run get_upstream_endpoint $HOSTPORT $CLUSTER_NAME + [ "$status" -eq 0 ] + echo "$output" + [ "" == "$output" ] +} + function assert_upstream_missing { local HOSTPORT=$1 local CLUSTER_NAME=$2 - run retry_default get_upstream_endpoint $HOSTPORT $CLUSTER_NAME + run retry_long assert_upstream_missing_once $HOSTPORT $CLUSTER_NAME echo "OUTPUT: $output $status" - [ "" == "$output" ] + + [ "$status" -eq 0 ] } function assert_upstream_has_endpoints_in_status { @@ -386,6 +445,8 @@ function assert_upstream_has_endpoints_in_status { local HEALTH_STATUS=$3 local EXPECT_COUNT=$4 run retry_long assert_upstream_has_endpoints_in_status_once $HOSTPORT $CLUSTER_NAME $HEALTH_STATUS $EXPECT_COUNT + echo "$output" + [ "$status" -eq 0 ] } @@ -570,7 +631,7 @@ function docker_consul_for_proxy_bootstrap { function docker_wget { local DC=$1 shift 1 - docker run --rm --network container:envoy_consul-${DC}_1 docker.mirror.hashicorp.services/alpine:3.9 wget "$@" + docker run --rm --network container:envoy_consul-${DC}_1 docker.mirror.hashicorp.services/alpine:3.17 wget "$@" } function docker_curl { diff --git a/test/integration/consul-container/go.mod b/test/integration/consul-container/go.mod index a88a6aff0b3..35174f5fd0b 100644 --- a/test/integration/consul-container/go.mod +++ b/test/integration/consul-container/go.mod @@ -1,13 +1,13 @@ module github.com/hashicorp/consul/test/integration/consul-container -go 1.19 +go 1.20 require ( github.com/avast/retry-go v3.0.0+incompatible github.com/docker/docker v20.10.22+incompatible github.com/docker/go-connections v0.4.0 - github.com/hashicorp/consul/api v1.18.0 - github.com/hashicorp/consul/sdk v0.13.0 + github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-uuid v1.0.2 @@ -20,7 +20,6 @@ require ( github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 github.com/testcontainers/testcontainers-go v0.15.0 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 - gotest.tools v2.2.0+incompatible ) require ( @@ -39,7 +38,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.0 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.2.1 // indirect diff --git a/test/integration/consul-container/go.sum b/test/integration/consul-container/go.sum index 029b5645d7c..e554f8ad3f7 100644 --- a/test/integration/consul-container/go.sum +++ b/test/integration/consul-container/go.sum @@ -387,7 +387,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/test/integration/consul-container/libs/assert/envoy.go b/test/integration/consul-container/libs/assert/envoy.go index e62118c4f1d..1fdf61feae3 100644 --- a/test/integration/consul-container/libs/assert/envoy.go +++ b/test/integration/consul-container/libs/assert/envoy.go @@ -11,6 +11,7 @@ import ( "time" "github.com/hashicorp/consul/sdk/testutil/retry" + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" "github.com/hashicorp/go-cleanhttp" "github.com/stretchr/testify/assert" @@ -70,7 +71,11 @@ func AssertUpstreamEndpointStatus(t *testing.T, adminPort int, clusterName, heal filter := fmt.Sprintf(`.cluster_statuses[] | select(.name|contains("%s")) | [.host_statuses[].health_status.eds_health_status] | [select(.[] == "%s")] | length`, clusterName, healthStatus) results, err := utils.JQFilter(clusters, filter) require.NoErrorf(r, err, "could not found cluster name %s", clusterName) - require.Equal(r, count, len(results)) + + resultToString := strings.Join(results, " ") + result, err := strconv.Atoi(resultToString) + assert.NoError(r, err) + require.Equal(r, count, result) }) } @@ -127,7 +132,7 @@ func AssertEnvoyMetricAtLeast(t *testing.T, adminPort int, prefix, metric string err error ) failer := func() *retry.Timer { - return &retry.Timer{Timeout: 30 * time.Second, Wait: 500 * time.Millisecond} + return &retry.Timer{Timeout: 60 * time.Second, Wait: 500 * time.Millisecond} } retry.RunWith(failer(), t, func(r *retry.R) { @@ -213,6 +218,23 @@ func AssertEnvoyPresentsCertURI(t *testing.T, port int, serviceName string) { } } +// AssertEnvoyRunning assert the envoy is running by querying its stats page +func AssertEnvoyRunning(t *testing.T, port int) { + var ( + err error + ) + failer := func() *retry.Timer { + return &retry.Timer{Timeout: 10 * time.Second, Wait: 500 * time.Millisecond} + } + + retry.RunWith(failer(), t, func(r *retry.R) { + _, _, err = GetEnvoyOutput(port, "stats", nil) + if err != nil { + r.Fatal("could not fetch envoy stats") + } + }) +} + func GetEnvoyOutput(port int, path string, query map[string]string) (string, int, error) { client := cleanhttp.DefaultClient() var u url.URL @@ -251,3 +273,20 @@ func sanitizeResult(s string) []string { result := strings.Split(strings.ReplaceAll(s, `,`, " "), " ") return append(result[:0], result[1:]...) } + +// AssertServiceHasHealthyInstances asserts the number of instances of service equals count for a given service. +// https://developer.hashicorp.com/consul/docs/connect/config-entries/service-resolver#onlypassing +func AssertServiceHasHealthyInstances(t *testing.T, node libcluster.Agent, service string, onlypassing bool, count int) { + failer := func() *retry.Timer { + return &retry.Timer{Timeout: 10 * time.Second, Wait: 500 * time.Millisecond} + } + + retry.RunWith(failer(), t, func(r *retry.R) { + services, _, err := node.GetClient().Health().Service(service, "", onlypassing, nil) + require.NoError(r, err) + for _, v := range services { + fmt.Printf("%s service status: %s\n", v.Service.ID, v.Checks.AggregatedStatus()) + } + require.Equal(r, count, len(services)) + }) +} diff --git a/test/integration/consul-container/libs/assert/service.go b/test/integration/consul-container/libs/assert/service.go index ba46821ffdc..d12c3eb0fda 100644 --- a/test/integration/consul-container/libs/assert/service.go +++ b/test/integration/consul-container/libs/assert/service.go @@ -24,9 +24,9 @@ const ( ) // CatalogServiceExists verifies the service name exists in the Consul catalog -func CatalogServiceExists(t *testing.T, c *api.Client, svc string) { +func CatalogServiceExists(t *testing.T, c *api.Client, svc string, opts *api.QueryOptions) { retry.Run(t, func(r *retry.R) { - services, _, err := c.Catalog().Service(svc, "", nil) + services, _, err := c.Catalog().Service(svc, "", opts) if err != nil { r.Fatal("error reading service data") } @@ -49,9 +49,17 @@ func CatalogNodeExists(t *testing.T, c *api.Client, nodeName string) { }) } +func HTTPServiceEchoes(t *testing.T, ip string, port int, path string) { + doHTTPServiceEchoes(t, ip, port, path, nil) +} + +func HTTPServiceEchoesResHeader(t *testing.T, ip string, port int, path string, expectedResHeader map[string]string) { + doHTTPServiceEchoes(t, ip, port, path, expectedResHeader) +} + // HTTPServiceEchoes verifies that a post to the given ip/port combination returns the data // in the response body. Optional path can be provided to differentiate requests. -func HTTPServiceEchoes(t *testing.T, ip string, port int, path string) { +func doHTTPServiceEchoes(t *testing.T, ip string, port int, path string, expectedResHeader map[string]string) { const phrase = "hello" failer := func() *retry.Timer { @@ -82,6 +90,24 @@ func HTTPServiceEchoes(t *testing.T, ip string, port int, path string) { if !strings.Contains(string(body), phrase) { r.Fatal("received an incorrect response ", string(body)) } + + for k, v := range expectedResHeader { + if headerValues, ok := res.Header[k]; !ok { + r.Fatal("expected header not found", k) + } else { + found := false + for _, value := range headerValues { + if value == v { + found = true + break + } + } + + if !found { + r.Fatalf("header %s value not match want %s got %s ", k, v, headerValues) + } + } + } }) } @@ -96,22 +122,22 @@ func ServiceLogContains(t *testing.T, service libservice.Service, target string) // has a `FORTIO_NAME` env variable set. This validates that the client is sending // traffic to the right envoy proxy. // +// If reqHost is set, the Host field of the HTTP request will be set to its value. +// // It retries with timeout defaultHTTPTimeout and wait defaultHTTPWait. -func AssertFortioName(t *testing.T, urlbase string, name string) { +func AssertFortioName(t *testing.T, urlbase string, name string, reqHost string) { t.Helper() var fortioNameRE = regexp.MustCompile(("\nFORTIO_NAME=(.+)\n")) - client := &http.Client{ - Transport: &http.Transport{ - DisableKeepAlives: true, - }, - } + client := cleanhttp.DefaultClient() retry.RunWith(&retry.Timer{Timeout: defaultHTTPTimeout, Wait: defaultHTTPWait}, t, func(r *retry.R) { fullurl := fmt.Sprintf("%s/debug?env=dump", urlbase) - t.Logf("making call to %s", fullurl) req, err := http.NewRequest("GET", fullurl, nil) if err != nil { r.Fatal("could not make request to service ", fullurl) } + if reqHost != "" { + req.Host = reqHost + } resp, err := client.Do(req) if err != nil { diff --git a/test/integration/consul-container/libs/cluster/agent.go b/test/integration/consul-container/libs/cluster/agent.go index 24c5b37bd5f..8dfa496d8bf 100644 --- a/test/integration/consul-container/libs/cluster/agent.go +++ b/test/integration/consul-container/libs/cluster/agent.go @@ -17,11 +17,13 @@ type Agent interface { NewClient(string, bool) (*api.Client, error) GetName() string GetAgentName() string + GetPartition() string GetPod() testcontainers.Container ClaimAdminPort() (int, error) GetConfig() Config GetInfo() AgentInfo GetDatacenter() string + GetNetwork() string IsServer() bool RegisterTermination(func() error) Terminate() error diff --git a/test/integration/consul-container/libs/cluster/builder.go b/test/integration/consul-container/libs/cluster/builder.go index f08b727d04b..2807985e17e 100644 --- a/test/integration/consul-container/libs/cluster/builder.go +++ b/test/integration/consul-container/libs/cluster/builder.go @@ -20,6 +20,13 @@ const ( ConsulCACertKey = "consul-agent-ca-key.pem" ) +type LogStore string + +const ( + LogStore_WAL LogStore = "wal" + LogStore_BoltDB LogStore = "boltdb" +) + // BuildContext provides a reusable object meant to share common configuration settings // between agent configuration builders. type BuildContext struct { @@ -41,6 +48,7 @@ type BuildContext struct { tlsCertIndex int // keeps track of the certificates issued for naming purposes aclEnabled bool + logStore LogStore } func (c *BuildContext) DockerImage() string { @@ -89,6 +97,9 @@ type BuildOptions struct { // ACLEnabled configures acl in agent configuration ACLEnabled bool + + //StoreLog define which LogStore to use + LogStore LogStore } func NewBuildContext(t *testing.T, opts BuildOptions) *BuildContext { @@ -103,6 +114,7 @@ func NewBuildContext(t *testing.T, opts BuildOptions) *BuildContext { useAPIWithTLS: opts.UseAPIWithTLS, useGRPCWithTLS: opts.UseGRPCWithTLS, aclEnabled: opts.ACLEnabled, + logStore: opts.LogStore, } if ctx.consulImageName == "" { @@ -202,6 +214,18 @@ func NewConfigBuilder(ctx *BuildContext) *Builder { b.conf.Set("acl.enable_token_persistence", true) } + ls := string(ctx.logStore) + if ls != "" && (ctx.consulVersion == "local" || + semver.Compare("v"+ctx.consulVersion, "v1.15.0") >= 0) { + // Enable logstore backend for version after v1.15.0 + if ls != string(LogStore_WAL) && ls != string(LogStore_BoltDB) { + ls = string(LogStore_BoltDB) + } + b.conf.Set("raft_logstore.backend", ls) + } else { + b.conf.Unset("raft_logstore.backend") + } + return b } @@ -245,6 +269,11 @@ func (b *Builder) Peering(enable bool) *Builder { return b } +func (b *Builder) Partition(name string) *Builder { + b.conf.Set("partition", name) + return b +} + func (b *Builder) RetryJoin(names ...string) *Builder { b.conf.Set("retry_join", names) return b diff --git a/test/integration/consul-container/libs/cluster/cluster.go b/test/integration/consul-container/libs/cluster/cluster.go index 2ec57a67173..9f80ee573fe 100644 --- a/test/integration/consul-container/libs/cluster/cluster.go +++ b/test/integration/consul-container/libs/cluster/cluster.go @@ -63,10 +63,10 @@ func NewN(t TestingT, conf Config, count int) (*Cluster, error) { // // The provided TestingT is used to register a cleanup function to terminate // the cluster. -func New(t TestingT, configs []Config) (*Cluster, error) { +func New(t TestingT, configs []Config, ports ...int) (*Cluster, error) { id, err := shortid.Generate() if err != nil { - return nil, fmt.Errorf("could not cluster id: %w", err) + return nil, fmt.Errorf("could not generate cluster id: %w", err) } name := fmt.Sprintf("consul-int-cluster-%s", id) @@ -99,7 +99,7 @@ func New(t TestingT, configs []Config) (*Cluster, error) { _ = cluster.Terminate() }) - if err := cluster.Add(configs, true); err != nil { + if err := cluster.Add(configs, true, ports...); err != nil { return nil, fmt.Errorf("could not start or join all agents: %w", err) } @@ -114,8 +114,8 @@ func (c *Cluster) AddN(conf Config, count int, join bool) error { return c.Add(configs, join) } -// Add starts an agent with the given configuration and joins it with the existing cluster -func (c *Cluster) Add(configs []Config, serfJoin bool) (xe error) { +// Add starts agents with the given configurations and joins them to the existing cluster +func (c *Cluster) Add(configs []Config, serfJoin bool, ports ...int) (xe error) { if c.Index == 0 && !serfJoin { return fmt.Errorf("the first call to Cluster.Add must have serfJoin=true") } @@ -125,19 +125,20 @@ func (c *Cluster) Add(configs []Config, serfJoin bool) (xe error) { // Each agent gets it's own area in the cluster scratch. conf.ScratchDir = filepath.Join(c.ScratchDir, strconv.Itoa(c.Index)) if err := os.MkdirAll(conf.ScratchDir, 0777); err != nil { - return err + return fmt.Errorf("container %d: %w", idx, err) } if err := os.Chmod(conf.ScratchDir, 0777); err != nil { - return err + return fmt.Errorf("container %d: %w", idx, err) } n, err := NewConsulContainer( context.Background(), conf, c, + ports..., ) if err != nil { - return fmt.Errorf("could not add container index %d: %w", idx, err) + return fmt.Errorf("container %d: %w", idx, err) } agents = append(agents, n) c.Index++ @@ -160,9 +161,11 @@ func (c *Cluster) Add(configs []Config, serfJoin bool) (xe error) { func (c *Cluster) Join(agents []Agent) error { return c.join(agents, false) } + func (c *Cluster) JoinExternally(agents []Agent) error { return c.join(agents, true) } + func (c *Cluster) join(agents []Agent, skipSerfJoin bool) error { if len(agents) == 0 { return nil // no change @@ -312,6 +315,16 @@ func (c *Cluster) StandardUpgrade(t *testing.T, ctx context.Context, targetVersi } t.Logf("The number of followers = %d", len(followers)) + // NOTE: we only assert the number of agents in default partition + // TODO: add partition to the cluster struct to assert partition size + clusterSize := 0 + for _, agent := range c.Agents { + if agent.GetPartition() == "" || agent.GetPartition() == "default" { + clusterSize++ + } + } + t.Logf("The number of agents in default partition = %d", clusterSize) + upgradeFn := func(agent Agent, clientFactory func() (*api.Client, error)) error { config := agent.GetConfig() config.Version = targetVersion @@ -346,8 +359,10 @@ func (c *Cluster) StandardUpgrade(t *testing.T, ctx context.Context, targetVersi return err } - // wait until the agent rejoin and leader is elected - WaitForMembers(t, client, len(c.Agents)) + // wait until the agent rejoin and leader is elected; skip non-default agent + if agent.GetPartition() == "" || agent.GetPartition() == "default" { + WaitForMembers(t, client, clusterSize) + } WaitForLeader(t, c, client) return nil @@ -475,7 +490,23 @@ func (c *Cluster) Servers() []Agent { return servers } -// Clients returns the handle to client agents +// Clients returns the handle to client agents in provided partition +func (c *Cluster) ClientsInPartition(partition string) []Agent { + var clients []Agent + + for _, n := range c.Agents { + if n.IsServer() { + continue + } + + if n.GetPartition() == partition { + clients = append(clients, n) + } + } + return clients +} + +// Clients returns the handle to client agents in all partitions func (c *Cluster) Clients() []Agent { var clients []Agent @@ -600,6 +631,20 @@ func (c *Cluster) ConfigEntryWrite(entry api.ConfigEntry) error { return err } +func (c *Cluster) ConfigEntryDelete(entry api.ConfigEntry) error { + client, err := c.GetClient(nil, true) + if err != nil { + return err + } + + entries := client.ConfigEntries() + _, err = entries.Delete(entry.GetKind(), entry.GetName(), nil) + if err != nil { + return fmt.Errorf("error deleting config entry: %v", err) + } + return err +} + func extractSecretIDFrom(tokenOutput string) (string, error) { lines := strings.Split(tokenOutput, "\n") for _, line := range lines { diff --git a/test/integration/consul-container/libs/cluster/container.go b/test/integration/consul-container/libs/cluster/container.go index c9ce7792b6d..4e3e1c39901 100644 --- a/test/integration/consul-container/libs/cluster/container.go +++ b/test/integration/consul-container/libs/cluster/container.go @@ -26,8 +26,9 @@ const bootLogLine = "Consul agent running" const disableRYUKEnv = "TESTCONTAINERS_RYUK_DISABLED" // Exposed ports info -const MaxEnvoyOnNode = 10 // the max number of Envoy sidecar can run along with the agent, base is 19000 -const ServiceUpstreamLocalBindPort = 5000 // local bind Port of service's upstream +const MaxEnvoyOnNode = 10 // the max number of Envoy sidecar can run along with the agent, base is 19000 +const ServiceUpstreamLocalBindPort = 5000 // local bind Port of service's upstream +const ServiceUpstreamLocalBindPort2 = 5001 // local bind Port of service's upstream, for services with 2 upstreams // consulContainerNode implements the Agent interface by running a Consul agent // in a container. @@ -37,6 +38,7 @@ type consulContainerNode struct { container testcontainers.Container serverMode bool datacenter string + partition string config Config podReq testcontainers.ContainerRequest consulReq testcontainers.ContainerRequest @@ -72,7 +74,7 @@ func (c *consulContainerNode) ClaimAdminPort() (int, error) { } // NewConsulContainer starts a Consul agent in a container with the given config. -func NewConsulContainer(ctx context.Context, config Config, cluster *Cluster) (Agent, error) { +func NewConsulContainer(ctx context.Context, config Config, cluster *Cluster, ports ...int) (Agent, error) { network := cluster.NetworkName index := cluster.Index if config.ScratchDir == "" { @@ -127,7 +129,7 @@ func NewConsulContainer(ctx context.Context, config Config, cluster *Cluster) (A addtionalNetworks: []string{"bridge", network}, hostname: fmt.Sprintf("agent-%d", index), } - podReq, consulReq := newContainerRequest(config, opts) + podReq, consulReq := newContainerRequest(config, opts, ports...) // Do some trickery to ensure that partial completion is correctly torn // down, but successful execution is not. @@ -152,11 +154,17 @@ func NewConsulContainer(ctx context.Context, config Config, cluster *Cluster) (A info AgentInfo ) if httpPort > 0 { - uri, err := podContainer.PortEndpoint(ctx, "8500", "http") + for i := 0; i < 10; i++ { + uri, err := podContainer.PortEndpoint(ctx, "8500", "http") + if err != nil { + time.Sleep(500 * time.Millisecond) + continue + } + clientAddr = uri + } if err != nil { return nil, err } - clientAddr = uri } else if httpsPort > 0 { uri, err := podContainer.PortEndpoint(ctx, "8501", "https") @@ -227,6 +235,7 @@ func NewConsulContainer(ctx context.Context, config Config, cluster *Cluster) (A container: consulContainer, serverMode: pc.Server, datacenter: pc.Datacenter, + partition: pc.Partition, ctx: ctx, podReq: podReq, consulReq: consulReq, @@ -290,6 +299,10 @@ func NewConsulContainer(ctx context.Context, config Config, cluster *Cluster) (A return node, nil } +func (c *consulContainerNode) GetNetwork() string { + return c.network +} + func (c *consulContainerNode) GetName() string { if c.container == nil { return c.consulReq.Name // TODO: is this safe to do all the time? @@ -313,6 +326,10 @@ func (c *consulContainerNode) GetDatacenter() string { return c.datacenter } +func (c *consulContainerNode) GetPartition() string { + return c.partition +} + func (c *consulContainerNode) IsServer() bool { return c.serverMode } @@ -488,7 +505,7 @@ func startContainer(ctx context.Context, req testcontainers.ContainerRequest) (t }) } -const pauseImage = "k8s.gcr.io/pause:3.3" +const pauseImage = "registry.k8s.io/pause:3.3" type containerOpts struct { configFile string @@ -500,7 +517,7 @@ type containerOpts struct { addtionalNetworks []string } -func newContainerRequest(config Config, opts containerOpts) (podRequest, consulRequest testcontainers.ContainerRequest) { +func newContainerRequest(config Config, opts containerOpts, ports ...int) (podRequest, consulRequest testcontainers.ContainerRequest) { skipReaper := isRYUKDisabled() pod := testcontainers.ContainerRequest{ @@ -517,10 +534,12 @@ func newContainerRequest(config Config, opts containerOpts) (podRequest, consulR "8079/tcp", // Envoy App Listener - grpc port used by static-server "8078/tcp", // Envoy App Listener - grpc port used by static-server-v1 "8077/tcp", // Envoy App Listener - grpc port used by static-server-v2 + "8076/tcp", // Envoy App Listener - grpc port used by static-server-v3 "8080/tcp", // Envoy App Listener - http port used by static-server "8081/tcp", // Envoy App Listener - http port used by static-server-v1 "8082/tcp", // Envoy App Listener - http port used by static-server-v2 + "8083/tcp", // Envoy App Listener - http port used by static-server-v3 "9998/tcp", // Envoy App Listener "9999/tcp", // Envoy App Listener }, @@ -530,6 +549,7 @@ func newContainerRequest(config Config, opts containerOpts) (podRequest, consulR // Envoy upstream listener pod.ExposedPorts = append(pod.ExposedPorts, fmt.Sprintf("%d/tcp", ServiceUpstreamLocalBindPort)) + pod.ExposedPorts = append(pod.ExposedPorts, fmt.Sprintf("%d/tcp", ServiceUpstreamLocalBindPort2)) // Reserve the exposed ports for Envoy admin port, e.g., 19000 - 19009 basePort := 19000 @@ -537,6 +557,10 @@ func newContainerRequest(config Config, opts containerOpts) (podRequest, consulR pod.ExposedPorts = append(pod.ExposedPorts, fmt.Sprintf("%d/tcp", basePort+i)) } + for _, port := range ports { + pod.ExposedPorts = append(pod.ExposedPorts, fmt.Sprintf("%d/tcp", port)) + } + // For handshakes like auto-encrypt, it can take 10's of seconds for the agent to become "ready". // If we only wait until the log stream starts, subsequent commands to agents will fail. // TODO: optimize the wait strategy @@ -629,6 +653,7 @@ type parsedConfig struct { Datacenter string `json:"datacenter"` Server bool `json:"server"` Ports parsedPorts `json:"ports"` + Partition string `json:"partition"` } type parsedPorts struct { diff --git a/test/integration/consul-container/libs/service/connect.go b/test/integration/consul-container/libs/service/connect.go index 3fa9bcbde25..a7385f2e3a0 100644 --- a/test/integration/consul-container/libs/service/connect.go +++ b/test/integration/consul-container/libs/service/connect.go @@ -2,6 +2,7 @@ package service import ( "context" + "errors" "fmt" "io" "path/filepath" @@ -14,31 +15,54 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" - libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" ) // ConnectContainer type ConnectContainer struct { - ctx context.Context - container testcontainers.Container - ip string - appPort int - adminPort int - mappedPublicPort int - serviceName string + ctx context.Context + container testcontainers.Container + ip string + appPort []int + externalAdminPort int + internalAdminPort int + mappedPublicPort int + serviceName string } var _ Service = (*ConnectContainer)(nil) +func (g ConnectContainer) Exec(ctx context.Context, cmd []string) (string, error) { + exitCode, reader, err := g.container.Exec(ctx, cmd) + if err != nil { + return "", fmt.Errorf("exec with error %s", err) + } + if exitCode != 0 { + return "", fmt.Errorf("exec with exit code %d", exitCode) + } + buf, err := io.ReadAll(reader) + if err != nil { + return "", fmt.Errorf("error reading from exec output: %w", err) + } + return string(buf), nil +} + func (g ConnectContainer) Export(partition, peer string, client *api.Client) error { return fmt.Errorf("ConnectContainer export unimplemented") } func (g ConnectContainer) GetAddr() (string, int) { + return g.ip, g.appPort[0] +} + +func (g ConnectContainer) GetAddrs() (string, []int) { return g.ip, g.appPort } +func (g ConnectContainer) GetPort(port int) (int, error) { + return 0, errors.New("not implemented") +} + func (g ConnectContainer) Restart() error { _, err := g.GetStatus() if err != nil { @@ -93,12 +117,24 @@ func (g ConnectContainer) Start() error { return g.container.Start(g.ctx) } +func (g ConnectContainer) Stop() error { + if g.container == nil { + return fmt.Errorf("container has not been initialized") + } + return g.container.Stop(context.Background(), nil) +} + func (g ConnectContainer) Terminate() error { return cluster.TerminateContainer(g.ctx, g.container, true) } +func (g ConnectContainer) GetInternalAdminAddr() (string, int) { + return "localhost", g.internalAdminPort +} + +// GetAdminAddr returns the external admin port func (g ConnectContainer) GetAdminAddr() (string, int) { - return "localhost", g.adminPort + return "localhost", g.externalAdminPort } func (g ConnectContainer) GetStatus() (string, error) { @@ -106,18 +142,24 @@ func (g ConnectContainer) GetStatus() (string, error) { return state.Status, err } +type SidecarConfig struct { + Name string + ServiceID string + Namespace string +} + // NewConnectService returns a container that runs envoy sidecar, launched by // "consul connect envoy", for service name (serviceName) on the specified // node. The container exposes port serviceBindPort and envoy admin port // (19000) by mapping them onto host ports. The container's name has a prefix // combining datacenter and name. -func NewConnectService(ctx context.Context, sidecarServiceName string, serviceID string, serviceBindPort int, node libcluster.Agent) (*ConnectContainer, error) { +func NewConnectService(ctx context.Context, sidecarCfg SidecarConfig, serviceBindPorts []int, node cluster.Agent) (*ConnectContainer, error) { nodeConfig := node.GetConfig() if nodeConfig.ScratchDir == "" { return nil, fmt.Errorf("node ScratchDir is required") } - namePrefix := fmt.Sprintf("%s-service-connect-%s", node.GetDatacenter(), sidecarServiceName) + namePrefix := fmt.Sprintf("%s-service-connect-%s", node.GetDatacenter(), sidecarCfg.Name) containerName := utils.RandName(namePrefix) envoyVersion := getEnvoyVersion() @@ -133,7 +175,7 @@ func NewConnectService(ctx context.Context, sidecarServiceName string, serviceID } dockerfileCtx.BuildArgs = buildargs - adminPort, err := node.ClaimAdminPort() + internalAdminPort, err := node.ClaimAdminPort() if err != nil { return nil, err } @@ -145,8 +187,9 @@ func NewConnectService(ctx context.Context, sidecarServiceName string, serviceID Name: containerName, Cmd: []string{ "consul", "connect", "envoy", - "-sidecar-for", serviceID, - "-admin-bind", fmt.Sprintf("0.0.0.0:%d", adminPort), + "-sidecar-for", sidecarCfg.ServiceID, + "-admin-bind", fmt.Sprintf("0.0.0.0:%d", internalAdminPort), + "-namespace", sidecarCfg.Namespace, "--", "--log-level", envoyLogLevel, }, @@ -181,28 +224,40 @@ func NewConnectService(ctx context.Context, sidecarServiceName string, serviceID } var ( - appPortStr = strconv.Itoa(serviceBindPort) - adminPortStr = strconv.Itoa(adminPort) + appPortStrs []string + adminPortStr = strconv.Itoa(internalAdminPort) ) - info, err := cluster.LaunchContainerOnNode(ctx, node, req, []string{appPortStr, adminPortStr}) + for _, port := range serviceBindPorts { + appPortStrs = append(appPortStrs, strconv.Itoa(port)) + } + + // expose the app ports and the envoy adminPortStr on the agent container + exposedPorts := make([]string, len(appPortStrs)) + copy(exposedPorts, appPortStrs) + exposedPorts = append(exposedPorts, adminPortStr) + info, err := cluster.LaunchContainerOnNode(ctx, node, req, exposedPorts) if err != nil { return nil, err } out := &ConnectContainer{ - ctx: ctx, - container: info.Container, - ip: info.IP, - appPort: info.MappedPorts[appPortStr].Int(), - adminPort: info.MappedPorts[adminPortStr].Int(), - serviceName: sidecarServiceName, + ctx: ctx, + container: info.Container, + ip: info.IP, + externalAdminPort: info.MappedPorts[adminPortStr].Int(), + internalAdminPort: internalAdminPort, + serviceName: sidecarCfg.Name, + } + + for _, port := range appPortStrs { + out.appPort = append(out.appPort, info.MappedPorts[port].Int()) } - fmt.Printf("NewConnectService: name %s, mapped App Port %d, service bind port %d\n", - serviceID, out.appPort, serviceBindPort) + fmt.Printf("NewConnectService: name %s, mapped App Port %d, service bind port %v\n", + sidecarCfg.ServiceID, out.appPort, serviceBindPorts) fmt.Printf("NewConnectService sidecar: name %s, mapped admin port %d, admin port %d\n", - sidecarServiceName, out.adminPort, adminPort) + sidecarCfg.Name, out.externalAdminPort, internalAdminPort) return out, nil } diff --git a/test/integration/consul-container/libs/service/examples.go b/test/integration/consul-container/libs/service/examples.go index e4c1fd186a3..0c28a152625 100644 --- a/test/integration/consul-container/libs/service/examples.go +++ b/test/integration/consul-container/libs/service/examples.go @@ -29,6 +29,21 @@ type exampleContainer struct { var _ Service = (*exampleContainer)(nil) +func (g exampleContainer) Exec(ctx context.Context, cmd []string) (string, error) { + exitCode, reader, err := g.container.Exec(ctx, cmd) + if err != nil { + return "", fmt.Errorf("exec with error %s", err) + } + if exitCode != 0 { + return "", fmt.Errorf("exec with exit code %d", exitCode) + } + buf, err := io.ReadAll(reader) + if err != nil { + return "", fmt.Errorf("error reading from exec output: %w", err) + } + return string(buf), nil +} + func (g exampleContainer) Export(partition, peerName string, client *api.Client) error { config := &api.ExportedServicesConfigEntry{ Name: partition, @@ -49,6 +64,14 @@ func (g exampleContainer) GetAddr() (string, int) { return g.ip, g.httpPort } +func (g exampleContainer) GetAddrs() (string, []int) { + return "", nil +} + +func (g exampleContainer) GetPort(port int) (int, error) { + return 0, nil +} + func (g exampleContainer) Restart() error { return fmt.Errorf("Restart Unimplemented by ConnectContainer") } @@ -86,6 +109,13 @@ func (g exampleContainer) Start() error { return g.container.Start(context.Background()) } +func (g exampleContainer) Stop() error { + if g.container == nil { + return fmt.Errorf("container has not been initialized") + } + return g.container.Stop(context.Background(), nil) +} + func (c exampleContainer) Terminate() error { return cluster.TerminateContainer(c.ctx, c.container, true) } @@ -95,7 +125,7 @@ func (c exampleContainer) GetStatus() (string, error) { return state.Status, err } -func NewExampleService(ctx context.Context, name string, httpPort int, grpcPort int, node libcluster.Agent) (Service, error) { +func NewExampleService(ctx context.Context, name string, httpPort int, grpcPort int, node libcluster.Agent, containerArgs ...string) (Service, error) { namePrefix := fmt.Sprintf("%s-service-example-%s", node.GetDatacenter(), name) containerName := utils.RandName(namePrefix) @@ -109,18 +139,22 @@ func NewExampleService(ctx context.Context, name string, httpPort int, grpcPort grpcPortStr = strconv.Itoa(grpcPort) ) + command := []string{ + "server", + "-http-port", httpPortStr, + "-grpc-port", grpcPortStr, + "-redirect-port", "-disabled", + } + + command = append(command, containerArgs...) + req := testcontainers.ContainerRequest{ Image: hashicorpDockerProxy + "/fortio/fortio", WaitingFor: wait.ForLog("").WithStartupTimeout(10 * time.Second), AutoRemove: false, Name: containerName, - Cmd: []string{ - "server", - "-http-port", httpPortStr, - "-grpc-port", grpcPortStr, - "-redirect-port", "-disabled", - }, - Env: map[string]string{"FORTIO_NAME": name}, + Cmd: command, + Env: map[string]string{"FORTIO_NAME": name}, } info, err := cluster.LaunchContainerOnNode(ctx, node, req, []string{httpPortStr, grpcPortStr}) diff --git a/test/integration/consul-container/libs/service/gateway.go b/test/integration/consul-container/libs/service/gateway.go index 5da2281338c..32c899583b9 100644 --- a/test/integration/consul-container/libs/service/gateway.go +++ b/test/integration/consul-container/libs/service/gateway.go @@ -20,16 +20,32 @@ import ( // gatewayContainer type gatewayContainer struct { - ctx context.Context - container testcontainers.Container - ip string - port int - adminPort int - serviceName string + ctx context.Context + container testcontainers.Container + ip string + port int + adminPort int + serviceName string + portMappings map[int]int } var _ Service = (*gatewayContainer)(nil) +func (g gatewayContainer) Exec(ctx context.Context, cmd []string) (string, error) { + exitCode, reader, err := g.container.Exec(ctx, cmd) + if err != nil { + return "", fmt.Errorf("exec with error %s", err) + } + if exitCode != 0 { + return "", fmt.Errorf("exec with exit code %d", exitCode) + } + buf, err := io.ReadAll(reader) + if err != nil { + return "", fmt.Errorf("error reading from exec output: %w", err) + } + return string(buf), nil +} + func (g gatewayContainer) Export(partition, peer string, client *api.Client) error { return fmt.Errorf("gatewayContainer export unimplemented") } @@ -38,6 +54,10 @@ func (g gatewayContainer) GetAddr() (string, int) { return g.ip, g.port } +func (g gatewayContainer) GetAddrs() (string, []int) { + return "", nil +} + func (g gatewayContainer) GetLogs() (string, error) { rc, err := g.container.Logs(context.Background()) if err != nil { @@ -71,6 +91,13 @@ func (g gatewayContainer) Start() error { return g.container.Start(context.Background()) } +func (g gatewayContainer) Stop() error { + if g.container == nil { + return fmt.Errorf("container has not been initialized") + } + return g.container.Stop(context.Background(), nil) +} + func (c gatewayContainer) Terminate() error { return cluster.TerminateContainer(c.ctx, c.container, true) } @@ -79,6 +106,14 @@ func (g gatewayContainer) GetAdminAddr() (string, int) { return "localhost", g.adminPort } +func (g gatewayContainer) GetPort(port int) (int, error) { + p, ok := g.portMappings[port] + if !ok { + return 0, fmt.Errorf("port does not exist") + } + return p, nil +} + func (g gatewayContainer) Restart() error { _, err := g.container.State(g.ctx) if err != nil { @@ -104,13 +139,19 @@ func (g gatewayContainer) GetStatus() (string, error) { return state.Status, err } -func NewGatewayService(ctx context.Context, name string, kind string, node libcluster.Agent) (Service, error) { +type GatewayConfig struct { + Name string + Kind string + Namespace string +} + +func NewGatewayService(ctx context.Context, gwCfg GatewayConfig, node libcluster.Agent, ports ...int) (Service, error) { nodeConfig := node.GetConfig() if nodeConfig.ScratchDir == "" { return nil, fmt.Errorf("node ScratchDir is required") } - namePrefix := fmt.Sprintf("%s-service-gateway-%s", node.GetDatacenter(), name) + namePrefix := fmt.Sprintf("%s-service-gateway-%s", node.GetDatacenter(), gwCfg.Name) containerName := utils.RandName(namePrefix) envoyVersion := getEnvoyVersion() @@ -138,9 +179,10 @@ func NewGatewayService(ctx context.Context, name string, kind string, node libcl Name: containerName, Cmd: []string{ "consul", "connect", "envoy", - fmt.Sprintf("-gateway=%s", kind), + fmt.Sprintf("-gateway=%s", gwCfg.Kind), "-register", - "-service", name, + "-namespace", gwCfg.Namespace, + "-service", gwCfg.Name, "-address", "{{ GetInterfaceIP \"eth0\" }}:8443", "-admin-bind", fmt.Sprintf("0.0.0.0:%d", adminPort), "--", @@ -181,21 +223,33 @@ func NewGatewayService(ctx context.Context, name string, kind string, node libcl adminPortStr = strconv.Itoa(adminPort) ) - info, err := cluster.LaunchContainerOnNode(ctx, node, req, []string{ + extraPorts := []string{} + for _, port := range ports { + extraPorts = append(extraPorts, strconv.Itoa(port)) + } + + info, err := cluster.LaunchContainerOnNode(ctx, node, req, append( + extraPorts, portStr, adminPortStr, - }) + )) if err != nil { return nil, err } + portMappings := make(map[int]int) + for _, port := range ports { + portMappings[port] = info.MappedPorts[strconv.Itoa(port)].Int() + } + out := &gatewayContainer{ - ctx: ctx, - container: info.Container, - ip: info.IP, - port: info.MappedPorts[portStr].Int(), - adminPort: info.MappedPorts[adminPortStr].Int(), - serviceName: name, + ctx: ctx, + container: info.Container, + ip: info.IP, + port: info.MappedPorts[portStr].Int(), + adminPort: info.MappedPorts[adminPortStr].Int(), + serviceName: gwCfg.Name, + portMappings: portMappings, } return out, nil diff --git a/test/integration/consul-container/libs/service/helpers.go b/test/integration/consul-container/libs/service/helpers.go index 54a16249eec..3c678f768ae 100644 --- a/test/integration/consul-container/libs/service/helpers.go +++ b/test/integration/consul-container/libs/service/helpers.go @@ -3,9 +3,11 @@ package service import ( "context" "fmt" + "testing" - "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + "github.com/hashicorp/consul/api" libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" ) @@ -16,20 +18,65 @@ const ( StaticClientServiceName = "static-client" ) +type Checks struct { + Name string + TTL string +} + +type SidecarService struct { + Port int +} + type ServiceOpts struct { - Name string - ID string - Meta map[string]string - HTTPPort int - GRPCPort int + Name string + ID string + Meta map[string]string + HTTPPort int + GRPCPort int + Checks Checks + Connect SidecarService + Namespace string } -func CreateAndRegisterStaticServerAndSidecar(node libcluster.Agent, serviceOpts *ServiceOpts) (Service, Service, error) { +// createAndRegisterStaticServerAndSidecar register the services and launch static-server containers +func createAndRegisterStaticServerAndSidecar(node libcluster.Agent, grpcPort int, svc *api.AgentServiceRegistration, containerArgs ...string) (Service, Service, error) { // Do some trickery to ensure that partial completion is correctly torn // down, but successful execution is not. var deferClean utils.ResettableDefer defer deferClean.Execute() + if err := node.GetClient().Agent().ServiceRegister(svc); err != nil { + return nil, nil, err + } + + // Create a service and proxy instance + serverService, err := NewExampleService(context.Background(), svc.ID, svc.Port, grpcPort, node, containerArgs...) + if err != nil { + return nil, nil, err + } + deferClean.Add(func() { + _ = serverService.Terminate() + }) + sidecarCfg := SidecarConfig{ + Name: fmt.Sprintf("%s-sidecar", svc.ID), + ServiceID: svc.ID, + Namespace: svc.Namespace, + } + serverConnectProxy, err := NewConnectService(context.Background(), sidecarCfg, []int{svc.Port}, node) // bindPort not used + if err != nil { + return nil, nil, err + } + deferClean.Add(func() { + _ = serverConnectProxy.Terminate() + }) + + // disable cleanup functions now that we have an object with a Terminate() function + deferClean.Reset() + + return serverService, serverConnectProxy, nil +} + +func CreateAndRegisterStaticServerAndSidecar(node libcluster.Agent, serviceOpts *ServiceOpts, containerArgs ...string) (Service, Service, error) { // Register the static-server service and sidecar first to prevent race with sidecar // trying to get xDS before it's ready req := &api.AgentServiceRegistration{ @@ -41,6 +88,7 @@ func CreateAndRegisterStaticServerAndSidecar(node libcluster.Agent, serviceOpts Proxy: &api.AgentServiceConnectProxyConfig{}, }, }, + Namespace: serviceOpts.Namespace, Check: &api.AgentServiceCheck{ Name: "Static Server Listening", TCP: fmt.Sprintf("127.0.0.1:%d", serviceOpts.HTTPPort), @@ -49,32 +97,32 @@ func CreateAndRegisterStaticServerAndSidecar(node libcluster.Agent, serviceOpts }, Meta: serviceOpts.Meta, } + return createAndRegisterStaticServerAndSidecar(node, serviceOpts.GRPCPort, req, containerArgs...) +} - if err := node.GetClient().Agent().ServiceRegister(req); err != nil { - return nil, nil, err - } - - // Create a service and proxy instance - serverService, err := NewExampleService(context.Background(), serviceOpts.ID, serviceOpts.HTTPPort, serviceOpts.GRPCPort, node) - if err != nil { - return nil, nil, err - } - deferClean.Add(func() { - _ = serverService.Terminate() - }) - - serverConnectProxy, err := NewConnectService(context.Background(), fmt.Sprintf("%s-sidecar", serviceOpts.ID), serviceOpts.ID, serviceOpts.HTTPPort, node) // bindPort not used - if err != nil { - return nil, nil, err +func CreateAndRegisterStaticServerAndSidecarWithChecks(node libcluster.Agent, serviceOpts *ServiceOpts) (Service, Service, error) { + // Register the static-server service and sidecar first to prevent race with sidecar + // trying to get xDS before it's ready + req := &api.AgentServiceRegistration{ + Name: serviceOpts.Name, + ID: serviceOpts.ID, + Port: serviceOpts.HTTPPort, + Connect: &api.AgentServiceConnect{ + SidecarService: &api.AgentServiceRegistration{ + Proxy: &api.AgentServiceConnectProxyConfig{}, + Port: serviceOpts.Connect.Port, + }, + }, + Checks: api.AgentServiceChecks{ + { + Name: serviceOpts.Checks.Name, + TTL: serviceOpts.Checks.TTL, + }, + }, + Meta: serviceOpts.Meta, } - deferClean.Add(func() { - _ = serverConnectProxy.Terminate() - }) - // disable cleanup functions now that we have an object with a Terminate() function - deferClean.Reset() - - return serverService, serverConnectProxy, nil + return createAndRegisterStaticServerAndSidecar(node, serviceOpts.GRPCPort, req) } func CreateAndRegisterStaticClientSidecar( @@ -119,7 +167,12 @@ func CreateAndRegisterStaticClientSidecar( } // Create a service and proxy instance - clientConnectProxy, err := NewConnectService(context.Background(), fmt.Sprintf("%s-sidecar", StaticClientServiceName), StaticClientServiceName, libcluster.ServiceUpstreamLocalBindPort, node) + sidecarCfg := SidecarConfig{ + Name: fmt.Sprintf("%s-sidecar", StaticClientServiceName), + ServiceID: StaticClientServiceName, + } + + clientConnectProxy, err := NewConnectService(context.Background(), sidecarCfg, []int{libcluster.ServiceUpstreamLocalBindPort}, node) if err != nil { return nil, err } @@ -132,3 +185,59 @@ func CreateAndRegisterStaticClientSidecar( return clientConnectProxy, nil } + +func ClientsCreate(t *testing.T, numClients int, image, version string, cluster *libcluster.Cluster) { + opts := libcluster.BuildOptions{ + ConsulImageName: image, + ConsulVersion: version, + } + ctx := libcluster.NewBuildContext(t, opts) + + conf := libcluster.NewConfigBuilder(ctx). + Client(). + ToAgentConfig(t) + t.Logf("Cluster client config:\n%s", conf.JSON) + + require.NoError(t, cluster.AddN(*conf, numClients, true)) +} + +func ServiceCreate(t *testing.T, client *api.Client, serviceName string) uint64 { + require.NoError(t, client.Agent().ServiceRegister(&api.AgentServiceRegistration{ + Name: serviceName, + Port: 9999, + Connect: &api.AgentServiceConnect{ + SidecarService: &api.AgentServiceRegistration{ + Port: 22005, + }, + }, + })) + + service, meta, err := client.Catalog().Service(serviceName, "", &api.QueryOptions{}) + require.NoError(t, err) + require.Len(t, service, 1) + require.Equal(t, serviceName, service[0].ServiceName) + require.Equal(t, 9999, service[0].ServicePort) + + return meta.LastIndex +} + +func ServiceHealthBlockingQuery(client *api.Client, serviceName string, waitIndex uint64) (chan []*api.ServiceEntry, chan error) { + var ( + ch = make(chan []*api.ServiceEntry, 1) + errCh = make(chan error, 1) + ) + go func() { + opts := &api.QueryOptions{WaitIndex: waitIndex} + service, q, err := client.Health().Service(serviceName, "", false, opts) + if err == nil && q.QueryBackend != api.QueryBackendStreaming { + err = fmt.Errorf("invalid backend for this test %s", q.QueryBackend) + } + if err != nil { + errCh <- err + } else { + ch <- service + } + }() + + return ch, errCh +} diff --git a/test/integration/consul-container/libs/service/service.go b/test/integration/consul-container/libs/service/service.go index 75a35a74a6f..f4efa4f909c 100644 --- a/test/integration/consul-container/libs/service/service.go +++ b/test/integration/consul-container/libs/service/service.go @@ -1,18 +1,27 @@ package service -import "github.com/hashicorp/consul/api" +import ( + "context" + + "github.com/hashicorp/consul/api" +) // Service represents a process that will be registered with the // Consul catalog, including Consul components such as sidecars and gateways type Service interface { + Exec(ctx context.Context, cmd []string) (string, error) // Export a service to the peering cluster Export(partition, peer string, client *api.Client) error GetAddr() (string, int) + GetAddrs() (string, []int) + GetPort(port int) (int, error) + // GetAdminAddr returns the external admin address GetAdminAddr() (string, int) GetLogs() (string, error) GetName() string GetServiceName() string Start() (err error) + Stop() (err error) Terminate() error Restart() error GetStatus() (string, error) diff --git a/test/integration/consul-container/libs/topology/peering_topology.go b/test/integration/consul-container/libs/topology/peering_topology.go index 1c764c45c53..3e7ac8d067b 100644 --- a/test/integration/consul-container/libs/topology/peering_topology.go +++ b/test/integration/consul-container/libs/topology/peering_topology.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/hashicorp/consul/api" @@ -41,18 +42,65 @@ type BuiltCluster struct { func BasicPeeringTwoClustersSetup( t *testing.T, consulVersion string, + peeringThroughMeshgateway bool, ) (*BuiltCluster, *BuiltCluster) { - // acceptingCluster, acceptingCtx, acceptingClient := NewPeeringCluster(t, "dc1", 3, consulVersion, true) - acceptingCluster, acceptingCtx, acceptingClient := NewPeeringCluster(t, 3, &libcluster.BuildOptions{ - Datacenter: "dc1", - ConsulVersion: consulVersion, - InjectAutoEncryption: true, + acceptingCluster, acceptingCtx, acceptingClient := NewCluster(t, &ClusterConfig{ + NumServers: 3, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + ConsulVersion: consulVersion, + InjectAutoEncryption: true, + }, + ApplyDefaultProxySettings: true, }) - dialingCluster, dialingCtx, dialingClient := NewPeeringCluster(t, 1, &libcluster.BuildOptions{ - Datacenter: "dc2", - ConsulVersion: consulVersion, - InjectAutoEncryption: true, + + dialingCluster, dialingCtx, dialingClient := NewCluster(t, &ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc2", + ConsulVersion: consulVersion, + InjectAutoEncryption: true, + }, + ApplyDefaultProxySettings: true, }) + + // Create the mesh gateway for dataplane traffic and peering control plane traffic (if enabled) + gwCfg := libservice.GatewayConfig{ + Name: "mesh", + Kind: "mesh", + } + acceptingClusterGateway, err := libservice.NewGatewayService(context.Background(), gwCfg, acceptingCluster.Clients()[0]) + require.NoError(t, err) + dialingClusterGateway, err := libservice.NewGatewayService(context.Background(), gwCfg, dialingCluster.Clients()[0]) + require.NoError(t, err) + + // Enable peering control plane traffic through mesh gateway + if peeringThroughMeshgateway { + req := &api.MeshConfigEntry{ + Peering: &api.PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, + } + configCluster := func(cli *api.Client) error { + libassert.CatalogServiceExists(t, cli, "mesh", nil) + ok, _, err := cli.ConfigEntries().Set(req, &api.WriteOptions{}) + if !ok { + return fmt.Errorf("config entry is not set") + } + + if err != nil { + return fmt.Errorf("error writing config entry: %s", err) + } + return nil + } + err = configCluster(dialingClient) + require.NoError(t, err) + err = configCluster(acceptingClient) + require.NoError(t, err) + } + require.NoError(t, dialingCluster.PeerWithCluster(acceptingClient, AcceptingPeerName, DialingPeerName)) libassert.PeeringStatus(t, acceptingClient, AcceptingPeerName, api.PeeringStateActive) @@ -60,7 +108,6 @@ func BasicPeeringTwoClustersSetup( // Register an static-server service in acceptingCluster and export to dialing cluster var serverService, serverSidecarService libservice.Service - var acceptingClusterGateway libservice.Service { clientNode := acceptingCluster.Clients()[0] @@ -77,19 +124,14 @@ func BasicPeeringTwoClustersSetup( serverService, serverSidecarService, err = libservice.CreateAndRegisterStaticServerAndSidecar(clientNode, &serviceOpts) require.NoError(t, err) - libassert.CatalogServiceExists(t, acceptingClient, libservice.StaticServerServiceName) - libassert.CatalogServiceExists(t, acceptingClient, "static-server-sidecar-proxy") + libassert.CatalogServiceExists(t, acceptingClient, libservice.StaticServerServiceName, nil) + libassert.CatalogServiceExists(t, acceptingClient, "static-server-sidecar-proxy", nil) require.NoError(t, serverService.Export("default", AcceptingPeerName, acceptingClient)) - - // Create the mesh gateway for dataplane traffic - acceptingClusterGateway, err = libservice.NewGatewayService(context.Background(), "mesh", "mesh", clientNode) - require.NoError(t, err) } // Register an static-client service in dialing cluster and set upstream to static-server service var clientSidecarService *libservice.ConnectContainer - var dialingClusterGateway libservice.Service { clientNode := dialingCluster.Clients()[0] @@ -98,18 +140,15 @@ func BasicPeeringTwoClustersSetup( clientSidecarService, err = libservice.CreateAndRegisterStaticClientSidecar(clientNode, DialingPeerName, true) require.NoError(t, err) - libassert.CatalogServiceExists(t, dialingClient, "static-client-sidecar-proxy") + libassert.CatalogServiceExists(t, dialingClient, "static-client-sidecar-proxy", nil) - // Create the mesh gateway for dataplane traffic - dialingClusterGateway, err = libservice.NewGatewayService(context.Background(), "mesh", "mesh", clientNode) - require.NoError(t, err) } _, adminPort := clientSidecarService.GetAdminAddr() libassert.AssertUpstreamEndpointStatus(t, adminPort, fmt.Sprintf("static-server.default.%s.external", DialingPeerName), "HEALTHY", 1) _, port := clientSidecarService.GetAddr() libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), libservice.StaticServerServiceName, "") return &BuiltCluster{ Cluster: acceptingCluster, @@ -127,95 +166,73 @@ func BasicPeeringTwoClustersSetup( } } -// NewDialingCluster creates a cluster for peering with a single dev agent -// TODO: note: formerly called CreatingPeeringClusterAndSetup -// -// Deprecated: use NewPeeringCluster mostly -func NewDialingCluster( - t *testing.T, - version string, - dialingPeerName string, -) (*libcluster.Cluster, *api.Client, libservice.Service) { - t.Helper() - t.Logf("creating the dialing cluster") - - opts := libcluster.BuildOptions{ - Datacenter: "dc2", - InjectAutoEncryption: true, - InjectGossipEncryption: true, - AllowHTTPAnyway: true, - ConsulVersion: version, - } - ctx := libcluster.NewBuildContext(t, opts) - - conf := libcluster.NewConfigBuilder(ctx). - Peering(true). - ToAgentConfig(t) - t.Logf("dc2 server config: \n%s", conf.JSON) - - cluster, err := libcluster.NewN(t, *conf, 1) - require.NoError(t, err) - - node := cluster.Agents[0] - client := node.GetClient() - libcluster.WaitForLeader(t, cluster, client) - libcluster.WaitForMembers(t, client, 1) - - // Default Proxy Settings - ok, err := utils.ApplyDefaultProxySettings(client) - require.NoError(t, err) - require.True(t, ok) - - // Create the mesh gateway for dataplane traffic - _, err = libservice.NewGatewayService(context.Background(), "mesh", "mesh", node) - require.NoError(t, err) - - // Create a service and proxy instance - clientProxyService, err := libservice.CreateAndRegisterStaticClientSidecar(node, dialingPeerName, true) - require.NoError(t, err) - - libassert.CatalogServiceExists(t, client, "static-client-sidecar-proxy") - - return cluster, client, clientProxyService +type ClusterConfig struct { + NumServers int + NumClients int + ApplyDefaultProxySettings bool + BuildOpts *libcluster.BuildOptions + Cmd string + LogConsumer *TestLogConsumer + Ports []int } -// NewPeeringCluster creates a cluster with peering enabled. It also creates +// NewCluster creates a cluster with peering enabled. It also creates // and registers a mesh-gateway at the client agent. The API client returned is // pointed at the client agent. // - proxy-defaults.protocol = tcp -func NewPeeringCluster( +func NewCluster( t *testing.T, - numServers int, - buildOpts *libcluster.BuildOptions, + config *ClusterConfig, ) (*libcluster.Cluster, *libcluster.BuildContext, *api.Client) { - require.NotEmpty(t, buildOpts.Datacenter) - require.True(t, numServers > 0) + var ( + cluster *libcluster.Cluster + err error + ) + require.NotEmpty(t, config.BuildOpts.Datacenter) + require.True(t, config.NumServers > 0) opts := libcluster.BuildOptions{ - Datacenter: buildOpts.Datacenter, - InjectAutoEncryption: buildOpts.InjectAutoEncryption, + Datacenter: config.BuildOpts.Datacenter, + InjectAutoEncryption: config.BuildOpts.InjectAutoEncryption, InjectGossipEncryption: true, AllowHTTPAnyway: true, - ConsulVersion: buildOpts.ConsulVersion, - ACLEnabled: buildOpts.ACLEnabled, + ConsulVersion: config.BuildOpts.ConsulVersion, + ACLEnabled: config.BuildOpts.ACLEnabled, + LogStore: config.BuildOpts.LogStore, } ctx := libcluster.NewBuildContext(t, opts) serverConf := libcluster.NewConfigBuilder(ctx). - Bootstrap(numServers). + Bootstrap(config.NumServers). Peering(true). ToAgentConfig(t) t.Logf("%s server config: \n%s", opts.Datacenter, serverConf.JSON) - cluster, err := libcluster.NewN(t, *serverConf, numServers) + // optional + if config.LogConsumer != nil { + serverConf.LogConsumer = config.LogConsumer + } + + t.Logf("Cluster config:\n%s", serverConf.JSON) + + // optional custom cmd + if config.Cmd != "" { + serverConf.Cmd = append(serverConf.Cmd, config.Cmd) + } + + if config.Ports != nil { + cluster, err = libcluster.New(t, []libcluster.Config{*serverConf}, config.Ports...) + } else { + cluster, err = libcluster.NewN(t, *serverConf, config.NumServers) + } require.NoError(t, err) var retryJoin []string - for i := 0; i < numServers; i++ { + for i := 0; i < config.NumServers; i++ { retryJoin = append(retryJoin, fmt.Sprintf("agent-%d", i)) } - // Add a stable client to register the service + // Add numClients static clients to register the service configbuiilder := libcluster.NewConfigBuilder(ctx). Client(). Peering(true). @@ -223,18 +240,33 @@ func NewPeeringCluster( clientConf := configbuiilder.ToAgentConfig(t) t.Logf("%s client config: \n%s", opts.Datacenter, clientConf.JSON) - require.NoError(t, cluster.AddN(*clientConf, 1, true)) + require.NoError(t, cluster.AddN(*clientConf, config.NumClients, true)) // Use the client agent as the HTTP endpoint since we will not rotate it in many tests. - clientNode := cluster.Agents[numServers] - client := clientNode.GetClient() + var client *api.Client + if config.NumClients > 0 { + clientNode := cluster.Agents[config.NumServers] + client = clientNode.GetClient() + } else { + client = cluster.Agents[0].GetClient() + } libcluster.WaitForLeader(t, cluster, client) - libcluster.WaitForMembers(t, client, numServers+1) + libcluster.WaitForMembers(t, client, config.NumServers+config.NumClients) // Default Proxy Settings - ok, err := utils.ApplyDefaultProxySettings(client) - require.NoError(t, err) - require.True(t, ok) + if config.ApplyDefaultProxySettings { + ok, err := utils.ApplyDefaultProxySettings(client) + require.NoError(t, err) + require.True(t, ok) + } return cluster, ctx, client } + +type TestLogConsumer struct { + Msgs []string +} + +func (g *TestLogConsumer) Accept(l testcontainers.Log) { + g.Msgs = append(g.Msgs, string(l.Content)) +} diff --git a/test/integration/consul-container/libs/topology/service_topology.go b/test/integration/consul-container/libs/topology/service_topology.go new file mode 100644 index 00000000000..25ef6179ec7 --- /dev/null +++ b/test/integration/consul-container/libs/topology/service_topology.go @@ -0,0 +1,51 @@ +package topology + +import ( + "fmt" + "testing" + + "github.com/hashicorp/consul/api" + libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" + libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" + "github.com/stretchr/testify/require" +) + +func CreateServices(t *testing.T, cluster *libcluster.Cluster) (libservice.Service, libservice.Service) { + node := cluster.Agents[0] + client := node.GetClient() + + // Register service as HTTP + serviceDefault := &api.ServiceConfigEntry{ + Kind: api.ServiceDefaults, + Name: libservice.StaticServerServiceName, + Protocol: "http", + } + + ok, _, err := client.ConfigEntries().Set(serviceDefault, nil) + require.NoError(t, err, "error writing HTTP service-default") + require.True(t, ok, "did not write HTTP service-default") + + // Create a service and proxy instance + serviceOpts := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server", + HTTPPort: 8080, + GRPCPort: 8079, + } + + // Create a service and proxy instance + _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts) + require.NoError(t, err) + + libassert.CatalogServiceExists(t, client, fmt.Sprintf("%s-sidecar-proxy", libservice.StaticServerServiceName), nil) + libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName, nil) + + // Create a client proxy instance with the server as an upstream + clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false) + require.NoError(t, err) + + libassert.CatalogServiceExists(t, client, fmt.Sprintf("%s-sidecar-proxy", libservice.StaticClientServiceName), nil) + + return serverConnectProxy, clientConnectProxy +} diff --git a/test/integration/consul-container/test/basic/connect_service_test.go b/test/integration/consul-container/test/basic/connect_service_test.go index 90a80c84c6d..655f90a1d0c 100644 --- a/test/integration/consul-container/test/basic/connect_service_test.go +++ b/test/integration/consul-container/test/basic/connect_service_test.go @@ -34,7 +34,7 @@ func TestBasicConnectService(t *testing.T) { libassert.AssertContainerState(t, clientService, "running") libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") } func createCluster(t *testing.T) *libcluster.Cluster { @@ -84,14 +84,14 @@ func createServices(t *testing.T, cluster *libcluster.Cluster) libservice.Servic _, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts) require.NoError(t, err) - libassert.CatalogServiceExists(t, client, "static-server-sidecar-proxy") - libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName) + libassert.CatalogServiceExists(t, client, "static-server-sidecar-proxy", nil) + libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName, nil) // Create a client proxy instance with the server as an upstream clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false) require.NoError(t, err) - libassert.CatalogServiceExists(t, client, "static-client-sidecar-proxy") + libassert.CatalogServiceExists(t, client, "static-client-sidecar-proxy", nil) return clientConnectProxy } diff --git a/test/integration/consul-container/test/gateways/gateway_endpoint_test.go b/test/integration/consul-container/test/gateways/gateway_endpoint_test.go new file mode 100644 index 00000000000..da642f18341 --- /dev/null +++ b/test/integration/consul-container/test/gateways/gateway_endpoint_test.go @@ -0,0 +1,304 @@ +package gateways + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/api" + libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" + libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" + libtopology "github.com/hashicorp/consul/test/integration/consul-container/libs/topology" + "github.com/hashicorp/go-cleanhttp" +) + +// Creates a gateway service and tests to see if it is routable +func TestAPIGatewayCreate(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + gatewayName := randomName("gateway", 16) + routeName := randomName("route", 16) + serviceName := randomName("service", 16) + listenerPortOne := 6000 + serviceHTTPPort := 6001 + serviceGRPCPort := 6002 + + clusterConfig := &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + InjectGossipEncryption: true, + AllowHTTPAnyway: true, + }, + Ports: []int{ + listenerPortOne, + serviceHTTPPort, + serviceGRPCPort, + }, + } + + cluster, _, _ := libtopology.NewCluster(t, clusterConfig) + client := cluster.APIClient(0) + + namespace := getNamespace() + if namespace != "" { + ns := &api.Namespace{Name: namespace} + _, _, err := client.Namespaces().Create(ns, nil) + require.NoError(t, err) + } + + // add api gateway config + apiGateway := &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Namespace: namespace, + Name: gatewayName, + Listeners: []api.APIGatewayListener{ + { + Name: "listener", + Port: listenerPortOne, + Protocol: "tcp", + }, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(apiGateway)) + + _, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(cluster.Agents[0], &libservice.ServiceOpts{ + ID: serviceName, + Name: serviceName, + Namespace: namespace, + HTTPPort: serviceHTTPPort, + GRPCPort: serviceGRPCPort, + }) + require.NoError(t, err) + + tcpRoute := &api.TCPRouteConfigEntry{ + Kind: api.TCPRoute, + Name: routeName, + Namespace: namespace, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Namespace: namespace, + Name: gatewayName, + }, + }, + Services: []api.TCPService{ + { + Namespace: namespace, + Name: serviceName, + }, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(tcpRoute)) + + // Create a gateway + gatewayService, err := libservice.NewGatewayService(context.Background(), libservice.GatewayConfig{ + Kind: "api", + Namespace: namespace, + Name: gatewayName, + }, cluster.Agents[0], listenerPortOne) + require.NoError(t, err) + + // make sure the gateway/route come online + // make sure config entries have been properly created + checkGatewayConfigEntry(t, client, gatewayName, namespace) + checkTCPRouteConfigEntry(t, client, routeName, namespace) + + port, err := gatewayService.GetPort(listenerPortOne) + require.NoError(t, err) + libassert.HTTPServiceEchoes(t, "localhost", port, "") +} + +func isAccepted(conditions []api.Condition) bool { + return conditionStatusIsValue("Accepted", "True", conditions) +} + +func isBound(conditions []api.Condition) bool { + return conditionStatusIsValue("Bound", "True", conditions) +} + +func conditionStatusIsValue(typeName string, statusValue string, conditions []api.Condition) bool { + for _, c := range conditions { + if c.Type == typeName && c.Status == statusValue { + return true + } + } + return false +} + +func checkGatewayConfigEntry(t *testing.T, client *api.Client, gatewayName string, namespace string) { + t.Helper() + + require.Eventually(t, func() bool { + entry, _, err := client.ConfigEntries().Get(api.APIGateway, gatewayName, &api.QueryOptions{Namespace: namespace}) + if err != nil { + t.Log("error constructing request", err) + return false + } + if entry == nil { + t.Log("returned entry is nil") + return false + } + + apiEntry := entry.(*api.APIGatewayConfigEntry) + return isAccepted(apiEntry.Status.Conditions) + }, time.Second*10, time.Second*1) +} + +func checkHTTPRouteConfigEntry(t *testing.T, client *api.Client, routeName string, namespace string) { + t.Helper() + + require.Eventually(t, func() bool { + entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, routeName, &api.QueryOptions{Namespace: namespace}) + if err != nil { + t.Log("error constructing request", err) + return false + } + if entry == nil { + t.Log("returned entry is nil") + return false + } + + apiEntry := entry.(*api.HTTPRouteConfigEntry) + return isBound(apiEntry.Status.Conditions) + }, time.Second*10, time.Second*1) +} + +func checkTCPRouteConfigEntry(t *testing.T, client *api.Client, routeName string, namespace string) { + t.Helper() + + require.Eventually(t, func() bool { + entry, _, err := client.ConfigEntries().Get(api.TCPRoute, routeName, &api.QueryOptions{Namespace: namespace}) + if err != nil { + t.Log("error constructing request", err) + return false + } + if entry == nil { + t.Log("returned entry is nil") + return false + } + + apiEntry := entry.(*api.TCPRouteConfigEntry) + return isBound(apiEntry.Status.Conditions) + }, time.Second*10, time.Second*1) +} + +type checkOptions struct { + debug bool + statusCode int + testName string +} + +// checkRoute, customized version of libassert.RouteEchos to allow for headers/distinguishing between the server instances +func checkRoute(t *testing.T, port int, path string, headers map[string]string, expected checkOptions) { + t.Helper() + + if expected.testName != "" { + t.Log("running " + expected.testName) + } + + client := cleanhttp.DefaultClient() + path = strings.TrimPrefix(path, "/") + url := fmt.Sprintf("http://localhost:%d/%s", port, path) + + require.Eventually(t, func() bool { + reader := strings.NewReader("hello") + req, err := http.NewRequest("POST", url, reader) + if err != nil { + t.Log("error constructing request", err) + return false + } + headers["content-type"] = "text/plain" + + for k, v := range headers { + req.Header.Set(k, v) + + if k == "Host" { + req.Host = v + } + } + + res, err := client.Do(req) + if err != nil { + t.Log("error sending request", err) + return false + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Log("error reading response body", err) + return false + } + + if expected.statusCode != res.StatusCode { + t.Logf("bad status code - expected: %d, actual: %d", expected.statusCode, res.StatusCode) + return false + } + if expected.debug { + if !strings.Contains(string(body), "debug") { + t.Log("body does not contain 'debug'") + return false + } + } + if !strings.Contains(string(body), "hello") { + t.Log("body does not contain 'hello'") + return false + } + + return true + }, time.Second*30, time.Second*1) +} + +func checkRouteError(t *testing.T, ip string, port int, path string, headers map[string]string, expected string) { + t.Helper() + + client := cleanhttp.DefaultClient() + url := fmt.Sprintf("http://%s:%d", ip, port) + + if path != "" { + url += "/" + path + } + + require.Eventually(t, func() bool { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Log("error constructing request", err) + return false + } + for k, v := range headers { + req.Header.Set(k, v) + + if k == "Host" { + req.Host = v + } + } + _, err = client.Do(req) + if err == nil { + t.Log("client request should have errored, but didn't") + return false + } + if expected != "" { + if !strings.Contains(err.Error(), expected) { + t.Logf("expected %q to contain %q", err.Error(), expected) + return false + } + } + return true + }, time.Second*30, time.Second*1) +} diff --git a/test/integration/consul-container/test/gateways/http_route_test.go b/test/integration/consul-container/test/gateways/http_route_test.go new file mode 100644 index 00000000000..a1a6c5a0e77 --- /dev/null +++ b/test/integration/consul-container/test/gateways/http_route_test.go @@ -0,0 +1,733 @@ +package gateways + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/api" + libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" + libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" + libtopology "github.com/hashicorp/consul/test/integration/consul-container/libs/topology" +) + +// randomName generates a random name of n length with the provided +// prefix. If prefix is omitted, the then entire name is random char. +func randomName(prefix string, n int) string { + if n == 0 { + n = 32 + } + if len(prefix) >= n { + return prefix + } + p := make([]byte, n) + rand.Read(p) + return fmt.Sprintf("%s-%s", prefix, hex.EncodeToString(p))[:n] +} + +func TestHTTPRouteFlattening(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + // infrastructure set up + listenerPort := 6004 + serviceOneHTTPPort := 6005 + serviceOneGRPCPort := 6006 + serviceTwoHTTPPort := 6007 + serviceTwoGRPCPort := 6008 + + serviceOneName := randomName("service", 16) + serviceTwoName := randomName("service", 16) + serviceOneResponseCode := 200 + serviceTwoResponseCode := 418 + gatewayName := randomName("gw", 16) + routeOneName := randomName("route", 16) + routeTwoName := randomName("route", 16) + path1 := "/" + path2 := "/v2" + + clusterConfig := &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + InjectGossipEncryption: true, + AllowHTTPAnyway: true, + }, + Ports: []int{ + listenerPort, + serviceOneHTTPPort, + serviceOneGRPCPort, + serviceTwoHTTPPort, + serviceTwoGRPCPort, + }, + ApplyDefaultProxySettings: true, + } + + cluster, _, _ := libtopology.NewCluster(t, clusterConfig) + client := cluster.Agents[0].GetClient() + + namespace := getNamespace() + if namespace != "" { + ns := &api.Namespace{Name: namespace} + _, _, err := client.Namespaces().Create(ns, nil) + require.NoError(t, err) + } + + _, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(cluster.Agents[0], &libservice.ServiceOpts{ + ID: serviceOneName, + Name: serviceOneName, + Namespace: namespace, + HTTPPort: serviceOneHTTPPort, + GRPCPort: serviceOneGRPCPort, + }, + // customizes response code so we can distinguish between which service is responding + "-echo-server-default-params", fmt.Sprintf("status=%d", serviceOneResponseCode), + ) + require.NoError(t, err) + + _, _, err = libservice.CreateAndRegisterStaticServerAndSidecar(cluster.Agents[0], &libservice.ServiceOpts{ + ID: serviceTwoName, + Name: serviceTwoName, + Namespace: namespace, + HTTPPort: serviceTwoHTTPPort, + GRPCPort: serviceTwoGRPCPort, + }, + // customizes response code so we can distinguish between which service is responding + "-echo-server-default-params", fmt.Sprintf("status=%d", serviceTwoResponseCode), + ) + require.NoError(t, err) + + // write config entries + proxyDefaults := &api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(proxyDefaults)) + + apiGateway := &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: gatewayName, + Listeners: []api.APIGatewayListener{ + { + Name: "listener", + Port: listenerPort, + Protocol: "http", + }, + }, + Namespace: namespace, + } + + routeOne := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: routeOneName, + Namespace: namespace, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + "test.example", + }, + Rules: []api.HTTPRouteRule{ + { + Services: []api.HTTPService{ + { + Name: serviceOneName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: path1, + }, + }, + }, + }, + }, + } + + routeTwo := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: routeTwoName, + Namespace: namespace, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + }, + Rules: []api.HTTPRouteRule{ + { + Services: []api.HTTPService{ + { + Name: serviceTwoName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: path2, + }, + }, + { + Headers: []api.HTTPHeaderMatch{{ + Match: api.HTTPHeaderMatchExact, + Name: "x-v2", + Value: "v2", + }}, + }, + }, + }, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(apiGateway)) + require.NoError(t, cluster.ConfigEntryWrite(routeOne)) + require.NoError(t, cluster.ConfigEntryWrite(routeTwo)) + + // create gateway service + gwCfg := libservice.GatewayConfig{ + Name: gatewayName, + Kind: "api", + Namespace: namespace, + } + gatewayService, err := libservice.NewGatewayService(context.Background(), gwCfg, cluster.Agents[0], listenerPort) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, gatewayName, &api.QueryOptions{Namespace: namespace}) + + // make sure config entries have been properly created + checkGatewayConfigEntry(t, client, gatewayName, namespace) + t.Log("checking route one") + checkHTTPRouteConfigEntry(t, client, routeOneName, namespace) + checkHTTPRouteConfigEntry(t, client, routeTwoName, namespace) + + // gateway resolves routes + gatewayPort, err := gatewayService.GetPort(listenerPort) + require.NoError(t, err) + fmt.Println("Gateway Port: ", gatewayPort) + + // Same v2 path with and without header + checkRoute(t, gatewayPort, "/v2", map[string]string{ + "Host": "test.foo", + "x-v2": "v2", + }, checkOptions{statusCode: serviceTwoResponseCode, testName: "service2 header and path"}) + checkRoute(t, gatewayPort, "/v2", map[string]string{ + "Host": "test.foo", + }, checkOptions{statusCode: serviceTwoResponseCode, testName: "service2 just path match"}) + + // //v1 path with the header + checkRoute(t, gatewayPort, "/check", map[string]string{ + "Host": "test.foo", + "x-v2": "v2", + }, checkOptions{statusCode: serviceTwoResponseCode, testName: "service2 just header match"}) + + checkRoute(t, gatewayPort, "/v2/path/value", map[string]string{ + "Host": "test.foo", + "x-v2": "v2", + }, checkOptions{statusCode: serviceTwoResponseCode, testName: "service2 v2 with path"}) + + // hit service 1 by hitting root path + checkRoute(t, gatewayPort, "", map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: false, statusCode: serviceOneResponseCode, testName: "service1 root prefix"}) + + // hit service 1 by hitting v2 path with v1 hostname + checkRoute(t, gatewayPort, "/v2", map[string]string{ + "Host": "test.example", + }, checkOptions{debug: false, statusCode: serviceOneResponseCode, testName: "service1, v2 path with v2 hostname"}) +} + +func TestHTTPRoutePathRewrite(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + // infrastructure set up + listenerPort := 6009 + fooHTTPPort := 6010 + fooGRPCPort := 6011 + barHTTPPort := 6012 + barGRPCPort := 6013 + + fooName := randomName("foo", 16) + barName := randomName("bar", 16) + gatewayName := randomName("gw", 16) + invalidRouteName := randomName("route", 16) + validRouteName := randomName("route", 16) + + // create cluster + clusterConfig := &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + InjectGossipEncryption: true, + AllowHTTPAnyway: true, + }, + Ports: []int{ + listenerPort, + fooHTTPPort, + fooGRPCPort, + barHTTPPort, + barGRPCPort, + }, + ApplyDefaultProxySettings: true, + } + + cluster, _, _ := libtopology.NewCluster(t, clusterConfig) + client := cluster.APIClient(0) + + fooStatusCode := 400 + barStatusCode := 201 + fooPath := "/v1/foo" + barPath := "/v1/bar" + + namespace := getNamespace() + if namespace != "" { + ns := &api.Namespace{Name: namespace} + _, _, err := client.Namespaces().Create(ns, nil) + require.NoError(t, err) + } + + _, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(cluster.Agents[0], &libservice.ServiceOpts{ + ID: fooName, + Name: fooName, + Namespace: namespace, + HTTPPort: fooHTTPPort, + GRPCPort: fooGRPCPort, + }, + // customizes response code so we can distinguish between which service is responding + "-echo-debug-path", fooPath, + "-echo-server-default-params", fmt.Sprintf("status=%d", fooStatusCode), + ) + require.NoError(t, err) + + _, _, err = libservice.CreateAndRegisterStaticServerAndSidecar(cluster.Agents[0], &libservice.ServiceOpts{ + ID: barName, + Name: barName, + Namespace: namespace, + HTTPPort: barHTTPPort, + GRPCPort: barGRPCPort, + }, + // customizes response code so we can distinguish between which service is responding + "-echo-debug-path", barPath, + "-echo-server-default-params", fmt.Sprintf("status=%d", barStatusCode), + ) + require.NoError(t, err) + + fooUnrewritten := "/foo" + barUnrewritten := "/bar" + + // write config entries + proxyDefaults := &api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Namespace: "", // proxy-defaults can only be set in the default namespace + Config: map[string]interface{}{ + "protocol": "http", + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(proxyDefaults)) + + apiGateway := &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: gatewayName, + Listeners: []api.APIGatewayListener{ + { + Name: "listener", + Port: listenerPort, + Protocol: "http", + }, + }, + Namespace: namespace, + } + + fooRoute := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: invalidRouteName, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + }, + Namespace: namespace, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + URLRewrite: &api.URLRewrite{ + Path: fooPath, + }, + }, + Services: []api.HTTPService{ + { + Name: fooName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: fooUnrewritten, + }, + }, + }, + }, + }, + } + + barRoute := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: validRouteName, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + }, + Namespace: namespace, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + URLRewrite: &api.URLRewrite{ + Path: barPath, + }, + }, + Services: []api.HTTPService{ + { + Name: barName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: barUnrewritten, + }, + }, + }, + }, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(apiGateway)) + require.NoError(t, cluster.ConfigEntryWrite(fooRoute)) + require.NoError(t, cluster.ConfigEntryWrite(barRoute)) + + // create gateway service + gwCfg := libservice.GatewayConfig{ + Name: gatewayName, + Kind: "api", + Namespace: namespace, + } + gatewayService, err := libservice.NewGatewayService(context.Background(), gwCfg, cluster.Agents[0], listenerPort) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, gatewayName, &api.QueryOptions{Namespace: namespace}) + + // make sure config entries have been properly created + checkGatewayConfigEntry(t, client, gatewayName, namespace) + checkHTTPRouteConfigEntry(t, client, invalidRouteName, namespace) + checkHTTPRouteConfigEntry(t, client, validRouteName, namespace) + + gatewayPort, err := gatewayService.GetPort(listenerPort) + require.NoError(t, err) + + // TODO these were the assertions we had in the original test. potentially would want more test cases + + // NOTE: Hitting the debug path code overrides default expected value + debugExpectedStatusCode := 200 + + // hit foo, making sure path is being rewritten by hitting the debug page + checkRoute(t, gatewayPort, fooUnrewritten, map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: true, statusCode: debugExpectedStatusCode, testName: "foo service"}) + // make sure foo is being sent to proper service + checkRoute(t, gatewayPort, fooUnrewritten+"/foo", map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: false, statusCode: fooStatusCode, testName: "foo service 2"}) + + // hit bar, making sure its been rewritten + checkRoute(t, gatewayPort, barUnrewritten, map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: true, statusCode: debugExpectedStatusCode, testName: "bar service"}) + + // hit bar, making sure its being sent to the proper service + checkRoute(t, gatewayPort, barUnrewritten+"/bar", map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: false, statusCode: barStatusCode, testName: "bar service"}) +} + +func TestHTTPRouteParentRefChange(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + // infrastructure set up + address := "localhost" + + listenerOnePort := 6014 + listenerTwoPort := 6015 + serviceHTTPPort := 6016 + serviceGRPCPort := 6017 + + serviceName := randomName("service", 16) + gatewayOneName := randomName("gw1", 16) + gatewayTwoName := randomName("gw2", 16) + routeName := randomName("route", 16) + + // create cluster + clusterConfig := &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + InjectGossipEncryption: true, + AllowHTTPAnyway: true, + }, + Ports: []int{ + listenerOnePort, + listenerTwoPort, + serviceHTTPPort, + serviceGRPCPort, + }, + ApplyDefaultProxySettings: true, + } + + cluster, _, _ := libtopology.NewCluster(t, clusterConfig) + client := cluster.APIClient(0) + + // getNamespace() should always return an empty string in Consul OSS + namespace := getNamespace() + if namespace != "" { + ns := &api.Namespace{Name: namespace} + _, _, err := client.Namespaces().Create(ns, nil) + require.NoError(t, err) + } + + _, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(cluster.Agents[0], &libservice.ServiceOpts{ + ID: serviceName, + Name: serviceName, + Namespace: namespace, + HTTPPort: serviceHTTPPort, + GRPCPort: serviceGRPCPort, + }) + require.NoError(t, err) + + // write config entries + proxyDefaults := &api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(proxyDefaults)) + + // create gateway config entry + gatewayOne := &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: gatewayOneName, + Listeners: []api.APIGatewayListener{ + { + Name: "listener", + Port: listenerOnePort, + Protocol: "http", + Hostname: "test.foo", + }, + }, + Namespace: namespace, + } + require.NoError(t, cluster.ConfigEntryWrite(gatewayOne)) + checkGatewayConfigEntry(t, client, gatewayOneName, namespace) + + // create gateway service + gwOneCfg := libservice.GatewayConfig{ + Name: gatewayOneName, + Kind: "api", + Namespace: namespace, + } + gatewayOneService, err := libservice.NewGatewayService(context.Background(), gwOneCfg, cluster.Agents[0], listenerOnePort) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, gatewayOneName, &api.QueryOptions{Namespace: namespace}) + + // create gateway config entry + gatewayTwo := &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: gatewayTwoName, + Listeners: []api.APIGatewayListener{ + { + Name: "listener", + Port: listenerTwoPort, + Protocol: "http", + Hostname: "test.example", + }, + }, + Namespace: namespace, + } + require.NoError(t, cluster.ConfigEntryWrite(gatewayTwo)) + checkGatewayConfigEntry(t, client, gatewayTwoName, namespace) + + // create gateway service + gwTwoCfg := libservice.GatewayConfig{ + Name: gatewayTwoName, + Kind: "api", + Namespace: namespace, + } + gatewayTwoService, err := libservice.NewGatewayService(context.Background(), gwTwoCfg, cluster.Agents[0], listenerTwoPort) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, gatewayTwoName, &api.QueryOptions{Namespace: namespace}) + + // create route to service, targeting first gateway + route := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: routeName, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayOneName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + "test.example", + }, + Namespace: namespace, + Rules: []api.HTTPRouteRule{ + { + Services: []api.HTTPService{ + { + Name: serviceName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/", + }, + }, + }, + }, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(route)) + + require.Eventually(t, func() bool { + entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, routeName, &api.QueryOptions{Namespace: namespace}) + assert.NoError(t, err) + if entry == nil { + return false + } + + apiEntry := entry.(*api.HTTPRouteConfigEntry) + t.Log(entry) + + // check if bound only to correct gateway + return len(apiEntry.Parents) == 1 && + apiEntry.Parents[0].Name == gatewayOneName && + isBound(apiEntry.Status.Conditions) + }, time.Second*10, time.Second*1) + + // fetch gateway listener ports + gatewayOnePort, err := gatewayOneService.GetPort(listenerOnePort) + assert.NoError(t, err) + gatewayTwoPort, err := gatewayTwoService.GetPort(listenerTwoPort) + assert.NoError(t, err) + + // hit service by requesting root path + // TODO: testName field in checkOptions struct looked to be unused, is it needed? + checkRoute(t, gatewayOnePort, "", map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: false, statusCode: 200}) + + // check that second gateway does not resolve service + checkRouteError(t, address, gatewayTwoPort, "", map[string]string{ + "Host": "test.example", + }, "") + + // swtich route target to second gateway + route.Parents = []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayTwoName, + Namespace: namespace, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(route)) + require.Eventually(t, func() bool { + entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, routeName, &api.QueryOptions{Namespace: namespace}) + assert.NoError(t, err) + if entry == nil { + return false + } + + apiEntry := entry.(*api.HTTPRouteConfigEntry) + t.Log(apiEntry) + t.Log(fmt.Sprintf("%#v", apiEntry)) + + // check if bound only to correct gateway + return len(apiEntry.Parents) == 1 && + apiEntry.Parents[0].Name == gatewayTwoName && + isBound(apiEntry.Status.Conditions) + }, time.Second*10, time.Second*1) + + // hit service by requesting root path on other gateway with different hostname + checkRoute(t, gatewayTwoPort, "", map[string]string{ + "Host": "test.example", + }, checkOptions{debug: false, statusCode: 200}) + + // check that first gateway has stopped resolving service + checkRouteError(t, address, gatewayOnePort, "", map[string]string{ + "Host": "test.foo", + }, "") +} diff --git a/test/integration/consul-container/test/gateways/namespace_oss.go b/test/integration/consul-container/test/gateways/namespace_oss.go new file mode 100644 index 00000000000..3867e72987b --- /dev/null +++ b/test/integration/consul-container/test/gateways/namespace_oss.go @@ -0,0 +1,8 @@ +//go:build !consulent +// +build !consulent + +package gateways + +func getNamespace() string { + return "" +} diff --git a/test/integration/consul-container/test/observability/access_logs_test.go b/test/integration/consul-container/test/observability/access_logs_test.go index dcedb0de553..37a80c3563e 100644 --- a/test/integration/consul-container/test/observability/access_logs_test.go +++ b/test/integration/consul-container/test/observability/access_logs_test.go @@ -45,9 +45,14 @@ func TestAccessLogs(t *testing.T) { t.Skip() } - cluster, _, _ := topology.NewPeeringCluster(t, 1, &libcluster.BuildOptions{ - Datacenter: "dc1", - InjectAutoEncryption: true, + cluster, _, _ := topology.NewCluster(t, &topology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + ApplyDefaultProxySettings: true, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + }, }) // Turn on access logs. Do this before starting the sidecars so that they inherit the configuration @@ -64,13 +69,13 @@ func TestAccessLogs(t *testing.T) { require.NoError(t, err) require.True(t, set) - serverService, clientService := createServices(t, cluster) + serverService, clientService := topology.CreateServices(t, cluster) _, port := clientService.GetAddr() // Validate Custom JSON require.Eventually(t, func() bool { libassert.HTTPServiceEchoes(t, "localhost", port, "banana") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") client := libassert.ServiceLogContains(t, clientService, "\"banana_path\":\"/banana\"") server := libassert.ServiceLogContains(t, serverService, "\"banana_path\":\"/banana\"") return client && server @@ -112,7 +117,7 @@ func TestAccessLogs(t *testing.T) { _, port = clientService.GetAddr() require.Eventually(t, func() bool { libassert.HTTPServiceEchoes(t, "localhost", port, "orange") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") client := libassert.ServiceLogContains(t, clientService, "Orange you glad I didn't say banana: /orange, -") server := libassert.ServiceLogContains(t, serverService, "Orange you glad I didn't say banana: /orange, -") return client && server @@ -121,42 +126,3 @@ func TestAccessLogs(t *testing.T) { // TODO: add a test to check that connections without a matching filter chain are NOT logged } - -func createServices(t *testing.T, cluster *libcluster.Cluster) (libservice.Service, libservice.Service) { - node := cluster.Agents[0] - client := node.GetClient() - - // Register service as HTTP - serviceDefault := &api.ServiceConfigEntry{ - Kind: api.ServiceDefaults, - Name: libservice.StaticServerServiceName, - Protocol: "http", - } - - ok, _, err := client.ConfigEntries().Set(serviceDefault, nil) - require.NoError(t, err, "error writing HTTP service-default") - require.True(t, ok, "did not write HTTP service-default") - - // Create a service and proxy instance - serviceOpts := &libservice.ServiceOpts{ - Name: libservice.StaticServerServiceName, - ID: "static-server", - HTTPPort: 8080, - GRPCPort: 8079, - } - - // Create a service and proxy instance - _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts) - require.NoError(t, err) - - libassert.CatalogServiceExists(t, client, fmt.Sprintf("%s-sidecar-proxy", libservice.StaticServerServiceName)) - libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName) - - // Create a client proxy instance with the server as an upstream - clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false) - require.NoError(t, err) - - libassert.CatalogServiceExists(t, client, fmt.Sprintf("%s-sidecar-proxy", libservice.StaticClientServiceName)) - - return serverConnectProxy, clientConnectProxy -} diff --git a/test/integration/consul-container/test/peering/rotate_server_and_ca_then_fail_test.go b/test/integration/consul-container/test/peering/rotate_server_and_ca_then_fail_test.go index 223effa449b..5081433e249 100644 --- a/test/integration/consul-container/test/peering/rotate_server_and_ca_then_fail_test.go +++ b/test/integration/consul-container/test/peering/rotate_server_and_ca_then_fail_test.go @@ -50,7 +50,7 @@ import ( func TestPeering_RotateServerAndCAThenFail_(t *testing.T) { t.Parallel() - accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, utils.TargetVersion) + accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, utils.TargetVersion, false) var ( acceptingCluster = accepting.Cluster dialingCluster = dialing.Cluster @@ -94,7 +94,7 @@ func TestPeering_RotateServerAndCAThenFail_(t *testing.T) { _, port := clientSidecarService.GetAddr() libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") } testutil.RunStep(t, "rotate exporting cluster's root CA", func(t *testing.T) { @@ -144,7 +144,7 @@ func TestPeering_RotateServerAndCAThenFail_(t *testing.T) { // Connectivity should still be contained _, port := clientSidecarService.GetAddr() libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") verifySidecarHasTwoRootCAs(t, clientSidecarService) }) @@ -166,7 +166,7 @@ func TestPeering_RotateServerAndCAThenFail_(t *testing.T) { _, port := clientSidecarService.GetAddr() libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") }) } diff --git a/test/integration/consul-container/test/ratelimit/ratelimit_test.go b/test/integration/consul-container/test/ratelimit/ratelimit_test.go index bde1b44be9b..cb5c259eefe 100644 --- a/test/integration/consul-container/test/ratelimit/ratelimit_test.go +++ b/test/integration/consul-container/test/ratelimit/ratelimit_test.go @@ -46,6 +46,7 @@ func TestServerRequestRateLimit(t *testing.T) { description string cmd string operations []operation + mode string } getKV := action{ @@ -70,6 +71,7 @@ func TestServerRequestRateLimit(t *testing.T) { { description: "HTTP & net/RPC / Mode: disabled - errors: no / exceeded logs: no / metrics: no", cmd: `-hcl=limits { request_limits { mode = "disabled" read_rate = 0 write_rate = 0 }}`, + mode: "disabled", operations: []operation{ { action: putKV, @@ -88,6 +90,7 @@ func TestServerRequestRateLimit(t *testing.T) { { description: "HTTP & net/RPC / Mode: permissive - errors: no / exceeded logs: yes / metrics: yes", cmd: `-hcl=limits { request_limits { mode = "permissive" read_rate = 0 write_rate = 0 }}`, + mode: "permissive", operations: []operation{ { action: putKV, @@ -106,6 +109,7 @@ func TestServerRequestRateLimit(t *testing.T) { { description: "HTTP & net/RPC / Mode: enforcing - errors: yes / exceeded logs: yes / metrics: yes", cmd: `-hcl=limits { request_limits { mode = "enforcing" read_rate = 0 write_rate = 0 }}`, + mode: "enforcing", operations: []operation{ { action: putKV, @@ -154,7 +158,7 @@ func TestServerRequestRateLimit(t *testing.T) { // require.NoError(t, err) if metricsInfo != nil && err == nil { if op.expectMetric { - checkForMetric(r, metricsInfo, op.action.rateLimitOperation, op.action.rateLimitType) + checkForMetric(r, metricsInfo, op.action.rateLimitOperation, op.action.rateLimitType, tc.mode) } } @@ -171,17 +175,17 @@ func TestServerRequestRateLimit(t *testing.T) { } } -func checkForMetric(t *retry.R, metricsInfo *api.MetricsInfo, operationName string, expectedLimitType string) { - const counterName = "rpc.rate_limit.exceeded" +func checkForMetric(t *retry.R, metricsInfo *api.MetricsInfo, operationName string, expectedLimitType string, expectedMode string) { + const counterName = "consul.rpc.rate_limit.exceeded" var counter api.SampledValue for _, c := range metricsInfo.Counters { - if counter.Name == counterName { + if c.Name == counterName { counter = c break } } - require.NotNilf(t, counter, "counter not found: %s", counterName) + require.NotEmptyf(t, counter.Name, "counter not found: %s", counterName) operation, ok := counter.Labels["op"] require.True(t, ok) @@ -193,9 +197,9 @@ func checkForMetric(t *retry.R, metricsInfo *api.MetricsInfo, operationName stri require.True(t, ok) if operation == operationName { - require.Equal(t, 2, counter.Count) + require.GreaterOrEqual(t, counter.Count, 1) require.Equal(t, expectedLimitType, limitType) - require.Equal(t, "disabled", mode) + require.Equal(t, expectedMode, mode) } } diff --git a/test/integration/consul-container/test/snapshot/snapshot_restore_test.go b/test/integration/consul-container/test/snapshot/snapshot_restore_test.go new file mode 100644 index 00000000000..1d82b1cfb10 --- /dev/null +++ b/test/integration/consul-container/test/snapshot/snapshot_restore_test.go @@ -0,0 +1,97 @@ +package snapshot + +import ( + "fmt" + "github.com/hashicorp/consul/api" + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" + libtopology "github.com/hashicorp/consul/test/integration/consul-container/libs/topology" + "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + "github.com/stretchr/testify/require" + "io" + "testing" +) + +func TestSnapshotRestore(t *testing.T) { + + cases := []libcluster.LogStore{libcluster.LogStore_WAL, libcluster.LogStore_BoltDB} + + for _, c := range cases { + t.Run(fmt.Sprintf("test log store: %s", c), func(t *testing.T) { + testSnapShotRestoreForLogStore(t, c) + }) + } +} + +func testSnapShotRestoreForLogStore(t *testing.T, logStore libcluster.LogStore) { + + const ( + numServers = 3 + ) + + // Create initial cluster + cluster, _, _ := libtopology.NewCluster(t, &libtopology.ClusterConfig{ + NumServers: numServers, + NumClients: 0, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + ConsulImageName: utils.TargetImageName, + ConsulVersion: utils.TargetVersion, + LogStore: logStore, + }, + ApplyDefaultProxySettings: true, + }) + + client := cluster.APIClient(0) + libcluster.WaitForLeader(t, cluster, client) + libcluster.WaitForMembers(t, client, 3) + + for i := 0; i < 100; i++ { + _, err := client.KV().Put(&api.KVPair{Key: fmt.Sprintf("key-%d", i), Value: []byte(fmt.Sprintf("value-%d", i))}, nil) + require.NoError(t, err) + } + + var snapshot io.ReadCloser + var err error + snapshot, _, err = client.Snapshot().Save(nil) + require.NoError(t, err) + + err = cluster.Terminate() + require.NoError(t, err) + // Create a fresh cluster from scratch + cluster2, _, _ := libtopology.NewCluster(t, &libtopology.ClusterConfig{ + NumServers: numServers, + NumClients: 0, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + ConsulImageName: utils.TargetImageName, + ConsulVersion: utils.TargetVersion, + LogStore: logStore, + }, + ApplyDefaultProxySettings: true, + }) + client2 := cluster2.APIClient(0) + + libcluster.WaitForLeader(t, cluster2, client2) + libcluster.WaitForMembers(t, client2, 3) + + // Restore the saved snapshot + require.NoError(t, client2.Snapshot().Restore(nil, snapshot)) + + libcluster.WaitForLeader(t, cluster2, client2) + + followers, err := cluster2.Followers() + require.NoError(t, err) + require.Len(t, followers, 2) + + // use a follower api client and set `AllowStale` to true + // to test the follower snapshot install code path as well. + fc := followers[0].GetClient() + + for i := 0; i < 100; i++ { + kv, _, err := fc.KV().Get(fmt.Sprintf("key-%d", i), &api.QueryOptions{AllowStale: true}) + require.NoError(t, err) + require.Equal(t, kv.Key, fmt.Sprintf("key-%d", i)) + require.Equal(t, kv.Value, []byte(fmt.Sprintf("value-%d", i))) + } + +} diff --git a/test/integration/consul-container/test/troubleshoot/troubleshoot_upstream_test.go b/test/integration/consul-container/test/troubleshoot/troubleshoot_upstream_test.go new file mode 100644 index 00000000000..92f5583381f --- /dev/null +++ b/test/integration/consul-container/test/troubleshoot/troubleshoot_upstream_test.go @@ -0,0 +1,76 @@ +package troubleshoot + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/stretchr/testify/require" + + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" + libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" + "github.com/hashicorp/consul/test/integration/consul-container/libs/topology" +) + +func TestTroubleshootProxy(t *testing.T) { + t.Parallel() + cluster, _, _ := topology.NewCluster(t, &topology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + }, + ApplyDefaultProxySettings: true, + }) + + serverService, clientService := topology.CreateServices(t, cluster) + + clientSidecar, ok := clientService.(*libservice.ConnectContainer) + require.True(t, ok) + _, clientAdminPort := clientSidecar.GetInternalAdminAddr() + + t.Run("upstream exists and is healthy", func(t *testing.T) { + require.Eventually(t, func() bool { + output, err := clientSidecar.Exec(context.Background(), + []string{"consul", "troubleshoot", "upstreams", + "-envoy-admin-endpoint", fmt.Sprintf("localhost:%v", clientAdminPort)}) + require.NoError(t, err) + upstreamExists := assert.Contains(t, output, libservice.StaticServerServiceName) + + output, err = clientSidecar.Exec(context.Background(), []string{"consul", "troubleshoot", "proxy", + "-envoy-admin-endpoint", fmt.Sprintf("localhost:%v", clientAdminPort), + "-upstream-envoy-id", libservice.StaticServerServiceName}) + require.NoError(t, err) + certsValid := strings.Contains(output, "Certificates are valid") + noRejectedConfig := strings.Contains(output, "Envoy has 0 rejected configurations") + noConnFailure := strings.Contains(output, "Envoy has detected 0 connection failure(s)") + listenersExist := strings.Contains(output, fmt.Sprintf("Listener for upstream \"%s\" found", libservice.StaticServerServiceName)) + healthyEndpoints := strings.Contains(output, "Healthy endpoints for cluster") + return upstreamExists && certsValid && listenersExist && noRejectedConfig && noConnFailure && healthyEndpoints + }, 60*time.Second, 10*time.Second) + }) + + t.Run("terminate upstream and check if client sees it as unhealthy", func(t *testing.T) { + err := serverService.Terminate() + require.NoError(t, err) + + require.Eventually(t, func() bool { + output, err := clientSidecar.Exec(context.Background(), []string{"consul", "troubleshoot", "proxy", + "-envoy-admin-endpoint", fmt.Sprintf("localhost:%v", clientAdminPort), + "-upstream-envoy-id", libservice.StaticServerServiceName}) + require.NoError(t, err) + + certsValid := strings.Contains(output, "Certificates are valid") + noRejectedConfig := strings.Contains(output, "Envoy has 0 rejected configurations") + noConnFailure := strings.Contains(output, "Envoy has detected 0 connection failure(s)") + listenersExist := strings.Contains(output, fmt.Sprintf("Listener for upstream \"%s\" found", libservice.StaticServerServiceName)) + endpointUnhealthy := strings.Contains(output, "No healthy endpoints for cluster") + return certsValid && listenersExist && noRejectedConfig && noConnFailure && endpointUnhealthy + }, 60*time.Second, 10*time.Second) + }) +} diff --git a/test/integration/consul-container/test/upgrade/README.md b/test/integration/consul-container/test/upgrade/README.md index adffc5344ad..6815323af0d 100644 --- a/test/integration/consul-container/test/upgrade/README.md +++ b/test/integration/consul-container/test/upgrade/README.md @@ -1,17 +1,186 @@ -# Consul Upgrade Integration tests +# Upgrade Integration Tests -## Local run +- [Introduction](#introduction) + - [How it works](#how-it-works) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Running Upgrade integration tests](#running-upgrade-integration-tests) +- [Adding a new upgrade integration test](#adding-a-new-upgrade-integration-test) + - [Errors Test Cases](#errors-test-cases) +- [FAQS](#faqs) + + +## Introduction + +The goal of upgrade tests is to ensure problem-free upgrades on supported upgrade paths. At any given time, Consul supports the latest minor release, and two older minor releases, e.g. 1.15, 1.14, and 1.13. Upgrades to any higher version are permitted, including skipping a minor version e.g. from 1.13 to 1.15. + +The upgrade tests also aims to highlight errors that may occur as users attempt to upgrade their current version to a newer version. + +### How it works + +This diagram illustrates the deployment architecture of an upgrade test, where +two consul agents (one server and one client), a static-server, static-client, +and envoy sidecars are deployed. + +isolated + +> Note that all consul agents and user workloads such as application services, mesh-gateway are running in docker containers. + +In general, each upgrade test has following steps: +1. Create a cluster with a specified number of server and client agents, then enable the feature to be tested. +2. Create some workload in the cluster, e.g., registering 2 services: static-server, static-client. +Static-server is a simple http application and the upstream service of static-client. +3. Make additional configuration to the cluster. For example, configure Consul intention to deny +connection between static client and server. Ensure that a connection cannot be made. +4. Upgrade Consul cluster to the `target-version` and restart the Envoy sidecars +(we restart Envoy sidecar to ensure the upgraded Consul binary can read the state from +the previous version and generate the correct Envoy configurations) +5. Re-validate the client, server and sidecars to ensure the persisted data from the pervious +version can be accessed in the target version. Verify connection / disconnection +(e.g., deny Action) + +## Getting Started +### Prerequisites +To run the upgrade test, the following tools are required: +- install [Go](https://go.dev/) (the version should match that of our CI config's Go image). +- install [`golangci-lint`](https://golangci-lint.run/usage/install/) +- install [`Makefile`](https://www.gnu.org/software/make/manual/make.html) +- [`Docker`](https://docs.docker.com/get-docker/) required to run tests locally + +### Running Upgrade integration tests - run `make dev-docker` -- run the tests, e.g., `go test -run ^TestBasicConnectService$ ./test/basic -v` +- run the single test `go test -v -timeout 30m -run ^TestACL_Upgrade_Node_Token$ ./.../upgrade/` +- run all upgrade tests `go test -v -timeout 30m -run ./.../upgrade` -To specify targets and latest image pass `target-version` and `latest-version` +To specify targets and latest image pass `--target-version` and `--latest-version` to the tests. By default, it uses the `consul` docker image with respectively `local` and `latest` tags. To use dev consul image, pass `target-image` and `target-version`: - -target-image hashicorppreview/consul -target-version 1.14-dev + -target-image hashicorppreview/consul -target-version 1.15-dev By default, all container's logs are written to either `stdout`, or `stderr`; this makes it hard to debug, when the test case creates many containers. To disable following container logs, run the test with `-follow-log false`. + +Below are the supported CLI options +| Flags | Default value | Description | +| ----------- | ----------- | ----------- | +| --latest-image | `consul` in OSS, `hashicorp/consulenterprise` in ENT | Name of the Docker image to deploy initially. +| --latest-version | latest | Tag of the Docker image to deploy initially. +| --target-image | `consul` in OSS, `hashicorp/consulenterprise` in ENT | Name of the Docker image to upgrade to. +| --target-version | local | Tag of the Docker image to upgrade to. `local` is the tag built by `make dev-docker` above. +| -follow-log | true | Emit all container logs. These can be noisy, so we recommend `--follow-log=false` for local development. + + +## Adding a new upgrade integration test + +All upgrade tests are defined in [test/integration/consul-container/test/upgrade](/test/integration/consul-container/test/upgrade) subdirectory. The test framework uses +[functional table-driven tests in Go](https://yourbasic.org/golang/table-driven-unit-test/) and +using function types to modify the basic configuration for each test case. + +Following is a guide for adding a new upgrade test case. +1. Create consul cluster(s) with a specified version. Some utility functions are provided to make +a single cluster or two peered clusters: + +```go + // NewCluster creates a single cluster + cluster, _, _ := libtopology.NewCluster(t, &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + ConsulVersion: oldVersion, + }, + }) + + // BasicPeeringTwoClustersSetup creates two peered clusters, named accpeting and dialing + accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, oldVersion, false) +``` + +2. For tests with multiple test cases, it should always start by invoking +```go + type testcase struct { + name string + create func() + extraAssertion func() + } +``` +see example [here](./l7_traffic_management/resolver_default_subset_test.go). For upgrade tests with a single test case, they can be written like +```go + run := func(t *testing.T, oldVersion, targetVersion string) { + // insert test + } + t.Run(fmt.Sprintf("Upgrade from %s to %s", utils.LatestVersion, utils.TargetVersion), + func(t *testing.T) { + run(t, utils.LatestVersion, utils.TargetVersion) + }) +``` +see example [here](./acl_node_test.go) + +Addtitional configurations or user-workload can be created with a customized [`create` function](./l7_traffic_management/resolver_default_subset_test.go). + +3. Call the upgrade method and assert the upgrading cluster succeeds. +We also restart the envoy proxy to make sure the upgraded agent can generate +the correct envoy configurations. + +```go + err = cluster.StandardUpgrade(t, context.Background(), targetVersion) + require.NoError(t, err) + require.NoError(t, staticServerConnectProxy.Restart()) + require.NoError(t, staticClientConnectProxy.Restart()) +``` + +4. Verify the user workload after upgrade, e.g., + +```go + libassert.HTTPServiceEchoes(t, "localhost", port, "") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPort), "static-server-2-v2", "") +``` + +### Errors Test Cases +There are some caveats for special error handling of versions prior to `1.14`. +Upgrade tests for features such peering, had API changes that returns an error if attempt to upgrade, and should be accounted for in upgrade tests. If running upgrade tests for any version before `1.14`, the following lines of code needs to be added to skip test or it will not pass. + +```go + fromVersion, err := version.NewVersion(utils.LatestVersion) + require.NoError(t, err) + if fromVersion.LessThan(utils.Version_1_14) { + continue + } +``` +See example [here](https://github.com/hashicorp/consul-enterprise/blob/005a0a92c5f39804cef4ad5c4cd6fd3334b95aa2/test/integration/consul-container/test/upgrade/peering_control_plane_mgw_test.go#L92-L96) + +To write tests for bugs found during upgrades, see example on how to add a testcase for those scenarios [here](./fullstopupgrade_test.go). + +## FAQS + +**Q.** Are containers' ports (e.g., consul's 8500, envoy sidecar's admin port +or local upstream port) exposed on the docker host? \ +**A.** Yes, they are exposed. However, they are exposed through a [pod container](https://github.com/hashicorp/consul/blob/57e034b74621180861226a01efeb3e9cedc74d3a/test/integration/consul-container/libs/cluster/container.go#L132). +That is, a consul agent and the envoy proxy containers registered with the agent +share the [same Linux network namespace (i.e., they share `localhost`)](https://github.com/hashicorp/consul/blob/57e034b74621180861226a01efeb3e9cedc74d3a/test/integration/consul-container/libs/cluster/app.go#L23-L30) as the pod container. +The pod container use the same prefix as the consul agent in its name. + +**Q.** To troubleshoot, how can I send API request or consul command to the deployed cluster? \ +**A.** To send an API request or command to the deployed cluster, ensure that a cluster, services and sidecars have been created. See example below: +```go + cluster, _, _ := topology.NewCluster() + clientService := createServices(t, cluster) + _, port := clientService.GetAddr() + _, adminPort := clientService.GetAdminAddr() + ... + time.Sleep(900 * time.Second) + fmt.Println(port, adminPort) +``` +Then in your terminal `docker ps -a | grep consul` to get the running services and cluster. Exec in the cluster and run commands directly or make API request to `localhost:port` to relevant service or `localhost:adminPort` for envoy. + +**Q.** To troubleshoot, how can I access the envoy admin page? \ +**A.** To access envoy admin page, ensure that a cluster, services and sidecars have been created. Then get the adminPort for the client or server sidecar. See example on how to get the port above. Then navigate to a browser and go to the url `http://localhost:adminPort/` + +**Q.** My test is stuck with the error "could not start or join all agents: container 0: port not found"? \ +**A.** Simply re-run the tests. If the error persists, prune docker images `docker system prune`, run `make dev-docker`, then re-run tests again. + +**Q.** How to clean up the resources created the upgrade test? +**A.** Run the command `docker ps | grep consul` to find all left over resources, then `docker stop {CONTAINER_ID} && docker rm {CONTAINER_ID}` diff --git a/test/integration/consul-container/test/upgrade/acl_node_test.go b/test/integration/consul-container/test/upgrade/acl_node_test.go index 26095cf1748..2ad304c5278 100644 --- a/test/integration/consul-container/test/upgrade/acl_node_test.go +++ b/test/integration/consul-container/test/upgrade/acl_node_test.go @@ -36,11 +36,16 @@ func TestACL_Upgrade_Node_Token(t *testing.T) { run := func(t *testing.T, tc testcase) { // NOTE: Disable auto.encrypt due to its conflict with ACL token during bootstrap - cluster, _, _ := libtopology.NewPeeringCluster(t, 1, &libcluster.BuildOptions{ - Datacenter: "dc1", - ConsulVersion: tc.oldversion, - InjectAutoEncryption: false, - ACLEnabled: true, + cluster, _, _ := libtopology.NewCluster(t, &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + ConsulVersion: tc.oldversion, + InjectAutoEncryption: false, + ACLEnabled: true, + }, + ApplyDefaultProxySettings: true, }) agentToken, err := cluster.CreateAgentToken("dc1", diff --git a/test/integration/consul-container/test/upgrade/l7_traffic_management/resolver_default_subset_test.go b/test/integration/consul-container/test/upgrade/l7_traffic_management/resolver_default_subset_test.go new file mode 100644 index 00000000000..bcbefe937ee --- /dev/null +++ b/test/integration/consul-container/test/upgrade/l7_traffic_management/resolver_default_subset_test.go @@ -0,0 +1,426 @@ +package upgrade + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/hashicorp/consul/api" + libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" + libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" + libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" + "github.com/hashicorp/consul/test/integration/consul-container/libs/topology" + libutils "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + upgrade "github.com/hashicorp/consul/test/integration/consul-container/test/upgrade" + "github.com/hashicorp/go-version" + "github.com/stretchr/testify/require" +) + +// TestTrafficManagement_ServiceResolver tests that upgraded cluster inherits and interpret +// the resolver config entry correctly. +// +// The basic topology is a cluster with one static-client and one static-server. Addtional +// services and resolver can be added to the create func() for each test cases. +func TestTrafficManagement_ServiceResolver(t *testing.T) { + t.Parallel() + + type testcase struct { + name string + // create creates addtional resources in the cluster depending on cases, e.g., static-client, + // static server, and config-entries. It returns the proxy services of the client, an assertation + // function to be called to verify the resources, and a restartFn to be called after upgrade. + create func(*libcluster.Cluster, libservice.Service) (libservice.Service, func(), func(), error) + // extraAssertion adds additional assertion function to the common resources across cases. + // common resources includes static-client in dialing cluster, and static-server in accepting cluster. + // + // extraAssertion needs to be run before and after upgrade + extraAssertion func(libservice.Service) + } + tcs := []testcase{ + { + // Test resolver directs traffic to default subset + // - Create 2 additional static-server instances: one in V1 subset and the other in V2 subset + // - resolver directs traffic to the default subset, which is V2. + name: "resolver default subset", + create: func(cluster *libcluster.Cluster, clientConnectProxy libservice.Service) (libservice.Service, func(), func(), error) { + node := cluster.Agents[0] + client := node.GetClient() + + // Create static-server-v1 and static-server-v2 + serviceOptsV1 := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server-v1", + Meta: map[string]string{"version": "v1"}, + HTTPPort: 8081, + GRPCPort: 8078, + } + _, serverConnectProxyV1, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOptsV1) + require.NoError(t, err) + + serviceOptsV2 := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server-v2", + Meta: map[string]string{"version": "v2"}, + HTTPPort: 8082, + GRPCPort: 8077, + } + _, serverConnectProxyV2, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOptsV2) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, "static-server", nil) + + // TODO: verify the number of instance of static-server is 3 + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServerServiceName, true, 3) + + // Register service resolver + serviceResolver := &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: libservice.StaticServerServiceName, + DefaultSubset: "v2", + Subsets: map[string]api.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == v1", + }, + "v2": { + Filter: "Service.Meta.version == v2", + }, + }, + } + err = cluster.ConfigEntryWrite(serviceResolver) + if err != nil { + return nil, nil, nil, fmt.Errorf("error writing config entry %s", err) + } + + _, serverAdminPortV1 := serverConnectProxyV1.GetAdminAddr() + _, serverAdminPortV2 := serverConnectProxyV2.GetAdminAddr() + + restartFn := func() { + require.NoError(t, serverConnectProxyV1.Restart()) + require.NoError(t, serverConnectProxyV2.Restart()) + } + + _, adminPort := clientConnectProxy.GetAdminAddr() + assertionFn := func() { + libassert.AssertEnvoyRunning(t, serverAdminPortV1) + libassert.AssertEnvoyRunning(t, serverAdminPortV2) + + libassert.AssertEnvoyPresentsCertURI(t, serverAdminPortV1, "static-server") + libassert.AssertEnvoyPresentsCertURI(t, serverAdminPortV2, "static-server") + + libassert.AssertUpstreamEndpointStatus(t, adminPort, "v2.static-server.default", "HEALTHY", 1) + + // assert static-server proxies should be healthy + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServerServiceName, true, 3) + } + return nil, assertionFn, restartFn, nil + }, + extraAssertion: func(clientConnectProxy libservice.Service) { + _, port := clientConnectProxy.GetAddr() + _, adminPort := clientConnectProxy.GetAdminAddr() + + libassert.AssertUpstreamEndpointStatus(t, adminPort, "v2.static-server.default", "HEALTHY", 1) + + // static-client upstream should connect to static-server-v2 because the default subset value is to v2 set in the service resolver + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server-v2", "") + }, + }, + { + // Test resolver resolves service instance based on their check status + // - Create one addtional static-server with checks and V1 subset + // - resolver directs traffic to "test" service + name: "resolver default onlypassing", + create: func(cluster *libcluster.Cluster, clientConnectProxy libservice.Service) (libservice.Service, func(), func(), error) { + node := cluster.Agents[0] + + serviceOptsV1 := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server-v1", + Meta: map[string]string{"version": "v1"}, + HTTPPort: 8081, + GRPCPort: 8078, + Checks: libservice.Checks{ + Name: "main", + TTL: "30m", + }, + Connect: libservice.SidecarService{ + Port: 21011, + }, + } + _, serverConnectProxyV1, err := libservice.CreateAndRegisterStaticServerAndSidecarWithChecks(node, serviceOptsV1) + require.NoError(t, err) + + // Register service resolver + serviceResolver := &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: libservice.StaticServerServiceName, + DefaultSubset: "test", + Subsets: map[string]api.ServiceResolverSubset{ + "test": { + OnlyPassing: true, + }, + }, + ConnectTimeout: 120 * time.Second, + } + _, serverAdminPortV1 := serverConnectProxyV1.GetAdminAddr() + + restartFn := func() { + require.NoError(t, serverConnectProxyV1.Restart()) + } + + _, port := clientConnectProxy.GetAddr() + _, adminPort := clientConnectProxy.GetAdminAddr() + assertionFn := func() { + // force static-server-v1 into a warning state + err = node.GetClient().Agent().UpdateTTL("service:static-server-v1", "", "warn") + require.NoError(t, err) + + // ########################### + // ## with onlypassing=true + // assert only one static-server proxy is healthy + err = cluster.ConfigEntryWrite(serviceResolver) + require.NoError(t, err) + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServerServiceName, true, 1) + + libassert.AssertEnvoyRunning(t, serverAdminPortV1) + libassert.AssertEnvoyPresentsCertURI(t, serverAdminPortV1, "static-server") + + // assert static-server proxies should be healthy + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServerServiceName, true, 1) + + // static-client upstream should have 1 healthy endpoint for test.static-server + libassert.AssertUpstreamEndpointStatus(t, adminPort, "test.static-server.default", "HEALTHY", 1) + + // static-client upstream should have 1 unhealthy endpoint for test.static-server + libassert.AssertUpstreamEndpointStatus(t, adminPort, "test.static-server.default", "UNHEALTHY", 1) + + // static-client upstream should connect to static-server since it is passing + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), libservice.StaticServerServiceName, "") + + // ########################### + // ## with onlypassing=false + // revert to OnlyPassing=false by deleting the config + err = cluster.ConfigEntryDelete(serviceResolver) + require.NoError(t, err) + + // Consul health check assert only one static-server proxy is healthy when onlyPassing is false + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServerServiceName, false, 2) + + // Although the service status is in warning state, when onlypassing is set to false Envoy + // health check returns all service instances with "warning" or "passing" state as Healthy enpoints + libassert.AssertUpstreamEndpointStatus(t, adminPort, "static-server.default", "HEALTHY", 2) + + // static-client upstream should have 0 unhealthy endpoint for static-server + libassert.AssertUpstreamEndpointStatus(t, adminPort, "static-server.default", "UNHEALTHY", 0) + + } + return nil, assertionFn, restartFn, nil + }, + extraAssertion: func(clientConnectProxy libservice.Service) { + }, + }, + { + // Test resolver directs traffic to default subset + // - Create 3 static-server-2 server instances: one in V1, one in V2, one without any version + // - service2Resolver directs traffic to static-server-2-v2 + name: "resolver subset redirect", + create: func(cluster *libcluster.Cluster, clientConnectProxy libservice.Service) (libservice.Service, func(), func(), error) { + node := cluster.Agents[0] + client := node.GetClient() + + serviceOpts2 := &libservice.ServiceOpts{ + Name: libservice.StaticServer2ServiceName, + ID: "static-server-2", + HTTPPort: 8081, + GRPCPort: 8078, + } + _, server2ConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts2) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, libservice.StaticServer2ServiceName, nil) + + serviceOptsV1 := &libservice.ServiceOpts{ + Name: libservice.StaticServer2ServiceName, + ID: "static-server-2-v1", + Meta: map[string]string{"version": "v1"}, + HTTPPort: 8082, + GRPCPort: 8077, + } + _, server2ConnectProxyV1, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOptsV1) + require.NoError(t, err) + + serviceOptsV2 := &libservice.ServiceOpts{ + Name: libservice.StaticServer2ServiceName, + ID: "static-server-2-v2", + Meta: map[string]string{"version": "v2"}, + HTTPPort: 8083, + GRPCPort: 8076, + } + _, server2ConnectProxyV2, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOptsV2) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, libservice.StaticServer2ServiceName, nil) + + // Register static-server service resolver + serviceResolver := &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: libservice.StaticServer2ServiceName, + Subsets: map[string]api.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == v1", + }, + "v2": { + Filter: "Service.Meta.version == v2", + }, + }, + } + err = cluster.ConfigEntryWrite(serviceResolver) + require.NoError(t, err) + + // Register static-server-2 service resolver to redirect traffic + // from static-server to static-server-2-v2 + service2Resolver := &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: libservice.StaticServerServiceName, + Redirect: &api.ServiceResolverRedirect{ + Service: libservice.StaticServer2ServiceName, + ServiceSubset: "v2", + }, + } + err = cluster.ConfigEntryWrite(service2Resolver) + require.NoError(t, err) + + _, server2AdminPort := server2ConnectProxy.GetAdminAddr() + _, server2AdminPortV1 := server2ConnectProxyV1.GetAdminAddr() + _, server2AdminPortV2 := server2ConnectProxyV2.GetAdminAddr() + + restartFn := func() { + require.NoErrorf(t, server2ConnectProxy.Restart(), "%s", server2ConnectProxy.GetName()) + require.NoErrorf(t, server2ConnectProxyV1.Restart(), "%s", server2ConnectProxyV1.GetName()) + require.NoErrorf(t, server2ConnectProxyV2.Restart(), "%s", server2ConnectProxyV2.GetName()) + } + + assertionFn := func() { + // assert 3 static-server-2 instances are healthy + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServer2ServiceName, false, 3) + + libassert.AssertEnvoyRunning(t, server2AdminPort) + libassert.AssertEnvoyRunning(t, server2AdminPortV1) + libassert.AssertEnvoyRunning(t, server2AdminPortV2) + + libassert.AssertEnvoyPresentsCertURI(t, server2AdminPort, libservice.StaticServer2ServiceName) + libassert.AssertEnvoyPresentsCertURI(t, server2AdminPortV1, libservice.StaticServer2ServiceName) + libassert.AssertEnvoyPresentsCertURI(t, server2AdminPortV2, libservice.StaticServer2ServiceName) + + // assert static-server proxies should be healthy + libassert.AssertServiceHasHealthyInstances(t, node, libservice.StaticServer2ServiceName, true, 3) + } + return nil, assertionFn, restartFn, nil + }, + extraAssertion: func(clientConnectProxy libservice.Service) { + _, appPort := clientConnectProxy.GetAddr() + _, adminPort := clientConnectProxy.GetAdminAddr() + + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPort), "static-server-2-v2", "") + libassert.AssertUpstreamEndpointStatus(t, adminPort, "v2.static-server-2.default", "HEALTHY", 1) + }, + }, + } + + run := func(t *testing.T, tc testcase, oldVersion, targetVersion string) { + buildOpts := &libcluster.BuildOptions{ + ConsulVersion: oldVersion, + Datacenter: "dc1", + InjectAutoEncryption: true, + } + // If version < 1.14 disable AutoEncryption + oldVersionTmp, _ := version.NewVersion(oldVersion) + if oldVersionTmp.LessThan(libutils.Version_1_14) { + buildOpts.InjectAutoEncryption = false + } + cluster, _, _ := topology.NewCluster(t, &topology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: buildOpts, + ApplyDefaultProxySettings: true, + }) + node := cluster.Agents[0] + client := node.GetClient() + + staticClientProxy, staticServerProxy, err := createStaticClientAndServer(cluster) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName, nil) + libassert.CatalogServiceExists(t, client, fmt.Sprintf("%s-sidecar-proxy", libservice.StaticClientServiceName), nil) + + err = cluster.ConfigEntryWrite(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: "global", + Config: map[string]interface{}{ + "protocol": "http", + }, + }) + require.NoError(t, err) + + _, port := staticClientProxy.GetAddr() + _, adminPort := staticClientProxy.GetAdminAddr() + _, serverAdminPort := staticServerProxy.GetAdminAddr() + libassert.HTTPServiceEchoes(t, "localhost", port, "") + libassert.AssertEnvoyPresentsCertURI(t, adminPort, libservice.StaticClientServiceName) + libassert.AssertEnvoyPresentsCertURI(t, serverAdminPort, libservice.StaticServerServiceName) + + _, assertionAdditionalResources, restartFn, err := tc.create(cluster, staticClientProxy) + require.NoError(t, err) + // validate client and proxy is up and running + libassert.AssertContainerState(t, staticClientProxy, "running") + assertionAdditionalResources() + tc.extraAssertion(staticClientProxy) + + // Upgrade cluster, restart sidecars then begin service traffic validation + require.NoError(t, cluster.StandardUpgrade(t, context.Background(), targetVersion)) + require.NoError(t, staticClientProxy.Restart()) + require.NoError(t, staticServerProxy.Restart()) + restartFn() + + // POST upgrade validation; repeat client & proxy validation + libassert.HTTPServiceEchoes(t, "localhost", port, "") + libassert.AssertEnvoyRunning(t, adminPort) + libassert.AssertEnvoyRunning(t, serverAdminPort) + + // certs are valid + libassert.AssertEnvoyPresentsCertURI(t, adminPort, libservice.StaticClientServiceName) + libassert.AssertEnvoyPresentsCertURI(t, serverAdminPort, libservice.StaticServerServiceName) + + assertionAdditionalResources() + tc.extraAssertion(staticClientProxy) + } + + targetVersion := libutils.TargetVersion + for _, oldVersion := range upgrade.UpgradeFromVersions { + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s upgrade from %s to %s", tc.name, oldVersion, targetVersion), + func(t *testing.T) { + run(t, tc, oldVersion, targetVersion) + }) + } + } +} + +// createStaticClientAndServer creates a static-client and a static-server in the cluster +func createStaticClientAndServer(cluster *libcluster.Cluster) (libservice.Service, libservice.Service, error) { + node := cluster.Agents[0] + serviceOpts := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server", + HTTPPort: 8080, + GRPCPort: 8079, + } + _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts) + if err != nil { + return nil, nil, err + } + + // Create a client proxy instance with the server as an upstream + clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false) + if err != nil { + return nil, nil, err + } + + return clientConnectProxy, serverConnectProxy, nil +} diff --git a/test/integration/consul-container/test/upgrade/peering_control_plane_mgw_test.go b/test/integration/consul-container/test/upgrade/peering_control_plane_mgw_test.go index f4112b6f6b8..f9407d60055 100644 --- a/test/integration/consul-container/test/upgrade/peering_control_plane_mgw_test.go +++ b/test/integration/consul-container/test/upgrade/peering_control_plane_mgw_test.go @@ -42,7 +42,7 @@ func TestPeering_Upgrade_ControlPlane_MGW(t *testing.T) { } run := func(t *testing.T, tc testcase) { - accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, tc.oldversion) + accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, tc.oldversion, true) var ( acceptingCluster = accepting.Cluster dialingCluster = dialing.Cluster @@ -54,19 +54,6 @@ func TestPeering_Upgrade_ControlPlane_MGW(t *testing.T) { acceptingClient, err := acceptingCluster.GetClient(nil, false) require.NoError(t, err) - // Enable peering control plane traffic through mesh gateway - req := &api.MeshConfigEntry{ - Peering: &api.PeeringMeshConfig{ - PeerThroughMeshGateways: true, - }, - } - ok, _, err := dialingClient.ConfigEntries().Set(req, &api.WriteOptions{}) - require.True(t, ok) - require.NoError(t, err) - ok, _, err = acceptingClient.ConfigEntries().Set(req, &api.WriteOptions{}) - require.True(t, ok) - require.NoError(t, err) - // Verify control plane endpoints and traffic in gateway _, gatewayAdminPort := dialing.Gateway.GetAdminAddr() libassert.AssertUpstreamEndpointStatus(t, gatewayAdminPort, "server.dc1.peering", "HEALTHY", 1) @@ -74,6 +61,9 @@ func TestPeering_Upgrade_ControlPlane_MGW(t *testing.T) { libassert.AssertEnvoyMetricAtLeast(t, gatewayAdminPort, "cluster.static-server.default.default.accepting-to-dialer.external", "upstream_cx_total", 1) + libassert.AssertEnvoyMetricAtLeast(t, gatewayAdminPort, + "cluster.server.dc1.peering", + "upstream_cx_total", 1) // Upgrade the accepting cluster and assert peering is still ACTIVE require.NoError(t, acceptingCluster.StandardUpgrade(t, context.Background(), tc.targetVersion)) @@ -90,11 +80,12 @@ func TestPeering_Upgrade_ControlPlane_MGW(t *testing.T) { // - Register a new static-client service in dialing cluster and // - set upstream to static-server service in peered cluster - // Restart the gateway & proxy sidecar + // Stop the accepting gateway and restart dialing gateway + // to force peering control plane traffic through dialing mesh gateway + require.NoError(t, accepting.Gateway.Stop()) require.NoError(t, dialing.Gateway.Restart()) - require.NoError(t, dialing.Container.Restart()) - // Restarted gateway should not have any measurement on data plane traffic + // Restarted dialing gateway should not have any measurement on data plane traffic libassert.AssertEnvoyMetricAtMost(t, gatewayAdminPort, "cluster.static-server.default.default.accepting-to-dialer.external", "upstream_cx_total", 0) @@ -102,6 +93,7 @@ func TestPeering_Upgrade_ControlPlane_MGW(t *testing.T) { libassert.AssertEnvoyMetricAtLeast(t, gatewayAdminPort, "cluster.server.dc1.peering", "upstream_cx_total", 1) + require.NoError(t, accepting.Gateway.Start()) clientSidecarService, err := libservice.CreateAndRegisterStaticClientSidecar(dialingCluster.Servers()[0], libtopology.DialingPeerName, true) require.NoError(t, err) @@ -110,7 +102,7 @@ func TestPeering_Upgrade_ControlPlane_MGW(t *testing.T) { require.NoError(t, clientSidecarService.Restart()) libassert.AssertUpstreamEndpointStatus(t, adminPort, fmt.Sprintf("static-server.default.%s.external", libtopology.DialingPeerName), "HEALTHY", 1) libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") } for _, tc := range tcs { diff --git a/test/integration/consul-container/test/upgrade/peering_http_test.go b/test/integration/consul-container/test/upgrade/peering_http_test.go index aec03a3edb4..3f7084fad47 100644 --- a/test/integration/consul-container/test/upgrade/peering_http_test.go +++ b/test/integration/consul-container/test/upgrade/peering_http_test.go @@ -21,10 +21,15 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { t.Parallel() type testcase struct { - oldversion string - targetVersion string - name string - create func(*cluster.Cluster) (libservice.Service, error) + oldversion string + targetVersion string + name string + // create creates addtional resources in peered clusters depending on cases, e.g., static-client, + // static server, and config-entries. It returns the proxy services, an assertation function to + // be called to verify the resources. + create func(*cluster.Cluster, *cluster.Cluster) (libservice.Service, libservice.Service, func(), error) + // extraAssertion adds additional assertion function to the common resources across cases. + // common resources includes static-client in dialing cluster, and static-server in accepting cluster. extraAssertion func(int) } tcs := []testcase{ @@ -38,8 +43,8 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { oldversion: "1.14", targetVersion: utils.TargetVersion, name: "basic", - create: func(c *cluster.Cluster) (libservice.Service, error) { - return nil, nil + create: func(accepting *cluster.Cluster, dialing *cluster.Cluster) (libservice.Service, libservice.Service, func(), error) { + return nil, nil, func() {}, nil }, extraAssertion: func(clientUpstreamPort int) {}, }, @@ -49,7 +54,8 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { name: "http_router", // Create a second static-service at the client agent of accepting cluster and // a service-router that routes /static-server-2 to static-server-2 - create: func(c *cluster.Cluster) (libservice.Service, error) { + create: func(accepting *cluster.Cluster, dialing *cluster.Cluster) (libservice.Service, libservice.Service, func(), error) { + c := accepting serviceOpts := &libservice.ServiceOpts{ Name: libservice.StaticServer2ServiceName, ID: "static-server-2", @@ -58,10 +64,11 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { GRPCPort: 8078, } _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(c.Clients()[0], serviceOpts) - libassert.CatalogServiceExists(t, c.Clients()[0].GetClient(), libservice.StaticServer2ServiceName) if err != nil { - return nil, err + return nil, nil, nil, err } + libassert.CatalogServiceExists(t, c.Clients()[0].GetClient(), libservice.StaticServer2ServiceName, nil) + err = c.ConfigEntryWrite(&api.ProxyConfigEntry{ Kind: api.ProxyDefaults, Name: "global", @@ -70,7 +77,7 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { }, }) if err != nil { - return nil, err + return nil, nil, nil, err } routerConfigEntry := &api.ServiceRouterConfigEntry{ Kind: api.ServiceRouter, @@ -90,16 +97,227 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { }, } err = c.ConfigEntryWrite(routerConfigEntry) - return serverConnectProxy, err + return serverConnectProxy, nil, func() {}, err }, extraAssertion: func(clientUpstreamPort int) { - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d/static-server-2", clientUpstreamPort), "static-server-2") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d/static-server-2", clientUpstreamPort), "static-server-2", "") + }, + }, + { + oldversion: "1.14", + targetVersion: utils.TargetVersion, + name: "http splitter and resolver", + // In addtional to the basic topology, this case provisions the following + // services in the dialing cluster: + // + // - a new static-client at server_0 that has two upstreams: split-static-server (5000) + // and peer-static-server (5001) + // - a local static-server service at client_0 + // - service-splitter named split-static-server w/ 2 services: "local-static-server" and + // "peer-static-server". + // - service-resolved named local-static-server + // - service-resolved named peer-static-server + create: func(accepting *cluster.Cluster, dialing *cluster.Cluster) (libservice.Service, libservice.Service, func(), error) { + err := dialing.ConfigEntryWrite(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: "global", + Config: map[string]interface{}{ + "protocol": "http", + }, + }) + if err != nil { + return nil, nil, nil, err + } + + clientConnectProxy, err := createAndRegisterStaticClientSidecarWith2Upstreams(dialing, + []string{"split-static-server", "peer-static-server"}, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("error creating client connect proxy in cluster %s", dialing.NetworkName) + } + + // make a resolver for service peer-static-server + resolverConfigEntry := &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: "peer-static-server", + Redirect: &api.ServiceResolverRedirect{ + Service: libservice.StaticServerServiceName, + Peer: libtopology.DialingPeerName, + }, + } + err = dialing.ConfigEntryWrite(resolverConfigEntry) + if err != nil { + return nil, nil, nil, fmt.Errorf("error writing resolver config entry for %s", resolverConfigEntry.Name) + } + + // make a splitter for service split-static-server + splitter := &api.ServiceSplitterConfigEntry{ + Kind: api.ServiceSplitter, + Name: "split-static-server", + Splits: []api.ServiceSplit{ + { + Weight: 50, + Service: "local-static-server", + ResponseHeaders: &api.HTTPHeaderModifiers{ + Set: map[string]string{ + "x-test-split": "local", + }, + }, + }, + { + Weight: 50, + Service: "peer-static-server", + ResponseHeaders: &api.HTTPHeaderModifiers{ + Set: map[string]string{ + "x-test-split": "peer", + }, + }, + }, + }, + } + err = dialing.ConfigEntryWrite(splitter) + if err != nil { + return nil, nil, nil, fmt.Errorf("error writing splitter config entry for %s", splitter.Name) + } + + // make a resolver for service local-static-server + resolverConfigEntry = &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: "local-static-server", + Redirect: &api.ServiceResolverRedirect{ + Service: libservice.StaticServerServiceName, + }, + } + err = dialing.ConfigEntryWrite(resolverConfigEntry) + if err != nil { + return nil, nil, nil, fmt.Errorf("error writing resolver config entry for %s", resolverConfigEntry.Name) + } + + // Make a static-server in dialing cluster + serviceOpts := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server", + HTTPPort: 8081, + GRPCPort: 8078, + } + _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(dialing.Clients()[0], serviceOpts) + libassert.CatalogServiceExists(t, dialing.Clients()[0].GetClient(), libservice.StaticServerServiceName, nil) + if err != nil { + return nil, nil, nil, err + } + + _, appPorts := clientConnectProxy.GetAddrs() + assertionFn := func() { + libassert.HTTPServiceEchoesResHeader(t, "localhost", appPorts[0], "", map[string]string{ + "X-Test-Split": "local", + }) + libassert.HTTPServiceEchoesResHeader(t, "localhost", appPorts[0], "", map[string]string{ + "X-Test-Split": "peer", + }) + libassert.HTTPServiceEchoes(t, "localhost", appPorts[0], "") + } + return serverConnectProxy, clientConnectProxy, assertionFn, nil + }, + extraAssertion: func(clientUpstreamPort int) {}, + }, + { + oldversion: "1.14", + targetVersion: utils.TargetVersion, + name: "http resolver and failover", + // Verify resolver and failover can direct traffic to server in peered cluster + // In addtional to the basic topology, this case provisions the following + // services in the dialing cluster: + // + // - a new static-client at server_0 that has two upstreams: static-server (5000) + // and peer-static-server (5001) + // - a local static-server service at client_0 + // - service-resolved named static-server with failover to static-server in accepting cluster + // - service-resolved named peer-static-server to static-server in accepting cluster + create: func(accepting *cluster.Cluster, dialing *cluster.Cluster) (libservice.Service, libservice.Service, func(), error) { + err := dialing.ConfigEntryWrite(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: "global", + Config: map[string]interface{}{ + "protocol": "http", + }, + }) + if err != nil { + return nil, nil, nil, err + } + + clientConnectProxy, err := createAndRegisterStaticClientSidecarWith2Upstreams(dialing, + []string{"static-server", "peer-static-server"}, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("error creating client connect proxy in cluster %s", dialing.NetworkName) + } + + // make a resolver for service peer-static-server + resolverConfigEntry := &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: "peer-static-server", + Redirect: &api.ServiceResolverRedirect{ + Service: libservice.StaticServerServiceName, + Peer: libtopology.DialingPeerName, + }, + } + err = dialing.ConfigEntryWrite(resolverConfigEntry) + if err != nil { + return nil, nil, nil, fmt.Errorf("error writing resolver config entry for %s", resolverConfigEntry.Name) + } + + // make a resolver for service static-server + resolverConfigEntry = &api.ServiceResolverConfigEntry{ + Kind: api.ServiceResolver, + Name: "static-server", + Failover: map[string]api.ServiceResolverFailover{ + "*": { + Targets: []api.ServiceResolverFailoverTarget{ + { + Peer: libtopology.DialingPeerName, + }, + }, + }, + }, + } + err = dialing.ConfigEntryWrite(resolverConfigEntry) + if err != nil { + return nil, nil, nil, fmt.Errorf("error writing resolver config entry for %s", resolverConfigEntry.Name) + } + + // Make a static-server in dialing cluster + serviceOpts := &libservice.ServiceOpts{ + Name: libservice.StaticServerServiceName, + ID: "static-server-dialing", + HTTPPort: 8081, + GRPCPort: 8078, + } + _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(dialing.Clients()[0], serviceOpts) + libassert.CatalogServiceExists(t, dialing.Clients()[0].GetClient(), libservice.StaticServerServiceName, nil) + if err != nil { + return nil, nil, nil, err + } + + _, appPorts := clientConnectProxy.GetAddrs() + assertionFn := func() { + // assert traffic can fail-over to static-server in peered cluster and restor to local static-server + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPorts[0]), "static-server-dialing", "") + require.NoError(t, serverConnectProxy.Stop()) + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPorts[0]), "static-server", "") + require.NoError(t, serverConnectProxy.Start()) + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPorts[0]), "static-server-dialing", "") + + // assert peer-static-server resolves to static-server in peered cluster + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPorts[1]), "static-server", "") + } + return serverConnectProxy, clientConnectProxy, assertionFn, nil }, + extraAssertion: func(clientUpstreamPort int) {}, }, } run := func(t *testing.T, tc testcase) { - accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, tc.oldversion) + accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, tc.oldversion, false) var ( acceptingCluster = accepting.Cluster dialingCluster = dialing.Cluster @@ -115,8 +333,9 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { _, staticClientPort := dialing.Container.GetAddr() _, appPort := dialing.Container.GetAddr() - _, err = tc.create(acceptingCluster) + _, secondClientProxy, assertionAdditionalResources, err := tc.create(acceptingCluster, dialingCluster) require.NoError(t, err) + assertionAdditionalResources() tc.extraAssertion(appPort) // Upgrade the accepting cluster and assert peering is still ACTIVE @@ -145,13 +364,19 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { require.NoError(t, accepting.Container.Restart()) libassert.HTTPServiceEchoes(t, "localhost", staticClientPort, "") + // restart the secondClientProxy if exist + if secondClientProxy != nil { + require.NoError(t, secondClientProxy.Restart()) + } + assertionAdditionalResources() + clientSidecarService, err := libservice.CreateAndRegisterStaticClientSidecar(dialingCluster.Servers()[0], libtopology.DialingPeerName, true) require.NoError(t, err) _, port := clientSidecarService.GetAddr() _, adminPort := clientSidecarService.GetAdminAddr() libassert.AssertUpstreamEndpointStatus(t, adminPort, fmt.Sprintf("static-server.default.%s.external", libtopology.DialingPeerName), "HEALTHY", 1) libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server") + libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server", "") // TODO: restart static-server-2's sidecar tc.extraAssertion(appPort) @@ -165,3 +390,69 @@ func TestPeering_UpgradeToTarget_fromLatest(t *testing.T) { // time.Sleep(3 * time.Second) } } + +// createAndRegisterStaticClientSidecarWith2Upstreams creates a static-client that +// has two upstreams connecting to destinationNames: local bind addresses are 5000 +// and 5001. +func createAndRegisterStaticClientSidecarWith2Upstreams(c *cluster.Cluster, destinationNames []string) (*libservice.ConnectContainer, error) { + // Do some trickery to ensure that partial completion is correctly torn + // down, but successful execution is not. + var deferClean utils.ResettableDefer + defer deferClean.Execute() + + node := c.Servers()[0] + mgwMode := api.MeshGatewayModeLocal + + // Register the static-client service and sidecar first to prevent race with sidecar + // trying to get xDS before it's ready + req := &api.AgentServiceRegistration{ + Name: libservice.StaticClientServiceName, + Port: 8080, + Connect: &api.AgentServiceConnect{ + SidecarService: &api.AgentServiceRegistration{ + Proxy: &api.AgentServiceConnectProxyConfig{ + Upstreams: []api.Upstream{ + { + DestinationName: destinationNames[0], + LocalBindAddress: "0.0.0.0", + LocalBindPort: cluster.ServiceUpstreamLocalBindPort, + MeshGateway: api.MeshGatewayConfig{ + Mode: mgwMode, + }, + }, + { + DestinationName: destinationNames[1], + LocalBindAddress: "0.0.0.0", + LocalBindPort: cluster.ServiceUpstreamLocalBindPort2, + MeshGateway: api.MeshGatewayConfig{ + Mode: mgwMode, + }, + }, + }, + }, + }, + }, + } + + if err := node.GetClient().Agent().ServiceRegister(req); err != nil { + return nil, err + } + + // Create a service and proxy instance + sidecarCfg := libservice.SidecarConfig{ + Name: fmt.Sprintf("%s-sidecar", libservice.StaticClientServiceName), + ServiceID: libservice.StaticClientServiceName, + } + clientConnectProxy, err := libservice.NewConnectService(context.Background(), sidecarCfg, []int{cluster.ServiceUpstreamLocalBindPort, cluster.ServiceUpstreamLocalBindPort2}, node) + if err != nil { + return nil, err + } + deferClean.Add(func() { + _ = clientConnectProxy.Terminate() + }) + + // disable cleanup functions now that we have an object with a Terminate() function + deferClean.Reset() + + return clientConnectProxy, nil +} diff --git a/test/integration/consul-container/test/upgrade/traffic_management_default_subset_test.go b/test/integration/consul-container/test/upgrade/traffic_management_default_subset_test.go deleted file mode 100644 index 5ff574e77e9..00000000000 --- a/test/integration/consul-container/test/upgrade/traffic_management_default_subset_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package upgrade - -import ( - "context" - "fmt" - "net/http" - "testing" - - "github.com/hashicorp/consul/api" - libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" - libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster" - libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" - "github.com/hashicorp/consul/test/integration/consul-container/libs/topology" - "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" - libutils "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" - "github.com/hashicorp/go-version" - "github.com/stretchr/testify/require" - "gotest.tools/assert" -) - -// TestTrafficManagement_Upgrade Summary -// This test starts up 3 servers and 1 client in the same datacenter. -// -// Steps: -// - Create a single agent cluster. -// - Create one static-server and 2 subsets and 1 client and sidecar, then register them with Consul -// - Validate static-server and 2 subsets are and proxy admin endpoint is healthy - 3 instances -// - Validate static servers proxy listeners should be up and have right certs -func TestTrafficManagement_ServiceWithSubsets(t *testing.T) { - t.Parallel() - - var responseFormat = map[string]string{"format": "json"} - - type testcase struct { - oldversion string - targetVersion string - } - tcs := []testcase{ - { - oldversion: "1.13", - targetVersion: utils.TargetVersion, - }, - { - oldversion: "1.14", - targetVersion: utils.TargetVersion, - }, - } - - run := func(t *testing.T, tc testcase) { - buildOpts := &libcluster.BuildOptions{ - ConsulVersion: tc.oldversion, - Datacenter: "dc1", - InjectAutoEncryption: true, - } - // If version < 1.14 disable AutoEncryption - oldVersion, _ := version.NewVersion(tc.oldversion) - if oldVersion.LessThan(libutils.Version_1_14) { - buildOpts.InjectAutoEncryption = false - } - cluster, _, _ := topology.NewPeeringCluster(t, 1, buildOpts) - - // Register service resolver - serviceResolver := &api.ServiceResolverConfigEntry{ - Kind: api.ServiceResolver, - Name: libservice.StaticServerServiceName, - DefaultSubset: "v2", - Subsets: map[string]api.ServiceResolverSubset{ - "v1": { - Filter: "Service.Meta.version == v1", - }, - "v2": { - Filter: "Service.Meta.version == v2", - }, - }, - } - err := cluster.ConfigEntryWrite(serviceResolver) - require.NoError(t, err) - - serverConnectProxy, serverConnectProxyV1, serverConnectProxyV2, clientConnectProxy := createService(t, cluster) - - _, port := clientConnectProxy.GetAddr() - _, adminPort := clientConnectProxy.GetAdminAddr() - _, serverAdminPort := serverConnectProxy.GetAdminAddr() - _, serverAdminPortV1 := serverConnectProxyV1.GetAdminAddr() - _, serverAdminPortV2 := serverConnectProxyV2.GetAdminAddr() - - // validate client and proxy is up and running - libassert.AssertContainerState(t, clientConnectProxy, "running") - - libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertUpstreamEndpointStatus(t, adminPort, "v2.static-server.default", "HEALTHY", 1) - - // Upgrade cluster, restart sidecars then begin service traffic validation - require.NoError(t, cluster.StandardUpgrade(t, context.Background(), tc.targetVersion)) - require.NoError(t, clientConnectProxy.Restart()) - require.NoError(t, serverConnectProxy.Restart()) - require.NoError(t, serverConnectProxyV1.Restart()) - require.NoError(t, serverConnectProxyV2.Restart()) - - // POST upgrade validation; repeat client & proxy validation - libassert.HTTPServiceEchoes(t, "localhost", port, "") - libassert.AssertUpstreamEndpointStatus(t, adminPort, "v2.static-server.default", "HEALTHY", 1) - - // validate static-client proxy admin is up - _, statusCode, err := libassert.GetEnvoyOutput(adminPort, "stats", responseFormat) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, statusCode, fmt.Sprintf("service cannot be reached %v", statusCode)) - - // validate static-server proxy admin is up - _, statusCode1, err := libassert.GetEnvoyOutput(serverAdminPort, "stats", responseFormat) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, statusCode1, fmt.Sprintf("service cannot be reached %v", statusCode1)) - - // validate static-server-v1 proxy admin is up - _, statusCode2, err := libassert.GetEnvoyOutput(serverAdminPortV1, "stats", responseFormat) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, statusCode2, fmt.Sprintf("service cannot be reached %v", statusCode2)) - - // validate static-server-v2 proxy admin is up - _, statusCode3, err := libassert.GetEnvoyOutput(serverAdminPortV2, "stats", responseFormat) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, statusCode3, fmt.Sprintf("service cannot be reached %v", statusCode3)) - - // certs are valid - libassert.AssertEnvoyPresentsCertURI(t, adminPort, "static-client") - libassert.AssertEnvoyPresentsCertURI(t, serverAdminPort, "static-server") - libassert.AssertEnvoyPresentsCertURI(t, serverAdminPortV1, "static-server") - libassert.AssertEnvoyPresentsCertURI(t, serverAdminPortV2, "static-server") - - // static-client upstream should connect to static-server-v2 because the default subset value is to v2 set in the service resolver - libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", port), "static-server-v2") - } - - for _, tc := range tcs { - t.Run(fmt.Sprintf("upgrade from %s to %s", tc.oldversion, tc.targetVersion), - func(t *testing.T) { - run(t, tc) - }) - } -} - -// create 3 servers and 1 client -func createService(t *testing.T, cluster *libcluster.Cluster) (libservice.Service, libservice.Service, libservice.Service, libservice.Service) { - node := cluster.Agents[0] - client := node.GetClient() - - serviceOpts := &libservice.ServiceOpts{ - Name: libservice.StaticServerServiceName, - ID: "static-server", - HTTPPort: 8080, - GRPCPort: 8079, - } - _, serverConnectProxy, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts) - libassert.CatalogServiceExists(t, client, "static-server") - require.NoError(t, err) - - serviceOptsV1 := &libservice.ServiceOpts{ - Name: libservice.StaticServerServiceName, - ID: "static-server-v1", - Meta: map[string]string{"version": "v1"}, - HTTPPort: 8081, - GRPCPort: 8078, - } - _, serverConnectProxyV1, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOptsV1) - libassert.CatalogServiceExists(t, client, "static-server") - require.NoError(t, err) - - serviceOptsV2 := &libservice.ServiceOpts{ - Name: libservice.StaticServerServiceName, - ID: "static-server-v2", - Meta: map[string]string{"version": "v2"}, - HTTPPort: 8082, - GRPCPort: 8077, - } - _, serverConnectProxyV2, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOptsV2) - libassert.CatalogServiceExists(t, client, "static-server") - require.NoError(t, err) - - // Create a client proxy instance with the server as an upstream - clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false) - require.NoError(t, err) - libassert.CatalogServiceExists(t, client, fmt.Sprintf("%s-sidecar-proxy", libservice.StaticClientServiceName)) - - return serverConnectProxy, serverConnectProxyV1, serverConnectProxyV2, clientConnectProxy -} diff --git a/test/integration/consul-container/test/upgrade/upgrade.go b/test/integration/consul-container/test/upgrade/upgrade.go new file mode 100644 index 00000000000..30bbdcc627c --- /dev/null +++ b/test/integration/consul-container/test/upgrade/upgrade.go @@ -0,0 +1,3 @@ +package upgrade + +var UpgradeFromVersions = []string{"1.13", "1.14"} diff --git a/test/integration/consul-container/test/util/upgrade_tests_workflow.png b/test/integration/consul-container/test/util/upgrade_tests_workflow.png new file mode 100644 index 00000000000..f8403259c36 Binary files /dev/null and b/test/integration/consul-container/test/util/upgrade_tests_workflow.png differ diff --git a/test/integration/consul-container/test/wanfed/wanfed_peering_test.go b/test/integration/consul-container/test/wanfed/wanfed_peering_test.go index 10b00874534..9a18a501b70 100644 --- a/test/integration/consul-container/test/wanfed/wanfed_peering_test.go +++ b/test/integration/consul-container/test/wanfed/wanfed_peering_test.go @@ -37,7 +37,11 @@ func TestPeering_WanFedSecondaryDC(t *testing.T) { t.Run("secondary dc can peer to alpha dc", func(t *testing.T) { // Create the gateway - _, err := libservice.NewGatewayService(context.Background(), "mesh", "mesh", c3.Servers()[0]) + gwCfg := libservice.GatewayConfig{ + Name: "mesh", + Kind: "mesh", + } + _, err := libservice.NewGatewayService(context.Background(), gwCfg, c3.Servers()[0]) require.NoError(t, err) // Create the peering connection diff --git a/tlsutil/config_test.go b/tlsutil/config_test.go index 7ce7893bfbe..388b4934ed4 100644 --- a/tlsutil/config_test.go +++ b/tlsutil/config_test.go @@ -906,7 +906,7 @@ func TestConfigurator_outgoingWrapperALPN_serverHasNoNodeNameInSAN(t *testing.T) _, err = wrap("dc1", "bob", "foo", client) require.Error(t, err) - _, ok := err.(x509.HostnameError) + _, ok := err.(*tls.CertificateVerificationError) require.True(t, ok) client.Close() diff --git a/troubleshoot/go.mod b/troubleshoot/go.mod index c4e445ee05f..b50703cd9a6 100644 --- a/troubleshoot/go.mod +++ b/troubleshoot/go.mod @@ -2,9 +2,9 @@ module github.com/hashicorp/consul/troubleshoot go 1.19 -replace github.com/hashicorp/consul/api => ../api +//replace github.com/hashicorp/consul/api => ../api -replace github.com/hashicorp/consul/envoyextensions => ../envoyextensions +//replace github.com/hashicorp/consul/envoyextensions => ../envoyextensions exclude ( github.com/hashicorp/go-msgpack v1.1.5 // has breaking changes and must be avoided @@ -13,8 +13,8 @@ exclude ( require ( github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 - github.com/hashicorp/consul/api v1.10.1-0.20230209203402-db2bd404bf72 - github.com/hashicorp/consul/envoyextensions v0.0.0-20230209212012-3b9c56956132 + github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/envoyextensions v0.1.2 github.com/stretchr/testify v1.8.0 google.golang.org/protobuf v1.28.1 ) diff --git a/troubleshoot/go.sum b/troubleshoot/go.sum index d2e1e9a2b5b..a5ce2f255f8 100644 --- a/troubleshoot/go.sum +++ b/troubleshoot/go.sum @@ -83,7 +83,11 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/consul/envoyextensions v0.1.2 h1:PvPqJ/td3UpOeIKQl5ycFPUy46XZP9awfhAUCduDeI4= +github.com/hashicorp/consul/envoyextensions v0.1.2/go.mod h1:N94DQQkgITiA40zuTQ/UdPOLAAWobgHfVT5u7wxE/aU= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/troubleshoot/proxy/certs.go b/troubleshoot/proxy/certs.go index 1fa61f3483e..ec4b2bc703d 100644 --- a/troubleshoot/proxy/certs.go +++ b/troubleshoot/proxy/certs.go @@ -18,7 +18,10 @@ func (t *Troubleshoot) validateCerts(certs *envoy_admin_v3.Certificates) validat if certs == nil { msg := validate.Message{ Success: false, - Message: "certificate object is nil in the proxy configuration", + Message: "Certificate object is nil in the proxy configuration", + PossibleActions: []string{ + "Check the logs of the Consul agent configuring the local proxy and ensure XDS updates are being sent to the proxy", + }, } return []validate.Message{msg} } @@ -26,7 +29,10 @@ func (t *Troubleshoot) validateCerts(certs *envoy_admin_v3.Certificates) validat if len(certs.GetCertificates()) == 0 { msg := validate.Message{ Success: false, - Message: "no certificates found", + Message: "No certificates found", + PossibleActions: []string{ + "Check the logs of the Consul agent configuring the local proxy and ensure XDS updates are being sent to the proxy", + }, } return []validate.Message{msg} } @@ -36,7 +42,10 @@ func (t *Troubleshoot) validateCerts(certs *envoy_admin_v3.Certificates) validat if now.After(cacert.GetExpirationTime().AsTime()) { msg := validate.Message{ Success: false, - Message: "ca certificate is expired", + Message: "CA certificate is expired", + PossibleActions: []string{ + "Check the logs of the Consul agent configuring the local proxy and ensure XDS updates are being sent to the proxy", + }, } certMessages = append(certMessages, msg) } @@ -46,7 +55,10 @@ func (t *Troubleshoot) validateCerts(certs *envoy_admin_v3.Certificates) validat if now.After(cc.GetExpirationTime().AsTime()) { msg := validate.Message{ Success: false, - Message: "certificate chain is expired", + Message: "Certificate chain is expired", + PossibleActions: []string{ + "Check the logs of the Consul agent configuring the local proxy and ensure XDS updates are being sent to the proxy", + }, } certMessages = append(certMessages, msg) } diff --git a/troubleshoot/proxy/certs_test.go b/troubleshoot/proxy/certs_test.go index 63f2a0256c1..fc03cb062ff 100644 --- a/troubleshoot/proxy/certs_test.go +++ b/troubleshoot/proxy/certs_test.go @@ -21,13 +21,13 @@ func TestValidateCerts(t *testing.T) { }{ "cert is nil": { certs: nil, - expectedError: "certificate object is nil in the proxy configuration", + expectedError: "Certificate object is nil in the proxy configuration", }, "no certificates": { certs: &envoy_admin_v3.Certificates{ Certificates: []*envoy_admin_v3.Certificate{}, }, - expectedError: "no certificates found", + expectedError: "No certificates found", }, "ca expired": { certs: &envoy_admin_v3.Certificates{ @@ -41,7 +41,7 @@ func TestValidateCerts(t *testing.T) { }, }, }, - expectedError: "ca certificate is expired", + expectedError: "CA certificate is expired", }, "cert expired": { certs: &envoy_admin_v3.Certificates{ @@ -55,7 +55,7 @@ func TestValidateCerts(t *testing.T) { }, }, }, - expectedError: "certificate chain is expired", + expectedError: "Certificate chain is expired", }, } @@ -67,7 +67,9 @@ func TestValidateCerts(t *testing.T) { var outputErrors string for _, msgError := range messages.Errors() { outputErrors += msgError.Message - outputErrors += msgError.PossibleActions + for _, action := range msgError.PossibleActions { + outputErrors += action + } } if tc.expectedError == "" { require.True(t, messages.Success()) diff --git a/troubleshoot/proxy/stats.go b/troubleshoot/proxy/stats.go index 0fdb0e43ee7..a2ab88ffa6b 100644 --- a/troubleshoot/proxy/stats.go +++ b/troubleshoot/proxy/stats.go @@ -3,6 +3,7 @@ package troubleshoot import ( "encoding/json" "fmt" + envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" "github.com/hashicorp/consul/troubleshoot/validate" ) @@ -32,10 +33,19 @@ func (t *Troubleshoot) troubleshootStats() (validate.Messages, error) { } } - statMessages = append(statMessages, validate.Message{ - Success: true, - Message: fmt.Sprintf("Envoy has %v rejected configurations", totalConfigRejections), - }) + if totalConfigRejections > 0 { + statMessages = append(statMessages, validate.Message{ + Message: fmt.Sprintf("Envoy has %v rejected configurations", totalConfigRejections), + PossibleActions: []string{ + "Check the logs of the Consul agent configuring the local proxy to see why Envoy rejected this configuration", + }, + }) + } else { + statMessages = append(statMessages, validate.Message{ + Success: true, + Message: fmt.Sprintf("Envoy has %v rejected configurations", totalConfigRejections), + }) + } connectionFailureStats, err := t.getEnvoyStats("upstream_cx_connect_fail") if err != nil { @@ -50,7 +60,7 @@ func (t *Troubleshoot) troubleshootStats() (validate.Messages, error) { } statMessages = append(statMessages, validate.Message{ Success: true, - Message: fmt.Sprintf("Envoy has detected %v connection failure(s).", totalCxFailures), + Message: fmt.Sprintf("Envoy has detected %v connection failure(s)", totalCxFailures), }) return statMessages, nil } diff --git a/troubleshoot/proxy/troubleshoot_proxy.go b/troubleshoot/proxy/troubleshoot_proxy.go index ff74aff0d4e..8d1bfdc0bf1 100644 --- a/troubleshoot/proxy/troubleshoot_proxy.go +++ b/troubleshoot/proxy/troubleshoot_proxy.go @@ -10,15 +10,6 @@ import ( "github.com/hashicorp/consul/troubleshoot/validate" ) -const ( - listeners string = "type.googleapis.com/envoy.admin.v3.ListenersConfigDump" - clusters string = "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" - routes string = "type.googleapis.com/envoy.admin.v3.RoutesConfigDump" - endpoints string = "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump" - bootstrap string = "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump" - httpConnManager string = "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" -) - type Troubleshoot struct { client *api.Client envoyAddr net.IPAddr @@ -79,7 +70,7 @@ func (t *Troubleshoot) RunAllTests(upstreamEnvoyID, upstreamIP string) (validate if errors := messages.Errors(); len(errors) == 0 { msg := validate.Message{ Success: true, - Message: "certificates are valid", + Message: "Certificates are valid", } allTestMessages = append(allTestMessages, msg) } @@ -97,7 +88,7 @@ func (t *Troubleshoot) RunAllTests(upstreamEnvoyID, upstreamIP string) (validate if errors := messages.Errors(); len(errors) == 0 { msg := validate.Message{ Success: true, - Message: "upstream resources are valid", + Message: "Upstream resources are valid", } allTestMessages = append(allTestMessages, msg) } diff --git a/troubleshoot/proxy/upstreams.go b/troubleshoot/proxy/upstreams.go index abd9711abfa..2ac7c8e953d 100644 --- a/troubleshoot/proxy/upstreams.go +++ b/troubleshoot/proxy/upstreams.go @@ -2,6 +2,7 @@ package troubleshoot import ( "fmt" + envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -29,7 +30,7 @@ func (t *Troubleshoot) GetUpstreams() ([]string, []UpstreamIP, error) { for _, cfg := range t.envoyConfigDump.Configs { switch cfg.TypeUrl { - case listeners: + case listenersType: lcd := &envoy_admin_v3.ListenersConfigDump{} err := proto.Unmarshal(cfg.GetValue(), lcd) @@ -135,7 +136,7 @@ func getClustersFromRoutes(routeName string, cfgDump *envoy_admin_v3.ConfigDump) for _, cfg := range cfgDump.Configs { switch cfg.TypeUrl { - case routes: + case routesType: rcd := &envoy_admin_v3.RoutesConfigDump{} err := proto.Unmarshal(cfg.GetValue(), rcd) diff --git a/troubleshoot/proxy/upstreams_test.go b/troubleshoot/proxy/upstreams_test.go index 6493b5349d2..e1d6ff90753 100644 --- a/troubleshoot/proxy/upstreams_test.go +++ b/troubleshoot/proxy/upstreams_test.go @@ -1,14 +1,15 @@ package troubleshoot import ( + "io" + "os" + "testing" + envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" "github.com/stretchr/testify/require" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - "io" - "os" - "testing" ) func TestGetUpstreamIPsFromFilterChain(t *testing.T) { @@ -61,7 +62,7 @@ func TestGetUpstreamIPsFromFilterChain(t *testing.T) { for _, cfg := range cfgDump.Configs { switch cfg.TypeUrl { - case listeners: + case listenersType: lcd := &envoy_admin_v3.ListenersConfigDump{} err := proto.Unmarshal(cfg.GetValue(), lcd) diff --git a/troubleshoot/validate/validate.go b/troubleshoot/validate/validate.go index 59dad4031a3..01f950ba6db 100644 --- a/troubleshoot/validate/validate.go +++ b/troubleshoot/validate/validate.go @@ -110,7 +110,7 @@ type Messages []Message type Message struct { Success bool Message string - PossibleActions string + PossibleActions []string } func (m Messages) Success() bool { @@ -137,6 +137,18 @@ func (m Messages) Errors() Messages { // GetMessages returns the error based only on Validate's state. func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator EndpointValidator, clusters *envoy_admin_v3.Clusters) Messages { var messages Messages + missingXDSActions := []string{ + "Check that your upstream service is registered with Consul", + "Make sure your upstream exists by running the `consul[-k8s] troubleshoot upstreams` command", + "If you are using transparent proxy for this upstream, ensure you have set up allow intentions to the upstream", + "Check the logs of the Consul agent configuring the local proxy to ensure XDS resources were sent by Consul", + } + missingEndpointsActions := []string{ + "Check that your upstream service is healthy and running", + "Check that your upstream service is registered with Consul", + "Check that the upstream proxy is healthy and running", + "If you are explicitly configuring upstreams, ensure the name of the upstream is correct", + } var upstream string upstream = v.envoyID @@ -145,19 +157,25 @@ func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator Endpoin } if !v.listener { - messages = append(messages, Message{Message: fmt.Sprintf("no listener for upstream %q", upstream)}) + messages = append(messages, Message{ + Message: fmt.Sprintf("No listener for upstream %q", upstream), + PossibleActions: missingXDSActions, + }) } else { messages = append(messages, Message{ - Message: fmt.Sprintf("listener for upstream %q found", upstream), + Message: fmt.Sprintf("Listener for upstream %q found", upstream), Success: true, }) } if v.usesRDS && !v.route { - messages = append(messages, Message{Message: fmt.Sprintf("no route for upstream %q", upstream)}) - } else { messages = append(messages, Message{ - Message: fmt.Sprintf("route for upstream %q found", upstream), + Message: fmt.Sprintf("No route for upstream %q", upstream), + PossibleActions: missingXDSActions, + }) + } else if v.route { + messages = append(messages, Message{ + Message: fmt.Sprintf("Route for upstream %q found", upstream), Success: true, }) } @@ -173,11 +191,14 @@ func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator Endpoin _, ok := v.snis[sni] if !ok || !resource.cluster { - messages = append(messages, Message{Message: fmt.Sprintf("no cluster %q for upstream %q", sni, upstream)}) + messages = append(messages, Message{ + Message: fmt.Sprintf("No cluster %q for upstream %q", sni, upstream), + PossibleActions: missingXDSActions, + }) continue } else { messages = append(messages, Message{ - Message: fmt.Sprintf("cluster %q for upstream %q found", sni, upstream), + Message: fmt.Sprintf("Cluster %q for upstream %q found", sni, upstream), Success: true, }) } @@ -186,7 +207,7 @@ func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator Endpoin // validation. if strings.Contains(sni, "passthrough~") { messages = append(messages, Message{ - Message: fmt.Sprintf("cluster %q is a passthrough cluster, skipping endpoint healthiness check", sni), + Message: fmt.Sprintf("Cluster %q is a passthrough cluster, skipping endpoint healthiness check", sni), Success: true, }) continue @@ -206,10 +227,13 @@ func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator Endpoin } } if !oneClusterHasEndpoints { - messages = append(messages, Message{Message: fmt.Sprintf("no healthy endpoints for aggregate cluster %q for upstream %q", sni, upstream)}) + messages = append(messages, Message{ + Message: fmt.Sprintf("No healthy endpoints for aggregate cluster %q for upstream %q", sni, upstream), + PossibleActions: missingEndpointsActions, + }) } else { messages = append(messages, Message{ - Message: fmt.Sprintf("healthy endpoints for aggregate cluster %q for upstream %q", sni, upstream), + Message: fmt.Sprintf("Healthy endpoints for aggregate cluster %q for upstream %q found", sni, upstream), Success: true, }) } @@ -218,11 +242,12 @@ func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator Endpoin endpointValidator(resource, sni, clusters) if (resource.usesEDS && !resource.loadAssignment) || resource.endpoints == 0 { messages = append(messages, Message{ - Message: fmt.Sprintf("no healthy endpoints for cluster %q for upstream %q", sni, upstream), + Message: fmt.Sprintf("No healthy endpoints for cluster %q for upstream %q", sni, upstream), + PossibleActions: missingEndpointsActions, }) } else { messages = append(messages, Message{ - Message: fmt.Sprintf("healthy endpoints for cluster %q for upstream %q", sni, upstream), + Message: fmt.Sprintf("Healthy endpoints for cluster %q for upstream %q found", sni, upstream), Success: true, }) } @@ -235,7 +260,7 @@ func (v *Validate) GetMessages(validateEndpoints bool, endpointValidator Endpoin } if numRequiredResources == 0 { - messages = append(messages, Message{Message: fmt.Sprintf("no clusters found on route or listener")}) + messages = append(messages, Message{Message: fmt.Sprintf("No clusters found on route or listener")}) } return messages diff --git a/troubleshoot/validate/validate_test.go b/troubleshoot/validate/validate_test.go index c8f353f306a..6496001b79a 100644 --- a/troubleshoot/validate/validate_test.go +++ b/troubleshoot/validate/validate_test.go @@ -63,7 +63,7 @@ func TestErrors(t *testing.T) { r.loadAssignment = true r.endpoints = 1 }, - err: "no clusters found on route or listener", + err: "No clusters found on route or listener", }, "no healthy endpoints": { validate: func() *Validate { @@ -86,7 +86,7 @@ func TestErrors(t *testing.T) { endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { r.loadAssignment = true }, - err: "no healthy endpoints for cluster \"db-sni\" for upstream \"db\"", + err: "No healthy endpoints for cluster \"db-sni\" for upstream \"db\"", }, "success: aggregate cluster with one target with endpoints": { validate: func() *Validate { @@ -169,7 +169,7 @@ func TestErrors(t *testing.T) { r.loadAssignment = true r.endpoints = 0 }, - err: "no healthy endpoints for aggregate cluster \"db-sni\" for upstream \"db\"", + err: "No healthy endpoints for aggregate cluster \"db-sni\" for upstream \"db\"", }, "success: passthrough cluster doesn't error even though there are zero endpoints": { validate: func() *Validate { @@ -203,7 +203,9 @@ func TestErrors(t *testing.T) { var outputErrors string for _, msgError := range messages.Errors() { outputErrors += msgError.Message - outputErrors += msgError.PossibleActions + for _, action := range msgError.PossibleActions { + outputErrors += action + } } if tc.err == "" { require.True(t, messages.Success()) diff --git a/ui/packages/consul-ui/app/adapters/token.js b/ui/packages/consul-ui/app/adapters/token.js index 5502dbd8994..4a9f2fa0395 100644 --- a/ui/packages/consul-ui/app/adapters/token.js +++ b/ui/packages/consul-ui/app/adapters/token.js @@ -80,6 +80,7 @@ export default class TokenAdapter extends Adapter { ${{ Description: serialized.Description, + AccessorID: serialized.AccessorID, Policies: serialized.Policies, Roles: serialized.Roles, ServiceIdentities: serialized.ServiceIdentities, diff --git a/ui/packages/consul-ui/app/components/consul/kind/index.hbs b/ui/packages/consul-ui/app/components/consul/kind/index.hbs index e5bfc6d4423..58b98154561 100644 --- a/ui/packages/consul-ui/app/components/consul/kind/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/kind/index.hbs @@ -1,89 +1,99 @@ {{#if item.Kind}} - {{#let (titleize (humanize item.Kind)) as |Name|}} - {{#if withInfo}} -
-
- - {{Name}} - -
-
- - - {{#if (eq item.Kind 'ingress-gateway')}} - Ingress gateways enable ingress traffic from services outside the Consul service mesh to services inside the Consul service mesh. - {{else if (eq item.Kind 'terminating-gateway')}} - Terminating gateways allow connect-enabled services in Consul service mesh to communicate with services outside the service mesh. - {{else}} - Mesh gateways enable routing of Connect traffic between different Consul datacenters. - {{/if}} - - -
  • - {{#if (eq item.Kind 'ingress-gateway')}} - About Ingress gateways - {{else if (eq item.Kind 'terminating-gateway')}} - About Terminating gateways - {{else}} - About Mesh gateways - {{/if}} -
  • - {{#let (from-entries (array - (array 'ingress-gateway' '/consul/developer-mesh/ingress-gateways') - (array 'terminating-gateway' '/consul/developer-mesh/understand-terminating-gateways') - (array 'mesh-gateway' '/consul/developer-mesh/connect-gateways') - ) - ) as |link|}} - - {{/let}} - {{#let (from-entries (array - (array 'ingress-gateway' '/connect/ingress-gateway') - (array 'terminating-gateway' '/connect/terminating-gateway') - (array 'mesh-gateway' '/connect/mesh-gateway') - ) - ) as |link|}} - -
  • - Other gateway types -
  • - {{#if (not-eq item.Kind 'mesh-gateway')}} - + {{#if withInfo}} +
    +
    + + {{Name}} + +
    +
    + + + {{#if (eq item.Kind 'ingress-gateway')}} + Ingress gateways enable ingress traffic from services outside the Consul service mesh to services inside the Consul service mesh. + {{else if (eq item.Kind 'terminating-gateway')}} + Terminating gateways allow connect-enabled services in Consul service mesh to communicate with services outside the service mesh. + {{else if (eq item.Kind 'api-gateway')}} + API gateways enable ingress traffic from services outside the Consul service mesh to services inside the Consul service mesh. + {{else}} + Mesh gateways enable routing of Connect traffic between different Consul datacenters. + {{/if}} + + +
  • + {{#if (eq item.Kind 'ingress-gateway')}} + About Ingress gateways + {{else if (eq item.Kind 'terminating-gateway')}} + About Terminating gateways + {{else if (eq item.Kind 'api-gateway')}} + About API gateways + {{else}} + About Mesh gateways {{/if}} - {{#if (not-eq item.Kind 'terminating-gateway')}} -
  • - {{/if}} - {{#if (not-eq item.Kind 'ingress-gateway')}} - - {{/if}} - {{/let}} -
    -
    -
    -
    - {{else}} - - {{Name}} - - {{/if}} - {{/let}} + + {{#let (from-entries (array + (array 'ingress-gateway' '/consul/developer-mesh/ingress-gateways') + (array 'terminating-gateway' '/consul/developer-mesh/understand-terminating-gateways') + (array 'mesh-gateway' '/consul/developer-mesh/connect-gateways') + ) + ) as |link|}} + + {{/let}} + {{#let (from-entries (array + (array 'ingress-gateway' '/connect/gateways/ingress-gateway') + (array 'terminating-gateway' '/connect/gateways/terminating-gateway') + (array 'api-gateway' '/connect/gateways/api-gateway') + (array 'mesh-gateway' '/connect/gateways/mesh-gateway') + ) + ) as |link|}} + +
  • + Other gateway types +
  • + {{#if (not-eq item.Kind 'mesh-gateway')}} + + {{/if}} + {{#if (not-eq item.Kind 'terminating-gateway')}} + + {{/if}} + {{#if (not-eq item.Kind 'ingress-gateway')}} + + {{/if}} + {{#if (not-eq item.Kind 'api-gateway')}} + + {{/if}} + {{/let}} +
    +
    +
    +
    + {{else}} + + {{Name}} + + {{/if}} {{/if}} diff --git a/ui/packages/consul-ui/app/components/consul/kind/index.js b/ui/packages/consul-ui/app/components/consul/kind/index.js index 4798652642b..63117c19fe4 100644 --- a/ui/packages/consul-ui/app/components/consul/kind/index.js +++ b/ui/packages/consul-ui/app/components/consul/kind/index.js @@ -1,5 +1,19 @@ import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { titleize } from 'ember-cli-string-helpers/helpers/titleize'; +import { humanize } from 'ember-cli-string-helpers/helpers/humanize'; + +const normalizedGatewayLabels = { + 'api-gateway': 'API Gateway', + 'mesh-gateway': 'Mesh Gateway', + 'ingress-gateway': 'Ingress Gateway', + 'terminating-gateway': 'Terminating Gateway', +}; export default Component.extend({ tagName: '', + Name: computed('item.Kind', function () { + const name = normalizedGatewayLabels[this.item.Kind]; + return name ? name : titleize(humanize(this.item.Kind)); + }), }); diff --git a/ui/packages/consul-ui/app/components/consul/service/search-bar/index.hbs b/ui/packages/consul-ui/app/components/consul/service/search-bar/index.hbs index 5c76cf501ca..bda5097c9c2 100644 --- a/ui/packages/consul-ui/app/components/consul/service/search-bar/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/service/search-bar/index.hbs @@ -102,7 +102,7 @@ {{t 'common.consul.service'}} - {{#each (array 'ingress-gateway' 'terminating-gateway' 'mesh-gateway') as |kind|}} + {{#each (array 'api-gateway' 'ingress-gateway' 'terminating-gateway' 'mesh-gateway') as |kind|}} diff --git a/ui/packages/consul-ui/app/filter/predicates/service.js b/ui/packages/consul-ui/app/filter/predicates/service.js index 14bae438467..a5030e34ed2 100644 --- a/ui/packages/consul-ui/app/filter/predicates/service.js +++ b/ui/packages/consul-ui/app/filter/predicates/service.js @@ -2,6 +2,7 @@ import setHelpers from 'mnemonist/set'; export default { kind: { + 'api-gateway': (item, value) => item.Kind === value, 'ingress-gateway': (item, value) => item.Kind === value, 'terminating-gateway': (item, value) => item.Kind === value, 'mesh-gateway': (item, value) => item.Kind === value, diff --git a/ui/packages/consul-ui/app/models/service-instance.js b/ui/packages/consul-ui/app/models/service-instance.js index 1863dd6bbab..51f98e25b53 100644 --- a/ui/packages/consul-ui/app/models/service-instance.js +++ b/ui/packages/consul-ui/app/models/service-instance.js @@ -64,9 +64,13 @@ export default class ServiceInstance extends Model { @computed('Service.Kind') get IsProxy() { - return ['connect-proxy', 'mesh-gateway', 'ingress-gateway', 'terminating-gateway'].includes( - this.Service.Kind - ); + return [ + 'connect-proxy', + 'mesh-gateway', + 'ingress-gateway', + 'terminating-gateway', + 'api-gateway', + ].includes(this.Service.Kind); } // IsOrigin means that the service can have associated up or down streams, diff --git a/ui/packages/consul-ui/mock-api/v1/internal/ui/exported-services b/ui/packages/consul-ui/mock-api/v1/internal/ui/exported-services index 8bfe712541b..f138a28d9f6 100644 --- a/ui/packages/consul-ui/mock-api/v1/internal/ui/exported-services +++ b/ui/packages/consul-ui/mock-api/v1/internal/ui/exported-services @@ -3,7 +3,7 @@ ${[0].map( () => { let prevKind; let name; - const gateways = ['mesh-gateway', 'ingress-gateway', 'terminating-gateway']; + const gateways = ['mesh-gateway', 'ingress-gateway', 'terminating-gateway', 'api-gateway']; return ` [ ${ diff --git a/ui/packages/consul-ui/mock-api/v1/internal/ui/services b/ui/packages/consul-ui/mock-api/v1/internal/ui/services index f44e59d179c..e29f7feefb7 100644 --- a/ui/packages/consul-ui/mock-api/v1/internal/ui/services +++ b/ui/packages/consul-ui/mock-api/v1/internal/ui/services @@ -2,7 +2,7 @@ ${[0].map( () => { let prevKind; let name; - const gateways = ['mesh-gateway', 'ingress-gateway', 'terminating-gateway']; + const gateways = ['mesh-gateway', 'ingress-gateway', 'terminating-gateway', 'api-gateway']; return ` [ ${ diff --git a/ui/packages/consul-ui/package.json b/ui/packages/consul-ui/package.json index 91467dd8f4f..5898c888faf 100644 --- a/ui/packages/consul-ui/package.json +++ b/ui/packages/consul-ui/package.json @@ -120,7 +120,7 @@ "ember-cli-page-object": "^1.17.11", "ember-cli-postcss": "^8.1.0", "ember-cli-sri": "^2.1.1", - "ember-cli-string-helpers": "^5.0.0", + "ember-cli-string-helpers": "^6.1.0", "ember-cli-template-lint": "^2.0.1", "ember-cli-terser": "^4.0.2", "ember-cli-yadda": "^0.7.0", @@ -134,7 +134,7 @@ "ember-export-application-global": "^2.0.1", "ember-in-viewport": "^3.8.1", "ember-inflector": "^4.0.1", - "ember-intl": "^5.5.1", + "ember-intl": "^5.7.0", "ember-load-initializers": "^2.1.2", "ember-math-helpers": "^2.4.0", "ember-maybe-import-regenerator": "^0.1.6", diff --git a/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature b/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature index ecfbc803230..df0ebc2dde3 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature @@ -71,7 +71,7 @@ Feature: dc / services / index: List Services --- Scenario: Viewing the service list page with gateways Given 1 datacenter model with the value "dc-1" - And 3 service models from yaml + And 4 service models from yaml --- - Name: Service-0-proxy Kind: 'connect-proxy' @@ -88,6 +88,11 @@ Feature: dc / services / index: List Services ChecksPassing: 0 ChecksWarning: 0 ChecksCritical: 1 + - Name: Service-3-api-gateway + Kind: 'api-gateway' + ChecksPassing: 0 + ChecksWarning: 0 + ChecksCritical: 1 --- When I visit the services page for yaml @@ -96,11 +101,12 @@ Feature: dc / services / index: List Services --- Then the url should be /dc-1/services And the title should be "Services - Consul" - Then I see 2 service models + Then I see 3 service models And I see kind on the services like yaml --- - ingress-gateway - terminating-gateway + - api-gateway --- Scenario: View a Service in mesh Given 1 datacenter model with the value "dc-1" diff --git a/ui/packages/consul-ui/tests/acceptance/dc/services/show/services.feature b/ui/packages/consul-ui/tests/acceptance/dc/services/show/services.feature index 3b19a699593..b6819558d84 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/services/show/services.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/services/show/services.feature @@ -50,6 +50,7 @@ Feature: dc / services / show / services | Name | Kind | | service | ~ | | ingress-gateway | ingress-gateway | + | api-gateway | api-gateway | | mesh-gateway | mesh-gateway | --------------------------------------------- diff --git a/ui/packages/consul-ui/translations/common/en-us.yaml b/ui/packages/consul-ui/translations/common/en-us.yaml index 5c584540eda..18180f2073a 100644 --- a/ui/packages/consul-ui/translations/common/en-us.yaml +++ b/ui/packages/consul-ui/translations/common/en-us.yaml @@ -32,6 +32,7 @@ consul: ingress-gateway: Ingress Gateway terminating-gateway: Terminating Gateway mesh-gateway: Mesh Gateway + api-gateway: API Gateway status: Health Status service.meta: Service Meta node.meta: Node Meta diff --git a/ui/yarn.lock b/ui/yarn.lock index 96953879f60..19ff697e44f 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.1.0": +"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== @@ -58,6 +58,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== +"@babel/compat-data@^7.20.5": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" + integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== + "@babel/core@^7.0.0", "@babel/core@^7.1.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.2.2", "@babel/core@^7.3.4", "@babel/core@^7.7.5": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559" @@ -80,6 +85,27 @@ semver "^6.3.0" source-map "^0.5.0" +"@babel/core@^7.13.10": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" + integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.0" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.0" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + "@babel/core@^7.13.8": version "7.20.2" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92" @@ -158,6 +184,16 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" +"@babel/generator@^7.21.0", "@babel/generator@^7.21.1": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" + integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== + dependencies: + "@babel/types" "^7.21.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" @@ -251,6 +287,17 @@ browserslist "^4.21.3" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.5.5", "@babel/helper-create-class-features-plugin@^7.8.3": version "7.13.11" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6" @@ -419,6 +466,14 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" +"@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== + dependencies: + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" + "@babel/helper-get-function-arity@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" @@ -554,6 +609,20 @@ "@babel/traverse" "^7.20.1" "@babel/types" "^7.20.2" +"@babel/helper-module-transforms@^7.21.0": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" + "@babel/helper-optimise-call-expression@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" @@ -822,6 +891,15 @@ "@babel/traverse" "^7.20.1" "@babel/types" "^7.20.0" +"@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" @@ -869,6 +947,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.2.tgz#9aeb9b92f64412b5f81064d46f6a1ac0881337f4" integrity sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg== +"@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" + integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" @@ -2530,6 +2613,15 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" +"@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@babel/traverse@^7.1.6", "@babel/traverse@^7.12.1", "@babel/traverse@^7.13.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" @@ -2593,6 +2685,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" + integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.1" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.2" + "@babel/types" "^7.21.2" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.1.6", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.7.2": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" @@ -2628,6 +2736,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1" + integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -3524,7 +3641,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -3542,7 +3659,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== @@ -3555,6 +3672,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.17": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@lit/reactive-element@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.2.1.tgz#8620d7f0baf63e12821fa93c34d21e23477736f7" @@ -8404,13 +8529,15 @@ ember-cli-sri@^2.1.1: dependencies: broccoli-sri-hash "^2.1.0" -ember-cli-string-helpers@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ember-cli-string-helpers/-/ember-cli-string-helpers-5.0.0.tgz#b1e08ec3ca1c9a457f9fd9aafff60b5939fbf91d" - integrity sha512-PodD3Uf7BkOXIu95E6cWEC0ERroTiUOAwOr828Vb+fPFtV7WYNSC27C9Ds1ggCyyRqnXmpS0JSqCTN1gPZfkWQ== +ember-cli-string-helpers@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-string-helpers/-/ember-cli-string-helpers-6.1.0.tgz#aeb96112bb91c540b869ed8b9c680f7fd5859cb6" + integrity sha512-Lw8B6MJx2n8CNF2TSIKs+hWLw0FqSYjr2/NRPyquyYA05qsl137WJSYW3ZqTsLgoinHat0DGF2qaCXocLhLmyA== dependencies: + "@babel/core" "^7.13.10" broccoli-funnel "^3.0.3" ember-cli-babel "^7.7.3" + resolve "^1.20.0" ember-cli-string-utils@^1.0.0, ember-cli-string-utils@^1.1.0: version "1.1.0" @@ -8914,10 +9041,10 @@ ember-inflector@^4.0.2: dependencies: ember-cli-babel "^7.26.5" -ember-intl@^5.5.1: - version "5.6.2" - resolved "https://registry.yarnpkg.com/ember-intl/-/ember-intl-5.6.2.tgz#ece4820923dfda033c279b7e3920cbbc8b6bde07" - integrity sha512-+FfI2udVbnEzueompcRb3ytBWhfnBfVVjAwnCuxwqIyS9ti8lK0ZiYHa5bquNPHjjfBzfFl4x5TlVgDNaCnccg== +ember-intl@^5.7.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ember-intl/-/ember-intl-5.7.2.tgz#76d933f974f041448b01247888bc3bcc9261e812" + integrity sha512-gs17uY1ywzMaUpx1gxfBkFQYRTWTSa/zbkL13MVtffG9aBLP+998MibytZOUxIipMtLCm4sr/g6/1aaKRr9/+g== dependencies: broccoli-caching-writer "^3.0.3" broccoli-funnel "^3.0.3" @@ -12212,6 +12339,11 @@ json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" diff --git a/version/VERSION b/version/VERSION index 0dec25d15b3..749afc16bf3 100644 --- a/version/VERSION +++ b/version/VERSION @@ -1 +1 @@ -1.15.0-dev \ No newline at end of file +1.15.3-dev diff --git a/version/version.go b/version/version.go index 175f3ef55c0..36b2eacfa5a 100644 --- a/version/version.go +++ b/version/version.go @@ -20,7 +20,7 @@ var ( //go:embed VERSION fullVersion string - Version, VersionPrerelease, _ = strings.Cut(fullVersion, "-") + Version, VersionPrerelease, _ = strings.Cut(strings.TrimSpace(fullVersion), "-") // https://semver.org/#spec-item-10 VersionMetadata = "" diff --git a/website/content/api-docs/agent/check.mdx b/website/content/api-docs/agent/check.mdx index d29f0de8fc6..03364b6aeb3 100644 --- a/website/content/api-docs/agent/check.mdx +++ b/website/content/api-docs/agent/check.mdx @@ -6,8 +6,7 @@ description: The /agent/check endpoints interact with checks on the local agent # Check - Agent HTTP API -Consul's health check capabilities are described in the -[health checks overview](/consul/docs/discovery/checks). +Refer to [Define Health Checks](/consul/docs/services/usage/checks) for information about Consul health check capabilities. The `/agent/check` endpoints interact with health checks managed by the local agent in Consul. These should not be confused with checks in the catalog. diff --git a/website/content/api-docs/agent/service.mdx b/website/content/api-docs/agent/service.mdx index a232c4491a3..38eedad3536 100644 --- a/website/content/api-docs/agent/service.mdx +++ b/website/content/api-docs/agent/service.mdx @@ -170,6 +170,8 @@ The table below shows this endpoint's support for ### Sample Request +The following example request calls the `web-sidecar-proxy` service: + ```shell-session $ curl \ http://127.0.0.1:8500/v1/agent/service/web-sidecar-proxy @@ -177,6 +179,11 @@ $ curl \ ### Sample Response +The response contains the fields specified in the [service +definition](/consul/docs/services/configuration/services-configuration-reference), but it includes an extra `ContentHash` field that contains the [hash-based blocking +query](/consul/api-docs/features/blocking#hash-based-blocking-queries) hash for the result. The +same hash is also present in `X-Consul-ContentHash`. + ```json { "Kind": "connect-proxy", @@ -227,12 +234,6 @@ $ curl \ } ``` -The response has the same structure as the [service -definition](/consul/docs/discovery/services) with one extra field `ContentHash` which -contains the [hash-based blocking -query](/consul/api-docs/features/blocking#hash-based-blocking-queries) hash for the result. The -same hash is also present in `X-Consul-ContentHash`. - ## Get local service health Retrieve an aggregated state of service(s) on the local agent by name. @@ -599,21 +600,18 @@ The corresponding CLI command is [`consul services register`](/consul/commands/s ### JSON Request Body Schema -Note that this endpoint, unlike most also [supports `snake_case`](/consul/docs/discovery/services#service-definition-parameter-case) -service definition keys for compatibility with the config file format. +The `/agent/service/register` endpoint supports camel case and _snake case_ for service definition keys. Snake case is a convention in which keys with two or more words have underscores between them. Snake case ensures compatibility with the agent configuration file format. - `Name` `(string: )` - Specifies the logical name of the service. Many service instances may share the same logical service name. We recommend using - [valid DNS labels](https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames) - for [compatibility with external DNS](/consul/docs/discovery/services#service-and-tag-names-with-dns). + valid DNS labels for service definition names. Refer to the 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 additional information. - `ID` `(string: "")` - Specifies a unique ID for this service. This must be unique per _agent_. This defaults to the `Name` parameter if not provided. - `Tags` `(array: nil)` - Specifies a list of tags to assign to the - service. These tags can be used for later filtering and are exposed via the APIs. - We recommend using [valid DNS labels](https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames) - for [compatibility with external DNS](/consul/docs/discovery/services#service-and-tag-names-with-dns) + service. Tags enable you to filter when querying for the services and are exposed in Consul APIs. We recommend using + valid DNS labels for tags. Refer to the Internet Engineering Task Force's [RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123#page-72) for additional information. Tags that conform to standard usage ensures compatibility with external DNSs. Refer to [Services Configuration Reference](/consul/docs/services/configuration/services-configuration-reference#tags) for additional information. - `Address` `(string: "")` - Specifies the address of the service. If not provided, the agent's address is used as the address for the service during @@ -676,19 +674,15 @@ service definition keys for compatibility with the config file format. service's port _and_ the tags would revert to the original value and all modifications would be lost. -- `Weights` `(Weights: nil)` - Specifies weights for the service. Please see the - [service documentation](/consul/docs/discovery/services) for more information about - weights. If this field is not provided weights will default to +- `Weights` `(Weights: nil)` - Specifies weights for the service. Refer to + [Services Configuration Reference](/consul/docs/services/configuraiton/services-configuration-reference#weights) for additional information. Default is `{"Passing": 1, "Warning": 1}`. - It is important to note that this applies only to the locally registered - service. If you have multiple nodes all registering the same service their - `EnableTagOverride` configuration and all other service configuration items - are independent of one another. Updating the tags for the service registered - on one node is independent of the same service (by name) registered on - another node. If `EnableTagOverride` is not specified the default value is + Weights only apply to the locally registered service. + If multiple nodes register the same service, each node implements `EnableTagOverride` and other service configuration items independently. Updating the tags for the service registered + on one node does not necessarily update the same tags on services with the same name registered on another node. If `EnableTagOverride` is not specified the default value is `false`. See [anti-entropy syncs](/consul/docs/architecture/anti-entropy) for - more info. + additional information. #### Connect Structure diff --git a/website/content/api-docs/api-structure.mdx b/website/content/api-docs/api-structure.mdx index 655399e7932..7a190894129 100644 --- a/website/content/api-docs/api-structure.mdx +++ b/website/content/api-docs/api-structure.mdx @@ -89,7 +89,7 @@ However, we generally recommend using resource names that don't require URL-enco Depending on the validation that Consul applies to a resource name, Consul may still reject a request if it considers the resource name invalid for that endpoint. And even if Consul considers the resource name valid, it may degrade other functionality, -such as failed [DNS lookups](/consul/docs/discovery/dns) +such as failed [DNS lookups](/consul/docs/services/discovery/dns-overview) for nodes or services with names containing invalid DNS characters. This HTTP API capability also allows the diff --git a/website/content/api-docs/catalog.mdx b/website/content/api-docs/catalog.mdx index b9a8c7bbd74..2b470dd5788 100644 --- a/website/content/api-docs/catalog.mdx +++ b/website/content/api-docs/catalog.mdx @@ -55,15 +55,16 @@ The table below shows this endpoint's support for - `NodeMeta` `(map: nil)` - Specifies arbitrary KV metadata pairs for filtering purposes. -- `Service` `(Service: nil)` - Specifies to register a service. If `ID` is not - provided, it will be defaulted to the value of the `Service.Service` property. - Only one service with a given `ID` may be present per node. We recommend using - [valid DNS labels](https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames) - for service definition names for [compatibility with external DNS](/consul/docs/discovery/services#service-and-tag-names-with-dns). - The service `Tags`, `Address`, `Meta`, and `Port` fields are all optional. For more - information about these fields and the implications of setting them, - see the [Service - Agent API](/consul/api-docs/agent/service) page - as registering services differs between using this or the Services Agent endpoint. +- `Service` `(Service: nil)` - Contains an object the specifies the service to register. The 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 the 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 additional information. + The following fields are optional: + - `Tags` + - `Address` + - `Meta` + - `Port` + + For additional information configuring services, refer to [Service - Agent API](/consul/api-docs/agent/service). The `/catalog` endpoint had different behaviors than the `/services` endpoint. - `Check` `(Check: nil)` - Specifies to register a check. The register API manipulates the health check entry in the Catalog, but it does not setup the @@ -78,8 +79,7 @@ The table below shows this endpoint's support for treated as a service level health check, instead of a node level health check. The `Status` must be one of `passing`, `warning`, or `critical`. - The `Definition` field can be provided with details for a TCP or HTTP health - check. For more information, see the [Health Checks](/consul/docs/discovery/checks) page. + You can provide defaults for TCP and HTTP health checks to the `Definition` field. Refer to [Health Checks](/consul/docs/services/usage/checks) for additional information. Multiple checks can be provided by replacing `Check` with `Checks` and sending an array of `Check` objects. diff --git a/website/content/api-docs/config.mdx b/website/content/api-docs/config.mdx index 3c49e0c8d34..96e6a7b4de7 100644 --- a/website/content/api-docs/config.mdx +++ b/website/content/api-docs/config.mdx @@ -31,25 +31,32 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------------------------- | -| `NO` | `none` | `none` | `service:write`
    `operator:write`1 | - -

    - 1 The ACL required depends on the config entry kind being updated: -

    - -| Config Entry Kind | Required ACL | -| ------------------- | ------------------ | -| ingress-gateway | `operator:write` | -| proxy-defaults | `operator:write` | -| service-defaults | `service:write` | -| service-intentions | `intentions:write` | -| service-resolver | `service:write` | -| service-router | `service:write` | -| service-splitter | `service:write` | -| terminating-gateway | `operator:write` | +| `NO` | `none` | `none` | Refer to [Permissions](#permissions) | The corresponding CLI command is [`consul config write`](/consul/commands/config/write). +### Permissions + +The ACL required depends on the config entry being written: + +| Config Entry Kind | Required ACLs | +| ------------------- | -------------------------------- | +| api-gateway | `mesh:write` or `operator:write` | +| bound-api-gateway | Not writable. | +| exported-services | `mesh:write` or `operator:write` | +| http-route | `mesh:write` or `operator:write` | +| ingress-gateway | `mesh:write` or `operator:write` | +| inline-certificate | `mesh:write` or `operator:write` | +| mesh | `mesh:write` or `operator:write` | +| proxy-defaults | `mesh:write` or `operator:write` | +| service-defaults | `service:write` | +| service-intentions | `intentions:write` | +| service-resolver | `service:write` | +| service-router | `service:write` | +| service-splitter | `service:write` | +| tcp-route | `mesh:write` or `operator:write` | +| terminating-gateway | `mesh:write` or `operator:write` | + ### Query Parameters - `dc` `(string: "")` - Specifies the datacenter to query. @@ -96,25 +103,35 @@ The table below shows this endpoint's support for [agent caching](/consul/api-docs/features/caching), and [required ACLs](/consul/api-docs/api-structure#authentication). -| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | -| ---------------- | ----------------- | ------------- | -------------------------- | -| `YES` | `all` | `none` | `service:read`1 | - -1 The ACL required depends on the config entry kind being read: +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | -------------------------------------- | +| `YES` | `all` | `none` | Refer to [Permissions](#permissions-1) | -| Config Entry Kind | Required ACL | -| ------------------- | ----------------- | -| ingress-gateway | `service:read` | -| proxy-defaults | `` | -| service-defaults | `service:read` | -| service-intentions | `intentions:read` | -| service-resolver | `service:read` | -| service-router | `service:read` | -| service-splitter | `service:read` | -| terminating-gateway | `service:read` | The corresponding CLI command is [`consul config read`](/consul/commands/config/read). +### Permissions + +The ACL required depends on the config entry kind being read: + +| Config Entry Kind | Required ACLs | +| ------------------- | -------------------------------- | +| api-gateway | `service:read` | +| bound-api-gateway | `service:read` | +| exported-services | `mesh:read` or `operator:read` | +| http-route | `mesh:read` or `operator:read` | +| ingress-gateway | `service:read` | +| inline-certificate | `mesh:read` or `operator:read` | +| mesh | No ACL required | +| proxy-defaults | No ACL required | +| service-defaults | `service:read` | +| service-intentions | `intentions:read` | +| service-resolver | `service:read` | +| service-router | `service:read` | +| service-splitter | `service:read` | +| tcp-route | `mesh:read` or `operator:read` | +| terminating-gateway | `service:read` | + ### Path Parameters - `kind` `(string: )` - Specifies the kind of the entry to read. @@ -167,22 +184,31 @@ The table below shows this endpoint's support for [agent caching](/consul/api-docs/features/caching), and [required ACLs](/consul/api-docs/api-structure#authentication). -| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | -| ---------------- | ----------------- | ------------- | -------------------------- | -| `YES` | `all` | `none` | `service:read`1 | - -1 The ACL required depends on the config entry kind being read: - -| Config Entry Kind | Required ACL | -| ------------------- | ----------------- | -| ingress-gateway | `service:read` | -| proxy-defaults | `` | -| service-defaults | `service:read` | -| service-intentions | `intentions:read` | -| service-resolver | `service:read` | -| service-router | `service:read` | -| service-splitter | `service:read` | -| terminating-gateway | `service:read` | +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | -------------------------------------- | +| `YES` | `all` | `none` | Refer to [Permissions](#permissions-2) | + +### Permissions + +The ACL required depends on the config entry kind being read: + +| Config Entry Kind | Required ACLs | +| ------------------- | -------------------------------- | +| api-gateway | `service:read` | +| bound-api-gateway | `service:read` | +| exported-services | `mesh:read` or `operator:read` | +| http-route | `mesh:read` or `operator:read` | +| ingress-gateway | `service:read` | +| inline-certificate | `mesh:read` or `operator:read` | +| mesh | No ACL required | +| proxy-defaults | No ACL required | +| service-defaults | `service:read` | +| service-intentions | `intentions:read` | +| service-resolver | `service:read` | +| service-router | `service:read` | +| service-splitter | `service:read` | +| tcp-route | `mesh:read` or `operator:read` | +| terminating-gateway | `service:read` | The corresponding CLI command is [`consul config list`](/consul/commands/config/list). @@ -243,20 +269,29 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------------------------- | -| `NO` | `none` | `none` | `service:write`
    `operator:write`1 | - -1 The ACL required depends on the config entry kind being deleted: - -| Config Entry Kind | Required ACL | -| ------------------- | ------------------ | -| ingress-gateway | `operator:write` | -| proxy-defaults | `operator:write` | -| service-defaults | `service:write` | -| service-intentions | `intentions:write` | -| service-resolver | `service:write` | -| service-router | `service:write` | -| service-splitter | `service:write` | -| terminating-gateway | `operator:write ` | +| `NO` | `none` | `none` | Refer to [Permissions](#permissions-3) | + +### Permissions + +The ACL required depends on the config entry kind being deleted: + +| Config Entry Kind | Required ACLs | +| ------------------- | -------------------------------- | +| api-gateway | `mesh:write` or `operator:write` | +| bound-api-gateway | Not writable. | +| exported-services | `mesh:write` or `operator:write` | +| http-route | `mesh:write` or `operator:write` | +| ingress-gateway | `mesh:write` or `operator:write` | +| inline-certificate | `mesh:write` or `operator:write` | +| mesh | `mesh:write` or `operator:write` | +| proxy-defaults | `mesh:write` or `operator:write` | +| service-defaults | `service:write` | +| service-intentions | `intentions:write` | +| service-resolver | `service:write` | +| service-router | `service:write` | +| service-splitter | `service:write` | +| tcp-route | `mesh:write` or `operator:write` | +| terminating-gateway | `mesh:write` or `operator:write` | The corresponding CLI command is [`consul config delete`](/consul/commands/config/delete). diff --git a/website/content/api-docs/connect/intentions.mdx b/website/content/api-docs/connect/intentions.mdx index 870b6d4b77d..1658b16992f 100644 --- a/website/content/api-docs/connect/intentions.mdx +++ b/website/content/api-docs/connect/intentions.mdx @@ -43,16 +43,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------ | -| `NO` | `none` | `none` | `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `NO` | `none` | `none` | `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention create -replace`](/consul/commands/intention/create#replace). @@ -149,16 +140,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------ | -| `NO` | `none` | `none` | `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `NO` | `none` | `none` | `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention create`](/consul/commands/intention/create). @@ -246,16 +228,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------ | -| `NO` | `none` | `none` | `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `NO` | `none` | `none` | `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | This endpoint supports the same parameters as the [create an intention](#create-intention-with-id) endpoint. Additional parameters unique to this endpoint include: @@ -300,16 +273,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ----------------------------- | -| `YES` | `all` | `none` | `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `YES` | `all` | `none` | `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention get`](/consul/commands/intention/get). @@ -372,16 +336,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ----------------------------- | -| `YES` | `all` | `none` | `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `YES` | `all` | `none` | `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention get`](/consul/commands/intention/get). @@ -435,16 +390,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ----------------------------- | -| `YES` | `all` | `none` | `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `YES` | `all` | `none` | `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention list`](/consul/commands/intention/list). @@ -522,16 +468,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------ | -| `NO` | `none` | `none` | `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `NO` | `none` | `none` | `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention delete`](/consul/commands/intention/delete). @@ -577,16 +514,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------------------------ | -| `NO` | `none` | `none` | `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `NO` | `none` | `none` | `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention delete`](/consul/commands/intention/delete). @@ -633,16 +561,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ----------------------------- | -| `NO` | `none` | `none` | `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `NO` | `none` | `none` | `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention check`](/consul/commands/intention/check). @@ -693,16 +612,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | -------------------- | ----------------------------- | -| `YES` | `all` | `background refresh` | `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `YES` | `all` | `background refresh` | `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | The corresponding CLI command is [`consul intention match`](/consul/commands/intention/match). diff --git a/website/content/api-docs/features/consistency.mdx b/website/content/api-docs/features/consistency.mdx index bcd7d621d65..746b062ab4c 100644 --- a/website/content/api-docs/features/consistency.mdx +++ b/website/content/api-docs/features/consistency.mdx @@ -131,7 +131,7 @@ The following diagrams show the cross-datacenter request paths when Consul serve ### Consul DNS Queries -When DNS queries are issued to [Consul's DNS interface](/consul/docs/discovery/dns), +When DNS queries are issued to [Consul's DNS interface](/consul/docs/services/discovery/dns-overview), Consul uses the `stale` consistency mode by default when interfacing with its underlying Consul service discovery HTTP APIs ([Catalog](/consul/api-docs/catalog), [Health](/consul/api-docs/health), and [Prepared Query](/consul/api-docs/query)). diff --git a/website/content/api-docs/operator/usage.mdx b/website/content/api-docs/operator/usage.mdx new file mode 100644 index 00000000000..75298e32b20 --- /dev/null +++ b/website/content/api-docs/operator/usage.mdx @@ -0,0 +1,167 @@ +--- +layout: api +page_title: Usage - Operator - HTTP API +description: |- + The /operator/usage endpoint returns usage information about the number of + services, service instances and Connect-enabled service instances by + datacenter. +--- + +# Usage Operator HTTP API + +The `/operator/usage` endpoint returns usage information about the number of +services, service instances and Connect-enabled service instances by datacenter. + +| Method | Path | Produces | +| ------ | ----------------- | ------------------ | +| `GET` | `/operator/usage` | `application/json` | + +The table below shows this endpoint's support for +[blocking queries](/consul/api-docs/features/blocking), +[consistency modes](/consul/api-docs/features/consistency), +[agent caching](/consul/api-docs/features/caching), and +[required ACLs](/consul/api-docs/api-structure#authentication). + +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | --------------- | +| `YES` | `all` | `none` | `operator:read` | + +The corresponding CLI command is [`consul operator usage instances`](/consul/commands/operator/usage). + +### Query Parameters + +- `global` `(bool: false)` - If present, usage information for all + known datacenters will be returned. By default, only the local datacenter's + usage information is returned. + +- `stale` `(bool: false)` - If the cluster does not currently have a leader, an + error will be returned. You can use the `?stale` query parameter to read the + Raft configuration from any of the Consul servers. + +### Sample Request + +```shell-session +$ curl \ + http://127.0.0.1:8500/v1/operator/usage +``` + +### Sample Response + + + + + +```json +{ + "Usage": { + "dc1": { + "Services": 1, + "ServiceInstances": 1, + "ConnectServiceInstances": { + "connect-native": 0, + "connect-proxy": 0, + "ingress-gateway": 0, + "mesh-gateway": 0, + "terminating-gateway": 0 + }, + "BillableServiceInstances": 0 + } + }, + "Index": 13, + "LastContact": 0, + "KnownLeader": true, + "ConsistencyLevel": "leader", + "NotModified": false, + "Backend": 0, + "ResultsFilteredByACLs": false +} +``` + + + + + +```json +{ + "Usage": { + "dc1": { + "Services": 1, + "ServiceInstances": 1, + "ConnectServiceInstances": { + "connect-native": 0, + "connect-proxy": 0, + "ingress-gateway": 0, + "mesh-gateway": 0, + "terminating-gateway": 0 + }, + "BillableServiceInstances": 0, + "PartitionNamespaceServices": { + "default": { + "default": 1 + } + }, + "PartitionNamespaceServiceInstances": { + "default": { + "default": 1 + } + }, + "PartitionNamespaceBillableServiceInstances": { + "default": { + "default": 1 + } + }, + "PartitionNamespaceConnectServiceInstances": { + "default": { + "default": { + "connect-native": 0, + "connect-proxy": 0, + "ingress-gateway": 0, + "mesh-gateway": 0, + "terminating-gateway": 0 + } + } + } + } + }, + "Index": 13, + "LastContact": 0, + "KnownLeader": true, + "ConsistencyLevel": "leader", + "NotModified": false, + "Backend": 0, + "ResultsFilteredByACLs": false +} +``` + + + + +- `Services` is the total number of unique service names registered in the + datacenter. + +- `ServiceInstances` is the total number of service instances registered in + the datacenter. + +- `ConnectServiceInstances` is the total number of Connect service instances + registered in the datacenter. + +- `BillableServiceInstances` is the total number of billable service instances + registered in the datacenter. This is only relevant in Consul Enterprise + and is the total service instance count, not including any Connect service + instances or any Consul server instances. + +- `PartitionNamespaceServices` is the total number + of unique service names registered in the datacenter, by partition and + namespace. + +- `PartitionNamespaceServiceInstances` is the total + number of service instances registered in the datacenter, by partition and + namespace. + +- `PartitionNamespaceBillableServiceInstances` is + the total number of billable service instances registered in the datacenter, + by partition and namespace. + +- `PartitionNamespaceConnectServiceInstances` is + the total number of Connect service instances registered in the datacenter, + by partition and namespace. diff --git a/website/content/api-docs/query.mdx b/website/content/api-docs/query.mdx index b3e2dcc2525..09aa867c479 100644 --- a/website/content/api-docs/query.mdx +++ b/website/content/api-docs/query.mdx @@ -9,10 +9,9 @@ description: The /query endpoints manage and execute prepared queries in Consul. The `/query` endpoints create, update, destroy, and execute prepared queries. Prepared queries allow you to register a complex service query and then execute -it later via its ID or name to get a set of healthy nodes that provide a given -service. This is particularly useful in combination with Consul's -[DNS Interface](/consul/docs/discovery/dns#prepared-query-lookups) as it allows for much richer queries than -would be possible given the limited entry points exposed by DNS. +it later by specifying the query ID or name. Consul returns a set of healthy nodes that provide a given +service. Refer to +[Enable Dynamic DNS Queries](/consul/docs/services/discovery/dns-dynamic-lookups) for additional information. Check the [Geo Failover tutorial](/consul/tutorials/developer-discovery/automate-geo-failover) for details and examples for using prepared queries to implement geo failover for services. diff --git a/website/content/commands/acl/policy/update.mdx b/website/content/commands/acl/policy/update.mdx index e62dfa72d99..f64a1f79068 100644 --- a/website/content/commands/acl/policy/update.mdx +++ b/website/content/commands/acl/policy/update.mdx @@ -49,6 +49,8 @@ Usage: `consul acl policy update [options] [args]` the value is a file path to load the rules from. `-` may also be given to indicate that the rules are available on stdin. +~> Specifying `-rules` will overwrite existing rules. + - `-valid-datacenter=` - Datacenter that the policy should be valid within. This flag may be specified multiple times. diff --git a/website/content/commands/acl/token/update.mdx b/website/content/commands/acl/token/update.mdx index 28158e6db79..1a1703cb143 100644 --- a/website/content/commands/acl/token/update.mdx +++ b/website/content/commands/acl/token/update.mdx @@ -33,34 +33,62 @@ Usage: `consul acl token update [options]` - `-id=` - The Accessor ID of the token to read. It may be specified as a unique ID prefix but will error if the prefix matches multiple token Accessor IDs -- `merge-node-identities` - Merge the new node identities with the existing node +- `-merge-node-identities` - Deprecated. Merge the new node identities with the existing node identities. +~> This is deprecated and will be removed in a future Consul version. Use `append-node-identity` instead. -- `-merge-policies` - Merge the new policies with the existing policies. +- `-merge-policies` - Deprecated. Merge the new policies with the existing policies. -- `-merge-roles` - Merge the new roles with the existing roles. +~> This is deprecated and will be removed in a future Consul version. Use `append-policy-id` or `append-policy-name` +instead. -- `-merge-service-identities` - Merge the new service identities with the existing service identities. +- `-merge-roles` - Deprecated. Merge the new roles with the existing roles. + +~> This is deprecated and will be removed in a future Consul version. Use `append-role-id` or `append-role-name` +instead. + +- `-merge-service-identities` - Deprecated. Merge the new service identities with the existing service identities. + +~> This is deprecated and will be removed in a future Consul version. Use `append-service-identity` instead. - `-meta` - Indicates that token metadata such as the content hash and Raft indices should be shown for each entry. -- `-node-identity=` - Name of a node identity to use for this role. May +- `-node-identity=` - Name of a node identity to use for this role. Overwrites existing node identity. May be specified multiple times. Format is `NODENAME:DATACENTER`. Added in Consul 1.8.1. -- `-policy-id=` - ID of a policy to use for this token. May be specified multiple times. +- `-append-node-identity=` - Name of a node identity to add to this role. May + be specified multiple times. The token retains existing node identities. Format is `NODENAME:DATACENTER`. + +- `-policy-id=` - ID of a policy to use for this token. Overwrites existing policies. May be specified multiple times. + +- `-policy-name=` - Name of a policy to use for this token. Overwrites existing policies. May be specified multiple times. + +~> `-policy-id` and `-policy-name` will overwrite existing token policies. Use `-append-policy-id` or `-append-policy-name` to retain existing policies. -- `-policy-name=` - Name of a policy to use for this token. May be specified multiple times. +- `-append-policy-id=` - ID of policy to be added for this token. The token retains existing policies. May be specified multiple times. -- `-role-id=` - ID of a role to use for this token. May be specified multiple times. +- `-append-policy-name=` - Name of a policy to be added for this token. The token retains existing policies. May be specified multiple times. -- `-role-name=` - Name of a role to use for this token. May be specified multiple times. +- `-role-id=` - ID of a role to use for this token. Overwrites existing roles. May be specified multiple times. + +- `-role-name=` - Name of a role to use for this token. Overwrites existing roles. May be specified multiple times. + +~> `-role-id` and `-role-name` will overwrite existing roles. Use `-append-role-id` or `-append-role-name` to retain the existing roles. + +- `-append-role-id=` - ID of a role to add to this token. The token retains existing roles. May be specified multiple times. + +- `-append-role-name=` - Name of a role to add to this token. The token retains existing roles. May be specified multiple times. - `-service-identity=` - Name of a service identity to use for this - token. May be specified multiple times. Format is the `SERVICENAME` or + token. Overwrites existing service identities. May be specified multiple times. Format is the `SERVICENAME` or `SERVICENAME:DATACENTER1,DATACENTER2,...` +- `-append-service-identity=` - Name of a service identity to add to this + token. May be specified multiple times. The token retains existing service identities. + Format is the `SERVICENAME` or `SERVICENAME:DATACENTER1,DATACENTER2,...` + - `-format={pretty|json}` - Command output format. The default value is `pretty`. #### Enterprise Options diff --git a/website/content/commands/config/delete.mdx b/website/content/commands/config/delete.mdx index 3fc9e6618b0..134d6885e66 100644 --- a/website/content/commands/config/delete.mdx +++ b/website/content/commands/config/delete.mdx @@ -27,6 +27,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | Config Entry Kind | Required ACL | | ------------------- | ------------------ | +| api-gateway | `mesh:write` | | ingress-gateway | `operator:write` | | proxy-defaults | `operator:write` | | service-defaults | `service:write` | @@ -45,16 +46,16 @@ Usage: `consul config delete [options]` - `-kind` - Specifies the kind of the config entry to read. - `-name` - Specifies the name of the config entry to delete. The name of the - `proxy-defaults` config entry must be `global`, and the name of the `mesh` - config entry must be `mesh`. +`proxy-defaults` config entry must be `global`, and the name of the `mesh` +config entry must be `mesh`. - `-filename` - Specifies the file describing the config entry to delete. - `-cas` - Perform a Check-And-Set operation. Specifying this value also - requires the -modify-index flag to be set. The default value is false. +requires the -modify-index flag to be set. The default value is false. - `-modify-index=` - Unsigned integer representing the ModifyIndex of the - config entry. This is used in combination with the -cas flag. +config entry. This is used in combination with the -cas flag. #### Enterprise Options diff --git a/website/content/commands/config/list.mdx b/website/content/commands/config/list.mdx index c72e3e903de..1a70af17872 100644 --- a/website/content/commands/config/list.mdx +++ b/website/content/commands/config/list.mdx @@ -27,6 +27,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | Config Entry Kind | Required ACL | | ------------------- | ----------------- | +| api-gateway | `mesh:read` | | ingress-gateway | `service:read` | | proxy-defaults | `` | | service-defaults | `service:read` | diff --git a/website/content/commands/config/read.mdx b/website/content/commands/config/read.mdx index a50574aaed7..7a49482c5b3 100644 --- a/website/content/commands/config/read.mdx +++ b/website/content/commands/config/read.mdx @@ -28,6 +28,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | Config Entry Kind | Required ACL | | ------------------- | ----------------- | +| api-gateway | `mesh:read` | | ingress-gateway | `service:read` | | proxy-defaults | `` | | service-defaults | `service:read` | diff --git a/website/content/commands/config/write.mdx b/website/content/commands/config/write.mdx index 7e586aff759..24e17aa34ae 100644 --- a/website/content/commands/config/write.mdx +++ b/website/content/commands/config/write.mdx @@ -30,6 +30,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | Config Entry Kind | Required ACL | | ------------------- | ------------------ | +| api-gateway | `mesh:write` | | ingress-gateway | `operator:write` | | proxy-defaults | `operator:write` | | service-defaults | `service:write` | diff --git a/website/content/commands/connect/envoy.mdx b/website/content/commands/connect/envoy.mdx index 90bccf2faf8..c9e0ffb2e93 100644 --- a/website/content/commands/connect/envoy.mdx +++ b/website/content/commands/connect/envoy.mdx @@ -56,7 +56,7 @@ Usage: `consul connect envoy [options] [-- pass-through options]` ACL token from `-token` or the environment and so should be handled as a secret. This token grants the identity of any service it has `service:write` permission for and so can be used to access any upstream service that that service is - allowed to access by [Connect intentions](/consul/docs/connect/intentions). + allowed to access by [service mesh intentions](/consul/docs/connect/intentions). - `-envoy-version` - The version of envoy that is being started. Default is `1.23.1`. This is required so that the correct configuration can be generated. @@ -75,6 +75,10 @@ Usage: `consul connect envoy [options] [-- pass-through options]` In cases where either assumption is violated this flag will prevent the command attempting to resolve config from the local agent. +- `envoy_hcp_metrics_bind_socket_dir` - Specifies the directory where Envoy creates a unix socket. + Envoy sends metrics to the socket so that HCP collectors can connect to collect them." + The socket is not configured by default. + - `-envoy-ready-bind-address` - By default the proxy does not have a readiness probe configured on it. This flag in conjunction with the `envoy-ready-bind-port` flag configures where the envoy readiness probe is configured on the proxy. A `/ready` HTTP diff --git a/website/content/commands/connect/index.mdx b/website/content/commands/connect/index.mdx index 166b8ff8352..b390f189935 100644 --- a/website/content/commands/connect/index.mdx +++ b/website/content/commands/connect/index.mdx @@ -10,7 +10,7 @@ description: >- Command: `consul connect` The `connect` command is used to interact with Consul -[Connect](/consul/docs/connect/intentions) subsystems. It exposes commands for +[service mesh](/consul/docs/connect) subsystems. It exposes commands for running the built-in mTLS proxy and viewing/updating the Certificate Authority (CA) configuration. This command is available in Consul 1.2 and later. diff --git a/website/content/commands/index.mdx b/website/content/commands/index.mdx index 2946d794ba4..415fc551d33 100644 --- a/website/content/commands/index.mdx +++ b/website/content/commands/index.mdx @@ -56,6 +56,7 @@ Available commands are: services Interact with services snapshot Saves, restores and inspects snapshots of Consul server state tls Builtin helpers for creating CAs and certificates + troubleshoot Provides tools to troubleshoot Consul's service mesh configuration validate Validate config files/directories version Prints the Consul version watch Watch for changes in Consul diff --git a/website/content/commands/intention/check.mdx b/website/content/commands/intention/check.mdx index a0c384c6461..fadc8d224bd 100644 --- a/website/content/commands/intention/check.mdx +++ b/website/content/commands/intention/check.mdx @@ -31,16 +31,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | ACL Required | | ----------------------------- | -| `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | ## Usage diff --git a/website/content/commands/intention/create.mdx b/website/content/commands/intention/create.mdx index b41b9c502fb..71c491b06ad 100644 --- a/website/content/commands/intention/create.mdx +++ b/website/content/commands/intention/create.mdx @@ -25,16 +25,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | ACL Required | | ------------------------------ | -| `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | ## Usage diff --git a/website/content/commands/intention/delete.mdx b/website/content/commands/intention/delete.mdx index 1f6971c4906..ad2fca1c97e 100644 --- a/website/content/commands/intention/delete.mdx +++ b/website/content/commands/intention/delete.mdx @@ -19,16 +19,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | ACL Required | | ------------------------------ | -| `intentions:write`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `intentions:write`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | -> **Deprecated** - The one argument form of this command is deprecated in Consul 1.9.0. Intentions no longer need IDs when represented as diff --git a/website/content/commands/intention/get.mdx b/website/content/commands/intention/get.mdx index 6ac253c898e..84b3a86065d 100644 --- a/website/content/commands/intention/get.mdx +++ b/website/content/commands/intention/get.mdx @@ -24,16 +24,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | ACL Required | | ----------------------------- | -| `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | ## Usage diff --git a/website/content/commands/intention/index.mdx b/website/content/commands/intention/index.mdx index 9246ef9e050..d3e4fc56c43 100644 --- a/website/content/commands/intention/index.mdx +++ b/website/content/commands/intention/index.mdx @@ -14,10 +14,9 @@ The `intention` command is used to interact with Connect creating, updating, reading, deleting, checking, and managing intentions. This command is available in Consul 1.2 and later. -Intentions are managed primarily via -[`service-intentions`](/consul/docs/connect/config-entries/service-intentions) config -entries after Consul 1.9. Intentions may also be managed via the [HTTP -API](/consul/api-docs/connect/intentions). +Use the +[`service-intentions`](/consul/docs/connect/config-entries/service-intentions) configuration entry or the [HTTP +API](/consul/api-docs/connect/intentions) to manage intentions. ~> **Deprecated** - This command is deprecated in Consul 1.9.0 in favor of using the [config entry CLI command](/consul/commands/config/write). To create an diff --git a/website/content/commands/intention/list.mdx b/website/content/commands/intention/list.mdx index cc5130ac993..85a7d6b9d6c 100644 --- a/website/content/commands/intention/list.mdx +++ b/website/content/commands/intention/list.mdx @@ -19,16 +19,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | ACL Required | | ----------------------------- | -| `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | ## Usage diff --git a/website/content/commands/intention/match.mdx b/website/content/commands/intention/match.mdx index 4936e12ba35..4c727b1fc21 100644 --- a/website/content/commands/intention/match.mdx +++ b/website/content/commands/intention/match.mdx @@ -24,16 +24,7 @@ are not supported from commands, but may be from the corresponding HTTP endpoint | ACL Required | | ----------------------------- | -| `intentions:read`1 | - -

    - 1 Intention ACL rules are specified as part of a{' '} - service rule. See{' '} - - Intention Management Permissions - {' '} - for more details. -

    +| `intentions:read`

    Define intention rules in the `service` policy. Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for additional information.

    | ## Usage diff --git a/website/content/commands/operator/index.mdx b/website/content/commands/operator/index.mdx index 14ba3290528..060c4225588 100644 --- a/website/content/commands/operator/index.mdx +++ b/website/content/commands/operator/index.mdx @@ -37,6 +37,7 @@ Subcommands: area Provides tools for working with network areas (Enterprise-only) autopilot Provides tools for modifying Autopilot configuration raft Provides cluster-level tools for Consul operators + usage Provides cluster-level usage information ``` For more information, examples, and usage about a subcommand, click on the name @@ -45,3 +46,4 @@ of the subcommand in the sidebar or one of the links below: - [area](/consul/commands/operator/area) - [autopilot](/consul/commands/operator/autopilot) - [raft](/consul/commands/operator/raft) +- [usage](/consul/commands/operator/usage) diff --git a/website/content/commands/operator/usage.mdx b/website/content/commands/operator/usage.mdx new file mode 100644 index 00000000000..b0d7d03db2f --- /dev/null +++ b/website/content/commands/operator/usage.mdx @@ -0,0 +1,100 @@ +--- +layout: commands +page_title: 'Commands: Operator Usage' +description: > + The operator usage command provides cluster-level tools for Consul operators + to view usage information, such as service and service instance counts. +--- + +# Consul Operator Usage + +Command: `consul operator usage` + +The Usage `operator` command provides cluster-level tools for Consul operators +to view usage information, such as service and service instance counts. + +```text +Usage: consul operator usage [options] + + # ... + +Subcommands: + instances Display service instance usage information +``` + +## instances + +Corresponding HTTP API Endpoint: [\[GET\] /v1/operator/usage](/consul/api-docs/operator/usage#operator-usage) + +This command retrieves usage information about the number of services registered in a given +datacenter. By default, the datacenter of the local agent is queried. + +The table below shows this command's [required ACLs](/consul/api-docs/api-structure#authentication). Configuration of +[blocking queries](/consul/api-docs/features/blocking) and [agent caching](/consul/api-docs/features/caching) +are not supported from commands, but may be from the corresponding HTTP endpoint. + +| ACL Required | +| --------------- | +| `operator:read` | + +Usage: `consul operator usage instances` + +The output looks like this: + +```text +$ consul operator usage instances +Billable Service Instances Total: 3 +dc1 Billable Service Instances: 3 + +Billable Services +Services Service instances +2 3 + +Connect Services +Type Service instances +connect-native 0 +connect-proxy 0 +ingress-gateway 0 +mesh-gateway 1 +terminating-gateway 0 +``` + +With the `-all-datacenters` flag: + +```text +$ consul operator usage instances -all-datacenters +Billable Service Instances Total: 4 +dc1 Billable Service Instances: 3 +dc2 Billable Service Instances: 1 + +Billable Services +Datacenter Services Service instances +dc1 2 3 +dc2 1 1 + +Total 3 4 + +Connect Services +Datacenter Type Service instances +dc1 connect-native 0 +dc1 connect-proxy 0 +dc1 ingress-gateway 0 +dc1 mesh-gateway 1 +dc1 terminating-gateway 0 +dc2 connect-native 0 +dc2 connect-proxy 0 +dc2 ingress-gateway 0 +dc2 mesh-gateway 1 +dc2 terminating-gateway 1 + +Total 3 +``` + +#### Command Options + +- `-all-datacenters` - Display service counts from all known datacenters. + Default is `false`. + +- `-billable` - Display only billable service information. Default is `false`. + +- `-connect` - Display only Connect service information. Default is `false`. diff --git a/website/content/commands/services/deregister.mdx b/website/content/commands/services/deregister.mdx index 5cc774422c0..79ea7cba27b 100644 --- a/website/content/commands/services/deregister.mdx +++ b/website/content/commands/services/deregister.mdx @@ -13,23 +13,19 @@ Corresponding HTTP API Endpoint: [\[PUT\] /v1/agent/service/deregister/:service_ The `services deregister` command deregisters a service with the local agent. Note that this command can only deregister services that were registered -with the agent specified (defaults to the local agent) and is meant to -be paired with `services register`. +with the agent specified and is intended to be paired with `services register`. +By default, the command deregisters services on the local agent. -This is just one method for service deregistration. If the service was -registered with a configuration file, then deleting that file and -[reloading](/consul/commands/reload) Consul is the correct method to -deregister. See [Service Definition](/consul/docs/discovery/services) for more -information about registering services generally. +We recommend deregistering services registered with a configuration file by deleting the file and [reloading](/consul/commands/reload) Consul. Refer to [Services Overview](/consul/docs/services/services) for additional information about services. -The table below shows this command's [required ACLs](/consul/api-docs/api-structure#authentication). Configuration of -[blocking queries](/consul/api-docs/features/blocking) and [agent caching](/consul/api-docs/features/caching) -are not supported from commands, but may be from the corresponding HTTP endpoint. +The following table shows the [ACLs](/consul/api-docs/api-structure#authentication) required to run the `consul services deregister` command: | ACL Required | | --------------- | | `service:write` | +You cannot use the Consul command line to configure [blocking queries](/consul/api-docs/features/blocking) and [agent caching](/consul/api-docs/features/caching), you can configure them from the corresponding HTTP endpoint. + ## Usage Usage: `consul services deregister [options] [FILE...]` diff --git a/website/content/commands/services/register.mdx b/website/content/commands/services/register.mdx index 8e3a9d1333e..cefa2359230 100644 --- a/website/content/commands/services/register.mdx +++ b/website/content/commands/services/register.mdx @@ -14,24 +14,16 @@ Corresponding HTTP API Endpoint: [\[PUT\] /v1/agent/service/register](/consul/ap The `services register` command registers a service with the local agent. This command returns after registration and must be paired with explicit service deregistration. This command simplifies service registration from -scripts, in dev mode, etc. +scripts. Refer to [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) for information about other service registeration methods. -This is just one method of service registration. Services can also be -registered by placing a [service definition](/consul/docs/discovery/services) -in the Consul agent configuration directory and issuing a -[reload](/consul/commands/reload). This approach is easiest for -configuration management systems that other systems that have access to -the configuration directory. Clients may also use the -[HTTP API](/consul/api-docs/agent/service) directly. - -The table below shows this command's [required ACLs](/consul/api-docs/api-structure#authentication). Configuration of -[blocking queries](/consul/api-docs/features/blocking) and [agent caching](/consul/api-docs/features/caching) -are not supported from commands, but may be from the corresponding HTTP endpoint. +The following table shows the [ACLs](/consul/api-docs/api-structure#authentication) required to use the `consul services register` command: | ACL Required | | --------------- | | `service:write` | +You cannot use the Consul command line to configure [blocking queries](/consul/api-docs/features/blocking) and [agent caching](/consul/api-docs/features/caching), you can configure them from the corresponding HTTP endpoint. + ## Usage Usage: `consul services register [options] [FILE...]` @@ -65,9 +57,7 @@ The flags below should only be set if _no arguments_ are given. If no arguments are given, the flags below can be used to register a single service. -Note that the behavior of each of the fields below is exactly the same -as when constructing a standard [service definition](/consul/docs/discovery/services). -Please refer to that documentation for full details. +The following fields specify identical parameters in a standard service definition file. Refer to [Services Configuration Reference](/consul/docs/services/configuration/services-configuration-reference) for details about each configuration option. - `-id` - The ID of the service. This will default to `-name` if not set. diff --git a/website/content/commands/troubleshoot/index.mdx b/website/content/commands/troubleshoot/index.mdx new file mode 100644 index 00000000000..0c992aab15c --- /dev/null +++ b/website/content/commands/troubleshoot/index.mdx @@ -0,0 +1,31 @@ +--- +layout: commands +page_title: 'Commands: Troubleshoot' +description: >- + The `consul troubleshoot` command provides tools to troubleshoot Consul's service mesh configuration. +--- + +# Consul Troubleshooting + +Command: `consul troubleshoot` + +Use the `troubleshoot` command to diagnose Consul service mesh configuration or network issues. For additional information about using the `troubleshoot` command, including explanations, requirements, usage instructions, refer to the [service-to-service troubleshooting overview](/consul/docs/troubleshoot/troubleshoot-services). + +## Usage + +```text +Usage: consul troubleshoot [options] + + # ... + +Subcommands: + + proxy Troubleshoots service mesh issues from the current Envoy instance + upstreams Gets upstream Envoy identifiers and IPs configured for the proxy +``` + +For more information, examples, and usage about a subcommand, click on the name +of the subcommand in the sidebar or one of the links below: + +- [proxy](/consul/commands/troubleshoot/proxy) +- [upstreams](/consul/commands/troubleshoot/upstreams) diff --git a/website/content/commands/troubleshoot/proxy.mdx b/website/content/commands/troubleshoot/proxy.mdx new file mode 100644 index 00000000000..d9749c0c254 --- /dev/null +++ b/website/content/commands/troubleshoot/proxy.mdx @@ -0,0 +1,65 @@ +--- +layout: commands +page_title: 'Commands: Troubleshoot Proxy' +description: >- + The `consul troubleshoot proxy` diagnoses Consul service mesh configuration and network issues to an upstream. +--- + +# Consul Troubleshoot Proxy + +Command: `consul troubleshoot proxy` + +The `troubleshoot proxy` command diagnoses Consul service mesh configuration and network issues to an upstream. For additional information about using the `troubleshoot proxy` command, including explanations, requirements, usage instructions, refer to the [service-to-service troubleshooting overview](/consul/docs/troubleshoot/troubleshoot-services). + +## Usage + +Usage: `consul troubleshoot proxy (-upstream-ip |-upstream-envoy-id ) [options]` +This command requires `-envoy-admin-endpoint` or `-upstream-ip` to specify the upstream. + +#### Command Options + +- `-envoy-admin-endpoint=` - Envoy admin endpoint address for the local Envoy instance. +Defaults to `127.0.0.1:19000`. + +- `-upstream-ip=` - The IP address of the upstream service; Use when the upstream is reached via a transparent proxy DNS address (KubeDNS or Consul DNS) + +- `-upstream-envoy-id=` - The Envoy identifier of the upstream service; Use when the upstream is explicitly configured on the downstream service. + +## Examples + +The following example illustrates how to troubleshoot Consul service mesh configuration and network issues to an upstream from a source proxy. + + ```shell-session + $ consul troubleshoot proxy -upstream-ip 10.4.6.160 + ==> Validation + ✓ Certificates are valid + ✓ Envoy has 0 rejected configurations + ✓ Envoy has detected 0 connection failure(s) + ✓ Listener for upstream "backend" found + ✓ Route for upstream "backend" found + ✓ Cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ Healthy endpoints for cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ Cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ! No healthy endpoints for cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + -> Check that your upstream service is healthy and running + -> Check that your upstream service is registered with Consul + -> Check that the upstream proxy is healthy and running + -> If you are explicitly configuring upstreams, ensure the name of the upstream is correct + ``` + +The following example troubleshoots Consul service mesh configuration and network issues from the local Envoy instance to the given upstream. + + ```shell-session + $ consul-k8s troubleshoot proxy -upstream-envoy-id db + ==> Validation + ✓ Certificates are valid + ✓ Envoy has 0 rejected configurations + ✓ Envoy has detected 0 connection failure(s) + ✓ Listener for upstream "db" found + ✓ Route for upstream "db" found + ✓ Cluster "db.default.dc1.internal.e08fa6d6-e91e-dfe0.consul" for upstream "db" found + ✓ Healthy endpoints for cluster "db.default.dc1.internal.e08fa6d6-e91e-dfe0.consul" for upstream "db" found + If you are still experiencing issues, you can: + -> Check intentions to ensure the upstream allows traffic from this source + -> If using transparent proxy, ensure DNS resolution is to the same IP you have verified here + ``` diff --git a/website/content/commands/troubleshoot/upstreams.mdx b/website/content/commands/troubleshoot/upstreams.mdx new file mode 100644 index 00000000000..425ec39e464 --- /dev/null +++ b/website/content/commands/troubleshoot/upstreams.mdx @@ -0,0 +1,35 @@ +--- +layout: commands +page_title: 'Commands: Troubleshoot Upstreams' +description: >- + The `consul troubleshoot upstreams` lists the available upstreams in the Consul service mesh from the current service. +--- + +# Consul Troubleshoot Upstreams + +Command: `consul troubleshoot upstreams` + +The `troubleshoot upstreams` lists the available upstreams in the Consul service mesh from the current service. For additional information about using the `troubleshoot upstreams` command, including explanations, requirements, usage instructions, refer to the [service-to-service troubleshooting overview](/consul/docs/troubleshoot/troubleshoot-services). + +## Usage + +Usage: `consul troubleshoot upstreams [options]` + +#### Command Options + +- `-envoy-admin-endpoint=` - Envoy admin endpoint address for the local Envoy instance. +Defaults to `127.0.0.1:19000`. + +## Examples + +Display all transparent proxy upstreams in Consul service mesh from the current Envoy instance. + + ```shell-session + $ consul troubleshoot upstreams + ==> Upstreams (explicit upstreams only) (0) + ==> Upstreams IPs (transparent proxy only) (1) + [10.4.6.160 240.0.0.3] true map[backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul] + If you cannot find the upstream address or cluster for a transparent proxy upstream: + - Check intentions: Tproxy upstreams are configured based on intentions. Make sure you have configured intentions to allow traffic to your upstream. + - To check that the right cluster is being dialed, run a DNS lookup for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing from the upstream IPs, it means that your proxy may be misconfigured. + ``` diff --git a/website/content/docs/agent/config/cli-flags.mdx b/website/content/docs/agent/config/cli-flags.mdx index ebcdb4c0764..bff79cc6352 100644 --- a/website/content/docs/agent/config/cli-flags.mdx +++ b/website/content/docs/agent/config/cli-flags.mdx @@ -92,7 +92,7 @@ information. only the given `-encrypt` key will be available on startup. This defaults to false. - `-enable-script-checks` ((#\_enable_script_checks)) This controls whether - [health checks that execute scripts](/consul/docs/discovery/checks) are enabled on this + [health checks that execute scripts](/consul/docs/services/usage/checks) are enabled on this agent, and defaults to `false` so operators must opt-in to allowing these. This was added in Consul 0.9.0. @@ -157,7 +157,7 @@ information. - `-segment` ((#\_segment)) - This flag is used to set the name of the network segment the agent belongs to. An agent can only join and communicate with other agents within its network segment. Ensure the [join - operation uses the correct port for this segment](/consul/docs/enterprise/network-segments#join_a_client_to_a_segment). + operation uses the correct port for this segment](/consul/docs/enterprise/network-segments/create-network-segment#configure-clients-to-join-segments). Review the [Network Segments documentation](/consul/docs/enterprise/network-segments/create-network-segment) for more details. By default, this is an empty string, which is the `` network segment. @@ -490,7 +490,7 @@ information. the data directory. This is useful when running multiple Consul agents on the same host for testing. This defaults to false in Consul prior to version 0.8.5 and in 0.8.5 and later defaults to true, so you must opt-in for host-based IDs. Host-based - IDs are generated using [gopsutil](https://github.com/shirou/gopsutil/tree/master/v3/host), which + IDs are generated using [gopsutil](https://github.com/shirou/gopsutil/), which is shared with HashiCorp's [Nomad](https://www.nomadproject.io/), so if you opt-in to host-based IDs then Consul and Nomad will use information on the host to automatically assign the same ID in both systems. diff --git a/website/content/docs/agent/config/config-files.mdx b/website/content/docs/agent/config/config-files.mdx index 82053a93567..4ba7ba54313 100644 --- a/website/content/docs/agent/config/config-files.mdx +++ b/website/content/docs/agent/config/config-files.mdx @@ -7,6 +7,10 @@ description: >- # Agents Configuration File Reference ((#configuration_files)) +This topic describes the parameters for configuring Consul agents. For information about how to start Consul agents, refer to [Starting the Consul Agent](/consul/docs/agent#starting-the-consul-agent). + +## Overview + You can create one or more files to configure the Consul agent on startup. We recommend grouping similar configurations into separate files, such as ACL parameters, to make it easier to manage configuration changes. Using external files may be easier than @@ -18,13 +22,6 @@ easily readable and editable by both humans and computers. JSON formatted configuration consists of a single JSON object with multiple configuration keys specified within it. -Configuration files are used for more than just setting up the agent. -They are also used to provide check and service definitions that -announce the availability of system servers to the rest of the cluster. -These definitions are documented separately under [check configuration](/consul/docs/discovery/checks) and -[service configuration](/consul/docs/discovery/services) respectively. Service and check -definitions support being updated during a reload. - ```hcl @@ -66,15 +63,27 @@ telemetry { -# Configuration Key Reference ((#config_key_reference)) +### Time-to-live values + +Consul uses the Go `time` package to parse all time-to-live (TTL) values used in Consul agent configuration files. Specify integer and float values as a string and include one or more of the following units of time: + +- `ns` +- `us` +- `µs` +- `ms` +- `s` +- `m` +- `h` --> **Note:** All the TTL values described below are parsed by Go's `time` package, and have the following -[formatting specification](https://golang.org/pkg/time/#ParseDuration): "A -duration string is a possibly signed sequence of decimal numbers, each with -optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'. -Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." +Examples: -## General +- `'300ms'` +- `'1.5h'` +- `'2h45m'` + +Refer to the [formatting specification](https://golang.org/pkg/time/#ParseDuration) for additional information. + +## General parameters - `addresses` - This is a nested object that allows setting bind addresses. In Consul 1.0 and later these can be set to a space-separated list @@ -534,17 +543,17 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." - `license_path` This specifies the path to a file that contains the Consul Enterprise license. Alternatively the license may also be specified in either the `CONSUL_LICENSE` or `CONSUL_LICENSE_PATH` environment variables. See the [licensing documentation](/consul/docs/enterprise/license/overview) for more information about Consul Enterprise license management. Added in versions 1.10.0, 1.9.7 and 1.8.13. Prior to version 1.10.0 the value may be set for all agents to facilitate forwards compatibility with 1.10 but will only actually be used by client agents. -- `limits` Available in Consul 0.9.3 and later, this is a nested - object that configures limits that are enforced by the agent. Prior to Consul 1.5.2, - this only applied to agents in client mode, not Consul servers. The following parameters - are available: +- `limits`: This block specifies various types of limits that the Consul server agent enforces. - `http_max_conns_per_client` - Configures a limit of how many concurrent TCP connections a single client IP address is allowed to open to the agent's HTTP(S) server. This affects the HTTP(S) servers in both client and server agents. Default value is `200`. - `https_handshake_timeout` - Configures the limit for how long the HTTPS server in both client and server agents will wait for a client to complete a TLS handshake. This should be kept conservative as it limits how many connections an unauthenticated attacker can open if `verify_incoming` is being using to authenticate clients (strongly recommended in production). Default value is `5s`. - - `request_limits` - This object povides configuration for rate limiting RPC and gRPC requests on the consul server. As a result of rate limiting gRPC and RPC request, HTTP requests to the Consul server are rate limited. - - `mode` - Configures whether rate limiting is enabled or not as well as how it behaves through the use of 3 possible modes. The default value of "disabled" will prevent any rate limiting from occuring. A value of "permissive" will cause the system to track requests against the `read_rate` and `write_rate` but will only log violations and will not block and will allow the request to continue processing. A value of "enforcing" also tracks requests against the `read_rate` and `write_rate` but in addition to logging violations, the system will block the request from processings by returning an error. - - `read_rate` - Configures how frequently RPC, gRPC, and HTTP queries are allowed to happen. The rate limiter limits the rate to tokens per second equal to this value. See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets. - - `write_rate` - Configures how frequently RPC, gRPC, and HTTP write are allowed to happen. The rate limiter limits the rate to tokens per second equal to this value. See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets. + - `request_limits` - This object specifies configurations that limit the rate of RPC and gRPC requests on the Consul server. Limiting the rate of gRPC and RPC requests also limits HTTP requests to the Consul server. + - `mode` - String value that specifies an action to take if the rate of requests exceeds the limit. You can specify the following values: + - `permissive`: The server continues to allow requests and records an error in the logs. + - `enforcing`: The server stops accepting requests and records an error in the logs. + - `disabled`: Limits are not enforced or tracked. This is the default value for `mode`. + - `read_rate` - Integer value that specifies the number of read requests per second. Default is `100`. + - `write_rate` - Integer value that specifies the number of write requests per second. Default is `100`. - `rpc_handshake_timeout` - Configures the limit for how long servers will wait after a client TCP connection is established before they complete the connection handshake. When TLS is used, the same timeout applies to the TLS handshake separately from the initial protocol negotiation. All Consul clients should perform this immediately on establishing a new connection. This should be kept conservative as it limits how many connections an unauthenticated attacker can open if `verify_incoming` is being using to authenticate clients (strongly recommended in production). When `verify_incoming` is true on servers, this limits how long the connection socket and associated goroutines will be held open before the client successfully authenticates. Default value is `5s`. - `rpc_client_timeout` - Configures the limit for how long a client is allowed to read from an RPC connection. This is used to set an upper bound for calls to eventually terminate so that RPC connections are not held indefinitely. Blocking queries can override this timeout. Default is `60s`. - `rpc_max_conns_per_client` - Configures a limit of how many concurrent TCP connections a single source IP address is allowed to open to a single server. It affects both clients connections and other server connections. In general Consul clients multiplex many RPC calls over a single TCP connection so this can typically be kept low. It needs to be more than one though since servers open at least one additional connection for raft RPC, possibly more for WAN federation when using network areas, and snapshot requests from clients run over a separate TCP conn. A reasonably low limit significantly reduces the ability of an unauthenticated attacker to consume unbounded resources by holding open many connections. You may need to increase this if WAN federated servers connect via proxies or NAT gateways or similar causing many legitimate connections from a single source IP. Default value is `100` which is designed to be extremely conservative to limit issues with certain deployment patterns. Most deployments can probably reduce this safely. 100 connections on modern server hardware should not cause a significant impact on resource usage from an unauthenticated attacker though. @@ -914,10 +923,9 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." - `agent_master` ((#acl_tokens_agent_master)) **Renamed in Consul 1.11 to [`acl.tokens.agent_recovery`](#acl_tokens_agent_recovery).** - - `config_file_service_registration` ((#acl_tokens_config_file_service_registration)) - The ACL - token this agent uses to register services and checks from [service - definitions](/consul/docs/discovery/services) and [check definitions](/consul/docs/discovery/checks) found - in configuration files or in configuration fragments passed to the agent using the `-hcl` + - `config_file_service_registration` ((#acl_tokens_config_file_service_registration)) - Specifies the ACL + token the agent uses to register services and checks from [service](/consul/docs/services/usage/define-services) and [check](/consul/docs/services/usage/checks) definitions + specified in configuration files or fragments passed to the agent using the `-hcl` flag. If the `token` field is defined in the service or check definition, then that token is used to @@ -1367,7 +1375,7 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." of `1ns` instead of 0. - `prefer_namespace` ((#dns_prefer_namespace)) **Deprecated in Consul 1.11. - Use the [canonical DNS format for enterprise service lookups](/consul/docs/discovery/dns#service-lookups-for-consul-enterprise) instead.** - + Use the [canonical DNS format for enterprise service lookups](/consul/docs/services/discovery/dns-static-lookups#service-lookups-for-consul-enterprise) instead.** - When set to `true`, in a DNS query for a service, a single label between the domain and the `service` label is treated as a namespace name instead of a datacenter. When set to `false`, the default, the behavior is the same as non-Enterprise @@ -1586,15 +1594,89 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." ## Raft Parameters -- `raft_boltdb` ((#raft_boltdb)) This is a nested object that allows configuring - options for Raft's BoltDB based log store. - - - `NoFreelistSync` ((#NoFreelistSync)) Setting this to `true` will disable - syncing the BoltDB freelist to disk within the raft.db file. Not syncing - the freelist to disk will reduce disk IO required for write operations - at the expense of potentially increasing start up time due to needing - to scan the db to discover where the free space resides within the file. - +- `raft_boltdb` ((#raft_boltdb)) **These fields are deprecated in Consul v1.15.0. + Use [`raft_logstore`](#raft_logstore) instead.** This is a nested + object that allows configuring options for Raft's BoltDB-based log store. + + - `NoFreelistSync` **This field is deprecated in Consul v1.15.0. Use the + [`raft_logstore.boltdb.no_freelist_sync`](#raft_logstore_boltdb_no_freelist_sync) field + instead.** Setting this to `true` disables syncing the BoltDB freelist + to disk within the raft.db file. Not syncing the freelist to disk + reduces disk IO required for write operations at the expense of potentially + increasing start up time due to needing to scan the db to discover where the + free space resides within the file. + +- `raft_logstore` ((#raft_logstore)) This is a nested object that allows + configuring options for Raft's LogStore component which is used to persist + logs and crucial Raft state on disk during writes. This was added in Consul + v1.15.0. + + - `backend` ((#raft_logstore_backend)) Specifies which storage + engine to use to persist logs. Valid options are `boltdb` or `wal`. Default + is `boltdb`. The `wal` option specifies an experimental backend that + should be used with caution. Refer to + [Experimental WAL LogStore backend](/consul/docs/agent/wal-logstore) + for more information. + + - `disable_log_cache` ((#raft_logstore_disable_log_cache)) Disables the in-memory cache for recent logs. We recommend using it for performance testing purposes, as no significant improvement has been measured when the cache is disabled. While the in-memory log cache theoretically prevents disk reads for recent logs, recent logs are also stored in the OS page cache, which does not slow either the `boltdb` or `wal` backend's ability to read them. + + - `verification` ((#raft_logstore_verification)) This is a nested object that + allows configuring the online verification of the LogStore. Verification + provides additional assurances that LogStore backends are correctly storing + data. It imposes low overhead on servers and is safe to run in + production. It is most useful when evaluating a new backend + implementation. + + Verification must be enabled on the leader to have any effect and can be + used with any backend. When enabled, the leader periodically writes a + special "checkpoint" log message that includes the checksums of all log entries + written to Raft since the last checkpoint. Followers that have verification + enabled run a background task for each checkpoint that reads all logs + directly from the LogStore and then recomputes the checksum. A report is output + as an INFO level log for each checkpoint. + + Checksum failure should never happen and indicate unrecoverable corruption + on that server. The only correct response is to stop the server, remove its + data directory, and restart so it can be caught back up with a correct + server again. Please report verification failures including details about + your hardware and workload via GitHub issues. Refer to + [Experimental WAL LogStore backend](/consul/docs/agent/wal-logstore) + for more information. + + - `enabled` ((#raft_logstore_verification_enabled)) - Set to `true` to + allow this Consul server to write and verify log verification checkpoints + when elected leader. + + - `interval` ((#raft_logstore_verification_interval)) - Specifies the time + interval between checkpoints. There is no default value. You must + configure the `interval` and set [`enabled`](#raft_logstore_verification_enabled) + to `true` to correctly enable intervals. We recommend using an interval + between `30s` and `5m`. The performance overhead is insignificant when the + interval is set to `5m` or less. + + - `boltdb` ((#raft_logstore_boltdb)) - Object that configures options for + Raft's `boltdb` backend. It has no effect if the `backend` is not `boltdb`. + + - `no_freelist_sync` ((#raft_logstore_boltdb_no_freelist_sync)) - Set to + `true` to disable storing BoltDB's freelist to disk within the + `raft.db` file. Disabling freelist syncs reduces the disk IO required + for write operations, but could potentially increase start up time + because Consul must scan the database to find free space + within the file. + + - - `wal` ((#raft_logstore_wal)) - Object that configures the `wal` backend. + Refer to [Experimental WAL LogStore backend](/consul/docs/agent/wal-logstore) + for more information. + + - `segment_size_mb` ((#raft_logstore_wal_segment_size_mb)) - Integer value + that represents the target size in MB for each segment file before + rolling to a new segment. The default value is `64` and is suitable for + most deployments. While a smaller value may use less disk space because you + can reclaim space by deleting old segments sooner, the smaller segment that results + may affect performance because safely rotating to a new file more + frequently can impact tail latencies. Larger values are unlikely + to improve performance significantly. We recommend using this + configuration for performance testing purposes. - `raft_protocol` ((#raft_protocol)) Equivalent to the [`-raft-protocol` command-line flag](/consul/docs/agent/config/cli-flags#_raft_protocol). diff --git a/website/content/docs/agent/config/index.mdx b/website/content/docs/agent/config/index.mdx index b2e3ac42c83..0ea4a030fb7 100644 --- a/website/content/docs/agent/config/index.mdx +++ b/website/content/docs/agent/config/index.mdx @@ -72,7 +72,7 @@ The following agent configuration options are reloadable at runtime: - These can be important in certain outage situations so being able to control them without a restart provides a recovery path that doesn't involve downtime. They generally shouldn't be changed otherwise. -- [RPC rate limiting](/consul/docs/agent/config/config-files#limits) +- [RPC rate limits](/consul/docs/agent/config/config-files#limits) - [HTTP Maximum Connections per Client](/consul/docs/agent/config/config-files#http_max_conns_per_client) - Services - TLS Configuration diff --git a/website/content/docs/agent/index.mdx b/website/content/docs/agent/index.mdx index bab9138e50d..51c5b713ef5 100644 --- a/website/content/docs/agent/index.mdx +++ b/website/content/docs/agent/index.mdx @@ -33,7 +33,7 @@ The following process describes the agent lifecycle within the context of an exi As a result, all nodes will eventually become aware of each other. 1. **Existing servers will begin replicating to the new node** if the agent is a server. -### Failures and Crashes +### Failures and crashes In the event of a network failure, some nodes may be unable to reach other nodes. Unreachable nodes will be marked as _failed_. @@ -48,7 +48,7 @@ catalog. Once the network recovers or a crashed agent restarts, the cluster will repair itself and unmark a node as failed. The health check in the catalog will also be updated to reflect the current state. -### Exiting Nodes +### Exiting nodes When a node leaves a cluster, it communicates its intent and the cluster marks the node as having _left_. In contrast to changes related to failures, all of the services provided by a node are immediately deregistered. @@ -61,6 +61,10 @@ interval of 72 hours (changing the reap interval is _not_ recommended due to its consequences during outage situations). Reaping is similar to leaving, causing all associated services to be deregistered. +## Limit traffic rates + +You can define a set of rate limiting configurations that help operators protect Consul servers from excessive or peak usage. The configurations enable you to gracefully degrade Consul servers to avoid a global interruption of service. Consul supports global server rate limiting, which lets configure Consul servers to deny requests that exceed the read or write limits. Refer to [Traffic Rate Limits Overview](/consul/docs/agent/limits). + ## Requirements You should run one Consul agent per server or host. @@ -73,7 +77,7 @@ Refer to the following sections for information about host, port, memory, and ot The [Datacenter Deploy tutorial](/consul/tutorials/production-deploy/reference-architecture#deployment-system-requirements) contains additional information, including licensing configuration, environment variables, and other details. -### Maximum Latency Network requirements +### Maximum latency network requirements Consul uses the gossip protocol to share information across agents. To function properly, you cannot exceed the protocol's maximum latency threshold. The latency threshold is calculated according to the total round trip time (RTT) for communication between all agents. Other network usages outside of Gossip are not bound by these latency requirements (i.e. client to server RPCs, HTTP API requests, xDS proxy configuration, DNS). @@ -82,7 +86,7 @@ For data sent between all Consul agents the following latency requirements must - Average RTT for all traffic cannot exceed 50ms. - RTT for 99 percent of traffic cannot exceed 100ms. -## Starting the Consul Agent +## Starting the Consul agent Start a Consul agent with the `consul` command and `agent` subcommand using the following syntax: @@ -111,7 +115,7 @@ $ consul agent -data-dir=tmp/consul -dev Agents are highly configurable, which enables you to deploy Consul to any infrastructure. Many of the default options for the `agent` command are suitable for becoming familiar with a local instance of Consul. In practice, however, several additional configuration options must be specified for Consul to function as expected. Refer to [Agent Configuration](/consul/docs/agent/config) topic for a complete list of configuration options. -### Understanding the Agent Startup Output +### Understanding the agent startup output Consul prints several important messages on startup. The following example shows output from the [`consul agent`](/consul/commands/agent) command: @@ -162,7 +166,7 @@ When running under `systemd` on Linux, Consul notifies systemd by sending this either the `join` or `retry_join` option has to be set and the service definition file has to have `Type=notify` set. -## Configuring Consul Agents +## Configuring Consul agents You can specify many options to configure how Consul operates when issuing the `consul agent` command. You can also create one or more configuration files and provide them to Consul at startup using either the `-config-file` or `-config-dir` option. @@ -180,7 +184,7 @@ $ consul agent -config-file=server.json The configuration options necessary to successfully use Consul depend on several factors, including the type of agent you are configuring (client or server), the type of environment you are deploying to (e.g., on-premise, multi-cloud, etc.), and the security options you want to implement (ACLs, gRPC encryption). The following examples are intended to help you understand some of the combinations you can implement to configure Consul. -### Common Configuration Settings +### Common configuration settings The following settings are commonly used in the configuration file (also called a service definition file when registering services with Consul) to configure Consul agents: @@ -195,7 +199,7 @@ The following settings are commonly used in the configuration file (also called | `addresses` | Block of nested objects that define addresses bound to the agent for internal cluster communication. | `"http": "0.0.0.0"` See the Agent Configuration page for [default address values](/consul/docs/agent/config/config-files#addresses) | | `ports` | Block of nested objects that define ports bound to agent addresses.
    See (link to addresses option) for details. | See the Agent Configuration page for [default port values](/consul/docs/agent/config/config-files#ports) | -### Server Node in a Service Mesh +### Server node in a service mesh The following example configuration is for a server agent named "`consul-server`". The server is [bootstrapped](/consul/docs/agent/config/cli-flags#_bootstrap) and the Consul GUI is enabled. The reason this server agent is configured for a service mesh is that the `connect` configuration is enabled. Connect is Consul's service mesh component that provides service-to-service connection authorization and encryption using mutual Transport Layer Security (TLS). Applications can use sidecar proxies in a service mesh configuration to establish TLS connections for inbound and outbound connections without being aware of Connect at all. See [Connect](/consul/docs/connect) for details. @@ -243,7 +247,7 @@ connect { -### Server Node with Encryption Enabled +### Server node with encryption enabled The following example shows a server node configured with encryption enabled. Refer to the [Security](/consul/docs/security) chapter for additional information about how to configure security options for Consul. @@ -313,10 +317,10 @@ tls { -### Client Node Registering a Service +### Client node registering a service Using Consul as a central service registry is a common use case. -The following example configuration includes common settings to register a service with a Consul agent and enable health checks (see [Checks](/consul/docs/discovery/checks) to learn more about health checks): +The following example configuration includes common settings to register a service with a Consul agent and enable health checks. Refer to [Define Health Checks](/consul/docs/services/usage/checks) to learn more about health checks. @@ -371,7 +375,7 @@ service { -## Client Node with Multiple Interfaces or IP addresses +## Client node with multiple interfaces or IP addresses The following example shows how to configure Consul to listen on multiple interfaces or IP addresses using a [go-sockaddr template]. @@ -422,7 +426,7 @@ advertise_addr = "{{ GetInterfaceIP \"en0\" }}" -## Stopping an Agent +## Stopping an agent An agent can be stopped in two ways: gracefully or forcefully. Servers and Clients both behave differently depending on the leave that is performed. There diff --git a/website/content/docs/agent/limits/index.mdx b/website/content/docs/agent/limits/index.mdx new file mode 100644 index 00000000000..75fb4f1ac0e --- /dev/null +++ b/website/content/docs/agent/limits/index.mdx @@ -0,0 +1,31 @@ +--- +layout: docs +page_title: Limit Traffic Rates Overview +description: Rate limiting is a set of Consul server agent configurations that you can use to mitigate the risks to Consul servers when clients send excessive requests to Consul resources. + +--- + +# Limit traffic rates overview + +This topic provides overview information about the traffic rates limits you can configure for Consul servers. + +## Introduction +You can configure global RPC rate limits to mitigate the risks to Consul servers when clients send excessive read or write requests to Consul resources. A read request is defined as any request that does not modify Consul internal state. A write request is defined as any request that modifies Consul internal state. Read and write requests are limited separately. + +## Rate limit modes +You can set one of the following modes, which determine how Consul servers react when the request limits are exceeded. + +- **Enforcing mode**: In this mode, the rate limiter denies requests to a server beyond a configurable rate. Consul generates metrics and logs to help operators understand their Consul load and configure limits accordingly. +- **Permissive mode**: The rate limiter allows requests if the limits are reached and produces metrics and logs to help operators understand their Consul load and configure limits accordingly. This mode is intended to help you configure limits and debug specific issues. +- **Disabled mode**: Disables the rate limiter. All requests are allowed and no logs or metrics are produced. This is the default mode. + +Refer to [`rate_limits`](/consul/docs/agent/config/config-files#request_limits) for additional configuration information. + +## Request denials +When an HTTP request is denied for rate limiting reason, Consul returns one of the following errors: + +- **429 Resource Exhausted**: Indicates that a server is not able to perform the request but that another server could potentially fulfill it. This error is most common on stale reads because any server may fulfill state read requests. To resolve this type of error, we recommend immediately retrying the request to another server. If the request came from a Consul client agent, the agent automatically retries the request up to the limit set in the [`rpc_hold_timeout`](/consul/docs/agent/config/config-files#rpc_hold_timeout) configuration . + +- **503 Service Unavailable**: Indicates that server is unable to perform the request and that no other server can fulfill the request, either. This usually occurs on consistent reads or for writes. In this case we recommend retrying according to an exponential backoff schedule. If the request came from a Consul client agent, the agent automatically retries the request according to the [`rpc_hold_timeout`](/consul/docs/agent/config/config-files#rpc_hold_timeout) configuration. + +Refer to [Rate limit reached on the server](/consul/docs/troubleshoot/common-errors#rate-limit-reached-on-the-server) for additional information. \ No newline at end of file diff --git a/website/content/docs/agent/limits/init-rate-limits.mdx b/website/content/docs/agent/limits/init-rate-limits.mdx new file mode 100644 index 00000000000..32057184530 --- /dev/null +++ b/website/content/docs/agent/limits/init-rate-limits.mdx @@ -0,0 +1,32 @@ +--- +layout: docs +page_title: Initialize Rate Limit Settings +description: Learn how to determins regular and peak loads in your network so that you can set the initial global rate limit configurations. +--- + +# Initialize rate limit settings + +In order to set limits for traffic, you must first understand regular and peak loads in your network. We recommend completing the following steps to benchmark request rates in your environment so that you can implement limits appropriate for your applications. + +1. Specify a global rate limit with arbitrary values in the agent configuration file based on the following conditions: + + - Environment where Consul servers are running + - Number of servers and the projected load + - Existing metrics expressing requests per second + +1. Set the `mode` to `permissive`. In the following example, Consul agents are allowed up to 1000 reads and 500 writes per second: + + ```hcl + request_limits { + mode = "permissive" + read_rate = 1000.0 + write_rate =500.0 + } + ``` + +1. Observe the logs and metrics for your application's typical cycle, such as a 24 hour period. Refer to [`log_file`](/consul/docs/agent/config/config-files#log_file) for information about where to retrieve logs. Call the [`/agent/metrics`](/consul/api-docs/agent#view-metrics) HTTP API endpoint and check the data for the following metrics: + + - `rpc.rate_limit.exceeded` with value `global/read` for label `limit_type` + - `rpc.rate_limit.exceeded` with value `global/write` for label `limit_type` + +1. If the limits are not reached, set the `mode` configuration to `enforcing`. Otherwise adjust and iterate limits. \ No newline at end of file diff --git a/website/content/docs/agent/limits/set-global-traffic-rate-limits.mdx b/website/content/docs/agent/limits/set-global-traffic-rate-limits.mdx new file mode 100644 index 00000000000..369d1b7b583 --- /dev/null +++ b/website/content/docs/agent/limits/set-global-traffic-rate-limits.mdx @@ -0,0 +1,108 @@ +--- +layout: docs +page_title: Set a Global Limit on Traffic Rates +description: Use global rate limits to prevent excessive rates of requests to Consul servers. +--- + +# Set a global limit on traffic rates + +This topic describes how to configure rate limits for RPC and gRPC traffic to the Consul server. + +## Introduction +Rate limits apply to each Consul server separately and are intended to limit the number of read requests or write requests to the server on the RPC and internal gRPC endpoints. + +Because all requests coming to a Consul server eventually perform an RPC or an internal gRPC request, these limits also apply to other user interfaces, such as the HTTP API interface, the CLI, and the external gRPC endpoint for services in the Consul service mesh. + +Refer to [Initialize Rate Limit Settings](/consul/docs/agent/limits/init-rate-limits) for additional information about right-sizing your gRPC request configurations. + +## Set a global rate limit for a Consul server +Configure the following settings in your Consul server configuration to limit the RPC and gRPC traffic rates. + +- Set the rate limiter [`mode`](/consul/docs/agent/config/config-files#mode-1) +- Set the [`read_rate`](/consul/docs/agent/config/config-files#read_rate) +- Set the [`write_rate`](/consul/docs/agent/config/config-files#write_rate) + +In the following example, the Consul server is configured to prevent more than `500` read and `200` write RPC calls: + + + +```hcl +limits = { + rate_limit = { + mode = "enforcing" + read_rate = 500 + write_rate = 200 + } +} +``` + +```json +{ + "limits" : { + "rate_limit" : { + "mode" : "enforcing", + "read_rate" : 500, + "write_rate" : 200 + } + } +} + +``` + + + +## Access rate limit logs +Consul prints a log line for each rate limit request. The log provides the information necessary for identifying the source of the request and the configured limit. Consul prints the log `DEBUG` log level and can drop the log to avoid affecting the server health. Dropping a log line increments the `rpc.rate_limit.log_dropped` metric. + +The following example log shows that RPC request from `127.0.0.1:53562` to `KVS.Apply` exceeded the limit: + + + +```shell-session +2023-02-17T10:01:15.565-0500 [DEBUG] agent.server.rpc-rate-limit: RPC +exceeded allowed rate limit: rpc=KVS.Apply source_addr=127.0.0.1:53562 +limit_type=global/write limit_enforced=false +``` + + +## Review rate limit metrics +Consul captures the following metrics associated with rate limits: + +- Type of limit +- Operation +- Rate limit mode + +Call the `agent/metrics` API endpoint to view the metrics associated with rate limits. Refer to [View Metrics](/consul/api-docs/agent#view-metrics) for API usage information. In the following example, Consul dropped a call to the `consul` service because it exceeded the limit by one call: + +```shell-session +$ curl http://127.0.0.1:8500/v1/agent/metrics +{ + . . . + "Counters": [ + { + "Name": "consul.rpc.rate_limit.exceeded", + "Count": 1, + "Sum": 1, + "Min": 1, + "Max": 1, + "Mean": 1, + "Stddev": 0, + "Labels": { + "service": "consul" + } + }, + { + "Name": "consul.rpc.rate_limit.log_dropped", + "Count": 1, + "Sum": 1, + "Min": 1, + "Max": 1, + "Mean": 1, + "Stddev": 0, + "Labels": {} + } + ], + . . . +``` + +Refer to [Telemetry]() for additional information. diff --git a/website/content/docs/agent/telemetry.mdx b/website/content/docs/agent/telemetry.mdx index 53d7ed91766..df8fdef15dc 100644 --- a/website/content/docs/agent/telemetry.mdx +++ b/website/content/docs/agent/telemetry.mdx @@ -294,7 +294,7 @@ This metric should be monitored to ensure that the license doesn't expire to pre | Metric Name | Description | Unit | Type | | :-------------------------------- | :--------------------------------------------------------------- | :---- | :---- | -| `consul.raft.boltdb.freelistBytes` | Represents the number of bytes necessary to encode the freelist metadata. When [`raft_boltdb.NoFreelistSync`](/consul/docs/agent/config/config-files#NoFreelistSync) is set to `false` these metadata bytes must also be written to disk for each committed log. | bytes | gauge | +| `consul.raft.boltdb.freelistBytes` | Represents the number of bytes necessary to encode the freelist metadata. When [`raft_logstore.boltdb.no_freelist_sync`](/consul/docs/agent/config/config-files#raft_logstore_boltdb_no_freelist_sync) is set to `false` these metadata bytes must also be written to disk for each committed log. | bytes | gauge | | `consul.raft.boltdb.logsPerBatch` | Measures the number of logs being written per batch to the db. | logs | sample | | `consul.raft.boltdb.storeLogs` | Measures the amount of time spent writing logs to the db. | ms | timer | | `consul.raft.boltdb.writeCapacity` | Theoretical write capacity in terms of the number of logs that can be written per second. Each sample outputs what the capacity would be if future batched log write operations were similar to this one. This similarity encompasses 4 things: batch size, byte size, disk performance and boltdb performance. While none of these will be static and its highly likely individual samples of this metric will vary, aggregating this metric over a larger time window should provide a decent picture into how this BoltDB store can perform | logs/second | sample | @@ -337,11 +337,12 @@ indicator of an actual issue, this metric can be used to diagnose why the `consu is high. If Bolt DB log storage performance becomes an issue and is caused by free list management then setting -[`raft_boltdb.NoFreelistSync`](/consul/docs/agent/config/config-files#NoFreelistSync) to `true` in the server's configuration +[`raft_logstore.boltdb.no_freelist_sync`](/consul/docs/agent/config/config-files#raft_logstore_boltdb_no_freelist_sync) to `true` in the server's configuration may help to reduce disk IO and log storage operation times. Disabling free list syncing will however increase the startup time for a server as it must scan the raft.db file for free space instead of loading the already populated free list structure. +Consul includes an experiment backend configuration that you can use instead of BoldDB. Refer to [Experimental WAL LogStore backend](/consul/docs/agent/wal-logstore) for more information. ## Metrics Reference @@ -418,7 +419,7 @@ These metrics are used to monitor the health of the Consul servers. | `consul.raft.applied_index` | Represents the raft applied index. | index | gauge | | `consul.raft.apply` | Counts the number of Raft transactions occurring over the interval, which is a general indicator of the write load on the Consul servers. | raft transactions / interval | counter | | `consul.raft.barrier` | Counts the number of times the agent has started the barrier i.e the number of times it has issued a blocking call, to ensure that the agent has all the pending operations that were queued, to be applied to the agent's FSM. | blocks / interval | counter | -| `consul.raft.boltdb.freelistBytes` | Represents the number of bytes necessary to encode the freelist metadata. When [`raft_boltdb.NoFreelistSync`](/consul/docs/agent/config/config-files#NoFreelistSync) is set to `false` these metadata bytes must also be written to disk for each committed log. | bytes | gauge | +| `consul.raft.boltdb.freelistBytes` | Represents the number of bytes necessary to encode the freelist metadata. When [`raft_logstore.boltdb.no_freelist_sync`](/consul/docs/agent/config/config-files#raft_logstore_boltdb_no_freelist_sync) is set to `false` these metadata bytes must also be written to disk for each committed log. | bytes | gauge | | `consul.raft.boltdb.freePageBytes` | Represents the number of bytes of free space within the raft.db file. | bytes | gauge | | `consul.raft.boltdb.getLog` | Measures the amount of time spent reading logs from the db. | ms | timer | | `consul.raft.boltdb.logBatchSize` | Measures the total size in bytes of logs being written to the db in a single batch. | bytes | sample | @@ -452,6 +453,13 @@ These metrics are used to monitor the health of the Consul servers. | `consul.raft.last_index` | Represents the raft applied index. | index | gauge | | `consul.raft.leader.dispatchLog` | Measures the time it takes for the leader to write log entries to disk. | ms | timer | | `consul.raft.leader.dispatchNumLogs` | Measures the number of logs committed to disk in a batch. | logs | gauge | +| `consul.raft.logstore.verifier.checkpoints_written` | Counts the number of checkpoint entries written to the LogStore. | checkpoints | counter | +| `consul.raft.logstore.verifier.dropped_reports` | Counts how many times the verifier routine was still busy when the next checksum came in and so verification for a range was skipped. If you see this happen, consider increasing the interval between checkpoints with [`raft_logstore.verification.interval`](/consul/docs/agent/config/config-files#raft_logstore_verification) | reports dropped | counter | +| `consul.raft.logstore.verifier.ranges_verified` | Counts the number of log ranges for which a verification report has been completed. Refer to [Monitor Raft metrics and logs for WAL +](/consul/docs/agent/wal-logstore/monitoring) for more information. | log ranges verifications | counter | +| `consul.raft.logstore.verifier.read_checksum_failures` | Counts the number of times a range of logs between two check points contained at least one disk corruption. Refer to [Monitor Raft metrics and logs for WAL +](/consul/docs/agent/wal-logstore/monitoring) for more information. | disk corruptions | counter | +| `consul.raft.logstore.verifier.write_checksum_failures` | Counts the number of times a follower has a different checksum to the leader at the point where it writes to the log. This could be caused by either a disk-corruption on the leader (unlikely) or some other corruption of the log entries in-flight. | in-flight corruptions | counter | | `consul.raft.leader.lastContact` | Measures the time since the leader was last able to contact the follower nodes when checking its leader lease. It can be used as a measure for how stable the Raft timing is and how close the leader is to timing out its lease.The lease timeout is 500 ms times the [`raft_multiplier` configuration](/consul/docs/agent/config/config-files#raft_multiplier), so this telemetry value should not be getting close to that configured value, otherwise the Raft timing is marginal and might need to be tuned, or more powerful servers might be needed. See the [Server Performance](/consul/docs/install/performance) guide for more details. | ms | timer | | `consul.raft.leader.oldestLogAge` | The number of milliseconds since the _oldest_ log in the leader's log store was written. This can be important for replication health where write rate is high and the snapshot is large as followers may be unable to recover from a restart if restoring takes longer than the minimum value for the current leader. Compare this with `consul.raft.fsm.lastRestoreDuration` and `consul.raft.rpc.installSnapshot` to monitor. In normal usage this gauge value will grow linearly over time until a snapshot completes on the leader and the log is truncated. Note: this metric won't be emitted until the leader writes a snapshot. After an upgrade to Consul 1.10.0 it won't be emitted until the oldest log was written after the upgrade. | ms | gauge | | `consul.raft.replication.heartbeat` | Measures the time taken to invoke appendEntries on a peer, so that it doesn't timeout on a periodic basis. | ms | timer | @@ -476,7 +484,18 @@ These metrics are used to monitor the health of the Consul servers. | `consul.raft.state.follower` | Counts the number of times an agent has entered the follower mode. This happens when a new agent joins the cluster or after the end of a leader election. | follower state entered / interval | counter | | `consul.raft.transition.heartbeat_timeout` | The number of times an agent has transitioned to the Candidate state, after receive no heartbeat messages from the last known leader. | timeouts / interval | counter | | `consul.raft.verify_leader` | This metric doesn't have a direct correlation to the leader change. It just counts the number of times an agent checks if it is still the leader or not. For example, during every consistent read, the check is done. Depending on the load in the system, this metric count can be high as it is incremented each time a consistent read is completed. | checks / interval | Counter | -| `consul.rpc.accept_conn` | Increments when a server accepts an RPC connection. | connections | counter | +| `consul.raft.wal.head_truncations` | Counts how many log entries have been truncated from the head - i.e. the oldest entries. by graphing the rate of change over time you can see individual truncate calls as spikes. | logs entries truncated | counter | +| `consul.raft.wal.last_segment_age_seconds` | A gauge that is set each time we rotate a segment and describes the number of seconds between when that segment file was first created and when it was sealed. this gives a rough estimate how quickly writes are filling the disk. | seconds | gauge | +| `consul.raft.wal.log_appends` | Counts the number of calls to StoreLog(s) i.e. number of batches of entries appended. | calls | counter | +| `consul.raft.wal.log_entries_read` | Counts the number of log entries read. | log entries read | counter | +| `consul.raft.wal.log_entries_written` | Counts the number of log entries written. | log entries written | counter | +| `consul.raft.wal.log_entry_bytes_read` | Counts the bytes of log entry read from segments before decoding. actual bytes read from disk might be higher as it includes headers and index entries and possible secondary reads for large entries that don't fit in buffers. | bytes | counter | +| `consul.raft.wal.log_entry_bytes_written` | Counts the bytes of log entry after encoding with Codec. Actual bytes written to disk might be slightly higher as it includes headers and index entries. | bytes | counter | +| `consul.raft.wal.segment_rotations` | Counts how many times we move to a new segment file. | rotations | counter | +| `consul.raft.wal.stable_gets` | Counts how many calls to StableStore.Get or GetUint64. | calls | counter | +| `consul.raft.wal.stable_sets` | Counts how many calls to StableStore.Set or SetUint64. | calls | counter | +| `consul.raft.wal.tail_truncations` | Counts how many log entries have been truncated from the head - i.e. the newest entries. by graphing the rate of change over time you can see individual truncate calls as spikes. | logs entries truncated | counter | +| `consul.rpc.accept_conn` | Increments when a server accepts an RPC connection. | connections | counter | | `consul.rpc.rate_limit.exceeded` | Increments whenever an RPC is over a configured rate limit. In permissive mode, the RPC is still allowed to proceed. | RPCs | counter | | `consul.rpc.rate_limit.log_dropped` | Increments whenever a log that is emitted because an RPC exceeded a rate limit gets dropped because the output buffer is full. | log messages dropped | counter | | `consul.catalog.register` | Measures the time it takes to complete a catalog register operation. | ms | timer | diff --git a/website/content/docs/agent/wal-logstore/enable.mdx b/website/content/docs/agent/wal-logstore/enable.mdx new file mode 100644 index 00000000000..2a339fcf89e --- /dev/null +++ b/website/content/docs/agent/wal-logstore/enable.mdx @@ -0,0 +1,152 @@ +--- +layout: docs +page_title: Enable the experimental WAL LogStore backend +description: >- + Learn how to safely configure and test the experimental WAL backend in your Consul deployment. +--- + +# Enable the experimental WAL LogStore backend + +This topic describes how to safely configure and test the WAL backend in your Consul deployment. + +The overall process for enabling the WAL LogStore backend for one server consists of the following steps. In production environments, we recommend starting by enabling the backend on a single server . If you eventually choose to expand the test to further servers, you must repeat these steps for each one. + +1. Enable log verification. +1. Select target server to enable WAL. +1. Stop target server gracefully. +1. Remove data directory from target server. +1. Update target server's configuration. +1. Start the target server. +1. Monitor target server raft metrics and logs. + +!> **Experimental feature:** The WAL LogStore backend is experimental and may contain bugs that could cause data loss. Follow this guide to manage risk during testing. + +## Requirements + +- Consul v1.15 or later is required for all servers in the datacenter. Refer to the [standard upgrade procedure](/consul/docs/upgrading/instructions/general-process) and the [1.15 upgrade notes](/consul/docs/upgrading/upgrade-specific#consul-1-15-x) for additional information. +- A Consul cluster with at least three nodes are required to safely test the WAL backend without downtime. + +We recommend taking the following additional measures: + +- Take a snapshot prior to testing. +- Monitor Consul server metrics and logs, and set an alert on specific log events that occur when WAL is enabled. Refer to [Monitor Raft metrics and logs for WAL](/consul/docs/agent/wal-logstore/monitoring) for more information. +- Enable WAL in a pre-production environment and run it for a several days before enabling it in production. + +## Known issues + +The following issues were discovered after release of Consul 1.15.1 and will be +fixed in a future patch release. + + * A follower that is disconnected may be unable to catch up if it is using the WAL backend. + * Restoring user snapshots can break replication to WAL-enabled followers. + * Restoring user snapshots can cause a WAL-enabled leader to panic. + +## Risks + +While their likelihood remains low to very low, be aware of the following risks before implementing the WAL backend: + + - If WAL corrupts data on a Consul server agent, the server data cannot be recovered. Restart the server with an empty data directory and reload its state from the leader to resolve the issue. + - WAL may corrupt data or contain a defect that causes the server to panic and crash. WAL may not restart if the defect recurs when WAL reads from the logs on startup. Restart the server with an empty data directory and reload its state from the leader to resolve the issue. + - If WAL corrupts data, clients may read corrupted data from the Consul server, such as invalid IP addresses or unmatched tokens. This outcome is unlikely even if a recurring defect causes WAL to corrupt data because replication uses objects cached in memory instead of reads from disk. Restart the server with an empty data directory and reload its state from the leader to resolve the issue. + - If you enable a Consul OSS server to use WAL or enable WAL on a voting server with Consul Enterprise, WAL may corrupt the server's state, become the leader, and replicate the corrupted state to all other servers. In this case, restoring from backup is required to recover a completely uncorrupted state. Test WAL on a non-voting server in Enterprise to prevent this outcome. You can add a new non-voting server to the cluster to test with if there are no existing ones. + +## Enable log verification + +You must enable log verification on all voting servers in Enterprise and all servers in OSS because the leader writes verification checkpoints. + +1. On each voting server, add the following to the server's configuration file: + + ```hcl + raft_logstore { + verification { + enabled = true + interval = "60s" + } + } + ``` + +1. Restart the server to apply the changes. The `consul reload` command is not sufficient to apply `raft_logstore` configuration changes. +1. Run the `consul operator raft list-peers` command to wait for each server to become a healthy voter before moving on to the next. This may take a few minutes for large snapshots. + +When complete, the server's logs should contain verifier reports that appear like the following example: + +```log hideClipboard +2023-01-31T14:44:31.174Z [INFO] agent.server.raft.logstore.verifier: verification checksum OK: elapsed=488.463268ms leaderChecksum=f15db83976f2328c rangeEnd=357802 rangeStart=298132 readChecksum=f15db83976f2328c +``` + +## Select target server to enable WAL + +If you are using Consul OSS or Consul Enterprise without non-voting servers, select a follower server to enable WAL. As noted in [Risks](#risks), Consul Enterprise users with non-voting servers should first select a non-voting server, or consider adding another server as a non-voter to test on. + +Retrieve the current state of the servers by running the following command: + +```shell-session +$ consul operator raft list-peers +``` + +## Stop target server + +Stop the target server gracefully. For example, if you are using `systemd`, +run the following command: + +```shell-session +$ systemctl stop consul +``` + +If your environment uses configuration management automation that might interfere with this process, such as Chef or Puppet, you must disable them until you have completely enabled WAL as a storage backend. + +## Remove data directory from target server + +Temporarily moving the data directory to a different location is less destructive than deleting it. We recommend moving it in cases where you unsuccessfully enable WAL. Do not use the old data directory (`/data-dir/raft.bak`) for recovery after restarting the server. We recommend eventually deleting the old directory. + +The following example assumes the `data_dir` in the server's configuration is `/data-dir` and renames it to `/data-dir.bak`. + +```shell-session +$ mv /data-dir/raft /data-dir/raft.bak +``` + +When switching backends, you must always remove _the entire raft directory_, not just the `raft.db` file or `wal` directory. The log must always be consistent with the snapshots to avoid undefined behavior or data loss. + +## Update target server configuration + +Add the following to the target server's configuration file: + +```hcl +raft_logstore { + backend = "wal" + verification { + enabled = true + interval = "60s" + } +} +``` + +## Start target server + +Start the target server. For example, if you are using `systemd`, run the following command: + +```shell-session +$ systemctl start consul +``` + +Watch for the server to become a healthy voter again. + +```shell-session +$ consul operator raft list-peers +``` + +## Monitor target server Raft metrics and logs + +Refer to [Monitor Raft metrics and logs for WAL](/consul/docs/agent/wal-logstore/monitoring) for details. + +We recommend leaving the cluster in the test configuration for several days or weeks, as long as you observe no errors. An extended test provides more confidence that WAL operates correctly under varied workloads and during routine server restarts. If you observe any errors, end the test immediately and report them. + +If you disabled configuration management automation, consider reenabling it during the testing phase to pick up other updates for the host. You must ensure that it does not revert the Consul configuration file and remove the altered backend configuration. One way to do this is add the `raft_logstore` block to a separate file that is not managed by your automation. This file can either be added to the directory if [`-config-dir`](/consul/docs/agent/config/cli-flags#_config_dir) is used or as an additional [`-config-file`](/consul/docs/agent/config/cli-flags#_config_file) argument. + +## Next steps + +- If you observe any verification errors, performance anomalies, or other suspicious behavior from the target server during the test, you should immediately follow [the procedure to revert back to BoltDB](/consul/docs/agent/wal-logstore/revert-to-boltdb). Report failures through GitHub. + +- If you do not see errors and would like to expand the test further, you can repeat the above procedure on another target server. We suggest waiting after each test expansion and slowly rolling WAL out to other parts of your environment. Once the majority of your servers use WAL, any bugs not yet discovered may result in cluster unavailability. + +- If you wish to permanently enable WAL on all servers, repeat the steps described in this topic for each server. Even if `backend = "wal"` is set in logs, servers continue to use BoltDB if they find an existing raft.db file in the data directory. \ No newline at end of file diff --git a/website/content/docs/agent/wal-logstore/index.mdx b/website/content/docs/agent/wal-logstore/index.mdx new file mode 100644 index 00000000000..491255e8ed5 --- /dev/null +++ b/website/content/docs/agent/wal-logstore/index.mdx @@ -0,0 +1,53 @@ +--- +layout: docs +page_title: WAL LogStore Backend Overview +description: >- + The experimental WAL (write-ahead log) LogStore backend shipped in Consul 1.15 is intended to replace the BoltDB backend, improving performance and log storage issues. +--- + +# Experimental WAL LogStore backend overview + +This topic provides an overview of the WAL (write-ahead log) LogStore backend. +The WAL backend is an experimental feature. Refer to +[Requirements](/consul/docs/agent/wal-logstore/enable#requirements) for +supported environments and known issues. + +We do not recommend enabling the WAL backend in production without following +[our guide for safe +testing](/consul/docs/agent/wal-logstore/enable). + +## WAL versus BoltDB + +WAL implements a traditional log with rotating, append-only log files. WAL resolves many issues with the existing `LogStore` provided by the BoltDB backend. The BoltDB `LogStore` is a copy-on-write BTree, which is not optimized for append-only, write-heavy workloads. + +### BoltDB storage scalability issues + +The existing BoltDB log store inefficiently stores append-only logs to disk because it was designed as a full key-value database. It is a single file that only ever grows. Deleting the oldest logs, which Consul does regularly when it makes new snapshots of the state, leaves free space in the file. The free space must be tracked in a `freelist` so that BoltDB can reuse it on future writes. By contrast, a simple segmented log can delete the oldest log files from disk. + +A burst of writes at double or triple the normal volume can suddenly cause the log file to grow to several times its steady-state size. After Consul takes the next snapshot and truncates the oldest logs, the resulting file is mostly empty space. + +To track the free space, Consul must write extra metadata to disk with every write. The metadata is proportional to the amount of free pages, so after a large burst write latencies tend to increase. In some cases, the latencies cause serious performance degradation to the cluster. + +To mitigate risks associated with sudden bursts of log data, Consul tries to limit lots of logs from accumulating in the LogStore. Significantly larger BoltDB files are slower to append to because the tree is deeper and freelist larger. For this reason, Consul's default options associated with snapshots, truncating logs, and keeping the log history have been aggressively set toward keeping BoltDB small rather than using disk IO optimally. + +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 asssociated with snapshots, truncating logs, and keeping the log history aggressively keep BoltDT small rather than uisng 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. + +### WAL approaches storage issues differently + +When directly measured, WAL is more performant than BoltDB because it solves a simpler storage problem. Despite this, some users may not notice a significant performance improvement from the upgrade with the same configuration and workload. In this case, the benefit of WAL is that retaining more logs does not affect write performance. As a result, strategies for reducing disk IO with slower snapshots or for keeping logs to permit slower followers to catch up with cluster state are all possible, increasing the reliability of the deployment. + +## WAL quality assurance + +The WAL backend has been tested thoroughly during development: + +- Every component in the WAL, such as [metadata management](https://github.com/hashicorp/raft-wal/blob/main/types/meta.go), [log file encoding](https://github.com/hashicorp/raft-wal/blob/main/types/segment.go) to actual [file-system interaction](https://github.com/hashicorp/raft-wal/blob/main/types/vfs.go) are abstracted so unit tests can simulate difficult-to-reproduce disk failures. + +- We used the [application-level intelligent crash explorer (ALICE)](https://github.com/hashicorp/raft-wal/blob/main/alice/README.md) to exhaustively simulate thousands of possible crash failure scenarios. WAL correctly recovered from all scenarios. + +- We ran hundreds of tests in a performance testing cluster with checksum verification enabled and did not detect data loss or corruption. We will continue testing before making WAL the default backend. + +We are aware of how complex and critical disk-persistence is for your data. + +We hope that many users at different scales will try WAL in their environments after upgrading to 1.15 or later and report success or failure so that we can confidently replace BoltDB as the default for new clusters in a future release. \ No newline at end of file diff --git a/website/content/docs/agent/wal-logstore/monitoring.mdx b/website/content/docs/agent/wal-logstore/monitoring.mdx new file mode 100644 index 00000000000..f4f81a986d2 --- /dev/null +++ b/website/content/docs/agent/wal-logstore/monitoring.mdx @@ -0,0 +1,85 @@ +--- +layout: docs +page_title: Monitor Raft metrics and logs for WAL +description: >- + Learn how to monitor Raft metrics emitted the experimental WAL (write-ahead log) LogStore backend shipped in Consul 1.15. +--- + +# Monitor Raft metrics and logs for WAL + +This topic describes how to monitor Raft metrics and logs if you are testing the WAL backend. We strongly recommend monitoring the Consul cluster, especially the target server, for evidence that the WAL backend is not functioning correctly. Refer to [Enable the experimental WAL LogStore backend](/consul/docs/agent/wal-logstore/enable) for additional information about the WAL backend. + +!> **Upgrade warning:** The WAL LogStore backend is experimental. + +## Monitor for checksum failures + +Log store verification failures on any server, regardless of whether you are running the BoltDB or WAL backed, are unrecoverable errors. Consul may report the following errors in logs. + +### Read failures: Disk Corruption + +```log hideClipboard +2022-11-15T22:41:23.546Z [ERROR] agent.raft.logstore: verification checksum FAILED: storage corruption rangeStart=1234 rangeEnd=3456 leaderChecksum=0xc1... readChecksum=0x45... +``` + +This indicates that the server read back data that is different from what it wrote to disk. This indicates corruption in the storage backend or filesystem. + +For convenience, Consul also increments a metric `consul.raft.logstore.verifier.read_checksum_failures` when this occurs. + +### Write failures: In-flight Corruption + +The following error indicates that the checksum on the follower did not match the leader when the follower received the logs. The error implies that the corruption happened in the network or software and not the log store: + +```log hideClipboard +2022-11-15T22:41:23.546Z [ERROR] agent.raft.logstore: verification checksum FAILED: in-flight corruption rangeStart=1234 rangeEnd=3456 leaderChecksum=0xc1... followerWriteChecksum=0x45... +``` + +It is unlikely that this error indicates an issue with the storage backend, but you should take the same steps to resolve and report it. + +The `consul.raft.logstore.verifier.write_checksum_failures` metric increments when this error occurs. + +## Resolve checksum failures + +If either type of corruption is detected, complete the instructions for [reverting to BoltDB](/consul/docs/agent/wal-logstore/revert-to-boltdb). If the server already uses BoltDB, the errors likely indicate a latent bug in BoltDB or a bug in the verification code. In both cases, you should follow the revert instructions. + +Report all verification failures as a [GitHub +issue](https://github.com/hashicorp/consul/issues/new?assignees=&labels=&template=bug_report.md&title=WAL:%20Checksum%20Failure). + +In your report, include the following: + - Details of your server cluster configuration and hardware + - Logs around the failure message + - Context for how long they have been running the configuration + - Any metrics or description of the workload you have. For example, how many raft + commits per second. Also include the performance metrics described on this page. + +We recommend setting up an alert on Consul server logs containing `verification checksum FAILED` or on the `consul.raft.logstore.verifier.{read|write}_checksum_failures` metrics. The sooner you respond to a corrupt server, the lower the chance of any of the [potential risks](/consul/docs/agent/wal-logstore/enable#risks) causing problems in your cluster. + +## Performance metrics + +The key performance metrics to watch are: + +- `consul.raft.commitTime` measures the time to commit new writes on a quorum of + servers. It should be the same or lower after deploying WAL. Even if WAL is + faster for your workload and hardware, it may not be reflected in `commitTime` + until enough followers are using WAL that the leader does not have to wait for + two slower followers in a cluster of five to catch up. + +- `consul.raft.rpc.appendEntries.storeLogs` measures the time spent persisting + logs to disk on each _follower_. It should be the same or lower for + WAL-enabled followers. + +- `consul.raft.replication.appendEntries.rpc` measures the time taken for each + `AppendEntries` RPC from the leader's perspective. If this is significantly + higher than `consul.raft.rpc.appendEntries` on the follower, it indicates a + known queuing issue in the Raft library and is unrelated to the backend. + Followers with WAL enabled should not be slower than the others. You can + determine which follower is associated with which metric by running the + `consul operator raft list-peers` command and matching the + `peer_id` label value to the server IDs listed. + +- `consul.raft.compactLogs` measures the time take to truncate the logs after a + snapshot. WAL-enabled servers should not be slower than BoltDB servers. + +- `consul.raft.leader.dispatchLog` measures the time spent persisting logs to + disk on the _leader_. It is only relevant if a WAL-enabled server becomes a + leader. It should be the same or lower than before when the leader was using + BoltDB. \ No newline at end of file diff --git a/website/content/docs/agent/wal-logstore/revert-to-boltdb.mdx b/website/content/docs/agent/wal-logstore/revert-to-boltdb.mdx new file mode 100644 index 00000000000..2bd0638b7cd --- /dev/null +++ b/website/content/docs/agent/wal-logstore/revert-to-boltdb.mdx @@ -0,0 +1,76 @@ +--- +layout: docs +page_title: Revert to BoltDB +description: >- + Learn how to revert Consul to the BoltDB backend after enabled the WAL (write-ahead log) LogStore backend shipped in Consul 1.15. +--- + +# Revert storage backend to BoltDB from WAL + +This topic describes how to revert your Consul storage backend from the experimental WAL LogStore backend to the default BoltDB. + +The overall process for reverting to BoltDB consists of the following steps. Repeat the steps for all Consul servers that you need to revert. + +1. Stop target server gracefully. +1. Remove data directory from target server. +1. Update target server's configuration. +1. Start target server. + +## Stop target server gracefully + +Stop the target server gracefully. For example, if you are using `systemd`, +run the following command: + +```shell-session +$ systemctl stop consul +``` + +If your environment uses configuration management automation that might interfere with this process, such as Chef or Puppet, you must disable them until you have completely revereted the storage backend. + +## Remove data directory from target server + +Temporarily moving the data directory to a different location is less destructive than deleting it. We recommend moving the data directory instead of deleted it in cases where you unsuccessfully enable WAL. Do not use the old data directory (`/data-dir/raft.bak`) for recovery after restarting the server. We recommend eventually deleting the old directory. + +The following example assumes the `data_dir` in the server's configuration is `/data-dir` and renames it to `/data-dir.wal.bak`. + +```shell-session +$ mv /data-dir/raft /data-dir/raft.wal.bak +``` + +When switching backend, you must always remove _the entire raft directory_ not just the `raft.db` file or `wal` directory. This is because the log must always be consistent with the snapshots to avoid undefined behavior or data loss. + +## Update target server's configuration + +Modify the `backend` in the target server's configuration file: + +```hcl +raft_logstore { + backend = "boltdb" + verification { + enabled = true + interval = "60s" + } +} +``` + +## Start target server + +Start the target server. For example, if you are using `systemd`, run the following command: + +```shell-session +$ systemctl start consul +``` + +Watch for the server to become a healthy voter again. + +```shell-session +$ consul operator raft list-peers +``` + +### Clean up old data directories + +If necessary, clean up any `raft.wal.bak` directories. Replace `/data-dir` with the value you specified in your configuration file. + +```shell-session +$ rm /data-dir/raft.bak +``` \ No newline at end of file diff --git a/website/content/docs/api-gateway/configuration/routes.mdx b/website/content/docs/api-gateway/configuration/routes.mdx index 4702a360831..27b52034024 100644 --- a/website/content/docs/api-gateway/configuration/routes.mdx +++ b/website/content/docs/api-gateway/configuration/routes.mdx @@ -168,7 +168,7 @@ The following example creates a route named `example-route` in namespace `gatewa ### rules.filters -The `filters` block defines steps for processing requests. You can configure filters to modify the properties of matching incoming requests and enable Consul API Gateway features, such as rewriting path prefixes (refer to [Reroute HTTP requests](/consul/docs/api-gateway/usage#reroute-http-requests) for additional information). +The `filters` block defines steps for processing requests. You can configure filters to modify the properties of matching incoming requests and enable Consul API Gateway features, such as rewriting path prefixes (refer to [Reroute HTTP requests](/consul/docs/api-gateway/usage/reroute-http-requests) for additional information). * Type: Array of objects * Required: Optional @@ -203,7 +203,7 @@ Specifies rules for rewriting the URL of incoming requests when `rules.filters.t ### rules.filters.urlRewrite.path -Specifies a list of objects that determine how Consul API Gateway rewrites URL paths (refer to [Reroute HTTP requests](/consul/docs/api-gateway/usage#reroute-http-requests) for additional information). +Specifies a list of objects that determine how Consul API Gateway rewrites URL paths (refer to [Reroute HTTP requests](/consul/docs/api-gateway/usage/reroute-http-requests) for additional information). The following table describes the parameters for `path`: diff --git a/website/content/docs/api-gateway/index.mdx b/website/content/docs/api-gateway/index.mdx index 1bdc0bfce9d..83372546ac4 100644 --- a/website/content/docs/api-gateway/index.mdx +++ b/website/content/docs/api-gateway/index.mdx @@ -1,13 +1,13 @@ --- layout: docs -page_title: Consul API Gateway Overview +page_title: API Gateway for Kubernetes Overview description: >- Consul API Gateway enables external network client access to a service mesh on Kubernetes and forwards requests based on path or header information. Learn about how the k8s Gateway API specification configures Consul API Gateway so you can control access and simplify traffic management. --- -# Consul API Gateway Overview +# API Gateway for Kubernetes overview -This topic provides an overview of the Consul API Gateway. +This topic provides an overview of the Consul API Gateway for deploying on Kubernetes. If you would like to deploy on virtual machines, refer to [API Gateways on Virtual Machines](/consul/docs/connect/gateways/api-gateway/usage). ## What is Consul API Gateway? diff --git a/website/content/docs/api-gateway/install.mdx b/website/content/docs/api-gateway/install.mdx index 1ee4c61c8fc..1d715c0c478 100644 --- a/website/content/docs/api-gateway/install.mdx +++ b/website/content/docs/api-gateway/install.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Install Consul API Gateway +page_title: Install API Gateway for Kubernetes description: >- Learn how to install custom resource definitions (CRDs) and configure the Helm chart so that you can run Consul API Gateway on your Kubernetes deployment. --- -# Install Consul API Gateway +# Install API Gateway for Kubernetes This topic describes how to install and configure Consul API Gateway. diff --git a/website/content/docs/api-gateway/tech-specs.mdx b/website/content/docs/api-gateway/tech-specs.mdx index 9695eb0126a..7fb142340de 100644 --- a/website/content/docs/api-gateway/tech-specs.mdx +++ b/website/content/docs/api-gateway/tech-specs.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Consul API Gateway Technical Specifications +page_title: API Gateway for Kubernetes Technical Specifications description: >- Consul API Gateway is a service mesh add-on for Kubernetes deployments. Learn about its requirements for system resources, ports, and component versions, its Enterprise limitations, and compatible k8s cloud environments. --- -# Consul API Gateway Technical Specifications +# API Gateway for Kubernetes Technical Specifications This topic describes the technical specifications associated with using Consul API Gateway. diff --git a/website/content/docs/api-gateway/upgrades.mdx b/website/content/docs/api-gateway/upgrades.mdx index 821a80dce8c..e67c3a0de9c 100644 --- a/website/content/docs/api-gateway/upgrades.mdx +++ b/website/content/docs/api-gateway/upgrades.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Upgrade Consul API Gateway +page_title: Upgrade API Gateway for Kubernetes description: >- Upgrade Consul API Gateway to use newly supported features. Learn about the requirements, procedures, and post-configuration changes involved in standard and specific version upgrades. --- -# Upgrade Consul API Gateway +# Upgrade API Gateway for Kubernetes This topic describes how to upgrade Consul API Gateway. diff --git a/website/content/docs/api-gateway/usage/route-to-peered-services.mdx b/website/content/docs/api-gateway/usage/route-to-peered-services.mdx index 33bc54cdb48..50924762397 100644 --- a/website/content/docs/api-gateway/usage/route-to-peered-services.mdx +++ b/website/content/docs/api-gateway/usage/route-to-peered-services.mdx @@ -12,13 +12,13 @@ This topic describes how to configure Consul API Gateway to route traffic to ser 1. Consul 1.14 or later 1. Verify that the [requirements](/consul/docs/api-gateway/tech-specs) have been met. 1. Verify that the Consul API Gateway CRDs and controller have been installed and applied. Refer to [Installation](/consul/docs/api-gateway/install) for details. -1. A peering connection must already be established between Consul clusters. Refer to [Cluster Peering on Kubernetes](/consul/docs/connect/cluster-peering/k8s) for instructions. -1. The Consul service that you want to route traffic to must be exported to the cluster containing your `Gateway`. Refer to [Cluster Peering on Kubernetes](/consul/docs/connect/cluster-peering/k8s) for instructions. +1. A peering connection must already be established between Consul clusters. Refer to [Cluster Peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/tech-specs) for instructions. +1. The Consul service that you want to route traffic to must be exported to the cluster containing your `Gateway`. Refer to [Cluster Peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/tech-specs) for instructions. 1. A `ServiceResolver` for the Consul service you want to route traffic to must be created in the cluster that contains your `Gateway`. Refer to [Service Resolver Configuration Entry](/consul/docs/connect/config-entries/service-resolver) for instructions. ## Configuration -Specify the following fields in your `MeshService` configuration to use this feature. Refer to the [MeshService configuration reference](/consul/docs/api-gateway/configuration/mesh) for details about the parameters. +Specify the following fields in your `MeshService` configuration to use this feature. Refer to the [MeshService configuration reference](/consul/docs/api-gateway/configuration/meshservice) for details about the parameters. - [`name`](/consul/docs/api-gateway/configuration/meshservice#name) - [`peer`](/consul/docs/api-gateway/configuration/meshservice#peer) diff --git a/website/content/docs/api-gateway/usage/usage.mdx b/website/content/docs/api-gateway/usage/usage.mdx index 6dc4dd57607..b9b864ce234 100644 --- a/website/content/docs/api-gateway/usage/usage.mdx +++ b/website/content/docs/api-gateway/usage/usage.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Use Consul API Gateway +page_title: Deploy API Gateway for Kubernetes description: >- Learn how to apply a configured Consul API Gateway to your Kubernetes cluster, review the required fields for rerouting HTTP requests, and troubleshoot an error message. --- -# Basic Consul API Gateway Usage +# Deploy API Gateway for Kubernetes This topic describes how to use Consul API Gateway. diff --git a/website/content/docs/architecture/anti-entropy.mdx b/website/content/docs/architecture/anti-entropy.mdx index 8c2b9bb9675..39d2a08156c 100644 --- a/website/content/docs/architecture/anti-entropy.mdx +++ b/website/content/docs/architecture/anti-entropy.mdx @@ -26,7 +26,7 @@ health checks and updating their local state. Services and checks within the context of an agent have a rich set of configuration options available. This is because the agent is responsible for generating information about its services and their health through the use of -[health checks](/consul/docs/discovery/checks). +[health checks](/consul/docs/services/usage/checks). #### Catalog @@ -117,8 +117,7 @@ the source of truth for tag information. For example, the Redis database and its monitoring service Redis Sentinel have this kind of relationship. Redis instances are responsible for much of their configuration, but Sentinels determine whether the Redis instance is a -primary or a secondary. Using the Consul service configuration item -[enable_tag_override](/consul/docs/discovery/services) you can instruct the -Consul agent on which the Redis database is running to NOT update the -tags during anti-entropy synchronization. For more information see -[Services](/consul/docs/discovery/services#enable-tag-override-and-anti-entropy) page. +primary or a secondary. Enable the +[`enable_tag_override`](/consul/docs/services/configuration/services-configuration-reference#enable_tag_override) parameter in your service definition file to tell the Consul agent where the Redis database is running to bypass +tags during anti-entropy synchronization. Refer to +[Modify anti-entropy synchronozation](/consul/docs/services/usage/define-services#modify-anti-entropy-synchronization) for additional information. diff --git a/website/content/docs/architecture/index.mdx b/website/content/docs/architecture/index.mdx index a36fa644b6b..a4656a7718b 100644 --- a/website/content/docs/architecture/index.mdx +++ b/website/content/docs/architecture/index.mdx @@ -55,7 +55,7 @@ You can also run Consul with an alternate service mesh configuration that deploy ## LAN gossip pool -Client and server agents participate in a LAN gossip pool so that they can distribute and perform node [health checks](/consul/docs/discovery/checks). Agents in the pool propagate the health check information across the cluster. Agent gossip communication occurs on port `8301` using UDP. Agent gossip falls back to TCP if UDP is not available. Refer to [Gossip Protocol](/consul/docs/architecture/gossip) for additional information. +Client and server agents participate in a LAN gossip pool so that they can distribute and perform node [health checks](/consul/docs/services/usage/checks). Agents in the pool propagate the health check information across the cluster. Agent gossip communication occurs on port `8301` using UDP. Agent gossip falls back to TCP if UDP is not available. Refer to [Gossip Protocol](/consul/docs/architecture/gossip) for additional information. The following simplified diagram shows the interactions between servers and clients. diff --git a/website/content/docs/architecture/scale.mdx b/website/content/docs/architecture/scale.mdx index 47176c60afd..da2031fd95b 100644 --- a/website/content/docs/architecture/scale.mdx +++ b/website/content/docs/architecture/scale.mdx @@ -51,7 +51,7 @@ To mitigate these risks, we recommend a maximum of 5,000 Consul client agents in 1. Run exactly one Consul agent per host in the infrastructure. 1. Break up the single Consul datacenter into multiple smaller datacenters. -1. Enterprise users can define [network segments](/consul/docs/enterprise/network-segments) to divide the single gossip pool in the Consul datacenter into multiple smaller pools. +1. Enterprise users can define [network segments](/consul/docs/enterprise/network-segments/network-segments-overview) to divide the single gossip pool in the Consul datacenter into multiple smaller pools. If appropriate for your use case, we recommend breaking up a single Consul datacenter into multiple smaller datacenters. Running multiple datacenters reduces your network’s blast radius more than applying network segments. @@ -236,7 +236,7 @@ Several factors influence Consul performance at scale when used primarily for it - Rate of catalog updates, which is affected by the following events: - A service instance’s health check status changes - A service instance’s node loses connectivity to Consul servers - - The contents of the [service definition file](/consul/docs/discovery/services#service-definition) changes + - The contents of the [service definition file](/consul/docs/services/configuration/services-configuration-reference) changes - Service instances are registered or deregistered - Orchestrators such as Kubernetes or Nomad move a service to a new node diff --git a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx deleted file mode 100644 index dbbf0d4fc3a..00000000000 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ /dev/null @@ -1,537 +0,0 @@ ---- -layout: docs -page_title: Cluster Peering - Create and Manage Connections -description: >- - Generate a peering token to establish communication, export services, and authorize requests for cluster peering connections. Learn how to create, list, read, check, and delete peering connections. ---- - - -# Create and Manage Cluster Peering Connections - -A peering token enables cluster peering between different datacenters. Once you generate a peering token, you can use it to establish a connection between clusters. Then you can export services and create intentions so that peered clusters can call those services. - -## Create a peering connection - -Cluster peering is enabled by default on Consul servers as of v1.14. For additional information, including options to disable cluster peering, refer to [Configuration Files](/consul/docs/agent/config/config-files). - -The process to create a peering connection is a sequence with multiple steps: - -1. Create a peering token -1. Establish a connection between clusters -1. Export services between clusters -1. Authorize services for peers - -You can generate peering tokens and initiate connections on any available agent using either the API, CLI, or the Consul UI. If you use the API or CLI, we recommend performing these operations through a client agent in the partition you want to connect. - -The UI does not currently support exporting services between clusters or authorizing services for peers. - -### Create a peering token - -To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. - -Every time you generate a peering token, a single-use establishment secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. - - - - -In `cluster-01`, use the [`/peering/token` endpoint](/consul/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. - -```shell-session -$ curl --request POST --data '{"Peer":"cluster-02"}' --url http://localhost:8500/v1/peering/token -``` - -The CLI outputs the peering token, which is a base64-encoded string containing the token details. - -Create a JSON file that contains the first cluster's name and the peering token. - - - -```json -{ - "Peer": "cluster-01", - "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" -} -``` - - - - - - -In `cluster-01`, use the [`consul peering generate-token` command](/consul/commands/peering/generate-token) to issue a request for a peering token. - -```shell-session -$ consul peering generate-token -name cluster-02 -``` - -The CLI outputs the peering token, which is a base64-encoded string containing the token details. -Save this value to a file or clipboard to be used in the next step on `cluster-02`. - - - - - -1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**. -1. Click **Add peer connection**. -1. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. -1. Click the **Generate token** button. -1. Copy the token before you proceed. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. - - - - -### Establish a connection between clusters - -Next, use the peering token to establish a secure connection between the clusters. - - - - -In one of the client agents in "cluster-02," use `peering_token.json` and the [`/peering/establish` endpoint](/consul/api-docs/peering#establish-a-peering-connection) to establish the peering connection. This endpoint does not generate an output unless there is an error. - -```shell-session -$ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish -``` - -When you connect server agents through cluster peering, their default behavior is to peer to the `default` partition. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/consul/api-docs/peering). - -You can dial the `peering/establish` endpoint once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. - - - - - -In one of the client agents in "cluster-02," issue the [`consul peering establish` command](/consul/commands/peering/establish) and specify the token generated in the previous step. The command establishes the peering connection. -The commands prints "Successfully established peering connection with cluster-01" after the connection is established. - -```shell-session -$ consul peering establish -name cluster-01 -peering-token token-from-generate -``` - -When you connect server agents through cluster peering, they peer their default partitions. -To establish peering connections for other partitions through server agents, you must add the `-partition` flag to the `establish` command and specify the partitions you want to peer. -For additional configuration information, refer to [`consul peering establish` command](/consul/commands/peering/establish) . - -You can run the `peering establish` command once per peering token. -Peering tokens cannot be reused after being used to establish a connection. -If you need to re-establish a connection, you must generate a new peering token. - - - - - -1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. -1. Click **Establish peering**. -1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. -1. Click **Add peer**. - - - - -### Export services between clusters - -After you establish a connection between the clusters, you need to create a configuration entry that defines the services that are available for other clusters. Consul uses this configuration entry to advertise service information and support service mesh connections across clusters. - -First, create a configuration entry and specify the `Kind` as `"exported-services"`. - - - -```hcl -Kind = "exported-services" -Name = "default" -Services = [ - { - ## The name and namespace of the service to export. - Name = "service-name" - Namespace = "default" - - ## The list of peer clusters to export the service to. - Consumers = [ - { - ## The peer name to reference in config is the one set - ## during the peering process. - Peer = "cluster-02" - } - ] - } -] -``` - - - -Then, add the configuration entry to your cluster. - -```shell-session -$ consul config write peering-config.hcl -``` - -Before you proceed, wait for the clusters to sync and make services available to their peers. You can issue an endpoint query to [check the peered cluster status](#check-peered-cluster-status). - -### Authorize services for peers - -Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. - -First, create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." The following example sets service intentions so that "frontend-service" can access "backend-service." - - - -```hcl -Kind = "service-intentions" -Name = "backend-service" - -Sources = [ - { - Name = "frontend-service" - Peer = "cluster-02" - Action = "allow" - } -] -``` - - - -If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. - -Then, add the configuration entry to your cluster. - -```shell-session -$ consul config write peering-intentions.hcl -``` - -### Authorize Service Reads with ACLs - -If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. -Read access to all imported services is granted using either of the following rules associated with an ACL token: -- `service:write` permissions for any service in the sidecar's partition. -- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. -For Consul Enterprise, access is granted to all imported services in the service's partition. -These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). - -Example rule files can be found in [Reading Servers](/consul/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` config entry documentation. - -Refer to [ACLs System Overview](/consul/docs/security/acl) for more information on ACLs and their setup. - -## Manage peering connections - -After you establish a peering connection, you can get a list of all active peering connections, read a specific peering connection's information, check peering connection health, and delete peering connections. - -### List all peering connections - -You can list all active peering connections in a cluster. - - - - -After you establish a peering connection, [query the `/peerings/` endpoint](/consul/api-docs/peering#list-all-peerings) to get a list of all peering connections. For example, the following command requests a list of all peering connections on `localhost` and returns the information as a series of JSON objects: - -```shell-session -$ curl http://127.0.0.1:8500/v1/peerings - -[ - { - "ID": "462c45e8-018e-f19d-85eb-1fc1bcc2ef12", - "Name": "cluster-02", - "State": "ACTIVE", - "Partition": "default", - "PeerID": "e83a315c-027e-bcb1-7c0c-a46650904a05", - "PeerServerName": "server.dc1.consul", - "PeerServerAddresses": [ - "10.0.0.1:8300" - ], - "CreateIndex": 89, - "ModifyIndex": 89 - }, - { - "ID": "1460ada9-26d2-f30d-3359-2968aa7dc47d", - "Name": "cluster-03", - "State": "INITIAL", - "Partition": "default", - "Meta": { - "env": "production" - }, - "CreateIndex": 109, - "ModifyIndex": 119 - }, -] -``` - - - - -After you establish a peering connection, run the [`consul peering list`](/consul/commands/peering/list) command to get a list of all peering connections. -For example, the following command requests a list of all peering connections and returns the information in a table: - -```shell-session -$ consul peering list - -Name State Imported Svcs Exported Svcs Meta -cluster-02 ACTIVE 0 2 env=production -cluster-03 PENDING 0 0 - ``` - - - - -In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in a datacenter. - -The name that appears in the list is the name of the cluster in a different datacenter with an established peering connection. - - - -### Read a peering connection - -You can get information about individual peering connections between clusters. - - - - -After you establish a peering connection, [query the `/peering/` endpoint](/consul/api-docs/peering#read-a-peering-connection) to get peering information about for a specific cluster. For example, the following command requests peering connection information for "cluster-02" and returns the info as a JSON object: - -```shell-session -$ curl http://127.0.0.1:8500/v1/peering/cluster-02 - -{ - "ID": "462c45e8-018e-f19d-85eb-1fc1bcc2ef12", - "Name": "cluster-02", - "State": "INITIAL", - "PeerID": "e83a315c-027e-bcb1-7c0c-a46650904a05", - "PeerServerName": "server.dc1.consul", - "PeerServerAddresses": [ - "10.0.0.1:8300" - ], - "CreateIndex": 89, - "ModifyIndex": 89 -} -``` - - - - -After you establish a peering connection, run the [`consul peering read`](/consul/commands/peering/list) command to get peering information about for a specific cluster. -For example, the following command requests peering connection information for "cluster-02": - -```shell-session -$ consul peering read -name cluster-02 - -Name: cluster-02 -ID: 3b001063-8079-b1a6-764c-738af5a39a97 -State: ACTIVE -Meta: - env=production - -Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 -Peer Server Name: server.dc1.consul -Peer CA Pems: 0 -Peer Server Addresses: - 10.0.0.1:8300 - -Imported Services: 0 -Exported Services: 2 - -Create Index: 89 -Modify Index: 89 - -``` - - - - -In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. Click the name of a peered cluster to view additional details about the peering connection. - - - -### Check peering connection health - -You can check the status of your peering connection to perform health checks. - -To confirm that the peering connection between your clusters remains healthy, query the [`health/service` endpoint](/consul/api-docs/health) of one cluster from the other cluster. For example, in "cluster-02," query the endpoint and add the `peer=cluster-01` query parameter to the end of the URL. - -```shell-session -$ curl \ - "http://127.0.0.1:8500/v1/health/service/?peer=cluster-01" -``` - -A successful query includes service information in the output. - -### Delete peering connections - -You can disconnect the peered clusters by deleting their connection. Deleting a peering connection stops data replication to the peer and deletes imported data, including services and CA certificates. - - - - -In "cluster-01," request the deletion through the [`/peering/ endpoint`](/consul/api-docs/peering#delete-a-peering-connection). - -```shell-session -$ curl --request DELETE http://127.0.0.1:8500/v1/peering/cluster-02 -``` - - - - -In "cluster-01," request the deletion through the [`consul peering delete`](/consul/commands/peering/list) command. - -```shell-session -$ consul peering delete -name cluster-02 - -Successfully submitted peering connection, cluster-02, for deletion -``` - - - - -In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. - -Next to the name of the peer, click **More** (three horizontal dots) and then **Delete**. Click **Delete** to confirm and remove the peering connection. - - - - -## L7 traffic management between peers - -The following sections describe how to enable L7 traffic management features between peered clusters. - -### Service resolvers for redirects and failover - -As of Consul v1.14, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically failover and redirect between peers. The following examples update the [`service-resolver` config entry](/consul/docs/connect/config-entries/) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. - - - -```hcl -Kind = "service-resolver" -Name = "frontend" -ConnectTimeout = "15s" -Failover = { - "*" = { - Targets = [ - {Peer = "cluster-02"} - ] - } -} -``` - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: frontend -spec: - connectTimeout: 15s - failover: - '*': - targets: - - peer: 'cluster-02' - service: 'frontend' - namespace: 'default' -``` - -```json -{ - "ConnectTimeout": "15s", - "Kind": "service-resolver", - "Name": "frontend", - "Failover": { - "*": { - "Targets": [ - { - "Peer": "cluster-02" - } - ] - } - }, - "CreateIndex": 250, - "ModifyIndex": 250 -} -``` - - - -### Service splitters and custom routes - -The `service-splitter` and `service-router` configuration entry kinds do not support directly targeting a service instance hosted on a peer. To split or route traffic to a service on a peer, you must combine the definition with a `service-resolver` configuration entry that defines the service hosted on the peer as an upstream service. For example, to split traffic evenly between `frontend` services hosted on peers, first define the desired behavior locally: - - - -```hcl -Kind = "service-splitter" -Name = "frontend" -Splits = [ - { - Weight = 50 - ## defaults to service with same name as configuration entry ("frontend") - }, - { - Weight = 50 - Service = "frontend-peer" - }, -] -``` - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceSplitter -metadata: - name: frontend -spec: - splits: - - weight: 50 - ## defaults to service with same name as configuration entry ("web") - - weight: 50 - service: frontend-peer -``` - -```json -{ - "Kind": "service-splitter", - "Name": "frontend", - "Splits": [ - { - "Weight": 50 - }, - { - "Weight": 50, - "Service": "frontend-peer" - } - ] -} -``` - - - -Then, create a local `service-resolver` configuration entry named `frontend-peer` and define a redirect targeting the peer and its service: - - - -```hcl -Kind = "service-resolver" -Name = "frontend-peer" -Redirect { - Service = frontend - Peer = "cluster-02" -} -``` - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: frontend-peer -spec: - redirect: - peer: 'cluster-02' - service: 'frontend' -``` - -```json -{ - "Kind": "service-resolver", - "Name": "frontend-peer", - "Redirect": { - "Service": "frontend", - "Peer": "cluster-02" - } -} -``` - - - diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 77f4b8f4c73..f69f093330e 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -1,32 +1,37 @@ --- layout: docs -page_title: Service Mesh - What is Cluster Peering? +page_title: Cluster Peering Overview description: >- - Cluster peering establishes communication between independent clusters in Consul, allowing services to interact across datacenters. Learn about the cluster peering process, differences with WAN federation for multi-datacenter deployments, and technical constraints. + Cluster peering establishes communication between independent clusters in Consul, allowing services to interact across datacenters. Learn how cluster peering works, its differences with WAN federation for multi-datacenter deployments, and how to troubleshoot common issues. --- -# What is Cluster Peering? +# Cluster peering overview -You can create peering connections between two or more independent clusters so that services deployed to different partitions or datacenters can communicate. +This topic provides an overview of cluster peering, which lets you connect two or more independent Consul clusters so that services deployed to different partitions or datacenters can communicate. +Cluster peering is enabled in Consul by default. For specific information about cluster peering configuration and usage, refer to following pages. -## Overview +## What is cluster peering? -Cluster peering is a process that allows Consul clusters to communicate with each other. The cluster peering process consists of the following steps: +Consul supports cluster peering connections between two [admin partitions](/consul/docs/enterprise/admin-partitions) _in different datacenters_. Deployments without an Enterprise license can still use cluster peering because every datacenter automatically includes a default partition. Meanwhile, admin partitions _in the same datacenter_ do not require cluster peering connections because you can export services between them without generating or exchanging a peering token. -1. Create a peering token in one cluster. -1. Use the peering token to establish peering with a second cluster. -1. Export services between clusters. -1. Create intentions to authorize services for peers. +The following diagram describes Consul's cluster peering architecture. -This process establishes cluster peering between two [admin partitions](/consul/docs/enterprise/admin-partitions). Deployments without an Enterprise license can still use cluster peering because every datacenter automatically includes a `default` partition. +![Diagram of cluster peering with admin partitions](/img/cluster-peering-diagram.png) -For detailed instructions on establishing cluster peering connections, refer to [Create and Manage Peering Connections](/consul/docs/connect/cluster-peering/create-manage-peering). +In this diagram, the `default` partition in Consul DC 1 has a cluster peering connection with the `web` partition in Consul DC 2. Enforced by their respective mesh gateways, this cluster peering connection enables `Service B` to communicate with `Service C` as a service upstream. -> To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](/consul/tutorials/developer-mesh/cluster-peering-aws?utm_source=docs). +Cluster peering leverages several components of Consul's architecture to enforce secure communication between services: -### Differences between WAN federation and cluster peering +- A _peering token_ contains an embedded secret that securely establishes communication when shared symetrically between datacenters. Sharing this token enables each datacenter's server agents to recognize requests from authorized peers, similar to how the [gossip encryption key secures agent LAN gossip](/consul/docs/security/encryption#gossip-encryption). +- A _mesh gateway_ encrypts outgoing traffic, decrypts incoming traffic, and directs traffic to healthy services. Consul's service mesh features must be enabled in order to use mesh gateways. Mesh gateways support the specific admin partitions they are deployed on. Refer to [Mesh gateways](/consul/docs/connect/gateways/mesh-gateway) for more information. +- An _exported service_ communicates with downstreams deployed in other admin partitions. They are explicitly defined in an [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services). +- A _service intention_ secures [service-to-service communication in a service mesh](/consul/docs/connect/intentions). Intentions enable identity-based access between services by exchanging TLS certificates, which the service's sidecar proxy verifies upon each request. -WAN federation and cluster peering are different ways to connect Consul deployments. WAN federation connects multiple datacenters to make them function as if they were a single cluster, while cluster peering treats each datacenter as a separate cluster. As a result, WAN federation requires a primary datacenter to maintain and replicate global states such as ACLs and configuration entries, but cluster peering does not. +### Compared with WAN federation + +WAN federation and cluster peering are different ways to connect services through mesh gateways so that they can communicate across datacenters. WAN federation connects multiple datacenters to make them function as if they were a single cluster, while cluster peering treats each datacenter as a separate cluster. As a result, WAN federation requires a primary datacenter to maintain and replicate global states such as ACLs and configuration entries, but cluster peering does not. + +WAN federation and cluster peering also treat encrypted traffic differently. While mesh gateways between WAN federated datacenters use mTLS to keep data encrypted, mesh gateways between peers terminate mTLS sessions, decrypt data to HTTP services, and then re-encrypt traffic to send to services. Data must be decrypted in order to evaluate and apply dynamic routing rules at the destination cluster, which reduces coupling between peers. Regardless of whether you connect your clusters through WAN federation or cluster peering, human and machine users can use either method to discover services in other clusters or dial them through the service mesh. @@ -42,11 +47,47 @@ Regardless of whether you connect your clusters through WAN federation or cluste | Shares key/value stores | ✅ | ❌ | | Can replicate ACL tokens, policies, and roles | ✅ | ❌ | -## Important Cluster Peering Constraints +## Guidance + +The following resources are available to help you use Consul's cluster peering features. + +### Tutorials + +- To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) and Google Kubernetes Engine (GKE) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](/consul/tutorials/developer-mesh/cluster-peering). + +### Usage documentation + +- [Establish cluster peering connections](/consul/docs/connect/cluster-peering/usage/establish-cluster-peering) +- [Manage cluster peering connections](/consul/docs/connect/cluster-peering/usage/manage-connections) +- [Manage L7 traffic with cluster peering](/consul/docs/connect/cluster-peering/usage/peering-traffic-management) + +### Kubernetes documentation + +- [Cluster peering on Kubernetes technical specifications](/consul/docs/k8s/connect/cluster-peering/tech-specs) +- [Establish cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering) +- [Manage cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/manage-peering) +- [Manage L7 traffic with cluster peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/l7-traffic) + +### HCP Consul documentation + +- [Cluster peering](/hcp/docs/consul/usage/cluster-peering) +- [Cluster peering topologies](/hcp/docs/consul/usage/cluster-peering/topologies) +- [Establish cluster peering connnections on HCP Consul](/hcp/docs/consul/usage/cluster-peering/create-connections) +- [Cluster peering with management plane](/hcp/docs/consul/usage/management-plane#cluster-peering) + +### Reference documentation + +- [Cluster peering technical specifications](/consul/docs/connect/cluster-peering/tech-specs) +- [HTTP API reference: `/peering/` endpoint](/consul/api-docs/peering) +- [CLI reference: `peering` command](/consul/commands/peering). + +## Basic troubleshooting -Consider the following technical constraints: +If you experience errors when using Consul's cluster peering features, refer to the following list of technical constraints. +- Peer names can only contain lowercase characters. - Services with node, instance, and check definitions totaling more than 8MB cannot be exported to a peer. -- Two admin partitions in the same datacenter cannot be peered. Use [`exported-services`](/consul/docs/connect/config-entries/exported-services#exporting-services-to-peered-clusters) directly. -- The `consul intention` CLI command is not supported. To manage intentions that specify services in peered clusters, use [configuration entries](/consul/docs/connect/config-entries/service-intentions). -- Accessing key/value stores across peers is not supported. +- Two admin partitions in the same datacenter cannot be peered. Use the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services#exporting-services-to-peered-clusters) instead. +- To manage intentions that specify services in peered clusters, use [configuration entries](/consul/docs/connect/config-entries/service-intentions). The `consul intention` CLI command is not supported. +- The Consul UI does not support exporting services between clusters or creating service intentions. Use either the API or the CLI to complete these required steps when establishing new cluster peering connections. +- Accessing key/value stores across peers is not supported. \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx deleted file mode 100644 index 570442fd4a7..00000000000 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ /dev/null @@ -1,593 +0,0 @@ ---- -layout: docs -page_title: Cluster Peering on Kubernetes -description: >- - If you use Consul on Kubernetes, learn how to enable cluster peering, create peering CRDs, and then manage peering connections in consul-k8s. ---- - -# Cluster Peering on Kubernetes - -To establish a cluster peering connection on Kubernetes, you need to enable several pre-requisite values in the Helm chart and create custom resource definitions (CRDs) for each side of the peering. - -The following Helm values are mandatory for cluster peering: -- [`global.tls.enabled = true`](/consul/docs/k8s/helm#v-global-tls-enabled) -- [`meshGateway.enabled = true`](/consul/docs/k8s/helm#v-meshgateway-enabled) - -The following CRDs are used to create and manage a peering connection: - -- `PeeringAcceptor`: Generates a peering token and accepts an incoming peering connection. -- `PeeringDialer`: Uses a peering token to make an outbound peering connection with the cluster that generated the token. - -Peering connections, including both data plane and control plane traffic, is routed through mesh gateways. -As of Consul v1.14, you can also [implement service failovers and redirects to control traffic](/consul/docs/connect/l7-traffic) between peers. - -> To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](/consul/tutorials/developer-mesh/cluster-peering-aws?utm_source=docs). - -## Prerequisites - -You must implement the following requirements to create and use cluster peering connections with Kubernetes: - -- Consul v1.14.0 or later -- At least two Kubernetes clusters -- The installation must be running on Consul on Kubernetes version 1.0.0 or later - -### Prepare for installation - -Complete the following procedure after you have provisioned a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters. - -1. Use the `kubectl` command to export the Kubernetes context names and then set them to variables for future use. For more information on how to use kubeconfig and contexts, refer to the [Kubernetes docs on configuring access to multiple clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/). - - You can use the following methods to get the context names for your clusters: - - - Use the `kubectl config current-context` command to get the context for the cluster you are currently in. - - Use the `kubectl config get-contexts` command to get all configured contexts in your kubeconfig file. - - ```shell-session - $ export CLUSTER1_CONTEXT= - $ export CLUSTER2_CONTEXT= - ``` - -1. To establish cluster peering through Kubernetes, create a `values.yaml` file with the following Helm values. **NOTE:** Mesh Gateway replicas are defaulted to 1 replica, and could be adjusted using the `meshGateway.replicas` value for higher availability. - - - - ```yaml - global: - name: consul - image: "hashicorp/consul:1.14.1" - peering: - enabled: true - tls: - enabled: true - meshGateway: - enabled: true - ``` - - - -### Install Consul on Kubernetes - -Install Consul on Kubernetes by using the CLI to apply `values.yaml` to each cluster. - - 1. In `cluster-01`, run the following commands: - - ```shell-session - $ export HELM_RELEASE_NAME=cluster-01 - ``` - - ```shell-session - $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc1 --kube-context $CLUSTER1_CONTEXT - ``` - - 1. In `cluster-02`, run the following commands: - - ```shell-session - $ export HELM_RELEASE_NAME=cluster-02 - ``` - - ```shell-session - $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc2 --kube-context $CLUSTER2_CONTEXT - ``` - -## Create a peering connection for Consul on Kubernetes - -To peer Kubernetes clusters running Consul, you need to create a peering token on one cluster (`cluster-01`) and share -it with the other cluster (`cluster-02`). The generated peering token from `cluster-01` will include the addresses of -the servers for that cluster. The servers for `cluster-02` will use that information to dial the servers in -`cluster-01`. Complete the following steps to create the peer connection. - -### Using mesh gateways for the peering connection -If the servers in `cluster-01` are not directly routable from the dialing cluster `cluster-02`, then you'll need to set up peering through mesh gateways. - -1. In `cluster-01` apply the `Mesh` custom resource so the generated token will have the mesh gateway addresses which will be routable from the other cluster. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: Mesh - metadata: - name: mesh - spec: - peering: - peerThroughMeshGateways: true - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply -f mesh.yaml - ``` - -1. In `cluster-02` apply the `Mesh` custom resource so that the servers for `cluster-02` will use their local mesh gateway to dial the servers for `cluster-01`. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: Mesh - metadata: - name: mesh - spec: - peering: - peerThroughMeshGateways: true - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply -f mesh.yaml - ``` - -### Create a peering token - -Peers identify each other using the `metadata.name` values you establish when creating the `PeeringAcceptor` and `PeeringDialer` CRDs. - -1. In `cluster-01`, create the `PeeringAcceptor` custom resource. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: PeeringAcceptor - metadata: - name: cluster-02 ## The name of the peer you want to connect to - spec: - peer: - secret: - name: "peering-token" - key: "data" - backend: "kubernetes" - ``` - - - -1. Apply the `PeeringAcceptor` resource to the first cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply --filename acceptor.yaml - ```` - -1. Save your peering token so that you can export it to the other cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT get secret peering-token --output yaml > peering-token.yaml - ``` - -### Establish a peering connection between clusters - -1. Apply the peering token to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename peering-token.yaml - ``` - -1. In `cluster-02`, create the `PeeringDialer` custom resource. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: PeeringDialer - metadata: - name: cluster-01 ## The name of the peer you want to connect to - spec: - peer: - secret: - name: "peering-token" - key: "data" - backend: "kubernetes" - ``` - - - -1. Apply the `PeeringDialer` resource to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename dialer.yaml - ``` - -### Configure the mesh gateway mode for traffic between services -Mesh gateways are required for service-to-service traffic between peered clusters. By default, this will mean that a -service dialing another service in a remote peer will dial the remote mesh gateway to reach that service. If you would -like to configure the mesh gateway mode such that this traffic always leaves through the local mesh gateway, you can use the `ProxyDefaults` CRD. - -1. In `cluster-01` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ProxyDefaults - metadata: - name: global - spec: - meshGateway: - mode: local - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply -f proxy-defaults.yaml - ``` - -1. In `cluster-02` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ProxyDefaults - metadata: - name: global - spec: - meshGateway: - mode: local - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply -f proxy-defaults.yaml - ``` - -### Export services between clusters - -The examples described in this section demonstrate how to export a service named `backend`. You should change instances of `backend` in the example code to the name of the service you want to export. - -1. For the service in `cluster-02` that you want to export, add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods prior to deploying. The annotation allows the workload to join the mesh. It is highlighted in the following example: - - - - ```yaml - # Service to expose backend - apiVersion: v1 - kind: Service - metadata: - name: backend - spec: - selector: - app: backend - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 9090 - --- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: backend - --- - # Deployment for backend - apiVersion: apps/v1 - kind: Deployment - metadata: - name: backend - labels: - app: backend - spec: - replicas: 1 - selector: - matchLabels: - app: backend - template: - metadata: - labels: - app: backend - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - serviceAccountName: backend - containers: - - name: backend - image: nicholasjackson/fake-service:v0.22.4 - ports: - - containerPort: 9090 - env: - - name: "LISTEN_ADDR" - value: "0.0.0.0:9090" - - name: "NAME" - value: "backend" - - name: "MESSAGE" - value: "Response from backend" - ``` - - - -1. Deploy the `backend` service to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename backend.yaml - ``` - -1. In `cluster-02`, create an `ExportedServices` custom resource. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ExportedServices - metadata: - name: default ## The name of the partition containing the service - spec: - services: - - name: backend ## The name of the service you want to export - consumers: - - peer: cluster-01 ## The name of the peer that receives the service - ``` - - - -1. Apply the `ExportedServices` resource to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename exported-service.yaml - ``` - -### Authorize services for peers - -1. Create service intentions for the second cluster. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ServiceIntentions - metadata: - name: backend-deny - spec: - destination: - name: backend - sources: - - name: "*" - action: deny - - name: frontend - action: allow - peer: cluster-01 ## The peer of the source service - ``` - - - -1. Apply the intentions to the second cluster. - - - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename intention.yaml - ``` - - - -1. Add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods before deploying the workload so that the services in `cluster-01` can dial `backend` in `cluster-02`. To dial the upstream service from an application, configure the application so that that requests are sent to the correct DNS name as specified in [Service Virtual IP Lookups](/consul/docs/discovery/dns#service-virtual-ip-lookups). In the following example, the annotation that allows the workload to join the mesh and the configuration provided to the workload that enables the workload to dial the upstream service using the correct DNS name is highlighted. [Service Virtual IP Lookups for Consul Enterprise](/consul/docs/discovery/dns#service-virtual-ip-lookups-for-consul-enterprise) details how you would similarly format a DNS name including partitions and namespaces. - - - - ```yaml - # Service to expose frontend - apiVersion: v1 - kind: Service - metadata: - name: frontend - spec: - selector: - app: frontend - ports: - - name: http - protocol: TCP - port: 9090 - targetPort: 9090 - --- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: frontend - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: frontend - labels: - app: frontend - spec: - replicas: 1 - selector: - matchLabels: - app: frontend - template: - metadata: - labels: - app: frontend - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - serviceAccountName: frontend - containers: - - name: frontend - image: nicholasjackson/fake-service:v0.22.4 - securityContext: - capabilities: - add: ["NET_ADMIN"] - ports: - - containerPort: 9090 - env: - - name: "LISTEN_ADDR" - value: "0.0.0.0:9090" - - name: "UPSTREAM_URIS" - value: "http://backend.virtual.cluster-02.consul" - - name: "NAME" - value: "frontend" - - name: "MESSAGE" - value: "Hello World" - - name: "HTTP_CLIENT_KEEP_ALIVES" - value: "false" - ``` - - - -1. Apply the service file to the first cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply --filename frontend.yaml - ``` - -1. Run the following command in `frontend` and then check the output to confirm that you peered your clusters successfully. - - - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT exec -it $(kubectl --context $CLUSTER1_CONTEXT get pod -l app=frontend -o name) -- curl localhost:9090 - - { - "name": "frontend", - "uri": "/", - "type": "HTTP", - "ip_addresses": [ - "10.16.2.11" - ], - "start_time": "2022-08-26T23:40:01.167199", - "end_time": "2022-08-26T23:40:01.226951", - "duration": "59.752279ms", - "body": "Hello World", - "upstream_calls": { - "http://backend.virtual.cluster-02.consul": { - "name": "backend", - "uri": "http://backend.virtual.cluster-02.consul", - "type": "HTTP", - "ip_addresses": [ - "10.32.2.10" - ], - "start_time": "2022-08-26T23:40:01.223503", - "end_time": "2022-08-26T23:40:01.224653", - "duration": "1.149666ms", - "headers": { - "Content-Length": "266", - "Content-Type": "text/plain; charset=utf-8", - "Date": "Fri, 26 Aug 2022 23:40:01 GMT" - }, - "body": "Response from backend", - "code": 200 - } - }, - "code": 200 - } - ``` - - - -## End a peering connection - -To end a peering connection, delete both the `PeeringAcceptor` and `PeeringDialer` resources. - -1. Delete the `PeeringDialer` resource from the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT delete --filename dialer.yaml - ``` - -1. Delete the `PeeringAcceptor` resource from the first cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT delete --filename acceptor.yaml - ```` - -1. Confirm that you deleted your peering connection in `cluster-01` by querying the the `/health` HTTP endpoint. The peered services should no longer appear. - - 1. Exec into the server pod for the first cluster. - - ```shell-session - $ kubectl exec -it consul-server-0 --context $CLUSTER1_CONTEXT -- /bin/sh - ``` - - 1. If you've enabled ACLs, export an ACL token to access the `/health` HTP endpoint for services. The bootstrap token may be used if an ACL token is not already provisioned. - - ```shell-session - $ export CONSUL_HTTP_TOKEN= - ``` - - 1. Query the the `/health` HTTP endpoint. The peered services should no longer appear. - - ```shell-session - $ curl "localhost:8500/v1/health/connect/backend?peer=cluster-02" - ``` - -## Recreate or reset a peering connection - -To recreate or reset the peering connection, you need to generate a new peering token from the cluster where you created the `PeeringAcceptor`. - -1. In the `PeeringAcceptor` CRD, add the annotation `consul.hashicorp.com/peering-version`. If the annotation already exists, update its value to a higher version. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: PeeringAcceptor - metadata: - name: cluster-02 - annotations: - consul.hashicorp.com/peering-version: "1" ## The peering version you want to set, must be in quotes - spec: - peer: - secret: - name: "peering-token" - key: "data" - backend: "kubernetes" - ``` - - - -1. After updating `PeeringAcceptor`, repeat the following steps to create a peering connection: - 1. [Create a peering token](#create-a-peering-token) - 1. [Establish a peering connection between clusters](#establish-a-peering-connection-between-clusters) - 1. [Export services between clusters](#export-services-between-clusters) - 1. [Authorize services for peers](#authorize-services-for-peers) - - Your peering connection is re-established with the updated token. - -~> **Note:** The only way to create or set a new peering token is to manually adjust the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token causes the previous token to expire. - -## Traffic management between peers - -As of Consul v1.14, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically failover and redirect between peers. - -To configure automatic service failovers and redirect, edit the `ServiceResolver` CRD so that traffic resolves to a backup service instance on a peer. The following example updates the `ServiceResolver` CRD in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in `cluster-02` when it detects multiple connection failures to the primary instance. - - - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: frontend -spec: - connectTimeout: 15s - failover: - '*': - targets: - - peer: 'cluster-02' - service: 'backup' - namespace: 'default' -``` - - diff --git a/website/content/docs/connect/cluster-peering/tech-specs.mdx b/website/content/docs/connect/cluster-peering/tech-specs.mdx new file mode 100644 index 00000000000..929a25f924d --- /dev/null +++ b/website/content/docs/connect/cluster-peering/tech-specs.mdx @@ -0,0 +1,84 @@ +--- +layout: docs +page_title: Cluster Peering Technical Specifications +description: >- + Cluster peering connections in Consul interact with mesh gateways, sidecar proxies, exported services, and ACLs. Learn about the configuration requirements for these components. +--- + +# Cluster peering technical specifications + +This reference topic describes the technical specifications associated with using cluster peering in your deployments. These specifications include required Consul components and their configurations. To learn more about Consul's cluster peering feature, refer to [cluster peering overview](/consul/docs/connect/cluster-peering). + +For cluster peering requirements in Kubernetes deployments, refer to [cluster peering on Kubernetes technical specifications](/consul/docs/k8s/connect/cluster-peering/tech-specs). + +## Requirements + +Consul's default configuration supports cluster peering connections directly between clusters. In production environments, we recommend using mesh gateways to securely route service mesh traffic between partitions with cluster peering connections. + +In addition, make sure your Consul environment meets the following prerequisites: + +- Consul v1.14 or higher. +- Use [Envoy proxies](/consul/docs/connect/proxies/envoy). Envoy is the only proxy with mesh gateway capabilities in Consul. +- A local Consul agent is required to manage mesh gateway configurations. + +## Mesh gateway specifications + +To change Consul's default configuration and enable cluster peering through mesh gateways, use a mesh configuration entry to update your network's service mesh proxies globally: + +1. In a `mesh` configuration entry, set `PeerThroughMeshGateways` to `true`: + + + + ```hcl + Kind = "mesh" + Peering { + PeerThroughMeshGateways = true + } + ``` + + + +1. Write the configuration entry to Consul: + + ```shell + $ consul config write mesh-config.hcl + ``` + +When cluster peering through mesh gateways, consider the following deployment requirements: + +- A cluster requires a registered mesh gateway in order to export services to peers in other regions or cloud providers. +- The mesh gateway must also be registered in the same admin partition as the exported services and their `exported-services` configuration entry. An enterprise license is required to use multiple admin partitions with a single cluster of Consul servers. +- To use the `local` mesh gateway mode, you must register a mesh gateway in the importing cluster. +- Define the `Proxy.Config` settings using opaque parameters compatible with your proxy. Refer to the [Gateway options](/consul/docs/connect/proxies/envoy#gateway-options) and [Escape-hatch Overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) documentation for additional Envoy proxy configuration information. + +### Mesh gateway modes + +By default, cluster peering connections use mesh gateways in [remote mode](/consul/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). Be aware of these additional requirements when changing a mesh gateway's mode. + +- For mesh gateways that connect peered clusters, you can set the `mode` as either `remote` or `local`. +- The `none` mode is invalid for mesh gateways with cluster peering connections. + +Refer to [mesh gateway modes](/consul/docs/connect/gateways/mesh-gateway#modes) for more information. + +## Sidecar proxy specifications + +The Envoy proxies that function as sidecars in your service mesh require configuration in order to properly route traffic to peers. Sidecar proxies are defined in the [service definition](/consul/docs/services/usage/define-services). + +- Configure the `proxy.upstreams` parameters to route traffic to the correct service, namespace, and peer. Refer to the [`upstreams`](/consul/docs/connect/registration/service-registration#upstream-configuration-reference) documentation for details. +- The `proxy.upstreams.destination_name` parameter is always required. +- The `proxy.upstreams.destination_peer` parameter must be configured to enable cross-cluster traffic. +- The `proxy.upstream/destination_namespace` configuration is only necessary if the destination service is in a non-default namespace. + +## Exported service specifications + +The `exported-services` configuration entry is required in order for services to communicate across partitions with cluster peering connections. Basic guidance on using the `exported-services` configuration entry is included in [Establish cluster peering connections](/consul/docs/connect/cluster-peering/usage/establish-peering#export-services-between-clusters). + +Refer to the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services) reference for more information. + +## ACL specifications + +If ACLs are enabled, you must add tokens to grant the following permissions: + +- Grant `service:write` permissions to services that define mesh gateways in their server definition. +- Grant `service:read` permissions for all services on the partition. +- Grant `mesh:write` permissions to the mesh gateways that participate in cluster peering connections. This permission allows a leaf certificate to be issued for mesh gateways to terminate TLS sessions for HTTP requests. \ No newline at end of file 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 new file mode 100644 index 00000000000..b8549c5c3fb --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx @@ -0,0 +1,269 @@ +--- +layout: docs +page_title: Establish Cluster Peering Connections +description: >- + Generate a peering token to establish communication, export services, and authorize requests for cluster peering connections. Learn how to establish peering connections with Consul's HTTP API, CLI or UI. +--- + +# Establish cluster peering connections + +This page details the process for establishing a cluster peering connection between services deployed to different datacenters. You can interact with Consul's cluster peering features using the CLI, the HTTP API, or the UI. The overall process for establishing a cluster peering connection consists of the following steps: + +1. Create a peering token in one cluster. +1. Use the peering token to establish peering with a second cluster. +1. Export services between clusters. +1. Create intentions to authorize services for peers. + +Cluster peering between services cannot be established until all four steps are complete. + +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). + +## Requirements + +You must meet the following requirements to use cluster peering: + +- Consul v1.14.1 or higher +- Services hosted in admin partitions on separate datacenters + +If you need to make services available to an admin partition in the same datacenter, do not use cluster peering. Instead, use the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services) to make service upstreams available to other admin partitions in a single datacenter. + +### Mesh gateway requirements + +Consul's default configuration supports cluster peering connections directly between clusters. In production environments, we recommend using mesh gateways to securely route service mesh traffic between partitions with cluster peering connections. + +To enable cluster peering through mesh gateways and configure mesh gateways to support cluster peering, refer to [mesh gateway specifications](/consul/docs/connect/cluster-peering/tech-specs#mesh-gateway-specifications). + +## Create a peering token + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use secret for establishing the secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + + + + +1. In `cluster-01`, use the [`consul peering generate-token` command](/consul/commands/peering/generate-token) to issue a request for a peering token. + + ```shell-session + $ consul peering generate-token -name cluster-02 + ``` + + The CLI outputs the peering token, which is a base64-encoded string containing the token details. + +1. Save this value to a file or clipboard to use in the next step on `cluster-02`. + + + + +1. In `cluster-01`, use the [`/peering/token` endpoint](/consul/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. + + ```shell-session + $ curl --request POST --data '{"Peer":"cluster-02"}' --url http://localhost:8500/v1/peering/token + ``` + + The CLI outputs the peering token, which is a base64-encoded string containing the token details. + +1. Create a JSON file that contains the first cluster's name and the peering token. + + + + ```json + { + "Peer": "cluster-01", + "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" + } + ``` + + + + + + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use secret for establishing the secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + +1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**. +1. Click **Add peer connection**. +1. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. +1. Click the **Generate token** button. +1. Copy the token before you proceed. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. + + + + +## Establish a connection between clusters + +Next, use the peering token to establish a secure connection between the clusters. + + + + +1. In one of the client agents deployed to "cluster-02," issue the [`consul peering establish` command](/consul/commands/peering/establish) and specify the token generated in the previous step. + + ```shell-session + $ consul peering establish -name cluster-01 -peering-token token-from-generate + "Successfully established peering connection with cluster-01" + ``` + +When you connect server agents through cluster peering, they peer their default partitions. To establish peering connections for other partitions through server agents, you must add the `-partition` flag to the `establish` command and specify the partitions you want to peer. For additional configuration information, refer to [`consul peering establish` command](/consul/commands/peering/establish). + +You can run the `peering establish` command once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. + + + + +1. In one of the client agents in "cluster-02," use `peering_token.json` and the [`/peering/establish` endpoint](/consul/api-docs/peering#establish-a-peering-connection) to establish the peering connection. This endpoint does not generate an output unless there is an error. + + ```shell-session + $ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish + ``` + +When you connect server agents through cluster peering, their default behavior is to peer to the `default` partition. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/consul/api-docs/peering). + +You can dial the `peering/establish` endpoint once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. + + + + + +1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. +1. Click **Establish peering**. +1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. +1. Click **Add peer**. + + + + +## Export services between clusters + +After you establish a connection between the clusters, you need to create an `exported-services` configuration entry that defines the services that are available for other clusters. Consul uses this configuration entry to advertise service information and support service mesh connections across clusters. + +An `exported-services` configuration entry makes services available to another admin partition. While it can target admin partitions either locally or remotely. Clusters peers always export services to remote partitions. Refer to [exported service consumers](/consul/docs/connect/config-entries/exported-services#consumers-1) for more information. + +You must use the Consul CLI to complete this step. The HTTP API and the Consul UI do not support `exported-services` configuration entries. + + + + +1. Create a configuration entry and specify the `Kind` as `"exported-services"`. + + + + ```hcl + Kind = "exported-services" + Name = "default" + Services = [ + { + ## The name and namespace of the service to export. + Name = "service-name" + Namespace = "default" + + ## The list of peer clusters to export the service to. + Consumers = [ + { + ## The peer name to reference in config is the one set + ## during the peering process. + Peer = "cluster-02" + } + ] + } + ] + ``` + + + +1. Add the configuration entry to your cluster. + + ```shell-session + $ consul config write peering-config.hcl + ``` + +Before you proceed, wait for the clusters to sync and make services available to their peers. To check the peered cluster status, [read the cluster peering connection](/consul/docs/connect/cluster-peering/usage/manage-connections#read-a-peering-connection). + + + + +## Authorize services for peers + +Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. + +You must use the HTTP API or the Consul CLI to complete this step. The Consul UI supports intentions for local clusters only. + + + + +1. Create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." In the following example, the service intentions configuration entry authorizes the `backend-service` to communicate with the `frontend-service` that is hosted on remote peer `cluster-02`: + + + + ```hcl + Kind = "service-intentions" + Name = "backend-service" + + Sources = [ + { + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } + ] + ``` + + + + If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. + +1. Add the configuration entry to your cluster. + + ```shell-session + $ consul config write peering-intentions.hcl + ``` + + + + +1. Create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." In the following example, the service intentions configuration entry authorizes the `backend-service` to communicate with the `frontend-service` that is hosted on remote peer `cluster-02`: + + + + ```hcl + Kind = "service-intentions" + Name = "backend-service" + + Sources = [ + { + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } + ] + ``` + + + + If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. + +1. Add the configuration entry to your cluster. + + ```shell-session + $ curl --request PUT --data @peering-intentions.hcl http://127.0.0.1:8500/v1/config + ``` + + + + +### Authorize service reads with ACLs + +If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. + +Read access to all imported services is granted using either of the following rules associated with an ACL token: + +- `service:write` permissions for any service in the sidecar's partition. +- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. + +For Consul Enterprise, the permissions apply to all imported services in the service's partition. These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). + +Refer to [Reading servers](/consul/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` configuration entry documentation for example rules. + +For additional information about how to configure and use ACLs, refer to [ACLs system overview](/consul/docs/security/acl). \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/usage/manage-connections.mdx b/website/content/docs/connect/cluster-peering/usage/manage-connections.mdx new file mode 100644 index 00000000000..a4e92373328 --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/manage-connections.mdx @@ -0,0 +1,137 @@ +--- +layout: docs +page_title: Manage Cluster Peering Connections +description: >- + Learn how to list, read, and delete cluster peering connections using Consul. You can use the HTTP API, the CLI, or the Consul UI to manage cluster peering connections. +--- + +# Manage cluster peering connections + +This usage topic describes how to manage cluster peering connections using the CLI, the HTTP API, and the UI. + +After you establish a cluster peering connection, you can get a list of all active peering connections, read a specific peering connection's information, and delete peering connections. + +For Kubernetes-specific guidance for managing cluster peering connections, refer to [Manage cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/manage-peering). + +## List all peering connections + +You can list all active peering connections in a cluster. + + + + + ```shell-session + $ consul peering list + Name State Imported Svcs Exported Svcs Meta + cluster-02 ACTIVE 0 2 env=production + cluster-03 PENDING 0 0 + ``` + +For more information, including optional flags and parameters, refer to the [`consul peering list` CLI command reference](/consul/commands/peering/list). + + + + +The following example shows how to format an API request to list peering connections: + + ```shell-session + $ curl --header "X-Consul-Token: 0137db51-5895-4c25-b6cd-d9ed992f4a52" http://127.0.0.1:8500/v1/peerings + ``` + +For more information, including optional parameters and sample responses, refer to the [`/peering` endpoint reference](/consul/api-docs/peering#list-all-peerings). + + + + +In the Consul UI, click **Peers**. + +The UI lists peering connections you created for clusters in a datacenter. The name that appears in the list is the name of the cluster in a different datacenter with an established peering connection. + + + + +## Read a peering connection + +You can get information about individual peering connections between clusters. + + + + + +The following example outputs information about a peering connection locally referred to as "cluster-02": + + ```shell-session + $ consul peering read -name cluster-02 + Name: cluster-02 + ID: 3b001063-8079-b1a6-764c-738af5a39a97 + State: ACTIVE + Meta: + env=production + + Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 + Peer Server Name: server.dc1.consul + Peer CA Pems: 0 + Peer Server Addresses: + 10.0.0.1:8300 + + Imported Services: 0 + Exported Services: 2 + + Create Index: 89 + Modify Index: 89 + ``` + +For more information, including optional flags and parameters, refer to the [`consul peering read` CLI command reference](/consul/commands/peering/read). + + + + + ```shell-session + $ curl --header "X-Consul-Token: b23b3cad-5ea1-4413-919e-c76884b9ad60" http://127.0.0.1:8500/v1/peering/cluster-02 + ``` + +For more information, including optional parameters and sample responses, refer to the [`/peering` endpoint reference](/consul/api-docs/peering#read-a-peering-connection). + + + + +1. In the Consul UI, click **Peers**. + +1. Click the name of a peered cluster to view additional details about the peering connection. + + + + +## Delete peering connections + +You can disconnect the peered clusters by deleting their connection. Deleting a peering connection stops data replication to the peer and deletes imported data, including services and CA certificates. + + + + + The following examples deletes a peering connection to a cluster locally referred to as "cluster-02": + + ```shell-session + $ consul peering delete -name cluster-02 + Successfully submitted peering connection, cluster-02, for deletion + ``` + +For more information, including optional flags and parameters, refer to the [`consul peering delete` CLI command reference](/consul/commands/peering/delete). + + + + + ```shell-session + $ curl --request DELETE --header "X-Consul-Token: b23b3cad-5ea1-4413-919e-c76884b9ad60" http://127.0.0.1:8500/v1/peering/cluster-02 + ``` + +This endpoint does not return a response. For more information, including optional parameters, refer to the [`/peering` endpoint reference](/consul/api-docs/peering#delete-a-peering-connection). + + + +1. In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. +1. Next to the name of the peer, click **More** (three horizontal dots) and then **Delete**. +1. Click **Delete** to confirm and remove the peering connection. + + + \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx b/website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx new file mode 100644 index 00000000000..240b56dc974 --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx @@ -0,0 +1,168 @@ +--- +layout: docs +page_title: Cluster Peering L7 Traffic Management +description: >- + Combine service resolver configurations with splitter and router configurations to manage L7 traffic in Consul deployments with cluster peering connections. Learn how to define dynamic traffic rules to target peers for redirects and failover. +--- + +# Manage L7 traffic with cluster peering + +This usage topic describes how to configure and apply the [`service-resolver` configuration entry](/consul/docs/connect/config-entries/service-resolver) to set up redirects and failovers between services that have an existing cluster peering connection. + +For Kubernetes-specific guidance for managing L7 traffic with cluster peering, refer to [Manage L7 traffic with cluster peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/l7-traffic). + +## Service resolvers for redirects and failover + +When you use cluster peering to connect datacenters through their admin partitions, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically forward traffic to services hosted on peer clusters. + +However, the `service-splitter` and `service-router` configuration entry kinds do not natively support directly targeting a service instance hosted on a peer. Before you can split or route traffic to a service on a peer, you must define the service hosted on the peer as an upstream service by configuring a failover in the `service-resolver` configuration entry. Then, you can set up a redirect in a second service resolver to interact with the peer service by name. + +For more information about formatting, updating, and managing configuration entries in Consul, refer to [How to use configuration entries](/consul/docs/agent/config-entries). + +## Configure dynamic traffic between peers + +To configure L7 traffic management behavior in deployments with cluster peering connections, complete the following steps in order: + +1. Define the peer cluster as a failover target in the service resolver configuration. + + The following examples update the [`service-resolver` configuration entry](/consul/docs/connect/config-entries/service-resolver) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. + + + + ```hcl + Kind = "service-resolver" + Name = "frontend" + ConnectTimeout = "15s" + Failover = { + "*" = { + Targets = [ + {Peer = "cluster-02"} + ] + } + } + ``` + + ```json + { + "ConnectTimeout": "15s", + "Kind": "service-resolver", + "Name": "frontend", + "Failover": { + "*": { + "Targets": [ + { + "Peer": "cluster-02" + } + ] + } + }, + "CreateIndex": 250, + "ModifyIndex": 250 + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend + spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-02' + service: 'frontend' + namespace: 'default' + ``` + + + +1. Define the desired behavior in `service-splitter` or `service-router` configuration entries. + + The following example splits traffic evenly between `frontend` services hosted on peers by defining the desired behavior locally: + + + + ```hcl + Kind = "service-splitter" + Name = "frontend" + Splits = [ + { + Weight = 50 + ## defaults to service with same name as configuration entry ("frontend") + }, + { + Weight = 50 + Service = "frontend-peer" + }, + ] + ``` + + ```json + { + "Kind": "service-splitter", + "Name": "frontend", + "Splits": [ + { + "Weight": 50 + }, + { + "Weight": 50, + "Service": "frontend-peer" + } + ] + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceSplitter + metadata: + name: frontend + spec: + splits: + - weight: 50 + ## defaults to service with same name as configuration entry ("frontend") + - weight: 50 + service: frontend-peer + ``` + + + +1. Create a local `service-resolver` configuration entry named `frontend-peer` and define a redirect targeting the peer and its service: + + + + ```hcl + Kind = "service-resolver" + Name = "frontend-peer" + Redirect { + Service = frontend + Peer = "cluster-02" + } + ``` + + ```json + { + "Kind": "service-resolver", + "Name": "frontend-peer", + "Redirect": { + "Service": "frontend", + "Peer": "cluster-02" + } + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend-peer + spec: + redirect: + peer: 'cluster-02' + service: 'frontend' + ``` + + \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/ingress-gateway.mdx b/website/content/docs/connect/config-entries/ingress-gateway.mdx index ee94412661c..07f01a03b1c 100644 --- a/website/content/docs/connect/config-entries/ingress-gateway.mdx +++ b/website/content/docs/connect/config-entries/ingress-gateway.mdx @@ -88,6 +88,9 @@ spec: + +For Kubernetes environments, the configuration entry is always created in the same partition as the Kubernetes cluster. + ```hcl @@ -117,7 +120,6 @@ kind: IngressGateway metadata: name: namespace: - partition: spec: listeners: @@ -172,10 +174,9 @@ gateway: - All services with the same [protocol](/consul/docs/connect/config-entries/ingress-gateway#protocol) as the listener will be routable. -- The ingress gateway will route traffic based on the host/authority header, - expecting a value matching `.ingress.*`, or if using namespaces, - `.ingress..*`. This matches the [Consul DNS - ingress subdomain](/consul/docs/discovery/dns#ingress-service-lookups). +- The ingress gateway routes traffic based on the host or authority header and expects a value matching either `.ingress.*` or + `.ingress..*`. The query matches the [Consul DNS + ingress subdomain](/consul/docs/services/discovery/dns-static-lookups#ingress-service-lookups). A wildcard specifier cannot be set on a listener of protocol `tcp`. diff --git a/website/content/docs/connect/config-entries/proxy-defaults.mdx b/website/content/docs/connect/config-entries/proxy-defaults.mdx index 5ac09c102f0..cb9b9991712 100644 --- a/website/content/docs/connect/config-entries/proxy-defaults.mdx +++ b/website/content/docs/connect/config-entries/proxy-defaults.mdx @@ -351,7 +351,8 @@ spec: { name: 'EnvoyExtensions', type: 'array: []', - description: `A list of extensions to modify Envoy proxy configuration.`, + description: `A list of extensions to modify Envoy proxy configuration.

    + Applying \`EnvoyExtensions\` to \`ProxyDefaults\` may produce unintended consequences. We recommend enabling \`EnvoyExtensions\` with [\`ServiceDefaults\`](/consul/docs/connect/config-entries/service-defaults#envoyextensions) in most cases.`, children: [ { name: 'Name', diff --git a/website/content/docs/connect/config-entries/service-defaults.mdx b/website/content/docs/connect/config-entries/service-defaults.mdx index 9245d8112c9..0f9f7cfa978 100644 --- a/website/content/docs/connect/config-entries/service-defaults.mdx +++ b/website/content/docs/connect/config-entries/service-defaults.mdx @@ -1,25 +1,1190 @@ --- layout: docs -page_title: Service Defaults - Configuration Entry Reference -description: >- - The service defaults configuration entry kind defines sets of default configurations that apply to all services in the mesh. Use the examples learn how to define a default protocol, default upstream configuration, and default terminating gateway. +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. --- -# Service Defaults Configuration Entry -The `service-defaults` config entry kind (`ServiceDefaults` on Kubernetes) controls default global values for a -service, such as its protocol. +# Service Defaults Configuration Reference +This topic describes how to configure service defaults configuration entries. The service defaults configuration entry contains common configuration settings for service mesh services, such as upstreams and gateways. Refer to [Define service defaults](/consul/docs/services/usage/define-services#define-service-defaults) for usage information. -## Sample Config Entries +## Configuration model -### Default protocol +The following outline shows how to format the service splitter configuration entry. Click on a property name to view details about the configuration. --> **NOTE**: The default protocol can also be configured globally for all proxies -using the [proxy defaults](/consul/docs/connect/config-entries/proxy-defaults#default-protocol) -config entry. However, if the protocol value is specified in a service defaults -config entry for a given service, that value will take precedence over the -globally configured value from proxy defaults. + + + +- [`Kind`](#kind): string | required +- [`Name`](#name): string | required +- [`Namespace`](#namespace): string +- [`Partition`](#partition): string +- [`Meta`](#meta): map | no default +- [`Protocol`](#protocol): string | default: `tcp` +- [`BalanceInboundConnections`](#balanceinboundconnections): string | no default +- [`Mode`](#mode): string | no default +- [`UpstreamConfig`](#upstreamconfig): map | no default + - [`Overrides`](#upstreamconfig-overrides): map | no default + - [`Name`](#upstreamconfig-overrides-name): string | no default + - [`Namespace`](#upstreamconfig-overrides-namespace): string | no default + - [`Protocol`](#upstreamconfig-overrides-protocol): string | no default + - [`ConnectTimeoutMs`](#upstreamconfig-overrides-connecttimeoutms): int | default: `5000` + - [`MeshGateway`](#upstreamconfig-overrides-meshgateway): map | no default + - [`mode`](#upstreamconfig-overrides-meshgateway): string | no default + - [`BalanceOutboundConnections`](#upstreamconfig-overrides-balanceoutboundconnections): string | no default + - [`Limits`](#upstreamconfig-overrides-limits): map | optional + - [`MaxConnections`](#upstreamconfig-overrides-limits): integer | `0` + - [`MaxPendingRequests`](#upstreamconfig-overrides-limits): integer | `0` + - [`MaxConcurrentRequests`](#upstreamconfig-overrides-limits): integer | `0` + - [`PassiveHealthCheck`](#upstreamconfig-overrides-passivehealthcheck): map | optional + - [`Interval`](#upstreamconfig-overrides-passivehealthcheck): string | `0s` + - [`MaxFailures`](#upstreamconfig-overrides-passivehealthcheck): integer | `0` + - [`EnforcingConsecutive5xx`](#upstreamconfig-overrides-passivehealthcheck): integer | `100` + - [`Defaults`](#upstreamconfig-defaults): map | no default + - [`Protocol`](#upstreamconfig-defaults-protocol): string | no default + - [`ConnectTimeoutMs`](#upstreamconfig-defaults-connecttimeoutms): int | default: `5000` + - [`MeshGateway`](#upstreamconfig-defaults-meshgateway): map | no default + - [`mode`](#upstreamconfig-defaults-meshgateway): string | no default + - [`BalanceOutboundConnections`](#upstreamconfig-defaults-balanceoutboundconnections): string | no default + - [`Limits`](#upstreamconfig-defaults-limits): map | optional + - [`MaxConnections`](#upstreamconfig-defaults-limits): integer | `0` + - [`MaxPendingRequests`](#upstreamconfig-defaults-limits): integer | `0` + - [`MaxConcurrentRequests`](#upstreamconfig-defaults-limits): integer | `0` + - [`PassiveHealthCheck`](#upstreamconfig-defaults-passivehealthcheck): map | optional + - [`Interval`](#upstreamconfig-defaults-passivehealthcheck): string | `0s` + - [`MaxFailures`](#upstreamconfig-defaults-passivehealthcheck): integer | `0` + - [`EnforcingConsecutive5xx`](#upstreamconfig-defaults-passivehealthcheck): integer | +- [`TransparentProxy`](#transparentproxy): map | no default + - [`OutboundListenerPort`](#transparentproxy): integer | `15001` + - [`DialedDirectly`](#transparentproxy ): boolean | `false` +- [`EnvoyExtensions`](#envoyextensions): list | no default + - [`Name`](#envoyextensions): string | `""` + - [`Required`](#envoyextensions): string | `""` + - [`Arguments`](#envoyextensions): map | `nil` +- [`Destination`](#destination): map | no default + - [`Addresses`](#destination): list | no default + - [`Port`](#destination): integer | `0` +- [`MaxInboundConnections`](#maxinboundconnections): integer | `0` +- [`LocalConnectTimeoutMs`](#localconnecttimeoutms): integer | `0` +- [`LocalRequestTiimeoutMs`](#localrequesttimeoutms): integer | `0` +- [`MeshGateway`](#meshgateway): map | no default + - [`Mode`](#meshgateway): string | no default +- [`ExternalSNI`](#externalsni): string | no default +- [`Expose`](#expose): map | no default + - [`Checks`](#expose-checks): boolean | `false` + - [`Paths`](#expose-paths): list | no default + - [`Path`](#expose-paths): string | no default + - [`LocalPathPort`](#expose-paths): integer | `0` + - [`ListenerPort`](#expose-paths): integer | `0` + - [`Protocol`](#expose-paths): string | `http` + + + + +- [`apiVersion`](#apiversion): string | must be set to `consul.hashicorp.com/v1alpha1` +- [`kind`](#kind): string | no default +- [`metadata`](#metadata): map | no default + - [`name`](#name): string | no default + - [`namespace`](#namespace): string | no default | +- [`spec`](#spec): map | no default + - [`protocol`](#protocol): string | default: `tcp` + - [`balanceInboundConnections`](#balanceinboundconnections): string | no default + - [`mode`](#mode): string | no default + - [`upstreamConfig`](#upstreamconfig): map | no default + - [`overrides`](#upstreamconfig-overrides): list | no default + - [`name`](#upstreamconfig-overrides-name): string | no default + - [`namespace`](#upstreamconfig-overrides-namespace): string | no default + - [`protocol`](#upstreamconfig-overrides-protocol): string | no default + - [`connectTimeoutMs`](#upstreamconfig-overrides-connecttimeoutms): int | default: `5000` + - [`meshGateway`](#upstreamconfig-overrides-meshgateway): map | no default + - [`mode`](#upstreamconfig-overrides-meshgateway): string | no default + - [`balanceOutboundConnections`](#overrides-balanceoutboundconnections): string | no default + - [`limits`](#upstreamconfig-overrides-limits): map | optional + - [`maxConnections`](#upstreamconfig-overrides-limits): integer | `0` + - [`maxPendingRequests`](#upstreamconfig-overrides-limits): integer | `0` + - [`maxConcurrentRequests`](#upstreamconfig-overrides-limits): integer | `0` + - [`passiveHealthCheck`](#upstreamconfig-overrides-passivehealthcheck): map | optional + - [`interval`](#upstreamconfig-overrides-passivehealthcheck): string | `0s` + - [`maxFailures`](#upstreamconfig-overrides-passivehealthcheck): integer | `0` + - [`mnforcingConsecutive5xx`](#upstreamconfig-overrides-passivehealthcheck): integer | `100` + - [`defaults`](#upstreamconfig-defaults): map | no default + - [`protocol`](#upstreamconfig-defaults-protocol): string | no default + - [`connectTimeoutMs`](#upstreamconfig-defaults-connecttimeoutms): int | default: `5000` + - [`meshGateway`](#upstreamconfig-defaults-meshgateway): map | no default + - [`mode`](#upstreamconfig-defaults-meshgateway): string | no default + - [`balanceOutboundConnections`](#upstreamconfig-defaults-balanceoutboundconnections): string | no default + - [`limits`](#upstreamconfig-defaults-limits): map | optional + - [`maxConnections`](#upstreamconfig-defaults-limits): integer | `0` + - [`maxPendingRequests`](#upstreamconfig-defaults-limits): integer | `0` + - [`maxConcurrentRequests`](#upstreamconfig-defaults-limits): integer | `0` + - [`passiveHealthCheck`](#upstreamconfig-defaults-passivehealthcheck): map | optional + - [`interval`](#upstreamconfig-defaults-passivehealthcheck): string | `0s` + - [`maxFailures`](#upstreamconfig-defaults-passivehealthcheck): integer | `0` + - [`enforcingConsecutive5xx`](#upstreamconfig-defaults-passivehealthcheck): integer | + - [`transparentProxy`](#transparentproxy): map | no default + - [`outboundListenerPort`](#transparentproxy): integer | `15001` + - [`dialedDirectly`](#transparentproxy): boolean | `false` + - [`envoyExtensions`](#envoyextensions): list | no default + - [`name`](#envoyextensions): string | `""` + - [`required`](#envoyextensions): string | `""` + - [`arguments`](#envoyextensions): map | `nil` + - [`destination`](#destination): map | no default + - [`addresses`](#destination): list | no default + - [`port`](#destination): integer | `0` + - [`maxInboundConnections`](#maxinboundconnections): integer | `0` + - [`localConnectTimeoutMs`](#localconnecttimeoutms): integer | `0` + - [`localRequestTiimeoutMs`](#localrequesttimeoutms): integer | `0` + - [`meshGateway`](#meshgateway): map | no default + - [`mode`](#meshgateway): string | no default + - [`externalSNI`](#externalsni): string | no default + - [`expose`](#expose): map | no default + - [`checks`](#expose-checks): boolean | `false` + - [`paths`](#expose-paths): list | no default + - [`path`](#expose-paths): string | no default + - [`localPathPort`](#expose-paths): integer | `0` + - [`listenerPort`](#expose-paths): integer | `0` + - [`protocol`](#expose-paths): string | `http` + + + + +## Complete configuration + +When every field is defined, a service splitter configuration entry has the following form: + + + + +```hcl +Kind = "service-defaults" +Name = "service_name" +Namespace = "namespace" +Partition = "partition" +Meta = { + Key = "value" +} +Protocol = "tcp" +BalanceInboundConnections = "exact_balance" +Mode = "transparent" +UpstreamConfig = { + Overrides = { + Name = "name-of-upstreams-to-override" + Namespace = "namespace-containing-upstreams-to-override" + Protocol = "http" + ConnectTimeoutMs = 100 + MeshGateway = { + mode = "remote" + } + BalanceOutboundConnections = "exact_balance" + Limits = { + MaxConnections = 10 + MaxPendingRequests = 50 + MaxConcurrentRequests = 100 + } + PassiveHealthCheck = { + Interval = "5s" + MaxFailures = 5 + EnforcingConsecutive5xx = 99 + } + } + Defaults = { + Protocol = "http2" + ConnectTimeoutMs = 2000 + MeshGateway = { + mode = "local" + } + BalanceOutboundConnections = "exact_balance" + Limits = { + MaxConnections = 100 + MaxPendingRequests = 500 + MaxConcurrentRequests = 1000 + } + PassiveHealthCheck = { + Interval = "1s" + MaxFailures = 1 + EnforcingConsecutive5xx = 89 + } + } +} +TransparentProxy = { + OutboundListenerPort = 15002 + DialedDirectly = true +} +Destination = { + Addresses = [ + "First IP address", + "Second IP address" + ] + Port = 88 +} +MaxInboundConnections = 100 +LocalConnectTimeoutMs = 10 +LocalRequestTimeoutMs = 10 +MeshGateway = { + Mode = "remote" +} +ExternalSNI = "sni-server-host" +Expose = { + Checks = true + Paths = [ + { + Path = "/local/dir" + LocalPathPort = 99 + LocalListenerPort = 98 + Protocol = "http2" + } + ] +} +``` + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: + namespace: +spec: + protocol: tcp + balanceInboundConnnections: exact_balance + mode: transparent + upstreamConfig: + overrides: + - name: + namespace: + protocol: + connectTimeoutMs: 5000 + meshGateway: + mode: + balanceOutboundConnections: exact_balance + limits: + maxConnections: 0 + maxPendingRequests: 0 + maxConcurrentRequests: 0 + passiveHealthCheck: + interval: 0s + maxFailures: 0 + enforcingConsecutive5xx: 100 + defaults: + protocol: + connectTimeoutMs: 5000 + meshGateway: + mode: + balanceOutboundConnections: exact_balance + limits: + maxConnections: 0 + maxPendingRequests: 0 + maxConcurrentRequests: 0 + passiveHealthCheck: + interval: 0s + maxFailures: 0 + enforcingConsecutive5xx: 100 + transparentProxy: + outboundListenerPort: 15001 + dialedDirectly: false + destination: + addresses: + - + + port: 0 + maxInboundConnections: 0 + meshGateway: + mode: + externalSNI: + expose: + checks: false + paths: + - path: + localPathPort: 0 + listenerPort: 0 + protocol: http +``` + + + + + +```json +{ + "apiVersion": "consul.hashicorp.com/v1alpha1", + "kind": "ServiceDefaults", + "metadata": { + "name": "", + "namespace": "", + "partition": "" + }, + "spec": { + "protocol": "tcp", + "balanceInboundConnnections": "exact_balance", + "mode": "transparent", + "upstreamConfig": { + "overrides": [ + { + "name": "", + "namespace": "", + "protocol": "", + "connectTimeoutMs": 5000, + "meshGateway": { + "mode": "" + }, + "balanceOutboundConnections": "exact_balance", + "limits": { + "maxConnections": 0, + "maxPendingRequests": 0, + "maxConcurrentRequests": 0 + }, + "passiveHealthCheck": { + "interval": "0s", + "maxFailures": 0, + "enforcingConsecutive5xx": 100 + } + } + ], + "defaults": { + "protocol": "", + "connectTimeoutMs": 5000, + "meshGateway": { + "mode": "" + }, + "balanceOutboundConnections": "exact_balance", + "limits": { + "maxConnections": 0, + "maxPendingRequests": 0, + "maxConcurrentRequests": 0 + }, + "passiveHealthCheck": { + "interval": "0s", + "maxFailures": 0, + "enforcingConsecutive5xx": 100 + } + } + }, + "transparentProxy": { + "outboundListenerPort": 15001, + "dialedDirectly": false + }, + "destination": { + "addresses": [ + "", + "" + ], + "port": 0 + }, + "maxInboundConnections": 0, + "meshGateway": { + "mode": "" + }, + "externalSNI": "", + "expose": { + "checks": false, + "paths": [ + { + "path": "", + "localPathPort": 0, + "listenerPort": 0, + "protocol": "http" + } + ] + } + } +} +``` + + + + + +## Specification + +This section provides details about the fields you can configure in the service defaults configuration entry. + + + + +### `Kind` + +Specifies the configuration entry type. + +#### Values + +- Default: none +- This field is required. +- Data type: String value that must be set to `service-defaults`. + +### `Name` + +Specifies the name of the service you are setting the defaults for. + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Namespace` + +Specifies the Consul namespace that the configuration entry applies to. + +#### Values + +- Default: `default` +- Data type: string + +### `Partition` + +Specifies the name of the name of the Consul admin partition that the configuration entry applies to. Refer to [Admin Partitions](/consul/docs/enterprise/admin-partitions) for additional information. + +#### Values + +- Default: `default` +- Data type: string + +### `Meta` + +Specifies a set of custom key-value pairs to add to the [Consul KV](/consul/docs/dynamic-app-config/kv) store. + +#### Values + +- Default: none +- Data type: Map of one or more key-value pairs. + - keys: string + - values: string, integer, or float + +### `Protocol` + +Specifies the default protocol for the service. In service mesh use cases, the `protocol` configuration is required to enable the following features and components: + +- [observability](/consul/docs/connect/observability) +- [service splitter configuration entry](/consul/docs/connect/config-entries/service-splitter) +- [service router configuration entry](/consul/docs/connect/config-entries/service-router) +- [L7 intentions](/consul/docs/connect/intentions/index#l7-traffic-intentions) + +You can set the global protocol for proxies in the [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults#default-protocol) configuration entry, but the protocol specified in the `service-defaults` configuration entry overrides the `proxy-defaults` configuration. + +#### Values + +- Default: `tcp` +- You can speciyf one of the following string values: + - `tcp` (default) + - `http` + - `http2` + - `grpc` + +Refer to [Set the default protocol](#set-the-default-protocol) for an example configuration. + +### `BalanceInboundConnections` + +Specifies the strategy for allocating inbound connections to the service across Envoy proxy threads. The only supported value is `exact_balance`. By default, no connections are balanced. Refer to the [Envoy documentation](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) for details. + +#### Values + +- Default: none +- Data type: string + +### `Mode` + +Specifies a mode for how the service directs inbound and outbound traffic. + +- Default: none +- You can specify the following string values: + - `direct`: The proxy's listeners must be dialed directly by the local application and other proxies. + - `transparent`: The service captures inbound and outbound traffic and redirects it through the proxy. The mode does not enable the traffic redirection. It instructs Consul to configure Envoy as if traffic is already being redirected. + + +### `UpstreamConfig` + +Controls default upstream connection settings and custom overrides for individual upstream services. If your network contains federated datacenters, individual upstream configurations apply to all pairs of source and upstream destination services in the network. Refer to the following fields for details: + +- [`UpstreamConfig.Overrides`](#upstreamconfig-overrides) +- [`UpstreamConfig.Defaults`](#upstreamconfig-defaults) + +#### Values + +- Default: none +- Data type: map + +### `UpstreamConfig.Overrides[]` + +Specifies options that override the [default upstream configurations](#upstreamconfig-defaults) for individual upstreams. + +#### Values + +- Default: none +- Data type: list + +### `UpstreamConfig.Overrides[].Name` + +Specifies the name of the upstream service that the configuration applies to. We recommend that you do not use the `*` wildcard to avoid applying the configuration to unintended upstreams. + +#### Values + +- Default: none +- Data type: string + +### `UpstreamConfig.Overrides[].Namespace` + +Specifies the namespace containing the upstream service that the configuration applies to. Do not use the `*` wildcard to prevent the configuration from appling to unintended upstreams. + +#### Values + +- Default: none +- Data type: string + +### `UpstreamConfig.Overrides[].Protocol` +Specifies the protocol to use for requests to the upstream listener. + +We recommend configuring the protocol in the main [`Protocol`](#protocol) field of the configuration entry so that you can leverage [L7 features](/consul/docs/connect/l7-traffic). Setting the protocol in an upstream configuration limits L7 management functionality. + +#### Values + +- Default: none +- Data type: string + + +### `UpstreamConfig.Overrides[].ConnectTimeoutMs` + +Specifies how long in milliseconds that the service should attempt to establish an upstream connection before timing out. + +We recommend configuring the upstream timeout in the [`connection_timeout`](/consul/docs/connect/config-entries/service-resolver#connecttimeout) field of the `service-resolver` configuration entry for the upstream destination service. Doing so enables you to leverage [L7 features](/consul/docs/connect/l7-traffic). Configuring the timeout in the `service-defaults` upstream configuration limits L7 management functionality. + +#### Values + +- Default: `5000` +- Data type: integer + +### `UpstreamConfig.Overrides[].MeshGateway` + +Map that contains the default mesh gateway `mode` field for the upstream. Refer to [Connect Proxy Configuration](/consul/docs/connect/gateways/mesh-gateway#connect-proxy-configuration) in the mesh gateway documentation for additional information. + +#### Values + +- Default: `none` +- You can specify the following string values for the `mode` field: + - `none`: The service does not make outbound connections through a mesh gateway. Instead, the service makes outbound connections directly to the destination services. + - `local`: The service mesh proxy makes an outbound connection to a gateway running in the same datacenter. + - `remote`: The service mesh proxy makes an outbound connection to a gateway running in the destination datacenter. + + +### `UpstreamConfig.Overrides[].BalanceOutboundConnections` + +Sets the strategy for allocating outbound connections from the upstream across Envoy proxy threads. + +#### Values + +The only supported value is `exact_balance`. By default, no connections are balanced. Refer to the [Envoy documentation](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) for details. + +- Default: none +- Data type: string + +### `UpstreamConfig.Overrides[].Limits` + +Map that specifies a set of limits to apply to when connecting to individual upstream services. + +#### Values + +The following table describes limits you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `MaxConnections` | Specifies the maximum number of connections a service instance can establish against the upstream. Define this limit for HTTP/1.1 traffic. | integer | `0` | +| `MaxPendingRequests` | Specifies the maximum number of requests that are queued while waiting for a connection to establish. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | +| `MaxConcurrentRequests` | Specifies the maximum number of concurrent requests. Define this limit for HTTP/2 traffic. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | + +Refer to the [upstream configuration example](#upstream-configuration) for additional guidance. + +### `UpstreamConfig.Overrides[].PassiveHealthCheck` + +Map that specifies a set of rules that enable Consul to remove hosts from the upstream cluster that are unreachable or that return errors. + +#### Values + +The following table describes passive health check parameters you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `Interval` | Specifies the time between checks. | string | `0s` | +| `MaxFailures` | Specifies the number of consecutive failures allowed per check interval. If exceeded, Consul removes the host from the load balancer. | integer | `0` | +| `EnforcingConsecutive5xx ` | Specifies a percentage that indicates how many times out of 100 that Consul ejects the host when it detects an outlier status. The outlier status is determined by consecutive errors in the 500-599 response range. | integer | `100` | + +### `UpstreamConfig.Defaults` + +Specifies configurations that set default upstream settings. For information about overriding the default configurations for in for individual upstreams, refer to [`UpstreamConfig.Overrides`](#upstreamconfig-overrides). + +#### Values + +- Default: none +- Data type: map + +### `UpstreamConfig.Defaults.Protocol` + +Specifies default protocol for upstream listeners. + +We recommend configuring the protocol in the main [`Protocol`](#protocol) field of the configuration entry so that you can leverage [L7 features](/consul/docs/connect/l7-traffic). Setting the protocol in an upstream configuration limits L7 management functionality. + +- Default: none +- Data type: string + +### `UpstreamConfig.Defaults.ConnectTimeoutMs` + +Specifies how long in milliseconds that all services should continue attempting to establish an upstream connection before timing out. + +For non-Kubernetes environments, we recommend configuring the upstream timeout in the [`connection_timeout`](/consul/docs/connect/config-entries/service-resolver#connecttimeout) field of the `service-resolver` configuration entry for the upstream destination service. Doing so enables you to leverage [L7 features](/consul/docs/connect/l7-traffic). Configuring the timeout in the `service-defaults` upstream configuration limits L7 management functionality. + +- Default: `5000` +- Data type: integer + +### `UpstreamConfig.Defaults.MeshGateway` + +Specifies the default mesh gateway `mode` field for all upstreams. Refer to [Connect Proxy Configuration](/consul/docs/connect/gateways/mesh-gateway#connect-proxy-configuration) in the mesh gateway documentation for additional information. + +You can specify the following string values for the `mode` field: + +- `none`: The service does not make outbound connections through a mesh gateway. Instead, the service makes outbound connections directly to the destination services. +- `local`: The service mesh proxy makes an outbound connection to a gateway running in the same datacenter. +- `remote`: The service mesh proxy makes an outbound connection to a gateway running in the destination datacenter. + +### `UpstreamConfig.Defaults.BalanceOutboundConnections` + +Sets the strategy for allocating outbound connections from upstreams across Envoy proxy threads. The only supported value is `exact_balance`. By default, no connections are balanced. Refer to the [Envoy documentation](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) for details. + +- Default: none +- Data type: string + +### `UpstreamConfig.Defaults.Limits` + +Map that specifies a set of limits to apply to when connecting upstream services. The following table describes limits you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `MaxConnections` | Specifies the maximum number of connections a service instance can establish against the upstream. Define this limit for HTTP/1.1 traffic. | integer | `0` | +| `MaxPendingRequests` | Specifies the maximum number of requests that are queued while waiting for a connection to establish. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | +| `MaxConcurrentRequests` | Specifies the maximum number of concurrent requests. Define this limit for HTTP/2 traffic. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | + +### `UpstreamConfig.Defaults.PassiveHealthCheck` + +Map that specifies a set of rules that enable Consul to remove hosts from the upstream cluster that are unreachable or that return errors. The following table describes the health check parameters you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `Interval` | Specifies the time between checks. | string | `0s` | +| `MaxFailures` | Specifies the number of consecutive failures allowed per check interval. If exceeded, Consul removes the host from the load balancer. | integer | `0` | +| `EnforcingConsecutive5xx ` | Specifies a percentage that indicates how many times out of 100 that Consul ejects the host when it detects an outlier status. The outlier status is determined by consecutive errors in the 500-599 response range. | integer | `100` | + +### `TransparentProxy` + +Controls configurations specific to proxies in transparent mode. Refer to [Transparent Proxy](/consul/docs/connect/transparent-proxy) for additional information. + +You can configure the following parameters in the `TransparentProxy` block: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `OutboundListenerPort` | Specifies the port that the proxy listens on for outbound traffic. This must be the same port number where outbound application traffic is redirected. | integer | `15001` | +| `DialedDirectly` | Enables transparent proxies to dial the proxy instance's IP address directly when set to `true`. Transparent proxies commonly dial upstreams at the `"virtual"` tagged address, which load balances across instances. Dialing individual instances can be helpful for stateful services, such as a database cluster with a leader. | boolean | `false` | + +### `EnvoyExtensions` + +List of extensions to modify Envoy proxy configuration. Refer to [Envoy Extensions](/consul/docs/connect/proxies/envoy-extensions) for additional information. + +You can configure the following parameters in the `EnvoyExtensions` block: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `Name` | Name of the extension. | string | `""` | +| `Required` | When Required is true and the extension does not update any Envoy resources, an error is returned. Use this parameter to ensure that extensions required for secure communication are not unintentionally bypassed. | string | `""` | +| `Arguments` | Arguments to pass to the extension executable. | map | `nil` | -Set the default protocol for a service in the default namespace to HTTP: +### `Destination[]` + +Configures the destination for service traffic through terminating gateways. Refer to [Terminating Gateway](/consul/docs/connect/gateways/terminating-gateway) for additional information. + +You can configure the following parameters in the `Destination` block: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `Address` | Specifies a list of addresses for the destination. You can configure a list of hostnames and IP addresses. Wildcards are not supported. | list | none | +| `Port` | Specifies the port number of the destination. | integer | `0` | + +### `MaxInboundConnections` + +Specifies the maximum number of concurrent inbound connections to each service instance. + +- Default: `0` +- Data type: integer + +### `LocalConnectTimeoutMs` + +Specifies the number of milliseconds allowed for establishing connections to the local application instance before timing out. + +- Default: `5000` +- Data type: integer + +### `LocalRequestTimeoutMs` + +Specifies the timeout for HTTP requests to the local application instance. Applies to HTTP-based protocols only. If not specified, inherits the Envoy default for route timeouts. + +- Default: Inherits `15s` from Envoy as the default +- Data type: string + +### `MeshGateway` + +Specifies the default mesh gateway `mode` field for the service. Refer to [Connect Proxy Configuration](/consul/docs/connect/gateways/mesh-gateway#connect-proxy-configuration) in the mesh gateway documentation for additional information. + +You can specify the following string values for the `mode` field: + +- `none`: The service does not make outbound connections through a mesh gateway. Instead, the service makes outbound connections directly to the destination services. +- `local`: The service mesh proxy makes an outbound connection to a gateway running in the same datacenter. +- `remote`: The service mesh proxy makes an outbound connection to a gateway running in the destination datacenter. + +### `ExternalSNI` + +Specifies the TLS server name indication (SNI) when federating with an external system. + +- Default: none +- Data type: string + +### `Expose` + +Specifies default configurations for exposing HTTP paths through Envoy. Exposing paths through Envoy enables services to listen on localhost only. Applications that are not Consul service mesh-enabled can still contact an HTTP endpoint. Refer to [Expose Paths Configuration Reference](/consul/docs/connect/registration/service-registration#expose-paths-configuration-reference) for additional information and example configurations. + +- Default: none +- Data type: map + +### `Expose.Checks` + +Exposes all HTTP and gRPC checks registered with the agent if set to `true`. Envoy exposes listeners for the checks and only accepts connections originating from localhost or Consul's [`advertise_addr`](/consul/docs/agent/config/config-files#advertise_addr). The ports for the listeners are dynamically allocated from the agent's [`expose_min_port`](/consul/docs/agent/config/config-files#expose_min_port) and [`expose_max_port`](/consul/docs/agent/config/config-files#expose_max_port) configurations. + +We recommend enabling the `Checks` configuration when a Consul client cannot reach registered services over localhost, such as when Consul agents run in their own pods in Kubernetes. + +- Default: `false` +- Data type: boolean + +### `Expose.Paths[]` + +Specifies a list of configuration maps that define paths to expose through Envoy when `Expose.Checks` is set to `true`. You can configure the following parameters for each map in the list: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `Path` | Specifies the HTTP path to expose. You must prepend the path with a forward slash (`/`). | string | none | +| `LocalPathPort` | Specifies the port where the local service listens for connections to the path. | integer | `0` | +| `ListenPort` | Specifies the port where the proxy listens for connections. The port must be available. If the port is unavailable, Envoy does not expose a listener for the path and the proxy registration still succeeds. | integer | `0` | +| `Protocol` | Specifies the protocol of the listener. You can configure one of the following values:
  • `http`
  • `http2`: Use with gRPC traffic
  • | integer | `http` | + +
    + + + +### `apiVersion` + +Specifies the version of the Consul API for integrating with Kubernetes. The value must be `consul.hashicorp.com/v1alpha1`. The `apiVersion` field is not supported for non-Kubernetes deployments. + +- Default: none +- This field is required. +- String value that must be set to `consul.hashicorp.com/v1alpha1`. + +### `kind` + +Specifies the configuration entry type. Must be ` ServiceDefaults`. + +- Required: required +- String value that must be set to `ServiceDefaults`. + +### `metadata` + +Map that contains the service name, namespace, and admin partition that the configuration entry applies to. + +#### Values + +- Default: none +- Map containing the following strings: + - [`name`](#name) + - [`namespace`](#namespace) + - [`partition`](#partition) + + +### `metadata.name` + +Specifies the name of the service you are setting the defaults for. + +#### Values + +- Default: none +- This field is required +- Data type: string + +### `metadata.namespace` + +Specifies the Consul namespace that the configuration entry applies to. Refer to [Consul Enterprise](/consul/docs/k8s/crds#consul-enterprise) for information about how Consul namespaces map to Kubernetes Namespaces. Open source Consul distributions (Consul OSS) ignore the `metadata.namespace` configuration. + +- Default: `default` +- Data type: string + +### `spec` + +Map that contains the details about the `ServiceDefaults` configuration entry. The `apiVersion`, `kind`, and `metadata` fields are siblings of the `spec` field. All other configurations are children. + +### `spec.protocol` + +Specifies the default protocol for the service. In service service mesh use cases, the `protocol` configuration is required to enable the following features and components: + +- [observability](/consul/docs/connect/observability) +- [`service-splitter` configuration entry](/consul/docs/connect/config-entries/service-splitter) +- [`service-router` configuration entry](/consul/docs/connect/config-entries/service-router) +- [L7 intentions](/consul/docs/connect/intentions/index#l7-traffic-intentions) + +You can set the global protocol for proxies in the [`ProxyDefaults` configuration entry](/consul/docs/connect/config-entries/proxy-defaults#default-protocol), but the protocol specified in the `ServiceDefaults` configuration entry overrides the `ProxyDefaults` configuration. + +#### Values + +- Default: `tcp` +- You can specify one of the following string values: + - `tcp` + - `http` + - `http2` + - `grpc` + +Refer to [Set the default protocol](#set-the-default-protocol) for an example configuration. + +### `spec.balanceInboundConnections` + +Specifies the strategy for allocating inbound connections to the service across Envoy proxy threads. The only supported value is `exact_balance`. By default, no connections are balanced. Refer to the [Envoy documentation](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) for details. + +#### Values + +- Default: none +- Data type: string + +### `spec.mode` + +Specifies a mode for how the service directs inbound and outbound traffic. + +#### Values + +- Default: none +- Required: optional +- You can specified the following string values: + +- `direct`: The proxy's listeners must be dialed directly by the local application and other proxies. +- `transparent`: The service captures inbound and outbound traffic and redirects it through the proxy. The mode does not enable the traffic redirection. It instructs Consul to configure Envoy as if traffic is already being redirected. + +### `spec.upstreamConfig` + +Specifies a map that controls default upstream connection settings and custom overrides for individual upstream services. If your network contains federated datacenters, individual upstream configurations apply to all pairs of source and upstream destination services in the network. + +#### Values + +- Default: none +- Map that contains the following configurations: + - [`UpstreamConfig.Overrides`](#upstreamconfig-overrides) + - [`UpstreamConfig.Defaults`](#upstreamconfig-defaults) + +### `spec.upstreamConfig.overrides[]` + +Specifies options that override the [default upstream configurations](#spec-upstreamconfig-defaults) for individual upstreams. + +#### Values + +- Default: none +- Data type: list + +### `spec.upstreamConfig.overrides[].name` + +Specifies the name of the upstream service that the configuration applies to. Do not use the `*` wildcard to prevent the configuration from applying to unintended upstreams. + +#### Values + +- Default: none +- Data type: string + +### `spec.upstreamConfig.overrides[].namespace` + +Specifies the namespace containing the upstream service that the configuration applies to. Do not use the `*` wildcard to prevent the configuration from applying to unintended upstreams. + +#### Values + +- Default: none +- Data type: string + +### `spec.upstreamConfig.overrides[].protocol` + +Specifies the protocol to use for requests to the upstream listener. We recommend configuring the protocol in the main [`protocol`](#protocol) field of the configuration entry so that you can leverage [L7 features](/consul/docs/connect/l7-traffic). Setting the protocol in an upstream configuration limits L7 management functionality. + +#### Values + +- Default: inherits the main [`protocol`](#protocol) configuration +- Data type: string + + +### `spec.upstreamConfig.overrides[].connectTimeoutMs` + +Specifies how long in milliseconds that the service should attempt to establish an upstream connection before timing out. + +We recommend configuring the upstream timeout in the [`connectTimeout`](/consul/docs/connect/config-entries/service-resolver#connecttimeout) field of the `ServiceResolver` CRD for the upstream destination service. Doing so enables you to leverage [L7 features](/consul/docs/connect/l7-traffic). Configuring the timeout in the `ServiceDefaults` upstream configuration limits L7 management functionality. + +#### Values + +- Default: `5000` +- Data type: integer + +### `spec.upstreamConfig.overrides[].meshGateway.mode` + +Map that contains the default mesh gateway `mode` field for the upstream. Refer to [Connect Proxy Configuration](/consul/docs/connect/gateways/mesh-gateway#connect-proxy-configuration) in the mesh gateway documentation for additional information. + +#### Values + +You can specify the following string values for the `mode` field: + +- `none`: The service does not make outbound connections through a mesh gateway. Instead, the service makes outbound connections directly to the destination services. +- `local`: The service mesh proxy makes an outbound connection to a gateway running in the same datacenter. +- `remote`: The service mesh proxy makes an outbound connection to a gateway running in the destination datacenter. + +### `spec.upstreamConfig.overrides[].balanceInboundConnections` + +Sets the strategy for allocating outbound connections from the upstream across Envoy proxy threads. The only supported value is `exact_balance`. By default, no connections are balanced. Refer to the [Envoy documentation](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) for details. + +#### Values + +- Default: none +- Data type: string + +### `spec.upstreamConfig.overrides[].limits` + +Map that specifies a set of limits to apply to when connecting to individual upstream services. + +#### Values + +The following table describes limits you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `maxConnections` | Specifies the maximum number of connections a service instance can establish against the upstream. Define this limit for HTTP/1.1 traffic. | integer | `0` | +| `maxPendingRequests` | Specifies the maximum number of requests that are queued while waiting for a connection to establish. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | +| `maxConcurrentRequests` | Specifies the maximum number of concurrent requests. Define this limit for HTTP/2 traffic. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | + +### `spec.upstreamConfig.overrides[].passiveHealthCheck` + +Map that specifies a set of rules that enable Consul to remove hosts from the upstream cluster that are unreachable or that return errors. + +#### Values + +The following table describes passive health check parameters you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `interval` | Specifies the time between checks. | string | `0s` | +| `maxFailures` | Specifies the number of consecutive failures allowed per check interval. If exceeded, Consul removes the host from the load balancer. | integer | `0` | +| `enforcingConsecutive5xx ` | Specifies a percentage that indicates how many times out of 100 that Consul ejects the host when it detects an outlier status. The outlier status is determined by consecutive errors in the 500-599 response range. | integer | `100` | + +### `spec.upstreamConfig.defaults` + +Map of configurations that set default upstream configurations for the service. For information about overriding the default configurations for in for individual upstreams, refer to [`spec.upstreamConfig.overrides`](#spec-upstreamconfig-overrides). + +#### Values + +- Default: none +- Data type: list + +### `spec.upstreamConfig.defaults.protocol` + +Specifies default protocol for upstream listeners. We recommend configuring the protocol in the main [`Protocol`](#protocol) field of the configuration entry so that you can leverage [L7 features](/consul/docs/connect/l7-traffic). Setting the protocol in an upstream configuration limits L7 management functionality. + +#### Values + +- Default: none +- Data type: string + +### `spec.upstreamConfig.default.connectTimeoutMs` + +Specifies how long in milliseconds that all services should continue attempting to establish an upstream connection before timing out. + +We recommend configuring the upstream timeout in the [`connectTimeout`](/consul/docs/connect/config-entries/service-resolver#connecttimeout) field of the `ServiceResolver` CRD for upstream destination services. Doing so enables you to leverage [L7 features](/consul/docs/connect/l7-traffic). Configuring the timeout in the `ServiceDefaults` upstream configuration limits L7 management functionality. + +#### Values + +- Default: `5000` +- Data type: integer + +### `spec.upstreamConfig.defaults.meshGateway.mode` + +Specifies the default mesh gateway `mode` field for all upstreams. Refer to [Connect Proxy Configuration](/consul/docs/connect/gateways/mesh-gateway#connect-proxy-configuration) in the mesh gateway documentation for additional information. + +#### Values + +You can specify the following string values for the `mode` field: + +- `none`: The service does not make outbound connections through a mesh gateway. Instead, the service makes outbound connections directly to the destination services. +- `local`: The service mesh proxy makes an outbound connection to a gateway running in the same datacenter. +- `remote`: The service mesh proxy makes an outbound connection to a gateway running in the destination datacenter. + +### `spec.upstreamConfig.defaults.balanceInboundConnections` + +Sets the strategy for allocating outbound connections from upstreams across Envoy proxy threads. The only supported value is `exact_balance`. By default, no connections are balanced. Refer to the [Envoy documentation](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) for details. + +#### Values + +- Default: none +- Data type: string + +### `spec.upstreamConfig.defaults.limits` + +Map that specifies a set of limits to apply to when connecting upstream services. + +#### Values + +The following table describes limits you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `maxConnections` | Specifies the maximum number of connections a service instance can establish against the upstream. Define this limit for HTTP/1.1 traffic. | integer | `0` | +| `maxPendingRequests` | Specifies the maximum number of requests that are queued while waiting for a connection to establish. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | +| `maxConcurrentRequests` | Specifies the maximum number of concurrent requests. Define this limit for HTTP/2 traffic. An L7 protocol must be defined in the [`protocol`](#protocol) field for this limit to take effect. | integer | `0` | + +### `spec.upstreamConfig.defaults.passiveHealthCheck` +Map that specifies a set of rules that enable Consul to remove hosts from the upstream cluster that are unreachable or that return errors. + +#### Values + +The following table describes the health check parameters you can configure: + +| Limit | Description | Data type | Default | +| --- | --- | --- | --- | +| `interval` | Specifies the time between checks. | string | `0s` | +| `maxFailures` | Specifies the number of consecutive failures allowed per check interval. If exceeded, Consul removes the host from the load balancer. | integer | `0` | +| `enforcingConsecutive5xx ` | Specifies a percentage that indicates how many times out of 100 that Consul ejects the host when it detects an outlier status. The outlier status is determined by consecutive errors in the 500-599 response range. | integer | `100` | + +### `spec.transparentProxy` + +Map of configurations specific to proxies in transparent mode. Refer to [Transparent Proxy](/consul/docs/connect/transparent-proxy) for additional information. + +#### Values + +You can configure the following parameters in the `TransparentProxy` block: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `outboundListenerPort` | Specifies the port that the proxy listens on for outbound traffic. This must be the same port number where outbound application traffic is redirected. | integer | `15001` | +| `dialedDirectly` | Enables transparent proxies to dial the proxy instance's IP address directly when set to `true`. Transparent proxies commonly dial upstreams at the `"virtual"` tagged address, which load balances across instances. Dialing individual instances can be helpful for stateful services, such as a database cluster with a leader. | boolean | `false` | + +### `spec.envoyExtensions` + +List of extensions to modify Envoy proxy configuration. Refer to [Envoy Extensions](/consul/docs/connect/proxies/envoy-extensions) for additional information. + +#### Values + +You can configure the following parameters in the `EnvoyExtensions` block: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `name` | Name of the extension. | string | `""` | +| `required` | When Required is true and the extension does not update any Envoy resources, an error is returned. Use this parameter to ensure that extensions required for secure communication are not unintentionally bypassed. | string | `""` | +| `arguments` | Arguments to pass to the extension executable. | map | `nil` | + +### `spec.destination` + +Map of configurations that specify one or more destinations for service traffic routed through terminating gateways. Refer to [Terminating Gateway](/consul/docs/connect/gateways/terminating-gateway) for additional information. + +#### Values + +You can configure the following parameters in the `Destination` block: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `address` | Specifies a list of addresses for the destination. You can configure a list of hostnames and IP addresses. Wildcards are not supported. | list | none | +| `port` | Specifies the port number of the destination. | integer | `0` | + +### `spec.maxInboundConnections` + +Specifies the maximum number of concurrent inbound connections to each service instance. + +#### Values + +- Default: `0` +- Data type: integer + +### `spec.localConnectTimeoutMs` + +Specifies the number of milliseconds allowed for establishing connections to the local application instance before timing out. + +#### Values + +- Default: `5000` +- Data type: integer + +### `spec.localRequestTimeoutMs` + +Specifies the timeout for HTTP requests to the local application instance. Applies to HTTP-based protocols only. If not specified, inherits the Envoy default for route timeouts. + +#### Values + +- Default of `15s` is inherited from Envoy +- Data type: string + +### `spec.meshGateway.mode` +Specifies the default mesh gateway `mode` field for the service. Refer to [Connect Proxy Configuration](/consul/docs/connect/gateways/mesh-gateway#connect-proxy-configuration) in the mesh gateway documentation for additional information. + +#### Values + +You can specify the following string values for the `mode` field: + +- `none`: The service does not make outbound connections through a mesh gateway. Instead, the service makes outbound connections directly to the destination services. +- `local`: The service mesh proxy makes an outbound connection to a gateway running in the same datacenter. +- `remote`: The service mesh proxy makes an outbound connection to a gateway running in the destination datacenter. + +### `spec.externalSNI` + +Specifies the TLS server name indication (SNI) when federating with an external system. + +#### Values + +- Default: none +- Data type: string + +### `spec.expose` + +Specifies default configurations for exposing HTTP paths through Envoy. Exposing paths through Envoy enables services to listen on localhost only. Applications that are not Consul service mesh-enabled can still contact an HTTP endpoint. Refer to [Expose Paths Configuration Reference](/consul/docs/connect/registration/service-registration#expose-paths-configuration-reference) for additional information and example configurations. + +#### Values + +- Default: none +- Data type: string + +### `spec.expose.checks` + +Exposes all HTTP and gRPC checks registered with the agent if set to `true`. Envoy exposes listeners for the checks and only accepts connections originating from localhost or Consul's [`advertise_addr`](/consul/docs/agent/config/config-files#advertise_addr). The ports for the listeners are dynamically allocated from the agent's [`expose_min_port`](/consul/docs/agent/config/config-files#expose_min_port) and [`expose_max_port`](/consul/docs/agent/config/config-files#expose_max_port) configurations. + +We recommend enabling the `Checks` configuration when a Consul client cannot reach registered services over localhost, such as when Consul agents run in their own pods in Kubernetes. + +#### Values + +- Default: `false` +- Data type: boolean + +### `spec.expose.paths[]` + +Specifies an list of maps that define paths to expose through Envoy when `spec.expose.checks` is set to `true`. + +#### Values + +The following table describes the parameters for each map: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `path` | Specifies the HTTP path to expose. You must prepend the path with a forward slash (`/`). | string | none | +| `localPathPort` | Specifies the port where the local service listens for connections to the path. | integer | `0` | +| `listenPort` | Specifies the port where the proxy listens for connections. The port must be available. If the port is unavailable, Envoy does not expose a listener for the path and the proxy registration still succeeds. | integer | `0` | +| `protocol` | Specifies the protocol of the listener. You can configure one of the following values:
  • `http`
  • `http2`: Use with gRPC traffic
  • | integer | `http` | + +
    +
    + +## Example configurations + +The following examples describe common `service-defaults` configurations. + +### Set the default protocol + +In the following example, protocol for the `web` service in the `default` namespace is set to `http`: @@ -50,14 +1215,15 @@ spec: +You can also set the global default protocol for all proxies in the [`proxy-defaults` configuration entry](/consul/docs/connect/config-entries/proxy-defaults#default-protocol), but the protocol specified for individual service instances in the `service-defaults` configuration entry takes precedence over the globally-configured value set in the `proxy-defaults`. + ### Upstream configuration -Set default connection limits and mesh gateway mode across all upstreams -of "dashboard", and also override the mesh gateway mode used when dialing -its upstream "counting" service. +The following example sets default connection limits and mesh gateway mode across all upstreams of the `dashboard` service. +It also overrides the mesh gateway mode used when dialing its `counting` upstream service. @@ -140,10 +1306,7 @@ spec: -Set default connection limits and mesh gateway mode across all upstreams -of "dashboard" in the "product" namespace, -and also override the mesh gateway mode used when dialing -its upstream "counting" service in the "backend" namespace. +The following example configures the default connection limits and mesh gateway mode for all of the `counting` service's upstreams. It also overrides the mesh gateway mode used when dialing the `dashboard` service in the `frontend` namespace. @@ -234,8 +1397,8 @@ spec: ### Terminating gateway destination -Create a default destination that will be assigned to a terminating gateway. A destination -represents a location outside the Consul cluster. They can be dialed directly when transparent proxy mode is enabled. +The following examples creates a default destination assigned to a terminating gateway. A destination +represents a location outside the Consul cluster. Services can dial destinations dialed directly when transparent proxy mode is enabled. @@ -276,6 +1439,7 @@ represents a location outside the Consul cluster. They can be dialed directly wh + \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/service-intentions.mdx b/website/content/docs/connect/config-entries/service-intentions.mdx index 619b4fb9fed..1eb2c42667a 100644 --- a/website/content/docs/connect/config-entries/service-intentions.mdx +++ b/website/content/docs/connect/config-entries/service-intentions.mdx @@ -1,40 +1,932 @@ --- layout: docs -page_title: Service Intentions - Configuration Entry Reference +page_title: Service intentions configuration entry reference description: >- - The service intentions configuration entry kind defines the communication permissions between service types. Use the reference guide to learn about `""service-intentions""` config entry parameters and how to authorize L4 and L7 communication int he service mesh with intentions. + 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 ((#service-intentions)) +# Service intentions configuration entry reference --> **1.9.0+:** This config entry is available in Consul versions 1.9.0 and newer. +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. -The `service-intentions` config entry kind (`ServiceIntentions` on Kubernetes) controls Connect traffic -authorization for both networking layer 4 (e.g. TCP) and networking layer 7 -(e.g. HTTP). +## Configuration model -Service intentions config entries represent a collection of -[intentions](/consul/docs/connect/intentions) sharing a specific destination. All -intentions governing access to a specific destination are stored in a single -`service-intentions` config entry. + -A single config entry may define a mix of both L4 and L7 style intentions, but -for a specific source L4 and L7 intentions are mutually exclusive. Only one -will apply at a time. Default behavior for L4 is configurable (regardless of -global setting) by defining a low precedence intention for that destination. + -## Interaction with other Config Entries +- [`Kind`](#kind): string | required | must be set to `service-intentions` +- [`Name`](#name): string | required +- [`Namespace`](#namespace): string | `default` | +- [`Partition`](#partition): string | `default` | +- [`Meta`](#meta): map | no default +- [`Sources`](#sources): list | no default + - [`Name`](#sources-name): string | no default + - [`Peer`](#sources-peer): string | no default + - [`Namespace`](#sources-namespace): string | no default | + - [`Partition`](#sources-partition): string | no default | + - [`Action`](#sources-action): string | no default | required for L4 intentions + - [`Permissions`](#sources-permissions): list | no default + - [`Action`](#sources-permissions-action): string | no default | required + - [`HTTP`](#sources-permissions-http): map | required + - [`PathExact`](#sources-permissions-http): string | no default + - [`PathPrefix`](#sources-permissions-http): string | no default + - [`PathRegex`](#sources-permissions-http): string | no default + - [`Methods`](#sources-permissions-http): list | no default + - [`Header`](#sources-permissions-http-header): list of maps |no default + - [`Name`](#sources-permissions-http-header): string | required + - [`Present`](#sources-permissions-http-header): boolean | `false` + - [`Exact`](#sources-permissions-http-header): string | no default + - [`Prefix`](#sources-permissions-http-header): string | no default + - [`Suffix`](#sources-permissions-http-header): string | no default + - [`Regex`](#sources-permissions-http-header): string | no default + - [`Invert`](#sources-permissions-http-header): boolean | `false` + - [`Precedence`](#sources-precedence): number | no default | _read-only_ + - [`Type`](#sources-type): string | `consul` + - [`Description`](#sources-description): string + - [`LegacyID`](#sources-legacyid): string | no default | _read-only_ + - [`LegacyMeta`](#sources-legacymeta): map | no default | _read-only_ + - [`LegacyCreateTime`](#sources-legacycreatetime): string | no default | _read-only_ + - [`LegacyUpdateTime`](#sources-legacyupdatetime): string | no default | _read-only_ -L7 intentions within a config entry are restricted to only destination services -that define their protocol as HTTP-based via a corresponding -[`service-defaults`](/consul/docs/connect/config-entries/service-defaults) config entry -or globally via [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults) . + + -## Sample Config Entries +- [`apiVersion`](#apiversion): string | must be set to `consul.hashicorp.com/v1alpha1` +- [`kind`](#kind): string | must be set to `ServiceIntentions` +- [`metadata`](#metadata): map | required + - [`name`](#metadata-name): string | required + - [`namespace`](#metadata-namespace): string | `default` | +- [`spec`](#spec): map | no default + - [`destination`](#spec-destination): map | no default + - [`name`](#spec-destination-name): string | required + - [`namespace`](#metadata-namespace): string | `default` | + - [`sources`](#spec-sources): list | no default + - [`name`](#spec-sources-name): string | no default + - [`peer`](#spec-sources-peer): string | no default + - [`namespace`](#spec-sources-namespace): string | no default | + - [`partition`](#spec-sources-partition): string | no default | + - [`action`](#spec-sources-action): string | no default | required for L4 intentions + - [`permissions`](#spec-sources-permissions): list | no default + - [`action`](#spec-sources-permissions-action): string | no default | required + - [`http`](#spec-sources-permissions-http): map | required + - [`pathExact`](#spec-sources-permissions-http): string | no default + - [`pathPrefix`](#spec-sources-permissions-http): string | no default + - [`pathRegex`](#spec-sources-permissions-http): string | no default + - [`methods`](#spec-sources-permissions-http): list | no default + - [`header`](#spec-sources-permissions-http-header): list of maps |no default + - [`name`](#spec-sources-permissions-http-header): string | required + - [`present`](#spec-sources-permissions-http-header): boolean | `false` + - [`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 + - [`regex`](#spec-sources-permissions-http-header): string | no default + - [`invert`](#spec-sources-permissions-http-header): boolean | `false` + - [`type`](#spec-sources-type): string | `consul` + - [`description`](#spec-sources-description): string -The following examples demonstrate potential use-cases for the `service-intentions` configuration entry. + + -### REST Access +## Complete configuration + + + + + +```hcl +Kind = "service-intentions" +Name = "" +Namespace = "" # string +Partition = "" # string +Meta = { + "" = "" + "" = "" + } +Sources = [ + { + Name = "" # string + Peer = "" # string + Namespace = "" # string + Partition = "" # string + Action = "allow" or "deny" # string for L4 intentions + Permissions = [ + { + Action = "allow" or "deny" # string for L7 intenions + HTTP = { + PathExact = "" # string + PathPrefix = "" # string + PathRegex = "" # string + Methods = [ + "", # string + "" + ] + Header = [ + { + Name = "" # string + Present = # boolean + }, + { + Name = "" # string + Exact = "" # boolean + }, + { + Name = "" # string + Prefix = "" # string + }, + { + Name = "" # string + Suffix = "" # string + }, + { + Name = "" # string + Regex = "" # string + Invert = # boolean + } + ] + } + } + ] + Type = "consul" # string + Description = "" # string + Precedence = # number + LegacyID = # string + LegacyMeta = # string + LegacyCreateTime = # string + LegacyUpdateTime = # string + } +] +``` + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: service-intentions +metadata: + name: + namespace: +spec: + destination: + destination: + name: + namespace: + sources: + name: + peer: + namespace: + partition: + action: allow or deny + permissions: + - action: allow or deny + http: + pathExact: + pathPrefix: + pathRegex: + methods: + - + + header: + - name: + present: true + - name: + exact: false + - name: + prefix: + - name: + suffix: + - name: + regex: + invert: false + type: consul + description: +``` + + + + +```json +{ + "Kind":"service-intentions", + "Name":"", + "Namespace":"", + "Partition":"", + "Meta":{ + "key-1":"", + "key-2":"" + }, + "Sources":[ + { + "Name":"", + "Peer":"", + "Namespace":"", + "Partition":"", + "Action":"allow or deny", + "Permissions":[ + { + "Action":"allow or deny", + "HTTP":{ + "PathExact":"", + "PathPrefix":"", + "PathRegex":"", + "Methods":[ + "", + "" + ], + "Header":[ + { + "Name":"", + "Present":true + }, + { + "Name":"", + "Exact":false + }, + { + "Name":"", + "Prefix":"" + }, + { + "Name":"", + "Suffix":"" + }, + { + "Name":"", + "Regex":"", + "Invert":false + } + ] + } + } + ], + "Type":"consul", + "Description":"", + "Precedence":"", + "LegacyID":"", + "LegacyMeta":"", + "LegacyCreateTime":"", + "LegacyUpdateTime":"" + } + ] +} +``` + + + + + +## Specification + +This section provides details about the fields you can configure in the service defaults configuration entry. + + + + + +### `Kind` + +Specifies the type of configuration entry to implement. Must be set to `service-intentions`. + +#### Values + +- Default: None +- This field is required. +- Data type: String value that must be set to `service-intentions`. + +### `Name` + +Specifies a name of the destination service for all intentions defined in the configuration entry. + +#### Values + +- Default: Defaults to the name of the node after writing the entry to the Consul server. +- This field is required. +- Data type: String + +You can also specify a wildcard character (`*`) to match all services without intentions. Intentions that are applied with a wildcard, however, are not supported when defining L7 [`Permissions`](#sources-permissions). + +### `Namespace` + +Specifies the [namespace](/consul/docs/enterprise/namespaces) that the configuration entry applies to. Services in the namespace are the traffic destinations that the intentions allow or deny traffic to. + +#### Values + +- Default: `default` +- Data type: String + +You can also specify a wildcard character (`*`) to match all namespaces. Intentions that are applied with a wildcard, however, are not supported when defining L7 [`Permissions`](#sources-permissions). + +### `Partition` + +Specifies the [admin partition](/consul/docs/enterprise/admin-partitions) to apply the configuration entry. Services in the specified partition are the traffic destinations that the intentions allow or deny traffic to. + +#### Values + +- Default: `default` +- Data type: String + +### `Meta` + +Specifies key-value pairs to add to the KV store when the configuration entry is evaluated. + +#### Values + +- Default: None +- Data type: Map of one or more key-value pairs + - keys: String + - values: String, integer, or float + +### `Sources[]` + +List of configurations that define intention sources and the authorization granted to the sources. You can specify source configurations in any order, but Consul stores and evaluates them in order of reverse precedence at runtime. Refer to [`Precedence`](#sources-precedence) for additional information. + +#### Values + +- Default: None +- List of objects that contain the following fields: + - `Name` + - `Peer` + - `Namespace` + - `Partition` + - `Action` + - `Permissions` + - `Precedence` + - `Type` + - `Description` + - `LegacyID` + - `LegacyMeta` + - `LegacyCreateTime` + - `LegacyUpdateTime` + +### `Sources[].Name` + +Specifies the name of the source that the intention allows or denies traffic from. If [`Type`](#sources-type) is set to `consul`, then the value refers to the name of a Consul service. The source is not required to be registered into the Consul catalog. + +#### Values + +- Default: None +- This field is required. +- Data type: String + +### `Sources[].Peer` + +Specifies the name of a peered Consul cluster that the intention allows or denies traffic from. Refer to [Cluster peering overview](/consul/docs/connect/cluster-peering) for additional information about peers. + +The `Peer` and `Partition` fields are mutually exclusive. + +#### Values + +- Default: None +- Data type: String + +### `Sources[].Namespace` + +Specifies the traffic source namespace that the intention allows or denies traffic from. + +#### Values + +- Default: If [`Peer`](#sources-peer) is unspecified, defaults to the destination [`Namespace`](#namespace). +- Data type: String + +### `Sources[].Partition` + +Specifies the name of an admin partition that the intention allows or denies traffic from. Refer to [Admin Partitions](/consul/docs/enterprise/admin-partitions) for additional information about partitions. + +The `Peer` and `Partition` fields are mutually exclusive. + +#### Values + +- Default: If [`Peer`](#sources-peer) is unspecified, defaults to the destination [`Partition`](#partition). +- Data type: string + +### `Sources[].Action` + +Specifies the action to take when the source sends traffic to the destination service. The value is either `allow` or `deny`. Do not configure this field to apply L7 intentions to the same source. Configure the [`Permissions`](#sources-permissions) field instead. + +#### Values + +- Default: None +- This field is required for L4 intentions. +- Data type: String value set to either `allow` or `deny` + +Refer to the following examples for additional guidance: + +- [L4 Intentions for specific sources and destinations](#l4-intentions-for-specific-sources-and-destinations) +- [L4 intentions for all destinations](#l4-intentions-for-all-destinations) +- [L4 intentions for all sources](#l4-intentions-for-all-sources) +- [L4 and L7](#l4-and-l7) + +### `Sources[].Permissions[]` + +Specifies a list of permissions for L7 traffic sources. The list contains one or more actions and a set of match criteria for each action. + +Consul applies permissions in the order specified in the configuration. Beginning at the top of the list, Consul applies the first matching request and stops evaluating against the remaining configurations. + +For requests that do not match any of the defined permissions, Consul applies the intention behavior defined in the [`acl_default_policy`](/consul/docs/agent/config/config-files#acl_default_policy) configuration. + +Do not configure this field for L4 intentions. Use the [`Sources.Action`](#sources-action) parameter instead. + +The `Permissions` only applies to services with a compatible protocol. `Permissions` are not supported when the [`Name`](#name) or [`Namespace`](#namespace) field is configured with a wildcard because service instances or services in a namespace may use different protocols. + +#### Values + +- Default: None +- List of objects that contain the following fields: + - `Action` + - `HTTP` + +Refer to the following examples for additional guidance: + +- [Rest access](#rest-access) +- [gRPC](#grpc) +- [Cluster peering](#cluster-peering) +- [L4 and L7](#l4-and-l7) + +### `Sources[].Permissions[].Action` + +Specifies the action to take when the source sends traffic to the destination service. The value is either `allow` or `deny`. + +#### Values + +- Default: None +- This field is required. +- Data type: String value set to either `allow` or `deny`. + +### `Sources[].Permissions[].HTTP` + +Specifies a set of HTTP-specific match criteria. Consul applies the action defined in the [`Action`](#sources-permissions-action) field to source traffic that matches the criteria. + +#### Values + +- Default: None +- This field is required. +- Data type: Map + +The following table describes the parameters that the HTTP map may contain: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `PathExact` | Specifies an exact path to match on the HTTP request path. Do not specify `PathExact` if `PathPrefix` or `PathRegex` are configured in the same `HTTP` configuration. | string | none | +| `PathPrefix` | Specifies a path prefix to match on the HTTP request path. Do not specify `PathPrefix` if `PathExact` or `PathRegex` are configured in the same `HTTP` configuration. | string | none | +| `PathRegex` | Defines a regular expression to match on the HTTP request path. Do not specify `PathRegex` if `PathExact` or `PathPrefix` are configured in the same `HTTP` 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 | none | +| `Methods` | Specifies a list of HTTP methods. Consul applies the permission if a request matches the `PathExact`, `PathPrefix`, `PathRegex`, or `Header`, and the source sent the request using one of the specified methods. Refer to the [Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for a list of supported request headers. | list | All request methods | +| `Header` | Specifies a header name and matching criteria for HTTP request headers. Refer to [`Sources[].Permissions[].HTTP[].Header`](#sources-permissions-http-header) for details. | list of maps | none | + +### `Sources[].Permissions[].HTTP[].Header[]` + +Specifies a header name and matching criteria for HTTP request headers. The request header must match all specified criteria for the permission to apply. + +#### Values + +- Default: None +- Data type: list of objects + +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: + +| 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 | +| `Invert` | Inverts the matching logic configured in the `Header`. Default is `false`. | boolean | optional | + + +### `Sources[].Precedence` + +The `Precedence` field contains a read-only integer. Consul generates the value based on name configurations for the source and destination services. Refer to [Precedence and matching order](/consul/docs/connect/intentions/create-manage-intentions#precedence-and-matching-order) for additional information. + +### `Sources[].Type` + +Specifies the type of destination service that the configuration entry applies to. The only value supported is `consul`. + +#### Values + +- Default: `consul` +- Data type: String + +### `Sources[].Description` + +Specifies a description of the intention. Consul presents the description in API responses to assist other tools integrated into the network. + +#### Values + +- Default: None +- Data type: String + +### `Sources[].LegacyID` + +Read-only unique user ID (UUID) for the intention in the system. Consul generates the value and exposes it in the configuration entry so that legacy API endpoints continue to function. Refer to [Read Specific Intention by ID](/consul/api-docs/connect/intentions#read-specific-intention-by-id) for additional information. + +### `Sources[].LegacyMeta` + +Read-only set of arbitrary key-value pairs to attach to the intention. Consul generates the metadata and exposes it in the configuration entry so that legacy intention API endpoints continue to function. Refer to [Read Specific Intention by ID](/consul/api-docs/connect/intentions#read-specific-intention-by-id) for additional information. + +### `Sources[].CreateTime` + +Read-only timestamp for the intention creation. Consul exposes the timestamp in the configuration entry to allow legacy intention API endpoints to continue functioning. Refer to [Read Specific Intention by ID](/consul/api-docs/connect/intentions#read-specific-intention-by-id) for additional information. + +### `Sources[].LegacyUpdateTime` + +Read-only timestamp marking the most recent intention update. Consul exposes the timestamp in the configuration entry to allow legacy intention API endpoints to continue functioning. Refer to [Read Specific Intention by ID](/consul/api-docs/connect/intentions#read-specific-intention-by-id) for additional information. + + + + + +### `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 `ServiceIntentions`. + +#### Values + +- Default: None +- This field is required. +- Data type: String value that must be set to `ServiceIntentions`. + +### `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 an arbitrary name for the configuration entry. Note that in other configuration entries, the `metadata.name` field specifies the name of the service that the settings apply to. For service intentions, the service that accepts the configurations is the _destination_ and is specified in the [`spec.destination.name`](#spec-destination-name) field. Refer to the following topics for additional information: + +- [ServiceIntentions Special Case (OSS)](/consul/docs/k8s/crds#serviceintentions-special-case) +- [ServiceIntentions Special Case (Enterprise)](/consul/docs/k8s/crds#serviceintentions-special-case-enterprise) + +#### Values + +- Default: None +- Data type: String + +### `metadata.namespace` + +Specifies the [namespace](/consul/docs/enterprise/namespaces) that the configuration entry applies to. Refer to [Consul Enterprise](/consul/docs/k8s/crds#consul-enterprise) for information about how Consul namespaces map to Kubernetes Namespaces. Open source Consul distributions (Consul OSS) ignore the `metadata.namespace` configuration. + +#### Values + +- Default: `default` +- Data type: String + +### `spec` +Map that contains the details about the `ServiceIntentions` configuration entry. 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` + +Map that identifies the destination name and destination namespace that source services are allowed or denied access to. + +#### Values + +- Default: None +- This field is required. +- Data type: Map + +### `spec.destination.name` + +Specifies the name of the destination service in the mesh that the intentions apply to. +You can also specify a wildcard character (`*`) to match all services that are missing intention settings. Intentions that are applied with a wildcard, however, are not supported when defining L7 [`permissions`](#spec-sources-permissions). + +#### Values + +- Default: None +- This field is required. +- Data type: String + +### `spec.metadata.namespace` + +Specifies the [namespace](/consul/docs/enterprise/namespaces) that the configuration entry applies to. You can also specify a wildcard character (`*`) to match all namespaces. Intentions that are applied with a wildcard, however, are not supported when defining L7 [`permissions`](#spec-sources-permissions). + +Refer to [Consul Enterprise](/consul/docs/k8s/crds#consul-enterprise) for information about how Consul namespaces map to Kubernetes Namespaces. Open source Consul distributions (Consul OSS) ignore the `metadata.namespace` configuration. + +#### Values + +- Default: If not set, destination service namespace is inherited from the `connectInject.consulNamespaces` configuration. Refer to [ServiceIntentions Special Case (Enterprise)](/consul/docs/k8s/crds#serviceintentions-special-case-enterprise) for details. +- Data type: String + +### `spec.sources[]` + +List of configurations that define intention sources and the authorization granted to the sources. You can specify source configurations in any order, but Consul stores and evaluates them in order of reverse precedence at runtime. + +#### Values + +- Default: None +- List of objects that contain the following fields: + - `name` + - `peer` + - `namespace` + - `partition` + - `Action` + - `permissions` + - `type` + - `description` + +### `spec.sources[].name` + +Specifies the name of the source that the intention allows or denies traffic from. If [`type`](#sources-type) is set to `consul`, then the value refers to the name of a Consul service. The source is not required to be registered into the Consul catalog. + +#### Values + +- Default: None +- This field is required. +- Data type: String + +### `spec.sources[].peer` + +Specifies the name of a peered Consul cluster that the intention allows or denies traffic from. Refer to [Cluster peering overview](/consul/docs/connect/cluster-peering) for additional information about peers. The `peer` and `partition` fields are mutually exclusive. +#### Values + +- Default: None +- Data type: String + +### `spec.sources[].namespace` + +Specifies the traffic source namespace that the intention allows or denies traffic from. + +#### Values + +- Default: If [`peer`](#spec-sources-peer) is unspecified, defaults to the namespace specified in the [`spec.destination.namespace`](#spec-destination-namespace) field. +- Data type: String + +### `spec.sources[].partition` + +Specifies the name of an admin partition that the intention allows or denies traffic from. Refer to [Admin Partitions](/consul/docs/enterprise/admin-partitions) for additional information about partitions. The `peer` and `partition` fields are mutually exclusive. + +#### Values + +- Default: If [`peer`](#sources-peer) is unspecified, defaults to the partition specified in [`spec.destination.partition`](#spec-destination-partition). +- Data type: String + +### `spec.sources[].action` + +Specifies the action to take when the source sends traffic to the destination service. The value is either `allow` or `deny`. Do not configure this field for L7 intentions. Configure the [`spec.sources.permissions`](#spec-sources-permissions) field instead. + +#### Values + +- Default: None +- This field is required for L4 intentions. +- Data type: String value set to either `allow` or `deny` + +### `spec.sources[].permissions[]` + +Specifies a list of permissions for L7 traffic sources. The list contains one or more actions and a set of match criteria for each action. + +Consul applies permissions in the order specified in the configuration. Starting at the beginning of the list, Consul applies the first matching request and stops evaluating against the remaining configurations. + +For requests that do not match any of the defined permissions, Consul applies the intention behavior defined in the [`acl_default_policy`](/consul/docs/agent/config/config-files#acl_default_policy) configuration. + +Do not configure this field for L4 intentions. Use the [`spec.sources.action`](#sources-action) parameter instead. + +`permissions` configurations only apply to services with a compatible protocol. As a result, they are not supported when the [`spec.destination.name`](#spec-destination-name) or [`spec.destination.namespace`](#spec-destination-namespace) field is configured with a wildcard because service instances or services in a namespace may use different protocols. + +#### Values + +- Default: None +- List of objects that contain the following fields: + - `action` + - `http` + +### `spec.sources[].permissions[].action` + +Specifies the action to take when the source sends traffic to the destination service. The value is either `allow` or `deny`. + +#### Values + +- Default: None +- This field is required. +- Data type: String value set to either `allow` or `deny` + +### `spec.sources[].permissions[].http` + +Specifies a set of HTTP-specific match criteria. Consul applies the action defined in the [`spec.sources.permissions.action`](#spec-sources-permissions-action) field to source traffic that matches the criteria. + +#### Values + +- Default: None +- This field is required. +- Data type: Map + +The following table describes the parameters that the HTTP map may contain: + +| Parameter | Description | Data type | Default | +| --- | --- | --- | --- | +| `pathExact` | Specifies an exact path to match on the HTTP request path. Do not specify `pathExact` if `pathPrefix` or `pathRegex` are configured in the same `http` configuration. | string | none | +| `pathPrefix` | Specifies a path prefix to match on the HTTP request path. Do not specify `pathPrefix` if `pathExact` or `pathRegex` are configured in the same `http` configuration. | string | none | +| `pathRegex` | Defines a regular expression to match on the HTTP request path. Do not specify `pathRegex` if `pathExact` or `pathPrefix` are configured in the same `http` 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 | none | +| `methods` | Specifies a list of HTTP methods. Consul applies the permission if a request matches the `pathExact`, `pathPrefix`, `pathRegex`, or `header`, and the source sent the request using one of the specified methods. Refer to the [Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for a list of supported request headers. | list | All request methods | +| `header` | Specifies a header name and matching criteria for HTTP request headers. Refer to [`spec.sources[].permissions[].http[].header`](#spec-sources-permissions-http-header) for details. | list of maps | none | + +### `spec.sources[].permissions[].http[].header` + +Specifies a set of criteria for matching HTTP request headers. The request header must match all specified criteria for the permission to apply. + +#### Values + +- 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: + +| 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 | +| `invert` | Inverts the matching logic configured in the `header`. Default is `false`. | boolean | optional | + +### `spec.sources[].type` + +Specifies the type of destination service that the configuration entry applies to. The only value supported is `consul`. + +#### Values + +- Default: `consul` +- Data type: String + +### `spec.sources[].description` + +Specifies a description of the intention. Consul presents the description in API responses to assist other tools integrated into the network. + +#### Values + +- Default: None +- Data type: String + + + + + + +## Examples + +The following examples demonstrate potential use-cases for the service intentions configuration entry. + +### L4 Intentions for specific sources and destinations + +The following example configuration entry specifies an L4 intention that denies traffic from `web` to `db` service instances, but allows traffic from `api` to `db`. + + + +```hcl +Kind = "service-intentions" +Name = "db" +Sources = [ + { + Name = "web" + Action = "deny" + }, + { + Name = "api" + Action = "allow" + } +] +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +spec: + destination: + name: db + sources: + - name: web + action: deny + - name: api + action: allow +``` + +```json +{ + "Kind": "service-intentions", + "Name": "db", + "Sources": [ + { + "Action": "deny", + "Name": "web" + }, + { + "Action": "allow", + "Name": "api" + } + ] +} +``` + + + +### L4 intentions for all destinations + +In the following L4 example, the destination is configured with a `*` wildcard. As a result, traffic from `web` service instances is denied for any service in the datacenter. + + + +```hcl +Kind = "service-intentions" +Name = "*" +Sources = [ + { + Name = "web" + Action = "deny" + } +] +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +spec: + destination: + name: * + sources: + - name: web + action: deny +``` + +```json +{ + "Kind": "service-intentions", + "Name": "*", + "Sources": [ + { + "Action": "deny", + "Name": "web" + } + ] +} +``` + + + +### L4 intentions for all sources + +In the following L4 example, the source is configured with a `*` wildcard. As a result, traffic from any service is denied to `db` service instances. + + + +```hcl +Kind = "service-intentions" +Name = "db" +Sources = [ + { + Name = "*" + Action = "deny" + } +] +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +spec: + destination: + name: db + sources: + - name: * + action: deny +``` + +```json +{ + "Kind": "service-intentions", + "Name": "db", + "Sources": [ + { + "Action": "deny", + "Name": "*" + } + ] +} +``` + + +### REST access In the following example, the `admin-dashboard` and `report-generator` services have different levels of access when making REST calls: @@ -134,8 +1026,8 @@ spec: ### gRPC -In the following use-case, access to the `IssueRefund` gRPC service method is set to `deny`. Because gRPC method calls [are -HTTP/2](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md), an HTTP path-matching rule can be used to control traffic: +In the following example, Consul denies requests from `frontend-web` to the `IssueRefund` gRPC service. +Because gRPC method calls use the [HTTP/2 protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md), you can apply an HTTP path-matching rule to control traffic: @@ -174,8 +1066,8 @@ Sources = [ } ] } - # NOTE: a default catch-all based on the default ACL policy will apply to - # unmatched connections and requests. Typically this will be DENY. + # A default catch-all based on the default ACL policy applies to + # unmatched connections and requests. This is typically DENY. ] ``` @@ -204,8 +1096,8 @@ spec: - action: allow http: pathPrefix: '/mycompany.BillingService/' - # NOTE: a default catch-all based on the default ACL policy will apply to - # unmatched connections and requests. Typically this will be DENY. + # A default catch-all based on the default ACL policy applies to + # unmatched connections and requests. This is typically DENY. ``` ```json @@ -249,7 +1141,7 @@ spec: ### L4 and L7 -You can mix and match L4 and L7 intentions per source: +In the following example, Consul enforces application layer intentions that deny requests to `api` from `hackathon-project` but allow requests from `web`. In the same configuration entry, Consul enforces network layer intentions that allow requests from `nightly-reconciler` that send `POST` requests to the `/v1/reconcile-data` HTTP endpoint: @@ -277,8 +1169,8 @@ Sources = [ } ] }, - # NOTE: a default catch-all based on the default ACL policy will apply to - # unmatched connections and requests. Typically this will be DENY. + # A default catch-all based on the default ACL policy applies to + # unmatched connections and requests. This is typically DENY. ] ``` @@ -301,8 +1193,8 @@ spec: http: pathExact: /v1/reconcile-data methods: ['POST'] - # NOTE: a default catch-all based on the default ACL policy will apply to - # unmatched connections and requests. Typically this will be DENY. + # A default catch-all based on the default ACL policy applies to + # unmatched connections and requests. This is typically DENY. ``` ```json @@ -335,404 +1227,54 @@ spec: ``` -## Available Fields - -', - yaml: false, - }, - { - name: 'Namespace', - type: `string: "default"`, - enterprise: true, - description: - "Specifies the namespaces the config entry will apply to. This may be set to the wildcard character (`*`) to match all services in all namespaces that don't otherwise have intentions defined. Wildcard intentions cannot be used when defining L7 [`Permissions`](/consul/docs/connect/config-entries/service-intentions#permissions).", - yaml: false, - }, - { - name: 'Partition', - type: `string: "default"`, - enterprise: true, - description: - "Specifies the admin partition on which the configuration entry will apply. Wildcard characters (`*`) are not supported. Admin partitions must specified explicitly.", - yaml: false, - }, - { - name: 'Meta', - type: 'map: nil', - description: 'Specifies arbitrary KV metadata pairs.', - yaml: false, - }, - { - name: 'metadata', - children: [ - { - name: 'name', - description: - 'Unlike other config entries, the `metadata.name` field is not used to set the name of the service being configured. Instead, that is set in `spec.destination.name`. Thus this name can be set to anything. See [ServiceIntentions Special Case (OSS)](/consul/docs/k8s/crds#serviceintentions-special-case) or [ServiceIntentions Special Case (Enterprise)](/consul/docs/k8s/crds#serviceintentions-special-case-enterprise) for more details.', - }, - { - name: 'namespace', - description: - 'If running Consul Open Source, the namespace is ignored (see [Kubernetes Namespaces in Consul OSS](/consul/docs/k8s/crds#consul-oss)). If running Consul Enterprise see [Kubernetes Namespaces in Consul Enterprise](/consul/docs/k8s/crds#consul-enterprise) for more details.', - }, - ], - hcl: false, - }, - { - name: 'destination', - hcl: false, - children: [ - { - name: 'name', - hcl: false, - type: 'string: ', - description: - "The name of the destination service for all intentions defined in this config entry. This may be set to the wildcard character (`*`) to match all services that don't otherwise have intentions defined. Wildcard intentions cannot be used when defining L7 [`Permissions`](/consul/docs/connect/config-entries/service-intentions#permissions).", - }, - { - name: 'namespace', - hcl: false, - enterprise: true, - type: 'string: ', - description: - "Specifies the namespaces the config entry will apply to. This may be set to the wildcard character (`*`) to match all services in all namespaces that don't otherwise have intentions defined. If not set, the namespace used will depend on the `connectInject.consulNamespaces` configuration. See [ServiceIntentions Special Case (Enterprise)](/consul/docs/k8s/crds#serviceintentions-special-case-enterprise) for more details. Wildcard intentions cannot be used when defining L7 [`Permissions`](/consul/docs/connect/config-entries/service-intentions#permissions).", - }, - ], - }, - { - name: 'Sources', - type: 'array', - description: `The list of all [intention sources and the authorization granted to those sources](#sourceintention). The order of - this list does not matter, but out of convenience Consul will always store - this reverse sorted by intention precedence, as that is the order that they - will be evaluated at enforcement time.`, - }, - ]} -/> +### Cluster peering -### `SourceIntention` +When using cluster peering connections, intentions secure your deployments with authorized service-to-service communication between remote datacenters. In the following example, the service intentions configuration entry authorizes the `backend-service` to communicate with the `frontend-service` that is hosted on remote peer `cluster-02`: -', - description: { - hcl: - "The source of the intention. For a `Type` of `consul` this is the name of a Consul service. The service doesn't need to be registered.", - yaml: - "The source of the intention. For a `type` of `consul` this is the name of a Consul service. The service doesn't need to be registered.", - }, - }, - { - name: 'Peer', - type: 'string: ""', - description: { - hcl: - "Specifies the [peer](/consul/docs/connect/cluster-peering/index.mdx) of the source service. `Peer` is mutually exclusive with `Partition`.", - yaml: - "Specifies the [peer](/consul/docs/connect/cluster-peering/index.mdx) of the source service. `peer` is mutually exclusive with `partition`.", - }, - }, - { - name: 'Namespace', - enterprise: true, - type: 'string: ""', - description: { - hcl: - "The namespace of the source service. If `Peer` is empty, `Namespace` defaults to the namespace of the destination service (i.e. the config entry's namespace).", - yaml: - 'The namespace of the source service. If `peer` is empty, `namespace` defaults to the namespace of the destination service (i.e. `spec.destination.namespace`).', - }, - }, - { - name: 'Partition', - enterprise: true, - type: 'string: ""', - description: { - hcl: - "Specifies the admin partition of the source service. If `Peer` is empty, `Partition` defaults to the destination service's partition (i.e. the configuration entry's partition). `Partition` is mutually exclusive with `Peer`.", - yaml: - "Specifies the admin partition of the source service. If `peer` is empty, `partition` defaults to the destination service's partition (i.e. `spec.destination.partition`). `partition` is mutually exclusive with `peer`.", - }, - }, - { - name: 'Action', - type: 'string: ""', - description: { - hcl: - 'For an L4 intention this is required, and should be set to one of `"allow"` or `"deny"` for the action that should be taken if this intention matches a request.' + - '

    This should be omitted for an L7 intention as it is mutually exclusive with the `Permissions` field.', - yaml: - 'For an L4 intention this is required, and should be set to one of `"allow"` or `"deny"` for the action that should be taken if this intention matches a request.' + - '

    This should be omitted for an L7 intention as it is mutually exclusive with the `permissions` field.', - }, - }, - { - name: 'Permissions', - type: 'array', - description: { - hcl: `The list of all [additional L7 attributes](#intentionpermission) that extend the intention match criteria.

    - Permission precedence is applied top to bottom. For any given request the - first permission to match in the list is terminal and stops further - evaluation. As with L4 intentions, traffic that fails to match any of the - provided permissions in this intention will be subject to the default - intention behavior is defined by the default [ACL policy](/consul/docs/agent/config/config-files#acl_default_policy).

    - This should be omitted for an L4 intention as it is mutually exclusive with - the \`Action\` field.

    - Setting \`Permissions\` is not valid if a wildcard is used for the \`Name\` or \`Namespace\` because they can only be - applied to services with a compatible protocol.`, - yaml: `The list of all [additional L7 attributes](#intentionpermission) that extend the intention match criteria.

    - Permission precedence is applied top to bottom. For any given request the - first permission to match in the list is terminal and stops further - evaluation. As with L4 intentions, traffic that fails to match any of the - provided permissions in this intention will be subject to the default - intention behavior is defined by the default [ACL policy](/consul/docs/agent/config/config-files#acl_default_policy).

    - This should be omitted for an L4 intention as it is mutually exclusive with - the \`action\` field.

    - Setting \`permissions\` is not valid if a wildcard is used for the \`spec.destination.name\` or \`spec.destination.namespace\` - because they can only be applied to services with a compatible protocol.`, - }, - }, - { - name: 'Precedence', - type: 'int: ', - description: - 'An [integer precedence value](/consul/docs/connect/intentions#precedence-and-match-order) computed from the source and destination naming components.', - yaml: false, - }, - { - name: 'Type', - type: 'string: "consul"', - description: { - hcl: - 'The type for the `Name` value. This can be only "consul" today to represent a Consul service. If not provided, this will be defaulted to "consul".', - yaml: - 'The type for the `name` value. This can be only "consul" today to represent a Consul service. If not provided, this will be defaulted to "consul".', - }, - }, - { - name: 'Description', - type: 'string: ""', - description: - 'Description for the intention. This is not used by Consul, but is presented in API responses to assist tooling.', - }, - { - name: 'LegacyID', - type: 'string: ', - description: `This is the UUID to uniquely identify - this intention in the system. Cannot be set directly and is exposed here as - an artifact of the config entry migration and is primarily used to allow - legacy intention [API](/consul/api-docs/connect/intentions#update-intention-by-id) - [endpoints](/consul/api-docs/connect/intentions#read-specific-intention-by-id) to - continue to function for a period of time after [upgrading to 1.9.0](/consul/docs/upgrading/upgrade-specific#consul-1-9-0).`, - yaml: false, - }, - { - name: 'LegacyMeta', - type: 'map: ', - description: `Specified arbitrary KV - metadata pairs attached to the intention, rather than to the enclosing config - entry. Cannot be set directly and is exposed here as an artifact of the - config entry migration and is primarily used to allow legacy intention - [API](/consul/api-docs/connect/intentions#update-intention-by-id) - [endpoints](/consul/api-docs/connect/intentions#read-specific-intention-by-id) to - continue to function for a period of time after [upgrading to 1.9.0](/consul/docs/upgrading/upgrade-specific#consul-1-9-0).`, - yaml: false, - }, - { - name: 'LegacyCreateTime', - type: 'time: optional', - description: `The timestamp that this intention was - created. Cannot be set directly and is exposed here as an artifact of the - config entry migration and is primarily used to allow legacy intention - [API](/consul/api-docs/connect/intentions#update-intention-by-id) - [endpoints](/consul/api-docs/connect/intentions#read-specific-intention-by-id) to - continue to function for a period of time after [upgrading to 1.9.0](/consul/docs/upgrading/upgrade-specific#consul-1-9-0).`, - yaml: false, - }, - { - name: 'LegacyUpdateTime', - type: 'time: optional', - description: `The timestamp that this intention was - last updated. Cannot be set directly and is exposed here as an artifact of the - config entry migration and is primarily used to allow legacy intention - [API](/consul/api-docs/connect/intentions#update-intention-by-id) - [endpoints](/consul/api-docs/connect/intentions#read-specific-intention-by-id) to - continue to function for a period of time after [upgrading to 1.9.0](/consul/docs/upgrading/upgrade-specific#consul-1-9-0).`, - yaml: false, - }, - ]} -/> + -### `IntentionPermission` + ```hcl + Kind = "service-intentions" + Name = "backend-service" -', - description: - 'This is one of "allow" or "deny" for the action that should be taken if this permission matches a request.', - }, - { - name: 'HTTP', - type: 'IntentionHTTPPermission: ', - description: - 'A set of [HTTP-specific authorization criteria](#intentionhttppermission)', - }, - ]} -/> + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } + ] + ``` -### `IntentionHTTPPermission` + ```yaml + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceIntentions + metadata: + name: backend-deny + spec: + destination: + name: backend + sources: + - name: "*" + action: deny + - name: frontend + action: allow + peer: cluster-01 ## The peer of the source service + ``` -
    At most only one of `PathExact`, `PathPrefix`, or `PathRegex` may be configured.', - yaml: - 'Exact path to match on the HTTP request path.

    At most only one of `pathExact`, `pathPrefix`, or `pathRegex` may be configured.', - }, - }, - { - name: 'PathPrefix', - type: 'string: ""', - description: { - hcl: - 'Path prefix to match on the HTTP request path.

    At most only one of `PathExact`, `PathPrefix`, or `PathRegex` may be configured.', - yaml: - 'Path prefix to match on the HTTP request path.

    At most only one of `pathExact`, `pathPrefix`, or `pathRegex` may be configured.', - }, - }, - { - name: 'PathRegex', - type: 'string: ""', - description: { - hcl: - 'Regular expression to match on the HTTP request path.

    The syntax is [described below](#regular-expression-syntax).

    At most only one of `PathExact`, `PathPrefix`, or `PathRegex` may be configured.', - yaml: - 'Regular expression to match on the HTTP request path.

    The syntax is [described below](#regular-expression-syntax).

    At most only one of `pathExact`, `pathPrefix`, or `pathRegex` may be configured.', - }, - }, - { - name: 'Methods', - type: 'array', - description: - 'A list of HTTP methods for which this match applies. If unspecified all HTTP methods are matched. If provided the names must be a valid [method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).', - }, + ```json + { + "Kind": "service-intentions", + "Name": "backend-service", + "Sources": [ { - name: 'Header', - type: 'array', - description: - 'A set of criteria that can match on HTTP request headers. If more than one is configured all must match for the overall match to apply.', - children: [ - { - name: 'Name', - type: 'string: ', - description: 'Name of the header to match', - }, - { - name: 'Present', - type: 'bool: false', - description: { - hcl: - 'Match if the header with the given name is present with any value.

    At most only one of `Exact`, `Prefix`, `Suffix`, `Regex`, or `Present` may be configured.', - yaml: - 'Match if the header with the given name is present with any value.

    At most only one of `exact`, `prefix`, `suffix`, `regex`, or `present` may be configured.', - }, - }, - { - name: 'Exact', - type: 'string: ""', - description: { - hcl: - 'Match if the header with the given name is this value.

    At most only one of `Exact`, `Prefix`, `Suffix`, `Regex`, or `Present` may be configured.', - yaml: - 'Match if the header with the given name is this value.

    At most only one of `exact`, `prefix`, `suffix`, `regex`, or `present` may be configured.', - }, - }, - { - name: 'Prefix', - type: 'string: ""', - description: { - hcl: - 'Match if the header with the given name has this prefix.

    At most only one of `Exact`, `Prefix`, `Suffix`, `Regex`, or `Present` may be configured.', - yaml: - 'Match if the header with the given name has this prefix.

    At most only one of `exact`, `prefix`, `suffix`, `regex`, or `present` may be configured.', - }, - }, - { - name: 'Suffix', - type: 'string: ""', - description: { - hcl: - 'Match if the header with the given name has this suffix.

    At most only one of `Exact`, `Prefix`, `Suffix`, `Regex`, or `Present` may be configured.', - yaml: - 'Match if the header with the given name has this suffix.

    At most only one of `exact`, `prefix`, `suffix`, `regex`, or `present` may be configured.', - }, - }, - { - name: 'Regex', - type: 'string: ""', - description: { - hcl: - 'Match if the header with the given name matches this pattern.

    The syntax is [described below](#regular-expression-syntax).

    At most only one of `Exact`, `Prefix`, `Suffix`, `Regex`, or `Present` may be configured.', - yaml: - 'Match if the header with the given name matches this pattern.

    The syntax is [described below](#regular-expression-syntax).

    At most only one of `exact`, `prefix`, `suffix`, `regex`, or `present` may be configured.', - }, - }, - { - name: 'Invert', - type: 'bool: false', - description: 'Inverts the logic of the match', - }, - ], - }, - ]} -/> - -## ACLs - -Configuration entries may be protected by [ACLs](/consul/docs/security/acl). - -Reading a `service-intentions` config entry requires `intentions:read` on the resource. - -Creating, updating, or deleting a `service-intentions` config entry requires -`intentions:write` on the resource. - -Intention ACL rules are specified as part of a `service` rule. See [Intention -Management Permissions](/consul/docs/connect/intentions#intention-management-permissions) for -more details. - -## Regular Expression Syntax - -The actual syntax of the regular expression fields described here is entirely -proxy-specific. + "Name": "frontend-service", + "Peer": "cluster-02", + "Action": "allow" + } + ] + } + ``` -When using [Envoy](/consul/docs/connect/proxies/envoy) as a proxy, the syntax for -these fields is [RE2](https://github.com/google/re2/wiki/Syntax). +
    diff --git a/website/content/docs/connect/config-entries/service-resolver.mdx b/website/content/docs/connect/config-entries/service-resolver.mdx index 9730beb132e..89147a24af3 100644 --- a/website/content/docs/connect/config-entries/service-resolver.mdx +++ b/website/content/docs/connect/config-entries/service-resolver.mdx @@ -440,6 +440,12 @@ spec: description: 'The timeout for establishing new network connections to this service. The default unit of time is `ns`.', }, + { + name: 'RequestTimeout', + type: 'duration: 0s', + description: + 'The timeout for receiving an HTTP response from this service before the connection is terminated. If unspecified or 0, the default of 15s is used. The default unit of time is `ns`.', + }, { name: 'DefaultSubset', type: 'string: ""', diff --git a/website/content/docs/connect/config-entries/service-splitter.mdx b/website/content/docs/connect/config-entries/service-splitter.mdx index 4386fba281b..5d94a4e38a2 100644 --- a/website/content/docs/connect/config-entries/service-splitter.mdx +++ b/website/content/docs/connect/config-entries/service-splitter.mdx @@ -1,54 +1,575 @@ --- layout: docs -page_title: Service Splitter - Configuration Entry Reference -description: >- - The service splitter configuration entry kind defines how to divide service mesh traffic between service instances. Use the reference guide to learn about `""service-splitter""` config entry parameters and how it can be used for traffic management behaviors like canary rollouts, blue green deployment, and load balancing across environments. +page_title: Service Splitter Configuration Entry 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 Entry +# 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. + +## Configuration model + +The following list outlines field hierarchy, language-specific data types, and requirements in a service splitter configuration entry. Click on a property name to view additional details, including default values. + + + + + +- [`Kind`](#kind): string | required +- [`Name`](#name): string | required +- [`Namespace`](#namespace): string +- [`Partition`](#partition): string +- [`Meta`](#meta): map +- [`Splits`](#splits): map | required + - [`Weight`](#splits-weight): number | required + - [`Service`](#splits-service): string | required + - [`ServiceSubset`](#splits-servicesubset): string + - [`Namespace`](#splits-namespace): string + - [`Partition`](#splits-partition): string + - [`RequestHeaders`](#splits-requestheaders): map + - [`Add`](#splits-requestheaders): map + - [`Set`](#splits-requestheaders): map + - [`Remove`](#splits-requestheaders): map + - [`ResponseHeaders`](#splits-responseheaders): map + - [`Add`](#splits-responseheaders): map + - [`Set`](#splits-responseheaders): map + - [`Remove`](#splits-responseheaders): map + + + + + +- [`apiVersion`](#apiversion): string | required +- [`kind`](#kind): string | required +- [`metadata`](#metadata): object | required + - [`name`](#metadata-name): string | required + - [`namespace`](#metadata-namespace): string | optional +- [`spec`](#spec): object | required + - [`splits`](#spec-splits): list | required + - [`weight`](#spec-splits-weight): float32 | required + - [`service`](#spec-splits-service): string | required + - [`serviceSubset`](#spec-splits-servicesubset): string + - [`namespace`](#spec-splits-namespace): string + - [`partition`](#spec-splits-partition): string + - [`requestHeaders`](#spec-splits-requestheaders): HTTPHeaderModifiers + - [`add`](#spec-splits-requestheaders): map + - [`set`](#spec-splits-requestheaders): map + - [`remove`](#spec-splits-requestheaders): map + - [`responseHeaders`](#spec-splits-responseheaders): HTTPHeaderModifiers + - [`add`](#spec-splits-responseheaders): map + - [`set`](#spec-splits-responseheaders): map + - [`remove`](#spec-splits-responseheaders): map + + + + +## Complete configuration + +When every field is defined, a service splitter configuration entry has the following form: + + + + --> **v1.8.4+:** On Kubernetes, the `ServiceSplitter` custom resource is supported in Consul versions 1.8.4+.
    -**v1.6.0+:** On other platforms, this config entry is supported in Consul versions 1.6.0+. +```hcl +Kind = "service-splitter" ## string | required +Name = "config-entry-name" ## string | required +Namespace = "main" ## string +Partition = "partition" ## string +Meta = { ## map + key = "value" +} +Splits = [ ## list | required + { ## map + Weight = 90 ## number | required + Service = "service" ## string + ServiceSubset = "v1" ## string + Namespace = "target-namespace" ## string + Partition = "target-partition" ## string + RequestHeaders = { ## map + Set = { + "X-Web-Version" : "from-v1" + } + } + ResponseHeaders = { ## map + Set = { + "X-Web-Version" : "to-v1" + } + } + }, + { + Weight = 10 + Service = "service" + ServiceSubset = "v2" + Namespace = "target-namespace" + Partition = "target-partition" + RequestHeaders = { + Set = { + "X-Web-Version" : "from-v2" + } + } + ResponseHeaders = { + Set = { + "X-Web-Version" : "to-v2" + } + } + } +] +``` + +
    + + + +```json +{ + "Kind" : "service-splitter", ## string | required + "Name" : "config-entry-name", ## string | required + "Namespace" : "main", ## string + "Partition" : "partition", ## string + "Meta" : { ## map + "_key_" : "_value_" + }, + "Splits" : [ ## list | required + { ## map + "Weight" : 90, ## number | required + "Service" : "service", ## string + "ServiceSubset" : "v1", ## string + "Namespace" : "target-namespace", ## string + "Partition" : "target-partition", ## string + "RequestHeaders" : { ## map + "Set" : { + "X-Web-Version": "from-v1" + } + }, + "ResponseHeaders" : { ## map + "Set" : { + "X-Web-Version": "to-v1" + } + } + }, + { + "Weight" : 10, + "Service" : "service", + "ServiceSubset" : "v2", + "Namespace" : "target-namespace", + "Partition" : "target-partition", + "RequestHeaders" : { + "Set" : { + "X-Web-Version": "from-v2" + } + }, + "ResponseHeaders" : { + "Set" : { + "X-Web-Version": "to-v2" + } + } + } + ] +} +``` + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 # string | required +kind: ServiceSplitter # string | required +metadata: # object | required + name: config-entry-name # string | required + namespace: main # string +spec: + splits: # list + - weight: 90 # floating point | required + service: service # string + serviceSubset: v1 # string + namespace: target-namespace # string + partition: target-partition # string + requestHeaders: + set: + x-web-version: from-v1 # string + responseHeaders: + set: + x-web-version: to-v1 # string + - weight: 10 + service: service + serviceSubset: v2 + namespace: target-namespace + partition: target-partition + requestHeaders: + set: + x-web-version: from-v2 + responseHeaders: + set: + x-web-version: to-v2 +``` + + + +
    + +## Specification + +This section provides details about the fields you can configure in the service splitter configuration entry. + + + + + +### `Kind` + +Specifies the type of configuration entry to implement. + +#### Values + +- Default: none +- This field is required. +- Data type: String value that must be set to `service-splitter`. + +### `Name` + +Specifies a name for the configuration entry. The name is metadata that you can use to reference the configuration entry when performing Consul operations, such as applying a configuration entry to a specific cluster. + +#### Values + +- Default: Defaults to the name of the node after writing the entry to the Consul server. +- This field is required. +- Data type: String + + +### `Namespace` + +Specifies the [namespace](/consul/docs/enterprise/namespaces) to apply the configuration entry. + +#### Values + +- Default: None +- Data type: String + +### `Partition` + +Specifies the [admin partition](/consul/docs/enterprise/admin-partitions) to apply the configuration entry. + +#### Values + +- Default: `Default` +- Data type: String + +### `Meta` + +Specifies key-value pairs to add to the KV store. + +#### Values + +- Default: none +- Data type: Map of one or more key-value pairs + - keys: String + - values: String, integer, or float + +### `Splits` + +Defines how much traffic to send to sets of service instances during a traffic split. + +#### Values + +- Default: None +- This field is required. +- Data type: list of objects that can contain the following fields: + - `Weight`: The sum of weights for a set of service instances must add up to 100. + - `Service`: This field is required. + - `ServiceSubset` + - `Namespace` + - `Partition` + - `RequestHeaders` + - `ResponseHeaders` + +### `Splits[].Weight` + +Specifies the percentage of traffic sent to the set of service instances specified in the [`Service`](#service) field. Each weight must be a floating integer between `0` and `100`. The smallest representable value is `.01`. The sum of weights across all splits must add up to `100`. + +#### Values + +- Default: `null` +- This field is required. +- Data type: Floating number from `.01` to `100`. + +### `Splits[].Service` + +Specifies the name of the service to resolve. + +#### Values + +- Default: Inherits the value of the [`Name`](#name) field. +- Data type: String + +### `Splits[].ServiceSubset` + +Specifies a subset of the service to resolve. A service subset assigns a name to a specific subset of discoverable service instances within a datacenter, such as `version2` or `canary`. All services have an unnamed default subset that returns all healthy instances. + +You can define service subsets in a [service resolver configuration entry](/consul/docs/connect/config-entries/service-resolver), which are referenced by their names throughout the other configuration entries. This field overrides the default subset value in the service resolver configuration entry. + +#### Values + +- Default: If empty, the `split` uses the default subset. +- Data type: String + +### `Splits[].Namespace` + +Specifies the [namespace](/consul/docs/enterprise/namespaces) to use in the FQDN when resolving the service. + +#### Values + +- Default: Inherits the value of [`Namespace`](#Namespace) from the top-level of the configuration entry. +- Data type: String + +### `Splits[].Partition` + +Specifies the [admin partition](/consul/docs/enterprise/admin-partitions) to use in the FQDN when resolving the service. + +#### Values + +- Default: By default, the `service-splitter` uses the [admin partition specified in the top-level configuration entry](#partition). +- Data type: String + +### `Splits[].RequestHeaders` + +Specifies a set of HTTP-specific header modification rules applied to requests routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`. Refer to [Set HTTP Headers](#set-http-headers) for an example configuration. + +#### 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 an 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%` in your configuration entry allows you to pass a value that is generated when the split occurs. + + +### `Splits[].ResponseHeaders` + +Specifies a set of HTTP-specific header modification rules applied to responses routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`. Refer to [Set HTTP Headers](#set-http-headers) for an example configuration. + +#### Values + +- Default: None +- Values: Object containing one or more fields that define header modification rules + - `Add`: Map of one or more string key-value pairs + - `Set`: Map of one or more string key-value pairs + - `Remove`: Map of one or more string key-value pairs + +The following table describes how to configure values for response 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 an 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%` in your configuration entry allows you to pass a value that is generated when the split occurs. + + + + + +### `apiVersion` + +Kubernetes-only parameter that specifies the version of the Consul API that the configuration entry maps to Kubernetes configurations. The value must be `consul.hashicorp.com/v1alpha1`. + +### `kind` + +Specifies the type of configuration entry to implement. + +#### Values + +- Default: none +- This field is required. +- Data type: String value that must be set to `serviceSplitter`. + +### `metadata.name` + +Specifies a name for the configuration entry. The name is metadata that you can use to reference the configuration entry when performing Consul operations, such as applying a configuration entry to a specific cluster. + +#### Values + +- Default: Inherits name from the host node +- This field is required. +- Data type: String + + +### `metadata.namespace` + +Specifies the Consul namespace to use for resolving the service. You can map Consul namespaces to Kubernetes Namespaces in different ways. Refer to [Custom Resource Definitions (CRDs) for Consul on Kubernetes](/consul/docs/k8s/crds#consul-enterprise) for additional information. + +#### Values + +- Default: None +- Data type: String + +### `spec` + +Kubernetes-only field that contains all of the configurations for service splitter pods. + +#### Values + +- Default: none +- This field is required. +- Data type: Object containing [`spec.splits`](#spec-splits) configuration + +### `spec.meta` + +Specifies key-value pairs to add to the KV store. + +#### Values + +- Default: none +- Data type: Map of one or more key-value pairs + - keys: String + - values: String, integer, or float + +### `spec.splits` + +Defines how much traffic to send to sets of service instances during a traffic split. + +#### Values + +- Default: None +- This field is required. +- Data type: list of objects that can contain the following fields: + - `weight`: The sum of weights for a set of service instances. The total defined value must add up to 100. + - `service`: This field is required. + - `serviceSubset` + - `namespace` + - `partition` + - `requestHeaders` + - `responseHeaders` -The `service-splitter` config entry kind (`ServiceSplitter` on Kubernetes) controls how to split incoming Connect -requests across different subsets of a single service (like during staged -canary rollouts), or perhaps across different services (like during a v2 -rewrite or other type of codebase migration). +### `spec.splits[].weight` -If no splitter config is defined for a service it is assumed 100% of traffic -flows to a service with the same name and discovery continues on to the -resolution stage. +Specifies the percentage of traffic sent to the set of service instances specified in the [`spec.splits.service`](#spec-splits-service) field. Each weight must be a floating integer between `0` and `100`. The smallest representable value is `.01`. The sum of weights across all splits must add up to `100`. -## Interaction with other Config Entries +#### Values -- Service splitter config entries are a component of [L7 Traffic - Management](/consul/docs/connect/l7-traffic). +- Default: `null` +- This field is required. +- Data type: Floating integer from `.01` to `100` -- Service splitter config entries are restricted to only services that define - their protocol as http-based via a corresponding - [`service-defaults`](/consul/docs/connect/config-entries/service-defaults) config - entry or globally via - [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults) . +### `spec.splits[].service` -- Any split destination that specifies a different `Service` field and omits - the `ServiceSubset` field is eligible for further splitting should a splitter - be configured for that other service, otherwise resolution proceeds according - to any configured - [`service-resolver`](/consul/docs/connect/config-entries/service-resolver). +Specifies the name of the service to resolve. -## UI +#### Values -Once a `service-splitter` is successfully entered, you can view it in the UI. Service routers, service splitters, and service resolvers can all be viewed by clicking on your service then switching to the _routing_ tab. +- Default: The service matching the configuration entry [`meta.name`](#metadata-name) field. +- Data type: String -![screenshot of service splitter in the UI](/img/l7-routing/Splitter.png) +### `spec.splits[].serviceSubset` -## Sample Config Entries +Specifies a subset of the service to resolve. This field overrides the `DefaultSubset`. + +#### Values + +- Default: Inherits the name of the default subset. +- Data type: String + +### `spec.splits[].namespace` + +Specifies the [namespace](/consul/docs/enterprise/namespaces) to use when resolving the service. + +#### Values + +- Default: The namespace specified in the top-level configuration entry. +- Data type: String + +### `spec.splits[].partition` + +Specifies which [admin partition](/consul/docs/enterprise/admin-partitions) to use in the FQDN when resolving the service. + +#### Values + +- Default: `default` +- Data type: String + +### `spec.splits[].requestHeaders` + +Specifies a set of HTTP-specific header modification rules applied to requests routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`. Refer to [Set HTTP Headers](#set-http-headers) for an example configuration. + +#### 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 an 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%` in your configuration entry allows you to pass a value that is generated when the split occurs. + +### `spec.splits[].responseHeaders` + +Specifies a set of HTTP-specific header modification rules applied to responses routed with the service split. You cannot configure request headers if the listener protocol is set to `tcp`. Refer to [Set HTTP Headers](#set-http-headers) for an example configuration. + +#### Values + +- Default: None +- Values: Object containing one or more fields that define header modification rules + - `add`: Map of one or more string key-value pairs + - `set`: Map of one or more string key-value pairs + - `remove`: Map of one or more string key-value pairs + +The following table describes how to configure values for response 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 an 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%` in your configuration entry allows you to pass a value that is generated when the split occurs. + + + + + +## Examples + +The following examples demonstrate common service splitter configuration patterns for specific use cases. ### Two subsets of same service Split traffic between two subsets of the same service: - + + + ```hcl Kind = "service-splitter" @@ -65,18 +586,9 @@ Splits = [ ] ``` -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceSplitter -metadata: - name: web -spec: - splits: - - weight: 90 - serviceSubset: v1 - - weight: 10 - serviceSubset: v2 -``` + + + ```json { @@ -95,13 +607,34 @@ spec: } ``` - +
    + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceSplitter +metadata: + name: web +spec: + splits: + - weight: 90 + serviceSubset: v1 + - weight: 10 + serviceSubset: v2 +``` + + + +
    ### Two different services Split traffic between two services: - + + + ```hcl Kind = "service-splitter" @@ -118,18 +651,9 @@ Splits = [ ] ``` -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceSplitter -metadata: - name: web -spec: - splits: - - weight: 50 - # will default to service with same name as config entry ("web") - - weight: 50 - service: web-rewrite -``` + + + ```json { @@ -147,14 +671,35 @@ spec: } ``` - +
    + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceSplitter +metadata: + name: web +spec: + splits: + - weight: 50 + # defaults to the service with same name as the configuration entry ("web") + - weight: 50 + service: web-rewrite +``` + + + + ### Set HTTP Headers Split traffic between two subsets with extra headers added so clients can tell which version: - + + + ```hcl Kind = "service-splitter" @@ -181,24 +726,9 @@ Splits = [ ] ``` -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceSplitter -metadata: - name: web -spec: - splits: - - weight: 90 - serviceSubset: v1 - responseHeaders: - set: - x-web-version: v1 - - weight: 10 - serviceSubset: v2 - responseHeaders: - set: - x-web-version: v2 -``` + + + ```json { @@ -227,136 +757,31 @@ spec: } ``` - + -## Available Fields -', - yaml: false, - }, - { - name: 'Namespace', - type: `string: "default"`, - enterprise: true, - description: - 'Specifies the namespace to which the configuration entry will apply.', - yaml: false, - }, - { - name: 'Partition', - type: `string: "default"`, - enterprise: true, - description: - 'Specifies the admin partition to which the configuration entry will apply.', - yaml: false, - }, - { - name: 'Meta', - type: 'map: nil', - description: - 'Specifies arbitrary KV metadata pairs. Added in Consul 1.8.4.', - yaml: false, - }, - { - name: 'metadata', - children: [ - { - name: 'name', - description: 'Set to the name of the service being configured.', - }, - { - name: 'namespace', - description: - 'If running Consul Open Source, the namespace is ignored (see [Kubernetes Namespaces in Consul OSS](/consul/docs/k8s/crds#consul-oss)). If running Consul Enterprise see [Kubernetes Namespaces in Consul Enterprise](/consul/docs/k8s/crds#consul-enterprise) for more details.', - }, - ], - hcl: false, - }, - { - name: 'Splits', - type: 'array', - description: - 'Defines how much traffic to send to which set of service instances during a traffic split. The sum of weights across all splits must add up to 100.', - children: [ - { - name: 'weight', - type: 'float32: 0', - description: - 'A value between 0 and 100 reflecting what portion of traffic should be directed to this split. The smallest representable weight is 1/10000 or .01%', - }, - { - name: 'Service', - type: 'string: ""', - description: 'The service to resolve instead of the default.', - }, - { - name: 'ServiceSubset', - type: 'string: ""', - description: { - hcl: - "A named subset of the given service to resolve instead of one defined as that service's `DefaultSubset`. If empty the default subset is used.", - yaml: - "A named subset of the given service to resolve instead of one defined as that service's `defaultSubset`. If empty the default subset is used.", - }, - }, - { - name: 'Namespace', - enterprise: true, - type: 'string: ""', - description: - 'The namespace to resolve the service from instead of the current namespace. If empty, the current namespace is used.', - }, - { - name: 'Partition', - enterprise: true, - type: 'string: ""', - description: - 'The admin partition to resolve the service from instead of the current partition. If empty, the current partition is used.', - }, - { - name: 'RequestHeaders', - type: 'HTTPHeaderModifiers: ', - description: `A set of [HTTP-specific header modification rules](/consul/docs/connect/config-entries/service-router#httpheadermodifiers) - that will be applied to requests routed to this split. - This cannot be used with a \`tcp\` listener.`, - }, - { - name: 'ResponseHeaders', - type: 'HTTPHeaderModifiers: ', - description: `A set of [HTTP-specific header modification rules](/consul/docs/connect/config-entries/service-router#httpheadermodifiers) - that will be applied to responses from this split. - This cannot be used with a \`tcp\` listener.`, - }, - ], - }, - ]} -/> -## ACLs + -Configuration entries may be protected by [ACLs](/consul/docs/security/acl). - -Reading a `service-splitter` config entry requires `service:read` on the resource. +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceSplitter +metadata: + name: web +spec: + splits: + - weight: 90 + serviceSubset: v1 + responseHeaders: + set: + x-web-version: v1 + - weight: 10 + serviceSubset: v2 + responseHeaders: + set: + x-web-version: v2 +``` -Creating, updating, or deleting a `service-splitter` config entry requires -`service:write` on the resource and `service:read` on any other service referenced by -name in these fields: + -- [`Splits[].Service`](#service) + diff --git a/website/content/docs/connect/configuration.mdx b/website/content/docs/connect/configuration.mdx index 8fbd88fa325..e3369468859 100644 --- a/website/content/docs/connect/configuration.mdx +++ b/website/content/docs/connect/configuration.mdx @@ -9,14 +9,11 @@ description: >- There are many configuration options exposed for Consul service mesh. The only option that must be set is the `connect.enabled` option on Consul servers to enable Consul service mesh. -All other configurations are optional and have reasonable defaults. +All other configurations are optional and have defaults suitable for many environments. -Consul Connect is the component shipped with Consul that enables service mesh functionality. The terms _Consul Connect_ and _Consul service mesh_ are used interchangeably throughout this documentation. +The terms _Consul Connect_ and _Consul service mesh_ are used interchangeably throughout this documentation. --> **Tip:** Service mesh is enabled by default when running Consul in -dev mode with `consul agent -dev`. - -## Agent Configuration +## Agent configuration Begin by enabling Connect for your Consul cluster. By default, Connect is disabled. Enabling Connect requires changing @@ -75,18 +72,12 @@ automatically ensure complete security. Please read the [Connect production tutorial](/consul/tutorials/developer-mesh/service-mesh-production-checklist) to understand the additional steps needed for a secure deployment. -## Centralized Proxy and Service Configuration - -To account for common Connect use cases where you have many instances of the -same service, and many colocated sidecar proxies, Consul allows you to customize -the settings for all of your proxies or all the instances of a given service at -once using [Configuration Entries](/consul/docs/agent/config-entries). +## Centralized proxy and service configuration -You can override centralized configurations for individual proxy instances in -their +If your network contains many instances of the same service and many colocated sidecar proxies, you can specify global settings for proxies or services in [Configuration Entries](/consul/docs/agent/config-entries). You can override the centralized configurations for individual proxy instances in their [sidecar service definitions](/consul/docs/connect/registration/sidecar-service), and the default protocols for service instances in their [service -registrations](/consul/docs/discovery/services). +definitions](/consul/docs/services/usage/define-services). ## Schedulers diff --git a/website/content/docs/connect/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx index b4661d86123..ab59a5ba60c 100644 --- a/website/content/docs/connect/dataplane/consul-dataplane.mdx +++ b/website/content/docs/connect/dataplane/consul-dataplane.mdx @@ -7,7 +7,7 @@ description: >- # Consul Dataplane CLI Reference -The `consul-dataplane` command interacts with the binary for [simplified service mesh with Consul Dataplane](/consul/docs/k8s/dataplane). Use this command to install Consul Dataplane, configure its Envoy proxies, and secure Dataplane deployments. +The `consul-dataplane` command interacts with the binary for [simplified service mesh with Consul Dataplane](/consul/docs/connect/dataplane). Use this command to install Consul Dataplane, configure its Envoy proxies, and secure Dataplane deployments. ## Usage diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/api-gateway.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/api-gateway.mdx new file mode 100644 index 00000000000..11ad78f2d27 --- /dev/null +++ b/website/content/docs/connect/gateways/api-gateway/configuration/api-gateway.mdx @@ -0,0 +1,331 @@ +--- +layout: docs +page_title: API Gateway Configuration Entry Reference +description: Learn how to configure a Consul API Gateway on VMs. +--- + +# API gateway configuration entry 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/api-gateway/configuration/gateway). + +## Introduction + +A gateway is a type of network infrastructure that determines how service traffic should be handled. Gateways contain one or more listeners that bind to a set of hosts and ports. An HTTP Route or TCP Route can then attach to a gateway listener to direct traffic from the gateway to a service. + +## Configuration model + +The following list outlines field hierarchy, language-specific data types, and requirements in an `api-gateway` configuration entry. Click on a property name to view additional details, including default values. + +- [`Kind`](#kind): string | must be `"api-gateway"` +- [`Name`](#name): string | no default +- [`Namespace`](#namespace): string | no default +- [`Partition`](#partition): string | no default +- [`Meta`](#meta): map | no default +- [`Listeners`](#listeners): list of objects | no default + - [`Name`](#listeners-name): string | no default + - [`Port`](#listeners-port): number | no default + - [`Hostname`](#listeners-hostname): string | `"*"` + - [`Protocol`](#listeners-protocol): string | `"tcp"` + - [`TLS`](#listeners-tls): map | none + - [`MinVersion`](#listeners-tls-minversion): string | no default + - [`MaxVersion`](#listeners-tls-maxversion): string | no default + - [`CipherSuites`](#listeners-tls-ciphersuites): list of strings | Envoy default cipher suites + - [`Certificates`](#listeners-tls-certificates): list of objects | no default + - [`Kind`](#listeners-tls-certificates-kind): string | must be `"inline-certificate"` + - [`Name`](#listeners-tls-certificates-name): string | no default + - [`Namespace`](#listeners-tls-certificates-namespace): string | no default + - [`Partition`](#listeners-tls-certificates-partition): string | no default + +## Complete configuration + +When every field is defined, an `api-gateway` configuration entry has the following form: + + + +```hcl +Kind = "api-gateway" +Name = "" +Namespace = "" +Partition = "" + +Meta = { + = "" +} + +Listeners = [ + { + Port = + Name = "" + Protocol = "" + TLS = { + MaxVersion = "" + MinVersion = "" + CipherSuites = [ + "" + ] + Certificates = [ + { + Kind = "inline-certificate" + Name = "" + Namespace = "" + Partition = "" + } + ] + } + } +] +``` + +```json +{ + "Kind": "api-gateway", + "Name": "", + "Namespace": "", + "Partition": "", + "Meta": { + "": "" + }, + "Listeners": [ + { + "Name": "", + "Port": , + "Protocol": "", + "TLS": { + "MaxVersion": "", + "MinVersion": "", + "CipherSuites": [ + "" + ], + "Certificates": [ + { + "Kind": "inline-certificate", + "Name": "", + "Namespace": "", + "Partition": "" + } + ] + } + } + ] +} +``` + + + +## Specification + +This section provides details about the fields you can configure in the +`api-gateway` configuration entry. + +### `Kind` + +Specifies the type of configuration entry to implement. This must be +`api-gateway`. + +#### Values + +- Default: none +- This field is required. +- Data type: string value that must be set to `"api-gateway"`. + +### `Name` + +Specifies a name for the configuration entry. The name is metadata that you can +use to reference the configuration entry when performing Consul operations, +such as applying a configuration entry to a specific cluster. + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Meta` + +Specifies an arbitrary set of key-value pairs to associate with the gateway. + +#### Values + +- Default: none +- Data type: map containing one or more keys and string values. + +### `Listeners[]` + +Specifies a list of listeners that gateway should set up. Listeners are +uniquely identified by their port number. + +#### Values + +- Default: none +- This field is required. +- Data type: List of maps. Each member of the list contains the following fields: + - [`Name`](#listeners-name) + - [`Port`](#listeners-port) + - [`Hostname`](#listeners-hostname) + - [`Protocol`](#listeners-protocol) + - [`TLS`](#listeners-tls) + +### `Listeners[].Name` + +Specifies the unique name for the listener. This field accepts letters, numbers, and hyphens. + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Listeners[].Port` + +Specifies the port number that the listener receives traffic on. + +#### Values + +- Default: `0` +- This field is required. +- Data type: integer + +### `Listeners[].Hostname` + +Specifies the hostname that the listener receives traffic on. + +#### Values + +- Default: `"*"` +- This field is optional. +- Data type: string + +### `Listeners[].Protocol` + +Specifies the protocol associated with the listener. + +#### Values + +- Default: none +- This field is required. +- The data type is one of the following string values: `"tcp"` or `"http"`. + +### `Listeners[].TLS` + +Specifies the TLS configurations for the listener. + +#### Values + +- Default: none +- Map that contains the following fields: + - [`MaxVersion`](#listeners-tls-maxversion) + - [`MinVersion`](#listeners-tls-minversion) + - [`CipherSuites`](#listeners-tls-ciphersuites) + - [`Certificates`](#listeners-tls-certificates) + +### `Listeners[].TLS.MaxVersion` + +Specifies the maximum TLS version supported for the listener. + +#### Values + +- Default depends on the version of Envoy: + - Envoy 1.22.0 and later default to `TLSv1_2` + - Older versions of Envoy default to `TLSv1_0` +- Data type is one of the following string values: + - `TLS_AUTO` + - `TLSv1_0` + - `TLSv1_1` + - `TLSv1_2` + - `TLSv1_3` + +### `Listeners[].TLS.MinVersion` + +Specifies the minimum TLS version supported for the listener. + +#### Values + +- Default: none +- Data type is one of the following string values: + - `TLS_AUTO` + - `TLSv1_0` + - `TLSv1_1` + - `TLSv1_2` + - `TLSv1_3` + +### `Listeners[].TLS.CipherSuites[]` + +Specifies a list of cipher suites that the listener supports when negotiating connections using TLS 1.2 or older. + +#### Values + +- Defaults to the ciphers supported by the version of Envoy in use. Refer to the + [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/common.proto#envoy-v3-api-field-extensions-transport-sockets-tls-v3-tlsparameters-cipher-suites) + for details. +- Data type: List of string values. Refer to the + [Consul repository](https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169) + for a list of supported ciphers. + +### `Listeners[].TLS.Certificates[]` + +The list of references to inline certificates that the listener uses for TLS termination. + +#### Values + +- Default: None +- Data type: List of maps. Each member of the list has the following fields: + - [`Kind`](#listeners-tls-certificates-kind) + - [`Name`](#listeners-tls-certificates-name) + - [`Namespace`](#listeners-tls-certificates-namespace) + - [`Partition`](#listeners-tls-certificates-partition) + +### `Listeners[].TLS.Certificates[].Kind` + +The list of references to inline-certificates that the listener uses for TLS termination. + +#### Values + +- Default: None +- This field is required and must be set to `"inline-certificate"`. +- Data type: string + +### `Listeners[].TLS.Certificates[].Name` + +The list of references to inline certificates that the listener uses for TLS termination. + +#### Values + +- Default: None +- This field is required. +- Data type: string + +### `Listeners[].TLS.Certificates[].Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) where the certificate can be found. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Listeners[].TLS.Certificates[].Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) where the certificate can be found. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx new file mode 100644 index 00000000000..997e2bbf692 --- /dev/null +++ b/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx @@ -0,0 +1,679 @@ +--- +layout: docs +page_title: HTTP Route Configuration +description: Learn how to configure an HTTP Route bound to an API Gateway on VMs. +--- + +# HTTP route configuration reference + +This topic provides reference information for the gateway routes configuration entry. Refer to [Route Resource Configuration](/consul/docs/api-gateway/configuration/routes) for information about configuring API gateway routes in Kubernetes environments. + +## Configuration model + +The following list outlines field hierarchy, language-specific data types, and +requirements in an `http-route` configuration entry. Click on a property name +to view additional details, including default values. + +- [`Kind`](#kind): string | must be `http-route` +- [`Name`](#name): string | no default +- [`Namespace`](#namespace): string | no default +- [`Partition`](#partition): string | no default +- [`Meta`](#meta): map | no default +- [`Hostnames`](#hostnames): list | no default +- [`Parents`](#parents): list | no default + - [`Kind`](#parents-kind): string | must be `api-gateway` + - [`Name`](#parents-name): string | no default + - [`Namespace`](#parents-namespace): string | no default + - [`Partition`](#parents-partition): string | no default + - [`SectionName`](#parents-sectionname): string | no default +- [`Rules`](#rules): list | no default + - [`Filters`](#rules-filters): map | no default + - [`Headers`](#rules-filters-headers): list | no default + - [`Add`](#rules-filters-headers-add): map | no default + - [`Remove`](#rules-filters-headers-remove): list | no default + - [`Set`](#rules-filters-headers-set): map | no default + - [`URLRewrite`](#rules-filters-urlrewrite): map | no default + - [`Path`](#rules-filters-urlrewrite-path): string | no default + - [`Matches`](#rules-matches): list | no default + - [`Headers`](#rules-matches-headers): list | no default + - [`Match`](#rules-matches-headers-match): string | no default + - [`Name`](#rules-matches-headers-name): string | no default + - [`Value`](#rules-matches-headers-value): string | no default + - [`Method`](#rules-matches-method): string | no default + - [`Path`](#rules-matches-path): map | no default + - [`Match`](#rules-matches-path-match): string | no default + - [`Value`](#rules-matches-path-value): string | no default + - [`Query`](#rules-matches-query): list | no default + - [`Match`](#rules-matches-query-match): string | no default + - [`Name`](#rules-matches-query-name): string | no default + - [`Value`](#rules-matches-query-value): string | no default + - [`Services`](#rules-services): list | no default + - [`Name`](#rules-services-name): string | no default + - [`Namespace`](#rules-services-namespace): string + - [`Partition`](#rules-services-partition): string + - [`Weight`](#rules-services-weight): number | `1` + - [`Filters`](#rules-services-filters): map | no default + - [`Headers`](#rules-services-filters-headers): list | no default + - [`Add`](#rules-services-filters-headers-add): map | no default + - [`Remove`](#rules-services-filters-headers-remove): list | no default + - [`Set`](#rules-services-filters-headers-set): map | no default + - [`URLRewrite`](#rules-services-filters-urlrewrite): map | no default + - [`Path`](#rules-services-filters-urlrewrite-path): string | no default + +## Complete configuration + +When every field is defined, an `http-route` configuration entry has the following form: + + + +```hcl +Kind = "http-route" +Name = "" +Namespace = "" +Partition = "" +Meta = { + "" = "" +} +Hostnames = [""] + +Parents = [ + { + Kind = "api-gateway" + Name = "" + Namespace = "" + Partition = "" + SectionName = "" + } +] + +Rules = [ + { + Filters = { + Headers = [ + { + Add = { + "" = "" + } + Remove = [ + "" + ] + Set = { + "" = "" + } + } + ] + URLRewrite = { + Path = "" + } + } + Matches = [ + { + Headers = [ + { + Match = "" + Name = "" + Value = "" + } + ] + Method = "" + Path = { + Match = "" + Value = "" + } + Query = [ + { + Match = "" + Name = "" + Value = "" + } + ] + } + ] + Services = [ + { + Name = "" + Namespace = "" + Partition = "" + Weight = "" + Filters = { + Headers = [ + { + Add = { + "" = "" + } + Remove = [ + "" + ] + Set = { + "" = "" + } + } + ] + URLRewrite = { + Path = "" + } + } + } + ] + } +] + +``` + +```json +{ + "Kind": "http-route", + "Name": "", + "Namespace": "", + "Partition": "", + "Meta": { + "": "" + }, + "Hostnames": [ + "" + ], + "Parents": [ + { + "Kind": "api-gateway", + "Name": "", + "Namespace": "", + "Partition": "", + "SectionName": "" + } + ], + "Rules": [ + { + "Filters": [ + { + "Headers": [ + { + "Add": [ + { + "": "" + } + ], + "Remove": ["
    "], + "Set": [ + { + "": "" + } + ] + } + ], + "URLRewrite": [ + { + "Path": "" + } + ] + } + ], + "Matches": [ + { + "Headers": [ + { + "Match": "", + "Name": "", + "Value": "" + } + ], + "Method": "", + "Path": [ + { + "Match": "", + "Value": "" + } + ], + "Query": [ + { + "Match": "", + "Name": "", + "Value": "" + } + ] + } + ], + "Services": [ + { + "Name": "", + "Namespace": "", + "Partition": "", + "Weight": "", + "Filters": [ + { + "Headers": [ + { + "Add": [ + { + "" + } + ], + "Remove": ["
    "], + "Set": [ + { + "" + } + ] + } + ], + "URLRewrite": [ + { + "Path": "" + } + ] + } + ] + } + ] + } + ] +} +``` + + + +## Specification + +This section provides details about the fields you can configure in the `http-route` configuration entry. + +### `Kind` + +Specifies the type of configuration entry to implement. For HTTP routes, this must be `http-route`. + +#### Values + +- Default: none +- This field is required. +- Data type: string value that must be set to `"http-route"`. + +### `Name` + +Specifies a name for the configuration entry. The name is metadata that you can +use to reference the configuration entry when performing Consul operations, +such as applying a configuration entry to a specific cluster. + +#### Values + +- Default: Defaults to the name of the node after writing the entry to the Consul server. +- This field is required. +- Data type: string + +### `Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Meta` + +Specifies an arbitrary set of key-value pairs to associate with the route. + +#### Values + +- Default: none +- Data type: map containing one or more keys and string values. + +### `Parents[]` + +Specifies the list of gateways that this route binds to. + +#### Values + +- Default: none +- Data type: List of map. Each member of the list contains the following fields: + - `Kind` + - `Name` + - `Namespace` + - `Partition` + - `SectionName` + +### `Parents[].Kind` + +Specifies the type of resource to bind to. This field is required and must be +set to `"api-gateway"` + +#### Values + +- Default: none +- This field is required. +- Data type: string value set to `"api-gateway"` + +### `Parents[].Name` + +Specifies the name of the api-gateway to bind to. + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Parents[].Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Parents[].Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Parents[].SectionName` + +Specifies the name of the listener to bind to on the `api-gateway`. If left +empty, this route binds to _all listeners_ on the parent gateway. + +#### Values + +- Default: "" +- Data type: string + +### `Rules[]` + +Specifies the list of HTTP-based routing rules that this route uses to construct a route table. + +#### Values + +- Default: +- Data type: List of maps. Each member of the list contains the following fields: + - `Filters` + - `Matches` + - `Services` + +### `Rules[].Filters` + +Specifies the list of HTTP-based filters used to modify a request prior to routing it to the upstream service. + +#### Values + +- Default: none +- Data type: Map that contains the following fields: + - `Headers` + - `UrlRewrite` + +### `Rules[].Filters.Headers[]` + +Defines operations to perform on matching request headers when an incoming request matches the `Rules.Matches` configuration. + +#### Values + +This field contains the following configuration objects: + +| Parameter | Description | Type | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `set` | Configure this field to rewrite the HTTP request header. It specifies the name of an HTTP header to overwrite and the new value to set. Any existing values associated with the header name are overwritten. You can specify the following configurations:
    • `name`: Required string that specifies the name of the HTTP header to set.
    • `value`: Required string that specifies the value of the HTTP header to set.
    | List of maps | +| `add` | Configure this field to append the request header with a new value. It specifies the name of an HTTP header to append and the values to add. You can specify the following configurations:
    • `name`: Required string that specifies the name of the HTTP header to append.
    • `value`: Required string that specifies the value of the HTTP header to add.
    | List of maps | +| `remove` | Configure this field to specify an array of header names to remove from the request header. | List of strings | + +### `Rules[].Filters.URLRewrite` + +Specifies rule for rewriting the URL of incoming requests when an incoming request matches the `Rules.Matches` configuration. + +#### Values + +- Default: none +- This field is a map that contains a `Path` field. + +### Rules[].Filters.URLRewrite.Path + +Specifies a path that determines how Consul API Gateway rewrites a URL path. Refer to [Reroute HTTP requests](/consul/docs/api-gateway/usage/reroute-http-requests) for additional information. + +#### Values + +The following table describes the parameters for `path`: + +| Parameter | Description | Type | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | +| `replacePrefixMatch` | Specifies a value that replaces the path prefix for incoming HTTP requests. The operation only affects the path prefix. The rest of the path is unchanged. | String | +| `type` | Specifies the type of replacement to use for the URL path. You can specify the following values:
    • `ReplacePrefixMatch`: Replaces the matched prefix of the URL path (default).
    | String | + +### `Rules[].Matches[]` + +Specifies the matching criteria used in the routing table. When an incoming +request matches the given HTTPMatch configuration, traffic routes to +services specified in the [`Rules.Services`](#rules-services) field. + +#### Values + +- Default: none +- Data type: List containing maps. Each member of the list contains the following fields: + - `Headers` + - `Method` + - `Path` + - `Query` + +### `Rules[].Matches[].Headers[]` + +Specifies rules for matching incoming request headers. You can specify multiple rules in a list, as well as multiple lists of rules. If all rules in a single list are satisfied, then the route forwards the request to the appropriate service defined in the [`Rules.Services`](#rules-services) configuration. You can create multiple `Header[]` lists to create a range of matching criteria. When at least one list of matching rules are satisfied, the route forwards the request to the appropriate service defined in the [`Rules.Services`](#rules-services) configuration. + +#### Values + +- Default: none +- Data type: List containing maps with the following fields: + - `Match` + - `Name` + - `Value` + +### `Rules.Matches.Headers.Match` + +Specifies type of match for headers: `"exact"`, `"prefix"`, or `"regex"`. + +#### Values + +- Default: none +- Data type: string + +### `Rules.Matches.Headers.Name` + +Specifies the name of the header to match. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Matches.Headers.Value` + +Specifies the value of the header to match. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Matches[].Method` + +Specifies a list of strings that define matches based on HTTP request method. + +#### Values + +Specify one of the following string values: + +- `HEAD` +- `POST` +- `PUT` +- `PATCH` +- `GET` +- `DELETE` +- `OPTIONS` +- `TRACE` +- `CONNECT` + +### `Rules[].Matches[].Path` + +Specifies the HTTP method to match. + +#### Values + +- Default: none +- Data type: map containing the following fields: + - `Match` + - `Value` + +### `Rules[].Matches[].Path.Match` + +Specifies type of match for the path: `"exact"`, `"prefix"`, or `"regex"`. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Matches[].Path.Value` + +Specifies the value of the path to match. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Matches[].Query[]` + +Specifies how a match is completed on a request’s query parameters. + +#### Values + +- Default: none +- Data type: List of map that contains the following fields: + - `Match` + - `Name` + - `Value` + +### `Rules[].Matches[].Query[].Match` + +Specifies type of match for query parameters: `"exact"`, `"prefix"`, or `"regex"`. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Matches[].Query[].Name` + +Specifies the name of the query parameter to match. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Matches[].Query[].Value` + +Specifies the value of the query parameter to match. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Services[]` + +Specifies the service that the API gateway routes incoming requests to when the +requests match the `Rules.Matches` configuration. + +#### Values + +- Default: none +- This field contains a list of maps. Each member of the list contains the following fields: + - `Name` + - `Weight` + - `Filters` + - `Namespace` + - `Partition` + +### `Rules[].Services[].Name` + +Specifies the name of an HTTP-based service to route to. + +#### Values + +- Default: none +- Data type: string + +### `Rules[].Services[].Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Rules[].Services.Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Rules[].Services[].Weight` + +Specifies the proportion of requests forwarded to the specified service. If no weight is specified, or if the specified +weight is set to less than or equal to `0`, the weight is normalized to `1`. The +proportion is determined by dividing the value of the weight by the sum of all +weights in the service list. For non-zero values, there may be some deviation +from the exact proportion depending on the precision an implementation +supports. Weight is not a percentage and the sum of weights does not need to +equal 100. + +#### Values + +- Default: none +- Data type: integer + +### `Rules[].Services[].Filters` + +Specifies the list of HTTP-based filters used to modify a request prior to +routing it to this upstream service. + +#### Values + +- Default: none +- Data type: Map that contains the following fields: + - `Headers` + - `UrlRewrite` + +### `Rules[].Services[].Filters.Headers[]` + +Defines operations to perform on matching request headers. + +#### Values + +This field contains the following configuration objects: + +| Parameter | Description | Type | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `set` | Configure this field to rewrite the HTTP request header. It specifies the name of an HTTP header to overwrite and the new value to set. Any existing values associated with the header name are overwritten. You can specify the following configurations:
    • `name`: Required string that specifies the name of the HTTP header to set.
    • `value`: Required string that specifies the value of the HTTP header to set.
    | List of maps | +| `add` | Configure this field to append the request header with a new value. It specifies the name of an HTTP header to append and the values to add. You can specify the following configurations:
    • `name`: Required string that specifies the name of the HTTP header to append.
    • `value`: Required string that specifies the value of the HTTP header to add.
    | List of maps | +| `remove` | Configure this field to specify an array of header names to remove from the request header. | List of strings | + +### `Rules[].Services[].Filters.URLRewrite` + +Specifies rule for rewriting the URL of incoming requests. + +#### Values + +- Default: none +- This field is a map that contains a `Path` field. diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/inline-certificate.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/inline-certificate.mdx new file mode 100644 index 00000000000..4fca7c54ee6 --- /dev/null +++ b/website/content/docs/connect/gateways/api-gateway/configuration/inline-certificate.mdx @@ -0,0 +1,127 @@ +--- +layout: docs +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/api-gateway/configuration/gateway). + +## Configuration model + +The following list outlines field hierarchy, language-specific data types, and +requirements in an `inline-certificate` configuration entry. Click on a property name +to view additional details, including default values. + +- [`Kind`](#kind): string | must be `"inline-certificate"` +- [`Name`](#name): string | no default +- [`Namespace`](#namespace): string | no default +- [`Partition`](#partition): string | no default +- [`Meta`](#meta): map | no default +- [`Certificate`](#certificate): string | no default +- [`PrivateKey`](#privatekey): string | no default + +## Complete configuration + +When every field is defined, an `inline-certificate` configuration entry has the following form: + + + +```HCL +Kind = "inline-certificate" +Name = "" + +Meta = { + "" = "" +} + +Certificate = "" +PrivateKey = "" +``` + +```JSON +{ + "Kind": "inline-certificate", + "Name": "", + "Meta": { + "any key": "any value" + } + "Certificate": "", + "PrivateKey": "" +} +``` + + + +## Specification + +### `Kind` + +Specifies the type of configuration entry to implement. + +#### Values + +- Default: none +- This field is required. +- Data type: string that must equal `"inline-certificate"` + +### `Name` + +Specifies a name for the configuration entry. The name is metadata that you can +use to reference the configuration entry when performing Consul operations, such +as applying a configuration entry to a specific cluster. + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Meta` + +Specifies an arbitrary set of key-value pairs to associate with the gateway. + +#### Values + +- Default: none +- Data type: map containing one or more keys and string values. + +### `Certificate` + +Specifies the inline public certificate to use for TLS. + +#### Values + +- Default: none +- This field is required. +- Data type: string value of the public certificate + +### `PrivateKey` + +Specifies the inline private key to use for TLS. + +#### Values + +- Default: none +- This field is required. +- Data type: string value of the private key diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/tcp-route.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/tcp-route.mdx new file mode 100644 index 00000000000..23b32662d92 --- /dev/null +++ b/website/content/docs/connect/gateways/api-gateway/configuration/tcp-route.mdx @@ -0,0 +1,256 @@ +--- +layout: docs +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 + +This topic provides reference information for the gateway TCP routes configuration +entry. Refer to [Route Resource Configuration](/consul/docs/api-gateway/configuration/routes) for information +about configuring API gateways in Kubernetes environments. + +## Configuration model + +The following list outlines field hierarchy, language-specific data types, and +requirements in an `tcp-route` configuration entry. Click on a property name to +view additional details, including default values. + +- [`Kind`](#kind): string | must be `"tcp-route"` +- [`Name`](#name): string | no default +- [`Namespace`](#namespace): string | no default +- [`Partition`](#partition): string | no default +- [`Meta`](#meta): map | no default +- [`Services`](#services): list | no default + - [`Name`](#services-name): string | no default + - [`Namespace`](#services-namespace): string | no default + - [`Partition`](#services-partition): string | no default +- [`Parents`](#parents): list | no default + - [`Kind`](#parents-kind): string | must be `"api-gateway"` + - [`Name`](#parents-name): string | no default + - [`Namespace`](#parents-namespace): string | no default + - [`Partition`](#parents-partition): string | no default + - [`SectionName`](#parents-sectionname): string | no default + +## Complete configuration + +When every field is defined, a `tcp-route` configuration entry has the following form: + + + +```HCL +Kind = "tcp-route" +Name = "" +Namespace = "" +Partition = "" + +Meta = { + "" = "" +} + +Services = [ + { + Name = "" + Namespace = "" + Partition = "" + } +] + + +Parents = [ + { + Kind = "api-gateway" + Name = "" + Namespace = "" + Partition = "" + SectionName = "" + } +] +``` + +```JSON +{ + "Kind": "tcp-route", + "Name": "", + "Namespace": "", + "Partition": "", + "Meta": { + "": "" + }, + "Services": [ + { + "Name": "" + "Namespace": "", + "Partition": "", + } + ], + "Parents": [ + { + "Kind": "api-gateway", + "Name": "", + "Namespace": "", + "Partition": "", + "SectionName": "" + } + ] +} +``` + + + +## Specification + +This section provides details about the fields you can configure in the +`tcp-route` configuration entry. + +### `Kind` + +Specifies the type of configuration entry to implement. This must be set to +`"tcp-route"`. + +#### Values + +- Default: none +- This field is required. +- Data type: string value that must be set to`tcp-route`. + +### `Name` + +Specifies a name for the configuration entry. The name is metadata that you can +use to reference the configuration entry when performing Consul operations, +such as applying a configuration entry to a specific cluster. + +#### Values + +- Default: Defaults to the name of the node after writing the entry to the Consul server. +- This field is required. +- Data type: string + +### `Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Meta` + +Specifies an arbitrary set of key-value pairs to associate with the gateway. + +#### Values + +- Default: none +- Data type: map containing one or more keys and string values. + +### `Services` + +Specifies a TCP-based service the API gateway routes incoming requests +to. You can only specify one service. + +#### Values + +- Default: none +- The data type is a list of maps. Each member of the list contains the following fields: + - [`Name`](#services-name) + - [`Namespace`](#services-namespace) + - [`Partition`](#services-partition) + +### `Services.Name` + +Specifies the list of TCP-based services to route to. You can specify a maximum of one service. + +#### Values + +- Default: none +- Data type: string + +### `Services.Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) where the service is located. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Services.Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) where the service is located. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Parents` + +Specifies the list of gateways that the route is bound to. + +#### Values + +- Default: none +- Data type: List of map. Each member of the list contains the following fields: + - [`Kind`](#parents-kind) + - [`Name`](#parents-name) + - [`Namespace`](#parents-namespace) + - [`Partition`](#parents-partition) + - [`SectionName`](#parents-sectionname) + +### `Parents.Kind` + +Specifies the type of resource to bind to. This field is required and must be +set to `"api-gateway"` + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Parents.Name` + +Specifies the name of the API gateway to bind to. + +#### Values + +- Default: none +- This field is required. +- Data type: string + +### `Parents.Namespace` + +Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) where the parent is located. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Parents.Partition` + +Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) where the parent is located. + +#### Values + +- Default: `"default"` in Enterprise +- Data type: string + +### `Parents.SectionName` + +Specifies the name of the listener defined in the [`api-gateway` configuration](/consul/docs/connect/gateways/api-gateway/configuration/api-gateway) that the route binds to. If the field is configured to an empty string, the route binds to all listeners on the parent gateway. + +#### Values + +- Default: `""` +- Data type: string diff --git a/website/content/docs/connect/gateways/api-gateway/index.mdx b/website/content/docs/connect/gateways/api-gateway/index.mdx new file mode 100644 index 00000000000..832fd943b2e --- /dev/null +++ b/website/content/docs/connect/gateways/api-gateway/index.mdx @@ -0,0 +1,28 @@ +--- +layout: docs +page_title: API Gateways Overview +description: API gateways are objects in Consul that enable ingress requests to services in your service mesh. Learn about API gateways for VMs in this overview. +--- + +# API gateway overview + +API gateways enable external network clients to access applications and services +running in a Consul datacenter. This type of network traffic is commonly +called _north-south_ network traffic because it refers to the flow of +data into and out of a specific environment. API gateways can also forward +requests from clients to specific destinations based on path or request +protocol. + +API gateways solve the following primary use cases: + +- **Control access at the point of entry**: Set the protocols of external connection + requests and secure inbound connections with TLS certificates from trusted + providers, such as Verisign and Let's Encrypt. +- **Simplify traffic management**: Load balance requests across services and route + traffic to the appropriate service by matching one or more criteria, such as + hostname, path, header presence or value, and HTTP method. + +Consul supports API +gateways for virtual machines and Kubernetes networks. Refer to the following documentation for next steps: +- [API Gateways on VMs](/consul/docs/connect/gateways/api-gateway/usage) +- [API Gateways for Kubernetes](/consul/docs/api-gateway). diff --git a/website/content/docs/connect/gateways/api-gateway/usage.mdx b/website/content/docs/connect/gateways/api-gateway/usage.mdx new file mode 100644 index 00000000000..a5fea6e8176 --- /dev/null +++ b/website/content/docs/connect/gateways/api-gateway/usage.mdx @@ -0,0 +1,211 @@ +--- +layout: docs +page_title: API Gateways on Virtual Machines +description: Learn how to configure and Consul API gateways and gateway routes on virtual machines so that you can enable ingress requests to services in your service mesh in VM environments. +--- + +# API gateways on virtual machines + +This topic describes how to deploy Consul API gateways to networks that operate +in virtual machine (VM) environments. If you want to implement an API gateway +in a Kubernetes environment, refer to [API Gateway for Kubernetes](/consul/docs/api-gateway). + +## Introduction + +Consul API gateways provide a configurable ingress points for requests into a Consul network. Usethe following configuration entries to set up API gateways: + +- [API gateway](/consul/docs/connect/gateways/api-gateway/configuration/api-gateway): Provides an endpoint for requests to enter the network. Define listeners that expose ports on the endpoint for ingress. +- [HTTP routes](/consul/docs/connect/gateways/api-gateway/configuration/http-route) and [TCP routes](/consul/docs/connect/gateways/api-gateway/configuration/tcp-route): The routes attach to listeners defined in the gateway and control how requests route to services in the network. +- [Inline certificates](/consul/docs/connect/gateways/api-gateway/configuration/inline-certificate): Makes TLS certificates available to gateways so that requests between the user and the gateway endpoint are encrypted. + +You can configure and reuse configuration entries separately. You can define and attach routes and inline certificates to multiple gateways. + +The following steps describe the general workflow for deploying a Consul API +gateway to a VM environment: + +1. Create an API gateway configuration entry. The configuration entry includes + listener configurations and references to TLS certificates. +1. Deploy the API gateway configuration entry to create the listeners. +1. Create and deploy routes to bind to the gateway. + +Refer to [API Gateway for Kubernetes](/consul/docs/api-gateway) for information +about using Consul API gateway on Kubernetes. + +## Requirements + +The following requirements must be satisfied to use API gateways on VMs: + +- Consul 1.15 or later +- 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 + +If ACLs are enabled, you must present a token with the following permissions to +configure Consul and deploy API gateways: + +- `mesh: read` +- `mesh: write` + +Refer [Mesh Rules](/consul/docs/security/acl/acl-rules#mesh-rules) for +additional information about configuring policies that enable you to interact +with Consul API gateway configurations. + +## Create the API gateway configuration + +Create an API gateway configuration that defines listeners and TLS certificates +in the mesh. In the following example, the API gateway specifies an HTTP +listener on port `8443` that routes can use to connect external traffic to +services in the mesh. + +```hcl +Kind = "api-gateway" +Name = "my-gateway" + +// Each listener configures a port which can be used to access the Consul cluster +Listeners = [ + { + Port = 8443 + Name = "my-http-listener" + Protocol = "http" + TLS = { + Certificates = [ + { + Kind = "inline-certificate" + Name = "my-certificate" + } + ] + } + } +] +``` + +Refer to [API Gateway Configuration Reference](/consul/docs/connect/gateways/api-gateway/configuration/api-gateway) for +information about all configuration fields. + +Gateways and routes are eventually-consistent objects that 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 +bound to the gateway successfully. + +## Deploy the API gateway + +Use the `consul config write` command to implement the API gateway +configuration entries. The following command applies the configuration entry +for the main gateway object: + +```shell-session +$ consul config write gateways.hcl +``` + +Run the following command to deploy an API gateway instance: + +```shell-session +$ consul connect envoy -gateway api -register -service my-api-gateway +``` + +The command directs Consul to configure Envoy as an API gateway. + +## Route requests + +Define route configurations and bind them to listeners configured on the +gateway so that Consul can route incoming requests to services in the mesh. +Create HTTP or TCP routes by setting the `Kind` parameter to `http-route` or +`tcp-route` and configuring rules that define request traffic flows. + +The following example routes requests from the listener on the API gateway at +port `8443` to services in Consul based on the path of the request. When an +incoming request starts at path `/`, Consul forwards 90 percent of the requests +to the `ui` service and 10 percent to `experimental-ui`. Consul also forwards +requests starting with `/api` to `api`. + +```hcl +Kind = "http-route" +Name = "my-http-route" + +// Rules define how requests will be routed +Rules = [ + // Send all requests to UI services with 10% going to the "experimental" UI + { + Matches = [ + { + Path = { + Match = "prefix" + Value = "/" + } + } + ] + Services = [ + { + Name = "ui" + Weight = 90 + }, + { + Name = "experimental-ui" + Weight = 10 + } + ] + }, + // Send all requests that start with the path `/api` to the API service + { + Matches = [ + { + Path = { + Match = "prefix" + Value = "/api" + } + } + ] + Services = [ + { + Name = "api" + } + ] + } +] + +Parents = [ + { + Kind = "api-gateway" + Name = "my-gateway" + SectionName = "my-http-listener" + } +] +``` + +Create this configuration by saving it to a file called `my-http-route.hcl` and using the command + +```shell-session +$ consul config write my-http-route.hcl +``` + +Refer to [HTTP Route Configuration Entry Reference](/consul/docs/connect/gateways/api-gateway/configuration/http-route) +and [TCP Route Configuration Entry Reference](/consul/docs/connect/gateways/api-gateway/configuration/tcp-route) for details about route configurations. + +## Add a TLS certificate + +Define an [`inline-certificate` configuration entry](/consul/docs/connect/gateways/api-gateway/configuration/inline-certificate) with a name matching 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. The inline certificate configuration entry takes a public certificate and private key in plaintext. + +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 +Kind = "inline-certificate" +Name = "my-certificate" + +Certificate = <.ingress.`. For listeners with a @@ -29,7 +29,7 @@ For listeners with a case, the ingress gateway relies on host/authority headers to decide the service that should receive the traffic. The host used to match traffic defaults to the [Consul DNS ingress -subdomain](/consul/docs/discovery/dns#ingress-service-lookups), but can be changed using +subdomain](/consul/docs/services/discovery/dns-static-lookups#ingress-service-lookups), but can be changed using the [hosts](/consul/docs/connect/config-entries/ingress-gateway#hosts) field. ![Ingress Gateway Architecture](/img/ingress-gateways.png) diff --git a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx b/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx deleted file mode 100644 index ee5a3e38d38..00000000000 --- a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: docs -page_title: Enabling Service-to-service Traffic Across Peered Clusters -description: >- - Mesh gateways are specialized proxies that route data between services that cannot communicate directly. Learn how to enable service-to-service traffic across clusters in different datacenters or admin partitions that have an established peering connection. ---- - -# Enabling Service-to-service Traffic Across Peered Clusters - -Mesh gateways are required for you to route service mesh traffic between peered Consul clusters. Clusters can reside in different clouds or runtime environments where general interconnectivity between all services in all clusters is not feasible. - -At a minimum, a peered cluster exporting a service must have a mesh gateway registered. -For Enterprise, this mesh gateway must also be registered in the same partition as the exported service(s). -To use the `local` mesh gateway mode, there must also be a mesh gateway regsitered in the importing cluster. - -Unlike mesh gateways for WAN-federated datacenters and partitions, mesh gateways between peers terminate mTLS sessions to decrypt data to HTTP services and then re-encrypt traffic to send to services. Data must be decrypted in order to evaluate and apply dynamic routing rules at the destination cluster, which reduces coupling between peers. - -## Prerequisites - -To configure mesh gateways for cluster peering, make sure your Consul environment meets the following requirements: - -- Consul version 1.14.0 or newer. -- A local Consul agent is required to manage mesh gateway configuration. -- Use [Envoy proxies](/consul/docs/connect/proxies/envoy). Envoy is the only proxy with mesh gateway capabilities in Consul. - -## Configuration - -Configure the following settings to register and use the mesh gateway as a service in Consul. - -### Gateway registration - -- Specify `mesh-gateway` in the `kind` field to register the gateway with Consul. -- Define the `Proxy.Config` settings using opaque parameters compatible with your proxy. For Envoy, refer to the [Gateway Options](/consul/docs/connect/proxies/envoy#gateway-options) and [Escape-hatch Overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) documentation for additional configuration information. - -Alternatively, you can also use the CLI to spin up and register a gateway in Consul. For additional information, refer to the [`consul connect envoy` command](/consul/commands/connect/envoy#mesh-gateways). - -### Sidecar registration - -- Configure the `proxy.upstreams` parameters to route traffic to the correct service, namespace, and peer. Refer to the [`upstreams` documentation](/consul/docs/connect/registration/service-registration#upstream-configuration-reference) for details. -- The service `proxy.upstreams.destination_name` is always required. -- The `proxy.upstreams.destination_peer` must be configured to enable cross-cluster traffic. -- The `proxy.upstream/destination_namespace` configuration is only necessary if the destination service is in a non-default namespace. - -### Service exports - -- Include the `exported-services` configuration entry to enable Consul to export services contained in a cluster to one or more additional clusters. For additional information, refer to the [Exported Services documentation](/consul/docs/connect/config-entries/exported-services). - -### ACL configuration - -If ACLs are enabled, you must add a token granting `service:write` for the gateway's service name and `service:read` for all services in the Enterprise admin partition or OSS datacenter to the gateway's service definition. -These permissions authorize the token to route communications for other Consul service mesh services. - -You must also grant `mesh:write` to mesh gateways routing peering traffic in the data plane. -This permission allows a leaf certificate to be issued for mesh gateways to terminate TLS sessions for HTTP requests. - -### Modes - -Modes are configurable as either `remote` or `local` for mesh gateways that connect peered clusters. -The `none` setting is invalid for mesh gateways in peered clusters and will be ignored by the gateway. -By default, all proxies connecting to peered clusters use mesh gateways in [remote mode](/consul/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). diff --git a/website/content/docs/connect/index.mdx b/website/content/docs/connect/index.mdx index cbd2903a331..7c7235569d3 100644 --- a/website/content/docs/connect/index.mdx +++ b/website/content/docs/connect/index.mdx @@ -5,7 +5,7 @@ description: >- Consul’s service mesh makes application and microservice networking secure and observable with identity-based authentication, mutual TLS (mTLS) encryption, and explicit service-to-service authorization enforced by sidecar proxies. Learn how Consul’s service mesh works and get started on VMs or Kubernetes. --- -# Consul Service Mesh +# Consul service mesh Consul Service Mesh provides service-to-service connection authorization and encryption using mutual Transport Layer Security (TLS). Consul Connect is used interchangeably @@ -25,30 +25,30 @@ Review the video below to learn more about Consul Connect from HashiCorp's co-fo height="315" > -## Application Security +## Application security -Connect enables secure deployment best-practices with automatic +Consul service mesh enables secure deployment best-practices with automatic service-to-service encryption, and identity-based authorization. -Connect uses the registered service identity (rather than IP addresses) to +Consul uses the registered service identity, rather than IP addresses, to enforce access control with [intentions](/consul/docs/connect/intentions). This -makes it easier to reason about access control and enables services to be -rescheduled by orchestrators including Kubernetes and Nomad. Intention -enforcement is network agnostic, so Connect works with physical networks, cloud +makes it easier to control access and enables services to be +rescheduled by orchestrators, including Kubernetes and Nomad. Intention +enforcement is network agnostic, so Consul service mesh works with physical networks, cloud networks, software-defined networks, cross-cloud, and more. ## Observability -One of the key benefits of Consul Connect is the uniform and consistent view it can +One of the key benefits of Consul service mesh is the uniform and consistent view it can provide of all the services on your network, irrespective of their different -programming languages and frameworks. When you configure Consul Connect to use -sidecar proxies, those proxies "see" all service-to-service traffic and can -collect data about it. Consul Connect can configure Envoy proxies to collect +programming languages and frameworks. When you configure Consul service mesh to use +sidecar proxies, those proxies see all service-to-service traffic and can +collect data about it. Consul service mesh can configure Envoy proxies to collect layer 7 metrics and export them to tools like Prometheus. Correctly instrumented applications can also send open tracing data through Envoy. -## Getting Started With Consul Service Mesh +## Getting started with Consul service mesh -There are several ways to try Connect in different environments. +Complete the following tutorials try Consul service mesh in different environments: - The [Getting Started with Consul Service Mesh collection](/consul/tutorials/kubernetes-deploy/service-mesh?utm_source=docs) walks you through installing Consul as service mesh for Kubernetes using the Helm diff --git a/website/content/docs/connect/intentions.mdx b/website/content/docs/connect/intentions.mdx deleted file mode 100644 index b3b0371a93a..00000000000 --- a/website/content/docs/connect/intentions.mdx +++ /dev/null @@ -1,341 +0,0 @@ ---- -layout: docs -page_title: Service Mesh Intentions -description: >- - Intentions define communication permissions in the service mesh between microservices. Learn about configuration basics, wildcard intentions, precedence and match order, and protecting intention management with ACLs. ---- - -# Service Mesh Intentions - --> **1.9.0 and later:** This guide only applies in Consul versions 1.9.0 and -later. The documentation for the legacy intentions system is -[here](/consul/docs/connect/intentions-legacy). - -Intentions define access control for services via Connect and are used to -control which services may establish connections or make requests. Intentions -can be managed via the API, CLI, or UI. - -Intentions are enforced on inbound connections or requests by the -[proxy](/consul/docs/connect/proxies) or within a [natively integrated -application](/consul/docs/connect/native). - -Depending upon the [protocol] in use by the destination service, you can define -intentions to control Connect traffic authorization either at networking layer -4 (e.g. TCP) and application layer 7 (e.g. HTTP): - -- **Identity-based** - All intentions may enforce access based on identities - encoded within [TLS - certificates](/consul/docs/connect/connect-internals#mutual-transport-layer-security-mtls). - This allows for coarse all-or-nothing access control between pairs of - services. These work with for services with any [protocol] as they only - require awareness of the TLS handshake that wraps the opaque TCP connection. - These can also be thought of as **L4 intentions**. - -- **Application-aware** - Some intentions may additionally enforce access based - on [L7 request - attributes](/consul/docs/connect/config-entries/service-intentions#permissions) in - addition to connection identity. These may only be defined for services with - a [protocol] that is HTTP-based. These can also be thought of as **L7 - intentions**. - -At any given point in time, between any pair of services **only one intention -controls authorization**. This may be either an L4 intention or an L7 -intention, but at any given point in time only one of those applies. - -The [intention match API](/consul/api-docs/connect/intentions#list-matching-intentions) -should be periodically called to retrieve all relevant intentions for the -target destination. After verifying the TLS client certificate, the cached -intentions should be consulted for each incoming connection/request to -determine if it should be accepted or rejected. - -The default intention behavior is defined by the [`default_policy`](/consul/docs/agent/config/config-files#acl_default_policy) configuration. -If the configuration is set `allow`, then all service mesh Connect connections will be allowed by default. -If is set to `deny`, then all connections or requests will be denied by default. - -## Intention Basics - -You can define a [`service-intentions`](/consul/docs/connect/config-entries/service-intentions) configuration entry to create and manage intentions, as well as manage intentions through the Consul UI. You can also perform some intention-related tasks using the API and CLI commands. Refer to the [API](/consul/api-docs/connect/intentions) and [CLI](/consul/commands/intention) documentation for details. - -The following example shows a `service-intentions` configuration entry that specifies two intentions. Refer to the [`service-intentions`](/consul/docs/connect/config-entries/service-intentions) documentation for the full data model and additional examples. - - - -```hcl -Kind = "service-intentions" -Name = "db" -Sources = [ - { - Name = "web" - Action = "deny" - }, - { - Name = "api" - Action = "allow" - } -] -``` - -```json -{ - "Kind": "service-intentions", - "Name": "db", - "Sources": [ - { - "Action": "deny", - "Name": "web" - }, - { - "Action": "allow", - "Name": "api" - } - ] -} -``` - - - -This configuration entry defines two intentions with a common destination of `db`. The -first intention above is a deny intention with a source of `web`. This says -that connections from web to db are not allowed and the connection will be -rejected. The second intention is an allow intention with a source of `api`. -This says that connections from api to db are allowed and connections will be -accepted. - -### Wildcard Intentions - -You can use the `*` wildcard to match service names when defining an intention source or destination. The wildcard matches _any_ value, which enables you to set a wide initial scope when configuring intentions. - -The wildcard is supported in Consul Enterprise `namespace` fields (see [Namespaces](/consul/docs/enterprise/namespaces) for additional information), but it _is not supported_ in `partition` fields (see [Admin Partitions](/consul/docs/enterprise/admin-partitions) for additional information). - -In the following example, the `web` service cannot connect to _any_ service: - - - -```hcl -Kind = "service-intentions" -Name = "*" -Sources = [ - { - Name = "web" - Action = "deny" - } -] -``` - -```json -{ - "Kind": "service-intentions", - "Name": "*", - "Sources": [ - { - "Action": "deny", - "Name": "web" - } - ] -} -``` - - - -The `db` service is configured to deny all connection in the following example: - - - -```hcl -Kind = "service-intentions" -Name = "db" -Sources = [ - { - Name = "*" - Action = "deny" - } -] -``` - -```json -{ - "Kind": "service-intentions", - "Name": "db", - "Sources": [ - { - "Action": "deny", - "Name": "*" - } - ] -} -``` - - - - This example grants Prometheus access to any service in -any namespace. - - - -```hcl -Kind = "service-intentions" -Name = "*" -Namespace = "*" -Sources = [ - { - Name = "prometheus" - Namespace = "monitoring" - Action = "allow" - } -] -``` - -```json -{ - "Kind": "service-intentions", - "Name": "*", - "Namespace": "*", - "Sources": [ - { - "Action": "allow", - "Name": "prometheus", - "Namespace": "monitoring" - } - ] -} -``` - - - -### Enforcement - -For services that define their [protocol] as TCP, intentions mediate the -ability to **establish new connections**. When an intention is modified, -existing connections will not be affected. This means that changing a -connection from "allow" to "deny" today _will not_ kill the connection. - -For services that define their protocol as HTTP-based, intentions mediate the -ability to **issue new requests**. - -When an intention is modified, requests received after the modification will -use the latest intention rules to enforce access. This means that though -changing a connection from "allow" to "deny" today will not kill the -connection, it will correctly block new requests from being processed. - -## Precedence and Match Order - -Intentions are matched in an implicit order based on specificity, preferring -deny over allow. Specificity is determined by whether a value is an exact -specified value or is the wildcard value `*`. -The full precedence table is shown below and is evaluated -top to bottom, with larger numbers being evaluated first. - -| Source Namespace | Source Name | Destination Namespace | Destination Name | Precedence | -| ---------------- | ----------- | --------------------- | ---------------- | ---------- | -| Exact | Exact | Exact | Exact | 9 | -| Exact | `*` | Exact | Exact | 8 | -| `*` | `*` | Exact | Exact | 7 | -| Exact | Exact | Exact | `*` | 6 | -| Exact | `*` | Exact | `*` | 5 | -| `*` | `*` | Exact | `*` | 4 | -| Exact | Exact | `*` | `*` | 3 | -| Exact | `*` | `*` | `*` | 2 | -| `*` | `*` | `*` | `*` | 1 | - -The precedence value can be read from a -[field](/consul/docs/connect/config-entries/service-intentions#precedence) on the -`service-intentions` configuration entry after it is modified. Precedence cannot be -manually overridden today. - -The numbers in the table above are not stable. Their ordering will remain -fixed but the actual number values may change in the future. - --> - Namespaces are an Enterprise feature. In Consul -OSS the only allowable value for either namespace field is `"default"`. Other -rows in this table are not applicable. - -## Intention Management Permissions - -Intention management can be protected by [ACLs](/consul/docs/security/acl). -Permissions for intentions are _destination-oriented_, meaning the ACLs -for managing intentions are looked up based on the destination value -of the intention, not the source. - -Intention permissions are by default implicitly granted at `read` level -when granting `service:read` or `service:write`. This is because a -service registered that wants to use Connect needs `intentions:read` -for its own service name in order to know whether or not to authorize -connections. The following ACL policy will implicitly grant `intentions:read` -(note _read_) for service `web`. - - - -```hcl -service "web" { - policy = "write" -} -``` - -```json -{ - "service": [ - { - "web": [ - { - "policy": "write" - } - ] - } - ] -} -``` - - - -It is possible to explicitly specify intention permissions. For example, -the following policy will allow a service to be discovered without granting -access to read intentions for it. - -```hcl -service "web" { - policy = "read" - intentions = "deny" -} -``` - -Note that `intentions:read` is required for a token that a Connect-enabled -service uses to register itself or its proxy. If the token used does not -have `intentions:read` then the agent will be unable to resolve intentions -for the service and so will not be able to authorize any incoming connections. - -~> **Security Note:** Explicitly allowing `intentions:write` on the token you -provide to a service instance at registration time opens up a significant -additional vulnerability. Although you may trust the service _team_ to define -which inbound connections they accept, using a combined token for registration -allows a compromised instance to to redefine the intentions which allows many -additional attack vectors and may be hard to detect. We strongly recommend only -delegating `intentions:write` using tokens that are used by operations teams or -orchestrators rather than spread via application config, or only manage -intentions with management tokens. - -## Performance and Intention Updates - -The intentions for services registered with a Consul agent are cached -locally on that agent. They are then updated via a background blocking query -against the Consul servers. - -Supported [proxies] (such as [Envoy]) also cache this data within their own -configuration so that inbound connections or requests require no Consul agent -involvement during authorization. All actions in the data path of connections -happen within the proxy. - -Updates to intentions are propagated nearly instantly to agents since agents -maintain a continuous blocking query in the background for intention updates -for registered services. Proxies similarly use blocking queries to update -their local configurations quickly. - -Because all the intention data is cached locally, the agents or proxies can -fail static. Even if the agents are severed completely from the Consul servers, -or the proxies are severed completely from their local Consul agent, inbound -connection authorization continues to work indefinitely. Changes to intentions -will not be picked up until the partition heals, but will then automatically -take effect when connectivity is restored. - -[protocol]: /consul/docs/connect/config-entries/service-defaults#protocol -[proxies]: /consul/docs/connect/proxies -[envoy]: /consul/docs/connect/proxies/envoy diff --git a/website/content/docs/connect/intentions/create-manage-intentions.mdx b/website/content/docs/connect/intentions/create-manage-intentions.mdx new file mode 100644 index 00000000000..80e68ce890a --- /dev/null +++ b/website/content/docs/connect/intentions/create-manage-intentions.mdx @@ -0,0 +1,178 @@ +--- +layout: docs +page_title: Create and manage service intentions +description: >- + Learn how to create and manage Consul service mesh intentions using service-intentions config entries, the `consul intentions` command, and `/connect/intentions` API endpoint. +--- + +# Create and manage intentions + +This topic describes how to create and manage service intentions, which are configurations for controlling access between services in the service mesh. + +## Overview + +You can create single intentions or create them in batches using the Consul API, CLI, or UI. You can also define a service intention configuration entry that sets default intentions for all services in the mesh. Refer to [Service intentions overview](/consul/docs/connnect/intentions/intentions) for additional background information about intentions. + +## Requirements + +- At least two services must be registered in the datacenter. +- TLS must be enabled to enforce L4 intentions. Refer to [Encryption](/consul/docs/security/encryption) for additional information. + +### ACL requirements + +Consul grants permissions for creating and managing intentions based on the destination, not the source. When ACLs are enabled, services and operators must present a token linked to a policy that grants read and write permissions to the destination service. + +Consul implicitly grants `intentions:read` permissions to destination services when they are configured with `service:read` or `service:write` permissions. This is so that the services can allow or deny inbound connections when they attempt to join the service mesh. Refer to [Service rules](/consul/docs/security/acl/acl-rules#service-rules) for additional information about configuring ALCs for intentions. + +The default ACL policy configuration determines the default behavior for intentions. If the policy is set to `deny`, then all connections or requests are denied and you must enable them explicitly. Refer to [`default_policy`](/consul/docs/agent/config/config-files#acl_default_policy) for details. + +## Create an intention + +You can create and manage intentions one at a time using the Consul API, CLI, or UI You can specify one destination or multiple destinations in a single intention. + +### API + +Send a `PUT` request to the `/connect/intentions/exact` HTTP API endpoint and specify the following query parameters: + +- `source`: Service sending the request +- `destination`: Service responding to the request +- `ns`: Namespace of the destination service + +For L4 intentions, you must also specify the intention action in the request payload. + +The following example creates an intention that allows `web` to send request to `db`: + +```shell-session +$ curl --request PUT \ +--data ' { "Action": "allow" } ' \ +http://localhost:8500/v1/connect/intentions/exact\?source\=web\&destination\=db +``` + +Refer to the `/connect/intentions/exact` [HTTP API endpoint documentation](/consul/api-docs/connect/intentions) for additional information request payload parameters. + +For L7 intentions, specify the `Permissions` in the request payload to configure attributes for dynamically enforcing intentions. In the following example payload, Consul allows HTTP GET requests if the request body is empty: + + + +```json +{ + "Permissions": [ + { + "Action": "allow", + "HTTP": { + "Methods": ["GET"], + "Header": [ + { + "Name": "Content-Length", + "Exact": "0" + } + ] + } + } + ] +} + +``` + + + +The `Permissions` object specifies a list of permissions for L7 traffic sources. The list contains one or more actions and a set of match criteria for each action. Refer to the [`Sources[].Permissions[]` parameter](/consul/connect/config-entries/service-intentions#source-permissions) in the service intentions configuration entry reference for configuration details. + +To apply the intention, call the endpoint and pass the configuration file containing the attributes to the endpoint: + +```shell-session +$ curl --request PUT \ +--data @payload.json \ +http://localhost:8500/v1/connect/intentions/exact\?source\=svc1\&destination\=sv2 +``` +### CLI + +Use the `consul intention create` command according to the following syntax to create a new intention: + +```shell-session +$ consul intention create - +``` + +The following example creates an intention that allows `web` service instances to connect to `db` service instances: + +```shell-session +$ consul intention create -allow web db +``` + +You can use the asterisk (`*`) wildcard to specify multiple destination services. Refer to [Precedence and match order](/consul/docs/connect/intentions/create-manage-intentions#precedence-and-match-order) for additional information. + +### Consul UI + +1. Log into the Consul UI and choose **Services** from the sidebar menu. +1. Click on a service and then click the **Intentions* tab. +1. Click **Create** and choose the source service from the drop-down menu. +1. You can add an optional description. +1. Choose one of the following options: + 1. **Allow**: Allows the source service to send requests to the destination. + 1. **Deny**: Prevents the source service from sending requests to the destination. + 1. **Application Aware**: Enables you to specify L7 criteria for dynamically enforcing intentions. Refer to [Configure application aware settings](#configure-application-aware-settings) for additional information. +1. Click **Save**. + +Repeat the procedure as necessary to create additional intentions. + +#### Configure application aware settings + +You can use the Consul UI to configure L7 permissions. + +1. Click **Add permission** to open the permission editor. +1. Enable the **Allow** or **Deny** option. +1. You can specify a path, request method, and request headers to match. All criteria must be satisfied for Consul to enforce the permission. Refer to the [`Sources[].Permissions[]` parameter](/consul/docs/connect/config-entries/service-intentions#sources-permissions) in the service intentions configuration entry reference for information about the available configuration fields. +1. Click **Save**. + +Repeat the procedure as necessary to create additional permissions. + +## Create multiple intentions + +You can create a service intentions configuration entry to specify default intentions for your service mesh. You can specify default settings for L4 or L7 application-aware traffic. + +### Define a service intention configuration entry + +Configure the following fields: + + + + + +- [`Kind`](/consul/docs/connect/config-entries/service-intentions#kind): Declares the type of configuration entry. Must be set to `service-intentions`. +- [`Name`](/consul/docs/connect/config-entries/service-intentions#kind): Specifies the name of the destination service for intentions defined in the configuration entry. You can use a wildcard character (*) to set L4 intentions for all services that are not protected by specific intentions. Wildcards are not supported for L7 intentions. +- [`Sources`](/consul/docs/connect/config-entries/service-intentions#sources): Specifies an unordered list of all intention sources and the authorizations granted to those sources. Consul stores and evaluates the list in reverse order sorted by intention precedence. +- [`Sources.Action`](/consul/docs/connect/config-entries/service-intentions#sources-action) or [`Sources.Permissions`](/consul/docs/connect/config-entries/service-intentions#sources-permissions): For L4 intentions, set the `Action` field to "allow" or "deny" so that Consul can enforce intentions that match the source service. For L7 intentions, configure the `Permissions` settings, which define a set of application-aware attributes for dynamically matching incoming requests. The `Actions` and `Permissions` settings are mutually exclusive. + + + + + +- [`apiVersion`](/consul/docs/connect/config-entries/service-intentions#apiversion): Specifies the Consul API version. Must be set to `consul.hashicorp.com/v1alpha1`. +- [`kind`](/consul/docs/connect/config-entries/service-intentions#kind): Declares the type of configuration entry. Must be set to `ServiceIntentions`. +- [`spec.destination.name`](/consul/docs/connect/config-entries/service-intentions#spec-destination-name): Specifies the name of the destination service for intentions defined in the configuration entry. You can use a wildcard character (*) to set L4 intentions for all services that are not protected by specific intentions. Wildcards are not supported for L7 intentions. +- [`spec.sources`](/consul/docs/connect/config-entries/service-intentions#spec-sources): Specifies an unordered list of all intention sources and the authorizations granted to those sources. Consul stores and evaluates the list in reverse order sorted by intention precedence. +- [`spec.sources.action`](/consul/docs/connect/config-entries/service-intentions#spec-sources-action) or [`spec.sources.permissions`](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions): For L4 intentions, set the `action` field to "allow" or "deny" so that Consul can enforce intentions that match the source service. For L7 intentions, configure the `permissions` settings, which define a set of application-aware attributes for dynamically matching incoming requests. The `actions` and `permissions` settings are mutually exclusive. + + + + + +Refer to the [service intentions configuration entry](/consul/docs/connect/config-entries/service-intentions) reference documentation for details about all configuration options. + +Refer to the [example service intentions configurations](/consul/docs/connect/config-entries/service-intentions#examples) for additional guidance. + +#### Interaction with other configuration entries + +L7 intentions defined in a configuration entry are restricted to destination services +configured with an HTTP-based protocol as defined in a corresponding +[service defaults configuration entry](/consul/docs/connect/config-entries/service-defaults) +or globally in a [proxy defaults configuration entry](/consul/docs/connect/config-entries/proxy-defaults). + +### Apply the service intentions configuration entry + +You can apply the configuration entry using the [`consul config` command](/consul/commands/config) or by calling the [`/config` API endpoint](/consul/api-docs/config). In Kubernetes environments, apply the `ServiceIntentions` custom resource definitions (CRD) to implement and manage Consul configuration entries. + +Refer to the following topics for details about applying configuration entries: + +- [How to Use Configuration Entries](/consul/docs/agent/config-entries) +- [Custom Resource Definitions for Consul on Kubernetes](/consul/docs/k8s/crds) \ No newline at end of file diff --git a/website/content/docs/connect/intentions/index.mdx b/website/content/docs/connect/intentions/index.mdx new file mode 100644 index 00000000000..301344c64ec --- /dev/null +++ b/website/content/docs/connect/intentions/index.mdx @@ -0,0 +1,91 @@ +--- +layout: docs +page_title: Service mesh intentions overview +description: >- + Intentions are access controls that allow or deny incoming requests to services in the mesh. +--- + +# Service intentions overview + +This topic provides overview information about Consul intentions, which are mechanisms that control traffic communication between services in the Consul service mesh. + +![Diagram showing how service intentions control access between services](/img/consul-connect/consul-service-mesh-intentions-overview.svg) + +## Intention types + +Intentions control traffic communication between services at the network layer, also called _L4_ traffic, or the application layer, also called _L7 traffic_. The protocol that the destination service uses to send and receive traffic determines the type of authorization the intention can enforce. + +### L4 traffic intentions + +If the destination service uses TCP or any non-HTTP-based protocol, then intentions can control traffic based on identities encoded in mTLS certificates. Refer to [Mutual transport layer security (mTLS)](/consul/docs/connect/connect-internals#mutual-transport-layer-security-mtls) for additional information. + +This implementation allows broad all-or-nothing access control between pairs of services. The only requirement is that the service is aware of the TLS handshake that wraps the opaque TCP connection. + +### L7 traffic intentions + +If the destination service uses an HTTP-based protocol, then intentions can enforce access based on application-aware request attributes, in addition to identity-based enforcement, to control traffic between services. Refer to [Service intentions configuration reference](/consul/docs/connect/config-entries/service-intentions#permissions) for additional information. + +## Workflow + +You can manually create intentions from the Consul UI, API, or CLI. You can also enable Consul to dynamically create them by defining traffic routes in service intention configuration entries. Refer to [Create and manage intentions](/consul/docs/connect/intentions/create-manage-intentions) for details. + +### Enforcement + +The [proxy](/consul/docs/connect/proxies) or [natively-integrated +application](/consul/docs/connect/native) enforces intentions on inbound connections or requests. Only one intention can control authorization between a pair of services at any single point in time. + +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. + +### Caching + +The intentions for services registered with a Consul agent are cached locally on the agent. Supported proxies also cache intention data in their own configurations so that they can authorize inbound connections or requests without relying on the Consul agent. All actions in the data path of connections take place within the proxy. + +### Updates + +Consul propagates updates to intentions almost instantly as a result of the continuous blocking query the agent uses. A _blocking query_ is a Consul API feature that uses long polling to wait for potential changes. Refer to [Blocking Queries](/consul/api-docs/features/blocking) for additional information. Proxies also use blocking queries to quickly update their local configurations. + +Because all intention data is cached locally, authorizations for inbound connection persist, even if the agents are completely severed from the Consul servers or if the proxies are completely severed from their local Consul agent. If the connection is severed, Consul automatically applies changes to intentions when connectivity is restored. + +### Intention maintenance + +Services should periodically call the [intention match API](/consul/api-docs/connect/intentions#list-matching-intentions) to retrieve all relevant intentions for the target destination. After verifying the TLS client certificate, the cached intentions for each incoming connection or request determine if it should be accepted or rejected. + +## Precedence and match order + +Consul processes criteria defined in the service intention configuration entry to match incoming requests. When Consul finds a match, it applies the corresponding action specified in the configuration entry. The match criteria may include specific HTTP headers, request methods, or other attributes. Additionally, you can use regular expressions to programmatically match attributes. Refer to [Service intention configuration entry reference](/consul/docs/connect/config-entries/service-intentions) for details. + +Consul orders the matches based the following factors: + +- Specificity: Incoming requests that match attributes directly have the highest precedence. For example, intentions that are configured to deny traffic from services that send `POST` requests take precedence over intentions that allow traffic from methods configured with the wildcard value `*`. +- Authorization: Consul enforces `deny` over `allow` if match criteria are weighted equally. + +The following table shows match precedence in descending order: + +| Precedence | Source Namespace | Source Name | Destination Namespace | Destination Name | +| -----------| ---------------- | ------------| --------------------- | ---------------- | +| 9 | Exact | Exact | Exact | Exact | +| 8 | Exact | `*` | Exact | Exact | +| 7 | `*` | `*` | Exact | Exact | +| 6 | Exact | Exact | Exact | `*` | +| 5 | Exact | `*` | Exact | `*` | +| 4 | `*` | `*` | Exact | `*` | +| 3 | Exact | Exact | `*` | `*` | +| 2 | Exact | `*` | `*` | `*` | +| 1 | `*` | `*` | `*` | `*` | + +Consul prints the precedence value to the service intentions configuration entry after it processes the matching criteria. The value is read-only. Refer to +[`Precedence`](/consul/docs/connect/config-entries/service-intentions#precedence) for additional information. + +Namespaces are an Enterprise feature. In Consul OSS, the only allowable value for either namespace field is `"default"`. Other rows in the table are not applicable. + +The [intention match API](/consul/api-docs/connect/intentions#list-matching-intentions) +should be periodically called to retrieve all relevant intentions for the +target destination. After verifying the TLS client certificate, the cached +intentions should be consulted for each incoming connection/request to +determine if it should be accepted or rejected. + +The default intention behavior is defined by the [`default_policy`](/consul/docs/agent/config/config-files#acl_default_policy) configuration. +If the configuration is set `allow`, then all service mesh Connect connections will be allowed by default. +If is set to `deny`, then all connections or requests will be denied by default. \ No newline at end of file diff --git a/website/content/docs/connect/intentions-legacy.mdx b/website/content/docs/connect/intentions/legacy.mdx similarity index 98% rename from website/content/docs/connect/intentions-legacy.mdx rename to website/content/docs/connect/intentions/legacy.mdx index 0e9e991d37c..b79d34a543a 100644 --- a/website/content/docs/connect/intentions-legacy.mdx +++ b/website/content/docs/connect/intentions/legacy.mdx @@ -8,8 +8,7 @@ description: >- # Intentions in Legacy Mode ~> **1.8.x and earlier:** This document only applies in Consul versions 1.8.x -and before. If you are using version 1.9.0 or later please use the updated -documentation [here](/consul/docs/connect/intentions). +and before. If you are using version 1.9.0 or later, refer to the [current intentions documentation](/consul/docs/connect/intentions). Intentions define access control for services via Connect and are used to control which services may establish connections. Intentions can be diff --git a/website/content/docs/connect/native/index.mdx b/website/content/docs/connect/native/index.mdx index ffeb720f8bf..6dee2160465 100644 --- a/website/content/docs/connect/native/index.mdx +++ b/website/content/docs/connect/native/index.mdx @@ -54,7 +54,7 @@ Details on the steps are below: - **Service discovery** - This is normal service discovery using Consul, a static IP, or any other mechanism. If you're using Consul DNS, the - [`.connect`](/consul/docs/discovery/dns#connect-capable-service-lookups) + [`.connect`](/consul/docs/services/discovery/dns-static-lookups#service-mesh-enabled-service-lookups) syntax to find Connect-capable endpoints for a service. After service discovery, choose one address from the list of **service addresses**. @@ -94,7 +94,7 @@ Details on the steps are below: HTTP) aware it can safely enforce intentions per _request_ instead of the coarser per _connection_ model. -## Updating Certificates and Certificate Roots +## Update certificates and certificate roots The leaf certificate and CA roots can be updated at any time and the natively integrated application must react to this relatively quickly @@ -129,14 +129,14 @@ Some language libraries such as the [Go library](/consul/docs/connect/native/go) automatically handle updating and locally caching the certificates. -## Service Registration +## Service registration Connect-native applications must tell Consul that they support Connect natively. This enables the service to be returned as part of service -discovery for Connect-capable services, used by other Connect-native applications +discovery for service mesh-capable services used by other Connect-native applications and client [proxies](/consul/docs/connect/proxies). -This can be specified directly in the [service definition](/consul/docs/discovery/services): +You can enable native service mesh support directly in the [service definition](/consul/docs/services/configuration/services-configuration-reference#connect) by configuring the `connect` block. In the following example, the `redis` service is configured to support service mesh natively: ```json { diff --git a/website/content/docs/connect/observability/index.mdx b/website/content/docs/connect/observability/index.mdx index 3da463b389b..23dfd81b19e 100644 --- a/website/content/docs/connect/observability/index.mdx +++ b/website/content/docs/connect/observability/index.mdx @@ -26,7 +26,7 @@ configuration](/consul/docs/agent/config/config-files#enable_central_service_con If you are using Kubernetes, the Helm chart can simplify much of the configuration needed to enable observability. See our [Kubernetes observability docs](/consul/docs/k8s/connect/observability/metrics) for more information. -### Metrics Destination +### Metrics destination For Envoy the metrics destination can be configured in the proxy configuration entry's `config` section. @@ -41,18 +41,18 @@ config { Find other possible metrics syncs in the [Connect Envoy documentation](/consul/docs/connect/proxies/envoy#bootstrap-configuration). -### Service Protocol +### Service protocol -You can specify the [service protocol](/consul/docs/connect/config-entries/service-defaults#protocol) -in the `service-defaults` configuration entry. You can override it in the -[service registration](/consul/docs/discovery/services). By default, proxies only give -you L4 metrics. This protocol allows proxies to handle requests at the right L7 -protocol and emit richer L7 metrics. It also allows proxies to make per-request +You can specify the [`protocol`](/consul/docs/connect/config-entries/service-defaults#protocol) +for all service instances in the `service-defaults` configuration entry. You can also override the default protocol when defining and registering proxies in a service definition file. Refer to [Expose Paths Configuration Reference](/consul/docs/connect/registration/service-registration#expose-paths-configuration-reference) for additional information. + +By default, proxies only provide L4 metrics. +Defining the protocol allows proxies to handle requests at the L7 +protocol and emit L7 metrics. It also allows proxies to make per-request load balancing and routing decisions. -### Service Upstreams +### Service upstreams You can set the upstream for each service using the proxy's [`upstreams`](/consul/docs/connect/registration/service-registration#upstreams) -sidecar parameter, which can be defined in a service's [sidecar -registration](/consul/docs/connect/registration/sidecar-service). +sidecar parameter, which can be defined in a service's [sidecar registration](/consul/docs/connect/registration/sidecar-service). diff --git a/website/content/docs/connect/proxies/envoy-extensions/index.mdx b/website/content/docs/connect/proxies/envoy-extensions/index.mdx new file mode 100644 index 00000000000..ecd9e63e074 --- /dev/null +++ b/website/content/docs/connect/proxies/envoy-extensions/index.mdx @@ -0,0 +1,31 @@ +--- +layout: docs +page_title: Envoy Extensions +description: >- + Learn how Envoy extensions enables you to add support for additional Envoy features without modifying the Consul codebase. +--- + +# Envoy extensions overview + +This topic provides an overview of Envoy extensions in Consul service mesh deployments. You can modify Consul-generated Envoy resources to add additional functionality without modifying the Consul codebase. + +## Introduction + +Consul supports two methods for modifying Envoy behavior. You can either modify the Envoy resources Consul generates through [escape hatches](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) or configure your services to use Envoy extensions using the `EnvoyExtension` parameter. Implementing escape hatches requires rewriting the Envoy resources so that they are compatible with Consul, a task that also requires understanding how Consul names Envoy resources and enforces intentions. + +Instead of modifying Consul code, you can configure your services to use Envoy extensions through the `EnvoyExtensions` field. This field is definable in [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults#envoyextensions) and [`service-defaults`](/consul/docs/connect/config-entries/service-defaults#envoyextensions) configuration entries. + +## Supported extensions + +Envoy extensions enable additional service mesh functionality in Consul by changing how the sidecar proxies behave. Extensions invoke custom code when traffic passes through an Envoy proxy. Consul supports the following extensions: + +- Lua +- Lambda + +### Lua + +The `lua` Envoy extension enables HTTP Lua filters in your Consul Envoy proxies. It allows you to run Lua scripts during Envoy requests and responses from Consul-generated Envoy resources. Refer to the [`lua`](/consul/docs/connect/proxies/envoy-extensions/usage/lua) documentation 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`](/consul/docs/connect/proxies/envoy-extensions/usage/lambda) documentation for more information. diff --git a/website/content/docs/connect/proxies/envoy-extensions/usage/lambda.mdx b/website/content/docs/connect/proxies/envoy-extensions/usage/lambda.mdx new file mode 100644 index 00000000000..ce155464196 --- /dev/null +++ b/website/content/docs/connect/proxies/envoy-extensions/usage/lambda.mdx @@ -0,0 +1,161 @@ +--- +layout: docs +page_title: Lambda Envoy Extension +description: >- + Learn how the `lambda` Envoy extension enables Consul to join AWS Lambda functions to its service mesh. +--- + +# Invoke Lambda functions in Envoy proxy + +The Lambda Envoy extension configures outbound traffic on upstream dependencies allowing mesh services to properly invoke AWS Lambda functions. Lambda functions appear in the catalog as any other Consul service. + +You can only enable the Lambda extension through `service-defaults`. This is because the Consul uses the `service-defaults` configuration entry name as the catalog name for the Lambda functions. + +## Specification + +The Lambda Envoy extension has the following arguments: + +| Arguments | Description | +| -------------------- | ------------------------------------------------------------------------------------------------ | +| `ARN` | Specifies the [AWS ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) for the service's Lambda. | +| `InvocationMode` | Determines if Consul configures the Lambda to be invoked using the synchronous or asynchronous [invocation mode](https://docs.aws.amazon.com/lambda/latest/operatorguide/invocation-modes.html). | +| `PayloadPassthrough` | Determines if the body Envoy receives is converted to JSON or directly passed to Lambda. | + +Be aware that unlike [manual lambda registration](/consul/docs/lambda/registration/manual#supported-meta-fields), region is inferred from the ARN when specified through an Envoy extension. + +## Workflow + +There are two steps to configure the Lambda Envoy extension: + +1. Configure EnvoyExtensions through `service-defaults`. +1. Apply the configuration entry. + +### Configure `EnvoyExtensions` + +To use the Lambda Envoy extension, you must configure and apply a `service-defaults` configuration entry. Consul uses the name of the entry as the Consul service name for the Lambdas in the catalog. Downstream services also use the name to invoke the Lambda. + +The following example configures the Lambda Envoy extension to create a service named `lambda` in the mesh that can invoke the associated Lambda function. + + + + + +```hcl +Kind = "service-defaults" +Name = "lambdaInvokingApp" +Protocol = "http" +EnvoyExtensions { + Name = "builtin/aws/lambda" + Arguments = { + ARN = "arn:aws:lambda:us-west-2:111111111111:function:lambda-1234" + } +} +``` + + + + + + +```hcl +{ + "kind": "service-defaults", + "name": "lambdaInvokingApp", + "protocol": "http", + "envoy_extensions": [{ + "name": "builtin/aws/lambda", + "arguments": { + "arn": "arn:aws:lambda:us-west-2:111111111111:function:lambda-1234" + } + }] +} + +``` + + + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: lambda +spec: + protocol: http + envoyExtensions: + name = "builtin/aws/lambda" + arguments: + arn: arn:aws:lambda:us-west-2:111111111111:function:lambda-1234 +``` + + + + + + +For a full list of parameters for `EnvoyExtensions`, refer to the [`service-defaults`](/consul/docs/connect/config-entries/service-defaults#envoyextensions) and [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults#envoyextensions) configuration entries reference documentation. + +~> **Note:** You can only enable the Lambda extension through `service-defaults`. + +Refer to [Configuration specification](#configuration-specification) section to find a full list of arguments for the Lambda Envoy extension. + +### Apply the configuration entry + +Apply the `service-defaults` configuration entry. + + + + +```shell-session +$ consul config write lambda-envoy-extension.hcl +``` + + + + +```shell-session +$ consul config write lambda-envoy-extension.json +``` + + + + +```shell-session +$ kubectl apply lambda-envoy-extension.yaml +``` + + + + +## Examples + +In the following example, the Lambda Envoy extension adds a single Lambda function running in two regions into the mesh. Then, you can use the `lambda` service name to invoke it, as if it was any other service in the mesh. + + + +```hcl +Kind = "service-defaults" +Name = "lambda" +Protocol = "http" +EnvoyExtensions { + Name = "builtin/aws/lambda" + + Arguments = { + payloadPassthrough: false + arn: arn:aws:lambda:us-west-2:111111111111:function:lambda-1234 + } +} +EnvoyExtensions { + Name = "builtin/aws/lambda" + + Arguments = { + payloadPassthrough: false + arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 + } +} +``` + + \ No newline at end of file diff --git a/website/content/docs/connect/proxies/envoy-extensions/usage/lua.mdx b/website/content/docs/connect/proxies/envoy-extensions/usage/lua.mdx new file mode 100644 index 00000000000..496b7d5fa58 --- /dev/null +++ b/website/content/docs/connect/proxies/envoy-extensions/usage/lua.mdx @@ -0,0 +1,228 @@ +--- +layout: docs +page_title: Lua Envoy Extension +description: >- + Learn how the `lua` Envoy extension enables Consul to run Lua scripts during Envoy requests and responses from Consul-generated Envoy resources. +--- + +# Run Lua scripts in Envoy proxy + +The Lua Envoy extension enables the [HTTP Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter) in your Consul Envoy proxies, letting you run Lua scripts when requests and responses pass through Consul-generated Envoy resources. + +Envoy filters support setting and getting dynamic metadata, allowing a filter to share state information with subsequent filters. To set dynamic metadata, configure the HTTP Lua filter. Users can call `streamInfo:dynamicMetadata()` from Lua scripts to get the request's dynamic metadata. + +## Configuration specifications + +To use the Lua Envoy extension, configure the following arguments in the `EnvoyExtensions` block: + +| Arguments | Description | +| -------------- | ------------------------------------------------------------------------------------------------ | +| `ProxyType` | Determines the proxy type the extension applies to. The only supported value is `connect-proxy`. | +| `ListenerType` | Specifies if the extension is applied to the `inbound` or `outbound` listener. | +| `Script` | The Lua script that is configured to run by the HTTP Lua filter. | + +## Workflow + +There are two steps to configure the Lua Envoy extension: + +1. Configure EnvoyExtensions through `service-defaults` or `proxy-defaults`. +1. Apply the configuration entry. + +### Configure `EnvoyExtensions` + +To use Envoy extensions, you must configure and apply a `proxy-defaults` or `service-defaults` configuration entry with the Envoy extension. + +- When you configure Envoy extensions on `proxy-defaults`, they apply to every service. +- When you configure Envoy extensions on `service-defaults`, they apply to a specific service. + +Consul applies Envoy extensions configured in `proxy-defaults` before it applies extensions in `service-defaults`. As a result, the Envoy extension configuration in `service-defaults` may override configurations in `proxy-defaults`. + +The following example configures the Lua Envoy extension on every service by using the `proxy-defaults`. + + + + + +```hcl +Kind = "proxy-defaults" +Name = "global" +Protocol = "http" +EnvoyExtensions { + Name = "builtin/lua" + + Arguments = { + ProxyType = "connect-proxy" + Listener = "inbound" + Script = <<-EOS +function envoy_on_request(request_handle) + meta = request_handle:streamInfo():dynamicMetadata() + m = meta:get("consul") + request_handle:headers():add("x-consul-service", m["service"]) + request_handle:headers():add("x-consul-namespace", m["namespace"]) + request_handle:headers():add("x-consul-datacenter", m["datacenter"]) + request_handle:headers():add("x-consul-trust-domain", m["trust-domain"]) +end +EOS + } +} +``` + + + + + + +```hcl +{ + "kind": "proxy-defaults", + "name": "global", + "protocol": "http", + "envoy_extensions": [{ + "name": "builtin/lua", + "arguments": { + "proxy_type": "connect-proxy", + "listener": "inbound", + "script": "function envoy_on_request(request_handle)\nmeta = request_handle:streamInfo():dynamicMetadata()\nm = \nmeta:get("consul")\nrequest_handle:headers():add("x-consul-service", m["service"])\nrequest_handle:headers():add("x-consul-namespace", m["namespace"])\nrequest_handle:headers():add("x-consul-datacenter", m["datacenter"])\nrequest_handle:headers():add("x-consul-trust-domain", m["trust-domain"])\nend" + } + }] +} +``` + + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ProxyDefaults +metadata: + name: global +spec: + protocol: http + envoyExtensions: + name = "builtin/lua" + arguments: + proxyType: "connect-proxy" + listener: "inbound" + script: |- +function envoy_on_request(request_handle) + meta = request_handle:streamInfo():dynamicMetadata() + m = meta:get("consul") + request_handle:headers():add("x-consul-service", m["service"]) + request_handle:headers():add("x-consul-namespace", m["namespace"]) + request_handle:headers():add("x-consul-datacenter", m["datacenter"]) + request_handle:headers():add("x-consul-trust-domain", m["trust-domain"]) +end +``` + + + + + +For a full list of parameters for `EnvoyExtensions`, refer to the [`service-defaults`](/consul/docs/connect/config-entries/service-defaults#envoyextensions) and [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults#envoyextensions) configuration entries reference documentation. + +!> **Warning:** Applying `EnvoyExtensions` to `ProxyDefaults` may produce unintended consequences. We recommend enabling `EnvoyExtensions` with `ServiceDefaults` in most cases. + +Refer to [Configuration specification](#configuration-specification) section to find a full list of arguments for the Lua Envoy extension. + +### Apply the configuration entry + +Apply the `proxy-defaults` or `service-defaults` configuration entry. + + + + +```shell-session +$ consul config write lua-envoy-extension-proxy-defaults.hcl +``` + + + + +```shell-session +$ consul config write lua-envoy-extension-proxy-defaults.json + +``` + + + + +```shell-session +$ kubectl apply lua-envoy-extension-proxy-defaults.yaml +``` + + + + +## Examples + +In the following example, the `service-defaults` configure the Lua Envoy extension to insert the HTTP Lua filter for service `myservice` and add the Consul service name to the`x-consul-service` header for all inbound requests. The `ListenerType` makes it so that the extension applies only on the inbound listener of the service's connect proxy. + + + +```hcl +Kind = "service-defaults" +Name = "myservice" +EnvoyExtensions = [ + { + Name = "builtin/lua" + + Arguments = { + ProxyType = "connect-proxy" + Listener = "inbound" + Script = < + +Alternatively, you can apply the same extension configuration to [`proxy-defaults`](/consul/docs/connect/config-entries/proxy-defaults#envoyextensions) configuration entries. + +You can also specify multiple Lua filters through the Envoy extensions. They will not override each other. + + + +```hcl +Kind = "service-defaults" +Name = "myservice" +EnvoyExtensions = [ + { + Name = "builtin/lua", + Arguments = { + ProxyType = "connect-proxy" + Listener = "inbound" + Script = <<-EOF +function envoy_on_request(request_handle) + meta = request_handle:streamInfo():dynamicMetadata() + m = meta:get("consul") + request_handle:headers():add("x-consul-datacenter", m["datacenter1"]) +end + EOF + } + }, + { + Name = "builtin/lua", + Arguments = { + ProxyType = "connect-proxy" + Listener = "inbound" + Script = <<-EOF +function envoy_on_request(request_handle) + meta = request_handle:streamInfo():dynamicMetadata() + m = meta:get("consul") + request_handle:headers():add("x-consul-datacenter", m["datacenter2"]) +end + EOF + } + } +] +``` + + \ No newline at end of file diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 19e98a13676..73e7e3c535c 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -39,20 +39,18 @@ Consul supports **four major Envoy releases** at the beginning of each major Con | Consul Version | Compatible Envoy Versions | | ------------------- | -----------------------------------------------------------------------------------| +| 1.15.x | 1.25.1, 1.24.2, 1.23.4, 1.22.5 | | 1.14.x | 1.24.0, 1.23.1, 1.22.5, 1.21.5 | | 1.13.x | 1.23.1, 1.22.5, 1.21.5, 1.20.7 | -| 1.12.x | 1.22.5, 1.21.5, 1.20.7, 1.19.5 | - -1. Envoy 1.20.1 and earlier are vulnerable to [CVE-2022-21654](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21654) and [CVE-2022-21655](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21655). Both CVEs were patched in Envoy versions 1.18.6, 1.19.3, and 1.20.2. -Envoy 1.16.x and older releases are no longer supported (see [HCSEC-2022-07](https://discuss.hashicorp.com/t/hcsec-2022-07-consul-s-connect-service-mesh-affected-by-recent-envoy-security-releases/36332)). Consul 1.9.x clusters should be upgraded to 1.10.x and Envoy upgraded to the latest supported Envoy version for that release, 1.18.6. ### Envoy and Consul Dataplane -Consul Dataplane is a feature introduced in Consul v1.14. Because each version of Consul Dataplane supports one specific version of Envoy, you must use the following versions of Consul, Consul Dataplane, and Envoy together. +The Consul dataplane component was introduced in Consul v1.14 as a way to manage Envoy proxies without the use of Consul clients. Each new minor version of Consul is released with a new minor version of Consul dataplane, which packages both Envoy and the `consul-dataplane` binary in a single container image. For backwards compatability reasons, each new minor version of Consul will also support the previous minor version of Consul dataplane to allow for seamless upgrades. In addition, each minor version of Consul will support the next minor version of Consul dataplane to allow for extended dataplane support via newer versions of Envoy. -| Consul Version | Consul Dataplane Version | Bundled Envoy Version | -| ------------------- | ------------------------ | ---------------------- | -| 1.14.x | 1.0.x | 1.24.x | +| Consul Version | Consul Dataplane Version (Bundled Envoy Version) | +| ------------------- | ------------------------------------------------- | +| 1.15.x | 1.1.x (Envoy 1.25.x), 1.0.x (Envoy 1.24.x) | +| 1.14.x | 1.1.x (Envoy 1.25.x), 1.0.x (Envoy 1.24.x) | ## Getting Started @@ -113,7 +111,7 @@ If TLS is enabled on Consul, you will also need to add the following environment - [`CONSUL_CACERT`](/consul/commands#consul_cacert) - [`CONSUL_CLIENT_CERT`](/consul/commands#consul_client_cert) -- [`CONSUL_CLIENT_KEY`](//consulcommands#consul_client_key) +- [`CONSUL_CLIENT_KEY`](/consul/commands#consul_client_key) - [`CONSUL_HTTP_SSL`](/consul/commands#consul_http_ssl) ## Bootstrap Configuration @@ -126,7 +124,7 @@ Connect to a local Consul client agent and run the [`consul connect envoy` comma If you experience issues when bootstrapping Envoy proxies from the CLI, use the `-enable-config-gen-logging` flag to enable debug message logging. These logs can -help you troubleshoot issues that occur during the bootstrapping process. +help you troubleshoot issues that occur during the bootstrapping process. For more information about available flags and parameters, refer to the [`consul connect envoy CLI` reference](/consul/commands/connect/envoy). @@ -196,7 +194,7 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Envoy on Windows VMs -> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://consul.io/consu/tutorials/consul-windows-workloads?utm_source=docs) to learn how to deploy Consul and use its service mesh on Windows VMs. +> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](/consul/tutorials/developer-mesh/consul-windows-workloads) to learn how to deploy Consul and use its service mesh on Windows VMs. If you are running Consul on a Windows VM, attempting to bootstrap Envoy with the `consul connect envoy` command returns the following output: @@ -338,7 +336,7 @@ defaults that are inherited by all services. - `local_idle_timeout_ms` - In milliseconds, the idle timeout for HTTP requests to the local application instance. Applies to HTTP based protocols only. If not - specified, inherits the Envoy default for route idle timeouts (15s). A value of 0 + specified, inherits the Envoy default for route idle timeouts (15s). A value of 0 disables request timeouts. - `max_inbound_connections` - The maximum number of concurrent inbound connections diff --git a/website/content/docs/connect/proxies/index.mdx b/website/content/docs/connect/proxies/index.mdx index 799daebd4ed..f47aad3f5dc 100644 --- a/website/content/docs/connect/proxies/index.mdx +++ b/website/content/docs/connect/proxies/index.mdx @@ -7,29 +7,23 @@ description: >- # Service Mesh Proxy Overview -A Connect-aware proxy enables unmodified applications to use Connect. A +Proxies enable unmodified applications to connect to other services in the service mesh. A per-service proxy sidecar transparently handles inbound and outbound service connections, automatically wrapping and verifying TLS connections. Consul -includes its own built-in L4 proxy and has first class support for Envoy. You -can choose other proxies to plug in as well. This section describes how to +ships with a built-in L4 proxy and has first class support for Envoy. You +can plug other proxies into your environment as well. This section describes how to configure Envoy or the built-in proxy using Connect, and how to integrate the proxy of your choice. -To ensure that services only allow external connections established via -the Connect protocol, you should configure all services to only accept connections on a loopback address. +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. -~> **Deprecation Note:** Managed Proxies are a deprecated method for deploying -sidecar proxies, and have been removed in Consul 1.6. See [managed proxy -deprecation](/consul/docs/connect/proxies/managed-deprecated) for more -information. If you are using managed proxies we strongly recommend that you -switch service definitions for registering proxies. +## Dynamic upstreams require native integration -## Dynamic Upstreams Require Native Integration +Service mesh proxies do not support dynamic upstreams. If an application requires dynamic dependencies that are only available -at runtime, it must [natively integrate](/consul/docs/connect/native) -with Connect. After natively integrating, the HTTP API or -[DNS interface](/consul/docs/discovery/dns#connect-capable-service-lookups) -can be used. - -!> Connect proxies do not currently support dynamic upstreams. +at runtime, you must [natively integrate](/consul/docs/connect/native) +the application with Consul service mesh. After natively integrating, the HTTP API or +[DNS interface](/consul/docs/services/discovery/dns-static-lookups#service-mesh-enabled-service-lookups) +can be used. \ No newline at end of file diff --git a/website/content/docs/connect/proxies/integrate.mdx b/website/content/docs/connect/proxies/integrate.mdx index 20a203059d0..24b9208af61 100644 --- a/website/content/docs/connect/proxies/integrate.mdx +++ b/website/content/docs/connect/proxies/integrate.mdx @@ -138,7 +138,7 @@ documentation for details about supported configuration parameters. ### Service Discovery -Proxies can use Consul's [service discovery API](https://consul.io/%60/v1/health/connect/:service_id%60) to return all available, Connect-capable endpoints for a given service. This endpoint supports a `cached` query parameter, which uses [agent caching](/consul/api-docs/features/caching) to improve +Proxies can use Consul's [service discovery API](/consul/api-docs/health#list-service-instances-for-connect-enabled-service) to return all available, Connect-capable endpoints for a given service. This endpoint supports a `cached` query parameter, which uses [agent caching](/consul/api-docs/features/caching) to improve performance. The API package provides a [`UseCache`] query option to leverage caching. In addition to performance improvements, using the cache makes the mesh more resilient to Consul server outages. This is because the mesh "fails static" with the last known set of service instances still used, rather than errors on new connections. diff --git a/website/content/docs/connect/proxies/managed-deprecated.mdx b/website/content/docs/connect/proxies/managed-deprecated.mdx deleted file mode 100644 index 4ac7adeb3a8..00000000000 --- a/website/content/docs/connect/proxies/managed-deprecated.mdx +++ /dev/null @@ -1,278 +0,0 @@ ---- -layout: docs -page_title: Managed Proxy for Connect (Legacy) -description: >- - Consul's service mesh originally included a proxy manager that was deprecated in version 1.6. Learn about the reasons for its deprecation and how it worked with this legacy documentation. ---- - -# Managed Proxy for Connect Legacy Documentation - -Consul Connect was first released as a beta feature in Consul 1.2.0. The initial -release included a feature called "Managed Proxies". Managed proxies were -Connect proxies where the proxy process was started, configured, and stopped by -Consul. They were enabled via basic configurations within the service -definition. - -!> **Consul 1.6.0 removes Managed Proxies completely.** -This documentation is provided for prior versions only. You may consider using -[sidecar service -registrations](/consul/docs/connect/registration/sidecar-service) instead. - -Managed proxies have been deprecated since Consul 1.3 and have been fully removed -in Consul 1.6. Anyone using Managed Proxies should aim to change their workflow -as soon as possible to avoid issues with a later upgrade. - -After transitioning away from all managed proxy usage, the `proxy` subdirectory inside [`data_dir`](/consul/docs/agent/config/cli-flags#_data_dir) (specified in Consul config) can be deleted to remove extraneous configuration files and free up disk space. - -**new and known issues will not be fixed**. - -## Deprecation Rationale - -Originally managed proxies traded higher implementation complexity for an easier -"getting started" user experience. After seeing how Connect was investigated and -adopted during beta it became obvious that they were not the best trade off. - -Managed proxies only really helped in local testing or VM-per-service based -models whereas a lot of users jumped straight to containers where they are not -helpful. They also add only targeted fairly basic supervisor features which -meant most people would want to use something else in production for consistency -with other workloads. So the high implementation cost of building robust process -supervision didn't actually benefit most real use-cases. - -Instead of this Connect 1.3.0 introduces the concept of [sidecar service -registrations](/consul/docs/connect/registration/sidecar-service) which -have almost all of the benefits of simpler configuration but without any of the -additional process management complexity. As a result they can be used to -simplify configuration in both container-based and realistic production -supervisor settings. - -## Managed Proxy Documentation - -As the managed proxy features continue to be supported for now, the rest of this -page will document how they work in the interim. - --> **Deprecation Note:** It's _strongly_ recommended you do not build anything -using Managed proxies and consider using [sidecar service -registrations](/consul/docs/connect/registration/sidecar-service) instead. - -Managed proxies are given -a unique proxy-specific ACL token that allows read-only access to Connect -information for the specific service the proxy is representing. This ACL -token is more restrictive than can be currently expressed manually in -an ACL policy. - -The default managed proxy is a basic proxy built-in to Consul and written -in Go. Having a basic built-in proxy allows Consul to have a reasonable default -with performance that is good enough for most workloads. In some basic -benchmarks, the service-to-service communication over the built-in proxy -could sustain 5 Gbps with sub-millisecond latency. Therefore, -the performance impact of even the basic built-in proxy is minimal. - -Consul will be integrating with advanced proxies in the near future to support -more complex configurations and higher performance. The configuration below is -all for the built-in proxy. - --> **Security Note:** 1.) Managed proxies can only be configured -via agent configuration files. They _cannot_ be registered via the HTTP API. -And 2.) Managed proxies are not started at all if Consul is running as root. -Both of these default configurations help prevent arbitrary process -execution or privilege escalation. This behavior can be configured -[per-agent](/consul/docs/agent/config). - -### Lifecycle - -The Consul agent starts managed proxies on demand and supervises them, -restarting them if they crash. The lifecycle of the proxy process is decoupled -from the agent so if the agent crashes or is restarted for an upgrade, the -managed proxy instances will _not_ be stopped. - -Note that this behavior while desirable in production might leave proxy -processes running indefinitely if you manually stop the agent and clear its -data dir during testing. - -To terminate a managed proxy cleanly you need to deregister the service that -requested it. If the agent is already stopped and will not be restarted again, -you may choose to locate the proxy processes and kill them manually. - -While in `-dev` mode, unless a `-data-dir` is explicitly set, managed proxies -switch to being killed when the agent exits since it can't store state in order -to re-adopt them on restart. - -### Minimal Configuration - -Managed proxies are configured within a -[service definition](/consul/docs/discovery/services). The simplest possible -managed proxy configuration is an empty configuration. This enables the -default managed proxy and starts a listener for that service: - -```json -{ - "service": { - "name": "redis", - "port": 6379, - "connect": { "proxy": {} } - } -} -``` - -The listener is started on random port within the configured Connect -port range. It can be discovered using the -[DNS interface](/consul/docs/discovery/dns#connect-capable-service-lookups) -or -[Catalog API](#). -In most cases, service-to-service communication is established by -a proxy configured with upstreams (described below), which handle the -discovery transparently. - -### Upstream Configuration - -To transparently discover and establish Connect-based connections to -dependencies, they must be configured with a static port on the managed -proxy configuration: - -```json -{ - "service": { - "name": "web", - "port": 8080, - "connect": { - "proxy": { - "upstreams": [ - { - "destination_name": "redis", - "local_bind_port": 1234 - } - ] - } - } - } -} -``` - -In the example above, -"redis" is configured as an upstream with static port 1234 for service "web". -When a TCP connection is established on port 1234, the proxy -will find Connect-compatible "redis" services via Consul service discovery -and establish a TLS connection identifying as "web". - -~> **Security:** Any application that can communicate to the configured -static port will be able to masquerade as the source service ("web" in the -example above). You must either trust any loopback access on that port or -use namespacing techniques provided by your operating system. - --> **Deprecation Note:** versions 1.2.0 to 1.3.0 required specifying `upstreams` -as part of the opaque `config` that is passed to the proxy. However, since -1.3.0, the `upstreams` configuration is now specified directly under the -`proxy` key. Old service definitions using the nested config will continue to -work and have the values copied into the new location. This allows the upstreams -to be registered centrally rather than being part of the local-only config -passed to the proxy instance. - -For full details of the additional configurable options available when using the -built-in proxy see the [built-in proxy configuration -reference](/consul/docs/connect/configuration). - -### Prepared Query Upstreams - -The upstream destination may also be a -[prepared query](/consul/api-docs/query). -This allows complex service discovery behavior such as connecting to -the nearest neighbor or filtering by tags. - -For example, given a prepared query named "nearest-redis" that is -configured to route to the nearest Redis instance, an upstream can be -configured to route to this query. In the example below, any TCP connection -to port 1234 will attempt a Connect-based connection to the nearest Redis -service. - -```json -{ - "service": { - "name": "web", - "port": 8080, - "connect": { - "proxy": { - "upstreams": [ - { - "destination_name": "redis", - "destination_type": "prepared_query", - "local_bind_port": 1234 - } - ] - } - } - } -} -``` - -For full details of the additional configurable options available when using the -built-in proxy see the [built-in proxy configuration -reference](/consul/docs/connect/configuration). - -### Custom Managed Proxy - -[Custom proxies](/consul/docs/connect/proxies/integrate) can also be -configured to run as a managed proxy. To configure custom proxies, specify -an alternate command to execute for the proxy: - -```json -{ - "service": { - "name": "web", - "port": 8080, - "connect": { - "proxy": { - "exec_mode": "daemon", - "command": ["/usr/bin/my-proxy", "-flag-example"], - "config": { - "foo": "bar" - } - } - } - } -} -``` - -The `exec_mode` value specifies how the proxy is executed. The only -supported value at this time is "daemon". The command is the binary and -any arguments to execute. -The "daemon" mode expects a proxy to run as a long-running, blocking -process. It should not double-fork into the background. The custom -proxy should retrieve its configuration (such as the port to run on) -via the [custom proxy integration APIs](/consul/docs/connect/proxies/integrate). - -The default proxy command can be changed at an agent-global level -in the agent configuration. An example in HCL format is shown below. - -``` -connect { - proxy_defaults { - command = ["/usr/bin/my-proxy"] - } -} -``` - -With this configuration, all services registered without an explicit -proxy command will use `my-proxy` instead of the default built-in proxy. - -The `config` key is an optional opaque JSON object which will be passed through -to the proxy via the proxy configuration endpoint to allow any configuration -options the proxy needs to be specified. See the [built-in proxy -configuration reference](/consul/docs/connect/configuration) -for details of config options that can be passed when using the built-in proxy. - -### Managed Proxy Logs - -Managed proxies have both stdout and stderr captured in log files in the agent's -`data_dir`. They can be found in -`/proxy/logs/-std{err,out}.log`. - -The built-in proxy will inherit its log level from the agent so if the agent is -configured with `log_level = DEBUG`, a proxy it starts will also output `DEBUG` -level logs showing service discovery, certificate and authorization information. - -~> **Note:** In `-dev` mode there is no `data_dir` unless one is explicitly -configured so logging is disabled. You can access logs by providing the -[`-data-dir`](/consul/docs/agent/config/cli-flags#_data_dir) CLI option. If a data dir is -configured, this will also cause proxy processes to stay running when the agent -terminates as described in [Lifecycle](#lifecycle). diff --git a/website/content/docs/connect/registration/index.mdx b/website/content/docs/connect/registration/index.mdx index 24c7a812519..96db3278877 100644 --- a/website/content/docs/connect/registration/index.mdx +++ b/website/content/docs/connect/registration/index.mdx @@ -7,14 +7,13 @@ description: >- # Service Mesh Proxy Overview -To make Connect aware of proxies you will need to register them in a [service -definition](/consul/docs/discovery/services), just like you would register any other service with Consul. This section outlines your options for registering Connect proxies, either using independent registrations, or in nested sidecar registrations. +To enable service mesh proxies, you must define and register them with Consul. Proxies are a type of service in Consul that facilitate highly secure communication between services in a service mesh. The topics in the section outline your options for registering service mesh proxies. You can register proxies independently or nested inside a sidecar service registration. -## Proxy Service Registration +## Proxy service registration To register proxies with independent proxy service registrations, you can define them in either in config files or via the API just like any other service. Learn more about all of the options you can define when registering your proxy service in the [proxy registration documentation](/consul/docs/connect/registration/service-registration). -## Sidecar Service Registration +## Sidecar service registration To reduce the amount of boilerplate needed for a sidecar proxy, application service definitions may define an inline sidecar service block. This is an opinionated diff --git a/website/content/docs/connect/registration/service-registration.mdx b/website/content/docs/connect/registration/service-registration.mdx index 2bfe409b5b8..666f739fa17 100644 --- a/website/content/docs/connect/registration/service-registration.mdx +++ b/website/content/docs/connect/registration/service-registration.mdx @@ -9,6 +9,7 @@ description: >- This topic describes how to declare a proxy as a `connect-proxy` in service definitions. The `kind` must be declared and information about the service they represent must be provided to function as a Consul service mesh proxy. + ## Configuration Configure a service mesh proxy using the following syntax: @@ -178,9 +179,7 @@ You can configure the service mesh proxy to create listeners for upstream servic ### Upstream Configuration Examples -Upstreams support multiple destination types. The following examples include information about each implementation. - --> **Snake case**: The examples in this topic use `snake_case` because the syntax is supported in configuration files and API registrations. See [Service Definition Parameter Case](/consul/docs/discovery/services#service-definition-parameter-case) for additional information. +Upstreams support multiple destination types. The following examples include information about each implementation. Note that the examples in this topic use snake case, which is a convention that separates words with underscores, because the format is supported in configuration files and API registrations. @@ -300,10 +299,7 @@ The proxy will default to `direct` mode if a mode cannot be determined from the The following examples show additional configuration for transparent proxies. -Added in v1.10.0. - --> Note that `snake_case` is used here as it works in both [config file and API -registrations](/consul/docs/discovery/services#service-definition-parameter-case). +Note that the examples in this topic use snake case, which is a convention that separates words with underscores, because the format is supported in configuration files and API registrations. #### Configure a proxy listener for outbound traffic on port 22500 @@ -328,8 +324,7 @@ registrations](/consul/docs/discovery/services#service-definition-parameter-case The following examples show all possible mesh gateway configurations. --> Note that `snake_case` is used here as it works in both [config file and API -registrations](/consul/docs/discovery/services#service-definition-parameter-case). + Note that the examples in this topic use snake case, which is a convention that separates words with underscores, because the format is supported in configuration files and API registrations. #### Using a Local/Egress Gateway in the Local Datacenter @@ -385,9 +380,8 @@ The following examples show possible configurations to expose HTTP paths through Exposing paths through Envoy enables a service to protect itself by only listening on localhost, while still allowing non-Connect-enabled applications to contact an HTTP endpoint. Some examples include: exposing a `/metrics` path for Prometheus or `/healthz` for kubelet liveness checks. - --> Note that `snake_case` is used here as it works in both [config file and API -registrations](/consul/docs/discovery/services#service-definition-parameter-case). + +Note that the examples in this topic use snake case, which is a convention that separates words with underscores, because the format is supported in configuration files and API registrations. #### Expose listeners in Envoy for HTTP and GRPC checks registered with the local Consul agent diff --git a/website/content/docs/connect/registration/sidecar-service.mdx b/website/content/docs/connect/registration/sidecar-service.mdx index adf5ef20f03..cfda3b22ab7 100644 --- a/website/content/docs/connect/registration/sidecar-service.mdx +++ b/website/content/docs/connect/registration/sidecar-service.mdx @@ -7,20 +7,16 @@ description: >- # Register a Service Mesh Proxy in a Service Registration -Connect proxies are typically deployed as "sidecars" that run on the same node -as the single service instance that they handle traffic for. They might be on -the same VM or running as a separate container in the same network namespace. +This topic describes how to declare a proxy as a _sidecar_ proxy. +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. -To simplify the configuration experience when deploying a sidecar for a service -instance, Consul 1.3 introduced a new field in the Connect block of the [service -definition](/consul/docs/discovery/services). +## Configuration -The `connect.sidecar_service` field is a complete nested service definition on -which almost any regular service definition field can be set. The exceptions are -[noted below](#limitations). If used, the service definition is treated -identically to another top-level service definition. The value of the nested -definition is that _all fields are optional_ with some opinionated defaults -applied that make setting up a sidecar proxy much simpler. +Add the `connect.sidecar_service` block to your service definition file and specify the parameters to configure sidecar proxy behavior. 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. ## Minimal Example @@ -134,7 +130,7 @@ proxy. - `kind` - Defaults to `connect-proxy`. This can't be overridden currently. - `check`, `checks` - By default we add a TCP check on the local address and port for the proxy, and a [service alias - check](/consul/docs/discovery/checks#alias) for the parent service. If either + check](/consul/docs/services/usage/checks#alias-checks) for the parent service. If either `check` or `checks` fields are set, only the provided checks are registered. - `proxy.destination_service_name` - Defaults to the parent service name. - `proxy.destination_service_id` - Defaults to the parent service ID. @@ -143,8 +139,7 @@ proxy. ## Limitations -Almost all fields in a [service definition](/consul/docs/discovery/services) may be -set on the `connect.sidecar_service` except for the following: +The following fields are not supported in the `connect.sidecar_service` block: - `id` - Sidecar services get an ID assigned and it is an error to override this. This ensures the agent can correctly deregister the sidecar service @@ -153,9 +148,6 @@ set on the `connect.sidecar_service` except for the following: unset this to make the registration be for a regular non-connect-proxy service. - `connect.sidecar_service` - Service definitions can't be nested recursively. -- `connect.proxy` - (Deprecated) [Managed - proxies](/consul/docs/connect/proxies/managed-deprecated) can't be defined on a - sidecar. - `connect.native` - Currently the `kind` is fixed to `connect-proxy` and it's an error to register a `connect-proxy` that is also Connect-native. diff --git a/website/content/docs/consul-vs-other/dns-tools-compare.mdx b/website/content/docs/consul-vs-other/dns-tools-compare.mdx index 6aab1440668..f4d27de8ab4 100644 --- a/website/content/docs/consul-vs-other/dns-tools-compare.mdx +++ b/website/content/docs/consul-vs-other/dns-tools-compare.mdx @@ -12,5 +12,5 @@ description: >- Consul was originally designed as a centralized service registry for any cloud environment that dynamically tracks services as they are added, changed, or removed within a compute infrastructure. Consul maintains a catalog of these registered services and their attributes, such as IP addresses or service name. For more information, refer to [What is Service Discovery?](/consul/docs/concepts/service-discovery). -As a result, Consul can also provide basic DNS functionality, including [lookups, alternate domains, and access controls](/consul/docs/discovery/dns). Since Consul is platform agnostic, you can retrieve service information across both cloud and on-premises data centers. Consul does not natively support some advanced DNS capabilities, such as filters or advanced routing logic. However, you can integrate Consul with existing DNS solutions, such as [NS1](https://help.ns1.com/hc/en-us/articles/360039417093-NS1-Consul-Integration-Overview) and [DNSimple](https://blog.dnsimple.com/2022/05/consul-integration/), to support these advanced capabilities. +As a result, Consul can also provide basic DNS functionality, including [lookups, alternate domains, and access controls](/consul/docs/services/discovery/dns-overview). Since Consul is platform agnostic, you can retrieve service information across both cloud and on-premises data centers. Consul does not natively support some advanced DNS capabilities, such as filters or advanced routing logic. However, you can integrate Consul with existing DNS solutions, such as [NS1](https://help.ns1.com/hc/en-us/articles/360039417093-NS1-Consul-Integration-Overview) and [DNSimple](https://blog.dnsimple.com/2022/05/consul-integration/), to support these advanced capabilities. diff --git a/website/content/docs/discovery/checks.mdx b/website/content/docs/discovery/checks.mdx deleted file mode 100644 index 0c2945602a2..00000000000 --- a/website/content/docs/discovery/checks.mdx +++ /dev/null @@ -1,885 +0,0 @@ ---- -layout: docs -page_title: Configure Health Checks -description: >- - Agents can be configured to periodically perform custom checks on the health of a service instance or node. Learn about the types of health checks and how to define them in agent and service configuration files. ---- - -# Health Checks - -One of the primary roles of the agent is management of system-level and application-level health -checks. A health check is considered to be application-level if it is associated with a -service. If not associated with a service, the check monitors the health of the entire node. - -Review the [health checks tutorial](/consul/tutorials/developer-discovery/service-registration-health-checks) -to get a more complete example on how to leverage health check capabilities in Consul. - -A check is defined in a configuration file or added at runtime over the HTTP interface. Checks -created via the HTTP interface persist with that node. - -There are several types of checks: - -- [`Script + Interval`](#script-check) - These checks invoke an external application - that performs the health check. - -- [`HTTP + Interval`](#http-check) - These checks make an HTTP `GET` request to the specified URL - in the health check definition. - -- [`TCP + Interval`](#tcp-check) - These checks attempt a TCP connection to the specified - address and port in the health check definition. - -- [`UDP + Interval`](#udp-check) - These checks direct the client to periodically send UDP datagrams - to the specified address and port in the health check definition. - -- [`OSService + Interval`](#osservice-check) - These checks periodically direct the Consul agent to monitor - the health of a service running on the host operating system. - -- [`Time to Live (TTL)`](#time-to-live-ttl-check) - These checks attempt an HTTP connection after a given TTL elapses. - -- [`Docker + Interval`](#docker-check) - These checks invoke an external application that - is packaged within a Docker container. - -- [`gRPC + Interval`](#grpc-check) - These checks are intended for applications that support the standard - [gRPC health checking protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - -- [`H2ping + Interval`](#h2ping-check) - These checks test an endpoint that uses HTTP/2 - by connecting to the endpoint and sending a ping frame. - -- [`Alias`](#alias-check) - These checks alias the health state of another registered - node or service. - - -## Registering a health check - -There are three ways to register a service with health checks: - -1. Start or reload a Consul agent with a service definition file in the - [agent's configuration directory](/consul/docs/agent#configuring-consul-agents). -1. Call the - [`/agent/service/register`](/consul/api-docs/agent/service#register-service) - HTTP API endpoint to register the service. -1. Use the - [`consul services register`](/consul/commands/services/register) - CLI command to register the service. - -When a service is registered using the HTTP API endpoint or CLI command, -the checks persist in the Consul data folder across Consul agent restarts. - -## Types of checks - -This section describes the available types of health checks you can use to -automatically monitor the health of a service instance or node. - --> **To manually mark a service unhealthy:** Use the maintenance mode - [CLI command](/consul/commands/maint) or - [HTTP API endpoint](/consul/api-docs/agent#enable-maintenance-mode) - to temporarily remove one or all service instances on a node - from service discovery DNS and HTTP API query results. - -### Script check - -Script checks periodically invoke an external application that performs the health check, -exits with an appropriate exit code, and potentially generates some output. -The specified `interval` determines the time between check invocations. -The output of a script check is limited to 4KB. -Larger outputs are truncated. - -By default, script checks are configured with a timeout equal to 30 seconds. -To configure a custom script check timeout value, -specify the `timeout` field in the check definition. -After reaching the timeout on a Windows system, -Consul waits for any child processes spawned by the script to finish. -After reaching the timeout on other systems, -Consul attempts to force-kill the script and any child processes it spawned. - -Script checks are not enabled by default. -To enable a Consul agent to perform script checks, -use one of the following agent configuration options: - -- [`enable_local_script_checks`](/consul/docs/agent/config/cli-flags#_enable_local_script_checks): - Enable script checks defined in local config files. - Script checks registered using the HTTP API are not allowed. -- [`enable_script_checks`](/consul/docs/agent/config/cli-flags#_enable_script_checks): - Enable script checks no matter how they are registered. - - ~> **Security Warning:** - Enabling non-local script checks in some configurations may introduce - a remote execution vulnerability known to be targeted by malware. - We strongly recommend `enable_local_script_checks` instead. - For more information, refer to - [this blog post](https://www.hashicorp.com/blog/protecting-consul-from-rce-risk-in-specific-configurations). - -The following service definition file snippet is an example -of a script check definition: - - - -```hcl -check = { - id = "mem-util" - name = "Memory utilization" - args = ["/usr/local/bin/check_mem.py", "-limit", "256MB"] - interval = "10s" - timeout = "1s" -} -``` - -```json -{ - "check": { - "id": "mem-util", - "name": "Memory utilization", - "args": ["/usr/local/bin/check_mem.py", "-limit", "256MB"], - "interval": "10s", - "timeout": "1s" - } -} -``` - - - -#### Check script conventions - -A check script's exit code is used to determine the health check status: - -- Exit code 0 - Check is passing -- Exit code 1 - Check is warning -- Any other code - Check is failing - -Any output of the script is captured and made available in the -`Output` field of checks included in HTTP API responses, -as in this example from the [local service health endpoint](/consul/api-docs/agent/service#by-name-json). - -### HTTP check - -HTTP checks periodically make an HTTP `GET` request to the specified URL, -waiting the specified `interval` amount of time between requests. -The status of the service depends on the HTTP response code: any `2xx` code is -considered passing, a `429 Too ManyRequests` is a warning, and anything else is -a failure. This type of check -should be preferred over a script that uses `curl` or another external process -to check a simple HTTP operation. By default, HTTP checks are `GET` requests -unless the `method` field specifies a different method. Additional request -headers can be set through the `header` field which is a map of lists of -strings, such as `{"x-foo": ["bar", "baz"]}`. - -By default, HTTP checks are configured with a request timeout equal to 10 seconds. -To configure a custom HTTP check timeout value, -specify the `timeout` field in the check definition. -The output of an HTTP check is limited to approximately 4KB. -Larger outputs are truncated. -HTTP checks also support TLS. By default, a valid TLS certificate is expected. -Certificate verification can be turned off by setting the `tls_skip_verify` -field to `true` in the check definition. When using TLS, the SNI is implicitly -determined from the URL if it uses a hostname instead of an IP address. -You can explicitly set the SNI value by setting `tls_server_name`. - -Consul follows HTTP redirects by default. -To disable redirects, set the `disable_redirects` field to `true`. - -The following service definition file snippet is an example -of an HTTP check definition: - - - -```hcl -check = { - id = "api" - name = "HTTP API on port 5000" - http = "https://localhost:5000/health" - tls_server_name = "" - tls_skip_verify = false - method = "POST" - header = { - Content-Type = ["application/json"] - } - body = "{\"method\":\"health\"}" - disable_redirects = true - interval = "10s" - timeout = "1s" -} -``` - -```json -{ - "check": { - "id": "api", - "name": "HTTP API on port 5000", - "http": "https://localhost:5000/health", - "tls_server_name": "", - "tls_skip_verify": false, - "method": "POST", - "header": { "Content-Type": ["application/json"] }, - "body": "{\"method\":\"health\"}", - "interval": "10s", - "timeout": "1s" - } -} -``` - - - -### TCP check - -TCP checks periodically make a TCP connection attempt to the specified IP/hostname and port, waiting `interval` amount of time between attempts. -If no hostname is specified, it defaults to "localhost". -The health check status is `success` if the target host accepts the connection attempt, -otherwise the status is `critical`. In the case of a hostname that -resolves to both IPv4 and IPv6 addresses, an attempt is made to both -addresses, and the first successful connection attempt results in a -successful check. This type of check should be preferred over a script that -uses `netcat` or another external process to check a simple socket operation. - -By default, TCP checks are configured with a request timeout equal to 10 seconds. -To configure a custom TCP check timeout value, -specify the `timeout` field in the check definition. - -The following service definition file snippet is an example -of a TCP check definition: - - - -```hcl -check = { - id = "ssh" - name = "SSH TCP on port 22" - tcp = "localhost:22" - interval = "10s" - timeout = "1s" -} -``` - -```json -{ - "check": { - "id": "ssh", - "name": "SSH TCP on port 22", - "tcp": "localhost:22", - "interval": "10s", - "timeout": "1s" - } -} -``` - - - -### UDP check - -UDP checks periodically direct the Consul agent to send UDP datagrams -to the specified IP/hostname and port, -waiting `interval` amount of time between attempts. -The check status is set to `success` if any response is received from the targeted UDP server. -Any other result sets the status to `critical`. - -By default, UDP checks are configured with a request timeout equal to 10 seconds. -To configure a custom UDP check timeout value, -specify the `timeout` field in the check definition. -If any timeout on read exists, the check is still considered healthy. - -The following service definition file snippet is an example -of a UDP check definition: - - - -```hcl -check = { - id = "dns" - name = "DNS UDP on port 53" - udp = "localhost:53" - interval = "10s" - timeout = "1s" -} -``` - -```json -{ - "check": { - "id": "dns", - "name": "DNS UDP on port 53", - "udp": "localhost:53", - "interval": "10s", - "timeout": "1s" - } -} -``` - - - -### OSService check - -OSService checks periodically direct the Consul agent to monitor the health of a service running on -the host operating system as either a Windows service (Windows) or a SystemD service (Unix). -The check is logged as `healthy` if the service is running. -If it is stopped or not running, the status is `critical`. All other results set -the status to `warning`, which indicates that the check is not reliable because -an issue is preventing the check from determining the health of the service. - -The following service definition file snippet is an example -of an OSService check definition: - - - -```hcl -check = { - id = "myco-svctype-svcname-001" - name = "svcname-001 Windows Service Health" - service_id = "flash_pnl_1" - os_service = "myco-svctype-svcname-001" - interval = "10s" -} -``` - -```json -{ - "check": { - "id": "myco-svctype-svcname-001", - "name": "svcname-001 Windows Service Health", - "service_id": "flash_pnl_1", - "os_service": "myco-svctype-svcname-001", - "interval": "10s" - } -} -``` - - - -### Time to live (TTL) check - -TTL checks retain their last known state for the specified `ttl` duration. -The state of the check updates periodically over the HTTP interface. -If the `ttl` duration elapses before a new check update -is provided over the HTTP interface, -the check is set to `critical` state. - -This mechanism relies on the application to directly report its health. -For example, a healthy app can periodically `PUT` a status update to the HTTP endpoint. -Then, if the app is disrupted and unable to perform this update -before the TTL expires, the health check enters the `critical` state. -The endpoints used to update health information for a given check are: [pass](/consul/api-docs/agent/check#ttl-check-pass), -[warn](/consul/api-docs/agent/check#ttl-check-warn), [fail](/consul/api-docs/agent/check#ttl-check-fail), -and [update](/consul/api-docs/agent/check#ttl-check-update). TTL checks also persist their -last known status to disk. This persistence allows the Consul agent to restore the last known -status of the check across agent restarts. Persisted check status is valid through the -end of the TTL from the time of the last check. - -To manually mark a service unhealthy, -it is far more convenient to use the maintenance mode -[CLI command](/consul/commands/maint) or -[HTTP API endpoint](/consul/api-docs/agent#enable-maintenance-mode) -rather than a TTL health check with arbitrarily high `ttl`. - -The following service definition file snippet is an example -of a TTL check definition: - - - -```hcl -check = { - id = "web-app" - name = "Web App Status" - notes = "Web app does a curl internally every 10 seconds" - ttl = "30s" -} -``` - -```json -{ - "check": { - "id": "web-app", - "name": "Web App Status", - "notes": "Web app does a curl internally every 10 seconds", - "ttl": "30s" - } -} -``` - - - -### Docker check - -These checks depend on periodically invoking an external application that -is packaged within a Docker Container. The application is triggered within the running -container through the Docker Exec API. We expect that the Consul agent user has access -to either the Docker HTTP API or the unix socket. Consul uses `$DOCKER_HOST` to -determine the Docker API endpoint. The application is expected to run, perform a health -check of the service running inside the container, and exit with an appropriate exit code. -The check should be paired with an invocation interval. The shell on which the check -has to be performed is configurable, making it possible to run containers which -have different shells on the same host. -The output of a Docker check is limited to 4KB. -Larger outputs are truncated. - -Docker checks are not enabled by default. -To enable a Consul agent to perform Docker checks, -use one of the following agent configuration options: - -- [`enable_local_script_checks`](/consul/docs/agent/config/cli-flags#_enable_local_script_checks): - Enable script checks defined in local config files. - Script checks registered using the HTTP API are not allowed. - -- [`enable_script_checks`](/consul/docs/agent/config/cli-flags#_enable_script_checks): - Enable script checks no matter how they are registered. - - !> **Security Warning:** - We recommend using `enable_local_script_checks` instead of `enable_script_checks` in production - environments, as remote script checks are more vulnerable to malware attacks. Learn more about how [script checks can be exploited](https://www.hashicorp.com/blog/protecting-consul-from-rce-risk-in-specific-configurations#how-script-checks-can-be-exploited). - -The following service definition file snippet is an example -of a Docker check definition: - - - -```hcl -check = { - id = "mem-util" - name = "Memory utilization" - docker_container_id = "f972c95ebf0e" - shell = "/bin/bash" - args = ["/usr/local/bin/check_mem.py"] - interval = "10s" -} -``` - -```json -{ - "check": { - "id": "mem-util", - "name": "Memory utilization", - "docker_container_id": "f972c95ebf0e", - "shell": "/bin/bash", - "args": ["/usr/local/bin/check_mem.py"], - "interval": "10s" - } -} -``` - - - -### gRPC check - -gRPC checks are intended for applications that support the standard -[gRPC health checking protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). -The state of the check will be updated by periodically probing the configured endpoint, -waiting `interval` amount of time between attempts. - -By default, gRPC checks are configured with a timeout equal to 10 seconds. -To configure a custom Docker check timeout value, -specify the `timeout` field in the check definition. - -gRPC checks default to not using TLS. -To enable TLS, set `grpc_use_tls` in the check definition. -If TLS is enabled, then by default, a valid TLS certificate is expected. -Certificate verification can be turned off by setting the -`tls_skip_verify` field to `true` in the check definition. -To check on a specific service instead of the whole gRPC server, -add the service identifier after the `gRPC` check's endpoint. - -The following example shows a gRPC check for a whole application: - - - -```hcl -check = { - id = "mem-util" - name = "Service health status" - grpc = "127.0.0.1:12345" - grpc_use_tls = true - interval = "10s" -} -``` - -```json -{ - "check": { - "id": "mem-util", - "name": "Service health status", - "grpc": "127.0.0.1:12345", - "grpc_use_tls": true, - "interval": "10s" - } -} -``` - - - -The following example shows a gRPC check for the specific `my_service` service: - - - -```hcl -check = { - id = "mem-util" - name = "Service health status" - grpc = "127.0.0.1:12345/my_service" - grpc_use_tls = true - interval = "10s" -} -``` - -```json -{ - "check": { - "id": "mem-util", - "name": "Service health status", - "grpc": "127.0.0.1:12345/my_service", - "grpc_use_tls": true, - "interval": "10s" - } -} -``` - - - -### H2ping check - -H2ping checks test an endpoint that uses http2 by connecting to the endpoint -and sending a ping frame, waiting `interval` amount of time between attempts. -If the ping is successful within a specified timeout, -then the check status is set to `success`. - -By default, h2ping checks are configured with a request timeout equal to 10 seconds. -To configure a custom h2ping check timeout value, -specify the `timeout` field in the check definition. - -TLS is enabled by default. -To disable TLS and use h2c, set `h2ping_use_tls` to `false`. -If TLS is not disabled, a valid certificate is required unless `tls_skip_verify` is set to `true`. - -The following service definition file snippet is an example -of an h2ping check definition: - - - -```hcl -check = { - id = "h2ping-check" - name = "h2ping" - h2ping = "localhost:22222" - interval = "10s" - h2ping_use_tls = false -} -``` - -```json -{ - "check": { - "id": "h2ping-check", - "name": "h2ping", - "h2ping": "localhost:22222", - "interval": "10s", - "h2ping_use_tls": false - } -} -``` - - - -### Alias check - -These checks alias the health state of another registered -node or service. The state of the check updates asynchronously, but is -nearly instant. For aliased services on the same agent, the local state is monitored -and no additional network resources are consumed. For other services and nodes, -the check maintains a blocking query over the agent's connection with a current -server and allows stale requests. If there are any errors in watching the aliased -node or service, the check state is set to `critical`. -For the blocking query, the check uses the ACL token set on the service or check definition. -If no ACL token is set in the service or check definition, -the blocking query uses the agent's default ACL token -([`acl.tokens.default`](/consul/docs/agent/config/config-files#acl_tokens_default)). - -~> **Configuration info**: The alias check configuration expects the alias to be -registered on the same agent as the one you are aliasing. If the service is -not registered with the same agent, `"alias_node": ""` must also be -specified. When using `alias_node`, if no service is specified, the check will -alias the health of the node. If a service is specified, the check will alias -the specified service on this particular node. - -The following service definition file snippet is an example -of an alias check for a local service: - - - -```hcl -check = { - id = "web-alias" - alias_service = "web" -} -``` - -```json -{ - "check": { - "id": "web-alias", - "alias_service": "web" - } -} -``` - - - -## Check definition - -This section covers some of the most common options for check definitions. -For a complete list of all check options, refer to the -[Register Check HTTP API endpoint documentation](/consul/api-docs/agent/check#json-request-body-schema). - --> **Casing for check options:** - The correct casing for an option depends on whether the check is defined in - a service definition file or an HTTP API JSON request body. - For example, the option `deregister_critical_service_after` in a service - definition file is instead named `DeregisterCriticalServiceAfter` in an - HTTP API JSON request body. - -#### General options - -- `name` `(string: )` - Specifies the name of the check. - -- `id` `(string: "")` - Specifies a unique ID for this check on this node. - - If unspecified, Consul defines the check id by: - - If the check definition is embedded within a service definition file, - a unique check id is auto-generated. - - Otherwise, the `id` is set to the value of `name`. - If names might conflict, you must provide unique IDs to avoid - overwriting existing checks with the same id on this node. - -- `interval` `(string: )` - Specifies - the frequency at which to run this check. - Required for all check types except TTL and alias checks. - - The value is parsed by Go's `time` package, and has the following - [formatting specification](https://golang.org/pkg/time/#ParseDuration): - - > A duration string is a possibly signed sequence of decimal numbers, each with - > optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". - > Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". - -- `service_id` `(string: )` - Specifies - the ID of a service instance to associate this check with. - That service instance must be on this node. - If not specified, this check is treated as a node-level check. - For more information, refer to the - [service-bound checks](#service-bound-checks) section. - -- `status` `(string: "")` - Specifies the initial status of the health check as - "critical" (default), "warning", or "passing". For more details, refer to - the [initial health check status](#initial-health-check-status) section. - - -> **Health defaults to critical:** If health status it not initially specified, - it defaults to "critical" to protect against including a service - in discovery results before it is ready. - -- `deregister_critical_service_after` `(string: "")` - If specified, - the associated service and all its checks are deregistered - after this check is in the critical state for more than the specified value. - The value has the same formatting specification as the [`interval`](#interval) field. - - The minimum timeout is 1 minute, - and the process that reaps critical services runs every 30 seconds, - so it may take slightly longer than the configured timeout to trigger the deregistration. - This field should generally be configured with a timeout that's significantly longer than - any expected recoverable outage for the given service. - -- `notes` `(string: "")` - Provides a human-readable description of the check. - This field is opaque to Consul and can be used however is useful to the user. - For example, it could be used to describe the current state of the check. - -- `token` `(string: "")` - Specifies an ACL token used for any interaction - with the catalog for the check, including - [anti-entropy syncs](/consul/docs/architecture/anti-entropy) and deregistration. - - For alias checks, this token is used if a remote blocking query is necessary to watch the state of the aliased node or service. - -#### Success/failures before passing/warning/critical - -To prevent flapping health checks and limit the load they cause on the cluster, -a health check may be configured to become passing/warning/critical only after a -specified number of consecutive checks return as passing/critical. -The status does not transition states until the configured threshold is reached. - -- `success_before_passing` - Number of consecutive successful results required - before check status transitions to passing. Defaults to `0`. Added in Consul 1.7.0. - -- `failures_before_warning` - Number of consecutive unsuccessful results required - before check status transitions to warning. Defaults to the same value as that of - `failures_before_critical` to maintain the expected behavior of not changing the - status of service checks to `warning` before `critical` unless configured to do so. - Values higher than `failures_before_critical` are invalid. Added in Consul 1.11.0. - -- `failures_before_critical` - Number of consecutive unsuccessful results required - before check status transitions to critical. Defaults to `0`. Added in Consul 1.7.0. - -This feature is available for all check types except TTL and alias checks. -By default, both passing and critical thresholds are set to 0 so the check -status always reflects the last check result. - - - -```hcl -checks = [ - { - name = "HTTP TCP on port 80" - tcp = "localhost:80" - interval = "10s" - timeout = "1s" - success_before_passing = 3 - failures_before_warning = 1 - failures_before_critical = 3 - } -] -``` - -```json -{ - "checks": [ - { - "name": "HTTP TCP on port 80", - "tcp": "localhost:80", - "interval": "10s", - "timeout": "1s", - "success_before_passing": 3, - "failures_before_warning": 1, - "failures_before_critical": 3 - } - ] -} -``` - - - -## Initial health check status - -By default, when checks are registered against a Consul agent, the state is set -immediately to "critical". This is useful to prevent services from being -registered as "passing" and entering the service pool before they are confirmed -to be healthy. In certain cases, it may be desirable to specify the initial -state of a health check. This can be done by specifying the `status` field in a -health check definition, like so: - - - -```hcl -check = { - id = "mem" - args = ["/bin/check_mem", "-limit", "256MB"] - interval = "10s" - status = "passing" -} -``` - -```json -{ - "check": { - "id": "mem", - "args": ["/bin/check_mem", "-limit", "256MB"], - "interval": "10s", - "status": "passing" - } -} -``` - - - -The above service definition would cause the new "mem" check to be -registered with its initial state set to "passing". - -## Service-bound checks - -Health checks may optionally be bound to a specific service. This ensures -that the status of the health check will only affect the health status of the -given service instead of the entire node. Service-bound health checks may be -provided by adding a `service_id` field to a check configuration: - - - -```hcl -check = { - id = "web-app" - name = "Web App Status" - service_id = "web-app" - ttl = "30s" -} -``` - -```json -{ - "check": { - "id": "web-app", - "name": "Web App Status", - "service_id": "web-app", - "ttl": "30s" - } -} -``` - - - -In the above configuration, if the web-app health check begins failing, it will -only affect the availability of the web-app service. All other services -provided by the node will remain unchanged. - -## Agent certificates for TLS checks - -The [enable_agent_tls_for_checks](/consul/docs/agent/config/config-files#enable_agent_tls_for_checks) -agent configuration option can be utilized to have HTTP or gRPC health checks -to use the agent's credentials when configured for TLS. - -## Multiple check definitions - -Multiple check definitions can be defined using the `checks` (plural) -key in your configuration file. - - - -```hcl -checks = [ - { - id = "chk1" - name = "mem" - args = ["/bin/check_mem", "-limit", "256MB"] - interval = "5s" - }, - { - id = "chk2" - name = "/health" - http = "http://localhost:5000/health" - interval = "15s" - }, - { - id = "chk3" - name = "cpu" - args = ["/bin/check_cpu"] - interval = "10s" - }, - ... -] -``` - -```json -{ - "checks": [ - { - "id": "chk1", - "name": "mem", - "args": ["/bin/check_mem", "-limit", "256MB"], - "interval": "5s" - }, - { - "id": "chk2", - "name": "/health", - "http": "http://localhost:5000/health", - "interval": "15s" - }, - { - "id": "chk3", - "name": "cpu", - "args": ["/bin/check_cpu"], - "interval": "10s" - }, - ... - ] -} -``` - - diff --git a/website/content/docs/discovery/dns.mdx b/website/content/docs/discovery/dns.mdx deleted file mode 100644 index 90038ae2955..00000000000 --- a/website/content/docs/discovery/dns.mdx +++ /dev/null @@ -1,594 +0,0 @@ ---- -layout: docs -page_title: Find services with DNS -description: >- - For service discovery use cases, Domain Name Service (DNS) is the main interface to look up, query, and address Consul nodes and services. Learn how a Consul DNS lookup can help you find services by tag, name, namespace, partition, datacenter, or domain. ---- - -# Query services with DNS - -One of the primary query interfaces for Consul is DNS. -The DNS interface allows applications to make use of service -discovery without any high-touch integration with Consul. - -For example, instead of making HTTP API requests to Consul, -a host can use the DNS server directly via name lookups -like `redis.service.us-east-1.consul`. This query automatically -translates to a lookup of nodes that provide the `redis` service, -are located in the `us-east-1` datacenter, and have no failing health checks. -It's that simple! - -There are a number of configuration options that are important for the DNS interface, -specifically [`client_addr`](/consul/docs/agent/config/config-files#client_addr),[`ports.dns`](/consul/docs/agent/config/config-files#dns_port), -[`recursors`](/consul/docs/agent/config/config-files#recursors),[`domain`](/consul/docs/agent/config/config-files#domain), -[`alt_domain`](/consul/docs/agent/config/config-files#alt_domain), and [`dns_config`](/consul/docs/agent/config/config-files#dns_config). -By default, Consul will listen on 127.0.0.1:8600 for DNS queries in the `consul.` -domain, without support for further DNS recursion. Please consult the -[documentation on configuration options](/consul/docs/agent/config), -specifically the configuration items linked above, for more details. - -There are a few ways to use the DNS interface. One option is to use a custom -DNS resolver library and point it at Consul. Another option is to set Consul -as the DNS server for a node and provide a -[`recursors`](/consul/docs/agent/config/config-files#recursors) configuration so that non-Consul queries -can also be resolved. The last method is to forward all queries for the "consul." -domain to a Consul agent from the existing DNS server. Review the -[DNS Forwarding tutorial](/consul/tutorials/networking/dns-forwarding?utm_source=docs) for examples. - -You can experiment with Consul's DNS server on the command line using tools such as `dig`: - -```shell-session -$ dig @127.0.0.1 -p 8600 redis.service.dc1.consul. ANY -``` - --> **Note:** In DNS, all queries are case-insensitive. A lookup of `PostgreSQL.node.dc1.consul` will find all nodes named `postgresql`. - -## Node Lookups - -To resolve names, Consul relies on a very specific format for queries. -There are fundamentally two types of queries: node lookups and service lookups. -A node lookup, a simple query for the address of a named node, looks like this: - -```text -.node[.]. -``` - -For example, if we have a `foo` node with default settings, we could -look for `foo.node.dc1.consul.` The datacenter is an optional part of -the FQDN: if not provided, it defaults to the datacenter of the agent. -If we know `foo` is running in the same datacenter as our local agent, -we can instead use `foo.node.consul.` This convention allows for terse -syntax where appropriate while supporting queries of nodes in remote -datacenters as necessary. - -For a node lookup, the only records returned are A and AAAA records -containing the IP address, and TXT records containing the -`node_meta` values of the node. - -```shell-session -$ dig @127.0.0.1 -p 8600 foo.node.consul ANY - -; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 foo.node.consul ANY -; (1 server found) -;; global options: +cmd -;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24355 -;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0 -;; WARNING: recursion requested but not available - -;; QUESTION SECTION: -;foo.node.consul. IN ANY - -;; ANSWER SECTION: -foo.node.consul. 0 IN A 10.1.10.12 -foo.node.consul. 0 IN TXT "meta_key=meta_value" -foo.node.consul. 0 IN TXT "value only" - - -;; AUTHORITY SECTION: -consul. 0 IN SOA ns.consul. postmaster.consul. 1392836399 3600 600 86400 0 -``` - -By default the TXT records value will match the node's metadata key-value -pairs according to [RFC1464](https://www.ietf.org/rfc/rfc1464.txt). -Alternatively, the TXT record will only include the node's metadata value when the -node's metadata key starts with `rfc1035-`. - - -### Node Lookups for Consul Enterprise - -Consul nodes exist at the admin partition level within a datacenter. -By default, the partition and datacenter used in a [node lookup](#node-lookups) are -the partition and datacenter of the Consul agent that received the DNS query. - -Use the following query format to specify a partition for a node lookup: -```text -.node..ap..dc. -``` - -Consul server agents are in the `default` partition. -If DNS queries are addressed to Consul server agents, -node lookups to non-`default` partitions must explicitly specify -the partition of the target node. - -## Service Lookups - -A service lookup is used to query for service providers. Service queries support -two lookup methods: standard and strict [RFC 2782](https://tools.ietf.org/html/rfc2782). - -By default, SRV weights are all set at 1, but changing weights is supported using the -`Weights` attribute of the [service definition](/consul/docs/discovery/services). - -Note that DNS is limited in size per request, even when performing DNS TCP -queries. - -For services having many instances (more than 500), it might not be possible to -retrieve the complete list of instances for the service. - -When DNS SRV response are sent, order is randomized, but weights are not -taken into account. In the case of truncation different clients using weighted SRV -responses will have partial and inconsistent views of instances weights so the -request distribution could be skewed from the intended weights. In that case, -it is recommended to use the HTTP API to retrieve the list of nodes. - -### Standard Lookup - -The format of a standard service lookup is: - -```text -[.].service[.]. -``` - -The `tag` is optional, and, as with node lookups, the `datacenter` is as -well. If no tag is provided, no filtering is done on tag. If no -datacenter is provided, the datacenter of this Consul agent is assumed. - -If we want to find any redis service providers in our local datacenter, -we could query `redis.service.consul.` If we want to find the PostgreSQL -primary in a particular datacenter, we could query -`primary.postgresql.service.dc2.consul.` - -The DNS query system makes use of health check information to prevent routing -to unhealthy nodes. When a service query is made, any services failing their health -check or failing a node system check will be omitted from the results. To allow -for simple load balancing, the set of nodes returned is also randomized each time. -These mechanisms make it easy to use DNS along with application-level retries -as the foundation for an auto-healing service oriented architecture. - -For standard services queries, both A and SRV records are supported. SRV records -provide the port that a service is registered on, enabling clients to avoid relying -on well-known ports. SRV records are only served if the client specifically requests -them, like so: - -```shell-session -$ dig @127.0.0.1 -p 8600 consul.service.consul SRV - -; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 consul.service.consul ANY -; (1 server found) -;; global options: +cmd -;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50483 -;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 1, ADDITIONAL: 1 -;; WARNING: recursion requested but not available - -;; QUESTION SECTION: -;consul.service.consul. IN SRV - -;; ANSWER SECTION: -consul.service.consul. 0 IN SRV 1 1 8300 foobar.node.dc1.consul. - -;; ADDITIONAL SECTION: -foobar.node.dc1.consul. 0 IN A 10.1.10.12 -``` - -### RFC 2782 Lookup - -Valid formats for RFC 2782 SRV lookups depend on -whether you want to filter results based on a service tag: - -- No filtering on service tag: - - ```text - _._tcp[.service][.]. - ``` - -- Filtering on service tag specified in the RFC 2782 protocol field: - - ```text - _._[.service][.]. - ``` - -Per [RFC 2782](https://tools.ietf.org/html/rfc2782), SRV queries must -prepend an underscore (`_`) to the `service` and `protocol` values in a query to -prevent DNS collisions. -To perform no tag-based filtering, specify `tcp` in the RFC 2782 protocol field. -To filter results on a service tag, specify the tag in the RFC 2782 protocol field. - -Other than the query format and default `tcp` protocol/tag value, the behavior -of the RFC style lookup is the same as the standard style of lookup. - -If you registered the service `rabbitmq` on port 5672 and tagged it with `amqp`, -you could make an RFC 2782 query for its SRV record as `_rabbitmq._amqp.service.consul`: - -```shell-session -$ dig @127.0.0.1 -p 8600 _rabbitmq._amqp.service.consul SRV - -; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 _rabbitmq._amqp.service.consul ANY -; (1 server found) -;; global options: +cmd -;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52838 -;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 -;; WARNING: recursion requested but not available - -;; QUESTION SECTION: -;_rabbitmq._amqp.service.consul. IN SRV - -;; ANSWER SECTION: -_rabbitmq._amqp.service.consul. 0 IN SRV 1 1 5672 rabbitmq.node1.dc1.consul. - -;; ADDITIONAL SECTION: -rabbitmq.node1.dc1.consul. 0 IN A 10.1.11.20 -``` - -Again, note that the SRV record returns the port of the service as well as its IP. - -#### SRV response for hosts in the .addr subdomain - -If a service registered to Consul has an explicit IP [`address`](/consul/api-docs/agent/service#address) -or tagged address(es) defined on the service registration, the hostname returned -in the target field of the answer section for the DNS SRV query for the service -will be in the format of `.addr..consul`. - - - - - -In the example below, the `rabbitmq` service has been registered with an explicit -IPv4 address of `192.0.2.10`. - - - -```hcl -node_name = "node1" - -services { - name = "rabbitmq" - address = "192.0.2.10" - port = 5672 -} -``` - -```json -{ - "node_name": "node1", - "services": [ - { - "name": "rabbitmq", - "address": "192.0.2.10", - "port": 5672 - } - ] -} -``` - - - -When performing an SRV query for this service, the SRV response contains a single -record with a hostname in the format of `.addr..consul`. - -```shell-session -$ dig @127.0.0.1 -p 8600 -t srv _rabbitmq._tcp.service.consul +short -1 1 5672 c000020a.addr.dc1.consul. -``` - -In this example, the hex-encoded IP from the returned hostname is `c000020a`. -Converting each hex octet to decimal reveals the IP address that was specified -in the service registration. - -```shell-session -$ echo -n "c000020a" | perl -ne 'printf("%vd\n", pack("H*", $_))' -192.0.2.10 -``` - - - - - -In the example below, the `rabbitmq` service has been registered with an explicit -IPv6 address of `2001:db8:1:2:cafe::1337`. - - - -```hcl -node_name = "node1" - -services { - name = "rabbitmq" - address = "2001:db8:1:2:cafe::1337" - port = 5672 -} -``` - -```json -{ - "node_name": "node1", - "services": [ - { - "name": "rabbitmq", - "address": "2001:db8:1:2:cafe::1337", - "port": 5672 - } - ] -} -``` - - - -When performing an SRV query for this service, the SRV response contains a single -record with a hostname in the format of `.addr..consul`. - -```shell-session -$ dig @127.0.0.1 -p 8600 -t srv _rabbitmq._tcp.service.consul +short -1 1 5672 20010db800010002cafe000000001337.addr.dc1.consul. -``` - -In this example, the hex-encoded IP from the returned hostname is -`20010db800010002cafe000000001337`. This is the fully expanded IPv6 address with -colon separators removed. - -The following command re-adds the colon separators to display the fully expanded -IPv6 address that was specified in the service registration. - -```shell-session -$ echo -n "20010db800010002cafe000000001337" | perl -ne 'printf join(":", unpack("(A4)*", $_))."\n"' -2001:0db8:0001:0002:cafe:0000:0000:1337 -``` - - - - - -### Service Lookups for Consul Enterprise - -By default, all service lookups use the `default` namespace -within the partition and datacenter of the Consul agent that received the DNS query. -To lookup services in another namespace, partition, and/or datacenter, -use the [canonical format](#canonical-format). - -Consul server agents are in the `default` partition. -If DNS queries are addressed to Consul server agents, -service lookups to non-`default` partitions must explicitly specify -the partition of the target service. - -To lookup services imported from a cluster peer, -refer to [service virtual IP lookups for Consul Enterprise](#service-virtual-ip-lookups-for-consul-enterprise) instead. - -#### Canonical format - -Use the following query format to specify namespace, partition, and/or datacenter -for `.service`, `.connect`, `.virtual`, and `.ingress` service lookup types. -All three fields (`namespace`, `partition`, `datacenter`) are optional. -```text -[.].service[..ns][..ap][..dc] -``` - -#### Alternative formats for specifying namespace - -Though the [canonical format](#canonical-format) is recommended for readability, -you can use the following query formats specify namespace but not partition: - -- Specify both namespace and datacenter: - - ```text - [.].service... - ``` - -- **Deprecated in Consul 1.11:** - Specify namespace without a datacenter, - which requires that DNS queries are addressed to a Consul agent with - [`dns_config.prefer_namespace`](/consul/docs/agent/config/config-files#dns_prefer_namespace) - set to `true`: - - ```text - [.].service.. - ``` - -### Prepared Query Lookups - -The following formats are valid for prepared query lookups: - -- Standard lookup - - ```text - .query[.]. - ``` - -- [RFC 2782](https://tools.ietf.org/html/rfc2782) SRV lookup - - ```text - _._tcp.query[.]. - ``` - -The `datacenter` is optional, and if not provided, the datacenter of this Consul -agent is assumed. - -The `query name or id` is the given name or ID of an existing -[Prepared Query](/consul/api-docs/query). These behave like standard service -queries but provide a much richer set of features, such as filtering by multiple -tags and automatically failing over to look for services in remote datacenters if -no healthy nodes are available in the local datacenter. Consul 0.6.4 and later also -added support for [prepared query templates](/consul/api-docs/query#prepared-query-templates) -which can match names using a prefix match, allowing one template to apply to -potentially many services. - -To allow for simple load balancing, the set of nodes returned is randomized each time. -Both A and SRV records are supported. SRV records provide the port that a service is -registered on, enabling clients to avoid relying on well-known ports. SRV records are -only served if the client specifically requests them. - -### Connect-Capable Service Lookups - -To find Connect-capable services: - -```text -.connect. -``` - -This will find all [Connect-capable](/consul/docs/connect) -endpoints for the given `service`. A Connect-capable endpoint may be -both a proxy for a service or a natively integrated Connect application. -The DNS interface does not differentiate the two. - -Most services will use a [proxy](/consul/docs/connect/proxies) that handles -service discovery automatically and therefore won't use this DNS format. -This DNS format is primarily useful for [Connect-native](/consul/docs/connect/native) -applications. - -This endpoint currently only finds services within the same datacenter -and doesn't support tags. This DNS interface will be expanded over time. -If you need more complex behavior, please use the -[catalog API](/consul/api-docs/catalog). - -### Service Virtual IP Lookups - -To find the unique virtual IP allocated for a service: - -```text -.virtual[.]. -``` - -This will return the unique virtual IP for any [Connect-capable](/consul/docs/connect) -service. Each Connect service has a virtual IP assigned to it by Consul - this is used -by sidecar proxies for the [Transparent Proxy](/consul/docs/connect/transparent-proxy) feature. -The peer name is an optional part of the FQDN, and it is used to query for the virtual IP -of a service imported from that peer. - -The virtual IP is also added to the service's [Tagged Addresses](/consul/docs/discovery/services#tagged-addresses) -under the `consul-virtual` tag. - -#### Service Virtual IP Lookups for Consul Enterprise - -By default, a service virtual IP lookup uses the `default` namespace -within the partition and datacenter of the Consul agent that received the DNS query. - -To lookup services imported from a cluster peered partition or open-source datacenter, -specify the namespace and peer name in the lookup: -```text -.virtual[.].. -``` - -To lookup services not imported from a cluster peer, -refer to [service lookups for Consul Enterprise](#service-lookups-for-consul-enterprise) instead. - -### Ingress Service Lookups - -To find ingress-enabled services: - -```text -.ingress. -``` - -This will find all [ingress gateway](/consul/docs/connect/gateways/ingress-gateway) -endpoints for the given `service`. - -This endpoint currently only finds services within the same datacenter -and doesn't support tags. This DNS interface will be expanded over time. -If you need more complex behavior, please use the -[catalog API](/consul/api-docs/catalog). - -### UDP Based DNS Queries - -When the DNS query is performed using UDP, Consul will truncate the results -without setting the truncate bit. This is to prevent a redundant lookup over -TCP that generates additional load. If the lookup is done over TCP, the results -are not truncated. - -## Alternative Domain - -By default, Consul responds to DNS queries in the `consul` domain, -but you can set a specific domain for responding to DNS queries by configuring the [`domain`](/consul/docs/agent/config/config-files#domain) parameter. - -In some instances, Consul may need to respond to queries in more than one domain, -such as during a DNS migration or to distinguish between internal and external queries. - -Consul versions 1.5.2+ can be configured to respond to DNS queries on an alternative domain -through the [`alt_domain`](/consul/docs/agent/config/config-files#alt_domain) agent configuration -option. As of Consul versions 1.11.0+, Consul's DNS response will use the same domain as was used in the query; -in prior versions, the response may use the primary [`domain`](/consul/docs/agent/config/config-files#domain) no matter which -domain was used in the query. - -In the following example, the `alt_domain` parameter is set to `test-domain`: - -```hcl - alt_domain = "test-domain" -``` - -```shell-session -$ dig @127.0.0.1 -p 8600 consul.service.test-domain SRV -``` - -The following responses are returned: - -``` -;; QUESTION SECTION: -;consul.service.test-domain. IN SRV - -;; ANSWER SECTION: -consul.service.test-domain. 0 IN SRV 1 1 8300 machine.node.dc1.test-domain. - -;; ADDITIONAL SECTION: -machine.node.dc1.test-domain. 0 IN A 127.0.0.1 -machine.node.dc1.test-domain. 0 IN TXT "consul-network-segment=" -``` - --> **PTR queries:** Responses to PTR queries (`.in-addr.arpa.`) will always use the -[primary domain](/consul/docs/agent/config/config-files#domain) (not the alternative domain), -as there is no way for the query to specify a domain. - -## Caching - -By default, all DNS results served by Consul set a 0 TTL value. This disables -caching of DNS results. However, there are many situations in which caching is -desirable for performance and scalability. This is discussed more in the tutorial -for [DNS caching](/consul/tutorials/networking/dns-caching). - -## WAN Address Translation - -By default, Consul DNS queries will return a node's local address, even when -being queried from a remote datacenter. If you need to use a different address -to reach a node from outside its datacenter, you can configure this behavior -using the [`advertise-wan`](/consul/docs/agent/config/cli-flags#_advertise-wan) and -[`translate_wan_addrs`](/consul/docs/agent/config/config-files#translate_wan_addrs) configuration -options. - -## DNS with ACLs - -In order to use the DNS interface when -[Access Control Lists (ACLs)](/consul/docs/security/acl) -are enabled, you must first create ACL tokens with the necessary policies. - -Consul agents resolve DNS requests using one of the preconfigured tokens below, -listed in order of precedence: - -1. The agent's [`default` token](/consul/docs/agent/config/config-files#acl_tokens_default). -2. The built-in [`anonymous` token](/consul/docs/security/acl/acl-tokens#built-in-tokens). - Because the anonymous token is used when any request is made to Consul without - explicitly specifying a token, production deployments should not apply policies - needed for DNS to this token. - -Consul will either accept or deny the request depending on whether the token -has the appropriate authorization. The following table describes the available -DNS lookups and required policies when ACLs are enabled: - -| Lookup | Type | Description | ACLs Required | -| ------------------------------------------------------------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*.node.consul` | [Node](#node-lookups) | Allow resolving DNS requests for the target node (i.e., `.node.consul`) | [`node:read`](/consul/docs/security/acl/acl-rules#node-rules) | -| `*.service.consul`, `*.connect.consul`, `*.ingress.consul`, `*.virtual.consul` | [Service: standard](#service-lookups) | Allow resolving DNS requests for target service (e.g., `.service.consul`) instances running on ACL-authorized nodes | [`service:read`](/consul/docs/security/acl/acl-rules#service-rules), [`node:read`](/consul/docs/security/acl/acl-rules#node-rules) | -| `*.query.consul` | [Service: prepared query](#prepared-query-lookups) | Allow resolving DNS requests for [service instances specified](/consul/api-docs/query#service-1) by the target prepared query (i.e., `.query.consul`) running on ACL-authorized nodes | [`query:read`](/consul/docs/security/acl/acl-rules#prepared-query-rules), [`service:read`](/consul/docs/security/acl/acl-rules#service-rules), [`node:read`](/consul/docs/security/acl/acl-rules#node-rules) | - -For guidance on how to configure an appropriate token for DNS, refer to the -securing Consul with ACLs guides for: - -- [Production Environments](/consul/tutorials/security/access-control-setup-production#token-for-dns) -- [Development Environments](/consul/tutorials/day-0/access-control-setup?utm_source=docs#enable-acls-on-consul-clients) diff --git a/website/content/docs/discovery/services.mdx b/website/content/docs/discovery/services.mdx deleted file mode 100644 index 9eba2e85bb5..00000000000 --- a/website/content/docs/discovery/services.mdx +++ /dev/null @@ -1,701 +0,0 @@ ---- -layout: docs -page_title: Register Services with Service Definitions -description: >- - Define and register services and their health checks with Consul to make a service available for service discovery or service mesh access. Learn how to format service definitions with this reference page and sample code. ---- - -# Register Services with Service Definitions - -One of the main goals of service discovery is to provide a catalog of available -services. To that end, the agent provides a simple service definition format -to declare the availability of a service and to potentially associate it with -a health check. A health check associated with a service is considered to be an -application-level check. Define services in a configuration file or add it at -runtime using the HTTP interface. - -Complete the [Getting Started tutorials](/consul/tutorials/get-started-vms/virtual-machine-gs-service-discovery) to get hands-on experience registering a simple service with a health check on your local machine. - -## Service Definition - -Configure a service by providing the service definition to the agent. You can -either specify the configuration file using the `-config-file` option, or specify -the directory containing the service definition file with the `-config-dir` option. -Consul can load service definitions saved as `.json` or `.hcl` files. - -Send a `SIGHUP` to the running agent or use [`consul reload`](/consul/commands/reload) to check for new service definitions or to -update existing services. Alternatively, the service can be [registered dynamically](/consul/api-docs/agent/service#register-service) -using the [HTTP API](/consul/api-docs). - -A service definition contains a set of parameters that specify various aspects of the service, including how it is discovered by other services in the network. -All possible parameters are included in the following example, but only the top-level `service` parameter and its `name` parameter child are required by default. - - - -```hcl -service { - name = "redis" - id = "redis" - port = 80 - tags = ["primary"] - - meta = { - custom_meta_key = "custom_meta_value" - } - - tagged_addresses = { - lan = { - address = "192.168.0.55" - port = 8000 - } - - wan = { - address = "198.18.0.23" - port = 80 - } - } - - port = 8000 - socket_path = "/tmp/redis.sock" - enable_tag_override = false - - checks = [ - { - args = ["/usr/local/bin/check_redis.py"] - interval = "10s" - } - ] - - kind = "connect-proxy" - proxy_destination = "redis" - - proxy = { - destination_service_name = "redis" - destination_service_id = "redis1" - local_service_address = "127.0.0.1" - local_service_port = 9090 - local_service_socket_path = "/tmp/redis.sock" - mode = "transparent" - - transparent_proxy { - outbound_listener_port = 22500 - } - - mesh_gateway = { - mode = "local" - } - - expose = { - checks = true - - paths = [ - { - path = "/healthz" - local_path_port = 8080 - listener_port = 21500 - protocol = "http2" - } - ] - } - } - - connect = { - native = false - } - - weights = { - passing = 5 - warning = 1 - } - - token = "233b604b-b92e-48c8-a253-5f11514e4b50" - namespace = "foo" -} -``` - -```json -{ - "service": { - "id": "redis", - "name": "redis", - "tags": ["primary"], - "address": "", - "meta": { - "meta": "for my service" - }, - "tagged_addresses": { - "lan": { - "address": "192.168.0.55", - "port": 8000, - }, - "wan": { - "address": "198.18.0.23", - "port": 80 - } - }, - "port": 8000, - "socket_path": "/tmp/redis.sock", - "enable_tag_override": false, - "checks": [ - { - "args": ["/usr/local/bin/check_redis.py"], - "interval": "10s" - } - ], - "kind": "connect-proxy", - "proxy_destination": "redis", // Deprecated - "proxy": { - "destination_service_name": "redis", - "destination_service_id": "redis1", - "local_service_address": "127.0.0.1", - "local_service_port": 9090, - "local_service_socket_path": "/tmp/redis.sock", - "mode": "transparent", - "transparent_proxy": { - "outbound_listener_port": 22500 - }, - "config": {}, - "upstreams": [], - "mesh_gateway": { - "mode": "local" - }, - "expose": { - "checks": true, - "paths": [ - { - "path": "/healthz", - "local_path_port": 8080, - "listener_port": 21500, - "protocol": "http2" - } - ] - } - }, - "connect": { - "native": false, - "sidecar_service": {} - "proxy": { // Deprecated - "command": [], - "config": {} - } - }, - "weights": { - "passing": 5, - "warning": 1 - }, - "token": "233b604b-b92e-48c8-a253-5f11514e4b50", - "namespace": "foo" - } -} -``` - - - -The following table describes the available parameters for service definitions. - -### `service` - -This is the root-level parameter that defines the service. You can specify the parameters to configure the service. - -| Parameter | Description | Default | Required | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ---------------------------- | -| `id` | String value that specifies the service ID.

    If not specified, the value of the `name` field will be used.

    Services must have unique IDs per node, so you should specify unique values if the default `name` will conflict with other services.

    | Value of the `name` parameter | Optional | -| `name` | Specifies the name of the service.
    The value for this parameter is used as the ID if the `id` parameter is not specified.
    We recommend using valid DNS labels for service definition names for compatibility with external DNSs. | None | Required | -| `tags` | List of string values that can be used to add service-level labels.
    For example, you can define tags that distinguish between `primary` and `secondary` nodes or service versions.
    We recommend using valid DNS labels for service definition IDs for compatibility with external DNSs.
    Tag values are opaque to Consul.
    | None | Optional | -| `address` | String value that 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. | IP address of the agent node | Optional | -| `meta` | Object that defines a map of the max 64 key/value pairs.
    The meta object has the same limitations as the node meta object in the node definition.
    Meta data can be retrieved per individual instance of the service. All instances of a given service have their own copy of the meta data.
    See [Adding Meta Data](#adding-meta-data) for supported parameters.
    | None | Optional | -| `tagged_addresses` | Tagged addresses are additional addresses that may be defined for a node or service. See [Tagged Addresses](#tagged-addresses) for details. | None | Optional | -| `port` | Integer value that specifies a service-specific port number. The port number should be specified when the `address` parameter is defined to improve service discoverability. | Optional | -| `socket_path` | String value that 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. | None | Optional | -| `enable_tag_override` | Boolean value that determines if the anti-entropy feature for the service is enabled.
    If set to `true`, then external agents can update this service in the catalog and modify the tags.
    Subsequent local sync operations by this agent will ignore the updated tags.
    This parameter only applies to the locally-registered service. If multiple nodes register the same service, the `enable_tag_override` configuration, and all other service configuration items, operate independently.
    Updating the tags for services registered on one node is independent from the same service (by name) registered on another node.
    See [anti-entropy syncs](/consul/docs/architecture/anti-entropy) for additional information.
    | False | Optional | -| `checks` | Array of objects that define health checks for the service. See [Health Checks](#health-checks) for details. | None | Optional | -| `kind` | String value that identifies the service as a Connect proxy. See [Connect](#connect) for details. | None | Optional | -| `proxy_destination` | String value that specifies the _name_ of the destination service that the service currently being configured proxies to.
    This parameter is deprecated. Use `proxy.destination_service` instead.
    See [Connect](#connect) for additional information. | None | Optional | -| `proxy` | Object that defines the destination services that the service currently being configured proxies to. See [Proxy](#proxy) for additional information. | None | Optional | -| `connect` | Object that configures a Consul Connect service mesh connection. See [Connect](#connect) for details. | None | Optional | -| `weights` | Object that configures the weight of the service in terms of its DNS service (SRV) response. See [DNS SRV Weights](#dns-srv-weights) for details. | None | Optional | -| `token` | String value specifying the ACL token to be used to register the service (if the ACL system is enabled). The token is required for the service to interact with the service catalog. See [Security Configurations](#security-configurations) for details. | None | Required if ACLs are enabled | -| `namespace` | String value specifying the Consul Namespace where the service should be registered. See [Security Configurations](#security-configurations) for details. | None | Optional | - -### Adding Meta Data - -You can add semantic meta data to the service using the `meta` parameter. This parameter defines a map of max 64 key/value pairs. You can specify the following parameters to define meta data for the service. - -| Parameter | Description | Default | Required | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- | -| `KEY` | String value that adds semantic metadata to the service.
    Keys can only have 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. | None | Optional | - -### Security Configurations - -If the ACL system is enabled, specify a value for the `token` parameter to provide an ACL token. This token is -used for any interaction with the catalog for the service, including [anti-entropy syncs](/consul/docs/architecture/anti-entropy) and deregistration. - -Services registered in Consul clusters where both [Consul Namespaces](/consul/docs/enterprise/namespaces) -and the ACL system are enabled can be registered to specific namespaces that are associated with -ACL tokens scoped to the namespace. Services registered with a service definition -will not inherit the namespace associated with the ACL token specified in the `token` -field. The `namespace` _and_ the `token` parameters must be included in the service definition for the service to be registered to the -namespace that the ACL token is scoped to. - -### Health Checks - -You can add health checks to your service definition. 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. The health check functionality is strongly integrated into the DNS interface, as well. If a service is failing its health check or a node has any failing system-level check, the DNS interface will omit that -node from any service query. - -The health check name is automatically generated as `service:`. If there are multiple service checks -registered, the ID will be generated as `service::` where -`` is an incrementing number starting from `1`. - -Consul includes several check types with different options. Refer to the [health checks documentation](/consul/docs/discovery/checks) for details. - -### Proxy - -Service definitions allow for an optional proxy registration. Proxies used with Connect -are registered as services in Consul's catalog. -See the [Proxy Service Registration](/consul/docs/connect/registration/service-registration) reference -for the available configuration options. - -### Connect - -The `kind` parameter determines the service's role. Services can be configured to perform several roles, but you must omit the `kind` parameter for typical non-proxy instances. - -The following roles are supported for service entries: - -- `connect-proxy`: Defines the configuration for a connect proxy -- `ingress-gateway`: Defines the configuration for an [ingress gateway](/consul/docs/connect/config-entries/ingress-gateway) -- `mesh-gateway`: Defines the configuration for a [mesh gateway](/consul/docs/connect/gateways/mesh-gateway) -- `terminating-gateway`: Defines the configuration for a [terminating gateway](/consul/docs/connect/config-entries/terminating-gateway#terminating-gateway) - -In the service definition example described above, the service is registered as a proxy because the `kind` property is set to `connect-proxy`. -The `proxy` parameter is also required for Connect proxy registrations and is only valid if `kind` is `connect-proxy`. -Refer to the [Proxy Service Registration](/consul/docs/connect/registration/service-registration) documentation for details about this type. - -When the `kind` parameter is set to `connect-proxy`, the only required parameter for the `proxy` configuration is `destination_service_name`. -Refer to the [complete proxy configuration example](/consul/docs/connect/registration/service-registration#complete-configuration-example) for additional information. - -The `connect` field can be specified to configure [Connect](/consul/docs/connect) for a service. This field is available in Consul 1.2.0 and later. The following parameters are available. - -| Parameter | Description | Default | Required | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------- | -| `native` | Boolean value that advertises the service as [Connect-native](/consul/docs/connect/native).
    If set to `true`, do not configure a `sidecar_service`. | `false` | Optional | -| `sidecar_service` | Object that defines a nested service definition.
    Do not configure if `native` is set to `true`. | See [Sidecar Service Registration](/consul/docs/connect/registration/sidecar-service) for default configurations. | Optional | - --> **Non-service registration roles**: The `kind` values supported for configuration entries are different than what is supported for service registrations. Refer to the [Configuration Entries](/consul/docs/connect/config-entries) documentation for information about non-service registration types. - -#### Deprecated parameters - -Different Consul Connect parameters are supported for different Consul versions. The following table describes changes applicable to service discovery. - -| Parameter | Description | Consul version | Status | -| ------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------- | -| `proxy_destination` | Specified the proxy destination **in the root level** of the definition file. | 1.2.0 to 1.3.0 | Deprecated since 1.5.0.
    Use `proxy.destination_service_name` instead. | -| `connect.proxy` | Specified "managed" proxies, [which have been deprecated](/consul/docs/connect/proxies/managed-deprecated). | 1.2.0 (beta) to 1.3.0 (beta) | Deprecated. | - -### DNS SRV Weights - -You can configure how the service responds to DNS SRV requests by specifying a set of states/weights in the `weights` field. - -#### `weights` - -When DNS SRV requests are made, the response will include the weights specified for the given state of the service. -This allows some instances to be given higher weight if they have more capacity. It also allows load reduction on -services with checks in `warning` status by giving passing instances a higher weight. - -| Parameter | Description | Default | Required | -| --------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -------- | -| `STATE` | Integer value indicating its weight. A higher number indicates more weight. | If not specified, the following weights are used:
    `"passing" : 1`
    `"warning" : 1` | Optional | - -If a service is `critical`, it is excluded from DNS responses. -Services with warning checks are included in responses by default, but excluded if the optional param `only_passing = true` -is present in the agent DNS configuration or the `passing` query parameter is used via the API. - -### Enable Tag Override and Anti-Entropy - -Services may also contain a `token` field to provide an ACL token. This token is -used for any interaction with the catalog for the service, including -[anti-entropy syncs](/consul/docs/architecture/anti-entropy) and deregistration. - -You can optionally disable the anti-entropy feature for this service using the -`enable_tag_override` flag. External agents can modify tags on services in the -catalog, so subsequent sync operations can either maintain tag modifications or -revert them. If `enable_tag_override` is set to `TRUE`, the next sync cycle may -revert some service properties, **but** the tags would maintain the updated value. -If `enable_tag_override` is set to `FALSE`, the next sync cycle will revert any -updated service properties, **including** tags, to their original value. - -It's important to note that this applies only to the locally registered -service. If you have multiple nodes all registering the same service -their `enable_tag_override` configuration and all other service -configuration items are independent of one another. Updating the tags -for the service registered on one node is independent of the same -service (by name) registered on another node. If `enable_tag_override` is -not specified the default value is false. See [anti-entropy -syncs](/consul/docs/architecture/anti-entropy) for more info. - -For Consul 0.9.3 and earlier you need to use `enableTagOverride`. Consul 1.0 -supports both `enable_tag_override` and `enableTagOverride` but the latter is -deprecated and has been removed as of Consul 1.1. - -### Tagged Addresses - -Tagged addresses are additional addresses that may be defined for a node or -service. Tagged addresses can be used by remote agents and services as alternative -addresses for communicating with the given node or service. Multiple tagged -addresses may be configured on a node or service. - -The following example describes the syntax for defining a tagged address. - - - -```hcl -service { - name = "redis" - port = 80 - tagged_addresses { - = { - address = "
    " - port = port - } - } -} -``` - -```json -{ - "service": { - "name": "redis", - "port": 80, - "tagged_addresses": { - "": { - "address": "
    ", - "port": port - } - } - } -} -``` - - - -The following table provides an overview of the various tagged address types supported by Consul. - -| Type | Description | Tags | -| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | -| LAN | LAN addresses are intended to be directly accessible only from services within the same Consul data center. See [LAN tags](#lan-tags) for details. | `lan`
    `lan_ipv4`
    `lan_ipv6` | -| Virtual | Virtual tagged addresses are logical address types that can be configured on [Connect](/consul/docs/connect)-enabled services. The virtual address provides a fixed IP address that can be used by downstream services when connecting to an upstream service. See [Virtual tags](#virtual-tags) for details. | `virtual` | -| WAN | Define a WAN address for the service or node when it should be accessed at an alternate address by services in a remote datacenter. See [WAN tags](#wan-tags) for details. | `wan`
    `wan_ipv4`
    `wan_ipv6` | - -#### LAN tags - -- `lan` - The IPv4 LAN address at which the node or service is accessible. -- `lan_ipv4` - The IPv4 LAN address at which the node or service is accessible. -- `lan_ipv6` - The IPv6 LAN address at which the node or service is accessible. - - - - - -```hcl -service { - name = "redis" - address = "192.0.2.10" - port = 80 - tagged_addresses { - lan = { - address = "192.0.2.10" - port = 80 - } - lan_ipv4 = { - address = "192.0.2.10" - port = 80 - } - lan_ipv6 = { - address = "2001:db8:1:2:cafe::1337" - port = 80 - } - } -} -``` - - - - - -```json -{ - "service": { - "name": "redis", - "address": "192.0.2.10", - "port": 80, - "tagged_addresses": { - "lan": { - "address": "192.0.2.10", - "port": 80 - }, - "lan_ipv4": { - "address": "192.0.2.10", - "port": 80 - }, - "lan_ipv6": { - "address": "2001:db8:1:2:cafe::1337", - "port": 80 - } - } - } -} -``` - - - - -#### Virtual tags - -Connections to virtual addresses are load balanced across available instances of a service, provided the following conditions are satisfied: - -1. [Transparent proxy](/consul/docs/connect/transparent-proxy) is enabled for the - downstream and upstream services. -1. The upstream service is not configured for individual instances to be - [dialed directly](/consul/docs/connect/config-entries/service-defaults#dialeddirectly). - -Virtual addresses are not required to be routable IPs within the -network. They are strictly a control plane construct used to provide a fixed -address for the instances of a given logical service. Egress connections from -the proxy to an upstream service will be destined to the IP address of an -individual service instance, not the virtual address of the logical service. - -Use the following address tag to specify the logical address at which the -service can be reached by other services in the mesh. - -- `virtual` - The virtual IP address at which a logical service is reachable. - - - - - -```hcl -service { - name = "redis" - address = "192.0.2.10" - port = 80 - tagged_addresses { - virtual = { - address = "203.0.113.50" - port = 80 - } - } -} -``` - - - - - -```json -{ - "service": { - "name": "redis", - "address": "192.0.2.10", - "port": 80, - "tagged_addresses": { - "virtual": { - "address": "203.0.113.50", - "port": 80 - } - } - } -} -``` - - - - -#### WAN tags - -One or more of the following address tags can be configured for a node or service -to advertise how it should be accessed over the WAN. - -- `wan` - The IPv4 WAN address at which the node or service is accessible when - being dialed from a remote data center. -- `wan_ipv4` - The IPv4 WAN address at which the node or service is accessible - when being dialed from a remote data center. -- `wan_ipv6` - The IPv6 WAN address at which the node or service is accessible - when being dialed from a remote data center. - - - - - -```hcl -service { - name = "redis" - address = "192.0.2.10" - port = 80 - tagged_addresses { - wan = { - address = "198.51.100.200" - port = 80 - } - wan_ipv4 = { - address = "198.51.100.200" - port = 80 - } - wan_ipv6 = { - address = "2001:db8:5:6:1337::1eaf" - port = 80 - } - } -} -``` - - - - - -```json -{ - "service": { - "name": "redis", - "address": "192.0.2.10", - "port": 80, - "tagged_addresses": { - "wan": { - "address": "198.51.100.200", - "port": 80 - }, - "wan_ipv4": { - "address": "198.51.100.200", - "port": 80 - }, - "wan_ipv6": { - "address": "2001:db8:5:6:1337::1eaf", - "port": 80 - } - } - } -} -``` - - - - -## Multiple Service Definitions - -Multiple services definitions can be provided at once when registering services -via the agent configuration by using the plural `services` key (registering -multiple services in this manner is not supported using the HTTP API). - - - - - -```hcl -services { - id = "red0" - name = "redis" - tags = [ - "primary" - ] - address = "" - port = 6000 - checks = [ - { - args = ["/bin/check_redis", "-p", "6000"] - interval = "5s" - timeout = "20s" - } - ] -} -services { - id = "red1" - name = "redis" - tags = [ - "delayed", - "secondary" - ] - address = "" - port = 7000 - checks = [ - { - args = ["/bin/check_redis", "-p", "7000"] - interval = "30s" - timeout = "60s" - } - ] -} - -``` - - - - - -```json -{ - "services": [ - { - "id": "red0", - "name": "redis", - "tags": [ - "primary" - ], - "address": "", - "port": 6000, - "checks": [ - { - "args": ["/bin/check_redis", "-p", "6000"], - "interval": "5s", - "timeout": "20s" - } - ] - }, - { - "id": "red1", - "name": "redis", - "tags": [ - "delayed", - "secondary" - ], - "address": "", - "port": 7000, - "checks": [ - { - "args": ["/bin/check_redis", "-p", "7000"], - "interval": "30s", - "timeout": "60s" - } - ] - }, - ... - ] -} -``` - - - - -## Service and Tag Names with DNS - -Consul exposes service definitions and tags over the [DNS](/consul/docs/discovery/dns) -interface. DNS queries have a strict set of allowed characters and a -well-defined format that Consul cannot override. While it is possible to -register services or tags with names that don't match the conventions, those -services and tags will not be discoverable via the DNS interface. It is -recommended to always use DNS-compliant service and tag names. - -DNS-compliant service and tag names may contain any alpha-numeric characters, as -well as dashes. Dots are not supported because Consul internally uses them to -delimit service tags. - -## Service Definition Parameter Case - -For historical reasons Consul's API uses `CamelCased` parameter names in -responses, however its configuration file uses `snake_case` for both HCL and -JSON representations. For this reason the registration _HTTP APIs_ accept both -name styles for service definition parameters although APIs will return the -listings using `CamelCase`. - -Note though that **all config file formats require -`snake_case` fields**. We always document service definition examples using -`snake_case` and JSON since this format works in both config files and API -calls. diff --git a/website/content/docs/ecs/configuration-reference.mdx b/website/content/docs/ecs/configuration-reference.mdx index da0ee28f681..fed887bc835 100644 --- a/website/content/docs/ecs/configuration-reference.mdx +++ b/website/content/docs/ecs/configuration-reference.mdx @@ -185,7 +185,7 @@ Defines the Consul checks for the service. Each `check` object may contain the f | `method` | `string` | optional | Specifies the HTTP method to be used for an HTTP check. When no value is specified, `GET` is used. | | `name` | `string` | optional | The name of the check. | | `notes` | `string` | optional | Specifies arbitrary information for humans. This is not used by Consul internally. | -| `os_service` | `string` | optional | Specifies the name of a service on which to perform an [OS service check](/consul/docs/discovery/checks#osservice-check). The check runs according the frequency specified in the `interval` parameter. | +| `os_service` | `string` | optional | Specifies the name of a service on which to perform an [OS service check](/consul/docs/services/usage/checks#osservice-check). The check runs according the frequency specified in the `interval` parameter. | | `status` | `string` | optional | Specifies the initial status the health check. Must be one of `passing`, `warning`, `critical`, `maintenance`, or `null`. | | `successBeforePassing` | `integer` | optional | Specifies the number of consecutive successful results required before check status transitions to passing. | | `tcp` | `string` | optional | Specifies this is a TCP check. Must be an IP/hostname plus port to which a TCP connection is made every `interval`. | diff --git a/website/content/docs/ecs/manual/install.mdx b/website/content/docs/ecs/manual/install.mdx index e648c6cf3ea..dddb9b310e5 100644 --- a/website/content/docs/ecs/manual/install.mdx +++ b/website/content/docs/ecs/manual/install.mdx @@ -373,11 +373,11 @@ configuration to a shared volume. ### `CONSUL_ECS_CONFIG_JSON` -Configuration is passed to the `consul-ecs` binary in JSON format using the `CONSUL_ECS_CONFIG_JSON` environment variable. +Consul uses the `CONSUL_ECS_CONFIG_JSON` environment variable to passed configurations to the `consul-ecs` binary in JSON format. -The following is an example of the configuration that might be used for a service named `example-client-app` with one upstream -service name `example-server-app`. The `proxy` and `service` blocks include information used by `consul-ecs-mesh-init` to perform -[service registration](/consul/docs/discovery/services) with Consul during task startup. The same configuration format is used for +The following example configures a service named `example-client-app` with one upstream +service name `example-server-app`. The `proxy` and `service` blocks include information used by `consul-ecs-mesh-init` to register the service with Consul during task start up. +The same configuration format is used for the `consul-ecs-health-sync` container. ```json @@ -409,7 +409,7 @@ the `consul-ecs-health-sync` container. | `proxy.upstreams` | list | The upstream services that your application calls over the service mesh, if any. The `destinationName` and `localBindPort` fields are required. | | `service.name` | string | The name used to register this service into the Consul service catalog. | | `service.port` | integer | The port your application listens on. Set to `0` if your application does not listen on any port. | -| `service.checks` | list | Consul [checks](/consul/docs/discovery/checks) to include so that Consul can run health checks against your application. | +| `service.checks` | list | Consul [checks](/consul/docs/services/usage/checks) to include so that Consul can run health checks against your application. | See the [Configuration Reference](/consul/docs/ecs/configuration-reference) for a complete reference of fields. diff --git a/website/content/docs/ecs/terraform/secure-configuration.mdx b/website/content/docs/ecs/terraform/secure-configuration.mdx index c1997241c3b..07031f5490e 100644 --- a/website/content/docs/ecs/terraform/secure-configuration.mdx +++ b/website/content/docs/ecs/terraform/secure-configuration.mdx @@ -108,8 +108,9 @@ The following table describes the required input variables for the `acl-controll | `name_prefix` | string | AWS resources created by the `acl-controller` module will include this prefix in the resource name. | -If you are using Consul Enterprise, see Admin Partitions and Namespaces for -additional configuration required to support Consul Enterprise on ECS. + +If you are using Consul Enterprise, see the [Admin Partitions and Namespaces requirements documentation](/consul/docs/ecs/requirements) for additional configuration required to support Consul Enterprise on ECS. + ## Deploy your services diff --git a/website/content/docs/enterprise/admin-partitions.mdx b/website/content/docs/enterprise/admin-partitions.mdx index e5fdea32cfa..e4a07104931 100644 --- a/website/content/docs/enterprise/admin-partitions.mdx +++ b/website/content/docs/enterprise/admin-partitions.mdx @@ -61,7 +61,7 @@ The partition in which [`proxy-defaults`](/consul/docs/connect/config-entries/pr ### Cross-partition Networking -You can configure services to be discoverable by downstream services in any partition within the datacenter. Specify the upstream services that you want to be available for discovery by configuring the `exported-services` configuration entry in the partition where the services are registered. Refer to the [`exported-services` documentation](/consul/docs/connect/config-entries/exported-services) for details. Additionally, the requests made by downstream applications must have the correct DNS name for the Virtual IP Service lookup to occur. Service Virtual IP lookups allow for communications across Admin Partitions when using Transparent Proxy. Refer to the [Service Virtual IP Lookups for Consul Enterprise](/consul/docs/discovery/dns#service-virtual-ip-lookups-for-consul-enterprise) for additional information. +You can configure services to be discoverable by downstream services in any partition within the datacenter. Specify the upstream services that you want to be available for discovery by configuring the `exported-services` configuration entry in the partition where the services are registered. Refer to the [`exported-services` documentation](/consul/docs/connect/config-entries/exported-services) for details. Additionally, the requests made by downstream applications must have the correct DNS name for the Virtual IP Service lookup to occur. Service Virtual IP lookups allow for communications across Admin Partitions when using Transparent Proxy. Refer to the [Service Virtual IP Lookups for Consul Enterprise](/consul/docs/services/discovery/dns-static-lookups#service-virtual-ip-lookups-for-consul-enterprise) for additional information. ### Cluster Peering diff --git a/website/content/docs/enterprise/index.mdx b/website/content/docs/enterprise/index.mdx index d03ab438d20..e63363e8106 100644 --- a/website/content/docs/enterprise/index.mdx +++ b/website/content/docs/enterprise/index.mdx @@ -116,17 +116,17 @@ Consul Enterprise feature availability can change depending on your server and c | Enterprise Feature | VM Client | K8s Client | ECS Client | | ----------------------------------------------------------------------- | :-------: | :--------: | :--------: | -| [Admin Partitions](/consul/docs/enterprise/admin-partitions) | ✅ | ✅ | ❌ | -| [Audit Logging](/consul/docs/enterprise/audit-logging) | ✅ | ✅ | ❌ | -| [Automated Server Backups](/consul/docs/enterprise/backups) | ✅ | ✅ | ❌ | +| [Admin Partitions](/consul/docs/enterprise/admin-partitions) | ✅ | ✅ | ✅ | +| [Audit Logging](/consul/docs/enterprise/audit-logging) | ✅ | ✅ | ✅ | +| [Automated Server Backups](/consul/docs/enterprise/backups) | ✅ | ✅ | ✅ | | [Automated Server Upgrades](/consul/docs/enterprise/upgrades) | ❌ | ❌ | ❌ | | [Enhanced Read Scalability](/consul/docs/enterprise/read-scale) | ❌ | ❌ | ❌ | -| [Namespaces](/consul/docs/enterprise/namespaces) | ✅ | ✅ | ❌ | -| [Network Areas](/consul/docs/enterprise/federation) | ✅ | ✅ | ❌ | +| [Namespaces](/consul/docs/enterprise/namespaces) | ✅ | ✅ | ✅ | +| [Network Areas](/consul/docs/enterprise/federation) | ✅ | ✅ | ✅ | | [Network Segments](/consul/docs/enterprise/network-segments/network-segments-overview) | ❌ | ❌ | ❌ | -| [OIDC Auth Method](/consul/docs/security/acl/auth-methods/oidc) | ✅ | ✅ | ❌ | +| [OIDC Auth Method](/consul/docs/security/acl/auth-methods/oidc) | ✅ | ✅ | ✅ | | [Redundancy Zones](/consul/docs/enterprise/redundancy) | ❌ | ❌ | ❌ | -| [Sentinel ](/consul/docs/enterprise/sentinel) | ✅ | ✅ | ❌ | +| [Sentinel ](/consul/docs/enterprise/sentinel) | ✅ | ✅ | ✅ | diff --git a/website/content/docs/enterprise/license/faq.mdx b/website/content/docs/enterprise/license/faq.mdx index 02841e9c6cf..d8a46ee2093 100644 --- a/website/content/docs/enterprise/license/faq.mdx +++ b/website/content/docs/enterprise/license/faq.mdx @@ -74,6 +74,8 @@ after license expiration and is defined in ~> **Starting with Consul 1.14, and patch releases 1.13.3 and 1.12.6, Consul will support non-terminating licenses**: Please contact your support representative for more details on non-terminating licenses. + An expired license will not allow Consul versions released after the expiration date to run. + It will not be possible to upgrade to a new version of Consul released after license expiration. ## Q: Does this affect client agents? @@ -136,7 +138,7 @@ When a customer deploys new clusters to a 1.10.0+ent release, they need to have New Consul cluster deployments using 1.10.0+ent will need to have a valid license on servers to successfully deploy. This valid license must be on-disk (auto-loaded) or as an environment variable. -Please see the [upgrade requirements](https://consul.io/faq#q-what-are-the-upgrade-requirements). +Please see the [upgrade requirements](/consul/docs/enterprise/license/faq#q-what-are-the-upgrade-requirements). ## Q: What is the migration path for customers who want to migrate from their existing license-as-applied-via-the-CLI flow to the license on disk flow? @@ -181,7 +183,7 @@ When downgrading to a version of Consul before 1.10.0+ent, customers will need t ## Q: Are there potential pitfalls when downgrading or upgrading Consul server instances? -~> Verify that you meet the [upgrade requirements](https://consul.io/faq#q-what-are-the-upgrade-requirements). +~> Verify that you meet the [upgrade requirements](/consul/docs/enterprise/license/faq#q-what-are-the-upgrade-requirements). Assume a scenario where there are three Consul server nodes: diff --git a/website/content/docs/install/cloud-auto-join.mdx b/website/content/docs/install/cloud-auto-join.mdx index 40e2f44edf0..82cf6b6d592 100644 --- a/website/content/docs/install/cloud-auto-join.mdx +++ b/website/content/docs/install/cloud-auto-join.mdx @@ -34,7 +34,7 @@ or via a configuration file: ## Auto-join with Network Segments -In order to use cloud auto-join with [Network Segments](/consul/docs/enterprise/network-segments), +In order to use cloud auto-join with [Network Segments](/consul/docs/enterprise/network-segments/network-segments-overview), you must reconfigure the Consul agent's Serf LAN port to match that of the segment you wish to join. diff --git a/website/content/docs/integrate/download-tools.mdx b/website/content/docs/integrate/download-tools.mdx index a1263b7d817..b348e82a66d 100644 --- a/website/content/docs/integrate/download-tools.mdx +++ b/website/content/docs/integrate/download-tools.mdx @@ -15,7 +15,7 @@ These Consul tools are created and managed by the dedicated engineers at HashiCo - [Envconsul](https://github.com/hashicorp/envconsul) - Read and set environmental variables for processes from Consul. - [Consul API Gateway](https://github.com/hashicorp/consul-api-gateway/) - dedicated ingress solution for intelligently routing traffic to applications running on a Consul Service Mesh. -- [Consul ESM](https://github.com/hashicorp/consul-esm) - Provides external service monitoring for Consul. Complete the [tutorial](https://consul.io/(https://learn.hashicorp.com/tutorials/consul/service-registration-external-services?utm_source=docs)) to learn more. +- [Consul ESM](https://github.com/hashicorp/consul-esm) - Provides external service monitoring for Consul. Complete the [tutorial](/consul/tutorials/developer-discovery/service-registration-external-services?utm_source=docs) to learn more. - [Consul Migrate](https://github.com/hashicorp/consul-migrate) - Data migration tool to handle Consul upgrades to 0.5.1+ - [Consul Replicate](https://github.com/hashicorp/consul-replicate) - Consul cross-DC KV replication daemon. - [Consul Template](https://github.com/hashicorp/consul-template) - Generic template rendering and notifications with Consul. Complete the [tutorial](/consul/tutorials/developer-configuration/consul-template?utm_source=docs) to the learn more. @@ -54,7 +54,6 @@ These Consul tools are created and managed by the amazing members of the Consul - [gradle-consul-plugin](https://github.com/amirkibbar/red-apple) - A Consul Gradle plugin - [hashi-ui](https://github.com/jippi/hashi-ui) - A modern user interface for the Consul and Nomad - [HashiBox](https://github.com/nunchistudio/hashibox) - Vagrant environment to simulate highly-available cloud with Consul, Nomad, Vault, and optional support for Waypoint. OSS & Enterprise supported. -- [helios-consul](https://github.com/SVT/helios-consul) - Service registrar plugin for Helios - [Jenkins Consul Plugin](https://plugins.jenkins.io/consul) - Jenkins plugin for service discovery and K/V store - [marathon-consul](https://github.com/allegro/marathon-consul) - Service registry bridge for Marathon - [marathon-consul](https://github.com/CiscoCloud/marathon-consul) - Bridge from Marathon apps to the Consul K/V store diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 0f9f7301b78..34bcfe2631f 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -89,7 +89,7 @@ Here are links to resources, documentation, examples and best practices to guide - [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) - [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) +- [Consul and SnappyFlow Full Stack Observability](https://docs.snappyflow.io/docs/Integrations/hcp_consul) **Network Performance Monitoring (NPM)** diff --git a/website/content/docs/internals/acl.mdx b/website/content/docs/internals/acl.mdx index 05c8d380191..87e35844179 100644 --- a/website/content/docs/internals/acl.mdx +++ b/website/content/docs/internals/acl.mdx @@ -10,7 +10,4 @@ description: >- # ACL System ((#version_8_acls)) -This content has been moved into the [ACL Guide](/consul/tutorials/security/access-control-setup-production). - -See [Complete ACL Coverage in Consul 0.8](/consul/docs/security/acl/acl-legacy) for details -about ACL changes in Consul 0.8 and later. +This content has been moved into the [ACL Guide](/consul/tutorials/security/access-control-setup-production). \ No newline at end of file diff --git a/website/content/docs/intro/index.mdx b/website/content/docs/intro/index.mdx index 50834137b4e..90a1759fe23 100644 --- a/website/content/docs/intro/index.mdx +++ b/website/content/docs/intro/index.mdx @@ -24,7 +24,7 @@ Consul interacts with the _data plane_ through proxies. The data plane is the pa The core Consul workflow consists of the following stages: -- **Register**: Teams add services to the Consul catalog, which is a central registry that lets services automatically discover each other without requiring a human operator to modify application code, deploy additional load balancers, or hardcode IP addresses. It is the runtime source of truth for all services and their addresses. Teams can manually [define and register services](/consul/docs/discovery/services) using the CLI or the API, or you can automate the process in Kubernetes with [service sync](/consul/docs/k8s/service-sync). Services can also include health checks so that Consul can monitor for unhealthy services. +- **Register**: Teams add services to the Consul catalog, which is a central registry that lets services automatically discover each other without requiring a human operator to modify application code, deploy additional load balancers, or hardcode IP addresses. It is the runtime source of truth for all services and their addresses. Teams can manually [define](/consul/docs/services/usage/define-services) and [register](/consul/docs/services/usage/register-services-checks) using the CLI or the API, or you can automate the process in Kubernetes with [service sync](/consul/docs/k8s/service-sync). Services can also include health checks so that Consul can monitor for unhealthy services. - **Query**: Consul’s identity-based DNS lets you find healthy services in the Consul catalog. Services registered with Consul provide health information, access points, and other data that help you control the flow of data through your network. Your services only access other services through their local proxy according to the identity-based policies you define. - **Secure**: After services locate upstreams, Consul ensures that service-to-service communication is authenticated, authorized, and encrypted. Consul service mesh secures microservice architectures with mTLS and can allow or restrict access based on service identities, regardless of differences in compute environments and runtimes. diff --git a/website/content/docs/k8s/annotations-and-labels.mdx b/website/content/docs/k8s/annotations-and-labels.mdx index d6ea11d4f1d..d51b591ac86 100644 --- a/website/content/docs/k8s/annotations-and-labels.mdx +++ b/website/content/docs/k8s/annotations-and-labels.mdx @@ -75,7 +75,7 @@ The following Kubernetes resource annotations could be used on a pod to control - Unlabeled: Use the unlabeled annotation format to specify a service name, Consul Enterprise namespaces and partitions, and - datacenters. To use [cluster peering](/consul/docs/connect/cluster-peering/k8s) with upstreams, use the following + datacenters. To use [cluster peering](/consul/docs/k8s/connect/cluster-peering/k8s-tech-specs) with upstreams, use the following labeled format. - Service name: Place the service name at the beginning of the annotation to specify the upstream service. You can also append the datacenter where the service is deployed (optional). @@ -98,7 +98,7 @@ The following Kubernetes resource annotations could be used on a pod to control - Admin partitions (requires Consul Enterprise 1.11+): Upstream services may be running in a different partition. You must specify the namespace when specifying a partition. Place the partition name after the namespace. If you specify the name of the datacenter (optional), it must be the local datacenter. Communicating across partitions using this method is only supported within a datacenter. For cross partition communication across datacenters, refer to [cluster - peering](/consul/docs/connect/cluster-peering/k8s). + peering](/consul/docs/k8s/connect/cluster-peering/k8s-tech-specs). ```yaml annotations: "consul.hashicorp.com/connect-service-upstreams":"[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]" diff --git a/website/content/docs/k8s/compatibility.mdx b/website/content/docs/k8s/compatibility.mdx index 60f71bdcb85..343387156df 100644 --- a/website/content/docs/k8s/compatibility.mdx +++ b/website/content/docs/k8s/compatibility.mdx @@ -15,9 +15,9 @@ Consul Kubernetes versions all of its components (`consul-k8s` CLI, `consul-k8s- | Consul Version | Compatible consul-k8s Versions | Compatible Kubernetes Versions | | -------------- | -------------------------------- | -------------------------------| +| 1.15.x | 1.1.x | 1.23.x - 1.26.x | | 1.14.x | 1.0.x | 1.22.x - 1.25.x | | 1.13.x | 0.49.x | 1.21.x - 1.24.x | -| 1.12.x | 0.43.0 - 0.49.x | 1.19.x - 1.22.x | ## Supported Envoy versions diff --git a/website/content/docs/k8s/connect/cluster-peering/tech-specs.mdx b/website/content/docs/k8s/connect/cluster-peering/tech-specs.mdx new file mode 100644 index 00000000000..cfe4ba7aebc --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/tech-specs.mdx @@ -0,0 +1,169 @@ +--- +layout: docs +page_title: Cluster Peering on Kubernetes Technical Specifications +description: >- + In Kubernetes deployments, cluster peering connections interact with mesh gateways, exported services, and ACLs. Learn about requirements specific to k8s, including required Helm values and custom resource definitions (CRDs). +--- + +# Cluster peering on Kubernetes technical specifications + +This reference topic describes the technical specifications associated with using cluster peering in your Kubernetes deployments. These specifications include [required Helm values](#helm-requirements) and [required custom resource definitions (CRDs)](#crd-requirements), as well as required Consul components and their configurations. To learn more about Consul's cluster peering feature, refer to [cluster peering overview](/consul/docs/connect/cluster-peering). + +For cluster peering requirements in non-Kubernetes deployments, refer to [cluster peering technical specifications](/consul/docs/connect/cluster-peering/tech-specs). + +## General requirements + +Make sure your Consul environment meets the following prerequisites: + +- Consul v1.14 or higher +- Consul on Kubernetes v1.0.0 or higher +- At least two Kubernetes clusters + +You must also configure the following service mesh components in order to establish cluster peering connections: + +- [Helm](#helm-requirements) +- [Custom resource definitions (CRD)](#crd-requirements) +- [Mesh gateways](#mesh-gateway-requirements) +- [Exported services](#exported-service-requirements) +- [ACLs](#acl-requirements) + +## Helm specifications + +Consul's default configuration supports cluster peering connections directly between clusters. In production environments, we recommend using mesh gateways to securely route service mesh traffic between partitions with cluster peering connections. The following values must be set in the Helm chart to enable mesh gateways: + +- [`global.tls.enabled = true`](/consul/docs/k8s/helm#v-global-tls-enabled) +- [`meshGateway.enabled = true`](/consul/docs/k8s/helm#v-meshgateway-enabled) + +Refer to the following example Helm configuration: + + + +```yaml +global: + name: consul + image: "hashicorp/consul:1.14.1" + peering: + enabled: true + tls: + enabled: true +meshGateway: + enabled: true +``` + + + +After mesh gateways are enabled in the Helm chart, you can separately [configure Mesh CRDs](#mesh-gateway-configuration-for-kubernetes). + +## CRD specifications + +You must create the following CRDs in order to establish a peering connection: + +- `PeeringAcceptor`: Generates a peering token and accepts an incoming peering connection. +- `PeeringDialer`: Uses a peering token to make an outbound peering connection with the cluster that generated the token. + +Refer to the following example CRDs: + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-02 ## The name of the peer you want to connect to +spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" +``` + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-01 ## The name of the peer you want to connect to +spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" +``` + + + + +## Mesh gateway specifications + +To change Consul's default configuration and enable cluster peering through mesh gateways, use a mesh configuration entry to update your network's service mesh proxies globally: + +1. In `cluster-01` create the `Mesh` custom resource with `peeringThroughMeshGateways` set to `true`. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: Mesh + metadata: + name: mesh + spec: + peering: + peerThroughMeshGateways: true + ``` + + + +1. Apply the mesh CRD to `cluster-01`. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply -f mesh.yaml + ``` + +1. Apply the mesh CRD to `cluster-02`. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply -f mesh.yaml + ``` + + + + For help setting up the cluster context variables used in this example, refer to [assign cluster IDs to environmental variables](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering#assign-cluster-ids-to-environmental-variables). + + + +When cluster peering through mesh gateways, consider the following deployment requirements: + +- A Consul cluster requires a registered mesh gateway in order to export services to peers in other regions or cloud providers. +- The mesh gateway must also be registered in the same admin partition as the exported services and their `exported-services` configuration entry. An enterprise license is required to use multiple admin partitions with a single cluster of Consul servers. +- To use the `local` mesh gateway mode, you must register a mesh gateway in the importing cluster. +- Define the `Proxy.Config` settings using opaque parameters compatible with your proxy. For additional Envoy proxy configuration information, refer to [Gateway options](/consul/docs/connect/proxies/envoy#gateway-options) and [Escape-hatch overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides). + +### Mesh gateway modes + +By default, all cluster peering connections use mesh gateways in [remote mode](/consul/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). Be aware of these additional requirements when changing a mesh gateway's mode. + +- For mesh gateways that connect peered clusters, you can set the `mode` as either `remote` or `local`. +- The `none` mode is invalid for mesh gateways with cluster peering connections. + +To learn how to change the mesh gateway mode to `local` on your Kubernetes deployment, refer to [configure the mesh gateway mode for traffic between services](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering#configure-the-mesh-gateway-mode-for-traffic-between-services). + +## Exported service specifications + +The `exported-services` CRD is required in order for services to communicate across partitions with cluster peering connections. Basic guidance on using the `exported-services` configuration entry is included in [Establish cluster peering connections](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering#export-services-between-clusters). + +Refer to [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services) for more information. + +## ACL specifications + +If ACLs are enabled, you must add tokens to grant the following permissions: + +- Grant `service:write` permissions to services that define mesh gateways in their server definition. +- Grant `service:read` permissions for all services on the partition. +- Grant `mesh:write` permissions to the mesh gateways that participate in cluster peering connections. This permission allows a leaf certificate to be issued for mesh gateways to terminate TLS sessions for HTTP requests. \ No newline at end of file diff --git a/website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx b/website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx new file mode 100644 index 00000000000..19e504b95d6 --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx @@ -0,0 +1,453 @@ +--- +layout: docs +page_title: Establish Cluster Peering Connections on Kubernetes +description: >- + To establish a cluster peering connection on Kubernetes, generate a peering token to establish communication. Then export services and authorize requests with service intentions. +--- + +# Establish cluster peering connections on Kubernetes + +This page details the process for establishing a cluster peering connection between services in a Consul on Kubernetes deployment. + +The overall process for establishing a cluster peering connection consists of the following steps: + +1. Create a peering token in one cluster. +1. Use the peering token to establish peering with a second cluster. +1. Export services between clusters. +1. Create intentions to authorize services for peers. + +Cluster peering between services cannot be established until all four steps are complete. + +For general guidance for establishing cluster peering connections, refer to [Establish cluster peering connections](/consul/docs/connect/cluster-peering/usage/establish-cluster-peering). + +## Prerequisites + +You must meet the following requirements to use Consul's cluster peering features with Kubernetes: + +- Consul v1.14.1 or higher +- Consul on Kubernetes v1.0.0 or higher +- At least two Kubernetes clusters + +In Consul on Kubernetes, peers identify each other using the `metadata.name` values you establish when creating the `PeeringAcceptor` and `PeeringDialer` CRDs. For additional information about requirements for cluster peering on Kubernetes deployments, refer to [Cluster peering on Kubernetes technical specifications](/consul/docs/k8s/connect/cluster-peering/tech-specs). + +### Assign cluster IDs to environmental variables + +After you provision a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters, you can assign your clusters to environmental variables for future use. + +1. Get the context names for your Kubernetes clusters using one of these methods: + + - Run the `kubectl config current-context` command to get the context for the cluster you are currently in. + - Run the `kubectl config get-contexts` command to get all configured contexts in your kubeconfig file. + +1. Use the `kubectl` command to export the Kubernetes context names and then set them to variables. For more information on how to use kubeconfig and contexts, refer to the [Kubernetes docs on configuring access to multiple clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/). + + ```shell-session + $ export CLUSTER1_CONTEXT= + $ export CLUSTER2_CONTEXT= + ``` + +### Update the Helm chart + +To use cluster peering with Consul on Kubernetes deployments, update the Helm chart with [the required values](/consul/docs/k8s/connect/cluster-peering/tech-specs#helm-requirements). After updating the Helm chart, you can use the `consul-k8s` CLI to apply `values.yaml` to each cluster. + +1. In `cluster-01`, run the following commands: + + ```shell-session + $ export HELM_RELEASE_NAME1=cluster-01 + ``` + + ```shell-session + $ helm install ${HELM_RELEASE_NAME1} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc1 --kube-context $CLUSTER1_CONTEXT + ``` + +1. In `cluster-02`, run the following commands: + + ```shell-session + $ export HELM_RELEASE_NAME2=cluster-02 + ``` + + ```shell-session + $ helm install ${HELM_RELEASE_NAME2} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc2 --kube-context $CLUSTER2_CONTEXT + ``` + +### Configure the mesh gateway mode for traffic between services + +In Kubernetes deployments, you can configure mesh gateways to use `local` mode so that a service dialing a service in a remote peer dials the remote mesh gateway instead of the local mesh gateway. To configure the mesh gateway mode so that this traffic always leaves through the local mesh gateway, you can use the `ProxyDefaults` CRD. + +1. In `cluster-01` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ProxyDefaults + metadata: + name: global + spec: + meshGateway: + mode: local + ``` + + + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply -f proxy-defaults.yaml + ``` + +1. In `cluster-02` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ProxyDefaults + metadata: + name: global + spec: + meshGateway: + mode: local + ``` + + + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply -f proxy-defaults.yaml + ``` + +## Create a peering token + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use secret for establishing the secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + +1. In `cluster-01`, create the `PeeringAcceptor` custom resource. To ensure cluster peering connections are secure, the `metadata.name` field cannot be duplicated. Refer to the peer by a specific name. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: PeeringAcceptor + metadata: + name: cluster-02 ## The name of the peer you want to connect to + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. Apply the `PeeringAcceptor` resource to the first cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply --filename acceptor.yaml + ``` + +1. Save your peering token so that you can export it to the other cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT get secret peering-token --output yaml > peering-token.yaml + ``` + +## Establish a connection between clusters + +Next, use the peering token to establish a secure connection between the clusters. + +1. Apply the peering token to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename peering-token.yaml + ``` + +1. In `cluster-02`, create the `PeeringDialer` custom resource. To ensure cluster peering connections are secure, the `metadata.name` field cannot be duplicated. Refer to the peer by a specific name. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: PeeringDialer + metadata: + name: cluster-01 ## The name of the peer you want to connect to + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. Apply the `PeeringDialer` resource to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename dialer.yaml + ``` + +## Export services between clusters + +After you establish a connection between the clusters, you need to create an `exported-services` CRD that defines the services that are available to another admin partition. + +While the CRD can target admin partitions either locally or remotely, clusters peering always exports services to remote admin partitions. Refer to [exported service consumers](/consul/docs/connect/config-entries/exported-services#consumers-1) for more information. + + +1. For the service in `cluster-02` that you want to export, add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods prior to deploying. The annotation allows the workload to join the mesh. It is highlighted in the following example: + + + + ```yaml + # Service to expose backend + apiVersion: v1 + kind: Service + metadata: + name: backend + spec: + selector: + app: backend + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 9090 + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: backend + --- + # Deployment for backend + apiVersion: apps/v1 + kind: Deployment + metadata: + name: backend + labels: + app: backend + spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + serviceAccountName: backend + containers: + - name: backend + image: nicholasjackson/fake-service:v0.22.4 + ports: + - containerPort: 9090 + env: + - name: "LISTEN_ADDR" + value: "0.0.0.0:9090" + - name: "NAME" + value: "backend" + - name: "MESSAGE" + value: "Response from backend" + ``` + + + +1. Deploy the `backend` service to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename backend.yaml + ``` + +1. In `cluster-02`, create an `ExportedServices` custom resource. The name of the peer that consumes the service should be identical to the name set in the `PeeringDialer` CRD. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ExportedServices + metadata: + name: default ## The name of the partition containing the service + spec: + services: + - name: backend ## The name of the service you want to export + consumers: + - peer: cluster-01 ## The name of the peer that receives the service + ``` + + + +1. Apply the `ExportedServices` resource to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename exported-service.yaml + ``` + +## Authorize services for peers + +Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. + +1. Create service intentions for the second cluster. The name of the peer should match the name set in the `PeeringDialer` CRD. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceIntentions + metadata: + name: backend-deny + spec: + destination: + name: backend + sources: + - name: "*" + action: deny + - name: frontend + action: allow + peer: cluster-01 ## The peer of the source service + ``` + + + +1. Apply the intentions to the second cluster. + + + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename intention.yaml + ``` + + + +1. Add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods before deploying the workload so that the services in `cluster-01` can dial `backend` in `cluster-02`. To dial the upstream service from an application, configure the application so that that requests are sent to the correct DNS name as specified in [Service Virtual IP Lookups](/consul/docs/services/discovery/dns-static-lookups#service-virtual-ip-lookups). In the following example, the annotation that allows the workload to join the mesh and the configuration provided to the workload that enables the workload to dial the upstream service using the correct DNS name is highlighted. [Service Virtual IP Lookups for Consul Enterprise](/consul/docs/services/discovery/dns-static-lookups#service-virtual-ip-lookups-for-consul-enterprise) details how you would similarly format a DNS name including partitions and namespaces. + + + + ```yaml + # Service to expose frontend + apiVersion: v1 + kind: Service + metadata: + name: frontend + spec: + selector: + app: frontend + ports: + - name: http + protocol: TCP + port: 9090 + targetPort: 9090 + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: frontend + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: frontend + labels: + app: frontend + spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + serviceAccountName: frontend + containers: + - name: frontend + image: nicholasjackson/fake-service:v0.22.4 + securityContext: + capabilities: + add: ["NET_ADMIN"] + ports: + - containerPort: 9090 + env: + - name: "LISTEN_ADDR" + value: "0.0.0.0:9090" + - name: "UPSTREAM_URIS" + value: "http://backend.virtual.cluster-02.consul" + - name: "NAME" + value: "frontend" + - name: "MESSAGE" + value: "Hello World" + - name: "HTTP_CLIENT_KEEP_ALIVES" + value: "false" + ``` + + + +1. Apply the service file to the first cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply --filename frontend.yaml + ``` + +1. Run the following command in `frontend` and then check the output to confirm that you peered your clusters successfully. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT exec -it $(kubectl --context $CLUSTER1_CONTEXT get pod -l app=frontend -o name) -- curl localhost:9090 + ``` + + + + ```json + { + "name": "frontend", + "uri": "/", + "type": "HTTP", + "ip_addresses": [ + "10.16.2.11" + ], + "start_time": "2022-08-26T23:40:01.167199", + "end_time": "2022-08-26T23:40:01.226951", + "duration": "59.752279ms", + "body": "Hello World", + "upstream_calls": { + "http://backend.virtual.cluster-02.consul": { + "name": "backend", + "uri": "http://backend.virtual.cluster-02.consul", + "type": "HTTP", + "ip_addresses": [ + "10.32.2.10" + ], + "start_time": "2022-08-26T23:40:01.223503", + "end_time": "2022-08-26T23:40:01.224653", + "duration": "1.149666ms", + "headers": { + "Content-Length": "266", + "Content-Type": "text/plain; charset=utf-8", + "Date": "Fri, 26 Aug 2022 23:40:01 GMT" + }, + "body": "Response from backend", + "code": 200 + } + }, + "code": 200 + } + ``` + + + +### Authorize service reads with ACLs + +If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. + +Read access to all imported services is granted using either of the following rules associated with an ACL token: + +- `service:write` permissions for any service in the sidecar's partition. +- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. + +For Consul Enterprise, the permissions apply to all imported services in the service's partition. These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). + +Refer to [Reading servers](/consul/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` configuration entry documentation for example rules. + +For additional information about how to configure and use ACLs, refer to [ACLs system overview](/consul/docs/security/acl). \ No newline at end of file diff --git a/website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx b/website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx new file mode 100644 index 00000000000..956298fe3cd --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx @@ -0,0 +1,75 @@ +--- +layout: docs +page_title: Manage L7 Traffic With Cluster Peering on Kubernetes +description: >- + Combine service resolver configurations with splitter and router configurations to manage L7 traffic in Consul on Kubernetes deployments with cluster peering connections. Learn how to define dynamic traffic rules to target peers for redirects in k8s. +--- + +# Manage L7 traffic with cluster peering on Kubernetes + +This usage topic describes how to configure the `service-resolver` custom resource definition (CRD) to set up and manage L7 traffic between services that have an existing cluster peering connection in Consul on Kubernetes deployments. + +For general guidance for managing L7 traffic with cluster peering, refer to [Manage L7 traffic with cluster peering](/consul/docs/connect/cluster-peering/usage/peering-traffic-management). + +## Service resolvers for redirects and failover + +When you use cluster peering to connect datacenters through their admin partitions, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically forward traffic to services hosted on peer clusters. + +However, the `service-splitter` and `service-router` CRDs do not natively support directly targeting a service instance hosted on a peer. Before you can split or route traffic to a service on a peer, you must define the service hosted on the peer as an upstream service by configuring a failover in a `service-resolver` CRD. Then, you can set up a redirect in a second service resolver to interact with the peer service by name. + +For more information about formatting, updating, and managing configuration entries in Consul, refer to [How to use configuration entries](/consul/docs/agent/config-entries). + +## Configure dynamic traffic between peers + +To configure L7 traffic management behavior in deployments with cluster peering connections, complete the following steps in order: + +1. Define the peer cluster as a failover target in the service resolver configuration. + + The following example updates the [`service-resolver` CRD](/consul/docs/connect/config-entries/) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend + spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-02' + service: 'frontend' + namespace: 'default' + ``` + +1. Define the desired behavior in `service-splitter` or `service-router` CRD. + + The following example splits traffic evenly between `frontend` and `frontend-peer`: + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceSplitter + metadata: + name: frontend + spec: + splits: + - weight: 50 + ## defaults to service with same name as configuration entry ("frontend") + - weight: 50 + service: frontend-peer + ``` + +1. Create a second `service-resolver` configuration entry on the local cluster that resolves the name of the peer service you used when splitting or routing the traffic. + + The following example uses the name `frontend-peer` to define a redirect targeting the `frontend` service on the peer `cluster-02`: + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend-peer + spec: + redirect: + peer: 'cluster-02' + service: 'frontend' + ``` \ No newline at end of file diff --git a/website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx b/website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx new file mode 100644 index 00000000000..bc622fe15d3 --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx @@ -0,0 +1,121 @@ +--- +layout: docs +page_title: Manage Cluster Peering Connections on Kubernetes +description: >- + Learn how to list, read, and delete cluster peering connections using Consul on Kubernetes. You can also reset cluster peering connections on k8s deployments. +--- + +# Manage cluster peering connections on Kubernetes + +This usage topic describes how to manage cluster peering connections on Kubernetes deployments. + +After you establish a cluster peering connection, you can get a list of all active peering connections, read a specific peering connection's information, and delete peering connections. + +For general guidance for managing cluster peering connections, refer to [Manage L7 traffic with cluster peering](/consul/docs/connect/cluster-peering/usage/peering-traffic-management). + +## Reset a peering connection + +To reset the cluster peering connection, you need to generate a new peering token from the cluster where you created the `PeeringAcceptor` CRD. The only way to create or set a new peering token is to manually adjust the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token causes the previous token to expire. + +1. In the `PeeringAcceptor` CRD, add the annotation `consul.hashicorp.com/peering-version`. If the annotation already exists, update its value to a higher version. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: PeeringAcceptor + metadata: + name: cluster-02 + annotations: + consul.hashicorp.com/peering-version: "1" ## The peering version you want to set, must be in quotes + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. After updating `PeeringAcceptor`, repeat all of the steps to [establish a new peering connection](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering). + +## List all peering connections + +In Consul on Kubernetes deployments, you can list all active peering connections in a cluster using the Consul CLI. + +1. If necessary, [configure your CLI to interact with the Consul cluster](/consul/tutorials/get-started-kubernetes/kubernetes-gs-deploy#configure-your-cli-to-interact-with-consul-cluster). + +1. Run the [`consul peering list` CLI command](/consul/commands/peering/list). + + ```shell-session + $ consul peering list + Name State Imported Svcs Exported Svcs Meta + cluster-02 ACTIVE 0 2 env=production + cluster-03 PENDING 0 0 + ``` + +## Read a peering connection + +In Consul on Kubernetes deployments, you can get information about individual peering connections between clusters using the Consul CLI. + +1. If necessary, [configure your CLI to interact with the Consul cluster](/consul/tutorials/get-started-kubernetes/kubernetes-gs-deploy#configure-your-cli-to-interact-with-consul-cluster). + +1. Run the [`consul peering read` CLI command](/consul/commands/peering/read). + + ```shell-session + $ consul peering read -name cluster-02 + Name: cluster-02 + ID: 3b001063-8079-b1a6-764c-738af5a39a97 + State: ACTIVE + Meta: + env=production + + Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 + Peer Server Name: server.dc1.consul + Peer CA Pems: 0 + Peer Server Addresses: + 10.0.0.1:8300 + + Imported Services: 0 + Exported Services: 2 + + Create Index: 89 + Modify Index: 89 + ``` + +## Delete peering connections + +To end a peering connection in Kubernetes deployments, delete both the `PeeringAcceptor` and `PeeringDialer` resources. + +1. Delete the `PeeringDialer` resource from the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT delete --filename dialer.yaml + ``` + +1. Delete the `PeeringAcceptor` resource from the first cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT delete --filename acceptor.yaml + ```` + +To confirm that you deleted your peering connection in `cluster-01`, query the the `/health` HTTP endpoint: + +1. Exec into the server pod for the first cluster. + + ```shell-session + $ kubectl exec -it consul-server-0 --context $CLUSTER1_CONTEXT -- /bin/sh + ``` + +1. If you've enabled ACLs, export an ACL token to access the `/health` HTP endpoint for services. The bootstrap token may be used if an ACL token is not already provisioned. + + ```shell-session + $ export CONSUL_HTTP_TOKEN= + ``` + +1. Query the the `/health` HTTP endpoint. Peered services with deleted connections should no longe appear. + + ```shell-session + $ curl "localhost:8500/v1/health/connect/backend?peer=cluster-02" + ``` \ No newline at end of file diff --git a/website/content/docs/k8s/connect/index.mdx b/website/content/docs/k8s/connect/index.mdx index 7e30ab2be89..00e5c8e9383 100644 --- a/website/content/docs/k8s/connect/index.mdx +++ b/website/content/docs/k8s/connect/index.mdx @@ -175,18 +175,18 @@ upstream. This is analogous to the standard Kubernetes service environment varia point instead to the correct local proxy port to establish connections via Connect. -We can verify access to the static text server using `kubectl exec`. +You can verify access to the static text server using `kubectl exec`. Because transparent proxy is enabled by default, -we use Kubernetes DNS to connect to our desired upstream. +use Kubernetes DNS to connect to your desired upstream. ```shell-session $ kubectl exec deploy/static-client -- curl --silent http://static-server/ "hello world" ``` -We can control access to the server using [intentions](/consul/docs/connect/intentions). +You can control access to the server using [intentions](/consul/docs/connect/intentions). If you use the Consul UI or [CLI](/consul/commands/intention/create) to -create a deny [intention](/consul/docs/connect/intentions) between +deny communication between "static-client" and "static-server", connections are immediately rejected without updating either of the running pods. You can then remove this intention to allow connections again. diff --git a/website/content/docs/k8s/crds/index.mdx b/website/content/docs/k8s/crds/index.mdx index 342fe1aaa4c..6a68960a04d 100644 --- a/website/content/docs/k8s/crds/index.mdx +++ b/website/content/docs/k8s/crds/index.mdx @@ -16,8 +16,8 @@ You can specify the following values in the `kind` field. Click on a configurati - [`Mesh`](/consul/docs/connect/config-entries/mesh) - [`ExportedServices`](/consul/docs/connect/config-entries/exported-services) -- [`PeeringAcceptor`](/consul/docs/connect/cluster-peering/k8s#peeringacceptor) -- [`PeeringDialer`](/consul/docs/connect/cluster-peering/k8s#peeringdialer) +- [`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) - [`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/consul-enterprise.mdx b/website/content/docs/k8s/deployment-configurations/consul-enterprise.mdx index 48c4db1fa0c..29314c94354 100644 --- a/website/content/docs/k8s/deployment-configurations/consul-enterprise.mdx +++ b/website/content/docs/k8s/deployment-configurations/consul-enterprise.mdx @@ -11,7 +11,7 @@ You can use this Helm chart to deploy Consul Enterprise by following a few extra Find the license file that you received in your welcome email. It should have a `.hclic` extension. You will use the contents of this file to create a Kubernetes secret before installing the Helm chart. --> **Note:** This guide assumes you are storing your license as a Kubernetes Secret. If you would like to store the enterprise license in Vault, please reference [Storing the Enterprise License in Vault](/consul/docs/k8s/deployment-configuration/vault/data-integration/enterprise-license). +-> **Note:** This guide assumes you are storing your license as a Kubernetes Secret. If you would like to store the enterprise license in Vault, please reference [Storing the Enterprise License in Vault](/consul/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license). You can use the following commands to create the secret with name `consul-ent-license` and key `key`: diff --git a/website/content/docs/k8s/deployment-configurations/servers-outside-kubernetes.mdx b/website/content/docs/k8s/deployment-configurations/servers-outside-kubernetes.mdx index 93c98d7f317..8050e4d01b6 100644 --- a/website/content/docs/k8s/deployment-configurations/servers-outside-kubernetes.mdx +++ b/website/content/docs/k8s/deployment-configurations/servers-outside-kubernetes.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Join External Servers to Consul on Kubernetes +page_title: Join Kubernetes Clusters to external Consul Servers description: >- - Client agents that run on Kubernetes pods can join existing clusters whose server agents run outside of k8s. Learn how to expose gossip ports and bootstrap ACLs by configuring the Helm chart. + Kubernetes clusters can be joined to existing Consul clusters in a much simpler way with the introduction of Consul Dataplane. Learn how to add Kubernetes Clusters into an existing Consul cluster and bootstrap ACLs by configuring the Helm chart. --- -# Join External Servers to Consul on Kubernetes +# Join Kubernetes Clusters to external Consul Servers If you have a Consul cluster already running, you can configure your Consul on Kubernetes installation to join this existing cluster. @@ -14,9 +14,7 @@ The below `values.yaml` file shows how to configure the Helm chart to install Consul so that it joins an existing Consul server cluster. The `global.enabled` value first disables all chart components by default -so that each component is opt-in. This allows us to _only_ setup the client -agents. We then opt-in to the client agents by setting `client.enabled` to -`true`. +so that each component is opt-in. Next, configure `externalServers` to point it to Consul servers. The `externalServers.hosts` value must be provided and should be set to a DNS, an IP, @@ -37,8 +35,10 @@ externalServers: - **Note:** To join Consul on Kubernetes to an existing Consul server cluster running outside of Kubernetes, -refer to [Consul servers outside of Kubernetes](/consul/docs/k8s/deployment-configurations/servers-outside-kubernetes). +With the introduction of [Consul Dataplane](/consul/docs/connect/dataplane#what-is-consul-dataplane), Consul installation on Kubernetes is simplified by removing the Consul Client agents. +This requires the Helm installation and rest of the consul-k8s components installed on Kubernetes to talk to Consul Servers directly on various ports. +Before starting the installation, ensure that the Consul Servers are configured to have the gRPC port enabled `8502/tcp` using the [`ports.grpc = 8502`](/consul/docs/agent/config/config-files#grpc) configuration option. + ## Configuring TLS @@ -68,7 +68,7 @@ externalServers: If your HTTPS port is different from Consul's default `8501`, you must also set -`externalServers.httpsPort`. +`externalServers.httpsPort`. If the Consul servers are not running TLS enabled, use this config to set the HTTP port the servers are configured with (default `8500`). ## Configuring ACLs diff --git a/website/content/docs/k8s/deployment-configurations/single-dc-multi-k8s.mdx b/website/content/docs/k8s/deployment-configurations/single-dc-multi-k8s.mdx index b9694b39125..46fc2228016 100644 --- a/website/content/docs/k8s/deployment-configurations/single-dc-multi-k8s.mdx +++ b/website/content/docs/k8s/deployment-configurations/single-dc-multi-k8s.mdx @@ -50,6 +50,17 @@ global: gossipEncryption: secretName: consul-gossip-encryption-key secretKey: key +server: + exposeService: + enabled: true + type: NodePort + nodePort: + ## all are random nodePorts and you can set your own + http: 30010 + https: 30011 + serf: 30012 + rpc: 30013 + grpc: 30014 ui: service: type: NodePort @@ -65,6 +76,8 @@ The UI's service type is set to be `NodePort`. This is needed to connect to servers from another cluster without using the pod IPs of the servers, which are likely going to change. +Other services are exposed as `NodePort` services and configured with random port numbers. In this example, the `grpc` port is set to `30014`, which enables services to discover Consul servers using gRPC when connecting from another cluster. + To deploy, first generate the Gossip encryption key and save it as a Kubernetes secret. ```shell-session @@ -123,6 +136,8 @@ externalServers: hosts: ["10.0.0.4"] # The node port of the UI's NodePort service or the load balancer port. httpsPort: 31557 + # Matches the gRPC port of the Consul servers in the first cluster. + grpcPort: 30014 tlsServerName: server.dc1.consul # The address of the kube API server of this Kubernetes cluster k8sAuthMethodHost: https://kubernetes.example.com:443 @@ -147,6 +162,8 @@ NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE cluster1-consul-ui NodePort 10.0.240.80 443:31557/TCP 40h ``` +The `grpcPort: 30014` configuration refers to the gRPC port number specified in the `NodePort` configuration in the first cluster. + Set the `externalServer.tlsServerName` to `server.dc1.consul`. This the DNS SAN (Subject Alternative Name) that is present in the Consul server's certificate. This is required because the connection to the Consul servers uses the node IP, diff --git a/website/content/docs/k8s/dns.mdx b/website/content/docs/k8s/dns.mdx index 5082919bd1d..0f34dd25078 100644 --- a/website/content/docs/k8s/dns.mdx +++ b/website/content/docs/k8s/dns.mdx @@ -8,10 +8,10 @@ description: >- # Resolve Consul DNS Requests in Kubernetes One of the primary query interfaces to Consul is the -[DNS interface](/consul/docs/discovery/dns). You can configure Consul DNS in +[DNS interface](/consul/docs/services/discovery/dns-overview). You can 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/proxy/) if using CoreDNS. +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. diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index b5eb83c0df0..ac6d802c12d 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -58,7 +58,7 @@ Use these links to navigate to a particular top-level stanza. the prefix will be `-consul`. - `domain` ((#v-global-domain)) (`string: consul`) - The domain Consul will answer DNS queries for - (see `-domain` (https://www.consul.io/docs/agent/config/cli-flags#_domain)) and the domain services synced from + (Refer to [`-domain`](/consul/docs/agent/config/cli-flags#_domain)) and the domain services synced from Consul into Kubernetes will have, e.g. `service-name.service.consul`. - `peering` ((#v-global-peering)) - Configures the Cluster Peering feature. Requires Consul v1.14+ and Consul-K8s v1.0.0+. @@ -94,7 +94,7 @@ Use these links to navigate to a particular top-level stanza. - `imagePullSecrets` ((#v-global-imagepullsecrets)) (`array`) - Array of objects containing image pull secret names that will be applied to each service account. This can be used to reference image pull secrets if using a custom consul or consul-k8s-control-plane Docker image. - See https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry for reference. + Refer to https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry. Example: @@ -114,12 +114,13 @@ Use these links to navigate to a particular top-level stanza. https://github.com/hashicorp/consul/issues/1858. - `enablePodSecurityPolicies` ((#v-global-enablepodsecuritypolicies)) (`boolean: false`) - Controls whether pod security policies are created for the Consul components - created by this chart. See https://kubernetes.io/docs/concepts/policy/pod-security-policy/. + created by this chart. Refer to https://kubernetes.io/docs/concepts/policy/pod-security-policy/. - `secretsBackend` ((#v-global-secretsbackend)) - secretsBackend is used to configure Vault as the secrets backend for the Consul on Kubernetes installation. The Vault cluster needs to have the Kubernetes Auth Method, KV2 and PKI secrets engines enabled and have necessary secrets, policies and roles created prior to installing Consul. - See https://www.consul.io/docs/k8s/installation/vault for full instructions. + Refer to [Vault as the Secrets Backend](/consul/docs/k8s/deployment-configurations/vault) + documentation for full instructions. The Vault cluster _must_ not have the Consul cluster installed by this Helm chart as its storage backend as that would cause a circular dependency. @@ -177,11 +178,6 @@ Use these links to navigate to a particular top-level stanza. ``` and check the name of `metadata.name`. - - `controllerRole` ((#v-global-secretsbackend-vault-controllerrole)) (`string: ""`) - The Vault role to read Consul controller's webhook's - CA and issue a certificate and private key. - A Vault policy must be created which grants issue capabilities to - `global.secretsBackend.vault.controller.tlsCert.secretName`. - - `connectInjectRole` ((#v-global-secretsbackend-vault-connectinjectrole)) (`string: ""`) - The Vault role to read Consul connect-injector webhook's CA and issue a certificate and private key. A Vault policy must be created which grants issue capabilities to @@ -214,21 +210,21 @@ Use these links to navigate to a particular top-level stanza. The provider will be configured to use the Vault Kubernetes auth method and therefore requires the role provided by `global.secretsBackend.vault.consulServerRole` to have permissions to the root and intermediate PKI paths. - Please see https://www.consul.io/docs/connect/ca/vault#vault-acl-policies - for information on how to configure the Vault policies. + Please refer to [Vault ACL policies](/consul/docs/connect/ca/vault#vault-acl-policies) + documentation for information on how to configure the Vault policies. - `address` ((#v-global-secretsbackend-vault-connectca-address)) (`string: ""`) - The address of the Vault server. - `authMethodPath` ((#v-global-secretsbackend-vault-connectca-authmethodpath)) (`string: kubernetes`) - The mount path of the Kubernetes auth method in Vault. - `rootPKIPath` ((#v-global-secretsbackend-vault-connectca-rootpkipath)) (`string: ""`) - The path to a PKI secrets engine for the root certificate. - For more details, please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#rootpkipath). + For more details, please refer to [Vault Connect CA configuration](/consul/docs/connect/ca/vault#rootpkipath). - `intermediatePKIPath` ((#v-global-secretsbackend-vault-connectca-intermediatepkipath)) (`string: ""`) - The path to a PKI secrets engine for the generated intermediate certificate. - For more details, please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#intermediatepkipath). + For more details, please refer to [Vault Connect CA configuration](/consul/docs/connect/ca/vault#intermediatepkipath). - `additionalConfig` ((#v-global-secretsbackend-vault-connectca-additionalconfig)) (`string: {}`) - Additional Connect CA configuration in JSON format. - Please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#configuration) + Please refer to [Vault Connect CA configuration](/consul/docs/connect/ca/vault#configuration) for all configuration options available for that provider. Example: @@ -245,22 +241,6 @@ Use these links to navigate to a particular top-level stanza. } ``` - - `controller` ((#v-global-secretsbackend-vault-controller)) - - - `tlsCert` ((#v-global-secretsbackend-vault-controller-tlscert)) - Configuration to the Vault Secret that Kubernetes will use on - Kubernetes CRD creation, deletion, and update, to get TLS certificates - used issued from vault to send webhooks to the controller. - - - `secretName` ((#v-global-secretsbackend-vault-controller-tlscert-secretname)) (`string: null`) - The Vault secret path that issues TLS certificates for controller - webhooks. - - - `caCert` ((#v-global-secretsbackend-vault-controller-cacert)) - Configuration to the Vault Secret that Kubernetes will use on - Kubernetes CRD creation, deletion, and update, to get CA certificates - used issued from vault to send webhooks to the controller. - - - `secretName` ((#v-global-secretsbackend-vault-controller-cacert-secretname)) (`string: null`) - The Vault secret path that contains the CA certificate for controller - webhooks. - - `connectInject` ((#v-global-secretsbackend-vault-connectinject)) - `caCert` ((#v-global-secretsbackend-vault-connectinject-cacert)) - Configuration to the Vault Secret that Kubernetes uses on @@ -278,7 +258,7 @@ Use these links to navigate to a particular top-level stanza. inject webhooks. - `gossipEncryption` ((#v-global-gossipencryption)) - Configures Consul's gossip encryption key. - (see `-encrypt` (https://www.consul.io/docs/agent/config/cli-flags#_encrypt)). + (Refer to [`-encrypt`](/consul/docs/agent/config/cli-flags#_encrypt)). By default, gossip encryption is not enabled. The gossip encryption key may be set automatically or manually. The recommended method is to automatically generate the key. To automatically generate and set a gossip encryption key, set autoGenerate to true. @@ -286,7 +266,7 @@ Use these links to navigate to a particular top-level stanza. To manually generate a gossip encryption key, set secretName and secretKey and use Consul to generate a key, saving this as a Kubernetes secret or Vault secret path and key. If `global.secretsBackend.vault.enabled=true`, be sure to add the "data" component of the secretName path as required by - the Vault KV-2 secrets engine [see example]. + the Vault KV-2 secrets engine [refer to example]. ```shell-session $ kubectl create secret generic consul-gossip-encryption-key --from-literal=key=$(consul keygen) @@ -309,17 +289,17 @@ Use these links to navigate to a particular top-level stanza. - `recursors` ((#v-global-recursors)) (`array: []`) - A list of addresses of upstream DNS servers that are used to recursively resolve DNS queries. These values are given as `-recursor` flags to Consul servers and clients. - See https://www.consul.io/docs/agent/config/cli-flags#_recursor for more details. + Refer to [`-recursor`](/consul/docs/agent/config/cli-flags#_recursor) for more details. If this is an empty array (the default), then Consul DNS will only resolve queries for the Consul top level domain (by default `.consul`). - - `tls` ((#v-global-tls)) - Enables TLS (https://learn.hashicorp.com/tutorials/consul/tls-encryption-secure) + - `tls` ((#v-global-tls)) - Enables [TLS](/consul/tutorials/security/tls-encryption-secure) across the cluster to verify authenticity of the Consul servers and clients. Requires Consul v1.4.1+. - `enabled` ((#v-global-tls-enabled)) (`boolean: false`) - If true, the Helm chart will enable TLS for Consul servers and clients and all consul-k8s-control-plane components, as well as generate certificate authority (optional) and server and client certificates. - This setting is required for [Cluster Peering](https://developer.hashicorp.com/consul/docs/connect/cluster-peering/k8s). + This setting is required for [Cluster Peering](/consul/docs/connect/cluster-peering/k8s). - `enableAutoEncrypt` ((#v-global-tls-enableautoencrypt)) (`boolean: false`) - If true, turns on the auto-encrypt feature on clients and servers. It also switches consul-k8s-control-plane components to retrieve the CA from the servers @@ -336,7 +316,7 @@ Use these links to navigate to a particular top-level stanza. - `verify` ((#v-global-tls-verify)) (`boolean: true`) - If true, `verify_outgoing`, `verify_server_hostname`, and `verify_incoming` for internal RPC communication will be set to `true` for Consul servers and clients. Set this to false to incrementally roll out TLS on an existing Consul cluster. - Please see https://consul.io/docs/k8s/operations/tls-on-existing-cluster + Please refer to [TLS on existing clusters](/consul/docs/k8s/operations/tls-on-existing-cluster) for more details. - `httpsOnly` ((#v-global-tls-httpsonly)) (`boolean: true`) - If true, the Helm chart will configure Consul to disable the HTTP port on @@ -372,8 +352,9 @@ Use these links to navigate to a particular top-level stanza. Note that we need the CA key so that we can generate server and client certificates. It is particularly important for the client certificates since they need to have host IPs - as Subject Alternative Names. In the future, we may support bringing your own server - certificates. + as Subject Alternative Names. If you are setting server certs yourself via `server.serverCert` + and you are not enabling clients (or clients are enabled with autoEncrypt) then you do not + need to provide the CA key. - `secretName` ((#v-global-tls-cakey-secretname)) (`string: null`) - The name of the Kubernetes or Vault secret that holds the CA key. @@ -392,14 +373,20 @@ Use these links to navigate to a particular top-level stanza. for all Consul and consul-k8s-control-plane components. This requires Consul >= 1.4. - - `bootstrapToken` ((#v-global-acls-bootstraptoken)) - A Kubernetes or Vault secret containing the bootstrap token to use for - creating policies and tokens for all Consul and consul-k8s-control-plane components. - If set, we will skip ACL bootstrapping of the servers and will only - initialize ACLs for the Consul clients and consul-k8s-control-plane system components. + - `bootstrapToken` ((#v-global-acls-bootstraptoken)) - A Kubernetes or Vault secret containing the bootstrap token to use for creating policies and + tokens for all Consul and consul-k8s-control-plane components. If `secretName` and `secretKey` + are unset, a default secret name and secret key are used. If the secret is populated, then + we will skip ACL bootstrapping of the servers and will only initialize ACLs for the Consul + clients and consul-k8s-control-plane system components. + If the secret is empty, then we will bootstrap ACLs on the Consul servers, and write the + bootstrap token to this secret. If ACLs are already bootstrapped on the servers, then the + secret must contain the bootstrap token. - `secretName` ((#v-global-acls-bootstraptoken-secretname)) (`string: null`) - The name of the Kubernetes or Vault secret that holds the bootstrap token. + If unset, this defaults to `{{ global.name }}-bootstrap-acl-token`. - `secretKey` ((#v-global-acls-bootstraptoken-secretkey)) (`string: null`) - The key within the Kubernetes or Vault secret that holds the bootstrap token. + If unset, this defaults to `token`. - `createReplicationToken` ((#v-global-acls-createreplicationtoken)) (`boolean: false`) - If true, an ACL token will be created that can be used in secondary datacenters for replication. This should only be set to true in the @@ -430,9 +417,9 @@ Use these links to navigate to a particular top-level stanza. - `tolerations` ((#v-global-acls-tolerations)) (`string: ""`) - tolerations configures the taints and tolerations for the server-acl-init and server-acl-init-cleanup jobs. This should be a multi-line string matching the - Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. - - `nodeSelector` ((#v-global-acls-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + - `nodeSelector` ((#v-global-acls-nodeselector)) (`string: null`) - This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) labels for the server-acl-init and server-acl-init-cleanup jobs pod assignment, formatted as a multi-line string. Example: @@ -482,7 +469,7 @@ Use these links to navigate to a particular top-level stanza. This address must be reachable from the Consul servers in the primary datacenter. This auth method will be used to provision ACL tokens for Consul components and is different from the one used by the Consul Service Mesh. - Please see the [Kubernetes Auth Method documentation](https://consul.io/docs/acl/auth-methods/kubernetes). + Please refer to the [Kubernetes Auth Method documentation](/consul/docs/security/acl/auth-methods/kubernetes). You can retrieve this value from your `kubeconfig` by running: @@ -593,7 +580,7 @@ Use these links to navigate to a particular top-level stanza. Consul server agents. - `replicas` ((#v-server-replicas)) (`integer: 1`) - The number of server agents to run. This determines the fault tolerance of - the cluster. Please see the deployment table (https://consul.io/docs/internals/consensus#deployment-table) + the cluster. Please refer to the [deployment table](/consul/docs/architecture/consensus#deployment-table) for more information. - `bootstrapExpect` ((#v-server-bootstrapexpect)) (`int: null`) - The number of servers that are expected to be running. @@ -632,8 +619,8 @@ Use these links to navigate to a particular top-level stanza. Vault Secrets backend: If you are using Vault as a secrets backend, a Vault Policy must be created which allows `["create", "update"]` capabilities on the PKI issuing endpoint, which is usually of the form `pki/issue/consul-server`. - Please see the following guide for steps to generate a compatible certificate: - https://learn.hashicorp.com/tutorials/consul/vault-pki-consul-secure-tls + Complete [this tutorial](/consul/tutorials/vault-secure/vault-pki-consul-secure-tls) + to learn how to generate a compatible certificate. Note: when using TLS, both the `server.serverCert` and `global.tls.caCert` which points to the CA endpoint of this PKI engine must be provided. @@ -672,15 +659,15 @@ Use these links to navigate to a particular top-level stanza. storage classes, the PersistentVolumeClaims would need to be manually created. A `null` value will use the Kubernetes cluster's default StorageClass. If a default StorageClass does not exist, you will need to create one. - Refer to the [Read/Write Tuning](https://www.consul.io/docs/install/performance#read-write-tuning) + Refer to the [Read/Write Tuning](/consul/docs/install/performance#read-write-tuning) section of the Server Performance Requirements documentation for considerations around choosing a performant storage class. - ~> **Note:** The [Reference Architecture](https://learn.hashicorp.com/tutorials/consul/reference-architecture#hardware-sizing-for-consul-servers) + ~> **Note:** The [Reference Architecture](/consul/tutorials/production-deploy/reference-architecture#hardware-sizing-for-consul-servers) contains best practices and recommendations for selecting suitable hardware sizes for your Consul servers. - - `connect` ((#v-server-connect)) (`boolean: true`) - This will enable/disable Connect (https://consul.io/docs/connect). Setting this to true + - `connect` ((#v-server-connect)) (`boolean: true`) - This will enable/disable [Connect](/consul/docs/connect). Setting this to true _will not_ automatically secure pod communication, this setting will only enable usage of the feature. Consul will automatically initialize a new CA and set of certificates. Additional Connect settings can be configured @@ -699,7 +686,7 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-server-resources)) (`map`) - The resource requests (CPU, memory, etc.) for each of the server agents. This should be a YAML map corresponding to a Kubernetes - ResourceRequirements (https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#resourcerequirements-v1-core) + [`ResourceRequirements``](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#resourcerequirements-v1-core) object. NOTE: The use of a YAML string is deprecated. Example: @@ -730,11 +717,12 @@ Use these links to navigate to a particular top-level stanza. - `updatePartition` ((#v-server-updatepartition)) (`integer: 0`) - This value is used to carefully control a rolling update of Consul server agents. This value specifies the - partition (https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions) - for performing a rolling update. Please read the linked Kubernetes documentation - and https://www.consul.io/docs/k8s/upgrade#upgrading-consul-servers for more information. + [partition](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions) + for performing a rolling update. Please read the linked Kubernetes + and [Upgrade Consul](/consul/docs/k8s/upgrade#upgrading-consul-servers) + documentation for more information. - - `disruptionBudget` ((#v-server-disruptionbudget)) - This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + - `disruptionBudget` ((#v-server-disruptionbudget)) - This configures the [`PodDisruptionBudget`](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for the server cluster. - `enabled` ((#v-server-disruptionbudget-enabled)) (`boolean: true`) - Enables registering a PodDisruptionBudget for the server @@ -747,7 +735,7 @@ Use these links to navigate to a particular top-level stanza. --set 'server.disruptionBudget.maxUnavailable=0'` flag to the helm chart installation command because of a limitation in the Helm templating language. - - `extraConfig` ((#v-server-extraconfig)) (`string: {}`) - A raw string of extra JSON configuration (https://consul.io/docs/agent/options) for Consul + - `extraConfig` ((#v-server-extraconfig)) (`string: {}`) - A raw string of extra [JSON configuration](/consul/docs/agent/config/config-files) for Consul servers. This will be saved as-is into a ConfigMap that is read by the Consul server agents. This can be used to add additional configuration that isn't directly exposed by the chart. @@ -803,7 +791,7 @@ Use these links to navigate to a particular top-level stanza. - ... ``` - - `affinity` ((#v-server-affinity)) (`string`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + - `affinity` ((#v-server-affinity)) (`string`) - This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) for server pods. It defaults to allowing only a single server pod on each node, which minimizes risk of the cluster becoming unusable if a node is lost. If you need to run more pods per node (for example, testing on Minikube), set this value @@ -824,12 +812,14 @@ Use these links to navigate to a particular top-level stanza. ``` - `tolerations` ((#v-server-tolerations)) (`string: ""`) - Toleration settings for server pods. This - should be a multi-line string matching the Tolerations - (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + should be a multi-line string matching the + [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) + array in a Pod spec. - `topologySpreadConstraints` ((#v-server-topologyspreadconstraints)) (`string: ""`) - Pod topology spread constraints for server pods. - This should be a multi-line YAML string matching the `topologySpreadConstraints` array - (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + This should be a multi-line YAML string matching the + [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + array in a Pod Spec. This requires K8S >= 1.18 (beta) or 1.19 (stable). @@ -847,7 +837,7 @@ Use these links to navigate to a particular top-level stanza. component: server ``` - - `nodeSelector` ((#v-server-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + - `nodeSelector` ((#v-server-nodeselector)) (`string: null`) - This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) labels for server pod assignment, formatted as a multi-line string. Example: @@ -858,7 +848,7 @@ Use these links to navigate to a particular top-level stanza. ``` - `priorityClassName` ((#v-server-priorityclassname)) (`string: ""`) - This value references an existing - Kubernetes `priorityClassName` (https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) + Kubernetes [`priorityClassName`](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) that can be assigned to server pods. - `extraLabels` ((#v-server-extralabels)) (`map`) - Extra labels to attach to the server pods. This should be a YAML map. @@ -921,19 +911,19 @@ Use these links to navigate to a particular top-level stanza. feature, in case kubernetes cluster is behind egress http proxies. Additionally, it could be used to configure custom consul parameters. - - `snapshotAgent` ((#v-server-snapshotagent)) - Values for setting up and running snapshot agents - (https://consul.io/commands/snapshot/agent) + - `snapshotAgent` ((#v-server-snapshotagent)) - Values for setting up and running + [snapshot agents](/consul/commands/snapshot/agent) within the Consul clusters. They run as a sidecar with Consul servers. - `enabled` ((#v-server-snapshotagent-enabled)) (`boolean: false`) - If true, the chart will install resources necessary to run the snapshot agent. - `interval` ((#v-server-snapshotagent-interval)) (`string: 1h`) - Interval at which to perform snapshots. - See https://www.consul.io/commands/snapshot/agent#interval + Refer to [`interval`](/consul/commands/snapshot/agent#interval) - `configSecret` ((#v-server-snapshotagent-configsecret)) - A Kubernetes or Vault secret that should be manually created to contain the entire config to be used on the snapshot agent. This is the preferred method of configuration since there are usually storage - credentials present. Please see Snapshot agent config (https://consul.io/commands/snapshot/agent#config-file-options) + credentials present. Please refer to the [Snapshot agent config](/consul/commands/snapshot/agent#config-file-options) for details. - `secretName` ((#v-server-snapshotagent-configsecret-secretname)) (`string: null`) - The name of the Kubernetes secret or Vault secret path that holds the snapshot agent config. @@ -991,7 +981,7 @@ Use these links to navigate to a particular top-level stanza. - `k8sAuthMethodHost` ((#v-externalservers-k8sauthmethodhost)) (`string: null`) - If you are setting `global.acls.manageSystemACLs` and `connectInject.enabled` to true, set `k8sAuthMethodHost` to the address of the Kubernetes API server. This address must be reachable from the Consul servers. - Please see the Kubernetes Auth Method documentation (https://consul.io/docs/acl/auth-methods/kubernetes). + Please refer to the [Kubernetes Auth Method documentation](/consul/docs/security/acl/auth-methods/kubernetes). You could retrieve this value from your `kubeconfig` by running: @@ -1014,7 +1004,7 @@ Use these links to navigate to a particular top-level stanza. - `image` ((#v-client-image)) (`string: null`) - The name of the Docker image (including any tag) for the containers running Consul client agents. - - `join` ((#v-client-join)) (`array: null`) - A list of valid `-retry-join` values (https://www.consul.io/docs/agent/config/cli-flags#_retry_join). + - `join` ((#v-client-join)) (`array: null`) - A list of valid [`-retry-join` values](/consul/docs/agent/config/cli-flags#_retry_join). If this is `null` (default), then the clients will attempt to automatically join the server cluster running within Kubernetes. This means that with `server.enabled` set to true, clients will automatically @@ -1035,7 +1025,7 @@ Use these links to navigate to a particular top-level stanza. required for Connect. - `nodeMeta` ((#v-client-nodemeta)) - nodeMeta specifies an arbitrary metadata key/value pair to associate with the node - (see https://www.consul.io/docs/agent/config/cli-flags#_node_meta) + (refer to [`-node-meta`](/consul/docs/agent/config/cli-flags#_node_meta)) - `pod-name` ((#v-client-nodemeta-pod-name)) (`string: ${HOSTNAME}`) @@ -1079,7 +1069,7 @@ Use these links to navigate to a particular top-level stanza. - `tlsInit` ((#v-client-containersecuritycontext-tlsinit)) (`map`) - The tls-init initContainer - - `extraConfig` ((#v-client-extraconfig)) (`string: {}`) - A raw string of extra JSON configuration (https://consul.io/docs/agent/options) for Consul + - `extraConfig` ((#v-client-extraconfig)) (`string: {}`) - A raw string of extra [JSON configuration](/consul/docs/agent/config/config-files) for Consul clients. This will be saved as-is into a ConfigMap that is read by the Consul client agents. This can be used to add additional configuration that isn't directly exposed by the chart. @@ -1172,7 +1162,7 @@ Use these links to navigate to a particular top-level stanza. ``` - `priorityClassName` ((#v-client-priorityclassname)) (`string: ""`) - This value references an existing - Kubernetes `priorityClassName` (https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) + Kubernetes [`priorityClassName`](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) that can be assigned to client pods. - `annotations` ((#v-client-annotations)) (`string: null`) - This value defines additional annotations for @@ -1199,7 +1189,7 @@ Use these links to navigate to a particular top-level stanza. feature, in case kubernetes cluster is behind egress http proxies. Additionally, it could be used to configure custom consul parameters. - - `dnsPolicy` ((#v-client-dnspolicy)) (`string: null`) - This value defines the Pod DNS policy (https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) + - `dnsPolicy` ((#v-client-dnspolicy)) (`string: null`) - This value defines the [Pod DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) for client pods to use. - `hostNetwork` ((#v-client-hostnetwork)) (`boolean: false`) - hostNetwork defines whether or not we use host networking instead of hostPort in the event @@ -1209,7 +1199,8 @@ Use these links to navigate to a particular top-level stanza. combined with `dnsPolicy: ClusterFirstWithHostNet` - `updateStrategy` ((#v-client-updatestrategy)) (`string: null`) - updateStrategy for the DaemonSet. - See https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. + Refer to the Kubernetes [Daemonset upgrade strategy](https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy) + documentation. This should be a multi-line string mapping directly to the updateStrategy Example: @@ -1307,7 +1298,7 @@ Use these links to navigate to a particular top-level stanza. - `ingressClassName` ((#v-ui-ingress-ingressclassname)) (`string: ""`) - Optionally set the ingressClassName. - - `pathType` ((#v-ui-ingress-pathtype)) (`string: Prefix`) - pathType override - see: https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types + - `pathType` ((#v-ui-ingress-pathtype)) (`string: Prefix`) - pathType override - refer to: https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types - `hosts` ((#v-ui-ingress-hosts)) (`array`) - hosts is a list of host name to create Ingress rules. @@ -1343,16 +1334,17 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-ui-metrics-enabled)) (`boolean: global.metrics.enabled`) - Enable displaying metrics in the UI. The default value of "-" will inherit from `global.metrics.enabled` value. - - `provider` ((#v-ui-metrics-provider)) (`string: prometheus`) - Provider for metrics. See - https://www.consul.io/docs/agent/options#ui_config_metrics_provider + - `provider` ((#v-ui-metrics-provider)) (`string: prometheus`) - Provider for metrics. Refer to + [`metrics_provider`](/consul/docs/agent/config/config-files#ui_config_metrics_provider) This value is only used if `ui.enabled` is set to true. - `baseURL` ((#v-ui-metrics-baseurl)) (`string: http://prometheus-server`) - baseURL is the URL of the prometheus server, usually the service URL. This value is only used if `ui.enabled` is set to true. - - `dashboardURLTemplates` ((#v-ui-dashboardurltemplates)) - Corresponds to https://www.consul.io/docs/agent/options#ui_config_dashboard_url_templates configuration. + - `dashboardURLTemplates` ((#v-ui-dashboardurltemplates)) - Corresponds to [`dashboard_url_templates`](/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates) + configuration. - - `service` ((#v-ui-dashboardurltemplates-service)) (`string: ""`) - Sets https://www.consul.io/docs/agent/options#ui_config_dashboard_url_templates_service. + - `service` ((#v-ui-dashboardurltemplates-service)) (`string: ""`) - Sets [`dashboardURLTemplates.service`](/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates_service). ### syncCatalog ((#h-synccatalog)) @@ -1372,8 +1364,8 @@ Use these links to navigate to a particular top-level stanza. to run the sync program. - `default` ((#v-synccatalog-default)) (`boolean: true`) - If true, all valid services in K8S are - synced by default. If false, the service must be annotated - (https://consul.io/docs/k8s/service-sync#sync-enable-disable) properly to sync. + synced by default. If false, the service must be [annotated](/consul/docs/k8s/service-sync#enable-and-disable-sync) + properly to sync. In either case an annotation can override the default. - `priorityClassName` ((#v-synccatalog-priorityclassname)) (`string: ""`) - Optional priorityClassName. @@ -1486,7 +1478,7 @@ Use these links to navigate to a particular top-level stanza. - `secretKey` ((#v-synccatalog-aclsynctoken-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the acl sync token. - - `nodeSelector` ((#v-synccatalog-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + - `nodeSelector` ((#v-synccatalog-nodeselector)) (`string: null`) - This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) labels for catalog sync pod assignment, formatted as a multi-line string. Example: @@ -1552,7 +1544,7 @@ Use these links to navigate to a particular top-level stanza. - `default` ((#v-connectinject-default)) (`boolean: false`) - If true, the injector will inject the Connect sidecar into all pods by default. Otherwise, pods must specify the - injection annotation (https://consul.io/docs/k8s/connect#consul-hashicorp-com-connect-inject) + [injection annotation](/consul/docs/k8s/connect#consul-hashicorp-com-connect-inject) to opt-in to Connect injection. If this is true, pods can use the same annotation to explicitly opt-out of injection. @@ -1570,7 +1562,7 @@ Use these links to navigate to a particular top-level stanza. This value is also overridable via the "consul.hashicorp.com/transparent-proxy-overwrite-probes" annotation. Note: This value has no effect if transparent proxy is disabled on the pod. - - `disruptionBudget` ((#v-connectinject-disruptionbudget)) - This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + - `disruptionBudget` ((#v-connectinject-disruptionbudget)) - This configures the [`PodDisruptionBudget`](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for the service mesh sidecar injector. - `enabled` ((#v-connectinject-disruptionbudget-enabled)) (`boolean: true`) - This will enable/disable registering a PodDisruptionBudget for the @@ -1629,7 +1621,8 @@ Use these links to navigate to a particular top-level stanza. by the OpenShift platform. - `updateStrategy` ((#v-connectinject-cni-updatestrategy)) (`string: null`) - updateStrategy for the CNI installer DaemonSet. - See https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. + Refer to the Kubernetes [Daemonset upgrade strategy](https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy) + documentation. This should be a multi-line string mapping directly to the updateStrategy Example: @@ -1731,7 +1724,19 @@ Use these links to navigate to a particular top-level stanza. "sample/annotation2": "bar" ``` - - `resources` ((#v-connectinject-resources)) (`map`) - The resource settings for connect inject pods. + - `resources` ((#v-connectinject-resources)) (`map`) - The resource settings for connect inject pods. The defaults, are optimized for getting started worklows on developer deployments. The settings should be tweaked for production deployments. + + - `requests` ((#v-connectinject-resources-requests)) + + - `memory` ((#v-connectinject-resources-requests-memory)) (`string: 50Mi`) - Recommended production default: 500Mi + + - `cpu` ((#v-connectinject-resources-requests-cpu)) (`string: 50m`) - Recommended production default: 250m + + - `limits` ((#v-connectinject-resources-limits)) + + - `memory` ((#v-connectinject-resources-limits-memory)) (`string: 50Mi`) - Recommended production default: 500Mi + + - `cpu` ((#v-connectinject-resources-limits-cpu)) (`string: 50m`) - Recommended production default: 250m - `failurePolicy` ((#v-connectinject-failurepolicy)) (`string: Fail`) - Sets the failurePolicy for the mutating webhook. By default this will cause pods not part of the consul installation to fail scheduling while the webhook is offline. This prevents a pod from skipping mutation if the webhook were to be momentarily offline. @@ -1742,12 +1747,12 @@ Use these links to navigate to a particular top-level stanza. - `namespaceSelector` ((#v-connectinject-namespaceselector)) (`string`) - Selector for restricting the webhook to only specific namespaces. Use with `connectInject.default: true` to automatically inject all pods in namespaces that match the selector. This should be set to a multiline string. - See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector + Refer to https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector for more details. - By default, we exclude the kube-system namespace since usually users won't - want those pods injected and also the local-path-storage namespace so that - Kind (Kubernetes In Docker) can provision Pods used to create PVCs. + By default, we exclude kube-system since usually users won't + want those pods injected and local-path-storage and openebs so that + Kind (Kubernetes In Docker) and [OpenEBS](https://openebs.io/) respectively can provision Pods used to create PVCs. Note that this exclusion is only supported in Kubernetes v1.21.1+. Example: @@ -1829,8 +1834,8 @@ Use these links to navigate to a particular top-level stanza. If set to an empty string all service accounts can log in. This only has effect if ACLs are enabled. - See https://www.consul.io/docs/acl/acl-auth-methods.html#binding-rules - and https://www.consul.io/docs/acl/auth-methods/kubernetes.html#trusted-identity-attributes + Refer to Auth methods [Binding rules](/consul/docs/security/acl/auth-methods#binding-rules) + and [Trusted identiy attributes](/consul/docs/security/acl/auth-methods/kubernetes#trusted-identity-attributes) for more details. Requires Consul >= v1.5. @@ -1856,7 +1861,7 @@ Use these links to navigate to a particular top-level stanza. leads to unnecessary thread and memory usage and leaves unnecessary idle connections open. It is advised to keep this number low for sidecars and high for edge proxies. This will control the `--concurrency` flag to Envoy. - For additional information see also: https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 + For additional information, refer to https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 This setting can be overridden on a per-pod basis via this annotation: - `consul.hashicorp.com/consul-envoy-proxy-concurrency` @@ -1872,25 +1877,41 @@ Use these links to navigate to a particular top-level stanza. - `requests` ((#v-connectinject-sidecarproxy-resources-requests)) - - `memory` ((#v-connectinject-sidecarproxy-resources-requests-memory)) (`string: null`) - Recommended default: 100Mi + - `memory` ((#v-connectinject-sidecarproxy-resources-requests-memory)) (`string: null`) - Recommended production default: 100Mi - - `cpu` ((#v-connectinject-sidecarproxy-resources-requests-cpu)) (`string: null`) - Recommended default: 100m + - `cpu` ((#v-connectinject-sidecarproxy-resources-requests-cpu)) (`string: null`) - Recommended production default: 100m - `limits` ((#v-connectinject-sidecarproxy-resources-limits)) - - `memory` ((#v-connectinject-sidecarproxy-resources-limits-memory)) (`string: null`) - Recommended default: 100Mi + - `memory` ((#v-connectinject-sidecarproxy-resources-limits-memory)) (`string: null`) - Recommended production default: 100Mi + + - `cpu` ((#v-connectinject-sidecarproxy-resources-limits-cpu)) (`string: null`) - Recommended production default: 100m + + - `initContainer` ((#v-connectinject-initcontainer)) (`map`) - The resource settings for the Connect injected init container. If null, the resources + won't be set for the initContainer. The defaults are optimized for developer instances of + Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times. + + - `resources` ((#v-connectinject-initcontainer-resources)) - - `cpu` ((#v-connectinject-sidecarproxy-resources-limits-cpu)) (`string: null`) - Recommended default: 100m + - `requests` ((#v-connectinject-initcontainer-resources-requests)) - - `initContainer` ((#v-connectinject-initcontainer)) (`map`) - The resource settings for the Connect injected init container. + - `memory` ((#v-connectinject-initcontainer-resources-requests-memory)) (`string: 25Mi`) - Recommended production default: 150Mi + + - `cpu` ((#v-connectinject-initcontainer-resources-requests-cpu)) (`string: 50m`) - Recommended production default: 250m + + - `limits` ((#v-connectinject-initcontainer-resources-limits)) + + - `memory` ((#v-connectinject-initcontainer-resources-limits-memory)) (`string: 150Mi`) - Recommended production default: 150Mi + + - `cpu` ((#v-connectinject-initcontainer-resources-limits-cpu)) (`string: null`) - Recommended production default: 500m ### meshGateway ((#h-meshgateway)) -- `meshGateway` ((#v-meshgateway)) - [Mesh Gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. +- `meshGateway` ((#v-meshgateway)) - [Mesh Gateways](/consul/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. - - `enabled` ((#v-meshgateway-enabled)) (`boolean: false`) - If [mesh gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) are enabled, a Deployment will be created that runs + - `enabled` ((#v-meshgateway-enabled)) (`boolean: false`) - If [mesh gateways](/consul/docs/connect/gateways/mesh-gateway) are enabled, a Deployment will be created that runs gateways and Consul Connect will be configured to use gateways. - This setting is required for [Cluster Peering](https://developer.hashicorp.com/consul/docs/connect/cluster-peering/k8s). + This setting is required for [Cluster Peering](/consul/docs/connect/cluster-peering/k8s). Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. - `replicas` ((#v-meshgateway-replicas)) (`integer: 1`) - Number of replicas for the Deployment. @@ -1924,7 +1945,7 @@ Use these links to navigate to a particular top-level stanza. - `port` ((#v-meshgateway-wanaddress-port)) (`integer: 443`) - Port that gets registered for WAN traffic. If source is set to "Service" then this setting will have no effect. - See the documentation for source as to which port will be used in that + Refer to the documentation for source as to which port will be used in that case. - `static` ((#v-meshgateway-wanaddress-static)) (`string: ""`) - If source is set to "Static" then this value will be used as the WAN @@ -1989,7 +2010,7 @@ Use these links to navigate to a particular top-level stanza. - `initServiceInitContainer` ((#v-meshgateway-initserviceinitcontainer)) (`map`) - The resource settings for the `service-init` init container. - - `affinity` ((#v-meshgateway-affinity)) (`string: null`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + - `affinity` ((#v-meshgateway-affinity)) (`string: null`) - This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) for mesh gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value to the value in the example below. @@ -2011,8 +2032,9 @@ Use these links to navigate to a particular top-level stanza. - `tolerations` ((#v-meshgateway-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. - `topologySpreadConstraints` ((#v-meshgateway-topologyspreadconstraints)) (`string: ""`) - Pod topology spread constraints for mesh gateway pods. - This should be a multi-line YAML string matching the `topologySpreadConstraints` array - (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + This should be a multi-line YAML string matching the + [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + array in a Pod Spec. This requires K8S >= 1.18 (beta) or 1.19 (stable). @@ -2053,8 +2075,7 @@ Use these links to navigate to a particular top-level stanza. for a specific gateway. Requirements: consul >= 1.8.0 - - `enabled` ((#v-ingressgateways-enabled)) (`boolean: false`) - Enable ingress gateway deployment. Requires `connectInject.enabled=true` - and `client.enabled=true`. + - `enabled` ((#v-ingressgateways-enabled)) (`boolean: false`) - Enable ingress gateway deployment. Requires `connectInject.enabled=true`. - `defaults` ((#v-ingressgateways-defaults)) - Defaults sets default values for all gateway fields. With the exception of annotations, defining any of these values in the `gateways` list @@ -2102,7 +2123,7 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-ingressgateways-defaults-resources)) (`map`) - Resource limits for all ingress gateway pods - - `affinity` ((#v-ingressgateways-defaults-affinity)) (`string: null`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + - `affinity` ((#v-ingressgateways-defaults-affinity)) (`string: null`) - This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) for ingress gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value to the value in the example below. @@ -2124,8 +2145,9 @@ Use these links to navigate to a particular top-level stanza. - `tolerations` ((#v-ingressgateways-defaults-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. - `topologySpreadConstraints` ((#v-ingressgateways-defaults-topologyspreadconstraints)) (`string: ""`) - Pod topology spread constraints for ingress gateway pods. - This should be a multi-line YAML string matching the `topologySpreadConstraints` array - (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + This should be a multi-line YAML string matching the + [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + array in a Pod Spec. This requires K8S >= 1.18 (beta) or 1.19 (stable). @@ -2182,8 +2204,7 @@ Use these links to navigate to a particular top-level stanza. for a specific gateway. Requirements: consul >= 1.8.0 - - `enabled` ((#v-terminatinggateways-enabled)) (`boolean: false`) - Enable terminating gateway deployment. Requires `connectInject.enabled=true` - and `client.enabled=true`. + - `enabled` ((#v-terminatinggateways-enabled)) (`boolean: false`) - Enable terminating gateway deployment. Requires `connectInject.enabled=true`. - `defaults` ((#v-terminatinggateways-defaults)) - Defaults sets default values for all gateway fields. With the exception of annotations, defining any of these values in the `gateways` list @@ -2208,7 +2229,7 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-terminatinggateways-defaults-resources)) (`map`) - Resource limits for all terminating gateway pods - - `affinity` ((#v-terminatinggateways-defaults-affinity)) (`string: null`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + - `affinity` ((#v-terminatinggateways-defaults-affinity)) (`string: null`) - This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) for terminating gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value to the value in the example below. @@ -2230,8 +2251,9 @@ Use these links to navigate to a particular top-level stanza. - `tolerations` ((#v-terminatinggateways-defaults-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. - `topologySpreadConstraints` ((#v-terminatinggateways-defaults-topologyspreadconstraints)) (`string: ""`) - Pod topology spread constraints for terminating gateway pods. - This should be a multi-line YAML string matching the `topologySpreadConstraints` array - (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + This should be a multi-line YAML string matching the + [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + array in a Pod Spec. This requires K8S >= 1.18 (beta) or 1.19 (stable). @@ -2306,7 +2328,7 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-apigateway-managedgatewayclass-enabled)) (`boolean: true`) - When true a GatewayClass is configured to automatically work with Consul as installed by helm. - - `nodeSelector` ((#v-apigateway-managedgatewayclass-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + - `nodeSelector` ((#v-apigateway-managedgatewayclass-nodeselector)) (`string: null`) - This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) labels for gateway pod assignment, formatted as a multi-line string. Example: @@ -2370,10 +2392,10 @@ Use these links to navigate to a particular top-level stanza. ``` - `priorityClassName` ((#v-apigateway-controller-priorityclassname)) (`string: ""`) - This value references an existing - Kubernetes `priorityClassName` (https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) + Kubernetes [`priorityClassName`](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) that can be assigned to api-gateway-controller pods. - - `nodeSelector` ((#v-apigateway-controller-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + - `nodeSelector` ((#v-apigateway-controller-nodeselector)) (`string: null`) - This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) labels for api-gateway-controller pod assignment, formatted as a multi-line string. Example: @@ -2384,7 +2406,7 @@ Use these links to navigate to a particular top-level stanza. ``` - `tolerations` ((#v-apigateway-controller-tolerations)) (`string: null`) - This value defines the tolerations for api-gateway-controller pod, this should be a multi-line string matching the - Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. - `service` ((#v-apigateway-controller-service)) - Configuration for the Service created for the api-gateway-controller @@ -2408,7 +2430,7 @@ Use these links to navigate to a particular top-level stanza. This should be a multi-line string matching the Toleration array in a PodSpec. - - `nodeSelector` ((#v-webhookcertmanager-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + - `nodeSelector` ((#v-webhookcertmanager-nodeselector)) (`string: null`) - This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) labels for the webhook-cert-manager pod assignment, formatted as a multi-line string. Example: diff --git a/website/content/docs/k8s/index.mdx b/website/content/docs/k8s/index.mdx index f4d0ffe4c32..e1a8291abe3 100644 --- a/website/content/docs/k8s/index.mdx +++ b/website/content/docs/k8s/index.mdx @@ -59,7 +59,7 @@ There are several ways to try Consul with Kubernetes in different environments. - The [Consul and Kubernetes Deployment](/consul/tutorials/kubernetes/kubernetes-deployment-guide?utm_source=docs) tutorial covers the necessary steps to install and configure a new Consul cluster on Kubernetes in production. -- The [Secure Consul and Registered Services on Kubernetes](https://consul.io/consul/tutorials/kubernetes/kubernetes-secure-agents?utm_source=docs) tutorial covers +- The [Secure Consul and Registered Services on Kubernetes](/consul/tutorials/kubernetes/kubernetes-secure-agents?utm_source=docs) tutorial covers the necessary steps to secure a Consul cluster running on Kubernetes in production. - The [Layer 7 Observability with Consul Service Mesh](/consul/tutorials/kubernetes/kubernetes-layer7-observability) tutorial covers monitoring a diff --git a/website/content/docs/k8s/installation/install.mdx b/website/content/docs/k8s/installation/install.mdx index 2872718166c..1d80696ba03 100644 --- a/website/content/docs/k8s/installation/install.mdx +++ b/website/content/docs/k8s/installation/install.mdx @@ -85,7 +85,7 @@ or read the [Helm Chart Reference](/consul/docs/k8s/helm). ### Minimal `values.yaml` for Consul service mesh -The following `values.yaml` config file contains the minimum required settings to enable [Consul Service Mesh](https://consul.io/(/docs/k8s/connect)): +The following `values.yaml` config file contains the minimum required settings to enable [Consul Service Mesh](/consul/docs/k8s/connect): diff --git a/website/content/docs/k8s/k8s-cli.mdx b/website/content/docs/k8s/k8s-cli.mdx index 402d2327b7d..bb45986e11e 100644 --- a/website/content/docs/k8s/k8s-cli.mdx +++ b/website/content/docs/k8s/k8s-cli.mdx @@ -32,7 +32,9 @@ You can use the following commands with `consul-k8s`. - [`proxy`](#proxy): Inspect Envoy proxies managed by Consul. - [`proxy list`](#proxy-list): List all Pods running proxies managed by Consul. - [`proxy read`](#proxy-read): Inspect the Envoy configuration for a given Pod. + - [`proxy log`](#proxy-log): Inspect and modify the Envoy logging configuration for a given Pod. - [`status`](#status): Check the status of a Consul installation on Kubernetes. + - [`troubleshoot`](#troubleshoot): Troubleshoot Consul service mesh and networking issues from a given pod. - [`uninstall`](#uninstall): Uninstall Consul deployment. - [`upgrade`](#upgrade): Upgrade Consul on Kubernetes from an existing installation. - [`version`](#version): Print the version of the Consul on Kubernetes CLI. @@ -100,6 +102,7 @@ Consul in your Kubernetes Cluster. - [`proxy list`](#proxy-list): List all Pods running proxies managed by Consul. - [`proxy read`](#proxy-read): Inspect the Envoy configuration for a given Pod. +- [`proxy log`](#proxy-log): Inspect and modify the Envoy logging configuration for a given Pod. ### `proxy list` @@ -447,6 +450,290 @@ $ consul-k8s proxy read backend-658b679b45-d5xlb -o raw } ``` +### `proxy log` + +The `proxy log` command allows you to inspect and modify the logging configuration of Envoy proxies running on a given Pod. + +```shell-session +$ consul-k8s proxy log +``` + +The command takes a required value, ``. This should be the full name +of a Kubernetes Pod. If a Pod is running more than one Envoy proxy managed by +Consul, as in the [Multiport configuration](/consul/docs/k8s/connect#kubernetes-pods-with-multiple-ports), +the terminal displays configuration information for all proxies in the pod. + +The following options are available. + +| Flag | Description | Default | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| `-namespace`, `-n` | `String` Specifies the namespace containing the target Pod. | Current [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) namespace. | +| `-update-level`, `-u` | `String` Specifies the logger (optional) and the level to update.

    Use the following format to configure the same level for loggers: `-update-level `.

    You can also specify a comma-delineated list to configure levels for specific loggers, for example: `-update-level grpc:warning,http:info`.

    | none | +| `-reset`, `-r` | `String` Reset the log levels for all loggers back to the default of `info` | `info` | + +#### Example commands +In the following example, Consul returns the log levels for all of an Envoy proxy's loggers in a pod with the ID `server-697458b9f8-4vr29`: + +```shell-session +$ consul-k8s proxy log server-697458b9f8-4vr29 +Envoy log configuration for server-697458b9f8-4vr29 in namespace default: + +==> Log Levels for server-697458b9f8-4vr29 +Name Level +rds info +backtrace info +hc info +http info +io info +jwt info +rocketmq info +matcher info +runtime info +redis info +stats info +tap info +alternate_protocols_cache info +grpc info +init info +quic info +thrift info +wasm info +aws info +conn_handler info +ext_proc info +hystrix info +tracing info +dns info +oauth2 info +connection info +health_checker info +kafka info +mongo info +config info +admin info +forward_proxy info +misc info +websocket info +dubbo info +happy_eyeballs info +main info +client info +lua info +udp info +cache_filter info +filter info +multi_connection info +quic_stream info +router info +http2 info +key_value_store info +secret info +testing info +upstream info +assert info +ext_authz info +rbac info +decompression info +envoy_bug info +file info +pool info +``` + +The following command updates the log levels for all loggers of an Envoy proxy to `warning`. +```shell-session +$ consul-k8s proxy log server-697458b9f8-4vr29 -update-level warning +Envoy log configuration for server-697458b9f8-4vr29 in namespace default: + +==> Log Levels for server-697458b9f8-4vr29 +Name Level +pool warning +rbac warning +tracing warning +aws warning +cache_filter warning +decompression warning +init warning +assert warning +client warning +misc warning +udp warning +config warning +hystrix warning +key_value_store warning +runtime warning +admin warning +dns warning +jwt warning +redis warning +quic warning +alternate_protocols_cache warning +conn_handler warning +ext_proc warning +http warning +oauth2 warning +ext_authz warning +http2 warning +kafka warning +mongo warning +router warning +thrift warning +grpc warning +matcher warning +hc warning +multi_connection warning +wasm warning +dubbo warning +filter warning +upstream warning +backtrace warning +connection warning +io warning +main warning +happy_eyeballs warning +rds warning +tap warning +envoy_bug warning +rocketmq warning +file warning +forward_proxy warning +stats warning +health_checker warning +lua warning +secret warning +quic_stream warning +testing warning +websocket warning +``` +The following command updates the `grpc` log level to `error`, the `http` log level to `critical`, and the `runtime` log level to `debug` for pod ID `server-697458b9f8-4vr29` +```shell-session +$ consul-k8s proxy log server-697458b9f8-4vr29 -update-level grpc:error,http:critical,runtime:debug +Envoy log configuration for server-697458b9f8-4vr29 in namespace default: + +==> Log Levels for server-697458b9f8-4vr29 +Name Level +assert info +dns info +http critical +pool info +thrift info +udp info +grpc error +hc info +stats info +wasm info +alternate_protocols_cache info +ext_authz info +filter info +http2 info +key_value_store info +tracing info +cache_filter info +quic_stream info +aws info +io info +matcher info +rbac info +tap info +connection info +conn_handler info +rocketmq info +hystrix info +oauth2 info +redis info +backtrace info +file info +forward_proxy info +kafka info +config info +router info +runtime debug +testing info +happy_eyeballs info +ext_proc info +init info +lua info +health_checker info +misc info +envoy_bug info +jwt info +main info +quic info +upstream info +websocket info +client info +decompression info +mongo info +multi_connection info +rds info +secret info +admin info +dubbo info +``` +The following command resets the log levels for all loggers of an Envoy proxy in pod `server-697458b9f8-4vr29` to the default level of `info`. +```shell-session +$ consul-k8s proxy log server-697458b9f8-4vr29 -r +Envoy log configuration for server-697458b9f8-4vr29 in namespace default: + +==> Log Levels for server-697458b9f8-4vr29 +Name Level +ext_proc info +secret info +thrift info +tracing info +dns info +rocketmq info +happy_eyeballs info +hc info +io info +misc info +conn_handler info +key_value_store info +rbac info +hystrix info +wasm info +admin info +cache_filter info +client info +health_checker info +oauth2 info +runtime info +testing info +grpc info +upstream info +forward_proxy info +matcher info +pool info +aws info +decompression info +jwt info +tap info +assert info +redis info +http info +quic info +rds info +connection info +envoy_bug info +stats info +alternate_protocols_cache info +backtrace info +filter info +http2 info +init info +multi_connection info +quic_stream info +dubbo info +ext_authz info +main info +udp info +websocket info +config info +mongo info +router info +file info +kafka info +lua info +``` ### `status` The `status` command provides an overall status summary of the Consul on Kubernetes installation. It also provides the configuration that was used to deploy Consul K8s and information about the health of Consul servers and clients. This command does not take in any flags. @@ -490,6 +777,106 @@ $ consul-k8s status ✓ Consul clients healthy (3/3) ``` +### `troubleshoot` + +The `troubleshoot` command exposes two subcommands for troubleshooting Consul +service mesh and network issues from a given pod. + +- [`troubleshoot upstreams`](#troubleshoot-upstreams): List all Envoy upstreams in Consul service mesh from the given pod. +- [`troubleshoot proxy`](#troubleshoot-proxy): Troubleshoot Consul service mesh configuration and network issues between the given pod and the given upstream. + +### `troubleshoot upstreams` + +```shell-session +$ consul-k8s troubleshoot upstreams -pod +``` + +| Flag | Description | Default | +| ------------------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `-namespace`, `-n` | `String` The Kubernetes namespace to list proxies in. | Current [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) namespace. | +| `-envoy-admin-endpoint` | `String` Envoy sidecar address and port | `127.0.0.1:19000` | + +#### Example Commands + +The following example displays all transparent proxy upstreams in Consul service mesh from the given pod. + + ```shell-session + $ consul-k8s troubleshoot upstreams -pod frontend-767ccfc8f9-6f6gx + + ==> Upstreams (explicit upstreams only) (0) + + ==> Upstreams IPs (transparent proxy only) (1) + [10.4.6.160 240.0.0.3] true map[backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul] + + If you cannot find the upstream address or cluster for a transparent proxy upstream: + - Check intentions: Tproxy upstreams are configured based on intentions. Make sure you have configured intentions to allow traffic to your upstream. + - To check that the right cluster is being dialed, run a DNS lookup for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing from the upstream IPs, it means that your proxy may be misconfigured. + ``` + +The following example displays all explicit upstreams from the given pod in the Consul service mesh. + + ```shell-session + $ consul-k8s troubleshoot upstreams -pod client-767ccfc8f9-6f6gx + + ==> Upstreams (explicit upstreams only) (1) + server + counting + + ==> Upstreams IPs (transparent proxy only) (0) + + If you cannot find the upstream address or cluster for a transparent proxy upstream: + - Check intentions: Tproxy upstreams are configured based on intentions. Make sure you have configured intentions to allow traffic to your upstream. + - To check that the right cluster is being dialed, run a DNS lookup for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing from the upstream IPs, it means that your proxy may be misconfigured. + ``` + +### `troubleshoot proxy` + +```shell-session +$ consul-k8s troubleshoot proxy -pod -upstream-ip +$ consul-k8s troubleshoot proxy -pod -upstream-envoy-id +``` + +| Flag | Description | Default | +| ------------------------------------ | ----------------------------------------------------------| ---------------------------------------------------------------------------------------------------------------------- | +| `-namespace`, `-n` | `String` The Kubernetes namespace to list proxies in. | Current [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) namespace. | +| `-envoy-admin-endpoint` | `String` Envoy sidecar address and port | `127.0.0.1:19000` | +| `-upstream-ip` | `String` The IP address of the upstream transparent proxy | | +| `-upstream-envoy-id` | `String` The Envoy identifier of the upstream | | + +#### Example Commands + +The following example troubleshoots the Consul service mesh configuration and network issues between the given pod and the given upstream IP. + + ```shell-session + $ consul-k8s troubleshoot proxy -pod frontend-767ccfc8f9-6f6gx -upstream-ip 10.4.6.160 + + ==> Validation + ✓ certificates are valid + ✓ Envoy has 0 rejected configurations + ✓ Envoy has detected 0 connection failure(s) + ✓ listener for upstream "backend" found + ✓ route for upstream "backend" found + ✓ cluster "backend.default.dc1.internal..consul" for upstream "backend" found + ✓ healthy endpoints for cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ cluster "backend2.default.dc1.internal..consul" for upstream "backend" found + ! no healthy endpoints for cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ``` + +The following example troubleshoots the Consul service mesh configuration and network issues between the given pod and the given upstream. + + ```shell-session + $ consul-k8s troubleshoot proxy -pod frontend-767ccfc8f9-6f6gx -upstream-envoy-id db + + ==> Validation + ✓ certificates are valid + ✓ Envoy has 0 rejected configurations + ✓ Envoy has detected 0 connection failure(s) + ! no listener for upstream "db" found + ! no route for upstream "backend" found + ! no cluster "db.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "db" found + ! no healthy endpoints for cluster "db.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "db" found + ``` + ### `uninstall` The `uninstall` command removes Consul from Kubernetes. diff --git a/website/content/docs/k8s/service-sync.mdx b/website/content/docs/k8s/service-sync.mdx index 0cd6cb7c382..db3e2bc9d83 100644 --- a/website/content/docs/k8s/service-sync.mdx +++ b/website/content/docs/k8s/service-sync.mdx @@ -20,7 +20,7 @@ automatically installed and configured using the Consul catalog enable Kubernetes services to be accessed by any node that is part of the Consul cluster, including other distinct Kubernetes clusters. For non-Kubernetes nodes, they can access services using the standard -[Consul DNS](/consul/docs/discovery/dns) or HTTP API. +[Consul DNS](/consul/docs/services/discovery/dns-overview) or HTTP API. **Why sync Consul services to Kubernetes?** Syncing Consul services to Kubernetes services enables non-Kubernetes services to be accessed using kube-dns and Kubernetes-specific diff --git a/website/content/docs/k8s/upgrade/index.mdx b/website/content/docs/k8s/upgrade/index.mdx index ae12c8e3d9f..246197a7eed 100644 --- a/website/content/docs/k8s/upgrade/index.mdx +++ b/website/content/docs/k8s/upgrade/index.mdx @@ -38,9 +38,9 @@ For example, if you installed Consul with `connectInject.enabled: false` and you ``` **Before performing the upgrade, be sure you read the other sections on this page, - continuing at [Determining What Will Change](#determining-what-will-change).** + continuing at [Determine scope of changes](#determine-scope-of-changes).** -~> Note: You should always set the `--version` flag when upgrading Helm. Otherwise Helm uses the most up-to-date version in its local cache, which may result in an unintended upgrade. +~> Note: You should always set the `--version` flag when upgrading Helm. Otherwise, Helm uses the most up-to-date version in its local cache, which may result in an unintended upgrade. ### Upgrade Helm chart version @@ -87,7 +87,7 @@ If you want to upgrade to the latest `0.40.0` version, use the following procedu ``` **Before performing the upgrade, be sure you've read the other sections on this page, - continuing at [Determining What Will Change](#determining-what-will-change).** + continuing at [Determine scope of changes](#determine-scope-of-changes).** ### Upgrade Consul version @@ -126,9 +126,9 @@ to update to the new version. Before you upgrade to a new version: ``` **Before performing the upgrade, be sure you have read the other sections on this page, - continuing at [Determining What Will Change](#determining-what-will-change).** + continuing at [Determine scope of changes](#determine-scope-of-changes).** -~> Note: You should always set the `--version` flag when upgrading Helm. Otherwise Helm uses the most up-to-date version in its local cache, which may result in an unintended upgrade. +~> Note: You should always set the `--version` flag when upgrading Helm. Otherwise, Helm uses the most up-to-date version in its local cache, which may result in an unintended upgrade. ## Determine scope of changes @@ -175,7 +175,7 @@ that can be used. Initiate the server upgrade: 1. Change the `global.image` value to the desired Consul version. -1. Set the `server.updatePartition` value to the number of server replicas. By default there are 3 servers, so you would set this value to `3`. +1. Set the `server.updatePartition` value to the number of server replicas. By default, there are 3 servers, so you would set this value to `3`. 1. Set the `updateStrategy` for clients to `OnDelete`. @@ -228,7 +228,7 @@ If you upgrade Consul from a version that uses client agents to a version the us type: OnDelete ``` -1. Add `consul.hashicorp.com/consul-k8s-version: 1.0.0` to the annotations for each pod you upgrade. +1. Add `consul.hashicorp.com/consul-k8s-version: 1.0.0` to the annotations for each pod you upgrade. 1. Follow our [recommended procedures to upgrade servers](#upgrading-consul-servers) on Kubernetes deployments to upgrade Helm values for the new version of Consul. @@ -236,7 +236,7 @@ If you upgrade Consul from a version that uses client agents to a version the us 1. Restart all gateways in your service mesh. -1. Disable client agents in your Helm chart by deleting the `client` stanza or setting `client.enabled` to `false`. +1. Disable client agents in your Helm chart by deleting the `client` stanza or setting `client.enabled` to `false`. ## Configuring TLS on an existing cluster diff --git a/website/content/docs/lambda/invoke-from-lambda.mdx b/website/content/docs/lambda/invoke-from-lambda.mdx index a7ec2913865..4c29e4b8d9c 100644 --- a/website/content/docs/lambda/invoke-from-lambda.mdx +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -264,7 +264,7 @@ Define the following environment variables in your Lambda functions to configure ## Invoke the Lambda function -If _intentions_ are enabled in the Consul service mesh, you must create an intention that allows the Lambda function's Consul service to invoke all upstream services prior to invoking the Lambda function. Refer to [Service Mesh Intentions](/consul/docs/connect/intentions) for additional information. +If _intentions_ are enabled in the Consul service mesh, you must create an intention that allows the Lambda function's Consul service to invoke all upstream services prior to invoking the Lambda function. Refer to [Service mesh intentions](/consul/docs/connect/intentions) for additional information. There are several ways to invoke Lambda functions. In the following example, the `aws lambda invoke` CLI command invokes the function: diff --git a/website/content/docs/nia/configuration.mdx b/website/content/docs/nia/configuration.mdx index 4d4e8113f8e..bd67ef5c07e 100644 --- a/website/content/docs/nia/configuration.mdx +++ b/website/content/docs/nia/configuration.mdx @@ -197,7 +197,7 @@ Service registration requires that the [Consul token](/consul/docs/nia/configura | `default_check.enabled` | Optional | boolean | Enables CTS to create the default health check. | `true` | | `default_check.address` | Optional | string | The address to use for the default HTTP health check. Needs to include the scheme (`http`/`https`) and the port, if applicable. | `http://localhost:` or `https://localhost:`. Determined from the [port configuration](/consul/docs/nia/configuration#port) and whether [TLS is enabled](/consul/docs/nia/configuration#enabled-2) on the CTS API. | -The default health check is an [HTTP check](/consul/docs/discovery/checks#http-interval) that calls the [Health API](/consul/docs/nia/api/health). The following table describes the values CTS sets for this default check, corresponding to the [Consul register check API](/consul/api-docs/agent/check#register-check). If an option is not listed in this table, then CTS is using the default value. +The default health check is an [HTTP check](/consul/docs/services/usage/checks#http-checks) that calls the [Health API](/consul/docs/nia/api/health). The following table describes the values CTS sets for this default check, corresponding to the [Consul register check API](/consul/api-docs/agent/check#register-check). If an option is not listed in this table, then CTS is using the default value. | Parameter | Value | | --------- | ----- | diff --git a/website/content/docs/release-notes/consul-api-gateway/v0_4_x.mdx b/website/content/docs/release-notes/consul-api-gateway/v0_4_x.mdx index a6e82167d30..71fa38c8965 100644 --- a/website/content/docs/release-notes/consul-api-gateway/v0_4_x.mdx +++ b/website/content/docs/release-notes/consul-api-gateway/v0_4_x.mdx @@ -31,7 +31,7 @@ description: >- to rewrite the URL path in a client's HTTP request before sending the request to a service. For example, you could configure the gateway to change the path from `//store/checkout` to `//cart/checkout`. Refer to the [usage - documentation](/consul/docs/api-gateway/usage) for additional information. + documentation](/consul/docs/connect/gateways/api-gateway/usage) for additional information. ## What has Changed diff --git a/website/content/docs/release-notes/consul-k8s/v0_47_x.mdx b/website/content/docs/release-notes/consul-k8s/v0_47_x.mdx index 3d266b13350..8f185bb6f37 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_47_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_47_x.mdx @@ -9,7 +9,7 @@ description: >- ## Release Highlights -- **Cluster Peering (Beta)**: This release introduces support for Cluster Peering, which allows service connectivity between two independent clusters. Enabling peering will deploy the peering controllers and PeeringAcceptor and PeeringDialer CRDs. The new CRDs are used to establish a peering connection between two clusters. Refer to [Cluster Peering on Kubernetes](/consul/docs/connect/cluster-peering/k8s) for full instructions on using Cluster Peering on Kubernetes. +- **Cluster Peering (Beta)**: This release introduces support for cluster peering, which allows service connectivity between two independent clusters. When you enable cluster peering, Consul deploys the peering controllers and `PeeringAcceptor` and `PeeringDialer` CRDs. The new CRDs are used to establish a peering connection between two clusters. Refer to [Cluster Peering Overview](/consul/docs/connect/cluster-peering) for full instructions on using Cluster Peering on Kubernetes. - **Envoy Proxy Debugging CLI Commands**: This release introduces new commands to quickly identify proxies and troubleshoot Envoy proxies for sidecars and gateways. * Add `consul-k8s proxy list` command for displaying pods running Envoy managed by Consul. diff --git a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx index 57a66e5c3e5..cb03589e1cf 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -44,3 +44,7 @@ The changelogs for this major release version and any maintenance versions are l ~> **Note:** The following link takes you to the changelogs on the GitHub website. - [0.49.0](https://github.com/hashicorp/consul-k8s/releases/tag/v0.49.0) +- [0.49.1](https://github.com/hashicorp/consul-k8s/releases/tag/v0.49.1) +- [0.49.2](https://github.com/hashicorp/consul-k8s/releases/tag/v0.49.2) +- [0.49.3](https://github.com/hashicorp/consul-k8s/releases/tag/v0.49.3) +- [0.49.4](https://github.com/hashicorp/consul-k8s/releases/tag/v0.49.4) diff --git a/website/content/docs/release-notes/consul-k8s/v1_0_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_0_x.mdx index f34e2397c27..b9236898dd5 100644 --- a/website/content/docs/release-notes/consul-k8s/v1_0_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v1_0_x.mdx @@ -61,3 +61,7 @@ The changelogs for this major release version and any maintenance versions are l ~> **Note:** The following link takes you to the changelogs on the GitHub website. - [1.0.0](https://github.com/hashicorp/consul-k8s/releases/tag/v1.0.0) +- [1.0.1](https://github.com/hashicorp/consul-k8s/releases/tag/v1.0.1) +- [1.0.2](https://github.com/hashicorp/consul-k8s/releases/tag/v1.0.2) +- [1.0.3](https://github.com/hashicorp/consul-k8s/releases/tag/v1.0.3) +- [1.0.4](https://github.com/hashicorp/consul-k8s/releases/tag/v1.0.4) diff --git a/website/content/docs/release-notes/consul-k8s/v1_1_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_1_x.mdx new file mode 100644 index 00000000000..5c3b8a5619c --- /dev/null +++ b/website/content/docs/release-notes/consul-k8s/v1_1_x.mdx @@ -0,0 +1,61 @@ +--- +layout: docs +page_title: 1.1.x +description: >- + Consul on Kubernetes release notes for version 1.1.x +--- + +# Consul on Kubernetes 1.1.0 + +## Release Highlights + +- **Enhanced Envoy Access Logging:** Envoy access logs are now centrally managed via the `accessLogs` field within the ProxyDefaults CRD to allow operators to easily turn on access logs for all proxies within the service mesh. Refer to [Access logs overview](/consul/docs/connect/observability/access-logs) for more information. + +- **Consul Envoy Extensions:** The new Envoy extension system enables you to modify Consul-generated Envoy resources outside of the Consul binary. This will allow extensions to add, delete, and modify Envoy listeners, routes, clusters, and endpoints, enabling support for additional Envoy features without changes to the Consul codebase. +The new `envoyExtensions` field in the ProxyDefaults and ServiceDefaults CRDs enable built-in Envoy extensions. Refer to [Envoy extensions overview](/consul/docs/connect/proxies/envoy-extensions) for more information on how to use these extensions. + +- **Envoy Proxy Debugging CLI Commands**: This release adds a new command to quickly modify the log level of Envoy proxies for sidecars and gateways for easier debugging. +Refer to [consul-k8s CLI proxy log command](/consul/docs/k8s/k8s-cli#proxy-log) docs for more information. + * Add `consul-k8s proxy log podname` command for displaying current log levels or updating log levels for Envoy in a given pod. + + +## What's Changed + +- Connect inject now excludes the `openebs` namespace from sidecar injection by default. If you previously had pods in that namespace +that you wanted to be injected, you must now set namespaceSelector as follows: + + ```yaml + connectInject: + namespaceSelector: | + matchExpressions: + - key: "kubernetes.io/metadata.name" + operator: "NotIn" + values: ["kube-system","local-path-storage"] + ``` + +## Supported Software + +~> **Note:** Consul 1.14.x and 1.13.x are not supported. Please refer to [Supported Consul and Kubernetes versions](/consul/docs/k8s/compatibility#supported-consul-and-kubernetes-versions) for more detail on choosing the correct `consul-k8s` version. +- Consul 1.15.x. +- Consul Dataplane v1.1.x. Refer to [Envoy and Consul Dataplane](/consul/docs/connect/proxies/envoy#envoy-and-consul-dataplane) for details about Consul Dataplane versions and the available packaged Envoy version. +- Kubernetes 1.23.x - 1.26.x +- `kubectl` 1.23.x - 1.26.x +- Helm 3.6+ + +## Upgrading + +For detailed information on upgrading, please refer to the [Upgrades page](/consul/docs/k8s/upgrade) + +## Known Issues + +The following issues are known to exist in the v1.1.0 release: + +- Pod Security Standards that are configured for the [Pod Security Admission controller](https://kubernetes.io/blog/2022/08/25/pod-security-admission-stable/) are currently not supported by Consul K8s. OpenShift 4.11.x enables Pod Security Standards on Kubernetes 1.25 [by default](https://connect.redhat.com/en/blog/important-openshift-changes-pod-security-standards) and is also not supported. Support will be added in a future Consul K8s 1.0.x patch release. + +## Changelogs + +The changelogs for this major release version and any maintenance versions are listed below. + +~> **Note:** The following link takes you to the changelogs on the GitHub website. + +- [1.1.0](https://github.com/hashicorp/consul-k8s/releases/tag/v1.1.0) diff --git a/website/content/docs/release-notes/consul/v1_12_x.mdx b/website/content/docs/release-notes/consul/v1_12_x.mdx index c1d0d4724d3..ebdaaed98b0 100644 --- a/website/content/docs/release-notes/consul/v1_12_x.mdx +++ b/website/content/docs/release-notes/consul/v1_12_x.mdx @@ -13,7 +13,7 @@ description: >- - **Per listener TLS Config**: It is now possible to configure TLS differently for each of Consul's listeners, such as HTTPS, gRPC, and the internal multiplexed RPC listener, using the `tls` stanza. Refer to [TLS Configuration Reference](/consul/docs/agent/config/config-files#tls-configuration-reference) for more details. -- **AWS Lambda**: Adds the ability to invoke AWS Lambdas through terminating gateways, which allows for cross-datacenter communication, transparent proxy, and intentions with Consul Service Mesh. Refer to [AWS Lambda](/consul/docs]/lambda) and [Invoke Lambda Functions](/consul/docs/lambda/invocation) for more details. +- **AWS Lambda**: Adds the ability to invoke AWS Lambdas through terminating gateways, which allows for cross-datacenter communication, transparent proxy, and intentions with Consul Service Mesh. Refer to [AWS Lambda](/consul/docs/lambda) and [Invoke Lambda Functions](/consul/docs/lambda/invocation) for more details. - **Mesh-wide TLS min/max versions and cipher suites**: Using the [Mesh](/consul/docs/connect/config-entries/mesh#tls) Config Entry or CRD, it is now possible to set TLS min/max versions and cipher suites for both inbound and outbound mTLS connections. diff --git a/website/content/docs/release-notes/consul/v1_13_x.mdx b/website/content/docs/release-notes/consul/v1_13_x.mdx index 736e84f9db5..dd3f7cfe309 100644 --- a/website/content/docs/release-notes/consul/v1_13_x.mdx +++ b/website/content/docs/release-notes/consul/v1_13_x.mdx @@ -15,7 +15,7 @@ description: >- - **Enables TLS on the Envoy Prometheus endpoint**: The Envoy prometheus endpoint can be enabled when `envoy_prometheus_bind_addr` is set and then secured over TLS using new CLI flags for the `consul connect envoy` command. These commands are: `-prometheus-ca-file`, `-prometheus-ca-path`, `-prometheus-cert-file` and `-prometheus-key-file`. The CA, cert, and key can be provided to Envoy by a Kubernetes mounted volume so that Envoy can watch the files and dynamically reload the certs when the volume is updated. -- **UDP Health Checks**: Adds the ability to register service discovery health checks that periodically send UDP datagrams to the specified IP/hostname and port. Refer to [UDP checks](/consul/docs/discovery/checks#udp-interval). +- **UDP Health Checks**: Adds the ability to register service discovery health checks that periodically send UDP datagrams to the specified IP/hostname and port. Refer to [UDP checks](/consul/docs//services/usage/checks#udp-checks). ## What's Changed diff --git a/website/content/docs/release-notes/consul/v1_15_x.mdx b/website/content/docs/release-notes/consul/v1_15_x.mdx new file mode 100644 index 00000000000..99c2961bd4a --- /dev/null +++ b/website/content/docs/release-notes/consul/v1_15_x.mdx @@ -0,0 +1,102 @@ +--- +layout: docs +page_title: 1.15.x +description: >- + Consul release notes for version 1.15.x +--- + +# Consul 1.15.0 + +## Release Highlights + +- **Enhanced Envoy Access Logging:** Envoy access logs are now centrally managed via config entries and CRDs to allow operators to easily turn on access logs for all proxies within the service mesh. Refer to [Access logs overview](/consul/docs/connect/observability/access-logs) for more information. Additionally, the [Proxy default configuration entry](/consul/docs/connect/config-entries/proxy-defaults) shows you how to enable access logs centrally via the ProxyDefaults config entry or CRD. + +- **Consul Envoy Extensions:** The new Envoy extension system enables you to modify Consul-generated Envoy resources outside of the Consul binary. This will allow extensions to add, delete, and modify Envoy listeners, routes, clusters, and endpoints, enabling support for additional Envoy features without changes to the Consul codebase. +Current supported extensions include the [Lua](/consul/docs/connect/proxies/envoy-extensions#lua) and [AWS Lambda](/consul/docs/connect/proxies/envoy-extensions#lambda) extensions. Refer to [Envoy extensions overview](/consul/docs/connect/proxies/envoy-extensions) for more information on how to use these extensions for Consul service mesh. + +- **API Gateway support on Linux VM runtimes:** You can now deploy Consul API Gateway on Linux VM runtimes. API Gateway is built into Consul and, when deploying on Linux VM runtimes, is not separately installed software. Refer to [API gateway overview](/consul/docs/connect/gateways/api-gateway) for more information on API Gateway specifically for VM. + + ~> **Note:** Support for API Gateway on Linux VM runtimes is considered a "Beta" feature in Consul v1.15.0. HashiCorp expects to change it to a GA feature as part of a v1.15 patch release in the near future. + +- **Limit traffic rates to Consul servers:** You can now configure global RPC rate limits to mitigate the risks to Consul servers when clients send excessive read or write requests to Consul resources. Refer to [Limit traffic rates overview](/consul/docs/agent/limits) for more details on how to use the new troubleshooting commands. + +- **Service-to-service troubleshooting:** Consul includes a built-in tool for troubleshooting communication between services in a service mesh. The `consul troubleshoot` command enables you to validate communication between upstream and downstream Envoy proxies on VM and Kubernetes deployments. Refer to [Service-to-service troubleshooting overview](/consul/docs/troubleshoot/troubleshoot-services) for more details on how to use the new troubleshooting commands. +Refer to [Service-to-service troubleshooting overview](/consul/docs/troubleshoot/troubleshoot-services) for more details on how to use the new troubleshooting commands. + +- **Raft write-ahead log (Experimental):** Consul provides an experimental storage backend called write-ahead log (WAL). WAL implements a traditional log with rotating, append-only log files which resolves a number of performance issues with the current BoltDB storage backend. Refer to [Experimental WAL LogStore backend overview](/consul/docs/agent/wal-logstore) for more details. + + ~> **Note:** The new Raft write-ahead log storage backend is not recommended for production use cases yet, but is ready for testing by the general community. + +## What's Changed + +- ACL errors have now been ehanced to return descriptive errors when the specified resource cannot be found. Other ACL request errors provide more information about when a resource is missing. In addition, errors are now gracefully thrown when interacting with the ACL system before the ACL system been bootstrapped. + - The Delete Token/Policy/AuthMethod/Role/BindingRule endpoints now return 404 when the resource cannot be found. The new error format is as follows: + + ```log hideClipboard + Requested * does not exist: ACL not found", "* not found in namespace $NAMESPACE: ACL not found` + ``` + + - The Read Token/Policy/Role endpoints now return 404 when the resource cannot be found. The new error format is as follows: + + ```log hideClipboard + Cannot find * to delete + ``` + + - The Logout endpoint now returns a 401 error when the supplied token cannot be found. The new error format is as follows: + + ```log hideClipboard + Supplied token does not exist + ``` + + - The Token Self endpoint now returns 404 when the token cannot be found. The new error format is as follows: + + ```log hideClipboard + Supplied token does not exist + ``` + +- Consul v1.15.0 formally removes all uses of legacy ACLs and ACL policies from Consul. The legacy ACL system was deprecated in Consul v1.4.0 and removed in Consul v1.11.0. The documentation for the new ACL system can be found [here](/consul/docs/v1.14.x/security/acl). For information on how to migrate to the new ACL System, please read the [Migrate Legacy ACL Tokens tutorial](/consul/tutorials/security-operations/access-control-token-migration). +- The following agent flags are now deprecated: `-join`, `-join-wan`, `start_join`, and `start_join_wan`. These options are now aliases of `-retry-join`, `-retry-join-wan`, `retry_join`, and `retry_join_wan`, respectively. +- A `peer` field has been added to ServiceDefaults upstream overrides to make it possible to apply upstream overrides only to peer services. Prior to this change, overrides would be applied based on matching the namespace and name fields only, which means users could not have different configuration for local versus peer services. With this change, peer upstreams are only affected if the peer field matches the destination peer name. +- If you run the `consul connect envoy` command with an incompatible Envoy version, Consul will now error and exit. To ignore this check, use flag `--ignore-envoy-compatibility`. +- Ingress Gateway upstream clusters will have empty `outlier_detection` if passive health check is unspecified. + +## Upgrading + +For more detailed information, please refer to the [upgrade details page](/consul/docs/upgrading/upgrade-specific#consul-1-15-0) and the changelogs. + +## Known Issues + +The following issues are known to exist in the v1.15.x releases: + +- All current 1.15.x versions are under investigation for a not-consistently-reproducible + issue that can cause some service instances to lose their ability to communicate in the mesh after + [72 hours (LeafCertTTL)](/consul/docs/connect/ca/consul#leafcertttl) + due to a problem with leaf certificate rotation. + We will update this section with more information as our investigation continues, + including the target availability for a fix. + Refer to [GH-16779](https://github.com/hashicorp/consul/issues/16779) + for the latest information. + +- For v1.15.0, Consul is reporting newer releases of Envoy (for example, v1.25.1) as not supported, even though these versions are listed as valid in the [Envoy compatilibity matrix](/consul/docs/connect/proxies/envoy#envoy-and-consul-client-agent). The following error would result for newer versions of Envoy: + + ```log hideClipboard + Envoy version 1.25.1 is not supported. If there is a reason you need to use this version of envoy use the ignore-envoy-compatibility flag. Using an unsupported version of Envoy is not recommended and your experience may vary. + ``` + + The workaround to resolve this issue until Consul v1.15.1 would be to run the client agents with the new `ingore-envoy-compatiblity` flag: + + ```shell-session + $ consul connect envoy --ignore-envoy-compatibility + ``` + +- For v1.15.0, there is a known issue where `consul acl token read -self` requires an `-accessor-id`. This is resolved in the uppcoming Consul v1.15.1 patch release. + +- For v1.15.0, there is a known issue where search filters produced errors and resulted in lists not showing full results until being interacted with. This is resolved in the upcoming Consul v1.15.1 patch release. + +## Changelogs + +The changelogs for this major release version and any maintenance versions are listed below. + +~> **Note:** These links take you to the changelogs on the GitHub website. + +- [1.15.0](https://github.com/hashicorp/consul/releases/tag/v1.15.0) diff --git a/website/content/docs/security/acl/acl-rules.mdx b/website/content/docs/security/acl/acl-rules.mdx index e9cd5c8b128..a09ffd2fc25 100644 --- a/website/content/docs/security/acl/acl-rules.mdx +++ b/website/content/docs/security/acl/acl-rules.mdx @@ -586,8 +586,7 @@ These actions may required an ACL token to complete. Use the following methods t This allows a single token to be used during all check registration operations. * Provide an ACL token with `service` and `check` definitions at registration time. This allows for greater flexibility and enables the use of multiple tokens on the same agent. - Refer to the [services](/consul/docs/discovery/services) and [checks](/consul/docs/discovery/checks) documentation for examples. - Tokens may also be passed to the [HTTP API](/consul/api-docs) for operations that require them. + Refer to the [services](/consul/docs/services/usage/define-services) and [checks](/consul/docs/services/usage/checks) documentation for examples. You can also pass tokens to the [HTTP API](/consul/api-docs) for operations that require them. ### Reading Imported Nodes @@ -815,12 +814,12 @@ to use for registration events: 2. Providing an ACL token with service and check definitions at registration time. This allows for greater flexibility and enables the use of multiple tokens on the same agent. Examples of what this looks like are available for - both [services](/consul/docs/discovery/services) and - [checks](/consul/docs/discovery/checks). Tokens may also be passed to the [HTTP - API](/consul/api-docs) for operations that require them. **Note:** all tokens + both [services](/consul/docs/services/usage/define-services) and + [checks](/consul/docs/services/usage/checks). Tokens may also be passed to the [HTTP + API](/consul/api-docs) for operations that require them. Note that all tokens passed to an agent are persisted on local disk to allow recovery from - restarts. See [`-data-dir` flag - documentation](/consul/docs/agent/config/cli-flags#_data_dir) for notes on securing + restarts. Refer to [`-data-dir` flag + documentation](/consul/docs/agent/config/cli-flags#_data_dir) for information about securing access. In addition to ACLs, in Consul 0.9.0 and later, the agent must be configured with @@ -870,7 +869,7 @@ service "app" { -Refer to [Intention Management Permissions](/consul/docs/connect/intentions#intention-management-permissions) +Refer to [ACL requirements for intentions](/consul/docs/connect/intentions/create-manage-intentions#acl-requirements) for more information about managing intentions access with service rules. ## Session Rules diff --git a/website/content/docs/security/acl/acl-tokens.mdx b/website/content/docs/security/acl/acl-tokens.mdx index da14d52d88a..068d8efa50e 100644 --- a/website/content/docs/security/acl/acl-tokens.mdx +++ b/website/content/docs/security/acl/acl-tokens.mdx @@ -66,7 +66,7 @@ service = { -Refer to the [service definitions documentation](/consul/docs/discovery/services#service-definition) for additional information. +Refer to [Services Configuration Reference](/consul/docs/services/configuration/services-configuration-reference) for additional information. ### Agent Requests diff --git a/website/content/docs/security/acl/auth-methods/kubernetes.mdx b/website/content/docs/security/acl/auth-methods/kubernetes.mdx index b0a432884a0..13f76481c98 100644 --- a/website/content/docs/security/acl/auth-methods/kubernetes.mdx +++ b/website/content/docs/security/acl/auth-methods/kubernetes.mdx @@ -76,14 +76,13 @@ The Kubernetes service account corresponding to the configured [`ServiceAccountJWT`](/consul/docs/security/acl/auth-methods/kubernetes#serviceaccountjwt) needs to have access to two Kubernetes APIs: -- [**TokenReview**](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#create-tokenreview-v1-authentication-k8s-io) +- [**TokenReview**](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/) -> Kubernetes should be running with `--service-account-lookup`. This is defaulted to true in Kubernetes 1.7, but any versions prior should ensure the Kubernetes API server is started with this setting. -- [**ServiceAccount**](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#read-serviceaccount-v1-core) - (`get`) +- [**ServiceAccount**](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#service-account-tokens) The following is an example [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) diff --git a/website/content/docs/security/acl/auth-methods/oidc.mdx b/website/content/docs/security/acl/auth-methods/oidc.mdx index ea62c510083..7d22f01df7d 100644 --- a/website/content/docs/security/acl/auth-methods/oidc.mdx +++ b/website/content/docs/security/acl/auth-methods/oidc.mdx @@ -73,7 +73,7 @@ parameters are required to properly configure an auth method of type - `JWTSupportedAlgs` `(array)` - JWTSupportedAlgs is a list of supported signing algorithms. Defaults to `RS256`. ([Available - algorithms](https://github.com/hashicorp/consul/blob/main/vendor/github.com/coreos/go-oidc/jose.go#L7)) + algorithms](https://github.com/hashicorp/consul/blob/main/internal/go-sso/oidcauth/jwt.go)) - `BoundAudiences` `(array)` - List of `aud` claims that are valid for login; any match is sufficient. diff --git a/website/content/docs/services/configuration/checks-configuration-reference.mdx b/website/content/docs/services/configuration/checks-configuration-reference.mdx new file mode 100644 index 00000000000..fee071de51b --- /dev/null +++ b/website/content/docs/services/configuration/checks-configuration-reference.mdx @@ -0,0 +1,55 @@ +--- +layout: docs +page_title: Health check configuration reference +description: -> + Use the health checks to direct safety functions, such as removing failing nodes and replacing secondary services. Learn how to configure health checks. +--- + +# Health check configuration reference + +This topic provides configuration reference information for health checks. For information about the different kinds of health checks and guidance on defining them, refer to [Define Health Checks]. + +## Introduction +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. Refer to [Define Health Checks](/consul/docs/services/usage/checks) for information about how to define the differnet types of health checks available in Consul. + +## Check block +Specify health check options in the `check` block. To register two or more heath checks in the same configuration, use the [`checks` block](#checks-block). The following table describes the configuration options contained in the `check` block. + +| Parameter | Description | Check types | +| --- | --- | --- | +| `name` | Required string value that specifies the name of the check. Default is `service:`. If multiple service checks are registered, the autogenerated default is appended with colon and incrementing number starting with `1`. |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `id` | A unique string value that specifies an ID for the check. Default to the `name` value. If `name` values conflict, specify a unique ID to avoid overwriting existing checks with same ID on the same node. Consul auto-generates an ID if the check is defined in a service definition file. |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `notes` | String value that provides a human-readabiole description of the check. The contents are not visible to Consul. |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `interval` | Required string value that specifies how frequently to run the check. The `interval` parameter is required for supported check types. The value is parsed by the golang [time package formatting specification](https://golang.org/pkg/time/#ParseDuration). |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • Docker
  • gRPC
  • H2ping
  • | +| `timeout` | String value that specifies how long unsuccessful requests take to end with a timeout. The `timeout` is optional for the supported check types and has the following defaults:
  • Script: `30s`
  • HTTP: `10s`
  • TCP: `10s`
  • UDP: `10s`
  • gRPC: `10s`
  • H2ping: `10s`
  • |
  • Script
  • HTTP
  • TCP
  • UDP
  • gRPC
  • H2ping
  • | +| `status` | Optional string value that specifies the initial status of the health check. You can specify the following values:
  • `critical` (default)
  • `warning`
  • `passing`
  • |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `deregister_critical_service_after` | String value that 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. The value is parsed by the golang [time package formatting specification](https://golang.org/pkg/time/#ParseDuration) |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `success_before_passing` | Integer value that specifies how many consecutive times the check must pass before Consul marks the service or node as `passing`. Default is `0`. |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `failures_before_warning` | Integer value that specifies how many consecutive times the check must fail before Consul marks the service or node as `warning`. The value cannot be more than `failures_before_critical`. Defaults to the value specified for `failures_before_critical`. |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `failures_before_critical` | Integer value that specifies how many consecutive times the check must fail before Consul marks the service or node as `critical`. Default is `0`. |
  • Script
  • HTTP
  • TCP
  • UDP
  • OSService
  • TTL
  • Docker
  • gRPC
  • H2ping
  • Alias
  • | +| `args` | Specifies a list of arguments strings to pass to the command line. The list of values includes the path to a script file or external application to invoke and any additional parameters for running the script or application. |
  • Script
  • Docker
  • | +| `docker_container_id` | Specifies the Docker container ID in which to run an external health check application. Specify the external application with the `args` parameter. |
  • Docker
  • | +| `shell` | String value that specifies the type of command line shell to use for running the health check application. Specify the external application with the `args` parameter. |
  • Docker
  • | +| `grpc` | String value that 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). |
  • gRPC
  • | +| `grpc_use_tls` | Boolean value that enables TLS for gRPC checks when set to `true`. |
  • gRPC
  • | +| `h2ping` | String value that specifies the HTTP2 endpoint, including port number, to send HTTP2 requests to. |
  • H2ping
  • | +| `h2ping_use_tls` | Boolean value that enables TLS for H2ping checks when set to `true`. |
  • H2ping
  • | +| `http` | String value that specifies an HTTP endpoint to send requests to. |
  • HTTP
  • | +| `tls_server_name` | String value that specifies the name of the TLS server that issues certificates. Defaults to the SNI determined by the address specified in the `http` field. Set the `tls_skip_verify` to `false` to disable this field. |
  • HTTP
  • | +| `tls_skip_verify` | Boolean value that disbles TLS for HTTP checks when set to `true`. Default is `false`. |
  • HTTP
  • | +| `method` | String value that specifies the request method to send during HTTP checks. Default is `GET`. |
  • HTTP
  • | +| `header` | Object that specifies header fields to send in HTTP check requests. Each header specified in `header` object contains a list of string values. |
  • HTTP
  • | +| `body` | String value that contains JSON attributes to send in HTTP check requests. You must escap the quotation marks around the keys and values for each attribute. |
  • HTTP
  • | +| `disable_redirects` | Boolean value that prevents HTTP checks from following redirects if set to `true`. Default is `false`. |
  • HTTP
  • | +| `os_service` | String value that specifies the name of the name of a service to check during an OSService check. |
  • OSService
  • | +| `service_id` | String value that specifies the ID of a service instance to associate with an OSService check. That service instance must be on the same node as the check. If not specified, the check verifies the health of the node. |
  • OSService
  • | +| `tcp` | String value that specifies an IP address or host and port number for the check establish a TCP connection with. |
  • TCP
  • | +| `udp` | String value that specifies an IP address or host and port number for the check to send UDP datagrams to. |
  • UDP
  • | +| `ttl` | String value that specifies how long to wait for an update from an external process during a TTL check. |
  • TTL
  • | +| `alias_service` | String value that specifies a service or node that the service associated with the health check aliases. |
  • Alias
  • | + + + +## Checks block +You can define multiple health checks in a single `checks` block. The `checks` block is an array of objects that contain the configuration options described in the [`check` block configuration reference](#check-block). + diff --git a/website/content/docs/services/configuration/services-configuration-overview.mdx b/website/content/docs/services/configuration/services-configuration-overview.mdx new file mode 100644 index 00000000000..3c01f05ae7c --- /dev/null +++ b/website/content/docs/services/configuration/services-configuration-overview.mdx @@ -0,0 +1,29 @@ +--- +layout: docs +page_title: Services configuration overview +description: -> + This topic provides introduces the configuration items that enable you to register services with Consul so that they can connect to other services and nodes registered with Consul. +--- + +# Services configuration overview + +This topic provides introduces the configuration items that enable you to register services with Consul so that they can connect to other services and nodes registered with Consul. + +## Service definitions +A service definition contains a set of parameters that specify various aspects of the service, including how it is discovered by other services in the network. The service definition may also contain health check configurations. Refer to [Health Check Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for configuration details about health checks. + +Configure individual services and health checks by specifying parameters in the `service` block of a service definition file. Refer to [Define Services](/consul/docs/services/usage/define-services) for information about defining services. + +To register a service, provide the service definition to the Consul agent. Refer to [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) for information about registering services. + +Consul supports service definitions written in JSON and HCL. + +## Service defaults +Use the `service-defaults` configuration entry to define the default parameters for service definitions. This enables you to configure common settings, such as namespace or partition for Consul Enterprise deployments, in a single definition. + +You can use `service-defaults` configuration entries on virtual machines and in Kubernetes environments. + +## ACLs +Services registered in Consul clusters where both Consul Namespaces and the ACL system are enabled can be registered to specific namespaces that are associated with ACL tokens scoped to the namespace. Services registered with a service definition will not inherit the namespace associated with the ACL token specified in the token field. The namespace and the token parameters must be included in the service definition for the service to be registered to the namespace that the ACL token is scoped to. + + diff --git a/website/content/docs/services/configuration/services-configuration-reference.mdx b/website/content/docs/services/configuration/services-configuration-reference.mdx new file mode 100644 index 00000000000..95f01e16ff7 --- /dev/null +++ b/website/content/docs/services/configuration/services-configuration-reference.mdx @@ -0,0 +1,655 @@ +--- +layout: docs +page_title: Service configuration reference +description: Use the service definition to configure and register services to the Consul catalog, including services used as proxies in a Consul service mesh +--- + +# Services configuration reference + +This topic describes the options you can use to define services for registering them with Consul. Refer to the following topics for usage information: + +- [Define Services](/consul/docs/services/usage/define-services) +- [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) + +## Configuration model +The following outline shows how to format the configurations in the root `service` block. Click on a property name to view details about the configuration. + +- [`name`](#name): string | required +- [`id`](#id): string | optional +- [`address`](#address): string | optional +- [`port`](#port): integer | optional +- [`tags`](#tags): list of strings | optional +- [`meta`](#meta): object | optional + - [_`custom_meta_key`_](#meta): string | optional +- [`tagged_addresses`](#tagged_addresses): object | optional + - [`lan`](#tagged_addresses-lan): object | optional + - [`address`](#tagged_addresses-lan): string | optional + - [`port`](#tagged_addresses-lan): integer | optional + - [`wan`](#tagged_addresses-wan): object | optional + - [`address`](#tagged_addresses-wan): string | optional + - [`port`](#tagged_addresses-wan): integer | optional +- [`socket_path`](#socket_path): string | optional +- [`enable_tag_override`](#enable_tag_override): boolean | optional +- [`checks`](#checks) : list of objects | optional +- [`kind`](#kind): string | optional +- [`proxy`](#proxy): object | optional +- [`connect`](#connect): object | optional + - [`native`](#connect): boolean | optional + - [`sidecar_service`](#connect): object | optional +- [`weights`](#weights): object | optional + - [`passing`](#weights): integer | optional + - [`warning`](#weights): integer | optional +- [`token`](#token): string | required if ACLs are enabled +- [`namespace`](#namespace): string | optional | + +## Specification +This topic provides details about the configuration parameters. + +### name +Required value that specifies a name for the service. We recommend using valid DNS labels for service definition names for compatibility with external DNSs. The value for this parameter is used as the ID if the `id` parameter is not specified. + +- Type: string +- Default: none + +### 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. + +- Type: string +- Default: Value of the `name` field. + +### address +String value that 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. + +- Type: string +- Default: IP address of the agent node + +### 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`](#tagged_addresses) parameter. + +- Type: integer +- Default: Port number of the agent + +### 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`: + +```hcl +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 the [`enable_tag_override`](#enable_tag_override) setting, 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. + +### meta +The `meta` field contains 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. + +In the following example, the `env` key is set to `prod`: + + + +```hcl +meta = { + env = "prod" +} +``` + +```json +"meta" : { + "env" : "prod" +} +``` + + + +### tagged_addresses +The `tagged_address` field is an object that 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 the [`address`](#address) field. You can configure multiple addresses for a node or service. The following tags are supported: + +- [`lan`](#tagged_addresses-lan): IPv4 LAN address where the node or service is accessible. +- [`lan_ipv4`](#tagged_addresses-lan): IPv4 LAN address where the node or service is accessible. +- [`lan_ipv6`](#tagged_addresses-lan): IPv6 LAN address where the node or service is accessible. +- [`virtual`](#tagged_addresses-virtual): A fixed address for the instances of a given logical service. +- [`wan`](#tagged_addresses-wan): IPv4 WAN address where the node or service is accessible when dialed from a remote data center. +- [`wan_ipv4`](#tagged_addresses-wan): IPv4 WAN address where the node or service is accessible when dialed from a remote data center. +- [`wan_ipv6`](#tagged_addresses-lan): IPv6 WAN address at which the node or service is accessible when being dialed from a remote data center. + +### tagged_addresses.lan +Object that specifies either an IPv4 or IPv6 LAN address and port number where the service or node is accessible. You can specify one or more of the following fields: + +- `lan` +- `lan_ipv4` +- `lan_ipv6` + +The field contains the following parameters: + +- `address` +- `port` + +In the following example, the `redis` service has an IPv4 LAN address of `192.0.2.10:80` and IPv6 LAN address of `[2001:db8:1:2:cafe::1337]:80`: + + + +```hcl +service { + name = "redis" + address = "192.0.2.10" + port = 80 + tagged_addresses { + lan = { + address = "192.0.2.10" + port = 80 + } + lan_ipv4 = { + address = "192.0.2.10" + port = 80 + } + lan_ipv6 = { + address = "2001:db8:1:2:cafe::1337" + port = 80 + } + } +} +``` + +```json +{ + "service": { + "name": "redis", + "address": "192.0.2.10", + "port": 80, + "tagged_addresses": { + "lan": { + "address": "192.0.2.10", + "port": 80 + }, + "lan_ipv4": { + "address": "192.0.2.10", + "port": 80 + }, + "lan_ipv6": { + "address": "2001:db8:1:2:cafe::1337", + "port": 80 + } + } + } +} +``` + + + +### tagged_addresses.virtual +Object that specifies a fixed IP address and port number that downstream services in a service mesh can use to connect to the service. The `virtual` field contains the following parameters: + +- `address` +- `port` + +Virtual addresses are not required to be routable IPs within the network. They are strictly a control plane construct used to provide a fixed address for the instances of a logical service. Egress connections from the proxy to an upstream service go to the IP address of an individual service instance and not the virtual address of the logical service. + +If the following conditions are met, connections to virtual addresses are load balanced across available instances of a service, provided the following conditions are satisfied: + +1. [Transparent proxy](/consul/docs/connect/transparent-proxy) is enabled for the downstream and upstream services. +1. The upstream service is not configured for individual instances to be [dialed directly](/consul/docs/connect/config-entries/service-defaults#dialeddirectly). + +In the following example, the downstream services in the mesh can connect to the `redis` service at `203.0.113.50` on port `80`: + + + +```hcl +service { + name = "redis" + address = "192.0.2.10" + port = 80 + tagged_addresses { + virtual = { + address = "203.0.113.50" + port = 80 + } + } +} +``` + +```json +{ + "service": { + "name": "redis", + "address": "192.0.2.10", + "port": 80, + "tagged_addresses": { + "virtual": { + "address": "203.0.113.50", + "port": 80 + } + } + } +} +``` + + + +### tagged_addresses.wan +Object that specifies either an IPv4 or IPv6 WAN address and port number where the service or node is accessible from a remote datacenter. You can specify one or more of the following fields: + +- `wan` +- `wan_ipv4` +- `wan_ipv6` + +The field contains the following parameters: + +- `address` +- `port` + +In the following example, services or nodes in remote datacenters can reach the `redis` service at `198.51.100.200:80` and `[2001:db8:5:6:1337::1eaf]:80`: + + + +```hcl +service { + name = "redis" + address = "192.0.2.10" + port = 80 + tagged_addresses { + wan = { + address = "198.51.100.200" + port = 80 + } + wan_ipv4 = { + address = "198.51.100.200" + port = 80 + } + wan_ipv6 = { + address = "2001:db8:5:6:1337::1eaf" + port = 80 + } + } +} +``` + +```json +{ + "service": { + "name": "redis", + "address": "192.0.2.10", + "port": 80, + "tagged_addresses": { + "wan": { + "address": "198.51.100.200", + "port": 80 + }, + "wan_ipv4": { + "address": "198.51.100.200", + "port": 80 + }, + "wan_ipv6": { + "address": "2001:db8:5:6:1337::1eaf", + "port": 80 + } + } + } +} +``` + + + +### socket_path +String value that 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. + +- Type: string +- Default: none + +### enable_tag_override +Boolean value that determines if the anti-entropy feature for the service is enabled. +Set to `true` to allow external Consul agents 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. If 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 usage information. + +- Type: boolean +- Default: `false` + +### checks +The `checks` block contains an array of objects that define health checks for the service. 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. Refer to [Health Check Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for information about configuring health checks. + +### kind +String value that identifies the service as a proxy and determines its role in the service mesh. Do not configure the `kind` parameter for non-proxy service instances. Refer to [Consul Service Mesh](/consul/docs/connect) for additional information. + +You can specify the following values: + +- `connect-proxy`: Defines the configuration for a service mesh proxy. Refer to [Register a Service Mesh Proxy Outside of a Service Registration](/consul/docs/connect/registration/service-registration) for details about registering a service as a service mesh proxy. +- `ingress-gateway`: Defines the configuration for an [ingress gateway](/consul/docs/connect/config-entries/ingress-gateway) +- `mesh-gateway`: Defines the configuration for a [mesh gateway](/consul/docs/connect/gateways/mesh-gateway) +- `terminating-gateway`: Defines the configuration for a [terminating gateway](/consul/docs/connect/gateways/terminating-gateway) + +For non-service registration roles, the `kind` field has a different context when used to define configuration entries, such as `service-defaults`. Refer to the documentation for the configuration entry you want to implement for additional information. + +### proxy +Object that specifies proxy configurations when the service is configured to operate as a proxy in a service mesh. Do not configure the `proxy` parameter for non-proxy service instances. Refer to [Register a Service Mesh Proxy Outside of a Service Registration](/consul/docs/connect/registration/service-registration) for details about registering your service as a service mesh proxy. Refer to [`kind`](#kind) for information about the types of proxies you can define. Services that you assign proxy roles to are registered as services in the catalog. + +### connect +Object that configures a Consul service mesh connection. You should only configure the `connect` block of parameters if you are using Consul service mesh. Refer to [Consul Service Mesh](/consul/docs/connect) for additional information. + +The following table describes the parameters that you can place in the `connect` block: + +| Parameter | Description | Default | +| --- | --- | --- | +| `native` | Boolean value that advertises the service as a native service mesh proxy. Use this parameter to integrate your application with the `connect` API. Refer to [Service Mesh Native App Integration Overview](/consul/docs/connect/native) for additional information. If set to `true`, do not configure a `sidecar_service`. | `false` | +| `sidecar_service` | Object that defines a sidecar proxy for the service. Do not configure if `native` is set to `true`. Refer to [Register a Service Mesh Proxy in a Service Registration](/consul/docs/connect/registration/sidecar-service) for usage and configuration details. | Refer to [Register a Service Mesh Proxy in a Service Registration](/consul/docs/connect/registration/sidecar-service) for default configurations. | + +### weights +Object that configures how the service responds to DNS SRV requests based on the service's health status. Configuring allows service instances with more capacity to respond to DNS SRV requests. It also reduces the load on services with checks in `warning` status by giving passing instances a higher weight. + +You can specify one or more of the following states and configure an integer value indicating its weight: + +- `passing` +- `warning` +- `critical` + +Larger integer values increase the weight state. Services have the following default weights: + +- `"passing" : 1` +- `"warning" : 1` + +Services in a `critical` state are excluded from DNS responses by default. Services with `warning` checks are included in responses by default. Refer to [Perform Static DNS Queries](/consul/docs/services/discovery/dns-static-lookups) for additional information. + +In the following example, service instances in a `passing` state respond to DNS SRV requests, while instances in a `critical` instance can still respond at a lower frequency: + + + +```hcl +service { + ## ... + weights = { + passing = 3 + warning = 2 + critical = 1 + } + ## ... +} +``` + +```json +"service": { + ## ... + "weights": { + "passing": 3, + "warning": 2, + "critical": 1 + }, + ## ... +} +``` + + + +### token +String value that specifies the ACL token to present when registering the service if ACLs are enabled. The token is required for the service to interact with the service catalog. + +If [ACLs](/consul/docs/security/acl) and [namespaces](/consul/docs/enterprise/namespaces) are enabled, you can register services scoped to the specific [`namespace`](#namespace) associated with the ACL token in a Consul cluster. + +Services registered with a service definition do not inherit the namespace associated with the ACL token specified in the token field. The `namespace` and `token` parameters must be included in the service definition for the service to be registered to the namespace that the ACL token is scoped to. + +- Type: string +- Default: none + +### namespace +String value that specifies the namespace in which to register the service. Refer [Namespaces](/consul/docs/enterprise/namespaces) for additional information. + +- Type: string +- Default: none + +## Multiple service definitions + +You can define multiple services in a single definition file in the `servcies` block. This enables you register multiple services in a single command. Note that the HTTP API does not support the `services` block. + + + +```hcl +services { + id = "red0" + name = "redis" + tags = [ + "primary" + ] + address = "" + port = 6000 + checks = [ + { + args = ["/bin/check_redis", "-p", "6000"] + interval = "5s" + timeout = "20s" + } + ] +} +services { + id = "red1" + name = "redis" + tags = [ + "delayed", + "secondary" + ] + address = "" + port = 7000 + checks = [ + { + args = ["/bin/check_redis", "-p", "7000"] + interval = "30s" + timeout = "60s" + } + ] +} +``` + +```json +{ + "services": [ + { + "id": "red0", + "name": "redis", + "tags": [ + "primary" + ], + "address": "", + "port": 6000, + "checks": [ + { + "args": ["/bin/check_redis", "-p", "6000"], + "interval": "5s", + "timeout": "20s" + } + ] + }, + { + "id": "red1", + "name": "redis", + "tags": [ + "delayed", + "secondary" + ], + "address": "", + "port": 7000, + "checks": [ + { + "args": ["/bin/check_redis", "-p", "7000"], + "interval": "30s", + "timeout": "60s" + } + ] + } + ] +} +``` + + + +## Example definition +The following example includes all possible parameters, but only the top-level `service` parameter and its `name` parameter are required by default. + + + +```hcl +service { + name = "redis" + id = "redis" + port = 80 + tags = ["primary"] + + meta = { + custom_meta_key = "custom_meta_value" + } + + tagged_addresses = { + lan = { + address = "192.168.0.55" + port = 8000 + } + + wan = { + address = "198.18.0.23" + port = 80 + } + } + + port = 8000 + socket_path = "/tmp/redis.sock" + enable_tag_override = false + + checks = [ + { + args = ["/usr/local/bin/check_redis.py"] + interval = "10s" + } + ] + + kind = "connect-proxy" + proxy_destination = "redis" + + proxy = { + destination_service_name = "redis" + destination_service_id = "redis1" + local_service_address = "127.0.0.1" + local_service_port = 9090 + local_service_socket_path = "/tmp/redis.sock" + mode = "transparent" + + transparent_proxy { + outbound_listener_port = 22500 + } + + mesh_gateway = { + mode = "local" + } + + expose = { + checks = true + + paths = [ + { + path = "/healthz" + local_path_port = 8080 + listener_port = 21500 + protocol = "http2" + } + ] + } + } + + connect = { + native = false + } + + weights = { + passing = 5 + warning = 1 + } + + token = "233b604b-b92e-48c8-a253-5f11514e4b50" + namespace = "foo" +} +``` + +```json +{ + "service": { + "id": "redis", + "name": "redis", + "tags": ["primary"], + "address": "", + "meta": { + "meta": "for my service" + }, + "tagged_addresses": { + "lan": { + "address": "192.168.0.55", + "port": 8000, + }, + "wan": { + "address": "198.18.0.23", + "port": 80 + } + }, + "port": 8000, + "socket_path": "/tmp/redis.sock", + "enable_tag_override": false, + "checks": [ + { + "args": ["/usr/local/bin/check_redis.py"], + "interval": "10s" + } + ], + "kind": "connect-proxy", + "proxy_destination": "redis", // Deprecated + "proxy": { + "destination_service_name": "redis", + "destination_service_id": "redis1", + "local_service_address": "127.0.0.1", + "local_service_port": 9090, + "local_service_socket_path": "/tmp/redis.sock", + "mode": "transparent", + "transparent_proxy": { + "outbound_listener_port": 22500 + }, + "config": {}, + "upstreams": [], + "mesh_gateway": { + "mode": "local" + }, + "expose": { + "checks": true, + "paths": [ + { + "path": "/healthz", + "local_path_port": 8080, + "listener_port": 21500, + "protocol": "http2" + } + ] + } + }, + "connect": { + "native": false, + "sidecar_service": {} + "proxy": { // Deprecated + "command": [], + "config": {} + } + }, + "weights": { + "passing": 5, + "warning": 1 + }, + "token": "233b604b-b92e-48c8-a253-5f11514e4b50", + "namespace": "foo" + } +} +``` + + + + + + diff --git a/website/content/docs/services/discovery/dns-configuration.mdx b/website/content/docs/services/discovery/dns-configuration.mdx new file mode 100644 index 00000000000..794be43a206 --- /dev/null +++ b/website/content/docs/services/discovery/dns-configuration.mdx @@ -0,0 +1,76 @@ +--- +layout: docs +page_title: Configure Consul DNS behavior +description: -> + Learn how to modify the default DNS behavior so that services and nodes can easily discover other services and nodes in your network. +--- + +# Configure Consul DNS behavior + +This topic describes the default behavior of the Consul DNS functionality and how to customize how Consul performs queries. + +## Introduction +The Consul DNS is the primary interface for querying records when Consul service mesh is disabled and your network runs in a non-Kubernetes environment. The DNS enables you to look up services and nodes registered with Consul using terminal commands instead of making HTTP API requests to Consul. Refer to the [Discover Consul Nodes and Services Overview](/consul/docs/services/discovery/dns-overview) for additional information. + +## Configure DNS behaviors +By default, the Consul DNS listens for queries at `127.0.0.1:8600` and uses the `consul` domain. Specify the following parameters in the agent configuration to determine DNS behavior when querying services: + +- [`client_addr`](/consul/docs/agent/config/config-files#client_addr) +- [`ports.dns`](/consul/docs/agent/config/config-files#dns_port) +- [`recursors`](/consul/docs/agent/config/config-files#recursors) +- [`domain`](/consul/docs/agent/config/config-files#domain) +- [`alt_domain`](/consul/docs/agent/config/config-files#alt_domain) +- [`dns_config`](/consul/docs/agent/config/config-files#dns_config) + +### Configure WAN address translation +By default, Consul DNS queries return a node's local address, even when being queried from a remote datacenter. You can configure the DNS to reach a node from outside its datacenter by specifying the address in the following configuration fields in the Consul agent: + +- [advertise-wan](/consul/docs/agent/config/cli-flags#_advertise-wan) +- [translate_wan_addrs](/consul//docs/agent/config/config-files#translate_wan_addrs) + +### Use a custom DNS resolver library +You can specify a list of addresses in the agent's [`recursors`](/consul/docs/agent/config/config-files#recursors) field to provide upstream DNS servers that recursively resolve queries that are outside the service domain for Consul. + +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. + +### 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. + +### Forward queries to an agent +You can forward all queries sent to the `consul.` domain from the existing DNS server to a Consul agent. Refer to [Forward DNS for Consul Service Discovery](/consul/tutorials/networking/dns-forwarding) for instructions. + +### Query an alternate domain +By default, Consul responds to DNS queries in the `consul` domain, but you can set a specific domain for responding to DNS queries by configuring the [`domain`](/consul/docs/agent/config/config-files#domain) parameter. + +You can also specify an additional domain in the [`alt_domain`](/consul/docs/agent/config/config-files#alt_domain) agent configuration option, which configures Consul to respond to queries in a secondary domain. Configuring an alternate domain may be useful during a DNS migration or to distinguish between internal and external queries, for example. + +Consul's DNS response uses the same domain as the query. + +In the following example, the `alt_domain` parameter in the agent configuration is set to `test-domain`, which enables operators to query the domain: + +```shell-session +$ dig @127.0.0.1 -p 8600 consul.service.test-domain SRV + +;; QUESTION SECTION: +;consul.service.test-domain. IN SRV + +;; ANSWER SECTION: +consul.service.test-domain. 0 IN SRV 1 1 8300 machine.node.dc1.test-domain. + +;; ADDITIONAL SECTION: +machine.node.dc1.test-domain. 0 IN A 127.0.0.1 +machine.node.dc1.test-domain. 0 IN TXT "consul-network-segment=" +``` +#### PTR queries +Responses to pointer record (PTR) queries, such as `.in-addr.arpa.`, always use the [primary domain](/consul/docs/agent/config/config-files#domain) and not the alternative domain. + +### Caching +By default, DNS results served by Consul are not cached. Refer to the [DNS Caching tutorial](/consul/tutorials/networking/dns-caching) for instructions on how to enable caching. + + + + + + + + diff --git a/website/content/docs/services/discovery/dns-dynamic-lookups.mdx b/website/content/docs/services/discovery/dns-dynamic-lookups.mdx new file mode 100644 index 00000000000..436998ad9c7 --- /dev/null +++ b/website/content/docs/services/discovery/dns-dynamic-lookups.mdx @@ -0,0 +1,110 @@ +--- +layout: docs +page_title: Enable dynamic DNS queries +description: -> + Learn how to dynamically query the Consul DNS using prepared queries, which enable robust service and node lookups. +--- + +# Enable dynamic DNS queries + +This topic describes how to dynamically query the Consul catalog using prepared queries. Prepared queries are configurations that enable you to register a complex service query and execute it on demand. For information about how to perform standard node and service lookups, refer to [Perform Static DNS Queries](/consul/docs/services/discovery/dns-static-lookups). + +## Introduction + +Prepared queries provide a rich set of lookup features, such as filtering by multiple tags and automatically failing over to look for services in remote datacenters 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 [Query Consul Nodes and Services Overview](/consul/docs/services/discovery/dns-overview) for additional information about DNS query behaviors. + +## Requirements + +Consul 0.6.4 or later is required to create prepared query templates. + +### ACLs + +If ACLs are enabled, the querying service must present a token linked to permissions that enable read access for query, service, and node resources. Refer to the following documentation for information about creating policies to enable read access to the necessary resources: + +- [Prepared query rules](/consul/docs/security/acl/acl-rules#prepared-query-rules) +- [Service rules](/consul/docs/security/acl/acl-rules#service-rules) +- [Node rules](/consul/docs/security/acl/acl-rules#node-rules) + +## Create prepared queries + +Refer to the [prepared query reference](/consul/api-docs/query#create-prepared-query) for usage information. + +1. Specify the prepared query options in JSON format. The following prepared query targets all instances of the `redis` service in `dc1` and `dc2`: + + ```json + { + "Name": "my-query", + "Session": "adf4238a-882b-9ddc-4a9d-5b6758e4159e", + "Token": "", + "Service": { + "Service": "redis", + "Failover": { + "NearestN": 3, + "Datacenters": ["dc1", "dc2"] + }, + "Near": "node1", + "OnlyPassing": false, + "Tags": ["primary", "!experimental"], + "NodeMeta": { + "instance_type": "m3.large" + }, + "ServiceMeta": { + "environment": "production" + } + }, + "DNS": { + "TTL": "10s" + } + } + ``` + + Refer to the [prepared query configuration reference](/consul/api-docs/query#create-prepared-query) for information about all available options. + +1. Send the query in a POST request to the [`/query` API endpoint](/consul/api-docs/query). If the request is successful, Consul prints an ID for the prepared query. + + In the following example, the prepared query configuration is stored in the `payload.json` file: + + ```shell-session + $ curl --request POST --data @payload.json http://127.0.0.1:8500/v1/query + {"ID":"014af5ff-29e6-e972-dcf8-6ee602137127"}% + ``` + +1. To run the query, send a GET request to the endpoint and specify the ID returned from the POST call. + + ```shell-session + $ curl http://127.0.0.1:8500/v1/query/14af5ff-29e6-e972-dcf8-6ee602137127/execute\?near\=_agent + ``` + +## Execute prepared queries + +You can execute a prepared query using the standard lookup format or the strict RFC 2782 SRV lookup. + +### Standard lookup + +Use the following format to execute a prepared query using the standard lookup format: + +``` +.query[.]. +``` + +Refer [Standard lookups](/consul/docs/services/discovery/dns-static-lookups#standard-lookups) for additional information about the standard lookup format in Consul. + +### RFC 2782 SRV lookup + +Use the following format to execute a prepared query using the RFC 2782 lookup format: + +``` +_._tcp.query[.]. +``` + +For additional information about following the RFC 2782 SRV lookup format in Consul, refer to [RFC 2782 Lookup](/consul/docs/services/discovery/dns-static-lookups#rfc-2782-lookup). For general information about the RFC 2782 specification, refer to [A DNS RR for specifying the location of services \(DNS SRV\)](https://tools.ietf.org/html/rfc2782). + +### Lookup options + +The `datacenter` subdomain is optional. By default, the lookup queries the datacenter of this Consul agent. + +The `query name` or `id` subdomain is the name or ID of an existing prepared query. + +## Results + +To allow for simple load balancing, Consul returns the set of nodes in random order for each query. Prepared queries support A and SRV records. SRV records provide the port that a service is registered. Consul only serves SRV records if the client specifically requests them. diff --git a/website/content/docs/services/discovery/dns-overview.mdx b/website/content/docs/services/discovery/dns-overview.mdx new file mode 100644 index 00000000000..37eda715de2 --- /dev/null +++ b/website/content/docs/services/discovery/dns-overview.mdx @@ -0,0 +1,41 @@ +--- +layout: docs +page_title: DNS usage overview +description: >- + For service discovery use cases, Domain Name Service (DNS) is the main interface to look up, query, and address Consul nodes and services. Learn how a Consul DNS lookup can help you find services by tag, name, namespace, partition, datacenter, or domain. +--- + +# DNS usage overview + +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. + +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 retreive the relevant 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/registration/service-registration#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. + +## 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. + +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 new file mode 100644 index 00000000000..6f2db4108c3 --- /dev/null +++ b/website/content/docs/services/discovery/dns-static-lookups.mdx @@ -0,0 +1,357 @@ +--- +layout: docs +page_title: Perform static DNS queries +description: -> + Learn how to use standard Consul DNS lookup formats to enable service discovery for services and nodes. +--- + +# Perform static DNS queries +This topic describes how to query the Consul DNS to look up nodes and services registered with Consul. Refer to [Enable Dynamic DNS Queries](/consul/docs/services/discovery/dns-dynamic-lookups) for information about using prepared queries. + +## Introduction +Node lookups and service lookups are the fundamental types of queries you can perform using the Consul DNS. Node lookups interrogate the catalog for named Consul agents. Service lookups interrogate the catalog for services registered with Consul. Refer to [DNS Usage Overivew](/consul/docs/services/discovery/dns-overview) for additional background information. + +## Requirements +All versions of Consul support DNS lookup features. + +### ACLs +If ACLs are enabled, you must present a token linked with the necessary policies. We recommend using a separate token in production deployments for querying the DNS. By default, Consul agents resolve DNS requests using the preconfigured tokens in order of precedence: + +The agent's [`default` token](/consul/docs/agent/config/config-files#acl_tokens_default) +The built-in [`anonymous` token](/consul/docs/security/acl/acl-tokens#built-in-tokens). + + +The following table describes the available DNS lookups and required policies when ACLs are enabled: + +| Lookup | Type | Description | ACLs Required | +| --- | --- | --- | --- | +| `*.node.consul` | Node | Allows Consul to resolve DNS requests for the target node. Example: `.node.consul` | `node:read` | +| `*.service.consul`
    `*.connect.consul`
    `*.ingress.consul`
    `*.virtual.consul` |Service: standard | Allows Consul to resolve DNS requests for target service instances running on ACL-authorized nodes. Example: `.service.consul` | `service:read`
    `node:read` | + +> **Tutorials**: For hands-on guidance on how to configure an appropriate token for DNS, refer to the tutorial for [Production Environments](/consul/tutorials/security/access-control-setup-production#token-for-dns) and [Development Environments](/consul/tutorials/day-0/access-control-setup#enable-acls-on-consul-clients). + +## Node lookups +Specify the name of the node, datacenter, and domain using the following FQDN syntax: + +```text +.node[..dc]. +``` + +The `datacenter` subdomain is optional. By default, the lookup queries the datacenter of the agent. + +By default, the domain is `consul`. Refer to [Configure DNS Behaviors](/consul/docs/services/discovery/dns-configuration) for information about using alternate domains. + +### Node lookup results + +Node lookups return A and AAAA records that contain the IP address and TXT records containing the `node_meta` values of the node. + +By default, TXT record values match the node's metadata key-value pairs according to [RFC1464](https://www.ietf.org/rfc/rfc1464.txt). If the metadata key starts with `rfc1035-`, the TXT record only includes the node's metadata value. + +The following example lookup queries the `foo` node in the `default` datacenter: + +```shell-session +$ dig @127.0.0.1 -p 8600 foo.node.consul ANY + +; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 foo.node.consul ANY +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24355 +;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;foo.node.consul. IN ANY + +;; ANSWER SECTION: +foo.node.consul. 0 IN A 10.1.10.12 +foo.node.consul. 0 IN TXT "meta_key=meta_value" +foo.node.consul. 0 IN TXT "value only" + + +;; AUTHORITY SECTION: +consul. 0 IN SOA ns.consul. postmaster.consul. 1392836399 3600 600 86400 0 +``` + +### Node lookups for Consul Enterprise + +Consul Enterprise includes the admin partition concept, which is an abstraction that lets you define isolated administrative network areas. Refer to [Admin Partitions](/consul/docs/enterprise/admin-partitions) for additional information. + +Consul nodes reside in admin partitions within a datacenter. By default, node lookups query the same partition and datacenter of the Consul agent that received the DNS query. + +Use the following query format to specify a partition for a node lookup: + +``` +.node[..ap][..dc]. +``` + +Consul server agents are in the `default` partition. If you send a DNS query to Consul server agents, you must explicitly specify the partition of the target node if it is not `default`. + +## Service lookups +You can query the network for service providers using either the [standard lookup](#standard-lookup) method or [strict RFC 2782 lookup](#rfc-2782-lookup) method. + +By default, all SRV records are weighted equally in service lookup responses, but you can configure the weights using the [`Weights`](/consul/docs/services/configuration/services-configuration-reference#weights) attribute of the service definition. Refer to [Define Services](/consul/docs/services/usage/define-services) for additional information. + +The DNS protocol limits the size of requests, even when performing DNS TCP queries, which may affect your experience querying for services. For services with more than 500 instances, you may not be able to retrieve the complete list of instances for the service. Refer to [RFC 1035, Domain Names - Implementation and Specification](https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4) for additional information. + +Consul randomizes DNS SRV records and ignores weights specified in service configurations when printing responses. If records are truncated, each client using weighted SRV responses may have partial and inconsistent views of instance weights. As a result, the request distribution may be skewed from the intended weights. We recommend calling the [`/catalog/nodes` API endpoint](/consul/api-docs/catalog#list-nodes) to retrieve the complete list of nodes. You can apply query parameters to API calls to sort and filter the results. + +### Standard lookups +To perform standard service lookups, specify tags, the name of the service, datacenter, and domain using the following syntax to query for service providers: + +```text +[.].service[.].dc. +``` + +The `tag` subdomain is optional. It filters responses so that only service providers containing the tag appear. + +The `datacenter` subdomain is optional. By default, Consul interrogates the querying agent's datacenter. + +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. + +#### Standard lookup results +Standard services queries return A and SRV records. SRV records include the port that the service is registered on. SRV records are only served if the client specifically requests them. + +Services that fail their health check or that fail a node system check are omitted from the results. As a load balancing measure, Consul randomizes the set of nodes returned in the response. These mechanisms help you use DNS with application-level retries as the foundation for a self-healing service-oriented architecture. + +The following example retrieves the SRV records for any `redis` service registered in Consul. + +```shell-session +$ dig @127.0.0.1 -p 8600 consul.service.consul SRV + +; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 consul.service.consul ANY +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50483 +;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 1, ADDITIONAL: 1 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;consul.service.consul. IN SRV + +;; ANSWER SECTION: +consul.service.consul. 0 IN SRV 1 1 8300 foobar.node.dc1.consul. + +;; ADDITIONAL SECTION: +foobar.node.dc1.consul. 0 IN A 10.1.10.12 +``` + +The following example command and FQDN retrieves the SRV records for the primary Postgres service in the secondary datacenter: + +```shell-session hideClipboard +$ dig @127.0.0.1 -p 8600 primary.postgresql.service.dc2.consul SRV + +; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 primary.postgresql.service.dc2.consul ANY +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50483 +;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 1, ADDITIONAL: 1 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;consul.service.consul. IN SRV + +;; ANSWER SECTION: +consul.service.consul. 0 IN SRV 1 1 5432 primary.postgresql.service.dc2.consul. + +;; ADDITIONAL SECTION: +primary.postgresql.service.dc2.consul. 0 IN A 10.1.10.12 +``` + +### RFC 2782 lookup +Per [RFC 2782](https://tools.ietf.org/html/rfc2782), SRV queries must prepend `service` and `protocol` values with an underscore (`_`) to prevent DNS collisions. Use the following syntax to perform RFC 2782 lookups: + +```text +_._[.service][.]. +``` + +You can create lookups that filter results by placing service tags in the `protocol` field. Use the following syntax to create RFC 2782 lookups that filter results based on service tags: + +```text +_._[.service][.]. +``` + +The following example queries the `rabbitmq` service tagged with `amqp`, which returns an instance at `rabbitmq.node1.dc1.consul` on port `5672`: + +```shell-session +$ dig @127.0.0.1 -p 8600 _rabbitmq._amqp.service.consul SRV + +; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 _rabbitmq._amqp.service.consul ANY +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52838 +;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;_rabbitmq._amqp.service.consul. IN SRV + +;; ANSWER SECTION: +_rabbitmq._amqp.service.consul. 0 IN SRV 1 1 5672 rabbitmq.node1.dc1.consul. + +;; ADDITIONAL SECTION: +rabbitmq.node1.dc1.consul. 0 IN A 10.1.11.20 +``` +#### SRV responses for hosts in the .addr subdomain + +If a service registered with Consul is configured with an explicit IP address or addresses in the [`address`](/consul/docs/services/configuration/services-configuration-reference#address) or [`tagged_address`](/consul/docs/services/configuration/services-configuration-reference#tagged_address) parameter, then Consul returns the hostname in the target field of the answer section for the DNS SRV query according to the following format: + +```text +.addr..consul`. +``` + +In the following example, the `rabbitmq` service is registered with an explicit IPv4 address of `192.0.2.10`. + +```hcl +node_name = "node1" + +services { + name = "rabbitmq" + address = "192.0.2.10" + port = 5672 +} +{ + "node_name": "node1", + "services": [ + { + "name": "rabbitmq", + "address": "192.0.2.10", + "port": 5672 + } + ] +} +``` + +The following example SRV query response contains a single record with a hostname written as a hexadecimal value: + +```shell-session +$ dig @127.0.0.1 -p 8600 -t srv _rabbitmq._tcp.service.consul +short +1 1 5672 c000020a.addr.dc1.consul. +``` + +You can convert hex octets to decimals to reveal the IP address. The following example command converts the hostname expressed as `c000020a` into the IPv4 address specified in the service registration. + +``` +$ echo -n "c000020a" | perl -ne 'printf("%vd\n", pack("H*", $_))' +192.0.2.10 +``` + +In the following example, the `rabbitmq` service is registered with an explicit IPv6 address of `2001:db8:1:2:cafe::1337`. + +```hcl +node_name = "node1" + +services { + name = "rabbitmq" + address = "2001:db8:1:2:cafe::1337" + port = 5672 +} +{ + "node_name": "node1", + "services": [ + { + "name": "rabbitmq", + "address": "2001:db8:1:2:cafe::1337", + "port": 5672 + } + ] +} +``` + +The following example SRV query response contains a single record with a hostname written as a hexadecimal value: + +```shell-session +$ dig @127.0.0.1 -p 8600 -t SRV _rabbitmq._tcp.service.consul +short +1 1 5672 20010db800010002cafe000000001337.addr.dc1.consul. +``` + +The response contains the fully-expanded IPv6 address with colon separators removed. The following command re-adds the colon separators to display the fully expanded IPv6 address that was specified in the service registration. + +```shell-session +$ echo -n "20010db800010002cafe000000001337" | perl -ne 'printf join(":", unpack("(A4)*", $_))."\n"' +2001:0db8:0001:0002:cafe:0000:0000:1337 +``` + +### Service lookups for Consul Enterprise +You can perform the following types of service lookups to query for services in another namespace, partition, and datacenter: + +- `.service` +- `.connect` +- `.virtual` +- `.ingress` + +Use the following query format to specify namespace, partition, or datacenter: +``` +[.].service[..ns][..ap][..dc] +``` + +The `namespace`, `partition`, and `datacenter` are optional. By default, all service lookups use the `default` namespace within the partition and datacenter of the Consul agent that received the DNS query. + +Consul server agents reside in the `default` partition. If DNS queries are addressed to Consul server agents, you must explicitly specify the partition of the target service when querying for services in partitions other than `default`. + +To lookup services imported from a cluster peer, refer to [Service virtual IP lookups for Consul Enterprise](#service-virtual-ip-lookups-for-consul-enterprise). + +#### Alternative formats for specifying namespace + +Although we recommend using the format described in [Service lookups for Consul Enterprise](#service-lookups-for-consul-enterprise) for readability, you can use the alternate query format to specify namespaces but not partitions: + +``` +[.].service... +``` + +### Service mesh-enabled service lookups + +Add the `.connect` subdomain to query for service mesh-enabled services: + +```text +.connect. +``` + +This finds all service mesh-capable endpoints for the service. A service mesh-capable endpoint may be a proxy for a service or a natively integrated service mesh application. The DNS interface does not differentiate the two. + +Many services use a proxy that handles service discovery automatically. As a result, they may not use the DNS format, which is primarily for service mesh-native applications. +This endpoint only finds services within the same datacenter and does not support tags. Refer to the [`catalog` API endpoint](/consul/api-docs/catalog) for more complex behaviors. + +### Service virtual IP lookups + +Add the `.virtual` subdomain to queries to find the unique virtual IP allocated for a service: + +```text +.virtual[.]. +``` + +This returns the unique virtual IP for any service mesh-capable service. Each service mesh service has a virtual IP assigned to it by Consul. Sidecar proxies use the virtual IP to enable the [transparent proxy](/consul/docs/connect/transparent-proxy) feature. + +The peer name is an optional. The DNS uses it to query for the virtual IP of a service imported from the specified peer. + +Consul adds virtual IPs to the [`tagged_addresses`](/consul/docs/services/configuration/services-configuration-reference#tagged_addresses) field in the service definition under the `consul-virtual` tag. + +#### Service virtual IP lookups for Consul Enterprise + +By default, a service virtual IP lookup checks the `default` namespace within the partition and datacenter of the Consul agent that received the DNS query. +To lookup services imported from a partition in another cluster peered to the querying cluster or open-source datacenter, specify the namespace and peer name in the lookup: + +```text +.virtual[.].. +``` + +To lookup services in a cluster peer that have not been imported, refer to [Service lookups for Consul Enterprise](#service-lookups-for-consul-enterprise). + +### Ingress Service Lookups + +Add the `.ingress` subdomain to your DNS FQDN to find ingress-enabled services: + +```text +.ingress. +``` + +This finds all ingress gateway endpoints for the service. + +This endpoint finds services within the same datacenter and does not support tags. Refer to the [`catalog` API endpoint](/consul/api-docs/catalog) for more complex behaviors. + +### UDP-based DNS queries + +When the DNS query is performed using UDP, Consul truncateß the results without setting the truncate bit. This prevents a redundant lookup over TCP that generates additional load. If the lookup is done over TCP, the results are not truncated. \ No newline at end of file diff --git a/website/content/docs/services/services.mdx b/website/content/docs/services/services.mdx new file mode 100644 index 00000000000..b24562e64e3 --- /dev/null +++ b/website/content/docs/services/services.mdx @@ -0,0 +1,39 @@ +--- +layout: docs +page_title: Services overview +description: >- + Learn about services and service discovery workflows and concepts for virtual machine environments. +--- + +# Services overview +This topic provides overview information about services and how to make them discoverable in Consul when your network operates on virtual machines. If service mesh is enabled in your network, refer to the topics in [Consul Service Mesh](/consul/docs/connect/). If your network is running in Kubernetes, refer to the topics in [Consul on Kubernetes](/consul/docs/k8s). + +## Introduction +A _service_ is an entity in your network that performs a specialized operation or set of related operations. In many contexts, a service is software that you want to make available to users or other programs with access to your network. Services can also refer to native Consul functionality, such as _service mesh proxies_ and _gateways_, that enable you to establish connections between different parts of your network. + +You can define and register services with Consul, which makes them discoverable to other services in the network. You can also define various types of health checks that perform several safety functions, such as allowing a web balancer to gracefully remove failing nodes and allowing a database to replace a failed secondary. + +## Workflow +For service discovery, the core Consul workflow for services consists of three stages: + +1. **Define services and health checks**: A service definition lets you define various aspects of the service, including how it is discovered by other services in the network. You can define health checks in the service definitions to verify the health of the service. Refer to [Define Services](/consul/docs/services/usage/define-services) and [Define Health Checks](/consul/docs/services/usage/checks) for additional information. + +1. **Register services and health checks**: After defining your services and health checks, you must register them with a Consul agent. Refer to [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) for additional information. + +1. **Query for services**: After registering your services and health checks, other services in your network can use the DNS to perform static or dynamic lookups to access your service. Refer to [DNS Usage Overview](/consul/docs/services/discovery/dns-overview) for additional information about the different ways to discover services in your datacenters. + + +## Service mesh use cases +Consul redirects service traffic through sidecar proxies if you use Consul service mesh. As a result, you must specify upstream configurations in service definitions. The service mesh experience is different for virtual machine (VM) and Kubernetes environments. + +### Virtual machines +You must define upstream services in the service definition. Consul uses the upstream configuration to bind the service with its upstreams. After registering the service, you must start a sidecar proxy on the VM to enable mesh connectivity. Refer to [Register a Service Mesh Proxy in a Service Registration](/consul/docs/connect/registration/sidecar-service) for details. + +### Kubernetes +If you use Consul on Kubernetes, enable the service mesh injector in your Consul Helm chart and Consul automatically adds a sidecar to each of your pods using the Kubernetes `Service` definition as a reference. You can specify upstream annotations in the `Deployment` definition to bind upstream services to the pods. +Refer to [`connectInject`](/consul/docs/k8s/connect#installation-and-configuration) and [the upstreams annotation documentation](/consul/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) for additional information. + +### Multiple services +You can define common characteristics for services in your mesh, such as the admin partition, namespace, or upstreams, by creating and applying a `service-defaults` configuration entry. You can also define override configurations for specific upstreams or service instances. To use `service-defaults` configuraiton entries, you must enable Consul service mesh in your network. + +Refer to [Define Service Defaults](/consul/docs/services/usage/define-services#define-service-defaults) for additional information. \ No newline at end of file diff --git a/website/content/docs/services/usage/checks.mdx b/website/content/docs/services/usage/checks.mdx new file mode 100644 index 00000000000..afbf53dcc99 --- /dev/null +++ b/website/content/docs/services/usage/checks.mdx @@ -0,0 +1,592 @@ +--- +layout: docs +page_title: Define health checks +description: -> + Learn how to configure different types of health checks for services you register with Consul. +--- + +# Define health checks +This topic describes how to create different types of health checks for your services. + + +## Overview +Health checks are configurations that verifies the health of a service or node. Health checks configurations are nested in the `service` block. Refer to [Define Services](/consul/docs/services/usage/define-services) for information about specifying other service parameters. + +You can define individual health checks for your service in separate `check` blocks or define multiple checks in a `checks` block. Refer to [Define multiple checks](#define-multiple-checks) for additional information. + +You can create several different kinds of checks: + +- _Script_ checks invoke an external application that performs the health check, exits with an appropriate exit code, and potentially generates output. Script checks are one of the most common types of checks. +- _HTTP_ checks make an HTTP GET request to the specified URL and wait for the specified amount of time. HTTP checks are one of the most common types of checks. +- _TCP_ checks attempt to connect to an IP or hostname and port over TCP and wait for the specified amount of time. +- _UDP_ checks send UDP datagrams to the specified IP or hostname and port and wait for the specified amount of time. +- _Time-to-live (TTL)_ checks are passive checks that await updates from the service. If the check does not receive a status update before the specified duration, the health check enters a `critical`state. +- _Docker_ checks are dependent on external applications packaged with a Docker container that are triggered by calls to the Docker `exec` API endpoint. +- _gRPC_ checks probe applications that support the standard gRPC health checking protocol. +- _H2ping_ checks test an endpoint that uses http2. The check connects to the endpoint and sends a ping frame. +- _Alias_ checks represent the health state of another registered node or service. + +If your network runs in a Kubernetes environment, you can sync service health information with Kubernetes health checks. Refer to [Configure Health Checks for Consul on Kubernetes](/consul/docs/k8s/connect/health) for details. + +### Registration + +After defining health checks, you must register the service containing the checks with Consul. Refer to [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) for additional information. If the service is already registered, you can reload the service configuration file to implement your health check. Refer to [Reload](/consul/commands/reload) for additional information. + +## Define multiple checks + +You can define multiple checks for a service in a single `checks` block. The `checks` block contains an array of objects. The objects contain the configuration for each health check you want to implement. The following example includes two script checks named `mem` and `cpu` and an HTTP check that calls the `/health` API endpoint. + + + +```hcl +checks = [ + { + id = "chk1" + name = "mem" + args = ["/bin/check_mem", "-limit", "256MB"] + interval = "5s" + }, + { + id = "chk2" + name = "/health" + http = "http://localhost:5000/health" + interval = "15s" + }, + { + id = "chk3" + name = "cpu" + args = ["/bin/check_cpu"] + interval = "10s" + }, + ... +] +``` + +```json +{ + "checks": [ + { + "id": "chk1", + "name": "mem", + "args": ["/bin/check_mem", "-limit", "256MB"], + "interval": "5s" + }, + { + "id": "chk2", + "name": "/health", + "http": "http://localhost:5000/health", + "interval": "15s" + }, + { + "id": "chk3", + "name": "cpu", + "args": ["/bin/check_cpu"], + "interval": "10s" + }, + ... + ] +} +``` + + + +## Define initial health check status +When checks are registered against a Consul agent, they are assigned a `critical` status by default. This prevents services from registering as `passing` and entering the service pool before their health is verified. You can add the `status` parameter to the check definition to specify the initial state. In the following example, the check registers in a `passing` state: + + + +```hcl +check = { + id = "mem" + args = ["/bin/check_mem", "-limit", "256MB"] + interval = "10s" + status = "passing" +} +``` + +```json +{ + "check": [ + { + "args": [ + "/bin/check_mem", + "-limit", + "256MB" + ], + "id": "mem", + "interval": "10s", + "status": "passing" + } + ] +} +``` + + + +## Script checks +Script checks invoke an external application that performs the health check, exits with an appropriate exit code, and potentially generates output data. The output of a script check is limited to 4KB. Outputs that exceed the limit are truncated. + +Script checks timeout after 30 seconds by default, but you can configure a custom script check timeout value by specifying the `timeout` field in the check definition. When the timeout is reached on Windows, Consul waits for any child processes spawned by the script to finish. For any other system, Consul attempts to force-kill the script and any child processes it has spawned once the timeout has passed. + +### Script check configuration +To enable script checks, you must first enable the agent to send external requests, then configure the health check settings in the service definition: + +1. Add one of the following configurations to your agent configuration file to enable a script check: + - [`enable_local_script_checks`](/consul/docs/agent/config/cli-flags#enable_local_script_checks): Enable script checks defined in local configuration files. Script checks registered using the HTTP API are not allowed. + - [`enable_script_checks`](/consul/docs/agent/config/cli-flags#enable_script_checks): Enable script checks no matter how they are registered. + + !> **Security warning:** Enabling non-local script checks in some configurations may introduce a known remote execution vulnerability targeted by malware. We strongly recommend `enable_local_script_checks` instead. + +1. Specify the script to run in the `args` of the `check` block in your service configuration file. In the following example, a check named `Memory utilization` invokes the `check_mem.py` script every 10 seconds and times out if a response takes longer than one second: + + + + ```hcl + service { + ## ... + check = { + id = "mem-util" + name = "Memory utilization" + args = ["/usr/local/bin/check_mem.py", "-limit", "256MB"] + interval = "10s" + timeout = "1s" + } + } + ``` + + ```json + { + "service": [ + { + "check": { + "id": "mem-util", + "name": "Memory utilization", + "args": ["/usr/local/bin/check_mem.py", "-limit", "256MB"], + "interval": "10s", + "timeout": "1s" + } + } ] + } + ``` + + +Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for information about all health check configurations. + +### Script check exit codes +The following exit codes returned by the script check determine the health check status: + +- Exit code 0 - Check is passing +- Exit code 1 - Check is warning +- Any other code - Check is failing + +Any output of the script is captured and made available in the `Output` field of checks included in HTTP API responses. Refer to the example described in the [local service health endpoint](/consul/api-docs/agent/service#by-name-json). + +## HTTP checks +_HTTP_ checks send an HTTP request to the specified URL and report the service health based on the [HTTP response code](#http-check-response-codes). We recommend using HTTP checks over [script checks](#script-checks) that use cURL or another external process to check an HTTP operation. + +### HTTP check configuration +Add an `http` field to the `check` block in your service definition file and specify the HTTP address, including port number, for the check to call. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for information about all health check configurations. + +In the following example, an HTTP check named `HTTP API on port 5000` sends a `POST` request to the `health` endpoint every 10 seconds: + + + +```hcl +check = { + id = "api" + name = "HTTP API on port 5000" + http = "https://localhost:5000/health" + tls_server_name = "" + tls_skip_verify = false + method = "POST" + header = { + Content-Type = ["application/json"] + } + body = "{\"method\":\"health\"}" + disable_redirects = true + interval = "10s" + timeout = "1s" +} +``` + +```json +{ + "check": { + "id": "api", + "name": "HTTP API on port 5000", + "http": "https://localhost:5000/health", + "tls_server_name": "", + "tls_skip_verify": false, + "method": "POST", + "header": { "Content-Type": ["application/json"] }, + "body": "{\"method\":\"health\"}", + "interval": "10s", + "timeout": "1s" + } +} +``` + + +HTTP checks send GET requests by default, but you can specify another request method in the `method` field. You can send additional headers in the `header` block. The `header` block contains a key and an array of strings, such as `{"x-foo": ["bar", "baz"]}`. By default, HTTP checks timeout at 10 seconds, but you can specify a custom timeout value in the `timeout` field. + +HTTP checks expect a valid TLS certificate by default. You can disable certificate verification by setting the `tls_skip_verify` field to `true`. When using TLS and a host name is specified in the `http` field, the check automatically determines the SNI from the URL. If the `http` field is configured with an IP address or if you want to explicitly set the SNI, specify the name in the `tls_server_name` field. + +The check follows HTTP redirects configured in the network by default. Set the `disable_redirects` field to `true` to disable redirects. + +### HTTP check response codes +Responses larger than 4KB are truncated. The HTTP response determines the status of the service: + +- A `200`-`299` response code is healthy. +- A `429` response code indicating too many requests is a warning. +- All other response codes indicate a failure. + + +## TCP checks +TCP checks establish connections to the specified IPs or hosts. If the check successfully establishes a connection, the service status is reported as `success`. If the IP or host does not accept the connection, the service status is reported as `critical`. We recommend TCP checks over [script checks](#script-checks) that use netcat or another external process to check a socket operation. + +### TCP check configuration +Add a `tcp` field to the `check` block in your service definition file and specify the address, including port number, for the check to call. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for information about all health check configurations. + +In the following example, a TCP check named `SSH TCP on port 22` attempts to connect to `localhost:22` every 10 seconds: + + + + +```hcl +check = { + id = "ssh" + name = "SSH TCP on port 22" + tcp = "localhost:22" + interval = "10s" + timeout = "1s" +} +``` + +```json +{ + "check": { + "id": "ssh", + "name": "SSH TCP on port 22", + "tcp": "localhost:22", + "interval": "10s", + "timeout": "1s" + } +} +``` + + + +If a hostname resolves to an IPv4 and an IPv6 address, Consul attempts to connect to both addresses. The first successful connection attempt results in a successful check. + +By default, TCP check requests timeout at 10 seconds, but you can specify a custom timeout in the `timeout` field. + +## UDP checks +UDP checks direct the Consul agent to send UDP datagrams to the specified IP or hostname and port. The check status is set to `success` if any response is received from the targeted UDP server. Any other result sets the status to `critical`. + +### UDP check configuration +Add a `udp` field to the `check` block in your service definition file and specify the address, including port number, for sending datagrams. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for information about all health check configurations. + +In the following example, a UDP check named `DNS UDP on port 53` sends datagrams to `localhost:53` every 10 seconds: + + + +```hcl +check = { + id = "dns" + name = "DNS UDP on port 53" + udp = "localhost:53" + interval = "10s" + timeout = "1s" +} +``` + +```json +{ + "check": { + "id": "dns", + "name": "DNS UDP on port 53", + "udp": "localhost:53", + "interval": "10s", + "timeout": "1s" + } +} +``` + + + +By default, UDP checks timeout at 10 seconds, but you can specify a custom timeout in the `timeout` field. If any timeout on read exists, the check is still considered healthy. + +## OSService check +OSService checks if an OS service is running on the host. OSService checks support Windows services on Windows hosts or SystemD services on Unix hosts. The check logs the service as `healthy` if it is running. If the service is not running, the status is logged as `critical`. All other results are logged with `warning`. A `warning` status indicates that the check is not reliable because an issue is preventing it from determining the health of the service. + +### OSService check configurations +Add an `os_service` field to the `check` block in your service definition file and specify the name of the service to check. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference] for information about all health check configurations. + +In the following example, an OSService check named `svcname-001 Windows Service Health` verifies that the `myco-svctype-svcname-001` service is running every 10 seconds: + + + +```hcl +check = { + id = "myco-svctype-svcname-001" + name = "svcname-001 Windows Service Health" + service_id = "flash_pnl_1" + os_service = "myco-svctype-svcname-001" + interval = "10s" +} +``` + +```json +{ + "check": { + "id": "myco-svctype-svcname-001", + "name": "svcname-001 Windows Service Health", + "service_id": "flash_pnl_1", + "os_service": "myco-svctype-svcname-001", + "interval": "10s" + } +} +``` + + + +## TTL checks +Time-to-live (TTL) checks wait for an external process to report the service's state to a Consul [`/agent/check` HTTP endpoint](/consul/api-docs/agent/check). If the check does not receive an update before the specified `ttl` duration, the check logs the service as `critical`. For example, if a healthy application is configured to periodically send a `PUT` request a status update to the HTTP endpoint, then the health check logs a `critical` state if the application is unable to send the update before the TTL expires. The check uses the following endpoints to update health information: + +- [pass](/consul/api-docs/agent/check#ttl-check-pass) +- [warn] (/consul/api-docs/agent/check#ttl-check-warn) +- [fail](/consul/api-docs/agent/check#ttl-check-fail) +- [update](/consul/api-docs/agent/check#ttl-check-update) + +TTL checks also persist their last known status to disk so that the Consul agent can restore the last known status of the check across restarts. Persisted check status is valid through the end of the TTL from the time of the last check. + +You can manually mark a service as unhealthy using the [`consul maint` CLI command](/consul/commands/maint) or [`agent/maintenance` HTTP API endpoint](/consul/api-docs/agent#enable-maintenance-mode), rather than waiting for a TTL health check if the `ttl` duration is high. + +### TTL check configuration +Add a `ttl` field to the `check` block in your service definition file and specify how long to wait for an update from the external process. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference] for information about all health check configurations. + +In the following example, a TTL check named `Web App Status` logs the application as `critical` if a status update is not received every 30 seconds: + + + + +```hcl +check = { + id = "web-app" + name = "Web App Status" + notes = "Web app does a curl internally every 10 seconds" + ttl = "30s" +} +``` + +```json +{ + "check": { + "id": "web-app", + "name": "Web App Status", + "notes": "Web app does a curl internally every 10 seconds", + "ttl": "30s" + } +} +``` + + + +## Docker checks +Docker checks invoke an application packaged within a Docker container. The application should perform a health check and exit with an appropriate exit code. + +The application is triggered within the running container through the Docker `exec` API. You should have access to either the Docker HTTP API or the Unix socket. Consul uses the `$DOCKER_HOST` environment variable to determine the Docker API endpoint. + +The output of a Docker check is limited to 4KB. Larger outputs are truncated. + +### Docker check configuration +To enable Docker checks, you must first enable the agent to send external requests, then configure the health check settings in the service definition: + +1. Add one of the following configurations to your agent configuration file to enable a Docker check: + + - [`enable_local_script_checks`](/consul/docs/agent/config/cli-flags#enable_local_script_checks): Enable script checks defined in local config files. Script checks registered using the HTTP API are not allowed. + + - [`enable_script_checks`](/consul/docs/agent/config/cli-flags#enable_script_checks): Enable script checks no matter how they are registered. + + !> **Security warning**: Enabling non-local script checks in some configurations may introduce a known remote execution vulnerability targeted by malware. We strongly recommend `enable_local_script_checks` instead. +1. Configure the following fields in the `check` block in your service definition file: + - `docker_container_id`: The `docker ps` command is a common way to get the ID. + - `shell`: Specifies the shell to use for performing the check. Different containers can run different shells on the same host. + - `args`: Specifies the external application to invoke. + - `interval`: Specifies the interval for running the check. + +In the following example, a Docker check named `Memory utilization` invokes the `check_mem.py` application in container `f972c95ebf0e` every 10 seconds: + + + + +```hcl +check = { + id = "mem-util" + name = "Memory utilization" + docker_container_id = "f972c95ebf0e" + shell = "/bin/bash" + args = ["/usr/local/bin/check_mem.py"] + interval = "10s" +} +``` + +```json +{ + "check": { + "id": "mem-util", + "name": "Memory utilization", + "docker_container_id": "f972c95ebf0e", + "shell": "/bin/bash", + "args": ["/usr/local/bin/check_mem.py"], + "interval": "10s" + } +} +``` + + + +## gRPC checks +gRPC checks send a request to the specified endpoint. These checks are intended for applications that support the standard [gRPC health checking protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + +### gRPC check configuration +Add a `grpc` field to the `check` block in your service definition file and specify the endpoint, including port number, for sending requests. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference] for information about all health check configurations. + +In the following example, a gRPC check named `Service health status` probes the entire application by sending requests to `127.0.0.1:12345` every 10 seconds: + + + +```hcl +check = { + id = "mem-util" + name = "Service health status" + grpc = "127.0.0.1:12345" + grpc_use_tls = true + interval = "10s" +} +``` + +```json +{ + "check": { + "id": "mem-util", + "name": "Service health status", + "grpc": "127.0.0.1:12345", + "grpc_use_tls": true, + "interval": "10s" + } +} +``` + + + +gRPC checks probe the entire gRPC server, but you can check on a specific service by adding the service identifier after the gRPC check's endpoint using the following format: `/:service_identifier`. + +In the following example, a gRPC check probes `my_service` in the application at `127.0.0.1:12345` every 10 seconds: + + + + +```hcl +check = { + id = "mem-util" + name = "Service health status" + grpc = "127.0.0.1:12345/my_service" + grpc_use_tls = true + interval = "10s" +} +``` + +```json +{ + "check": { + "id": "mem-util", + "name": "Service health status", + "grpc": "127.0.0.1:12345/my_service", + "grpc_use_tls": true, + "interval": "10s" + } +} +``` + + + +TLS is disabled for gRPC checks by default. You can enable TLS by setting `grpc_use_tls` to `true`. If TLS is enabled, you must either provide a valid TLS certificate or disable certificate verification by setting the `tls_skip_verify` field to `true`. + +By default, gRPC checks timeout after 10 seconds, but you can specify a custom duration in the `timeout` field. + +## H2ping checks +H2ping checks test an endpoint that uses HTTP2 by connecting to the endpoint and sending a ping frame. If the endpoint sends a response within the specified interval, the check status is set to `success`. + +### H2ping check configuration +Add an `h2ping` field to the `check` block in your service definition file and specify the HTTP2 endpoint, including port number, for the check to ping. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference) for information about all health check configurations. + +In the following example, an H2ping check named `h2ping` pings the endpoint at `localhost:22222` every 10 seconds: + + + + +```hcl +check = { + id = "h2ping-check" + name = "h2ping" + h2ping = "localhost:22222" + interval = "10s" + h2ping_use_tls = false +} +``` + +```json +{ + "check": { + "id": "h2ping-check", + "name": "h2ping", + "h2ping": "localhost:22222", + "interval": "10s", + "h2ping_use_tls": false + } +} +``` + + + +TLS is enabled by default, but you can disable TLS by setting `h2ping_use_tls` to `false`. When TLS is disabled, the Consul sends pings over h2c. When TLS is enabled, a valid certificate is required unless `tls_skip_verify` is set to `true`. + +By default, H2ping checks timeout at 10 seconds, but you can specify a custom duration in the `timeout` field. + + +## Alias checks +Alias checks continuously report the health state of another registered node or service. If the alias experiences errors while watching the actual node or service, the check reports a`critical` state. Consul updates the alias and actual node or service state asynchronously but nearly instantaneously. + +For aliased services on the same agent, the check monitors the local state without consuming additional network resources. For services and nodes on different agents, the check maintains a blocking query over the agent's connection with a current server and allows stale requests. + +### ACLs +For the blocking query, the alias check presents the ACL token set on the actual service or the token configured in the check definition. If neither are available, the alias check falls back to the default ACL token set for the agent. Refer to [`acl.tokens.default`](/consul/docs/agent/config/config-files#acl_tokens_default) for additional information about the default ACL token. + +### Alias checks configuration +Add an `alias_service` field to the `check` block in your service definition file and specify the name of the service or node to alias. All other fields are optional. Refer to [Health Checks Configuration Reference](/consul/docs/services/configuration/checks-configuration-reference] for information about all health check configurations. + +In the following example, an alias check with the ID `web-alias` reports the health state of the `web` service: + + + + +```hcl +check = { + id = "web-alias" + alias_service = "web" +} +``` + +```json +{ + "check": { + "id": "web-alias", + "alias_service": "web" + } +} +``` + + + +By default, the alias must be registered with the same Consul agent as the alias check. If the service is not registered with the same agent, you must specify `"alias_node": ""` in the `check` configuration. If no service is specified and the `alias_node` field is enabled, the check aliases the health of the node. If a service is specified, the check will alias the specified service on this particular node. \ No newline at end of file diff --git a/website/content/docs/services/usage/define-services.mdx b/website/content/docs/services/usage/define-services.mdx new file mode 100644 index 00000000000..1e0490b9b3a --- /dev/null +++ b/website/content/docs/services/usage/define-services.mdx @@ -0,0 +1,450 @@ +--- +layout: docs +page_title: Define services +description: >- + Learn how to define services so that they are discoverable in your network. +--- + +# Define services + +This topic describes how to define services so that they can be discovered by other services. Refer to [Services Overview](/consul/docs/services/services) for additional information. + +## Overview + +You must tell Consul about the services deployed to your network if you want them to be discoverable. You can define services in a configuration file or send the service definition parameters as a payload to the `/agent/service/register` API endpoint. Refer to [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) for details about how to register services with Consul. + +You can define multiple services individually using `service` blocks or group multiple services into the same `services` configuration block. Refer to [Define multiple services in a single file](#define-multiple-services-in-a-single-file) for additional information. + +If Consul service mesh is enabled in your network, you can use the [service defaults configuration entry](/consul/docs/connect/config-entries/service-defaults) to specify default global values for services. The configuration entry lets you define common service parameter, such as upstreams, namespaces, and partitions. Refer to [Define service defaults](#define-service-defaults) for additional information. + +## Requirements + +The core service discovery features are available in all versions of Consul. + +### Service defaults +To use the [service defaults configuration entry](#define-service-defaults), verify that your installation meets the following requirements: + +- Consul 1.5.0+ +- Consul 1.8.4+ is required to use the `ServiceDefaults` custom resource on Kubernetes + +### ACLs +If ACLs are enabled, resources in your network must present a token with `service:read` access to read a service defaults configuration entry. + +You must also present a token with `service:write` access to create, update, or delete a service defaults configuration entry. + +Service configurations must also contain and present an ACL token to perform anti-entropy syncs and deregistration operations. Refer to [Modify anti-entropy synchronozation](#modify-anti-entropy-synchronization) for additional information. + +On Consul Enterprise, you can register services with specific namespaces if the services' ACL tokens are scoped to the namespace. Services registered with a service definition do not inherit the namespace associated with the ACL token specified in the `token` field. The `namespace` and the `token` parameters must be included in the service definition for the service to be registered to the namespace that the ACL token is scoped to. + +## Define a service +Create a file for your service configurations and add a `service` block. The `service` block contains the parameters that configure various aspects of the service, including how it is discovered by other services in the network. The only required parameter is `name`. Refer to [Service Definition Reference](/consul/docs/services/configuration/services-configuration-reference) for details about the configuration options. + +For Kubernetes environments, you can enable the [`connectInject`](/consul/docs/k8s/connect#installation-and-configuration) configuration in your Consul Helm chart so that Consul automatically adds a sidecar to each of your pods. Consul uses the Kubernetes `Service` definition as a reference. + +The following example defines a service named `redis` that is available on port `80`. By default, the service has the IP address of the agent node. + + + + +```hcl +service { + name = "redis" + id = "redis" + port = 80 + tags = ["primary"] + + meta = { + custom_meta_key = "custom_meta_value" + } + + tagged_addresses = { + lan = { + address = "192.168.0.55" + port = 8000 + } + + wan = { + address = "198.18.0.23" + port = 80 + } + } +} +``` + + + + +```json +{ + "service": [ + { + "id": "redis", + "meta": [ + { + "custom_meta_key": "custom_meta_value" + } + ], + "name": "redis", + "port": 80, + "tagged_addresses": [ + { + "lan": [ + { + "address": "192.168.0.55", + "port": 8000 + } + ], + "wan": [ + { + "address": "198.18.0.23", + "port": 80 + } + ] + } + ], + "tags": [ + "primary" + ] + } + ] +} +``` + + + +```yaml +service: +- id: redis + meta: + - custom_meta_key: custom_meta_value + name: redis + port: 80 + tagged_addresses: + - lan: + - address: 192.168.0.55 + port: 8000 + wan: + - address: 198.18.0.23 + port: 80 + tags: + - primary +``` + + + +### Health checks + +You can add a `check` or `checks` block to your service configuration to define one or more health checks that monitor the health of your services. Refer to [Define Health Checks](/consul/docs/services/usage/checks) for additional information. + +### Register a service + +You can register your service using the [`consul services` command](/consul/commands/services) or by calling the [`/agent/services` API endpoint](/consul/api-docs/agent/service). Refer to [Register Services and Health Checks](/consul/docs/services/usage/register-services-checks) for details. + +## Define service defaults +If Consul service mesh is enabled in your network, you can define default values for services in your mesh by creating and applying a `service-defaults` configuration entry containing. Refer to [Service Mesh Configuration Overview](/consul/docs/connect/configuration) for additional information. + +Create a file for the configuration entry and specify the required fields. If you are authoring `service-defaults` in HCL or JSON, the `Kind` and `Name` fields are required. On Kubernetes, the `apiVersion`, `kind`, and `metadata.name` fields are required. Refer to [Service Defaults Reference](/consul/docs/connect/config-entries/service-defaults) for details about the configuration options. + +If you use Consul Enterprise, you can also specify the `Namespace` and `Partition` fields to apply the configuration to services in a specific namespace or partition. For Kubernetes environments, the configuration entry is always created in the same partition as the Kubernetes cluster. + +### Consul OSS example +The following example instructs services named `counting` to send up to `512` concurrent requests to a mesh gateway: + + + +```hcl +Kind = "service-defaults" +Name = "counting" + +UpstreamConfig = { + Defaults = { + MeshGateway = { + Mode = "local" + } + Limits = { + MaxConnections = 512 + MaxPendingRequests = 512 + MaxConcurrentRequests = 512 + } + } + + Overrides = [ + { + Name = "dashboard" + MeshGateway = { + Mode = "remote" + } + } + ] +} +``` +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: counting +spec: + upstreamConfig: + defaults: + meshGateway: + mode: local + limits: + maxConnections: 512 + maxPendingRequests: 512 + maxConcurrentRequests: 512 + overrides: + - name: dashboard + meshGateway: + mode: remote +``` +```json +{ + "Kind": "service-defaults", + "Name": "counting", + "UpstreamConfig": { + "Defaults": { + "MeshGateway": { + "Mode": "local" + }, + "Limits": { + "MaxConnections": 512, + "MaxPendingRequests": 512, + "MaxConcurrentRequests": 512 + } + }, + "Overrides": [ + { + "Name": "dashboard", + "MeshGateway": { + "Mode": "remote" + } + } + ] + } +} +``` + + +### Consul Enterprise example +The following example instructs services named `counting` in the `prod` namespace to send up to `512` concurrent requests to a mesh gateway: + + + +```hcl +Kind = "service-defaults" +Name = "counting" +Namespace = "prod" + +UpstreamConfig = { + Defaults = { + MeshGateway = { + Mode = "local" + } + Limits = { + MaxConnections = 512 + MaxPendingRequests = 512 + MaxConcurrentRequests = 512 + } + } + + Overrides = [ + { + Name = "dashboard" + MeshGateway = { + Mode = "remote" + } + } + ] +} +``` +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: counting + namespace: prod +spec: + upstreamConfig: + defaults: + meshGateway: + mode: local + limits: + maxConnections: 512 + maxPendingRequests: 512 + maxConcurrentRequests: 512 + overrides: + - name: dashboard + meshGateway: + mode: remote +``` +```json +{ + "Kind": "service-defaults", + "Name": "counting", + "Namespace" : "prod", + "UpstreamConfig": { + "Defaults": { + "MeshGateway": { + "Mode": "local" + }, + "Limits": { + "MaxConnections": 512, + "MaxPendingRequests": 512, + "MaxConcurrentRequests": 512 + } + }, + "Overrides": [ + { + "Name": "dashboard", + "MeshGateway": { + "Mode": "remote" + } + } + ] + } +} +``` + + +### Apply service defaults + +You can apply your `service-defaults` configuration entry using the [`consul config` command](/consul/commands/config) or by calling the [`/config` API endpoint](/consul/api-docs/config). In Kubernetes environments, apply the `service-defaults` custom resource definitions (CRD) to implement and manage Consul configuration entries. + +Refer to the following topics for details about applying configuration entries: +- [How to Use Configuration Entries](/consul/docs/agent/config-entries) +- [Custom Resource Definitions for Consul on Kubernetes](/consul/docs/k8s/crds) + +## Define multiple services in a single file + +The `services` block contains an array of `service` objects. It is a wrapper that enables you to define multiple services in the service definition and instruct Consul to expect more than just a single service configuration. As a result, you can register multiple services in a single `consul services register` command. Note that the `/agent/service/register` API endpoint does not support the `services` parameter. + +In the following example, the service definition configures an instance of the `redis` service tagged as `primary` running on port `6000`. It also configures an instance of the service tagged as `secondary` running on port `7000`. + + + + + +```hcl +services { + id = "red0" + name = "redis" + tags = [ + "primary" + ] + address = "" + port = 6000 + checks = [ + { + args = ["/bin/check_redis", "-p", "6000"] + interval = "5s" + timeout = "20s" + } + ] +} +services { + id = "red1" + name = "redis" + tags = [ + "delayed", + "secondary" + ] + address = "" + port = 7000 + checks = [ + { + args = ["/bin/check_redis", "-p", "7000"] + interval = "30s" + timeout = "60s" + } + ] +} + +``` + + + + + +```json +{ + "services": [ + { + "id": "red0", + "name": "redis", + "tags": [ + "primary" + ], + "address": "", + "port": 6000, + "checks": [ + { + "args": ["/bin/check_redis", "-p", "6000"], + "interval": "5s", + "timeout": "20s" + } + ] + }, + { + "id": "red1", + "name": "redis", + "tags": [ + "delayed", + "secondary" + ], + "address": "", + "port": 7000, + "checks": [ + { + "args": ["/bin/check_redis", "-p", "7000"], + "interval": "30s", + "timeout": "60s" + } + ] + }, + ... + ] +} +``` + + + + +## Modify anti-entropy synchronization + +By default, the Consul agent uses anti-entropy mechanisms to maintain information about services and service health, and synchronize local states with the Consul catalog. You can enable the `enable_tag_override` option in the service configuration, which lets external agents change the tags for a service. This can be useful in situations where an external monitoring service needs to be the source of truth for tag information. Refer [Anti-entropy](/consul/docs/architecture/anti-entropy) for details. + +Add the `enable_tag_override` option to the `service` block and set the value to `true`: + + + + +```hcl +service { + ## ... + enable_tag_override = true + ## ... +} +``` + +```json +"service": { + ## ... + "enable_tag_override": true, + ## ... +} +``` + + + +This configuration only applies to the locally registered service. Nodes that register the same service apply the `enable_tag_override` and other service configurations independently. The tags for a service registered on one node update are not affected by operations performed on services with the same name registered on other nodes. + +Refer to [`enable_tag_override`](/consul/docs/services/configuration/services-configuration-reference#enable_tag_override) for additional configuration information. + +## Services in service mesh environments +Defining services for service mesh environments on virtual machines and in Kubernetes requires a different workflow. + +### Define service mesh proxies +You can register services to function as service mesh or sidecar proxies so that they can facilitate communication between other services across your network. Refer to [Service Mesh Proxy Overview](/consul/docs/connect/registration) for additional information. + +### Define services in Kubernetes +You can enable the services running in Kubernetes and Consul to sync automatically. Doing so ensures that Kubernetes services are available to Consul agents and services in Consul can be available as first-class Kubernetes services. Refer to [Service Sync for Consul on Kubernetes](/consul/docs/k8s/service-sync) for details. \ No newline at end of file diff --git a/website/content/docs/services/usage/register-services-checks.mdx b/website/content/docs/services/usage/register-services-checks.mdx new file mode 100644 index 00000000000..07a3f20ad9c --- /dev/null +++ b/website/content/docs/services/usage/register-services-checks.mdx @@ -0,0 +1,68 @@ +--- +layout: docs +page_title: Register services and health checks +description: -> + 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. + +## 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. +- 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. + +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: + +```shell-session +$ consul agent -config-dir configs +``` + +Refer to [Starting the Consul Agent](/consul/docs/agent#starting-the-consul-agent) for additional information. + +## Reload an agent +Store your service definition file in the directory containing your Consul configuration files and either send a `SIGHUP` signal to the Consul agent service or run the `consul reload` command. Refer to [Consul Reload](/consul/commands/reload) for additional information. + +## Register using the CLI +Run the `consul services register` command and specify the service definition file to register the services and health checks defined in the file. In the following example, a service named `web` is registered: + +```shell-session +$ consul services register -name=web services.hcl +``` + +Refer to [Consul Agent Service Registration](/consul/commands/services/register) for additional information about using the command. + +## Register using the API + +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). + +The following example request registers the service defined in the `service.json` file. + +```shell-session +$ curl --request PUT --data @service.json http://localhost:8500/v1/agent/service/register +``` + +Refer to [Service - Agent HTTP API](/consul/api-docs/agent/service) for additional information about the `services` endpoint. + +### Register health checks +Send a `PUT` request to the `/agent/check/register` API endpoint to dynamically register a health check to the local Consul agent. The following example request registers a health check defined in a `payload.json` file. + +```shell-session +$ curl --request PUT --data @payload.json http://localhost:8500/v1/agent/check/register +``` + +Refer to [Check - Agent HTTP API](/consul/api-docs/agent/check) for additional information about the `check` endpoint. diff --git a/website/content/docs/troubleshoot/troubleshoot-services.mdx b/website/content/docs/troubleshoot/troubleshoot-services.mdx new file mode 100644 index 00000000000..92a66881475 --- /dev/null +++ b/website/content/docs/troubleshoot/troubleshoot-services.mdx @@ -0,0 +1,150 @@ +--- +layout: docs +page_title: Service-to-service troubleshooting overview +description: >- + Consul includes a built-in tool for troubleshooting communication between services in a service mesh. Learn how to use the `consul troubleshoot` command to validate communication between upstream and downstream Envoy proxies on VM and Kubernetes deployments. +--- + +# Service-to-service troubleshooting overview + +This topic provides an overview of Consul’s built-in service-to-service troubleshooting capabilities. When communication between an upstream service and a downstream service in a service mesh fails, you can run the `consul troubleshoot` command to initiate a series of automated validation tests. + +For more information, refer to the [`consul troubleshoot` CLI documentation](/consul/commands/troubleshoot) or the [`consul-k8s troubleshoot` CLI reference](/consul/docs/k8s/k8s-cli#troubleshoot). + +## Introduction + +When communication between upstream and downstream services in a service mesh fails, you can diagnose the cause manually with one or more of Consul’s built-in features, including [health check queries](/consul/docs/services/usage/checks), [the UI topology view](/consul/docs/connect/observability/ui-visualization), and [agent telemetry metrics](/consul/docs/agent/telemetry#metrics-reference). + +The `consul troubleshoot` command performs several checks in sequence that enable you to discover issues that impede service-to-service communication. The process systematically queries the [Envoy administration interface API](https://www.envoyproxy.io/docs/envoy/latest/operations/admin) and the Consul API to determine the cause of the communication failure. + +The troubleshooting command validates service-to-service communication by checking for the following common issues: + +- Upstream service does not exist +- One or both hosts are unhealthy +- A filter affects the upstream service +- The CA has expired mTLS certificates +- The services have expired mTLS certificates + +Consul outputs the results of these validation checks to the terminal along with suggested actions to resolve the service communication failure. When it detects rejected configurations or connection failures, Consul also outputs Envoy metrics for services. + +### Envoy proxies in a service mesh + +Consul validates communication in a service mesh by checking the Envoy proxies that are deployed as sidecars for the upstream and downstream services. As a result, troubleshooting requires that [Consul’s service mesh features are enabled](/consul/docs/connect/configuration). + +For more information about using Envoy proxies with Consul, refer to [Envoy proxy configuration for service mesh](/consul/docs/connect/proxies/envoy). + +## Requirements + +- Consul v1.15 or later. +- For Kubernetes, the `consul-k8s` CLI must be installed. + +### Technical constraints + +When troubleshooting service-to-service communication issues, be aware of the following constraints: + +- The troubleshooting tool does not check service intentions. For more information about intentions, including precedence and match order, refer to [service mesh intentions](/consul/docs/connect/intentions). +- The troubleshooting tool validates one direct connection between a downstream service and an upstream service. You must run the `consul troubleshoot` command with the Envoy ID for an individual upstream service. It does support validating multiple connections simultaneously. +- The troubleshooting tool only validates Envoy configurations for sidecar proxies. As a result, the troubleshooting tool does not validate Envoy configurations on upstream proxies such as mesh gateways and terminating gateways. + +## Usage + +Using the service-to-service troubleshooting tool is a two-step process: + +1. Find the identifier for the upstream service. +1. Use the upstream’s identifier to validate communication. + +In deployments without transparent proxies, the identifier is the _Envoy ID for the upstream service’s sidecar proxy_. If you use transparent proxies, the identifier is the _upstream service’s IP address_. For more information about using transparent proxies, refer to [Enable transparent proxy mode](/consul/docs/connect/transparent-proxy). + +### Troubleshoot on VMs + +To troubleshoot service-to-service communication issues in deployments that use VMs or bare-metal servers: + +1. Run the `consul troubleshoot upstreams` command to retrieve the upstream information for the service that is experiencing communication failures. Depending on your network’s configuration, the upstream information is either an Envoy ID or an IP address. + + ```shell-session + $ consul troubleshoot upstreams + ==> Upstreams (explicit upstreams only) (0) + ==> Upstreams IPs (transparent proxy only) (1) + [10.4.6.160 240.0.0.3] true map[backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul] + If you cannot find the upstream address or cluster for a transparent proxy upstream: + - Check intentions: Tproxy upstreams are configured based on intentions. Make sure you have configured intentions to allow traffic to your upstream. + - To check that the right cluster is being dialed, run a DNS lookup for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing from the upstream IPs, it means that your proxy may be misconfigured. + ``` + +1. Run the `consul troubleshoot proxy` command and specify the Envoy ID or IP address with the `-upstream-ip` flag to identify the proxy you want to perform the troubleshooting process on. The following example uses the upstream IP to validate communication with the upstream service `backend`: + + ```shell-session + $ consul troubleshoot proxy -upstream-ip 10.4.6.160 + ==> Validation + ✓ Certificates are valid + ✓ Envoy has 0 rejected configurations + ✓ Envoy has detected 0 connection failure(s) + ✓ Listener for upstream "backend" found + ✓ Route for upstream "backend" found + ✓ Cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ Healthy endpoints for cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ Cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ! No healthy endpoints for cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + -> Check that your upstream service is healthy and running + -> Check that your upstream service is registered with Consul + -> Check that the upstream proxy is healthy and running + -> If you are explicitly configuring upstreams, ensure the name of the upstream is correct + ``` + +In the example output, troubleshooting upstream communication reveals that the `backend` service has two service instances running in datacenter `dc1`. One of the services is healthy, but Consul cannot detect healthy endpoints for the second service instance. This information appears in the following lines of the example: + +```text hideClipboard + ✓ Cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ Healthy endpoints for cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ Cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ! No healthy endpoints for cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found +``` + +The output from the troubleshooting process identifies service instances according to their [Consul DNS address](/consul/docs/services/discovery/dns-static-lookups#standard-lookup). Use the DNS information for failing services to diagnose the specific issues affecting the service instance. + +For more information, refer to the [`consul troubleshoot` CLI documentation](/consul/commands/troubleshoot). + +### Troubleshoot on Kubernetes + +To troubleshoot service-to-service communication issues in deployments that use Kubernetes, retrieve the upstream information for the pod that is experiencing communication failures and use the upstream information to identify the proxy you want to perform the troubleshooting process on. + +1. Run the `consul-k8s troubleshoot upstreams` command and specify the pod ID with the `-pod` flag to retrieve upstream information. Depending on your network’s configuration, the upstream information is either an Envoy ID or an IP address. The following example displays all transparent proxy upstreams in Consul service mesh from the given pod. + + ```shell-session + $ consul-k8s troubleshoot upstreams -pod frontend-767ccfc8f9-6f6gx + ==> Upstreams (explicit upstreams only) (0) + ==> Upstreams IPs (transparent proxy only) (1) + [10.4.6.160 240.0.0.3] true map[backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul] + If you cannot find the upstream address or cluster for a transparent proxy upstream: + - Check intentions: Tproxy upstreams are configured based on intentions. Make sure you have configured intentions to allow traffic to your upstream. + - To check that the right cluster is being dialed, run a DNS lookup for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing from the upstream IPs, it means that your proxy may be misconfigured. + ``` + +1. Run the `consul-k8s troubleshoot proxy` command and specify the pod ID and upstream IP address to identify the proxy you want to troubleshoot. The following example uses the upstream IP to validate communication with the upstream service `backend`: + + ```shell-session + $ consul-k8s troubleshoot proxy -pod frontend-767ccfc8f9-6f6gx -upstream-ip 10.4.6.160 + ==> Validation + ✓ certificates are valid + ✓ Envoy has 0 rejected configurations + ✓ Envoy has detected 0 connection failure(s) + ✓ listener for upstream "backend" found + ✓ route for upstream "backend" found + ✓ cluster "backend.default.dc1.internal..consul" for upstream "backend" found + ✓ healthy endpoints for cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ cluster "backend2.default.dc1.internal..consul" for upstream "backend" found + ! no healthy endpoints for cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ``` + +In the example output, troubleshooting upstream communication reveals that the `backend` service has two clusters in datacenter `dc1`. One of the clusters returns healthy endpoints, but Consul cannot detect healthy endpoints for the second cluster. This information appears in the following lines of the example: + + ```text hideClipboard + ✓ cluster "backend.default.dc1.internal..consul" for upstream "backend" found + ✓ healthy endpoints for cluster "backend.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found + ✓ cluster "backend2.default.dc1.internal..consul" for upstream "backend" found + ! no healthy endpoints for cluster "backend2.default.dc1.internal.e08fa6d6-e91e-dfe0-f6e1-ba097a828e31.consul" for upstream "backend" found +``` + +The output from the troubleshooting process identifies service instances according to their [Consul DNS address](/consul/docs/k8s/dns). Use the DNS information for failing services to diagnose the specific issues affecting the service instance. + +For more information, refer to the [`consul-k8s troubleshoot` CLI reference](/consul/docs/k8s/k8s-cli#troubleshoot). \ No newline at end of file diff --git a/website/content/docs/upgrading/instructions/index.mdx b/website/content/docs/upgrading/instructions/index.mdx index 8c2b3c08050..1ea7effb735 100644 --- a/website/content/docs/upgrading/instructions/index.mdx +++ b/website/content/docs/upgrading/instructions/index.mdx @@ -10,45 +10,43 @@ description: >- This document is intended to help users who find themselves many versions behind to upgrade safely. -## Upgrade Path - -Our recommended upgrade path is to move through the following sequence of versions: - -- 0.8.5 (final 0.8.x) -- 1.2.4 (final 1.2.x) -- 1.6.10 (final 1.6.x) -- 1.8.19 (final 1.8.x) -- 1.10.12 (final 1.10.x) -- Latest 1.12.x -- Latest 1.13.x ([at least 1.13.1](/consul/docs/upgrading/upgrade-specific#service-mesh-compatibility)) -- Latest 1.14.x - -## Getting Started - -To get instructions for your upgrade, follow the instructions given below for -your _currently installed_ release series until you are on the latest current version. -The upgrade guides will mention notable changes and link to relevant changelogs – -we recommend reviewing the changelog for versions between the one you are on and the -one you are upgrading to at each step to familiarize yourself with changes. - -Select your _currently installed_ release series: -- 1.13.x: work upwards from [1.14 upgrade notes](/consul/docs/upgrading/upgrade-specific#consul-1-14-x) -- 1.12.x: work upwards from [1.13 upgrade notes](/consul/docs/upgrading/upgrade-specific#consul-1-13-x) -- 1.11.x: work upwards from [1.12 upgrade notes](/consul/docs/upgrading/upgrade-specific#consul-1-12-0) -- 1.10.x: work upwards from [1.11 upgrade notes](/consul/docs/upgrading/upgrade-specific#consul-1-11-0) -- [1.9.x](/consul/docs/upgrading/instructions/upgrade-to-1-10-x) -- [1.8.x](/consul/docs/upgrading/instructions/upgrade-to-1-10-x) -- [1.7.x](/consul/docs/upgrading/instructions/upgrade-to-1-8-x) -- [1.6.x](/consul/docs/upgrading/instructions/upgrade-to-1-8-x) -- [1.5.x](/consul/docs/upgrading/instructions/upgrade-to-1-6-x) -- [1.4.x](/consul/docs/upgrading/instructions/upgrade-to-1-6-x) -- [1.3.x](/consul/docs/upgrading/instructions/upgrade-to-1-6-x) -- [1.2.x](/consul/docs/upgrading/instructions/upgrade-to-1-6-x) -- [1.1.x](/consul/docs/upgrading/instructions/upgrade-to-1-2-x) -- [1.0.x](/consul/docs/upgrading/instructions/upgrade-to-1-2-x) -- [0.9.x](/consul/docs/upgrading/instructions/upgrade-to-1-2-x) -- [0.8.x](/consul/docs/upgrading/instructions/upgrade-to-1-2-x) - -If you are using <= 0.7.x, please contact support for assistance: -- OSS users without paid support plans can request help in our [Community Forum](https://discuss.hashicorp.com/c/consul/29) -- Enterprise and OSS users with paid support plans can contact [HashiCorp Support](https://support.hashicorp.com/) +## General Upgrade Path + +Each upgrade should jump at most 2 major versions, except where +[dedicated instructions](#dedicated-instructions-for-specific-upgrade-paths) +are provided for a larger jump between specific versions. +If your upgrade path has no applicable [dedicated instructions](#dedicated-instructions-for-specific-upgrade-paths), +review the [version-specific upgrade details](/consul/docs/upgrading/upgrade-specific) +to plan your upgrade, starting from the next version and working +upwards to your target version. + +For example, to upgrade from Consul 1.12 to Consul 1.15: + +1. Upgrade to Consul 1.14 as an intermediate step. + To plan, review the upgrade details for + [1.13](/consul/docs/upgrading/upgrade-specific#consul-1-13-x) and + [1.14](/consul/docs/upgrading/upgrade-specific#consul-1-14-x). +1. Upgrade to Consul 1.15. + To plan, review the upgrade details for + [1.15](/consul/docs/upgrading/upgrade-specific#consul-1-15-x). + +## Dedicated Instructions for Specific Upgrade Paths + +The following table provides links to dedicated instructions +for directly upgrading from a version in the starting range +to a destination version. + +| Starting Version Range | Destination Version | Upgrade Instructions | +| ---------------------- | ------------------- | -------------------- | +| 1.8.0 - 1.9.17 | 1.10.12 | Refer to [upgrading to latest 1.10.x](/consul/docs/upgrading/instructions/upgrade-to-1-10-x) | +| 1.6.9 - 1.8.18 | 1.8.19 | Refer to [upgrading to latest 1.8.x](/consul/docs/upgrading/instructions/upgrade-to-1-8-x) | +| 1.2.4 - 1.6.9 | 1.6.10 | Refer to [upgrading to latest 1.6.x](/consul/docs/upgrading/instructions/upgrade-to-1-6-x) | +| 0.8.5 - 1.2.3 | 1.2.4 | Refer to [upgrading to latest 1.2.x](/consul/docs/upgrading/instructions/upgrade-to-1-2-x) | + +For example, to upgrade from Consul 1.3.1 to latest 1.12: +1. Upgrade to Consul 1.6.10 using the dedicated instructions. +1. Upgrade to Consul 1.8.19 using the dedicated instructions. +1. Upgrade to Consul 1.10.12 using the dedicated instructions. +1. Upgrade to latest Consul 1.12.x after consulting the + [version-specific upgrade details](/consul/docs/upgrading/upgrade-specific) + for 1.11 and 1.12. \ No newline at end of file diff --git a/website/content/docs/upgrading/instructions/upgrade-to-1-6-x.mdx b/website/content/docs/upgrading/instructions/upgrade-to-1-6-x.mdx index c39aed602c0..d88560260eb 100644 --- a/website/content/docs/upgrading/instructions/upgrade-to-1-6-x.mdx +++ b/website/content/docs/upgrading/instructions/upgrade-to-1-6-x.mdx @@ -18,8 +18,8 @@ as part of this upgrade. The 1.6.x series is the last series that had support fo ACL tokens, so this migration _must_ happen before upgrading past the 1.6.x release series. Here is some documentation that may prove useful for reference during this upgrade process: -- [ACL System in Legacy Mode](/consul/docs/security/acl/acl-legacy) - You can find - information about legacy configuration options and differences between modes here. +- [Upgrading Legacy ACL tokens](/consul/tutorials/security-operations/access-control-token-migration) - You can find + information about upgrading legacy ACL tokens and differences between modes here. - [Configuration](/consul/docs/agent/config) - You can find more details around legacy ACL and new ACL configuration options here. Legacy ACL config options will be listed as deprecates as of 1.4.0. diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index 07d456ad816..1c9a7384910 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -16,6 +16,22 @@ upgrade flow. ## Consul 1.15.x +#### Service mesh known issue + +To err on the side of caution, +service mesh deployments should not upgrade to Consul v1.15 at this time. + +We are currently investigating a not-consistently-reproducible issue that can cause +some service instances to lose their ability to communicate in the mesh after +[72 hours (LeafCertTTL)](/consul/docs/connect/ca/consul#leafcertttl) +due to a problem with leaf certificate rotation. +We will update this section with more information as our investigation continues, +including the target availability for a fix. + +If you are already operating Consul v1.15, refer to discussion of this issue on +[GH-16779](https://github.com/hashicorp/consul/issues/16779) +for potential workarounds and to share your observations. + #### Removing configuration options The `connect.enable_serverless_plugin` configuration option was removed. Lambda integration is now enabled by default. @@ -499,6 +515,14 @@ to Consul 1.11.11 or later to avoid the breaking nature of that change. ### Licensing Changes +You can only upgrade to Consul Enterprise 1.10 from the following Enterprise versions: +- 1.8 release series: 1.8.13+ +- 1.9 release series: 1.9.7+ + +Other versions of Consul Enterprise are not forward compatible with v1.10 and will +cause issues during the upgrade that could result in agents failing to start due to +[changes in the way we manage licenses](/consul/docs/enterprise/license/faq). + Consul Enterprise 1.10 has removed temporary licensing capabilities from the binaries found on https://releases.hashicorp.com. Servers will no longer load a license previously set through the CLI or API. Instead the license must be present in the server's configuration @@ -990,11 +1014,9 @@ config files loaded by Consul, even when using the [`-config-file`](/consul/docs/agent/config/cli-flags#_config_file) argument to specify a file directly. -#### Service Definition Parameter Case changed +#### Use Snake Case for Service Definition Parameters -All config file formats now require snake_case fields, so all CamelCased parameter -names should be changed before upgrading. -See [Service Definition Parameter Case](/consul/docs/discovery/services#service-definition-parameter-case) documentation for details. +Snake case, which is a convention that uses underscores between words in a configuration key, is required for all configuration file formats. Change any camel cased parameter to snake case equivalents before upgrading. #### Deprecated Options Have Been Removed diff --git a/website/data/api-docs-nav-data.json b/website/data/api-docs-nav-data.json index fb1dd87421b..66d8fa9a949 100644 --- a/website/data/api-docs-nav-data.json +++ b/website/data/api-docs-nav-data.json @@ -165,6 +165,10 @@ { "title": "Segment", "path": "operator/segment" + }, + { + "title": "Usage", + "path": "operator/usage" } ] }, diff --git a/website/data/commands-nav-data.json b/website/data/commands-nav-data.json index 62ed01a4004..ee491e9dfa7 100644 --- a/website/data/commands-nav-data.json +++ b/website/data/commands-nav-data.json @@ -425,6 +425,10 @@ { "title": "raft", "path": "operator/raft" + }, + { + "title": "usage", + "path": "operator/usage" } ] }, @@ -528,6 +532,23 @@ } ] }, + { + "title": "troubleshoot", + "routes": [ + { + "title": "Overview", + "path": "troubleshoot" + }, + { + "title": "upstreams", + "path": "troubleshoot/upstreams" + }, + { + "title": "proxy", + "path": "troubleshoot/proxy" + } + ] + }, { "title": "validate", "path": "validate" diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 16bf17d84ba..ec1afef6f1f 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -145,6 +145,10 @@ { "title": "Consul", "routes": [ + { + "title": "v1.15.x", + "path": "release-notes/consul/v1_15_x" + }, { "title": "v1.14.x", "path": "release-notes/consul/v1_14_x" @@ -174,6 +178,10 @@ { "title": "Consul K8s", "routes": [ + { + "title": "v1.1.x", + "path": "release-notes/consul-k8s/v1_1_x" + }, { "title": "v1.0.x", "path": "release-notes/consul-k8s/v1_0_x" @@ -193,7 +201,7 @@ ] }, { - "title": "Consul API Gateway", + "title": "API Gateway for Kubernetes", "routes": [ { "title": "v0.5.x", @@ -303,19 +311,70 @@ "divider": true }, { - "title": "Service Discovery", + "title": "Services", "routes": [ { - "title": "Register Services - Service Definitions", - "path": "discovery/services" + "title": "Overview", + "path": "services/services" }, { - "title": "Find Services - DNS Interface", - "path": "discovery/dns" + "title": "Usage", + "routes": [ + { + "title": "Define services", + "path": "services/usage/define-services" + }, + { + "title": "Define health checks", + "path": "services/usage/checks" + }, + { + "title": "Register services and health checks", + "path": "services/usage/register-services-checks" + } + ] + }, + { + "title": "Discover services with DNS", + "routes": [ + { + "title": "Overview", + "path": "services/discovery/dns-overview" + }, + { + "title": "Configure DNS behavior", + "path": "services/discovery/dns-configuration" + }, + { + "title": "Perform static DNS lookups", + "path": "services/discovery/dns-static-lookups" + }, + { + "title": "Enable dynamic DNS lookups", + "path": "services/discovery/dns-dynamic-lookups" + } + ] }, { - "title": "Monitor Services - Check Definitions", - "path": "discovery/checks" + "title": "Configuration", + "routes": [ + { + "title": "Overview", + "path": "services/configuration/services-configuration-overview" + }, + { + "title": "Services", + "path": "services/configuration/services-configuration-reference" + }, + { + "title": "Health checks", + "path": "services/configuration/checks-configuration-reference" + }, + { + "title": "Service defaults", + "href": "connect/config-entries/service-defaults" + } + ] } ] }, @@ -341,6 +400,42 @@ "title": "Overview", "path": "connect/config-entries" }, + { + "title": "API Gateway", + "href": "/consul/docs/connect/gateways/api-gateway/configuration/api-gateway", + "badge": { + "text": "BETA", + "type": "outlined", + "color": "neutral" + } + }, + { + "title": "HTTP Route", + "href": "/consul/docs/connect/gateways/api-gateway/configuration/http-route", + "badge": { + "text": "BETA", + "type": "outlined", + "color": "neutral" + } + }, + { + "title": "TCP Route", + "href": "/consul/docs/connect/gateways/api-gateway/configuration/tcp-route", + "badge": { + "text": "BETA", + "type": "outlined", + "color": "neutral" + } + }, + { + "title": "Inline Certificate", + "href": "/consul/docs/connect/gateways/api-gateway/configuration/inline-certificate", + "badge": { + "text": "BETA", + "type": "outlined", + "color": "neutral" + } + }, { "title": "Ingress Gateway", "path": "connect/config-entries/ingress-gateway" @@ -394,6 +489,28 @@ "title": "Envoy", "path": "connect/proxies/envoy" }, + { + "title": "Envoy Extensions", + "routes": [ + { + "title": "Overview", + "path": "connect/proxies/envoy-extensions" + }, + { + "title": "Usage", + "routes": [ + { + "title": "Run Lua scripts in Envoy proxies", + "path": "connect/proxies/envoy-extensions/usage/lua" + }, + { + "title": "Invoke Lambda functions in Envoy proxies", + "path": "connect/proxies/envoy-extensions/usage/lambda" + } + ] + } + ] + }, { "title": "Built-in Proxy", "path": "connect/proxies/built-in" @@ -401,11 +518,6 @@ { "title": "Proxy Integration", "path": "connect/proxies/integrate" - }, - { - "title": "Managed (Deprecated)", - "path": "connect/proxies/managed-deprecated", - "hidden": true } ] }, @@ -427,12 +539,25 @@ ] }, { - "title": "Service-to-service permissions - Intentions", - "path": "connect/intentions" - }, - { - "title": "Service-to-service permissions - Intentions (Legacy Mode)", - "path": "connect/intentions-legacy" + "title": "Service intentions", + "routes": [ + { + "title": "Overview", + "path": "connect/intentions" + }, + { + "title": "Create and manage service intentions", + "path": "connect/intentions/create-manage-intentions" + }, + { + "title": "Service intentions legacy mode", + "path": "connect/intentions/legacy" + }, + { + "title": "Configuration", + "href": "/consul/docs/connect/config-entries/service-intentions" + } + ] }, { "title": "Transparent Proxy", @@ -483,6 +608,45 @@ "title": "Overview", "path": "connect/gateways" }, + { + "title": "API Gateways", + "badge": { + "text": "BETA", + "type": "outlined", + "color": "neutral" + }, + "routes": [ + { + "title": "Overview", + "path": "connect/gateways/api-gateway" + }, + { + "title": "Usage", + "path": "connect/gateways/api-gateway/usage" + }, + { + "title": "Configuration", + "routes": [ + { + "title": "API Gateway", + "path": "connect/gateways/api-gateway/configuration/api-gateway" + }, + { + "title": "HTTP Route", + "path": "connect/gateways/api-gateway/configuration/http-route" + }, + { + "title": "TCP Route", + "path": "connect/gateways/api-gateway/configuration/tcp-route" + }, + { + "title": "Inline Certificate", + "path": "connect/gateways/api-gateway/configuration/inline-certificate" + } + ] + } + ] + }, { "title": "Mesh Gateways", "routes": [ @@ -505,10 +669,6 @@ { "title": "Enabling Peering Control Plane Traffic", "path": "connect/gateways/mesh-gateway/peering-via-mesh-gateways" - }, - { - "title": "Enabling Service-to-service Traffic Across Peered Clusters", - "path": "connect/gateways/mesh-gateway/service-to-service-traffic-peers" } ] }, @@ -526,16 +686,29 @@ "title": "Cluster Peering", "routes": [ { - "title": "What is Cluster Peering?", + "title": "Overview", "path": "connect/cluster-peering" }, { - "title": "Create and Manage Peering Connections", - "path": "connect/cluster-peering/create-manage-peering" + "title": "Technical Specifications", + "path": "connect/cluster-peering/tech-specs" }, { - "title": "Cluster Peering on Kubernetes", - "path": "connect/cluster-peering/k8s" + "title": "Usage", + "routes": [ + { + "title": "Establish Cluster Peering Connections", + "path": "connect/cluster-peering/usage/establish-cluster-peering" + }, + { + "title": "Manage Cluster Peering Connections", + "path": "connect/cluster-peering/usage/manage-connections" + }, + { + "title": "Manage L7 Traffic With Cluster Peering", + "path": "connect/cluster-peering/usage/peering-traffic-management" + } + ] } ] }, @@ -734,6 +907,23 @@ } ] }, + { + "title": "Limit Traffic Rates", + "routes": [ + { + "title": "Overview", + "path": "agent/limits" + }, + { + "title": "Initialize Rate Limit Settings", + "path": "agent/limits/init-rate-limits" + }, + { + "title": "Set Global Traffic Rate Limits", + "path": "agent/limits/set-global-traffic-rate-limits" + } + ] + }, { "title": "Configuration Entries", "path": "agent/config-entries" @@ -750,6 +940,27 @@ "title": "RPC", "path": "agent/rpc", "hidden": true + }, + { + "title": "Experimental WAL LogStore", + "routes": [ + { + "title": "Overview", + "path": "agent/wal-logstore" + }, + { + "title": "Enable WAL LogStore backend", + "path": "agent/wal-logstore/enable" + }, + { + "title": "Monitor Raft metrics and logs for WAL", + "path": "agent/wal-logstore/monitoring" + }, + { + "title": "Revert to BoltDB", + "path": "agent/wal-logstore/revert-to-boltdb" + } + ] } ] }, @@ -781,6 +992,10 @@ { "title": "Troubleshoot", "routes": [ + { + "title": "Service-to-Service Troubleshooting", + "path": "troubleshoot/troubleshoot-services" + }, { "title": "Common Error Messages", "path": "troubleshoot/common-errors" @@ -805,7 +1020,6 @@ "title": "Architecture", "path": "k8s/architecture" }, - { "title": "Installation", "routes": [ @@ -963,6 +1177,32 @@ "title": "Admin Partitions", "href": "/docs/enterprise/admin-partitions" }, + { + "title": "Cluster Peering", + "routes": [ + { + "title": "Technical Specifications", + "path": "k8s/connect/cluster-peering/tech-specs" + }, + { + "title": "Usage", + "routes": [ + { + "title": "Establish Cluster Peering Connections", + "path": "k8s/connect/cluster-peering/usage/establish-peering" + }, + { + "title": "Manage Cluster Peering Connections", + "path": "k8s/connect/cluster-peering/usage/manage-peering" + }, + { + "title": "Manage L7 Traffic With Cluster Peering", + "path": "k8s/connect/cluster-peering/usage/l7-traffic" + } + ] + } + ] + }, { "title": "Transparent Proxy", "href": "/docs/connect/transparent-proxy" @@ -1293,7 +1533,7 @@ "divider": true }, { - "title": "Consul API Gateway", + "title": "API Gateway for Kubernetes", "routes": [ { "title": "Overview", @@ -1315,7 +1555,7 @@ "title": "Usage", "routes": [ { - "title": "Basic Usage", + "title": "Deploy", "path": "api-gateway/usage/usage" }, { diff --git a/website/public/img/cluster-peering-diagram.png b/website/public/img/cluster-peering-diagram.png new file mode 100644 index 00000000000..d00aa3eb0f3 Binary files /dev/null and b/website/public/img/cluster-peering-diagram.png differ diff --git a/website/public/img/consul-connect/consul-service-mesh-intentions-overview.svg b/website/public/img/consul-connect/consul-service-mesh-intentions-overview.svg new file mode 100644 index 00000000000..cdd7ea0ad6e --- /dev/null +++ b/website/public/img/consul-connect/consul-service-mesh-intentions-overview.svg @@ -0,0 +1 @@ + diff --git a/website/redirects.js b/website/redirects.js index 8e31b56f340..fb9ec442c66 100644 --- a/website/redirects.js +++ b/website/redirects.js @@ -4,4 +4,32 @@ // modify or delete existing redirects without first verifying internally. // Next.js redirect documentation: https://nextjs.org/docs/api-reference/next.config.js/redirects -module.exports = [] +module.exports = [ + { + source: '/consul/docs/connect/cluster-peering/create-manage-peering', + destination: + '/consul/docs/connect/cluster-peering/usage/establish-cluster-peering', + permanent: true, + }, + { + source: '/consul/docs/connect/cluster-peering/usage/establish-peering', + destination: + '/consul/docs/connect/cluster-peering/usage/establish-cluster-peering', + permanent: true, + }, + { + source: '/consul/docs/connect/cluster-peering/k8s', + destination: '/consul/docs/k8s/connect/cluster-peering/tech-specs', + permanent: true, + }, + { + source: '/consul/docs/connect/intentions#intention-management-permissions', + destination: `/consul/docs/connect/intentions/create-manage-intentions#acl-requirements`, + permanent: true, + }, + { + source: '/consul/docs/connect/intentions#intention-basics', + destination: `/consul/docs/connect/intentions`, + permanent: true, + }, +]