diff --git a/.changelog/11742.txt b/.changelog/11742.txt new file mode 100644 index 0000000000..6c6d4c2498 --- /dev/null +++ b/.changelog/11742.txt @@ -0,0 +1,3 @@ +```release-note:improvement +api: Add filtering support to Catalog's List Services (v1/catalog/services) +``` diff --git a/.changelog/13493.txt b/.changelog/13493.txt new file mode 100644 index 0000000000..9c3eec605d --- /dev/null +++ b/.changelog/13493.txt @@ -0,0 +1,3 @@ +```release-note:bug +cli: Fix Consul kv CLI 'GET' flags 'keys' and 'recurse' to be set together +``` diff --git a/.changelog/13613.txt b/.changelog/13613.txt new file mode 100644 index 0000000000..5696c646b0 --- /dev/null +++ b/.changelog/13613.txt @@ -0,0 +1,4 @@ +```release-note:feature +connect: Adds a new `destination` field to the `service-default` config entry that allows routing egress traffic +through a terminating gateway in transparent proxy mode without modifying the catalog. +``` diff --git a/.changelog/13958.txt b/.changelog/13958.txt new file mode 100644 index 0000000000..a64a0026b4 --- /dev/null +++ b/.changelog/13958.txt @@ -0,0 +1,4 @@ +```release-note:bug +connect: Ingress gateways with a wildcard service entry should no longer pick up non-connect services as upstreams. +connect: Terminating gateways with a wildcard service entry should no longer pick up connect services as upstreams. +``` diff --git a/.changelog/14021.txt b/.changelog/14021.txt new file mode 100644 index 0000000000..af10a467b9 --- /dev/null +++ b/.changelog/14021.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fixes an issue where client side validation errors were not showing in certain areas +``` diff --git a/.changelog/14034.txt b/.changelog/14034.txt new file mode 100644 index 0000000000..216c5406a3 --- /dev/null +++ b/.changelog/14034.txt @@ -0,0 +1,3 @@ +```release-note:bug +cli: When launching a sidecar proxy with `consul connect envoy` or `consul connect proxy`, the `-sidecar-for` service ID argument is now treated as case-insensitive. +``` diff --git a/.changelog/14081.txt b/.changelog/14081.txt new file mode 100644 index 0000000000..ccb03ffb03 --- /dev/null +++ b/.changelog/14081.txt @@ -0,0 +1,3 @@ +```release-note:bug +agent: Fixes an issue where an agent that fails to start due to bad addresses won't clean up any existing listeners +``` diff --git a/.changelog/14119.txt b/.changelog/14119.txt new file mode 100644 index 0000000000..f0958361bd --- /dev/null +++ b/.changelog/14119.txt @@ -0,0 +1,3 @@ +```release-note:bug +connect: Fixed some spurious issues during peering establishment when a follower is dialed +``` diff --git a/.changelog/14149.txt b/.changelog/14149.txt new file mode 100644 index 0000000000..726861f5a2 --- /dev/null +++ b/.changelog/14149.txt @@ -0,0 +1,3 @@ +```release-note:bug +agent: Fixed a compatibility issue when restoring snapshots from pre-1.13.0 versions of Consul [[GH-14107](https://github.com/hashicorp/consul/issues/14107)] +``` \ No newline at end of file diff --git a/.changelog/14161.txt b/.changelog/14161.txt new file mode 100644 index 0000000000..2926ffbe9b --- /dev/null +++ b/.changelog/14161.txt @@ -0,0 +1,3 @@ +```release-note:improvement +metrics: add labels of segment, partition, network area, network (lan or wan) to serf and memberlist metrics +``` diff --git a/.changelog/14162.txt b/.changelog/14162.txt new file mode 100644 index 0000000000..d1ab0e08d3 --- /dev/null +++ b/.changelog/14162.txt @@ -0,0 +1,5 @@ +```release-note:improvement +config-entry: Validate that service-resolver `Failover`s and `Redirect`s only +specify `Partition` and `Namespace` on Consul Enterprise. This prevents scenarios +where OSS Consul would save service-resolvers that require Consul Enterprise. +``` diff --git a/.changelog/14178.txt b/.changelog/14178.txt new file mode 100644 index 0000000000..99f87bbfc3 --- /dev/null +++ b/.changelog/14178.txt @@ -0,0 +1,4 @@ +```release-note:breaking-change +xds: Convert service mesh failover to use Envoy's aggregate clusters. This +changes the names of some [Envoy dynamic HTTP metrics](https://www.envoyproxy.io/docs/envoy/latest/configuration/upstream/cluster_manager/cluster_stats#dynamic-http-statistics). +``` diff --git a/.changelog/14233.txt b/.changelog/14233.txt new file mode 100644 index 0000000000..5a2c6dee18 --- /dev/null +++ b/.changelog/14233.txt @@ -0,0 +1,3 @@ +```release-note:bugfix +rpc: Adds max jitter to client deadlines to prevent i/o deadline errors on blocking queries +``` diff --git a/.changelog/14269.txt b/.changelog/14269.txt new file mode 100644 index 0000000000..29eec6d5da --- /dev/null +++ b/.changelog/14269.txt @@ -0,0 +1,3 @@ +```release-note:bugfix +connect: Fix issue where `auto_config` and `auto_encrypt` could unintentionally enable TLS for gRPC xDS connections. +``` \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index af1a2f5c66..105666c661 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,6 +28,10 @@ references: - "1.21.4" - "1.22.2" - "1.23.0" + nomad-versions: &supported_nomad_versions + - &default_nomad_version "1.3.3" + - "1.2.10" + - "1.1.16" images: # When updating the Go version, remember to also update the versions in the # workflows section for go-test-lib jobs. @@ -105,15 +109,18 @@ commands: type: env_var_name default: ROLE_ARN steps: + # Only run the assume-role command for the main repo. The AWS credentials aren't available for forks. - run: | - export AWS_ACCESS_KEY_ID="${<< parameters.access-key >>}" - export AWS_SECRET_ACCESS_KEY="${<< parameters.secret-key >>}" - export ROLE_ARN="${<< parameters.role-arn >>}" - # assume role has duration of 15 min (the minimum allowed) - CREDENTIALS="$(aws sts assume-role --duration-seconds 900 --role-arn ${ROLE_ARN} --role-session-name build-${CIRCLE_SHA1} | jq '.Credentials')" - echo "export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')" >> $BASH_ENV - echo "export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')" >> $BASH_ENV - echo "export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')" >> $BASH_ENV + if [[ "${CIRCLE_BRANCH%%/*}/" != "pull/" ]]; then + export AWS_ACCESS_KEY_ID="${<< parameters.access-key >>}" + export AWS_SECRET_ACCESS_KEY="${<< parameters.secret-key >>}" + export ROLE_ARN="${<< parameters.role-arn >>}" + # assume role has duration of 15 min (the minimum allowed) + CREDENTIALS="$(aws sts assume-role --duration-seconds 900 --role-arn ${ROLE_ARN} --role-session-name build-${CIRCLE_SHA1} | jq '.Credentials')" + echo "export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')" >> $BASH_ENV + echo "export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')" >> $BASH_ENV + echo "export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')" >> $BASH_ENV + fi run-go-test-full: parameters: @@ -560,17 +567,20 @@ jobs: - run: make ci.dev-docker - run: *notify-slack-failure - # Nomad 0.8 builds on go1.10 - # Run integration tests on nomad/v0.8.7 - nomad-integration-0_8: + nomad-integration-test: &NOMAD_TESTS docker: - - image: docker.mirror.hashicorp.services/cimg/go:1.10 + - image: docker.mirror.hashicorp.services/cimg/go:1.19 + parameters: + nomad-version: + type: enum + enum: *supported_nomad_versions + default: *default_nomad_version environment: <<: *ENVIRONMENT NOMAD_WORKING_DIR: &NOMAD_WORKING_DIR /home/circleci/go/src/github.com/hashicorp/nomad - NOMAD_VERSION: v0.8.7 + NOMAD_VERSION: << parameters.nomad-version >> steps: &NOMAD_INTEGRATION_TEST_STEPS - - run: git clone https://github.com/hashicorp/nomad.git --branch ${NOMAD_VERSION} ${NOMAD_WORKING_DIR} + - run: git clone https://github.com/hashicorp/nomad.git --branch v${NOMAD_VERSION} ${NOMAD_WORKING_DIR} # get consul binary - attach_workspace: @@ -601,16 +611,6 @@ jobs: path: *TEST_RESULTS_DIR - run: *notify-slack-failure - # run integration tests on nomad/main - nomad-integration-main: - docker: - - image: docker.mirror.hashicorp.services/cimg/go:1.18 - environment: - <<: *ENVIRONMENT - NOMAD_WORKING_DIR: /home/circleci/go/src/github.com/hashicorp/nomad - NOMAD_VERSION: main - steps: *NOMAD_INTEGRATION_TEST_STEPS - # build frontend yarn cache frontend-cache: docker: @@ -1117,12 +1117,12 @@ workflows: - dev-upload-docker: <<: *dev-upload context: consul-ci - - nomad-integration-main: - requires: - - dev-build - - nomad-integration-0_8: + - nomad-integration-test: requires: - dev-build + matrix: + parameters: + nomad-version: *supported_nomad_versions - envoy-integration-test: requires: - dev-build diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc2506abf8..45891047dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: branches: # Push events on the main branch - main + - release/** env: PKG_NAME: consul @@ -409,8 +410,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - arch: ["i386", "x86_64", "armv7hl", "aarch64"] - # fail-fast: true + # TODO(eculver): re-enable when there is a smaller verification container available + arch: ["i386", "x86_64"] #, "armv7hl", "aarch64"] env: version: ${{ needs.get-product-version.outputs.product-version }} diff --git a/.release/ci.hcl b/.release/ci.hcl index ceb11f7595..9bbe6e7068 100644 --- a/.release/ci.hcl +++ b/.release/ci.hcl @@ -11,10 +11,7 @@ project "consul" { repository = "consul" release_branches = [ "main", - "release/1.9.x", - "release/1.10.x", - "release/1.11.x", - "release/1.12.x", + "release/**", ] } } @@ -265,3 +262,16 @@ event "promote-production-packaging" { on = "always" } } + +event "post-publish-website" { + depends = ["promote-production-packaging"] + action "post-publish-website" { + organization = "hashicorp" + repository = "crt-workflows-common" + workflow = "post-publish-website" + } + + notification { + on = "always" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index d77193026b..94217c74a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,91 @@ +## 1.13.1 (August 11, 2022) + +BUG FIXES: + +* agent: Fixed a compatibility issue when restoring snapshots from pre-1.13.0 versions of Consul [[GH-14107](https://github.com/hashicorp/consul/issues/14107)] [[GH-14149](https://github.com/hashicorp/consul/issues/14149)] +* connect: Fixed some spurious issues during peering establishment when a follower is dialed [[GH-14119](https://github.com/hashicorp/consul/issues/14119)] + +## 1.12.4 (August 11, 2022) + +BUG FIXES: + +* cli: when `acl token read` is used with the `-self` and `-expanded` flags, return an error instead of panicking [[GH-13787](https://github.com/hashicorp/consul/issues/13787)] +* connect: Fixed a goroutine/memory leak that would occur when using the ingress gateway. [[GH-13847](https://github.com/hashicorp/consul/issues/13847)] +* connect: Ingress gateways with a wildcard service entry should no longer pick up non-connect services as upstreams. +connect: Terminating gateways with a wildcard service entry should no longer pick up connect services as upstreams. [[GH-13958](https://github.com/hashicorp/consul/issues/13958)] +* ui: Fixes an issue where client side validation errors were not showing in certain areas [[GH-14021](https://github.com/hashicorp/consul/issues/14021)] + +## 1.11.8 (August 11, 2022) + +BUG FIXES: + +* connect: Fixed a goroutine/memory leak that would occur when using the ingress gateway. [[GH-13847](https://github.com/hashicorp/consul/issues/13847)] +* connect: Ingress gateways with a wildcard service entry should no longer pick up non-connect services as upstreams. +connect: Terminating gateways with a wildcard service entry should no longer pick up connect services as upstreams. [[GH-13958](https://github.com/hashicorp/consul/issues/13958)] + +## 1.13.0 (August 9, 2022) + +BREAKING CHANGES: + +* config-entry: Exporting a specific service name across all namespace is invalid. +* connect: contains an upgrade compatibility issue when restoring snapshots containing service mesh proxy registrations from pre-1.13 versions of Consul [[GH-14107](https://github.com/hashicorp/consul/issues/14107)]. Fixed in 1.13.1 [[GH-14149](https://github.com/hashicorp/consul/issues/14149)]. Refer to [1.13 upgrade guidance](https://www.consul.io/docs/upgrading/upgrade-specific#all-service-mesh-deployments) for more information. +* connect: if using auto-encrypt or auto-config, TLS is required for gRPC communication between Envoy and Consul as of 1.13.0; this TLS for gRPC requirement will be removed in a future 1.13 patch release. Refer to [1.13 upgrade guidance](https://www.consul.io/docs/upgrading/upgrade-specific#service-mesh-deployments-using-auto-encrypt-or-auto-config) for more information. +* connect: if a pre-1.13 Consul agent's HTTPS port was not enabled, upgrading to 1.13 may turn on TLS for gRPC communication for Envoy and Consul depending on the agent's TLS configuration. Refer to [1.13 upgrade guidance](https://www.consul.io/docs/upgrading/upgrade-specific#grpc-tls) for more information. +* connect: Removes support for Envoy 1.19 [[GH-13807](https://github.com/hashicorp/consul/issues/13807)] +* telemetry: config flag `telemetry { disable_compat_1.9 = (true|false) }` has been removed. Before upgrading you should remove this flag from your config if the flag is being used. [[GH-13532](https://github.com/hashicorp/consul/issues/13532)] + +FEATURES: + +* **Cluster Peering (Beta)** This version adds a new model to federate Consul clusters for both service mesh and traditional service discovery. Cluster peering allows for service interconnectivity with looser coupling than the existing WAN federation. For more information refer to the [cluster peering](https://www.consul.io/docs/connect/cluster-peering) documentation. +* **Transparent proxying through terminating gateways** This version adds egress traffic control to destinations outside of Consul's catalog, such as APIs on the public internet. Transparent proxies can dial [destinations defined in service-defaults](https://www.consul.io/docs/connect/config-entries/service-defaults#destination) and have the traffic routed through terminating gateways. For more information refer to the [terminating gateway](https://www.consul.io/docs/connect/gateways/terminating-gateway#terminating-gateway-configuration) documentation. +* acl: It is now possible to login and logout using the gRPC API [[GH-12935](https://github.com/hashicorp/consul/issues/12935)] +* agent: Added information about build date alongside other version information for Consul. Extended /agent/self endpoint and `consul version` commands +to report this. Agent also reports build date in log on startup. [[GH-13357](https://github.com/hashicorp/consul/issues/13357)] +* ca: Leaf certificates can now be obtained via the gRPC API: `Sign` [[GH-12787](https://github.com/hashicorp/consul/issues/12787)] +* checks: add UDP health checks.. [[GH-12722](https://github.com/hashicorp/consul/issues/12722)] +* cli: A new flag for config delete to delete a config entry in a +valid config file, e.g., config delete -filename intention-allow.hcl [[GH-13677](https://github.com/hashicorp/consul/issues/13677)] +* connect: Adds a new `destination` field to the `service-default` config entry that allows routing egress traffic +through a terminating gateway in transparent proxy mode without modifying the catalog. [[GH-13613](https://github.com/hashicorp/consul/issues/13613)] +* grpc: New gRPC endpoint to return envoy bootstrap parameters. [[GH-12825](https://github.com/hashicorp/consul/issues/12825)] +* grpc: New gRPC endpoint to return envoy bootstrap parameters. [[GH-1717](https://github.com/hashicorp/consul/issues/1717)] +* grpc: New gRPC service and endpoint to return the list of supported consul dataplane features [[GH-12695](https://github.com/hashicorp/consul/issues/12695)] +* server: broadcast the public grpc port using lan serf and update the consul service in the catalog with the same data [[GH-13687](https://github.com/hashicorp/consul/issues/13687)] +* streaming: Added topic that can be used to consume updates about the list of services in a datacenter [[GH-13722](https://github.com/hashicorp/consul/issues/13722)] +* streaming: Added topics for `ingress-gateway`, `mesh`, `service-intentions` and `service-resolver` config entry events. [[GH-13658](https://github.com/hashicorp/consul/issues/13658)] + +IMPROVEMENTS: + +* api: `merge-central-config` query parameter support added to `/catalog/node-services/:node-name` API, to view a fully resolved service definition (especially when not written into the catalog that way). [[GH-13450](https://github.com/hashicorp/consul/issues/13450)] +* api: `merge-central-config` query parameter support added to `/catalog/node-services/:node-name` API, to view a fully resolved service definition (especially when not written into the catalog that way). [[GH-2046](https://github.com/hashicorp/consul/issues/2046)] +* api: `merge-central-config` query parameter support added to some catalog and health endpoints to view a fully resolved service definition (especially when not written into the catalog that way). [[GH-13001](https://github.com/hashicorp/consul/issues/13001)] +* api: add the ability to specify a path prefix for when consul is behind a reverse proxy or API gateway [[GH-12914](https://github.com/hashicorp/consul/issues/12914)] +* catalog: Add per-node indexes to reduce watchset firing for unrelated nodes and services. [[GH-12399](https://github.com/hashicorp/consul/issues/12399)] +* connect: add validation to ensure connect native services have a port or socketpath specified on catalog registration. +This was the only missing piece to ensure all mesh services are validated for a port (or socketpath) specification on catalog registration. [[GH-12881](https://github.com/hashicorp/consul/issues/12881)] +* ui: Add new CopyableCode component and use it in certain pre-existing areas [[GH-13686](https://github.com/hashicorp/consul/issues/13686)] +* acl: Clarify node/service identities must be lowercase [[GH-12807](https://github.com/hashicorp/consul/issues/12807)] +* command: Add support for enabling TLS in the Envoy Prometheus endpoint via the `consul connect envoy` command. +Adds the `-prometheus-ca-file`, `-prometheus-ca-path`, `-prometheus-cert-file` and `-prometheus-key-file` flags. [[GH-13481](https://github.com/hashicorp/consul/issues/13481)] +* connect: Add Envoy 1.23.0 to support matrix [[GH-13807](https://github.com/hashicorp/consul/issues/13807)] +* connect: Added a `max_inbound_connections` setting to service-defaults for limiting the number of concurrent inbound connections to each service instance. [[GH-13143](https://github.com/hashicorp/consul/issues/13143)] +* grpc: Add a new ServerDiscovery.WatchServers gRPC endpoint for being notified when the set of ready servers has changed. [[GH-12819](https://github.com/hashicorp/consul/issues/12819)] +* telemetry: Added `consul.raft.thread.main.saturation` and `consul.raft.thread.fsm.saturation` metrics to measure approximate saturation of the Raft goroutines [[GH-12865](https://github.com/hashicorp/consul/issues/12865)] +* ui: removed external dependencies for serving UI assets in favor of Go's native embed capabilities [[GH-10996](https://github.com/hashicorp/consul/issues/10996)] +* ui: upgrade ember-composable-helpers to v5.x [[GH-13394](https://github.com/hashicorp/consul/issues/13394)] + +BUG FIXES: + +* acl: Fixed a bug where the ACL down policy wasn't being applied on remote errors from the primary datacenter. [[GH-12885](https://github.com/hashicorp/consul/issues/12885)] +* cli: when `acl token read` is used with the `-self` and `-expanded` flags, return an error instead of panicking [[GH-13787](https://github.com/hashicorp/consul/issues/13787)] +* connect: Fixed a goroutine/memory leak that would occur when using the ingress gateway. [[GH-13847](https://github.com/hashicorp/consul/issues/13847)] +* connect: Ingress gateways with a wildcard service entry should no longer pick up non-connect services as upstreams. +connect: Terminating gateways with a wildcard service entry should no longer pick up connect services as upstreams. [[GH-13958](https://github.com/hashicorp/consul/issues/13958)] +* proxycfg: Fixed a minor bug that would cause configuring a terminating gateway to watch too many service resolvers and waste resources doing filtering. [[GH-13012](https://github.com/hashicorp/consul/issues/13012)] +* raft: upgrade to v1.3.8 which fixes a bug where non cluster member can still be able to participate in an election. [[GH-12844](https://github.com/hashicorp/consul/issues/12844)] +* serf: upgrade serf to v0.9.8 which fixes a bug that crashes Consul when serf keyrings are listed [[GH-13062](https://github.com/hashicorp/consul/issues/13062)] +* ui: Fixes an issue where client side validation errors were not showing in certain areas [[GH-14021](https://github.com/hashicorp/consul/issues/14021)] + ## 1.12.3 (July 13, 2022) IMPROVEMENTS: @@ -36,61 +124,6 @@ BUG FIXES: * agent: Fixed a bug in HTTP handlers where URLs were being decoded twice [[GH-13264](https://github.com/hashicorp/consul/issues/13264)] * fix a bug that caused an error when creating `grpc` or `http2` ingress gateway listeners with multiple services [[GH-13127](https://github.com/hashicorp/consul/issues/13127)] -## 1.13.0-alpha2 (June 21, 2022) - -IMPROVEMENTS: - -* api: `merge-central-config` query parameter support added to `/catalog/node-services/:node-name` API, to view a fully resolved service definition (especially when not written into the catalog that way). [[GH-13450](https://github.com/hashicorp/consul/issues/13450)] -* connect: Update Envoy support matrix to latest patch releases (1.22.2, 1.21.3, 1.20.4, 1.19.5) [[GH-13431](https://github.com/hashicorp/consul/issues/13431)] - -BUG FIXES: - -* ui: Fix incorrect text on certain page empty states [[GH-13409](https://github.com/hashicorp/consul/issues/13409)] - -## 1.13.0-alpha1 (June 15, 2022) - -BREAKING CHANGES: - -* config-entry: Exporting a specific service name across all namespace is invalid. - -FEATURES: - -* acl: It is now possible to login and logout using the gRPC API [[GH-12935](https://github.com/hashicorp/consul/issues/12935)] -* agent: Added information about build date alongside other version information for Consul. Extended /agent/self endpoint and `consul version` commands -to report this. Agent also reports build date in log on startup. [[GH-13357](https://github.com/hashicorp/consul/issues/13357)] -* ca: Leaf certificates can now be obtained via the gRPC API: `Sign` [[GH-12787](https://github.com/hashicorp/consul/issues/12787)] -* checks: add UDP health checks.. [[GH-12722](https://github.com/hashicorp/consul/issues/12722)] -* grpc: New gRPC endpoint to return envoy bootstrap parameters. [[GH-12825](https://github.com/hashicorp/consul/issues/12825)] -* grpc: New gRPC endpoint to return envoy bootstrap parameters. [[GH-1717](https://github.com/hashicorp/consul/issues/1717)] -* grpc: New gRPC service and endpoint to return the list of supported consul dataplane features [[GH-12695](https://github.com/hashicorp/consul/issues/12695)] - -IMPROVEMENTS: - -* api: `merge-central-config` query parameter support added to some catalog and health endpoints to view a fully resolved service definition (especially when not written into the catalog that way). [[GH-13001](https://github.com/hashicorp/consul/issues/13001)] -* api: add the ability to specify a path prefix for when consul is behind a reverse proxy or API gateway [[GH-12914](https://github.com/hashicorp/consul/issues/12914)] -* connect: add validation to ensure connect native services have a port or socketpath specified on catalog registration. -This was the only missing piece to ensure all mesh services are validated for a port (or socketpath) specification on catalog registration. [[GH-12881](https://github.com/hashicorp/consul/issues/12881)] -* Support Vault namespaces in Connect CA by adding RootPKINamespace and -IntermediatePKINamespace fields to the config. [[GH-12904](https://github.com/hashicorp/consul/issues/12904)] -* acl: Clarify node/service identities must be lowercase [[GH-12807](https://github.com/hashicorp/consul/issues/12807)] -* connect: Added a `max_inbound_connections` setting to service-defaults for limiting the number of concurrent inbound connections to each service instance. [[GH-13143](https://github.com/hashicorp/consul/issues/13143)] -* dns: Added support for specifying admin partition in node lookups. [[GH-13421](https://github.com/hashicorp/consul/issues/13421)] -* grpc: Add a new ServerDiscovery.WatchServers gRPC endpoint for being notified when the set of ready servers has changed. [[GH-12819](https://github.com/hashicorp/consul/issues/12819)] -* telemetry: Added `consul.raft.thread.main.saturation` and `consul.raft.thread.fsm.saturation` metrics to measure approximate saturation of the Raft goroutines [[GH-12865](https://github.com/hashicorp/consul/issues/12865)] -* telemetry: Added a `consul.server.isLeader` metric to track if a server is a leader or not. [[GH-13304](https://github.com/hashicorp/consul/issues/13304)] -* ui: removed external dependencies for serving UI assets in favor of Go's native embed capabilities [[GH-10996](https://github.com/hashicorp/consul/issues/10996)] -* ui: upgrade ember-composable-helpers to v5.x [[GH-13394](https://github.com/hashicorp/consul/issues/13394)] - -BUG FIXES: - -* acl: Fixed a bug where the ACL down policy wasn't being applied on remote errors from the primary datacenter. [[GH-12885](https://github.com/hashicorp/consul/issues/12885)] -* agent: Fixed a bug in HTTP handlers where URLs were being decoded twice [[GH-13256](https://github.com/hashicorp/consul/issues/13256)] -* deps: Update go-grpc/grpc, resolving connection memory leak [[GH-13051](https://github.com/hashicorp/consul/issues/13051)] -* fix a bug that caused an error when creating `grpc` or `http2` ingress gateway listeners with multiple services [[GH-13127](https://github.com/hashicorp/consul/issues/13127)] -* proxycfg: Fixed a minor bug that would cause configuring a terminating gateway to watch too many service resolvers and waste resources doing filtering. [[GH-13012](https://github.com/hashicorp/consul/issues/13012)] -* raft: upgrade to v1.3.8 which fixes a bug where non cluster member can still be able to participate in an election. [[GH-12844](https://github.com/hashicorp/consul/issues/12844)] -* serf: upgrade serf to v0.9.8 which fixes a bug that crashes Consul when serf keyrings are listed [[GH-13062](https://github.com/hashicorp/consul/issues/13062)] - ## 1.12.2 (June 3, 2022) BUG FIXES: @@ -914,6 +947,24 @@ NOTES: * legal: **(Enterprise only)** Enterprise binary downloads will now include a copy of the EULA and Terms of Evaluation in the zip archive +## 1.9.17 (April 13, 2022) + +SECURITY: + +* agent: Added a new check field, `disable_redirects`, that allows for disabling the following of redirects for HTTP checks. The intention is to default this to true in a future release so that redirects must explicitly be enabled. [[GH-12685](https://github.com/hashicorp/consul/issues/12685)] +* connect: Properly set SNI when configured for services behind a terminating gateway. [[GH-12672](https://github.com/hashicorp/consul/issues/12672)] + +DEPRECATIONS: + +* tls: With the upgrade to Go 1.17, the ordering of `tls_cipher_suites` will no longer be honored, and `tls_prefer_server_cipher_suites` is now ignored. [[GH-12767](https://github.com/hashicorp/consul/issues/12767)] + +BUG FIXES: + +* connect/ca: cancel old Vault renewal on CA configuration. Provide a 1 - 6 second backoff on repeated token renewal requests to prevent overwhelming Vault. [[GH-12607](https://github.com/hashicorp/consul/issues/12607)] +* memberlist: fixes a bug which prevented members from joining a cluster with +large amounts of churn [[GH-253](https://github.com/hashicorp/memberlist/issues/253)] [[GH-12046](https://github.com/hashicorp/consul/issues/12046)] +* replication: Fixed a bug which could prevent ACL replication from continuing successfully after a leader election. [[GH-12565](https://github.com/hashicorp/consul/issues/12565)] + ## 1.9.16 (February 28, 2022) FEATURES: diff --git a/Dockerfile b/Dockerfile index 762471eb5a..8e127254fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,10 +22,11 @@ LABEL org.opencontainers.image.authors="Consul Team " \ org.opencontainers.image.url="https://www.consul.io/" \ org.opencontainers.image.documentation="https://www.consul.io/docs" \ org.opencontainers.image.source="https://github.com/hashicorp/consul" \ - org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.version=${VERSION} \ org.opencontainers.image.vendor="HashiCorp" \ org.opencontainers.image.title="consul" \ - org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." + org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." \ + version=${VERSION} # This is the location of the releases. ENV HASHICORP_RELEASES=https://releases.hashicorp.com @@ -110,13 +111,13 @@ CMD ["agent", "-dev", "-client", "0.0.0.0"] # Remember, this image cannot be built locally. FROM docker.mirror.hashicorp.services/alpine:3.15 as default -ARG VERSION +ARG PRODUCT_VERSION ARG BIN_NAME # PRODUCT_NAME and PRODUCT_VERSION are the name of the software on releases.hashicorp.com # and the version to download. Example: PRODUCT_NAME=consul PRODUCT_VERSION=1.2.3. ENV BIN_NAME=$BIN_NAME -ENV VERSION=$VERSION +ENV PRODUCT_VERSION=$PRODUCT_VERSION ARG PRODUCT_REVISION ARG PRODUCT_NAME=$BIN_NAME @@ -128,10 +129,11 @@ LABEL org.opencontainers.image.authors="Consul Team " \ org.opencontainers.image.url="https://www.consul.io/" \ org.opencontainers.image.documentation="https://www.consul.io/docs" \ org.opencontainers.image.source="https://github.com/hashicorp/consul" \ - org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.version=${PRODUCT_VERSION} \ org.opencontainers.image.vendor="HashiCorp" \ org.opencontainers.image.title="consul" \ - org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." + org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." \ + version=${PRODUCT_VERSION} # Set up certificates and base tools. # libc6-compat is needed to symlink the shared libraries for ARM builds @@ -217,10 +219,11 @@ LABEL org.opencontainers.image.authors="Consul Team " \ org.opencontainers.image.url="https://www.consul.io/" \ org.opencontainers.image.documentation="https://www.consul.io/docs" \ org.opencontainers.image.source="https://github.com/hashicorp/consul" \ - org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.version=${PRODUCT_VERSION} \ org.opencontainers.image.vendor="HashiCorp" \ org.opencontainers.image.title="consul" \ - org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." + org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." \ + version=${PRODUCT_VERSION} # Copy license for Red Hat certification. COPY LICENSE /licenses/mozilla.txt @@ -284,4 +287,4 @@ USER 100 # By default you'll get an insecure single-node development server that stores # everything in RAM, exposes a web UI and HTTP endpoints, and bootstraps itself. # Don't use this configuration for production. -CMD ["agent", "-dev", "-client", "0.0.0.0"] \ No newline at end of file +CMD ["agent", "-dev", "-client", "0.0.0.0"] diff --git a/agent/agent.go b/agent/agent.go index e087af5187..8a263647e9 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -863,8 +863,18 @@ func (a *Agent) listenAndServeDNS() error { return merr.ErrorOrNil() } +// startListeners will return a net.Listener for every address unless an +// error is encountered, in which case it will close all previously opened +// listeners and return the error. func (a *Agent) startListeners(addrs []net.Addr) ([]net.Listener, error) { - var ln []net.Listener + var lns []net.Listener + + closeAll := func() { + for _, l := range lns { + l.Close() + } + } + for _, addr := range addrs { var l net.Listener var err error @@ -873,22 +883,25 @@ func (a *Agent) startListeners(addrs []net.Addr) ([]net.Listener, error) { case *net.UnixAddr: l, err = a.listenSocket(x.Name) if err != nil { + closeAll() return nil, err } case *net.TCPAddr: l, err = net.Listen("tcp", x.String()) if err != nil { + closeAll() return nil, err } l = &tcpKeepAliveListener{l.(*net.TCPListener)} default: + closeAll() return nil, fmt.Errorf("unsupported address type %T", addr) } - ln = append(ln, l) + lns = append(lns, l) } - return ln, nil + return lns, nil } // listenHTTP binds listeners to the provided addresses and also returns diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 11ec5f9ca7..b2d68e3044 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -1123,8 +1123,8 @@ func (s *HTTPHandlers) AgentRegisterService(resp http.ResponseWriter, req *http. return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Invalid Service Meta: %v", err)} } - // Run validation. This is the same validation that would happen on - // the catalog endpoint so it helps ensure the sync will work properly. + // Run validation. This same validation would happen on the catalog endpoint, + // so it helps ensure the sync will work properly. if err := ns.Validate(); err != nil { return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Validation failed: %v", err.Error())} } @@ -1164,7 +1164,7 @@ func (s *HTTPHandlers) AgentRegisterService(resp http.ResponseWriter, req *http. return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Invalid SidecarService: %s", err)} } if sidecar != nil { - if err := sidecar.Validate(); err != nil { + if err := sidecar.ValidateForAgent(); err != nil { return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Failed Validation: %v", err.Error())} } // Make sure we are allowed to register the sidecar using the token diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index 270cc7dc13..67850f9ebd 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -6799,7 +6799,7 @@ func TestAgentConnectCALeafCert_good(t *testing.T) { ca2 := connect.TestCAConfigSet(t, a, nil) // Issue a blocking query to ensure that the cert gets updated appropriately - { + t.Run("test blocking queries update leaf cert", func(t *testing.T) { resp := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test?index="+index, nil) a.srv.h.ServeHTTP(resp, req) @@ -6815,7 +6815,7 @@ func TestAgentConnectCALeafCert_good(t *testing.T) { // Should not be a cache hit! The data was updated in response to the blocking // query being made. require.Equal(t, "MISS", resp.Header().Get("X-Cache")) - } + }) t.Run("test non-blocking queries update leaf cert", func(t *testing.T) { resp := httptest.NewRecorder() @@ -6834,33 +6834,26 @@ func TestAgentConnectCALeafCert_good(t *testing.T) { // Set a new CA ca3 := connect.TestCAConfigSet(t, a, nil) - resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil) require.NoError(t, err) - obj, err = a.srv.AgentConnectCALeafCert(resp, req) - require.NoError(t, err) - issued2 := obj.(*structs.IssuedCert) - require.NotEqual(t, issued.CertPEM, issued2.CertPEM) - require.NotEqual(t, issued.PrivateKeyPEM, issued2.PrivateKeyPEM) - // Verify that the cert is signed by the new CA - requireLeafValidUnderCA(t, issued2, ca3) - - // Should not be a cache hit! - require.Equal(t, "MISS", resp.Header().Get("X-Cache")) - } - - // Test caching for the leaf cert - { - - for fetched := 0; fetched < 4; fetched++ { - - // Fetch it again + retry.Run(t, func(r *retry.R) { resp := httptest.NewRecorder() - obj2, err := a.srv.AgentConnectCALeafCert(resp, req) - require.NoError(t, err) - require.Equal(t, obj, obj2) - } + a.srv.h.ServeHTTP(resp, req) + + // Should not be a cache hit! + require.Equal(r, "MISS", resp.Header().Get("X-Cache")) + + dec := json.NewDecoder(resp.Body) + issued2 := &structs.IssuedCert{} + require.NoError(r, dec.Decode(issued2)) + + require.NotEqual(r, issued.CertPEM, issued2.CertPEM) + require.NotEqual(r, issued.PrivateKeyPEM, issued2.PrivateKeyPEM) + + // Verify that the cert is signed by the new CA + requireLeafValidUnderCA(r, issued2, ca3) + }) } }) } @@ -7405,7 +7398,7 @@ func waitForActiveCARoot(t *testing.T, srv *HTTPHandlers, expect *structs.CARoot }) } -func requireLeafValidUnderCA(t *testing.T, issued *structs.IssuedCert, ca *structs.CARoot) { +func requireLeafValidUnderCA(t require.TestingT, issued *structs.IssuedCert, ca *structs.CARoot) { leaf, intermediates, err := connect.ParseLeafCerts(issued.CertPEM) require.NoError(t, err) diff --git a/agent/agent_test.go b/agent/agent_test.go index d7b118fcba..8bae81ce45 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -5857,6 +5857,73 @@ func Test_coalesceTimerTwoPeriods(t *testing.T) { } +func TestAgent_startListeners(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + t.Parallel() + + ports := freeport.GetN(t, 3) + bd := BaseDeps{ + Deps: consul.Deps{ + Logger: hclog.NewInterceptLogger(nil), + Tokens: new(token.Store), + GRPCConnPool: &fakeGRPCConnPool{}, + }, + RuntimeConfig: &config.RuntimeConfig{ + HTTPAddrs: []net.Addr{}, + }, + Cache: cache.New(cache.Options{}), + } + + bd, err := initEnterpriseBaseDeps(bd, nil) + require.NoError(t, err) + + agent, err := New(bd) + require.NoError(t, err) + + // use up an address + used := net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[2]} + l, err := net.Listen("tcp", used.String()) + require.NoError(t, err) + t.Cleanup(func() { l.Close() }) + + var lns []net.Listener + t.Cleanup(func() { + for _, ln := range lns { + ln.Close() + } + }) + + // first two addresses open listeners but third address should fail + lns, err = agent.startListeners([]net.Addr{ + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[0]}, + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[1]}, + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[2]}, + }) + require.Contains(t, err.Error(), "address already in use") + + // first two ports should be freed up + retry.Run(t, func(r *retry.R) { + lns, err = agent.startListeners([]net.Addr{ + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[0]}, + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[1]}, + }) + require.NoError(r, err) + require.Len(r, lns, 2) + }) + + // first two ports should be in use + retry.Run(t, func(r *retry.R) { + _, err = agent.startListeners([]net.Addr{ + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[0]}, + &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[1]}, + }) + require.Contains(r, err.Error(), "address already in use") + }) + +} + func getExpectedCaPoolByFile(t *testing.T) *x509.CertPool { pool := x509.NewCertPool() data, err := ioutil.ReadFile("../test/ca/root.cer") diff --git a/agent/cache-types/trust_bundles.go b/agent/cache-types/trust_bundles.go index 70c63cb4be..eddc8dabbe 100644 --- a/agent/cache-types/trust_bundles.go +++ b/agent/cache-types/trust_bundles.go @@ -8,6 +8,8 @@ import ( "github.com/mitchellh/hashstructure" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/hashicorp/consul/agent/cache" external "github.com/hashicorp/consul/agent/grpc-external" @@ -87,6 +89,13 @@ func (t *TrustBundles) Fetch(_ cache.FetchOptions, req cache.Request) (cache.Fet // Fetch reply, err := t.Client.TrustBundleListByService(external.ContextWithToken(context.Background(), reqReal.Token), reqReal.Request) if err != nil { + // Return an empty result if the error is due to peering being disabled. + // This allows mesh gateways to receive an update and confirm that the watch is set. + if e, ok := status.FromError(err); ok && e.Code() == codes.FailedPrecondition { + result.Index = 1 + result.Value = &pbpeering.TrustBundleListByServiceResponse{Index: 1} + return result, nil + } return result, err } diff --git a/agent/cache-types/trust_bundles_test.go b/agent/cache-types/trust_bundles_test.go index 09d8a80bcb..85248dba1d 100644 --- a/agent/cache-types/trust_bundles_test.go +++ b/agent/cache-types/trust_bundles_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/proto/pbpeering" @@ -48,6 +50,29 @@ func TestTrustBundles(t *testing.T) { }, result) } +func TestTrustBundles_PeeringDisabled(t *testing.T) { + client := NewMockTrustBundleLister(t) + typ := &TrustBundles{Client: client} + + var resp *pbpeering.TrustBundleListByServiceResponse + + // Expect the proper call. + // This also returns the canned response above. + client.On("TrustBundleListByService", mock.Anything, mock.Anything). + Return(resp, grpcstatus.Error(codes.FailedPrecondition, "peering must be enabled to use this endpoint")) + + // Fetch and assert against the result. + result, err := typ.Fetch(cache.FetchOptions{}, &TrustBundleListRequest{ + Request: &pbpeering.TrustBundleListByServiceRequest{ + ServiceName: "foo", + }, + }) + require.NoError(t, err) + require.NotNil(t, result) + require.EqualValues(t, 1, result.Index) + require.NotNil(t, result.Value) +} + func TestTrustBundles_badReqType(t *testing.T) { client := pbpeering.NewPeeringServiceClient(nil) typ := &TrustBundles{Client: client} diff --git a/agent/config/builder.go b/agent/config/builder.go index 40389553d2..960d86ea43 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -2531,10 +2531,9 @@ func (b *builder) buildTLSConfig(rt RuntimeConfig, t TLS) (tlsutil.Config, error return c, errors.New("verify_server_hostname is only valid in the tls.internal_rpc stanza") } - // TLS is only enabled on the gRPC listener if there's an HTTPS port configured - // for historic and backwards-compatibility reasons. - if rt.HTTPSPort <= 0 && (t.GRPC != TLSProtocolConfig{} && t.GRPCModifiedByDeprecatedConfig == nil) { - b.warn("tls.grpc was provided but TLS will NOT be enabled on the gRPC listener without an HTTPS listener configured (e.g. via ports.https)") + // And UseAutoCert right now only applies to external gRPC interface. + if t.Defaults.UseAutoCert != nil || t.HTTPS.UseAutoCert != nil || t.InternalRPC.UseAutoCert != nil { + return c, errors.New("use_auto_cert is only valid in the tls.grpc stanza") } defaultTLSMinVersion := b.tlsVersion("tls.defaults.tls_min_version", t.Defaults.TLSMinVersion) @@ -2591,6 +2590,7 @@ func (b *builder) buildTLSConfig(rt RuntimeConfig, t TLS) (tlsutil.Config, error mapCommon("https", t.HTTPS, &c.HTTPS) mapCommon("grpc", t.GRPC, &c.GRPC) + c.GRPC.UseAutoCert = boolValWithDefault(t.GRPC.UseAutoCert, false) c.ServerName = rt.ServerName c.NodeName = rt.NodeName diff --git a/agent/config/config.go b/agent/config/config.go index ca6900b51f..2d21e75dae 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -611,7 +611,7 @@ type Connect struct { MeshGatewayWANFederationEnabled *bool `mapstructure:"enable_mesh_gateway_wan_federation"` EnableServerlessPlugin *bool `mapstructure:"enable_serverless_plugin"` - // TestCALeafRootChangeSpread controls how long after a CA roots change before new leaft certs will be generated. + // TestCALeafRootChangeSpread controls how long after a CA roots change before new leaf certs will be generated. // This is only tuned in tests, generally set to 1ns to make tests deterministic with when to expect updated leaf // certs by. This configuration is not exposed to users (not documented, and agent/config/default.go will override it) TestCALeafRootChangeSpread *string `mapstructure:"test_ca_leaf_root_change_spread"` @@ -867,6 +867,7 @@ type TLSProtocolConfig struct { VerifyIncoming *bool `mapstructure:"verify_incoming"` VerifyOutgoing *bool `mapstructure:"verify_outgoing"` VerifyServerHostname *bool `mapstructure:"verify_server_hostname"` + UseAutoCert *bool `mapstructure:"use_auto_cert"` } type TLS struct { diff --git a/agent/config/default.go b/agent/config/default.go index bb78218513..861db9e3ba 100644 --- a/agent/config/default.go +++ b/agent/config/default.go @@ -104,9 +104,6 @@ func DefaultSource() Source { kv_max_value_size = ` + strconv.FormatInt(raft.SuggestedMaxDataSize, 10) + ` txn_max_req_len = ` + strconv.FormatInt(raft.SuggestedMaxDataSize, 10) + ` } - peering = { - enabled = true - } performance = { leave_drain_time = "5s" raft_multiplier = ` + strconv.Itoa(int(consul.DefaultRaftMultiplier)) + ` diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index b05b314919..f5e9bd3352 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -5516,7 +5516,70 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, }) run(t, testCase{ - desc: "tls.grpc without ports.https", + desc: "tls.grpc.use_auto_cert defaults to false", + args: []string{ + `-data-dir=` + dataDir, + }, + json: []string{` + { + "tls": { + "grpc": {} + } + } + `}, + hcl: []string{` + tls { + grpc {} + } + `}, + expected: func(rt *RuntimeConfig) { + rt.DataDir = dataDir + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + rt.TLS.GRPC.UseAutoCert = false + }, + }) + run(t, testCase{ + desc: "tls.grpc.use_auto_cert defaults to false (II)", + args: []string{ + `-data-dir=` + dataDir, + }, + json: []string{` + { + "tls": {} + } + `}, + hcl: []string{` + tls { + } + `}, + expected: func(rt *RuntimeConfig) { + rt.DataDir = dataDir + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + rt.TLS.GRPC.UseAutoCert = false + }, + }) + run(t, testCase{ + desc: "tls.grpc.use_auto_cert defaults to false (III)", + args: []string{ + `-data-dir=` + dataDir, + }, + json: []string{` + { + } + `}, + hcl: []string{` + `}, + expected: func(rt *RuntimeConfig) { + rt.DataDir = dataDir + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + rt.TLS.GRPC.UseAutoCert = false + }, + }) + run(t, testCase{ + desc: "tls.grpc.use_auto_cert enabled when true", args: []string{ `-data-dir=` + dataDir, }, @@ -5524,7 +5587,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { { "tls": { "grpc": { - "cert_file": "cert-1234" + "use_auto_cert": true } } } @@ -5532,30 +5595,43 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { hcl: []string{` tls { grpc { - cert_file = "cert-1234" + use_auto_cert = true } } `}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir - rt.TLS.Domain = "consul." rt.TLS.NodeName = "thehostname" - - rt.TLS.GRPC.CertFile = "cert-1234" - }, - expectedWarnings: []string{ - "tls.grpc was provided but TLS will NOT be enabled on the gRPC listener without an HTTPS listener configured (e.g. via ports.https)", + rt.TLS.GRPC.UseAutoCert = true }, }) run(t, testCase{ - desc: "peering.enabled defaults to true", + desc: "tls.grpc.use_auto_cert disabled when false", args: []string{ `-data-dir=` + dataDir, }, + json: []string{` + { + "tls": { + "grpc": { + "use_auto_cert": false + } + } + } + `}, + hcl: []string{` + tls { + grpc { + use_auto_cert = false + } + } + `}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir - rt.PeeringEnabled = true + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + rt.TLS.GRPC.UseAutoCert = false }, }) } @@ -6350,6 +6426,7 @@ func TestLoad_FullConfig(t *testing.T) { TLSMinVersion: types.TLSv1_0, CipherSuites: []types.TLSCipherSuite{types.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, types.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, VerifyOutgoing: false, + UseAutoCert: true, }, HTTPS: tlsutil.ProtocolConfig{ VerifyIncoming: true, diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index 09ecd4cfeb..8f91743dba 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -374,7 +374,8 @@ "TLSMinVersion": "", "VerifyIncoming": false, "VerifyOutgoing": false, - "VerifyServerHostname": false + "VerifyServerHostname": false, + "UseAutoCert": false }, "HTTPS": { "CAFile": "", @@ -385,7 +386,8 @@ "TLSMinVersion": "", "VerifyIncoming": false, "VerifyOutgoing": false, - "VerifyServerHostname": false + "VerifyServerHostname": false, + "UseAutoCert": false }, "InternalRPC": { "CAFile": "", @@ -396,7 +398,8 @@ "TLSMinVersion": "", "VerifyIncoming": false, "VerifyOutgoing": false, - "VerifyServerHostname": false + "VerifyServerHostname": false, + "UseAutoCert": false }, "NodeName": "", "ServerName": "" @@ -466,4 +469,4 @@ "VersionMetadata": "", "VersionPrerelease": "", "Watches": [] -} \ No newline at end of file +} diff --git a/agent/config/testdata/full-config.hcl b/agent/config/testdata/full-config.hcl index ed8203296c..305df9b89e 100644 --- a/agent/config/testdata/full-config.hcl +++ b/agent/config/testdata/full-config.hcl @@ -697,6 +697,7 @@ tls { tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" tls_min_version = "TLSv1_0" verify_incoming = true + use_auto_cert = true } } tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" diff --git a/agent/config/testdata/full-config.json b/agent/config/testdata/full-config.json index 8294a27b7c..bc72c2955e 100644 --- a/agent/config/testdata/full-config.json +++ b/agent/config/testdata/full-config.json @@ -692,7 +692,8 @@ "key_file": "1y4prKjl", "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "tls_min_version": "TLSv1_0", - "verify_incoming": true + "verify_incoming": true, + "use_auto_cert": true } }, "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", diff --git a/agent/connect/sni_test.go b/agent/connect/sni_test.go index 26fae1da72..59e9f41fcd 100644 --- a/agent/connect/sni_test.go +++ b/agent/connect/sni_test.go @@ -178,20 +178,43 @@ func TestQuerySNI(t *testing.T) { func TestTargetSNI(t *testing.T) { // empty namespace, empty subset require.Equal(t, "api.default.foo."+testTrustDomainSuffix1, - TargetSNI(structs.NewDiscoveryTarget("api", "", "", "default", "foo"), testTrustDomain1)) + TargetSNI(structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "api", + Partition: "default", + Datacenter: "foo", + }), testTrustDomain1)) require.Equal(t, "api.default.foo."+testTrustDomainSuffix1, - TargetSNI(structs.NewDiscoveryTarget("api", "", "", "", "foo"), testTrustDomain1)) + TargetSNI(structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "api", + Datacenter: "foo", + }), testTrustDomain1)) // set namespace, empty subset require.Equal(t, "api.neighbor.foo."+testTrustDomainSuffix2, - TargetSNI(structs.NewDiscoveryTarget("api", "", "neighbor", "default", "foo"), testTrustDomain2)) + TargetSNI(structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "api", + Namespace: "neighbor", + Partition: "default", + Datacenter: "foo", + }), testTrustDomain2)) // empty namespace, set subset require.Equal(t, "v2.api.default.foo."+testTrustDomainSuffix1, - TargetSNI(structs.NewDiscoveryTarget("api", "v2", "", "default", "foo"), testTrustDomain1)) + TargetSNI(structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "api", + ServiceSubset: "v2", + Partition: "default", + Datacenter: "foo", + }), testTrustDomain1)) // set namespace, set subset require.Equal(t, "canary.api.neighbor.foo."+testTrustDomainSuffix2, - TargetSNI(structs.NewDiscoveryTarget("api", "canary", "neighbor", "default", "foo"), testTrustDomain2)) + TargetSNI(structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "api", + ServiceSubset: "canary", + Namespace: "neighbor", + Partition: "default", + Datacenter: "foo", + }), testTrustDomain2)) } diff --git a/agent/consul/catalog_endpoint.go b/agent/consul/catalog_endpoint.go index 111ee7b2ba..696ae314a7 100644 --- a/agent/consul/catalog_endpoint.go +++ b/agent/consul/catalog_endpoint.go @@ -565,6 +565,11 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I return err } + filter, err := bexpr.CreateFilter(args.Filter, nil, []*structs.ServiceNode{}) + if err != nil { + return err + } + // Set reply enterprise metadata after resolving and validating the token so // that we can properly infer metadata from the token. reply.EnterpriseMeta = args.EnterpriseMeta @@ -574,10 +579,11 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I &reply.QueryMeta, func(ws memdb.WatchSet, state *state.Store) error { var err error + var serviceNodes structs.ServiceNodes if len(args.NodeMetaFilters) > 0 { - reply.Index, reply.Services, err = state.ServicesByNodeMeta(ws, args.NodeMetaFilters, &args.EnterpriseMeta, args.PeerName) + reply.Index, serviceNodes, err = state.ServicesByNodeMeta(ws, args.NodeMetaFilters, &args.EnterpriseMeta, args.PeerName) } else { - reply.Index, reply.Services, err = state.Services(ws, &args.EnterpriseMeta, args.PeerName) + reply.Index, serviceNodes, err = state.Services(ws, &args.EnterpriseMeta, args.PeerName) } if err != nil { return err @@ -588,11 +594,43 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I return nil } + raw, err := filter.Execute(serviceNodes) + if err != nil { + return err + } + + reply.Services = servicesTagsByName(raw.(structs.ServiceNodes)) + c.srv.filterACLWithAuthorizer(authz, reply) + return nil }) } +func servicesTagsByName(services []*structs.ServiceNode) structs.Services { + unique := make(map[string]map[string]struct{}) + for _, svc := range services { + tags, ok := unique[svc.ServiceName] + if !ok { + unique[svc.ServiceName] = make(map[string]struct{}) + tags = unique[svc.ServiceName] + } + for _, tag := range svc.ServiceTags { + tags[tag] = struct{}{} + } + } + + // Generate the output structure. + var results = make(structs.Services) + for service, tags := range unique { + results[service] = make([]string, 0, len(tags)) + for tag := range tags { + results[service] = append(results[service], tag) + } + } + return results +} + // ServiceList is used to query the services in a DC. // Returns services as a list of ServiceNames. func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.IndexedServiceList) error { diff --git a/agent/consul/catalog_endpoint_test.go b/agent/consul/catalog_endpoint_test.go index ca00efaea2..daa22c90c1 100644 --- a/agent/consul/catalog_endpoint_test.go +++ b/agent/consul/catalog_endpoint_test.go @@ -1523,6 +1523,45 @@ func TestCatalog_ListServices_NodeMetaFilter(t *testing.T) { } } +func TestCatalog_ListServices_Filter(t *testing.T) { + t.Parallel() + _, s1 := testServer(t) + codec := rpcClient(t, s1) + + testrpc.WaitForTestAgent(t, s1.RPC, "dc1") + + // prep the cluster with some data we can use in our filters + registerTestCatalogEntries(t, codec) + + // Run the tests against the test server + + t.Run("ListServices", func(t *testing.T) { + args := structs.DCSpecificRequest{ + Datacenter: "dc1", + } + + args.Filter = "ServiceName == redis" + out := new(structs.IndexedServices) + require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.ListServices", &args, out)) + require.Contains(t, out.Services, "redis") + require.ElementsMatch(t, []string{"v1", "v2"}, out.Services["redis"]) + + args.Filter = "NodeMeta.os == NoSuchOS" + out = new(structs.IndexedServices) + require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.ListServices", &args, out)) + require.Len(t, out.Services, 0) + + args.Filter = "NodeMeta.NoSuchMetadata == linux" + out = new(structs.IndexedServices) + require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.ListServices", &args, out)) + require.Len(t, out.Services, 0) + + args.Filter = "InvalidField == linux" + out = new(structs.IndexedServices) + require.Error(t, msgpackrpc.CallWithCodec(codec, "Catalog.ListServices", &args, out)) + }) +} + func TestCatalog_ListServices_Blocking(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") diff --git a/agent/consul/client_serf.go b/agent/consul/client_serf.go index 55df7a5471..7d993c4ac9 100644 --- a/agent/consul/client_serf.go +++ b/agent/consul/client_serf.go @@ -62,6 +62,8 @@ func (c *Client) setupSerf(conf *serf.Config, ch chan serf.Event, path string) ( return nil, err } + addSerfMetricsLabels(conf, false, c.config.Segment, c.config.AgentEnterpriseMeta().PartitionOrDefault(), "") + addEnterpriseSerfTags(conf.Tags, c.config.AgentEnterpriseMeta()) conf.ReconnectTimeoutOverride = libserf.NewReconnectOverride(c.logger) diff --git a/agent/consul/client_test.go b/agent/consul/client_test.go index 84135ee184..32199d8aba 100644 --- a/agent/consul/client_test.go +++ b/agent/consul/client_test.go @@ -893,8 +893,8 @@ func TestClient_RPC_Timeout(t *testing.T) { } }) - // waiter will sleep for 50ms - require.NoError(t, s1.RegisterEndpoint("Wait", &waiter{duration: 50 * time.Millisecond})) + // waiter will sleep for 101ms which is 1ms more than the DefaultQueryTime + require.NoError(t, s1.RegisterEndpoint("Wait", &waiter{duration: 101 * time.Millisecond})) // Requests with QueryOptions have a default timeout of RPCHoldTimeout (10ms) // so we expect the RPC call to timeout. @@ -903,7 +903,8 @@ func TestClient_RPC_Timeout(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "rpc error making call: i/o deadline reached") - // Blocking requests have a longer timeout (100ms) so this should pass + // Blocking requests have a longer timeout (100ms) so this should pass since we + // add the maximum jitter which should be 16ms out = struct{}{} err = c1.RPC("Wait.Wait", &structs.NodeSpecificRequest{ QueryOptions: structs.QueryOptions{ diff --git a/agent/consul/config.go b/agent/consul/config.go index 64bf90257c..38063f808a 100644 --- a/agent/consul/config.go +++ b/agent/consul/config.go @@ -517,7 +517,6 @@ func DefaultConfig() *Config { DefaultQueryTime: 300 * time.Second, MaxQueryTime: 600 * time.Second, - PeeringEnabled: true, PeeringTestAllowPeerRegistrations: false, EnterpriseConfig: DefaultEnterpriseConfig(), @@ -585,6 +584,7 @@ func CloneSerfLANConfig(base *serf.Config) *serf.Config { cfg.MemberlistConfig.ProbeTimeout = base.MemberlistConfig.ProbeTimeout cfg.MemberlistConfig.SuspicionMult = base.MemberlistConfig.SuspicionMult cfg.MemberlistConfig.RetransmitMult = base.MemberlistConfig.RetransmitMult + cfg.MemberlistConfig.MetricLabels = base.MemberlistConfig.MetricLabels // agent/keyring.go cfg.MemberlistConfig.Keyring = base.MemberlistConfig.Keyring @@ -594,6 +594,7 @@ func CloneSerfLANConfig(base *serf.Config) *serf.Config { cfg.ReapInterval = base.ReapInterval cfg.TombstoneTimeout = base.TombstoneTimeout cfg.MemberlistConfig.SecretKey = base.MemberlistConfig.SecretKey + cfg.MetricLabels = base.MetricLabels return cfg } diff --git a/agent/consul/config_endpoint_test.go b/agent/consul/config_endpoint_test.go index bbed2bf1b9..3f79b3d1b4 100644 --- a/agent/consul/config_endpoint_test.go +++ b/agent/consul/config_endpoint_test.go @@ -1804,8 +1804,6 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams_Blocking(t *testing.T) { t.Skip("too slow for testing.Short") } - t.Parallel() - dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() diff --git a/agent/consul/discovery_chain_endpoint_test.go b/agent/consul/discovery_chain_endpoint_test.go index 21c34aa864..c1ad0fef35 100644 --- a/agent/consul/discovery_chain_endpoint_test.go +++ b/agent/consul/discovery_chain_endpoint_test.go @@ -56,8 +56,17 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) { return &resp, nil } - newTarget := func(service, serviceSubset, namespace, partition, datacenter string) *structs.DiscoveryTarget { - t := structs.NewDiscoveryTarget(service, serviceSubset, namespace, partition, datacenter) + newTarget := func(opts structs.DiscoveryTargetOpts) *structs.DiscoveryTarget { + if opts.Namespace == "" { + opts.Namespace = "default" + } + if opts.Partition == "" { + opts.Partition = "default" + } + if opts.Datacenter == "" { + opts.Datacenter = "dc1" + } + t := structs.NewDiscoveryTarget(opts) t.SNI = connect.TargetSNI(t, connect.TestClusterID+".consul") t.Name = t.SNI t.ConnectTimeout = 5 * time.Second // default @@ -119,7 +128,7 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "web.default.default.dc1": newTarget("web", "", "default", "default", "dc1"), + "web.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "web"}), }, }, } @@ -245,7 +254,7 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) { }, Targets: map[string]*structs.DiscoveryTarget{ "web.default.default.dc1": targetWithConnectTimeout( - newTarget("web", "", "default", "default", "dc1"), + newTarget(structs.DiscoveryTargetOpts{Service: "web"}), 33*time.Second, ), }, diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index ed664878b4..3a9a1f0ed7 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -8,6 +8,7 @@ import ( "github.com/mitchellh/hashstructure" "github.com/mitchellh/mapstructure" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/structs" @@ -576,7 +577,10 @@ func (c *compiler) assembleChain() error { if router == nil { // If no router is configured, move on down the line to the next hop of // the chain. - node, err := c.getSplitterOrResolverNode(c.newTarget(c.serviceName, "", "", "", "")) + node, err := c.getSplitterOrResolverNode(c.newTarget(structs.DiscoveryTargetOpts{ + Service: c.serviceName, + })) + if err != nil { return err } @@ -626,11 +630,20 @@ func (c *compiler) assembleChain() error { ) if dest.ServiceSubset == "" { node, err = c.getSplitterOrResolverNode( - c.newTarget(svc, "", destNamespace, destPartition, ""), - ) + c.newTarget(structs.DiscoveryTargetOpts{ + Service: svc, + Namespace: destNamespace, + Partition: destPartition, + }, + )) } else { node, err = c.getResolverNode( - c.newTarget(svc, dest.ServiceSubset, destNamespace, destPartition, ""), + c.newTarget(structs.DiscoveryTargetOpts{ + Service: svc, + ServiceSubset: dest.ServiceSubset, + Namespace: destNamespace, + Partition: destPartition, + }), false, ) } @@ -642,7 +655,12 @@ func (c *compiler) assembleChain() error { // If we have a router, we'll add a catch-all route at the end to send // unmatched traffic to the next hop in the chain. - defaultDestinationNode, err := c.getSplitterOrResolverNode(c.newTarget(router.Name, "", router.NamespaceOrDefault(), router.PartitionOrDefault(), "")) + opts := structs.DiscoveryTargetOpts{ + Service: router.Name, + Namespace: router.NamespaceOrDefault(), + Partition: router.PartitionOrDefault(), + } + defaultDestinationNode, err := c.getSplitterOrResolverNode(c.newTarget(opts)) if err != nil { return err } @@ -674,26 +692,36 @@ func newDefaultServiceRoute(serviceName, namespace, partition string) *structs.S } } -func (c *compiler) newTarget(service, serviceSubset, namespace, partition, datacenter string) *structs.DiscoveryTarget { - if service == "" { +func (c *compiler) newTarget(opts structs.DiscoveryTargetOpts) *structs.DiscoveryTarget { + if opts.Service == "" { panic("newTarget called with empty service which makes no sense") } - t := structs.NewDiscoveryTarget( - service, - serviceSubset, - defaultIfEmpty(namespace, c.evaluateInNamespace), - defaultIfEmpty(partition, c.evaluateInPartition), - defaultIfEmpty(datacenter, c.evaluateInDatacenter), - ) + if opts.Peer == "" { + opts.Datacenter = defaultIfEmpty(opts.Datacenter, c.evaluateInDatacenter) + opts.Namespace = defaultIfEmpty(opts.Namespace, c.evaluateInNamespace) + opts.Partition = defaultIfEmpty(opts.Partition, c.evaluateInPartition) + } else { + // Don't allow Peer and Datacenter. + opts.Datacenter = "" + // Peer and Partition cannot both be set. + opts.Partition = acl.PartitionOrDefault("") + // Default to "default" rather than c.evaluateInNamespace. + opts.Namespace = acl.PartitionOrDefault(opts.Namespace) + } - // Set default connect SNI. This will be overridden later if the service - // has an explicit SNI value configured in service-defaults. - t.SNI = connect.TargetSNI(t, c.evaluateInTrustDomain) + t := structs.NewDiscoveryTarget(opts) - // Use the same representation for the name. This will NOT be overridden - // later. - t.Name = t.SNI + // We don't have the peer's trust domain yet so we can't construct the SNI. + if opts.Peer == "" { + // Set default connect SNI. This will be overridden later if the service + // has an explicit SNI value configured in service-defaults. + t.SNI = connect.TargetSNI(t, c.evaluateInTrustDomain) + + // Use the same representation for the name. This will NOT be overridden + // later. + t.Name = t.SNI + } prev, ok := c.loadedTargets[t.ID] if ok { @@ -703,34 +731,30 @@ func (c *compiler) newTarget(service, serviceSubset, namespace, partition, datac return t } -func (c *compiler) rewriteTarget(t *structs.DiscoveryTarget, service, serviceSubset, partition, namespace, datacenter string) *structs.DiscoveryTarget { - var ( - service2 = t.Service - serviceSubset2 = t.ServiceSubset - partition2 = t.Partition - namespace2 = t.Namespace - datacenter2 = t.Datacenter - ) +func (c *compiler) rewriteTarget(t *structs.DiscoveryTarget, opts structs.DiscoveryTargetOpts) *structs.DiscoveryTarget { + mergedOpts := t.ToDiscoveryTargetOpts() - if service != "" && service != service2 { - service2 = service + if opts.Service != "" && opts.Service != mergedOpts.Service { + mergedOpts.Service = opts.Service // Reset the chosen subset if we reference a service other than our own. - serviceSubset2 = "" + mergedOpts.ServiceSubset = "" } - if serviceSubset != "" { - serviceSubset2 = serviceSubset + if opts.ServiceSubset != "" { + mergedOpts.ServiceSubset = opts.ServiceSubset } - if partition != "" { - partition2 = partition + if opts.Partition != "" { + mergedOpts.Partition = opts.Partition } - if namespace != "" { - namespace2 = namespace + // Only use explicit Namespace with Peer + if opts.Namespace != "" || opts.Peer != "" { + mergedOpts.Namespace = opts.Namespace } - if datacenter != "" { - datacenter2 = datacenter + if opts.Datacenter != "" { + mergedOpts.Datacenter = opts.Datacenter } + mergedOpts.Peer = opts.Peer - return c.newTarget(service2, serviceSubset2, namespace2, partition2, datacenter2) + return c.newTarget(mergedOpts) } func (c *compiler) getSplitterOrResolverNode(target *structs.DiscoveryTarget) (*structs.DiscoveryGraphNode, error) { @@ -803,10 +827,13 @@ func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGra // fall through to group-resolver } - node, err := c.getResolverNode( - c.newTarget(splitID.ID, split.ServiceSubset, splitID.NamespaceOrDefault(), splitID.PartitionOrDefault(), ""), - false, - ) + opts := structs.DiscoveryTargetOpts{ + Service: splitID.ID, + ServiceSubset: split.ServiceSubset, + Namespace: splitID.NamespaceOrDefault(), + Partition: splitID.PartitionOrDefault(), + } + node, err := c.getResolverNode(c.newTarget(opts), false) if err != nil { return nil, err } @@ -881,11 +908,7 @@ RESOLVE_AGAIN: redirectedTarget := c.rewriteTarget( target, - redirect.Service, - redirect.ServiceSubset, - redirect.Partition, - redirect.Namespace, - redirect.Datacenter, + redirect.ToDiscoveryTargetOpts(), ) if redirectedTarget.ID != target.ID { target = redirectedTarget @@ -895,14 +918,9 @@ RESOLVE_AGAIN: // Handle default subset. if target.ServiceSubset == "" && resolver.DefaultSubset != "" { - target = c.rewriteTarget( - target, - "", - resolver.DefaultSubset, - "", - "", - "", - ) + target = c.rewriteTarget(target, structs.DiscoveryTargetOpts{ + ServiceSubset: resolver.DefaultSubset, + }) goto RESOLVE_AGAIN } @@ -1027,56 +1045,54 @@ RESOLVE_AGAIN: failover, ok = f["*"] } - if ok { - // Determine which failover definitions apply. - var failoverTargets []*structs.DiscoveryTarget - if len(failover.Datacenters) > 0 { - for _, dc := range failover.Datacenters { - // Rewrite the target as per the failover policy. - failoverTarget := c.rewriteTarget( - target, - failover.Service, - failover.ServiceSubset, - target.Partition, - failover.Namespace, - dc, - ) - if failoverTarget.ID != target.ID { // don't failover to yourself - failoverTargets = append(failoverTargets, failoverTarget) - } - } - } else { + if !ok { + return node, nil + } + + // Determine which failover definitions apply. + var failoverTargets []*structs.DiscoveryTarget + if len(failover.Datacenters) > 0 { + opts := failover.ToDiscoveryTargetOpts() + for _, dc := range failover.Datacenters { // Rewrite the target as per the failover policy. - failoverTarget := c.rewriteTarget( - target, - failover.Service, - failover.ServiceSubset, - target.Partition, - failover.Namespace, - "", - ) + opts.Datacenter = dc + failoverTarget := c.rewriteTarget(target, opts) if failoverTarget.ID != target.ID { // don't failover to yourself failoverTargets = append(failoverTargets, failoverTarget) } } - - // If we filtered everything out then no point in having a failover. - if len(failoverTargets) > 0 { - df := &structs.DiscoveryFailover{} - node.Resolver.Failover = df - - // Take care of doing any redirects or configuration loading - // related to targets by cheating a bit and recursing into - // ourselves. - for _, target := range failoverTargets { - failoverResolveNode, err := c.getResolverNode(target, true) - if err != nil { - return nil, err - } - failoverTarget := failoverResolveNode.Resolver.Target - df.Targets = append(df.Targets, failoverTarget) + } else if len(failover.Targets) > 0 { + for _, t := range failover.Targets { + // Rewrite the target as per the failover policy. + failoverTarget := c.rewriteTarget(target, t.ToDiscoveryTargetOpts()) + if failoverTarget.ID != target.ID { // don't failover to yourself + failoverTargets = append(failoverTargets, failoverTarget) } } + } else { + // Rewrite the target as per the failover policy. + failoverTarget := c.rewriteTarget(target, failover.ToDiscoveryTargetOpts()) + if failoverTarget.ID != target.ID { // don't failover to yourself + failoverTargets = append(failoverTargets, failoverTarget) + } + } + + // If we filtered everything out then no point in having a failover. + if len(failoverTargets) > 0 { + df := &structs.DiscoveryFailover{} + node.Resolver.Failover = df + + // Take care of doing any redirects or configuration loading + // related to targets by cheating a bit and recursing into + // ourselves. + for _, target := range failoverTargets { + failoverResolveNode, err := c.getResolverNode(target, true) + if err != nil { + return nil, err + } + failoverTarget := failoverResolveNode.Resolver.Target + df.Targets = append(df.Targets, failoverTarget) + } } } diff --git a/agent/consul/discoverychain/compile_test.go b/agent/consul/discoverychain/compile_test.go index 221ac757f9..6505fdb9ea 100644 --- a/agent/consul/discoverychain/compile_test.go +++ b/agent/consul/discoverychain/compile_test.go @@ -46,6 +46,7 @@ func TestCompile(t *testing.T) { "service and subset failover": testcase_ServiceAndSubsetFailover(), "datacenter failover": testcase_DatacenterFailover(), "datacenter failover with mesh gateways": testcase_DatacenterFailover_WithMeshGateways(), + "target failover": testcase_Failover_Targets(), "noop split to resolver with default subset": testcase_NoopSplit_WithDefaultSubset(), "resolver with default subset": testcase_Resolve_WithDefaultSubset(), "default resolver with external sni": testcase_DefaultResolver_ExternalSNI(), @@ -182,7 +183,7 @@ func testcase_JustRouterWithDefaults() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } @@ -244,7 +245,7 @@ func testcase_JustRouterWithNoDestination() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } @@ -294,7 +295,7 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ "main.default.default.dc1": targetWithConnectTimeout( - newTarget("main", "", "default", "default", "dc1", nil), + newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), 33*time.Second, ), }, @@ -361,7 +362,7 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } @@ -426,7 +427,10 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc1", + }, nil), }, } @@ -498,7 +502,7 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ "main.default.default.dc1": targetWithConnectTimeout( - newTarget("main", "", "default", "default", "dc1", nil), + newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), 33*time.Second, ), }, @@ -584,8 +588,11 @@ func testcase_RouteBypassesSplit() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), - "bypass.other.default.default.dc1": newTarget("other", "bypass", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), + "bypass.other.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "other", + ServiceSubset: "bypass", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == bypass", } @@ -638,7 +645,7 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } @@ -694,7 +701,7 @@ func testcase_NoopSplit_WithResolver() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ "main.default.default.dc1": targetWithConnectTimeout( - newTarget("main", "", "default", "default", "dc1", nil), + newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), 33*time.Second, ), }, @@ -776,12 +783,19 @@ func testcase_SubsetSplit() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "v2.main.default.default.dc1": newTarget("main", "v2", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + + "v2.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v2", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 2", } }), - "v1.main.default.default.dc1": newTarget("main", "v1", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v1.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v1", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 1", } @@ -855,8 +869,8 @@ func testcase_ServiceSplit() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "foo.default.default.dc1": newTarget("foo", "", "default", "default", "dc1", nil), - "bar.default.default.dc1": newTarget("bar", "", "default", "default", "dc1", nil), + "foo.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "foo"}, nil), + "bar.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "bar"}, nil), }, } @@ -935,7 +949,10 @@ func testcase_SplitBypassesSplit() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "bypassed.next.default.default.dc1": newTarget("next", "bypassed", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "bypassed.next.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "next", + ServiceSubset: "bypassed", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == bypass", } @@ -973,7 +990,7 @@ func testcase_ServiceRedirect() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "other.default.default.dc1": newTarget("other", "", "default", "default", "dc1", nil), + "other.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "other"}, nil), }, } @@ -1019,7 +1036,10 @@ func testcase_ServiceAndSubsetRedirect() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "v2.other.default.default.dc1": newTarget("other", "v2", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v2.other.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "other", + ServiceSubset: "v2", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 2", } @@ -1055,7 +1075,10 @@ func testcase_DatacenterRedirect() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc9": newTarget("main", "", "default", "default", "dc9", nil), + "main.default.default.dc9": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc9", + }, nil), }, } return compileTestCase{entries: entries, expect: expect} @@ -1095,7 +1118,10 @@ func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc9": newTarget("main", "", "default", "default", "dc9", func(t *structs.DiscoveryTarget) { + "main.default.default.dc9": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc9", + }, func(t *structs.DiscoveryTarget) { t.MeshGateway = structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, } @@ -1134,8 +1160,8 @@ func testcase_ServiceFailover() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), - "backup.default.default.dc1": newTarget("backup", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), + "backup.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "backup"}, nil), }, } return compileTestCase{entries: entries, expect: expect} @@ -1177,8 +1203,8 @@ func testcase_ServiceFailoverThroughRedirect() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), - "actual.default.default.dc1": newTarget("actual", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), + "actual.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "actual"}, nil), }, } return compileTestCase{entries: entries, expect: expect} @@ -1220,8 +1246,8 @@ func testcase_Resolver_CircularFailover() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), - "backup.default.default.dc1": newTarget("backup", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), + "backup.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "backup"}, nil), }, } return compileTestCase{entries: entries, expect: expect} @@ -1261,8 +1287,11 @@ func testcase_ServiceAndSubsetFailover() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), - "backup.main.default.default.dc1": newTarget("main", "backup", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), + "backup.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "backup", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == backup", } @@ -1301,9 +1330,15 @@ func testcase_DatacenterFailover() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), - "main.default.default.dc2": newTarget("main", "", "default", "default", "dc2", nil), - "main.default.default.dc4": newTarget("main", "", "default", "default", "dc4", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), + "main.default.default.dc2": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc2", + }, nil), + "main.default.default.dc4": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc4", + }, nil), }, } return compileTestCase{entries: entries, expect: expect} @@ -1350,17 +1385,105 @@ func testcase_DatacenterFailover_WithMeshGateways() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, func(t *structs.DiscoveryTarget) { t.MeshGateway = structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, } }), - "main.default.default.dc2": newTarget("main", "", "default", "default", "dc2", func(t *structs.DiscoveryTarget) { + "main.default.default.dc2": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc2", + }, func(t *structs.DiscoveryTarget) { t.MeshGateway = structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, } }), - "main.default.default.dc4": newTarget("main", "", "default", "default", "dc4", func(t *structs.DiscoveryTarget) { + "main.default.default.dc4": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc4", + }, func(t *structs.DiscoveryTarget) { + t.MeshGateway = structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + } + }), + }, + } + return compileTestCase{entries: entries, expect: expect} +} + +func testcase_Failover_Targets() compileTestCase { + entries := newEntries() + + entries.AddProxyDefaults(&structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + MeshGateway: structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + }, + }) + + entries.AddResolvers( + &structs.ServiceResolverConfigEntry{ + Kind: "service-resolver", + Name: "main", + Failover: map[string]structs.ServiceResolverFailover{ + "*": { + Targets: []structs.ServiceResolverFailoverTarget{ + {Datacenter: "dc3"}, + {Service: "new-main"}, + {Peer: "cluster-01"}, + }, + }, + }, + }, + ) + + expect := &structs.CompiledDiscoveryChain{ + Protocol: "tcp", + StartNode: "resolver:main.default.default.dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main.default.default.dc1": { + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main.default.default.dc1", + Resolver: &structs.DiscoveryResolver{ + ConnectTimeout: 5 * time.Second, + Target: "main.default.default.dc1", + Failover: &structs.DiscoveryFailover{ + Targets: []string{ + "main.default.default.dc3", + "new-main.default.default.dc1", + "main.default.default.external.cluster-01", + }, + }, + }, + }, + }, + Targets: map[string]*structs.DiscoveryTarget{ + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, func(t *structs.DiscoveryTarget) { + t.MeshGateway = structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + } + }), + "main.default.default.dc3": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc3", + }, func(t *structs.DiscoveryTarget) { + t.MeshGateway = structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + } + }), + "new-main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "new-main"}, func(t *structs.DiscoveryTarget) { + t.MeshGateway = structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + } + }), + "main.default.default.external.cluster-01": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Peer: "cluster-01", + }, func(t *structs.DiscoveryTarget) { + t.SNI = "" + t.Name = "" + t.Datacenter = "" t.MeshGateway = structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, } @@ -1422,7 +1545,10 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "v2.main.default.default.dc1": newTarget("main", "v2", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v2.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v2", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 2", } @@ -1452,7 +1578,7 @@ func testcase_DefaultResolver() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ // TODO-TARGET - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } return compileTestCase{entries: entries, expect: expect} @@ -1488,7 +1614,7 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, func(t *structs.DiscoveryTarget) { t.MeshGateway = structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, } @@ -1530,7 +1656,7 @@ func testcase_ServiceMetaProjection() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } @@ -1588,7 +1714,7 @@ func testcase_ServiceMetaProjectionWithRedirect() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "other.default.default.dc1": newTarget("other", "", "default", "default", "dc1", nil), + "other.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "other"}, nil), }, } @@ -1623,7 +1749,7 @@ func testcase_RedirectToDefaultResolverIsNotDefaultChain() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "other.default.default.dc1": newTarget("other", "", "default", "default", "dc1", nil), + "other.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "other"}, nil), }, } @@ -1658,7 +1784,10 @@ func testcase_Resolve_WithDefaultSubset() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "v2.main.default.default.dc1": newTarget("main", "v2", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v2.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v2", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 2", } @@ -1692,7 +1821,7 @@ func testcase_DefaultResolver_ExternalSNI() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, func(t *structs.DiscoveryTarget) { t.SNI = "main.some.other.service.mesh" t.External = true }), @@ -1857,11 +1986,17 @@ func testcase_MultiDatacenterCanary() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ "main.default.default.dc2": targetWithConnectTimeout( - newTarget("main", "", "default", "default", "dc2", nil), + newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc2", + }, nil), 33*time.Second, ), "main.default.default.dc3": targetWithConnectTimeout( - newTarget("main", "", "default", "default", "dc3", nil), + newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + Datacenter: "dc3", + }, nil), 33*time.Second, ), }, @@ -2155,27 +2290,42 @@ func testcase_AllBellsAndWhistles() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "prod.redirected.default.default.dc1": newTarget("redirected", "prod", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "prod.redirected.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "redirected", + ServiceSubset: "prod", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "ServiceMeta.env == prod", } }), - "v1.main.default.default.dc1": newTarget("main", "v1", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v1.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v1", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 1", } }), - "v2.main.default.default.dc1": newTarget("main", "v2", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v2.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v2", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 2", } }), - "v3.main.default.default.dc1": newTarget("main", "v3", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "v3.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "v3", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{ Filter: "Service.Meta.version == 3", } }), - "default-subset.main.default.default.dc1": newTarget("main", "default-subset", "default", "default", "dc1", func(t *structs.DiscoveryTarget) { + "default-subset.main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{ + Service: "main", + ServiceSubset: "default-subset", + }, func(t *structs.DiscoveryTarget) { t.Subset = structs.ServiceResolverSubset{OnlyPassing: true} }), }, @@ -2379,7 +2529,7 @@ func testcase_ResolverProtocolOverride() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ // TODO-TARGET - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } return compileTestCase{entries: entries, expect: expect, @@ -2413,7 +2563,7 @@ func testcase_ResolverProtocolOverrideIgnored() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ // TODO-TARGET - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } return compileTestCase{entries: entries, expect: expect, @@ -2451,7 +2601,7 @@ func testcase_RouterIgnored_ResolverProtocolOverride() compileTestCase { }, Targets: map[string]*structs.DiscoveryTarget{ // TODO-TARGET - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } return compileTestCase{entries: entries, expect: expect, @@ -2685,9 +2835,9 @@ func testcase_LBSplitterAndResolver() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "foo.default.default.dc1": newTarget("foo", "", "default", "default", "dc1", nil), - "bar.default.default.dc1": newTarget("bar", "", "default", "default", "dc1", nil), - "baz.default.default.dc1": newTarget("baz", "", "default", "default", "dc1", nil), + "foo.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "foo"}, nil), + "bar.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "bar"}, nil), + "baz.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "baz"}, nil), }, } @@ -2743,7 +2893,7 @@ func testcase_LBResolver() compileTestCase { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "main.default.default.dc1": newTarget("main", "", "default", "default", "dc1", nil), + "main.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "main"}, nil), }, } @@ -2791,8 +2941,17 @@ func newEntries() *configentry.DiscoveryChainSet { } } -func newTarget(service, serviceSubset, namespace, partition, datacenter string, modFn func(t *structs.DiscoveryTarget)) *structs.DiscoveryTarget { - t := structs.NewDiscoveryTarget(service, serviceSubset, namespace, partition, datacenter) +func newTarget(opts structs.DiscoveryTargetOpts, modFn func(t *structs.DiscoveryTarget)) *structs.DiscoveryTarget { + if opts.Namespace == "" { + opts.Namespace = "default" + } + if opts.Partition == "" { + opts.Partition = "default" + } + if opts.Datacenter == "" { + opts.Datacenter = "dc1" + } + t := structs.NewDiscoveryTarget(opts) t.SNI = connect.TargetSNI(t, "trustdomain.consul") t.Name = t.SNI t.ConnectTimeout = 5 * time.Second // default diff --git a/agent/consul/fsm/commands_oss.go b/agent/consul/fsm/commands_oss.go index 83e863ca5c..c8512569d0 100644 --- a/agent/consul/fsm/commands_oss.go +++ b/agent/consul/fsm/commands_oss.go @@ -720,9 +720,9 @@ func (c *FSM) applyPeeringDelete(buf []byte, index uint64) interface{} { } func (c *FSM) applyPeeringSecretsWrite(buf []byte, index uint64) interface{} { - var req pbpeering.PeeringSecrets + var req pbpeering.SecretsWriteRequest if err := structs.DecodeProto(buf, &req); err != nil { - panic(fmt.Errorf("failed to decode peering write request: %v", err)) + panic(fmt.Errorf("failed to decode peering secrets write request: %v", err)) } defer metrics.MeasureSinceWithLabels([]string{"fsm", "peering_secrets"}, time.Now(), diff --git a/agent/consul/fsm/snapshot_oss.go b/agent/consul/fsm/snapshot_oss.go index a58f0cf1db..7fa53381a7 100644 --- a/agent/consul/fsm/snapshot_oss.go +++ b/agent/consul/fsm/snapshot_oss.go @@ -1,8 +1,12 @@ package fsm import ( + "fmt" + "net" + "github.com/hashicorp/consul-net-rpc/go-msgpack/codec" "github.com/hashicorp/raft" + "github.com/mitchellh/mapstructure" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" @@ -38,6 +42,7 @@ func init() { registerRestorer(structs.FreeVirtualIPRequestType, restoreFreeVirtualIP) registerRestorer(structs.PeeringWriteType, restorePeering) registerRestorer(structs.PeeringTrustBundleWriteType, restorePeeringTrustBundle) + registerRestorer(structs.PeeringSecretsWriteType, restorePeeringSecrets) } func persistOSS(s *snapshot, sink raft.SnapshotSink, encoder *codec.Encoder) error { @@ -95,6 +100,9 @@ func persistOSS(s *snapshot, sink raft.SnapshotSink, encoder *codec.Encoder) err if err := s.persistPeeringTrustBundles(sink, encoder); err != nil { return err } + if err := s.persistPeeringSecrets(sink, encoder); err != nil { + return err + } return nil } @@ -582,6 +590,24 @@ func (s *snapshot) persistPeeringTrustBundles(sink raft.SnapshotSink, encoder *c return nil } +func (s *snapshot) persistPeeringSecrets(sink raft.SnapshotSink, encoder *codec.Encoder) error { + secrets, err := s.state.PeeringSecrets() + if err != nil { + return err + } + + for entry := secrets.Next(); entry != nil; entry = secrets.Next() { + if _, err := sink.Write([]byte{byte(structs.PeeringSecretsWriteType)}); err != nil { + return err + } + if err := encoder.Encode(entry.(*pbpeering.PeeringSecrets)); err != nil { + return err + } + } + + return nil +} + func restoreRegistration(header *SnapshotHeader, restore *state.Restore, decoder *codec.Decoder) error { var req structs.RegisterRequest if err := decoder.Decode(&req); err != nil { @@ -864,11 +890,43 @@ func restoreSystemMetadata(header *SnapshotHeader, restore *state.Restore, decod } func restoreServiceVirtualIP(header *SnapshotHeader, restore *state.Restore, decoder *codec.Decoder) error { - var req state.ServiceVirtualIP + // state.ServiceVirtualIP was changed in a breaking way in 1.13.0 (2e4cb6f77d2be36b02e9be0b289b24e5b0afb794). + // We attempt to reconcile the older type by decoding to a map then decoding that map into + // structs.PeeredServiceName first, and then structs.ServiceName. + var req struct { + Service map[string]interface{} + IP net.IP + + structs.RaftIndex + } if err := decoder.Decode(&req); err != nil { return err } - if err := restore.ServiceVirtualIP(req); err != nil { + + vip := state.ServiceVirtualIP{ + IP: req.IP, + RaftIndex: req.RaftIndex, + } + + // PeeredServiceName is the expected primary key type. + var psn structs.PeeredServiceName + if err := mapstructure.Decode(req.Service, &psn); err != nil { + return fmt.Errorf("cannot decode to structs.PeeredServiceName: %w", err) + } + vip.Service = psn + + // If the expected primary key field is empty, it must be the older ServiceName type. + if vip.Service.ServiceName.Name == "" { + var sn structs.ServiceName + if err := mapstructure.Decode(req.Service, &sn); err != nil { + return fmt.Errorf("cannot decode to structs.ServiceName: %w", err) + } + vip.Service = structs.PeeredServiceName{ + ServiceName: sn, + } + } + + if err := restore.ServiceVirtualIP(vip); err != nil { return err } return nil @@ -906,3 +964,14 @@ func restorePeeringTrustBundle(header *SnapshotHeader, restore *state.Restore, d } return nil } + +func restorePeeringSecrets(header *SnapshotHeader, restore *state.Restore, decoder *codec.Decoder) error { + var req pbpeering.PeeringSecrets + if err := decoder.Decode(&req); err != nil { + return err + } + if err := restore.PeeringSecrets(&req); err != nil { + return err + } + return nil +} diff --git a/agent/consul/fsm/snapshot_oss_test.go b/agent/consul/fsm/snapshot_oss_test.go index 36a75bbf13..2b2d3e8701 100644 --- a/agent/consul/fsm/snapshot_oss_test.go +++ b/agent/consul/fsm/snapshot_oss_test.go @@ -3,6 +3,7 @@ package fsm import ( "bytes" "fmt" + "net" "testing" "time" @@ -18,6 +19,7 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib/stringslice" "github.com/hashicorp/consul/proto/pbpeering" + "github.com/hashicorp/consul/proto/prototest" "github.com/hashicorp/consul/sdk/testutil" ) @@ -482,6 +484,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) { ID: "1fabcd52-1d46-49b0-b1d8-71559aee47f5", Name: "baz", }, + SecretsRequest: &pbpeering.SecretsWriteRequest{ + PeerID: "1fabcd52-1d46-49b0-b1d8-71559aee47f5", + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: "baaeea83-8419-4aa8-ac89-14e7246a3d2f", + }, + }, + }, })) // Peering Trust Bundles @@ -491,6 +501,27 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) { RootPEMs: []string{"qux certificate bundle"}, })) + // Issue two more secrets writes so that there are three secrets associated with the peering: + // - Establishment: "389bbcdf-1c31-47d6-ae96-f2a3f4c45f84" + // - Pending: "0b7812d4-32d9-4e54-b1b3-4d97084982a0" + require.NoError(t, fsm.state.PeeringSecretsWrite(34, &pbpeering.SecretsWriteRequest{ + PeerID: "1fabcd52-1d46-49b0-b1d8-71559aee47f5", + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + EstablishmentSecret: "baaeea83-8419-4aa8-ac89-14e7246a3d2f", + PendingStreamSecret: "0b7812d4-32d9-4e54-b1b3-4d97084982a0", + }, + }, + })) + require.NoError(t, fsm.state.PeeringSecretsWrite(33, &pbpeering.SecretsWriteRequest{ + PeerID: "1fabcd52-1d46-49b0-b1d8-71559aee47f5", + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: "389bbcdf-1c31-47d6-ae96-f2a3f4c45f84", + }, + }, + })) + // Snapshot snap, err := fsm.Snapshot() require.NoError(t, err) @@ -797,6 +828,29 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) { require.NotNil(t, prngRestored) require.Equal(t, "baz", prngRestored.Name) + // Verify peering secrets are restored + secretsRestored, err := fsm2.state.PeeringSecretsRead(nil, "1fabcd52-1d46-49b0-b1d8-71559aee47f5") + require.NoError(t, err) + expectSecrets := &pbpeering.PeeringSecrets{ + PeerID: "1fabcd52-1d46-49b0-b1d8-71559aee47f5", + Establishment: &pbpeering.PeeringSecrets_Establishment{ + SecretID: "389bbcdf-1c31-47d6-ae96-f2a3f4c45f84", + }, + Stream: &pbpeering.PeeringSecrets_Stream{ + PendingSecretID: "0b7812d4-32d9-4e54-b1b3-4d97084982a0", + }, + } + prototest.AssertDeepEqual(t, expectSecrets, secretsRestored) + + uuids := []string{"389bbcdf-1c31-47d6-ae96-f2a3f4c45f84", "0b7812d4-32d9-4e54-b1b3-4d97084982a0"} + for _, id := range uuids { + free, err := fsm2.state.ValidateProposedPeeringSecretUUID(id) + require.NoError(t, err) + + // The UUIDs in the peering secret should be tracked as in use. + require.False(t, free) + } + // Verify peering trust bundle is restored idx, ptbRestored, err := fsm2.state.PeeringTrustBundleRead(nil, state.Query{ Value: "qux", @@ -909,3 +963,66 @@ func TestFSM_BadSnapshot_NilCAConfig(t *testing.T) { require.EqualValues(t, 0, idx) require.Nil(t, config) } + +// This test asserts that ServiceVirtualIP, which made a breaking change +// in 1.13.0, can still restore from older snapshots which use the old +// state.ServiceVirtualIP type. +func Test_restoreServiceVirtualIP(t *testing.T) { + psn := structs.PeeredServiceName{ + ServiceName: structs.ServiceName{ + Name: "foo", + }, + } + + run := func(t *testing.T, input interface{}) { + t.Helper() + + var b []byte + buf := bytes.NewBuffer(b) + // Encode input + encoder := codec.NewEncoder(buf, structs.MsgpackHandle) + require.NoError(t, encoder.Encode(input)) + + // Create a decoder + dec := codec.NewDecoder(buf, structs.MsgpackHandle) + + logger := testutil.Logger(t) + fsm, err := New(nil, logger) + require.NoError(t, err) + + restore := fsm.State().Restore() + + // Call restore + require.NoError(t, restoreServiceVirtualIP(nil, restore, dec)) + require.NoError(t, restore.Commit()) + + ip, err := fsm.State().VirtualIPForService(psn) + require.NoError(t, err) + + // 240->224 due to addIPOffset + require.Equal(t, "224.0.0.2", ip) + } + + t.Run("new ServiceVirtualIP with PeeredServiceName", func(t *testing.T) { + run(t, state.ServiceVirtualIP{ + Service: psn, + IP: net.ParseIP("240.0.0.2"), + RaftIndex: structs.RaftIndex{}, + }) + }) + t.Run("pre-1.13.0 ServiceVirtualIP with ServiceName", func(t *testing.T) { + type compatServiceVirtualIP struct { + Service structs.ServiceName + IP net.IP + RaftIndex structs.RaftIndex + } + + run(t, compatServiceVirtualIP{ + Service: structs.ServiceName{ + Name: "foo", + }, + IP: net.ParseIP("240.0.0.2"), + RaftIndex: structs.RaftIndex{}, + }) + }) +} diff --git a/agent/consul/leader_peering.go b/agent/consul/leader_peering.go index aa425c7380..556f1b5bfc 100644 --- a/agent/consul/leader_peering.go +++ b/agent/consul/leader_peering.go @@ -31,11 +31,18 @@ import ( ) var leaderExportedServicesCountKey = []string{"consul", "peering", "exported_services"} +var leaderHealthyPeeringKey = []string{"consul", "peering", "healthy"} var LeaderPeeringMetrics = []prometheus.GaugeDefinition{ { Name: leaderExportedServicesCountKey, Help: "A gauge that tracks how many services are exported for the peering. " + - "The labels are \"peering\" and, for enterprise, \"partition\". " + + "The labels are \"peer_name\", \"peer_id\" and, for enterprise, \"partition\". " + + "We emit this metric every 9 seconds", + }, + { + Name: leaderHealthyPeeringKey, + Help: "A gauge that tracks how if a peering is healthy (1) or not (0). " + + "The labels are \"peer_name\", \"peer_id\" and, for enterprise, \"partition\". " + "We emit this metric every 9 seconds", }, } @@ -85,13 +92,6 @@ func (s *Server) emitPeeringMetricsOnce(logger hclog.Logger, metricsImpl *metric } for _, peer := range peers { - status, found := s.peerStreamServer.StreamStatus(peer.ID) - if !found { - logger.Trace("did not find status for", "peer_name", peer.Name) - continue - } - - esc := status.GetExportedServicesCount() part := peer.Partition labels := []metrics.Label{ {Name: "peer_name", Value: peer.Name}, @@ -101,7 +101,25 @@ func (s *Server) emitPeeringMetricsOnce(logger hclog.Logger, metricsImpl *metric labels = append(labels, metrics.Label{Name: "partition", Value: part}) } - metricsImpl.SetGaugeWithLabels(leaderExportedServicesCountKey, float32(esc), labels) + status, found := s.peerStreamServer.StreamStatus(peer.ID) + if found { + // exported services count metric + esc := status.GetExportedServicesCount() + metricsImpl.SetGaugeWithLabels(leaderExportedServicesCountKey, float32(esc), labels) + } + + // peering health metric + if status.NeverConnected { + metricsImpl.SetGaugeWithLabels(leaderHealthyPeeringKey, float32(math.NaN()), labels) + } else { + healthy := status.IsHealthy() + healthyInt := 0 + if healthy { + healthyInt = 1 + } + + metricsImpl.SetGaugeWithLabels(leaderHealthyPeeringKey, float32(healthyInt), labels) + } } return nil @@ -237,7 +255,7 @@ func (s *Server) syncPeeringsAndBlock(ctx context.Context, logger hclog.Logger, } } - logger.Trace("checking connected streams", "streams", s.peerStreamServer.ConnectedStreams(), "sequence_id", seq) + logger.Trace("checking connected streams", "streams", connectedStreams, "sequence_id", seq) // Clean up active streams of peerings that were deleted from the state store. // TODO(peering): This is going to trigger shutting down peerings we generated a token for. Is that OK? @@ -277,13 +295,6 @@ func (s *Server) establishStream(ctx context.Context, logger hclog.Logger, ws me return fmt.Errorf("failed to build TLS dial option from peering: %w", err) } - // Create a ring buffer to cycle through peer addresses in the retry loop below. - buffer := ring.New(len(peer.PeerServerAddresses)) - for _, addr := range peer.PeerServerAddresses { - buffer.Value = addr - buffer = buffer.Next() - } - secret, err := s.fsm.State().PeeringSecretsRead(ws, peer.ID) if err != nil { return fmt.Errorf("failed to read secret for peering: %w", err) @@ -294,27 +305,26 @@ func (s *Server) establishStream(ctx context.Context, logger hclog.Logger, ws me logger.Trace("establishing stream to peer") - retryCtx, cancel := context.WithCancel(ctx) - cancelFns[peer.ID] = cancel - streamStatus, err := s.peerStreamTracker.Register(peer.ID) if err != nil { return fmt.Errorf("failed to register stream: %v", err) } + streamCtx, cancel := context.WithCancel(ctx) + cancelFns[peer.ID] = cancel + + // Start a goroutine to watch for updates to peer server addresses. + // The latest valid server address can be received from nextServerAddr. + nextServerAddr := make(chan string) + go s.watchPeerServerAddrs(streamCtx, peer, nextServerAddr) + // Establish a stream-specific retry so that retrying stream/conn errors isn't dependent on state store changes. - go retryLoopBackoffPeering(retryCtx, logger, func() error { + go retryLoopBackoffPeering(streamCtx, logger, func() error { // Try a new address on each iteration by advancing the ring buffer on errors. - defer func() { - buffer = buffer.Next() - }() - addr, ok := buffer.Value.(string) - if !ok { - return fmt.Errorf("peer server address type %T is not a string", buffer.Value) - } + addr := <-nextServerAddr logger.Trace("dialing peer", "addr", addr) - conn, err := grpc.DialContext(retryCtx, addr, + conn, err := grpc.DialContext(streamCtx, addr, // TODO(peering): use a grpc.WithStatsHandler here?) tlsOption, // For keep alive parameters there is a larger comment in ClientConnPool.dial about that. @@ -331,7 +341,7 @@ func (s *Server) establishStream(ctx context.Context, logger hclog.Logger, ws me defer conn.Close() client := pbpeerstream.NewPeerStreamServiceClient(conn) - stream, err := client.StreamResources(retryCtx) + stream, err := client.StreamResources(streamCtx) if err != nil { return err } @@ -379,6 +389,74 @@ func (s *Server) establishStream(ctx context.Context, logger hclog.Logger, ws me return nil } +// watchPeerServerAddrs sends an up-to-date peer server address to nextServerAddr. +// It loads the server addresses into a ring buffer and cycles through them until: +// 1. streamCtx is cancelled (peer is deleted) +// 2. the peer is modified and the watchset fires. +// +// In case (2) we refetch the peering and rebuild the ring buffer. +func (s *Server) watchPeerServerAddrs(ctx context.Context, peer *pbpeering.Peering, nextServerAddr chan<- string) { + defer close(nextServerAddr) + + // we initialize the ring buffer with the peer passed to `establishStream` + // because the caller has pre-checked `peer.ShouldDial`, guaranteeing + // at least one server address. + // + // IMPORTANT: ringbuf must always be length > 0 or else `<-nextServerAddr` may block. + ringbuf := ring.New(len(peer.PeerServerAddresses)) + for _, addr := range peer.PeerServerAddresses { + ringbuf.Value = addr + ringbuf = ringbuf.Next() + } + innerWs := memdb.NewWatchSet() + _, _, err := s.fsm.State().PeeringReadByID(innerWs, peer.ID) + if err != nil { + s.logger.Warn("failed to watch for changes to peer; server addresses may become stale over time.", + "peer_id", peer.ID, + "error", err) + } + + fetchAddrs := func() error { + // reinstantiate innerWs to prevent it from growing indefinitely + innerWs = memdb.NewWatchSet() + _, peering, err := s.fsm.State().PeeringReadByID(innerWs, peer.ID) + if err != nil { + return fmt.Errorf("failed to fetch peer %q: %w", peer.ID, err) + } + if !peering.IsActive() { + return fmt.Errorf("peer %q is no longer active", peer.ID) + } + if len(peering.PeerServerAddresses) == 0 { + return fmt.Errorf("peer %q has no addresses to dial", peer.ID) + } + + ringbuf = ring.New(len(peering.PeerServerAddresses)) + for _, addr := range peering.PeerServerAddresses { + ringbuf.Value = addr + ringbuf = ringbuf.Next() + } + return nil + } + + for { + select { + case nextServerAddr <- ringbuf.Value.(string): + ringbuf = ringbuf.Next() + case err := <-innerWs.WatchCh(ctx): + if err != nil { + // context was cancelled + return + } + // watch fired so we refetch the peering and rebuild the ring buffer + if err := fetchAddrs(); err != nil { + s.logger.Warn("watchset for peer was fired but failed to update server addresses", + "peer_id", peer.ID, + "error", err) + } + } + } +} + func (s *Server) startPeeringDeferredDeletion(ctx context.Context) { s.leaderRoutineManager.Start(ctx, peeringDeletionRoutineName, s.runPeeringDeletions) } @@ -391,6 +469,12 @@ func (s *Server) runPeeringDeletions(ctx context.Context) error { // process. This includes deletion of the peerings themselves in addition to any peering data raftLimiter := rate.NewLimiter(defaultDeletionApplyRate, int(defaultDeletionApplyRate)) for { + select { + case <-ctx.Done(): + return nil + default: + } + ws := memdb.NewWatchSet() state := s.fsm.State() _, peerings, err := s.fsm.State().PeeringListDeleted(ws) @@ -606,6 +690,15 @@ func isFailedPreconditionErr(err error) bool { if err == nil { return false } + + // Handle wrapped errors, since status.FromError does a naive assertion. + var statusErr interface { + GRPCStatus() *grpcstatus.Status + } + if errors.As(err, &statusErr) { + return statusErr.GRPCStatus().Code() == codes.FailedPrecondition + } + grpcErr, ok := grpcstatus.FromError(err) if !ok { return false diff --git a/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index fd724e373d..b8b5166d8f 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -7,15 +7,18 @@ import ( "errors" "fmt" "io/ioutil" + "math" "testing" "time" "github.com/armon/go-metrics" "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" grpcstatus "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/state" @@ -23,6 +26,7 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto/pbpeering" "github.com/hashicorp/consul/sdk/freeport" + "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" "github.com/hashicorp/consul/types" @@ -41,8 +45,8 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo t.Skip("too slow for testing.Short") } - _, s1 := testServerWithConfig(t, func(c *Config) { - c.NodeName = "bob" + _, acceptor := testServerWithConfig(t, func(c *Config) { + c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" if enableTLS { @@ -51,25 +55,25 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" } }) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + testrpc.WaitForLeader(t, acceptor.RPC, "dc1") // Create a peering by generating a token ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) t.Cleanup(cancel) - conn, err := grpc.DialContext(ctx, s1.config.RPCAddr.String(), - grpc.WithContextDialer(newServerDialer(s1.config.RPCAddr.String())), + conn, err := grpc.DialContext(ctx, acceptor.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(acceptor.config.RPCAddr.String())), grpc.WithInsecure(), grpc.WithBlock()) require.NoError(t, err) defer conn.Close() - s1Client := pbpeering.NewPeeringServiceClient(conn) + acceptorClient := pbpeering.NewPeeringServiceClient(conn) req := pbpeering.GenerateTokenRequest{ - PeerName: "my-peer-s2", + PeerName: "my-peer-dialer", } - resp, err := s1Client.GenerateToken(ctx, &req) + resp, err := acceptorClient.GenerateToken(ctx, &req) require.NoError(t, err) tokenJSON, err := base64.StdEncoding.DecodeString(resp.PeeringToken) @@ -78,14 +82,14 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo var token structs.PeeringToken require.NoError(t, json.Unmarshal(tokenJSON, &token)) - // S1 should not have a stream tracked for dc2 because s1 generated a token for baz, and therefore needs to wait to be dialed. + // S1 should not have a stream tracked for dc2 because acceptor generated a token for baz, and therefore needs to wait to be dialed. time.Sleep(1 * time.Second) - _, found := s1.peerStreamServer.StreamStatus(token.PeerID) + _, found := acceptor.peerStreamServer.StreamStatus(token.PeerID) require.False(t, found) - // Bring up s2 and establish a peering with s1's token so that it attempts to dial. - _, s2 := testServerWithConfig(t, func(c *Config) { - c.NodeName = "betty" + // Bring up dialer and establish a peering with acceptor's token so that it attempts to dial. + _, dialer := testServerWithConfig(t, func(c *Config) { + c.NodeName = "dialer" c.Datacenter = "dc2" c.PrimaryDatacenter = "dc2" if enableTLS { @@ -94,33 +98,39 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Betty.key" } }) - testrpc.WaitForLeader(t, s2.RPC, "dc2") + testrpc.WaitForLeader(t, dialer.RPC, "dc2") - // Create a peering at s2 by establishing a peering with s1's token + // Create a peering at dialer by establishing a peering with acceptor's token ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) t.Cleanup(cancel) - conn, err = grpc.DialContext(ctx, s2.config.RPCAddr.String(), - grpc.WithContextDialer(newServerDialer(s2.config.RPCAddr.String())), + conn, err = grpc.DialContext(ctx, dialer.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(dialer.config.RPCAddr.String())), grpc.WithInsecure(), grpc.WithBlock()) require.NoError(t, err) defer conn.Close() - s2Client := pbpeering.NewPeeringServiceClient(conn) + dialerClient := pbpeering.NewPeeringServiceClient(conn) establishReq := pbpeering.EstablishRequest{ - PeerName: "my-peer-s1", + PeerName: "my-peer-acceptor", PeeringToken: resp.PeeringToken, } - _, err = s2Client.Establish(ctx, &establishReq) + _, err = dialerClient.Establish(ctx, &establishReq) require.NoError(t, err) - p, err := s2Client.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-s1"}) + p, err := dialerClient.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-acceptor"}) require.NoError(t, err) retry.Run(t, func(r *retry.R) { - status, found := s2.peerStreamServer.StreamStatus(p.Peering.ID) + status, found := dialer.peerStreamServer.StreamStatus(p.Peering.ID) + require.True(r, found) + require.True(r, status.Connected) + }) + + retry.Run(t, func(r *retry.R) { + status, found := acceptor.peerStreamServer.StreamStatus(p.Peering.PeerID) require.True(r, found) require.True(r, status.Connected) }) @@ -128,21 +138,21 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo // Delete the peering to trigger the termination sequence. deleted := &pbpeering.Peering{ ID: p.Peering.ID, - Name: "my-peer-s1", + Name: "my-peer-acceptor", DeletedAt: structs.TimeToProto(time.Now()), } - require.NoError(t, s2.fsm.State().PeeringWrite(2000, &pbpeering.PeeringWriteRequest{Peering: deleted})) - s2.logger.Trace("deleted peering for my-peer-s1") + require.NoError(t, dialer.fsm.State().PeeringWrite(2000, &pbpeering.PeeringWriteRequest{Peering: deleted})) + dialer.logger.Trace("deleted peering for my-peer-acceptor") retry.Run(t, func(r *retry.R) { - _, found := s2.peerStreamServer.StreamStatus(p.Peering.ID) + _, found := dialer.peerStreamServer.StreamStatus(p.Peering.ID) require.False(r, found) }) - // s1 should have also marked the peering as terminated. + // acceptor should have also marked the peering as terminated. retry.Run(t, func(r *retry.R) { - _, peering, err := s1.fsm.State().PeeringRead(nil, state.Query{ - Value: "my-peer-s2", + _, peering, err := acceptor.fsm.State().PeeringRead(nil, state.Query{ + Value: "my-peer-dialer", }) require.NoError(r, err) require.Equal(r, pbpeering.PeeringState_TERMINATED, peering.State) @@ -151,20 +161,20 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo func TestLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T) { t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ServerDeletion(t, false) + testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, false) }) t.Run("with-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ServerDeletion(t, true) + testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, true) }) } -func testLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T, enableTLS bool) { +func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, enableTLS bool) { if testing.Short() { t.Skip("too slow for testing.Short") } - _, s1 := testServerWithConfig(t, func(c *Config) { - c.NodeName = "bob" + _, acceptor := testServerWithConfig(t, func(c *Config) { + c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" if enableTLS { @@ -173,14 +183,14 @@ func testLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T, enableTLS boo c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" } }) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + testrpc.WaitForLeader(t, acceptor.RPC, "dc1") - // Define a peering by generating a token for s2 + // Define a peering by generating a token for dialer ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) t.Cleanup(cancel) - conn, err := grpc.DialContext(ctx, s1.config.RPCAddr.String(), - grpc.WithContextDialer(newServerDialer(s1.config.RPCAddr.String())), + conn, err := grpc.DialContext(ctx, acceptor.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(acceptor.config.RPCAddr.String())), grpc.WithInsecure(), grpc.WithBlock()) require.NoError(t, err) @@ -189,7 +199,7 @@ func testLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T, enableTLS boo peeringClient := pbpeering.NewPeeringServiceClient(conn) req := pbpeering.GenerateTokenRequest{ - PeerName: "my-peer-s2", + PeerName: "my-peer-dialer", } resp, err := peeringClient.GenerateToken(ctx, &req) require.NoError(t, err) @@ -200,9 +210,9 @@ func testLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T, enableTLS boo var token structs.PeeringToken require.NoError(t, json.Unmarshal(tokenJSON, &token)) - // Bring up s2 and establish a peering with s1's token so that it attempts to dial. - _, s2 := testServerWithConfig(t, func(c *Config) { - c.NodeName = "betty" + // Bring up dialer and establish a peering with acceptor's token so that it attempts to dial. + _, dialer := testServerWithConfig(t, func(c *Config) { + c.NodeName = "dialer" c.Datacenter = "dc2" c.PrimaryDatacenter = "dc2" if enableTLS { @@ -211,33 +221,39 @@ func testLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T, enableTLS boo c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Betty.key" } }) - testrpc.WaitForLeader(t, s2.RPC, "dc2") + testrpc.WaitForLeader(t, dialer.RPC, "dc2") - // Create a peering at s2 by establishing a peering with s1's token + // Create a peering at dialer by establishing a peering with acceptor's token ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) t.Cleanup(cancel) - conn, err = grpc.DialContext(ctx, s2.config.RPCAddr.String(), - grpc.WithContextDialer(newServerDialer(s2.config.RPCAddr.String())), + conn, err = grpc.DialContext(ctx, dialer.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(dialer.config.RPCAddr.String())), grpc.WithInsecure(), grpc.WithBlock()) require.NoError(t, err) defer conn.Close() - s2Client := pbpeering.NewPeeringServiceClient(conn) + dialerClient := pbpeering.NewPeeringServiceClient(conn) establishReq := pbpeering.EstablishRequest{ - PeerName: "my-peer-s1", + PeerName: "my-peer-acceptor", PeeringToken: resp.PeeringToken, } - _, err = s2Client.Establish(ctx, &establishReq) + _, err = dialerClient.Establish(ctx, &establishReq) require.NoError(t, err) - p, err := s2Client.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-s1"}) + p, err := dialerClient.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-acceptor"}) require.NoError(t, err) retry.Run(t, func(r *retry.R) { - status, found := s2.peerStreamServer.StreamStatus(p.Peering.ID) + status, found := dialer.peerStreamServer.StreamStatus(p.Peering.ID) + require.True(r, found) + require.True(r, status.Connected) + }) + + retry.Run(t, func(r *retry.R) { + status, found := acceptor.peerStreamServer.StreamStatus(p.Peering.PeerID) require.True(r, found) require.True(r, status.Connected) }) @@ -245,21 +261,22 @@ func testLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T, enableTLS boo // Delete the peering from the server peer to trigger the termination sequence. deleted := &pbpeering.Peering{ ID: p.Peering.PeerID, - Name: "my-peer-s2", + Name: "my-peer-dialer", DeletedAt: structs.TimeToProto(time.Now()), } - require.NoError(t, s1.fsm.State().PeeringWrite(2000, &pbpeering.PeeringWriteRequest{Peering: deleted})) - s2.logger.Trace("deleted peering for my-peer-s2") + + require.NoError(t, acceptor.fsm.State().PeeringWrite(2000, &pbpeering.PeeringWriteRequest{Peering: deleted})) + acceptor.logger.Trace("deleted peering for my-peer-dialer") retry.Run(t, func(r *retry.R) { - _, found := s1.peerStreamServer.StreamStatus(p.Peering.PeerID) + _, found := acceptor.peerStreamServer.StreamStatus(p.Peering.PeerID) require.False(r, found) }) - // s2 should have received the termination message and updated the peering state. + // dialer should have received the termination message and updated the peering state. retry.Run(t, func(r *retry.R) { - _, peering, err := s2.fsm.State().PeeringRead(nil, state.Query{ - Value: "my-peer-s1", + _, peering, err := dialer.fsm.State().PeeringRead(nil, state.Query{ + Value: "my-peer-acceptor", }) require.NoError(r, err) require.Equal(r, pbpeering.PeeringState_TERMINATED, peering.State) @@ -452,8 +469,7 @@ func TestLeader_Peering_DeferredDeletion(t *testing.T) { // // To test this, we start the two peer servers (accepting and dialing), set up peering, and then shut down // the accepting peer. This terminates the connection without sending a Terminated message. -// We then restart the accepting peer (we actually spin up a new server with the same config and port) and then -// assert that the dialing peer reestablishes the connection. +// We then restart the accepting peer and assert that the dialing peer reestablishes the connection. func TestLeader_Peering_DialerReestablishesConnectionOnError(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -566,20 +582,17 @@ func TestLeader_Peering_DialerReestablishesConnectionOnError(t *testing.T) { // Have to manually shut down the gRPC server otherwise it stays bound to the port. acceptingServer.externalGRPCServer.Stop() - // Mimic the server restarting by starting a new server with the same config. + // Restart the server by re-using the previous acceptor's data directory and node id. _, acceptingServerRestart := testServerWithConfig(t, func(c *Config) { c.NodeName = "acceptingServer.dc1" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" c.GRPCPort = acceptingServerPort + c.DataDir = acceptingServer.config.DataDir + c.NodeID = acceptingServer.config.NodeID }) - testrpc.WaitForLeader(t, acceptingServerRestart.RPC, "dc1") - // Re-insert the peering state, mimicking a snapshot restore. - require.NoError(t, acceptingServerRestart.fsm.State().PeeringWrite(2000, &pbpeering.PeeringWriteRequest{ - Peering: peering.Peering, - Secret: secrets, - })) + testrpc.WaitForLeader(t, acceptingServerRestart.RPC, "dc1") // The dialing peer should eventually reconnect. retry.Run(t, func(r *retry.R) { @@ -964,6 +977,7 @@ func TestLeader_PeeringMetrics_emitPeeringMetrics(t *testing.T) { var ( s2PeerID1 = generateUUID() s2PeerID2 = generateUUID() + s2PeerID3 = generateUUID() testContextTimeout = 60 * time.Second lastIdx = uint64(0) ) @@ -1053,6 +1067,24 @@ func TestLeader_PeeringMetrics_emitPeeringMetrics(t *testing.T) { // mimic tracking exported services mst2.TrackExportedService(structs.ServiceName{Name: "d-service"}) mst2.TrackExportedService(structs.ServiceName{Name: "e-service"}) + + // pretend that the hearbeat happened + mst2.TrackRecvHeartbeat() + } + + // Simulate a peering that never connects + { + p3 := &pbpeering.Peering{ + ID: s2PeerID3, + Name: "my-peer-s4", + PeerID: token.PeerID, // doesn't much matter what these values are + PeerCAPems: token.CA, + PeerServerName: token.ServerName, + PeerServerAddresses: token.ServerAddresses, + } + require.True(t, p3.ShouldDial()) + lastIdx++ + require.NoError(t, s2.fsm.State().PeeringWrite(lastIdx, &pbpeering.PeeringWriteRequest{Peering: p3})) } // set up a metrics sink @@ -1082,6 +1114,18 @@ func TestLeader_PeeringMetrics_emitPeeringMetrics(t *testing.T) { require.True(r, ok, fmt.Sprintf("did not find the key %q", keyMetric2)) require.Equal(r, float32(2), metric2.Value) // for d, e services + + keyHealthyMetric2 := fmt.Sprintf("us-west.consul.peering.healthy;peer_name=my-peer-s3;peer_id=%s", s2PeerID2) + healthyMetric2, ok := intv.Gauges[keyHealthyMetric2] + require.True(r, ok, fmt.Sprintf("did not find the key %q", keyHealthyMetric2)) + + require.Equal(r, float32(1), healthyMetric2.Value) + + keyHealthyMetric3 := fmt.Sprintf("us-west.consul.peering.healthy;peer_name=my-peer-s4;peer_id=%s", s2PeerID3) + healthyMetric3, ok := intv.Gauges[keyHealthyMetric3] + require.True(r, ok, fmt.Sprintf("did not find the key %q", keyHealthyMetric3)) + + require.True(r, math.IsNaN(float64(healthyMetric3.Value))) }) } @@ -1323,3 +1367,148 @@ func TestLeader_Peering_retryLoopBackoffPeering_cancelContext(t *testing.T) { fmt.Errorf("error 1"), }, allErrors) } + +func Test_isFailedPreconditionErr(t *testing.T) { + st := grpcstatus.New(codes.FailedPrecondition, "cannot establish a peering stream on a follower node") + err := st.Err() + assert.True(t, isFailedPreconditionErr(err)) + + // test that wrapped errors are checked correctly + werr := fmt.Errorf("wrapped: %w", err) + assert.True(t, isFailedPreconditionErr(werr)) +} + +func Test_Leader_PeeringSync_ServerAddressUpdates(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + // We want 1s retries for this test + orig := maxRetryBackoff + maxRetryBackoff = 1 + t.Cleanup(func() { maxRetryBackoff = orig }) + + _, acceptor := testServerWithConfig(t, func(c *Config) { + c.NodeName = "acceptor" + c.Datacenter = "dc1" + c.TLSConfig.Domain = "consul" + }) + testrpc.WaitForLeader(t, acceptor.RPC, "dc1") + + // Create a peering by generating a token + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + conn, err := grpc.DialContext(ctx, acceptor.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(acceptor.config.RPCAddr.String())), + grpc.WithInsecure(), + grpc.WithBlock()) + require.NoError(t, err) + defer conn.Close() + + acceptorClient := pbpeering.NewPeeringServiceClient(conn) + + req := pbpeering.GenerateTokenRequest{ + PeerName: "my-peer-dialer", + } + resp, err := acceptorClient.GenerateToken(ctx, &req) + require.NoError(t, err) + + // Bring up dialer and establish a peering with acceptor's token so that it attempts to dial. + _, dialer := testServerWithConfig(t, func(c *Config) { + c.NodeName = "dialer" + c.Datacenter = "dc2" + c.PrimaryDatacenter = "dc2" + }) + testrpc.WaitForLeader(t, dialer.RPC, "dc2") + + // Create a peering at dialer by establishing a peering with acceptor's token + ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + conn, err = grpc.DialContext(ctx, dialer.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(dialer.config.RPCAddr.String())), + grpc.WithInsecure(), + grpc.WithBlock()) + require.NoError(t, err) + defer conn.Close() + + dialerClient := pbpeering.NewPeeringServiceClient(conn) + + establishReq := pbpeering.EstablishRequest{ + PeerName: "my-peer-acceptor", + PeeringToken: resp.PeeringToken, + } + _, err = dialerClient.Establish(ctx, &establishReq) + require.NoError(t, err) + + p, err := dialerClient.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-acceptor"}) + require.NoError(t, err) + + retry.Run(t, func(r *retry.R) { + status, found := dialer.peerStreamServer.StreamStatus(p.Peering.ID) + require.True(r, found) + require.True(r, status.Connected) + }) + + testutil.RunStep(t, "calling establish with active connection does not overwrite server addresses", func(t *testing.T) { + ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + // generate a new token from the acceptor + req := pbpeering.GenerateTokenRequest{ + PeerName: "my-peer-dialer", + } + resp, err := acceptorClient.GenerateToken(ctx, &req) + require.NoError(t, err) + + token, err := acceptor.peeringBackend.DecodeToken([]byte(resp.PeeringToken)) + require.NoError(t, err) + + // we will update the token with bad addresses to assert it doesn't clobber existing ones + token.ServerAddresses = []string{"1.2.3.4:1234"} + + badToken, err := acceptor.peeringBackend.EncodeToken(token) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + // Try establishing. + // This call will only succeed if the bad address was not used in the calls to exchange the peering secret. + establishReq := pbpeering.EstablishRequest{ + PeerName: "my-peer-acceptor", + PeeringToken: string(badToken), + } + _, err = dialerClient.Establish(ctx, &establishReq) + require.NoError(t, err) + + p, err := dialerClient.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-acceptor"}) + require.NoError(t, err) + require.NotContains(t, p.Peering.PeerServerAddresses, "1.2.3.4:1234") + }) + + testutil.RunStep(t, "updated server addresses are picked up by the leader", func(t *testing.T) { + // force close the acceptor's gRPC server so the dialier retries with a new address. + acceptor.externalGRPCServer.Stop() + + clone := proto.Clone(p.Peering) + updated := clone.(*pbpeering.Peering) + // start with a bad address so we can assert for a specific error + updated.PeerServerAddresses = append([]string{ + "bad", + }, p.Peering.PeerServerAddresses...) + + // this write will wake up the watch on the leader to refetch server addresses + require.NoError(t, dialer.fsm.State().PeeringWrite(2000, &pbpeering.PeeringWriteRequest{Peering: updated})) + + retry.Run(t, func(r *retry.R) { + status, found := dialer.peerStreamServer.StreamStatus(p.Peering.ID) + require.True(r, found) + // We assert for this error to be set which would indicate that we iterated + // through a bad address. + require.Contains(r, status.LastSendErrorMessage, "transport: Error while dialing dial tcp: address bad: missing port in address") + require.False(r, status.Connected) + }) + }) +} diff --git a/agent/consul/merge_service_config.go b/agent/consul/merge_service_config.go index 027a2d3f5c..91fe229eae 100644 --- a/agent/consul/merge_service_config.go +++ b/agent/consul/merge_service_config.go @@ -159,6 +159,13 @@ func computeResolvedServiceConfig( thisReply.Destination = *serviceConf.Destination } + if serviceConf.MaxInboundConnections > 0 { + if thisReply.ProxyConfig == nil { + thisReply.ProxyConfig = map[string]interface{}{} + } + thisReply.ProxyConfig["max_inbound_connections"] = serviceConf.MaxInboundConnections + } + thisReply.Meta = serviceConf.Meta } diff --git a/agent/consul/merge_service_config_test.go b/agent/consul/merge_service_config_test.go index 5a866dce2a..a497579a76 100644 --- a/agent/consul/merge_service_config_test.go +++ b/agent/consul/merge_service_config_test.go @@ -3,12 +3,60 @@ package consul import ( "testing" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" "github.com/mitchellh/copystructure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func Test_ComputeResolvedServiceConfig(t *testing.T) { + type args struct { + scReq *structs.ServiceConfigRequest + upstreamIDs []structs.ServiceID + entries *configentry.ResolvedServiceConfigSet + } + + sid := structs.ServiceID{ + ID: "sid", + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + tests := []struct { + name string + args args + want *structs.ServiceConfigResponse + }{ + { + name: "proxy with maxinboundsconnections", + args: args{ + scReq: &structs.ServiceConfigRequest{ + Name: "sid", + }, + entries: &configentry.ResolvedServiceConfigSet{ + ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ + sid: { + MaxInboundConnections: 20, + }, + }, + }, + }, + want: &structs.ServiceConfigResponse{ + ProxyConfig: map[string]interface{}{ + "max_inbound_connections": 20, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := computeResolvedServiceConfig(tt.args.scReq, tt.args.upstreamIDs, + false, tt.args.entries, nil) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + func Test_MergeServiceConfig_TransparentProxy(t *testing.T) { type args struct { defaults *structs.ServiceConfigResponse @@ -153,6 +201,12 @@ func Test_MergeServiceConfig_UpstreamOverrides(t *testing.T) { DestinationNamespace: "default", DestinationPartition: "default", DestinationName: "zap", + Config: map[string]interface{}{ + "passive_health_check": map[string]interface{}{ + "Interval": int64(20), + "MaxFailures": int64(4), + }, + }, }, }, }, @@ -171,8 +225,8 @@ func Test_MergeServiceConfig_UpstreamOverrides(t *testing.T) { DestinationName: "zap", Config: map[string]interface{}{ "passive_health_check": map[string]interface{}{ - "Interval": int64(10), - "MaxFailures": int64(2), + "Interval": int64(20), + "MaxFailures": int64(4), }, "protocol": "grpc", }, diff --git a/agent/consul/peering_backend.go b/agent/consul/peering_backend.go index 95f9feb971..0f8b009e9c 100644 --- a/agent/consul/peering_backend.go +++ b/agent/consul/peering_backend.go @@ -141,7 +141,7 @@ func (b *PeeringBackend) ValidateProposedPeeringSecret(id string) (bool, error) return b.srv.fsm.State().ValidateProposedPeeringSecretUUID(id) } -func (b *PeeringBackend) PeeringSecretsWrite(req *pbpeering.PeeringSecrets) error { +func (b *PeeringBackend) PeeringSecretsWrite(req *pbpeering.SecretsWriteRequest) error { _, err := b.srv.raftApplyProtobuf(structs.PeeringSecretsWriteType, req) return err } diff --git a/agent/consul/peering_backend_test.go b/agent/consul/peering_backend_test.go index fc73ba53d0..7636dc48be 100644 --- a/agent/consul/peering_backend_test.go +++ b/agent/consul/peering_backend_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/proto/pbpeering" + "github.com/hashicorp/consul/proto/pbpeerstream" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/testrpc" ) @@ -76,3 +77,62 @@ func newServerDialer(serverAddr string) func(context.Context, string) (net.Conn, return conn, nil } } + +func TestPeerStreamService_ForwardToLeader(t *testing.T) { + t.Parallel() + + _, conf1 := testServerConfig(t) + server1, err := newServer(t, conf1) + require.NoError(t, err) + + _, conf2 := testServerConfig(t) + conf2.Bootstrap = false + server2, err := newServer(t, conf2) + require.NoError(t, err) + + // server1 is leader, server2 follower + testrpc.WaitForLeader(t, server1.RPC, "dc1") + joinLAN(t, server2, server1) + testrpc.WaitForLeader(t, server2.RPC, "dc1") + + peerId := testUUID() + + // Simulate a GenerateToken call on server1, which stores the establishment secret + { + require.NoError(t, server1.FSM().State().PeeringWrite(10, &pbpeering.PeeringWriteRequest{ + Peering: &pbpeering.Peering{ + Name: "foo", + ID: peerId, + }, + SecretsRequest: &pbpeering.SecretsWriteRequest{ + PeerID: peerId, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: "389bbcdf-1c31-47d6-ae96-f2a3f4c45f84", + }, + }, + }, + })) + } + + testutil.RunStep(t, "server2 forwards write to server1", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + // We will dial server2 which should forward to server1 + conn, err := gogrpc.DialContext(ctx, server2.config.RPCAddr.String(), + gogrpc.WithContextDialer(newServerDialer(server2.config.RPCAddr.String())), + gogrpc.WithInsecure(), + gogrpc.WithBlock()) + require.NoError(t, err) + t.Cleanup(func() { conn.Close() }) + + peerStreamClient := pbpeerstream.NewPeerStreamServiceClient(conn) + req := &pbpeerstream.ExchangeSecretRequest{ + PeerID: peerId, + EstablishmentSecret: "389bbcdf-1c31-47d6-ae96-f2a3f4c45f84", + } + _, err = peerStreamClient.ExchangeSecret(ctx, req) + require.NoError(t, err) + }) +} diff --git a/agent/consul/server.go b/agent/consul/server.go index 10b9d48f07..8f2986c3eb 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -742,6 +742,7 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server) (*Ser return s.ForwardGRPC(s.grpcConnPool, info, fn) }, }) + s.peerStreamTracker.SetHeartbeatTimeout(s.peerStreamServer.Config.IncomingHeartbeatTimeout) s.peerStreamServer.Register(s.externalGRPCServer) // Initialize internal gRPC server. @@ -816,6 +817,7 @@ func newGRPCHandlerFromConfig(deps Deps, config *Config, s *Server) connHandler // Note: these external gRPC services are also exposed on the internal server to // enable RPC forwarding. + s.peerStreamServer.Register(srv) s.externalACLServer.Register(srv) s.externalConnectCAServer.Register(srv) } diff --git a/agent/consul/server_oss.go b/agent/consul/server_oss.go index 5ae2fc3ea6..4ae524b65c 100644 --- a/agent/consul/server_oss.go +++ b/agent/consul/server_oss.go @@ -159,3 +159,18 @@ func (s *Server) addEnterpriseStats(stats map[string]map[string]string) { func getSerfMemberEnterpriseMeta(member serf.Member) *acl.EnterpriseMeta { return structs.NodeEnterpriseMetaInDefaultPartition() } + +func addSerfMetricsLabels(conf *serf.Config, wan bool, segment string, partition string, areaID string) { + conf.MetricLabels = []metrics.Label{} + + networkMetric := metrics.Label{ + Name: "network", + } + if wan { + networkMetric.Value = "wan" + } else { + networkMetric.Value = "lan" + } + + conf.MetricLabels = append(conf.MetricLabels, networkMetric) +} diff --git a/agent/consul/server_serf.go b/agent/consul/server_serf.go index 5e29b47dd2..569bc977ff 100644 --- a/agent/consul/server_serf.go +++ b/agent/consul/server_serf.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/armon/go-metrics" "github.com/hashicorp/go-hclog" "github.com/hashicorp/memberlist" "github.com/hashicorp/raft" @@ -177,9 +178,10 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) { if opts.WAN { nt, err := memberlist.NewNetTransport(&memberlist.NetTransportConfig{ - BindAddrs: []string{conf.MemberlistConfig.BindAddr}, - BindPort: conf.MemberlistConfig.BindPort, - Logger: conf.MemberlistConfig.Logger, + BindAddrs: []string{conf.MemberlistConfig.BindAddr}, + BindPort: conf.MemberlistConfig.BindPort, + Logger: conf.MemberlistConfig.Logger, + MetricLabels: []metrics.Label{{Name: "network", Value: "wan"}}, }) if err != nil { return nil, err @@ -230,6 +232,8 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) { conf.ReconnectTimeoutOverride = libserf.NewReconnectOverride(s.logger) + addSerfMetricsLabels(conf, opts.WAN, opts.Segment, s.config.AgentEnterpriseMeta().PartitionOrDefault(), "") + addEnterpriseSerfTags(conf.Tags, s.config.AgentEnterpriseMeta()) if s.config.OverrideInitialSerfTags != nil { diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go index b9f9cc4f14..35bbe720e2 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -179,6 +179,7 @@ func testServerConfig(t *testing.T) (string, *Config) { "IntermediateCertTTL": "288h", }, } + config.PeeringEnabled = true return dir, config } diff --git a/agent/consul/state/catalog.go b/agent/consul/state/catalog.go index 849d0820c3..7dad6e36fd 100644 --- a/agent/consul/state/catalog.go +++ b/agent/consul/state/catalog.go @@ -11,6 +11,7 @@ import ( "github.com/mitchellh/copystructure" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib" @@ -871,7 +872,7 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool if svc.Kind == structs.ServiceKindTypical && svc.Service != "consul" { // Check if this service is covered by a gateway's wildcard specifier, we force the service kind to a gateway-service here as that take precedence sn := structs.NewServiceName(svc.Service, &svc.EnterpriseMeta) - if err = checkGatewayWildcardsAndUpdate(tx, idx, &sn, structs.GatewayServiceKindService); err != nil { + if err = checkGatewayWildcardsAndUpdate(tx, idx, &sn, svc, structs.GatewayServiceKindService); err != nil { return fmt.Errorf("failed updating gateway mapping: %s", err) } if err = checkGatewayAndUpdate(tx, idx, &sn, structs.GatewayServiceKindService); err != nil { @@ -890,6 +891,15 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool return fmt.Errorf("failed updating upstream/downstream association") } + service := svc.Service + if svc.Kind == structs.ServiceKindConnectProxy { + service = svc.Proxy.DestinationServiceName + } + sn := structs.ServiceName{Name: service, EnterpriseMeta: svc.EnterpriseMeta} + if err = checkGatewayWildcardsAndUpdate(tx, idx, &sn, svc, structs.GatewayServiceKindService); err != nil { + return fmt.Errorf("failed updating gateway mapping: %s", err) + } + supported, err := virtualIPsSupported(tx, nil) if err != nil { return err @@ -897,12 +907,6 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool // Update the virtual IP for the service if supported { - service := svc.Service - if svc.Kind == structs.ServiceKindConnectProxy { - service = svc.Proxy.DestinationServiceName - } - - sn := structs.ServiceName{Name: service, EnterpriseMeta: svc.EnterpriseMeta} psn := structs.PeeredServiceName{Peer: svc.PeerName, ServiceName: sn} vip, err := assignServiceVirtualIP(tx, idx, psn) if err != nil { @@ -1130,7 +1134,7 @@ func terminatingGatewayVirtualIPsSupported(tx ReadTxn, ws memdb.WatchSet) (bool, } // Services returns all services along with a list of associated tags. -func (s *Store) Services(ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.Services, error) { +func (s *Store) Services(ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, peerName string) (uint64, []*structs.ServiceNode, error) { tx := s.db.Txn(false) defer tx.Abort() @@ -1144,30 +1148,11 @@ func (s *Store) Services(ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, peerNam } ws.Add(services.WatchCh()) - // Rip through the services and enumerate them and their unique set of - // tags. - unique := make(map[string]map[string]struct{}) + var result []*structs.ServiceNode for service := services.Next(); service != nil; service = services.Next() { - svc := service.(*structs.ServiceNode) - tags, ok := unique[svc.ServiceName] - if !ok { - unique[svc.ServiceName] = make(map[string]struct{}) - tags = unique[svc.ServiceName] - } - for _, tag := range svc.ServiceTags { - tags[tag] = struct{}{} - } + result = append(result, service.(*structs.ServiceNode)) } - - // Generate the output structure. - var results = make(structs.Services) - for service, tags := range unique { - results[service] = make([]string, 0, len(tags)) - for tag := range tags { - results[service] = append(results[service], tag) - } - } - return idx, results, nil + return idx, result, nil } func (s *Store) ServiceList(ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.ServiceList, error) { @@ -1208,7 +1193,7 @@ func serviceListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, } // ServicesByNodeMeta returns all services, filtered by the given node metadata. -func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.Services, error) { +func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string, entMeta *acl.EnterpriseMeta, peerName string) (uint64, []*structs.ServiceNode, error) { tx := s.db.Txn(false) defer tx.Abort() @@ -1255,8 +1240,7 @@ func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string, } allServicesCh := allServices.WatchCh() - // Populate the services map - unique := make(map[string]map[string]struct{}) + var result structs.ServiceNodes for node := nodes.Next(); node != nil; node = nodes.Next() { n := node.(*structs.Node) if len(filters) > 1 && !structs.SatisfiesMetaFilters(n.Meta, filters) { @@ -1270,30 +1254,11 @@ func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string, } ws.AddWithLimit(watchLimit, services.WatchCh(), allServicesCh) - // Rip through the services and enumerate them and their unique set of - // tags. for service := services.Next(); service != nil; service = services.Next() { - svc := service.(*structs.ServiceNode) - tags, ok := unique[svc.ServiceName] - if !ok { - unique[svc.ServiceName] = make(map[string]struct{}) - tags = unique[svc.ServiceName] - } - for _, tag := range svc.ServiceTags { - tags[tag] = struct{}{} - } + result = append(result, service.(*structs.ServiceNode)) } } - - // Generate the output structure. - var results = make(structs.Services) - for service, tags := range unique { - results[service] = make([]string, 0, len(tags)) - for tag := range tags { - results[service] = append(results[service], tag) - } - } - return idx, results, nil + return idx, result, nil } // maxIndexForService return the maximum Raft Index for a service @@ -1713,6 +1678,9 @@ func (s *Store) ServiceNode(nodeID, nodeName, serviceID string, entMeta *acl.Ent if err != nil { return 0, nil, fmt.Errorf("failed querying service for node %q: %w", node.Node, err) } + if service != nil { + service.ID = node.ID + } return idx, service, nil } @@ -1984,11 +1952,6 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st if err := catalogUpdateServiceExtinctionIndex(tx, idx, entMeta, svc.PeerName); err != nil { return err } - if svc.PeerName == "" { - if err := cleanupGatewayWildcards(tx, idx, svc); err != nil { - return fmt.Errorf("failed to clean up gateway-service associations for %q: %v", name.String(), 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) @@ -2001,6 +1964,13 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st return fmt.Errorf("Could not find any service %s: %s", svc.ServiceName, 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 nil } @@ -3652,6 +3622,18 @@ func updateGatewayNamespace(tx WriteTxn, idx uint64, service *structs.GatewaySer continue } + hasConnectInstance, hasNonConnectInstance, err := serviceHasConnectInstances(tx, sn.ServiceName, entMeta) + if err != nil { + return err + } + + if service.GatewayKind == structs.ServiceKindIngressGateway && !hasConnectInstance { + continue + } + if service.GatewayKind == structs.ServiceKindTerminatingGateway && !hasNonConnectInstance { + continue + } + existing, err := tx.First(tableGatewayServices, indexID, service.Gateway, sn.CompoundServiceName(), service.Port) if err != nil { return fmt.Errorf("gateway service lookup failed: %s", err) @@ -3717,6 +3699,38 @@ func updateGatewayNamespace(tx WriteTxn, idx uint64, service *structs.GatewaySer return nil } +// serviceHasConnectInstances returns whether the service has at least one connect instance, +// and at least one non-connect instance. +func serviceHasConnectInstances(tx WriteTxn, serviceName string, entMeta *acl.EnterpriseMeta) (bool, bool, error) { + hasConnectInstance := false + query := Query{ + Value: serviceName, + EnterpriseMeta: *entMeta, + } + svc, err := tx.First(tableServices, indexConnect, query) + if err != nil { + return false, false, fmt.Errorf("failed service lookup: %s", err) + } + if svc != nil { + hasConnectInstance = true + } + + hasNonConnectInstance := false + iter, err := tx.Get(tableServices, indexService, query) + if err != nil { + return false, false, fmt.Errorf("failed service lookup: %s", err) + } + for service := iter.Next(); service != nil; service = iter.Next() { + sn := service.(*structs.ServiceNode) + if !sn.ServiceConnect.Native { + hasNonConnectInstance = true + break + } + } + + return hasConnectInstance, hasNonConnectInstance, 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 { @@ -3754,14 +3768,35 @@ func updateGatewayService(tx WriteTxn, idx uint64, mapping *structs.GatewayServi // checkWildcardForGatewaysAndUpdate checks whether a service matches a // wildcard definition in gateway config entries and if so adds it the the // gateway-services table. -func checkGatewayWildcardsAndUpdate(tx WriteTxn, idx uint64, svc *structs.ServiceName, kind structs.GatewayServiceKind) error { +func checkGatewayWildcardsAndUpdate(tx WriteTxn, idx uint64, svc *structs.ServiceName, ns *structs.NodeService, kind structs.GatewayServiceKind) error { sn := structs.ServiceName{Name: structs.WildcardSpecifier, EnterpriseMeta: svc.EnterpriseMeta} svcGateways, err := tx.Get(tableGatewayServices, indexService, sn) if err != nil { return fmt.Errorf("failed gateway lookup for %q: %s", svc.Name, err) } + + hasConnectInstance, hasNonConnectInstance, err := serviceHasConnectInstances(tx, svc.Name, &svc.EnterpriseMeta) + if err != nil { + return err + } + // If we were passed a NodeService, this might be the first registered instance of the service + // so we need to count it as either a connect or non-connect instance. + if ns != nil { + if ns.Connect.Native || ns.Kind == structs.ServiceKindConnectProxy { + hasConnectInstance = true + } else { + hasNonConnectInstance = true + } + } + for service := svcGateways.Next(); service != nil; service = svcGateways.Next() { if wildcardSvc, ok := service.(*structs.GatewayService); ok && wildcardSvc != nil { + if wildcardSvc.GatewayKind == structs.ServiceKindIngressGateway && !hasConnectInstance { + continue + } + if wildcardSvc.GatewayKind == structs.ServiceKindTerminatingGateway && !hasNonConnectInstance && kind != structs.GatewayServiceKindDestination { + continue + } // Copy the wildcard mapping and modify it gatewaySvc := wildcardSvc.Clone() @@ -3803,12 +3838,11 @@ func checkGatewayAndUpdate(tx WriteTxn, idx uint64, svc *structs.ServiceName, ki return nil } -func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode) error { +func cleanupGatewayWildcards(tx WriteTxn, idx uint64, sn structs.ServiceName, cleaningUpDestination bool) error { // Clean up association between service name and gateways if needed - sn := structs.ServiceName{Name: svc.ServiceName, EnterpriseMeta: svc.EnterpriseMeta} gateways, err := tx.Get(tableGatewayServices, indexService, sn) if err != nil { - return fmt.Errorf("failed gateway lookup for %q: %s", svc.ServiceName, err) + return fmt.Errorf("failed gateway lookup for %q: %s", sn.Name, err) } mappings := make([]*structs.GatewayService, 0) @@ -3818,12 +3852,44 @@ func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode) } } + // Check whether there are any connect or non-connect instances remaining for this service. + // If there are no connect instances left, ingress gateways with a wildcard entry can remove + // their association with it (same with terminating gateways if there are no non-connect + // instances left). + hasConnectInstance, hasNonConnectInstance, err := serviceHasConnectInstances(tx, sn.Name, &sn.EnterpriseMeta) + if err != nil { + return err + } + + // If we're deleting a service instance but this service is defined as a destination via config entry, + // keep the mapping around. + hasDestination := false + if !cleaningUpDestination { + q := configentry.NewKindName(structs.ServiceDefaults, sn.Name, &sn.EnterpriseMeta) + existing, err := tx.First(tableConfigEntries, indexID, q) + if err != nil { + return fmt.Errorf("failed config entry lookup: %s", err) + } + if existing != nil { + if entry, ok := existing.(*structs.ServiceConfigEntry); ok && entry.Destination != nil { + hasDestination = true + } + } + } + // Do the updates in a separate loop so we don't trash the iterator. for _, m := range mappings { // Only delete if association was created by a wildcard specifier. // Otherwise the service was specified in the config entry, and the association should be maintained // for when the service is re-registered if m.FromWildcard { + if m.GatewayKind == structs.ServiceKindIngressGateway && hasConnectInstance { + continue + } + if m.GatewayKind == structs.ServiceKindTerminatingGateway && (hasNonConnectInstance || hasDestination) { + continue + } + if err := tx.Delete(tableGatewayServices, m); err != nil { return fmt.Errorf("failed to truncate gateway services table: %v", err) } @@ -3836,7 +3902,7 @@ func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode) } else { kind, err := GatewayServiceKind(tx, m.Service.Name, &m.Service.EnterpriseMeta) if err != nil { - return fmt.Errorf("failed to get gateway service kind for service %s: %v", svc.ServiceName, err) + return fmt.Errorf("failed to get gateway service kind for service %s: %v", sn.Name, err) } checkGatewayAndUpdate(tx, idx, &structs.ServiceName{Name: m.Service.Name, EnterpriseMeta: m.Service.EnterpriseMeta}, kind) } diff --git a/agent/consul/state/catalog_test.go b/agent/consul/state/catalog_test.go index fed3bd0ee3..d354b9b094 100644 --- a/agent/consul/state/catalog_test.go +++ b/agent/consul/state/catalog_test.go @@ -4,13 +4,16 @@ import ( "context" crand "crypto/rand" "fmt" - "github.com/hashicorp/consul/acl" "reflect" "sort" "strings" "testing" "time" + "github.com/hashicorp/consul/acl" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/go-memdb" "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/assert" @@ -269,17 +272,20 @@ func TestStateStore_EnsureRegistration(t *testing.T) { require.Equal(t, uint64(2), idx) require.Equal(t, svcmap["redis1"], r) + exp := svcmap["redis1"].ToServiceNode("node1") + exp.ID = nodeID + // lookup service by node name idx, sn, err := s.ServiceNode("", "node1", "redis1", nil, peerName) require.NoError(t, err) require.Equal(t, uint64(2), idx) - require.Equal(t, svcmap["redis1"].ToServiceNode("node1"), sn) + require.Equal(t, exp, sn) // lookup service by node ID idx, sn, err = s.ServiceNode(string(nodeID), "", "redis1", nil, peerName) require.NoError(t, err) require.Equal(t, uint64(2), idx) - require.Equal(t, svcmap["redis1"].ToServiceNode("node1"), sn) + require.Equal(t, exp, sn) // lookup service by invalid node _, _, err = s.ServiceNode("", "invalid-node", "redis1", nil, peerName) @@ -2101,10 +2107,13 @@ func TestStateStore_Services(t *testing.T) { Address: "1.1.1.1", Port: 1111, } + ns1.EnterpriseMeta.Normalize() if err := s.EnsureService(2, "node1", ns1); err != nil { t.Fatalf("err: %s", err) } - testRegisterService(t, s, 3, "node1", "dogs") + ns1Dogs := testRegisterService(t, s, 3, "node1", "dogs") + ns1Dogs.EnterpriseMeta.Normalize() + testRegisterNode(t, s, 4, "node2") ns2 := &structs.NodeService{ ID: "service3", @@ -2113,6 +2122,7 @@ func TestStateStore_Services(t *testing.T) { Address: "1.1.1.1", Port: 1111, } + ns2.EnterpriseMeta.Normalize() if err := s.EnsureService(5, "node2", ns2); err != nil { t.Fatalf("err: %s", err) } @@ -2130,19 +2140,13 @@ func TestStateStore_Services(t *testing.T) { t.Fatalf("bad index: %d", idx) } - // Verify the result. We sort the lists since the order is - // non-deterministic (it's built using a map internally). - expected := structs.Services{ - "redis": []string{"prod", "primary", "replica"}, - "dogs": []string{}, - } - sort.Strings(expected["redis"]) - for _, tags := range services { - sort.Strings(tags) - } - if !reflect.DeepEqual(expected, services) { - t.Fatalf("bad: %#v", services) + // Verify the result. + expected := []*structs.ServiceNode{ + ns1Dogs.ToServiceNode("node1"), + ns1.ToServiceNode("node1"), + ns2.ToServiceNode("node2"), } + assertDeepEqual(t, expected, services, cmpopts.IgnoreFields(structs.ServiceNode{}, "RaftIndex")) // Deleting a node with a service should fire the watch. if err := s.DeleteNode(6, "node1", nil, ""); err != nil { @@ -2181,6 +2185,7 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) { Address: "1.1.1.1", Port: 1111, } + ns1.EnterpriseMeta.Normalize() if err := s.EnsureService(2, "node0", ns1); err != nil { t.Fatalf("err: %s", err) } @@ -2191,6 +2196,7 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) { Address: "1.1.1.1", Port: 1111, } + ns2.EnterpriseMeta.Normalize() if err := s.EnsureService(3, "node1", ns2); err != nil { t.Fatalf("err: %s", err) } @@ -2205,11 +2211,10 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } - expected := structs.Services{ - "redis": []string{"primary", "prod"}, + expected := []*structs.ServiceNode{ + ns1.ToServiceNode("node0"), } - sort.Strings(res["redis"]) - require.Equal(t, expected, res) + assertDeepEqual(t, res, expected, cmpopts.IgnoreFields(structs.ServiceNode{}, "RaftIndex")) }) t.Run("Get all services using the common meta value", func(t *testing.T) { @@ -2217,11 +2222,12 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } - expected := structs.Services{ - "redis": []string{"primary", "prod", "replica"}, + require.Len(t, res, 2) + expected := []*structs.ServiceNode{ + ns1.ToServiceNode("node0"), + ns2.ToServiceNode("node1"), } - sort.Strings(res["redis"]) - require.Equal(t, expected, res) + assertDeepEqual(t, res, expected, cmpopts.IgnoreFields(structs.ServiceNode{}, "RaftIndex")) }) t.Run("Get an empty list for an invalid meta value", func(t *testing.T) { @@ -2229,8 +2235,8 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } - expected := structs.Services{} - require.Equal(t, expected, res) + var expected []*structs.ServiceNode + assertDeepEqual(t, res, expected, cmpopts.IgnoreFields(structs.ServiceNode{}, "RaftIndex")) }) t.Run("Get the first node's service instance using multiple meta filters", func(t *testing.T) { @@ -2238,11 +2244,10 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } - expected := structs.Services{ - "redis": []string{"primary", "prod"}, + expected := []*structs.ServiceNode{ + ns1.ToServiceNode("node0"), } - sort.Strings(res["redis"]) - require.Equal(t, expected, res) + assertDeepEqual(t, res, expected, cmpopts.IgnoreFields(structs.ServiceNode{}, "RaftIndex")) }) t.Run("Registering some unrelated node + service should not fire the watch.", func(t *testing.T) { @@ -5337,13 +5342,70 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) { } assert.Equal(t, expect, out) + // Add a destination via config entry and make sure it's picked up by the wildcard. + configEntryDest := &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "destination1", + Destination: &structs.DestinationConfig{Port: 9000, Addresses: []string{"kafka.test.com"}}, + } + assert.NoError(t, s.EnsureConfigEntry(27, configEntryDest)) + + idx, out, err = s.GatewayServices(ws, "gateway2", nil) + assert.Nil(t, err) + assert.Equal(t, idx, uint64(27)) + assert.Len(t, out, 3) + + expectWildcardIncludesDest := structs.GatewayServices{ + { + Service: structs.NewServiceName("api", nil), + Gateway: structs.NewServiceName("gateway2", nil), + GatewayKind: structs.ServiceKindTerminatingGateway, + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 26, + ModifyIndex: 26, + }, + }, + { + Service: structs.NewServiceName("db", nil), + Gateway: structs.NewServiceName("gateway2", nil), + GatewayKind: structs.ServiceKindTerminatingGateway, + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 26, + ModifyIndex: 26, + }, + }, + { + Service: structs.NewServiceName("destination1", nil), + Gateway: structs.NewServiceName("gateway2", nil), + GatewayKind: structs.ServiceKindTerminatingGateway, + ServiceKind: structs.GatewayServiceKindDestination, + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 27, + ModifyIndex: 27, + }, + }, + } + assert.ElementsMatch(t, expectWildcardIncludesDest, out) + + // Delete the destination. + assert.NoError(t, s.DeleteConfigEntry(28, structs.ServiceDefaults, "destination1", nil)) + + idx, out, err = s.GatewayServices(ws, "gateway2", nil) + assert.Nil(t, err) + assert.Equal(t, idx, uint64(28)) + assert.Len(t, out, 2) + assert.Equal(t, expect, out) + // Deleting the config entry should remove existing mappings - assert.Nil(t, s.DeleteConfigEntry(27, "terminating-gateway", "gateway", nil)) + assert.Nil(t, s.DeleteConfigEntry(29, "terminating-gateway", "gateway", nil)) assert.True(t, watchFired(ws)) idx, out, err = s.GatewayServices(ws, "gateway", nil) assert.Nil(t, err) - assert.Equal(t, idx, uint64(27)) + assert.Equal(t, idx, uint64(29)) assert.Len(t, out, 0) } @@ -5753,6 +5815,10 @@ func TestStateStore_GatewayServices_ServiceDeletion(t *testing.T) { assert.Nil(t, s.EnsureService(13, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: nil, Address: "", Port: 5000})) assert.Nil(t, s.EnsureService(14, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000})) + // Connect services (should be ignored by terminating gateway) + assert.Nil(t, s.EnsureService(15, "foo", &structs.NodeService{ID: "web", Service: "web", Tags: nil, Address: "", Connect: structs.ServiceConnect{Native: true}, Port: 5000})) + assert.Nil(t, s.EnsureService(16, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Connect: structs.ServiceConnect{Native: true}, Port: 5000})) + // Register two gateways assert.Nil(t, s.EnsureService(17, "bar", &structs.NodeService{Kind: structs.ServiceKindTerminatingGateway, ID: "gateway", Service: "gateway", Port: 443})) assert.Nil(t, s.EnsureService(18, "baz", &structs.NodeService{Kind: structs.ServiceKindTerminatingGateway, ID: "other-gateway", Service: "other-gateway", Port: 443})) @@ -5895,6 +5961,16 @@ func TestStateStore_GatewayServices_ServiceDeletion(t *testing.T) { }, } assert.Equal(t, expect, out) + + // Delete the non-connect instance of api + assert.Nil(t, s.DeleteService(21, "foo", "api", nil, "")) + + // Gateway with wildcard entry should have no services left, because the last + // non-connect instance of 'api' was deleted. + idx, out, err = s.GatewayServices(ws, "other-gateway", nil) + assert.Nil(t, err) + assert.Equal(t, idx, uint64(21)) + assert.Empty(t, out) } func TestStateStore_CheckIngressServiceNodes(t *testing.T) { @@ -5904,7 +5980,7 @@ func TestStateStore_CheckIngressServiceNodes(t *testing.T) { t.Run("check service1 ingress gateway", func(t *testing.T) { idx, results, err := s.CheckIngressServiceNodes(ws, "service1", nil) require.NoError(t, err) - require.Equal(t, uint64(15), idx) + require.Equal(t, uint64(18), idx) // Multiple instances of the ingress2 service require.Len(t, results, 4) @@ -5923,7 +5999,7 @@ func TestStateStore_CheckIngressServiceNodes(t *testing.T) { t.Run("check service2 ingress gateway", func(t *testing.T) { idx, results, err := s.CheckIngressServiceNodes(ws, "service2", nil) require.NoError(t, err) - require.Equal(t, uint64(15), idx) + require.Equal(t, uint64(18), idx) require.Len(t, results, 2) ids := make(map[string]struct{}) @@ -5941,7 +6017,7 @@ func TestStateStore_CheckIngressServiceNodes(t *testing.T) { ws := memdb.NewWatchSet() idx, results, err := s.CheckIngressServiceNodes(ws, "service3", nil) require.NoError(t, err) - require.Equal(t, uint64(15), idx) + require.Equal(t, uint64(18), idx) require.Len(t, results, 1) require.Equal(t, "wildcardIngress", results[0].Service.ID) }) @@ -5952,17 +6028,17 @@ func TestStateStore_CheckIngressServiceNodes(t *testing.T) { idx, results, err := s.CheckIngressServiceNodes(ws, "service1", nil) require.NoError(t, err) - require.Equal(t, uint64(15), idx) + require.Equal(t, uint64(18), idx) require.Len(t, results, 3) idx, results, err = s.CheckIngressServiceNodes(ws, "service2", nil) require.NoError(t, err) - require.Equal(t, uint64(15), idx) + require.Equal(t, uint64(18), idx) require.Len(t, results, 1) idx, results, err = s.CheckIngressServiceNodes(ws, "service3", nil) require.NoError(t, err) - require.Equal(t, uint64(15), idx) + require.Equal(t, uint64(18), idx) // TODO(ingress): index goes backward when deleting last config entry // require.Equal(t,uint64(11), idx) require.Len(t, results, 0) @@ -6305,23 +6381,80 @@ func TestStateStore_GatewayServices_WildcardAssociation(t *testing.T) { }) t.Run("do not associate connect-proxy services with gateway", func(t *testing.T) { + // Should only associate web (the destination service of the proxy), not the + // sidecar service name itself. testRegisterSidecarProxy(t, s, 19, "node1", "web") - require.False(t, watchFired(ws)) + expected := structs.GatewayServices{ + { + Gateway: structs.NewServiceName("wildcardIngress", nil), + Service: structs.NewServiceName("service1", nil), + GatewayKind: structs.ServiceKindIngressGateway, + Port: 4444, + Protocol: "http", + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 12, + ModifyIndex: 12, + }, + }, + { + Gateway: structs.NewServiceName("wildcardIngress", nil), + Service: structs.NewServiceName("service2", nil), + GatewayKind: structs.ServiceKindIngressGateway, + Port: 4444, + Protocol: "http", + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 12, + ModifyIndex: 12, + }, + }, + { + Gateway: structs.NewServiceName("wildcardIngress", nil), + Service: structs.NewServiceName("service3", nil), + GatewayKind: structs.ServiceKindIngressGateway, + Port: 4444, + Protocol: "http", + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 12, + ModifyIndex: 12, + }, + }, + { + Gateway: structs.NewServiceName("wildcardIngress", nil), + Service: structs.NewServiceName("web", nil), + ServiceKind: structs.GatewayServiceKindService, + GatewayKind: structs.ServiceKindIngressGateway, + Port: 4444, + Protocol: "http", + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 19, + ModifyIndex: 19, + }, + }, + } + idx, results, err := s.GatewayServices(ws, "wildcardIngress", nil) require.NoError(t, err) - require.Equal(t, uint64(16), idx) - require.Len(t, results, 3) + require.Equal(t, uint64(19), idx) + require.ElementsMatch(t, results, expected) }) t.Run("do not associate consul services with gateway", func(t *testing.T) { + ws := memdb.NewWatchSet() + _, _, err := s.GatewayServices(ws, "wildcardIngress", nil) + require.NoError(t, err) + require.Nil(t, s.EnsureService(20, "node1", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil}, )) require.False(t, watchFired(ws)) idx, results, err := s.GatewayServices(ws, "wildcardIngress", nil) require.NoError(t, err) - require.Equal(t, uint64(16), idx) - require.Len(t, results, 3) + require.Equal(t, uint64(19), idx) + require.Len(t, results, 4) }) } @@ -6346,8 +6479,8 @@ func TestStateStore_GatewayServices_IngressProtocolFiltering(t *testing.T) { } testRegisterNode(t, s, 0, "node1") - testRegisterService(t, s, 1, "node1", "service1") - testRegisterService(t, s, 2, "node1", "service2") + testRegisterConnectService(t, s, 1, "node1", "service1") + testRegisterConnectService(t, s, 2, "node1", "service2") assert.NoError(t, s.EnsureConfigEntry(4, ingress1)) }) @@ -6510,15 +6643,25 @@ func setupIngressState(t *testing.T, s *Store) memdb.WatchSet { testRegisterNode(t, s, 0, "node1") testRegisterNode(t, s, 1, "node2") - // Register a service against the nodes. + // Register some connect services against the nodes. testRegisterIngressService(t, s, 3, "node1", "wildcardIngress") testRegisterIngressService(t, s, 4, "node1", "ingress1") testRegisterIngressService(t, s, 5, "node1", "ingress2") testRegisterIngressService(t, s, 6, "node2", "ingress2") testRegisterIngressService(t, s, 7, "node1", "nothingIngress") - testRegisterService(t, s, 8, "node1", "service1") - testRegisterService(t, s, 9, "node2", "service2") + testRegisterConnectService(t, s, 8, "node1", "service1") + testRegisterConnectService(t, s, 9, "node2", "service2") testRegisterService(t, s, 10, "node2", "service3") + testRegisterServiceWithChangeOpts(t, s, 11, "node2", "service3-proxy", false, func(service *structs.NodeService) { + service.Kind = structs.ServiceKindConnectProxy + service.Proxy = structs.ConnectProxyConfig{ + DestinationServiceName: "service3", + } + }) + + // Register some non-connect services - these shouldn't be picked up by a wildcard. + testRegisterService(t, s, 17, "node1", "service4") + testRegisterService(t, s, 18, "node2", "service5") // Default protocol to http proxyDefaults := &structs.ProxyConfigEntry{ @@ -7883,6 +8026,7 @@ func TestCatalog_upstreamsFromRegistration_Ingress(t *testing.T) { Address: "127.0.0.3", Port: 443, EnterpriseMeta: *defaultMeta, + Connect: structs.ServiceConnect{Native: true}, } require.NoError(t, s.EnsureService(5, "foo", &svc)) assert.True(t, watchFired(ws)) @@ -8667,3 +8811,10 @@ func setVirtualIPFlags(t *testing.T, s *Store) { Value: "true", })) } + +func assertDeepEqual(t *testing.T, x, y interface{}, opts ...cmp.Option) { + t.Helper() + if diff := cmp.Diff(x, y, opts...); diff != "" { + t.Fatalf("assertion failed: values are not equal\n--- expected\n+++ actual\n%v", diff) + } +} diff --git a/agent/consul/state/config_entry.go b/agent/consul/state/config_entry.go index af0fe21133..97b7e3f281 100644 --- a/agent/consul/state/config_entry.go +++ b/agent/consul/state/config_entry.go @@ -371,9 +371,12 @@ func deleteConfigEntryTxn(tx WriteTxn, idx uint64, kind, name string, entMeta *a gsKind = structs.GatewayServiceKindUnknown } serviceName := structs.NewServiceName(c.GetName(), c.GetEnterpriseMeta()) - if err := checkGatewayWildcardsAndUpdate(tx, idx, &serviceName, gsKind); err != nil { + if err := checkGatewayWildcardsAndUpdate(tx, idx, &serviceName, nil, gsKind); err != nil { return fmt.Errorf("failed updating gateway mapping: %s", err) } + if err := cleanupGatewayWildcards(tx, idx, serviceName, true); err != nil { + return fmt.Errorf("failed to cleanup gateway mapping: \"%s\"; err: %v", serviceName, err) + } if err := checkGatewayAndUpdate(tx, idx, &serviceName, gsKind); err != nil { return fmt.Errorf("failed updating gateway mapping: %s", err) } @@ -434,7 +437,7 @@ func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry) if err != nil { return fmt.Errorf("failed updating gateway mapping: %s", err) } - if err := checkGatewayWildcardsAndUpdate(tx, idx, &sn, gsKind); err != nil { + if err := checkGatewayWildcardsAndUpdate(tx, idx, &sn, nil, gsKind); err != nil { return fmt.Errorf("failed updating gateway mapping: %s", err) } if err := checkGatewayAndUpdate(tx, idx, &sn, gsKind); err != nil { diff --git a/agent/consul/state/config_entry_test.go b/agent/consul/state/config_entry_test.go index 78140d021b..2731899331 100644 --- a/agent/consul/state/config_entry_test.go +++ b/agent/consul/state/config_entry_test.go @@ -372,7 +372,7 @@ func TestStore_ServiceDefaults_Kind_Destination(t *testing.T) { _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) require.NoError(t, err) require.Len(t, gatewayServices, 1) - require.Equal(t, gatewayServices[0].ServiceKind, structs.GatewayServiceKindUnknown) + require.Equal(t, structs.GatewayServiceKindUnknown, gatewayServices[0].ServiceKind) _, kindServices, err = s.ServiceNamesOfKind(ws, structs.ServiceKindDestination) require.NoError(t, err) @@ -710,13 +710,141 @@ func TestStore_ServiceDefaults_Kind_Destination_Wildcard(t *testing.T) { require.NoError(t, s.DeleteConfigEntry(6, structs.ServiceDefaults, destination.Name, &destination.EnterpriseMeta)) - //Watch is fired because we transitioned to a destination, by default we assume it's not. + // Watch is fired because we deleted the destination - now the mapping should be gone. require.True(t, watchFired(ws)) _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) require.NoError(t, err) - require.Len(t, gatewayServices, 1) - require.Equal(t, gatewayServices[0].ServiceKind, structs.GatewayServiceKindUnknown) + require.Len(t, gatewayServices, 0) + + t.Run("delete service instance before config entry", func(t *testing.T) { + // Set up a service with both a real instance and destination from a config entry. + require.NoError(t, s.EnsureNode(7, &structs.Node{Node: "foo", Address: "127.0.0.1"})) + require.NoError(t, s.EnsureService(8, "foo", &structs.NodeService{ID: "dest2", Service: "dest2", Tags: nil, Address: "", Port: 5000})) + + ws = memdb.NewWatchSet() + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Len(t, gatewayServices, 1) + require.Equal(t, structs.GatewayServiceKindService, gatewayServices[0].ServiceKind) + + // Register destination; shouldn't change the gateway mapping. + destination2 := &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "dest2", + Destination: &structs.DestinationConfig{}, + } + require.NoError(t, s.EnsureConfigEntry(9, destination2)) + require.False(t, watchFired(ws)) + + ws = memdb.NewWatchSet() + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Len(t, gatewayServices, 1) + expected := structs.GatewayServices{ + { + Service: structs.NewServiceName("dest2", nil), + Gateway: structs.NewServiceName("Gtwy1", nil), + ServiceKind: structs.GatewayServiceKindService, + GatewayKind: structs.ServiceKindTerminatingGateway, + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 8, + ModifyIndex: 8, + }, + }, + } + require.Equal(t, expected, gatewayServices) + + // Delete the service, mapping should still exist. + require.NoError(t, s.DeleteService(10, "foo", "dest2", nil, "")) + require.False(t, watchFired(ws)) + + ws = memdb.NewWatchSet() + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Len(t, gatewayServices, 1) + require.Equal(t, expected, gatewayServices) + + // Delete the config entry, mapping should be gone. + require.NoError(t, s.DeleteConfigEntry(11, structs.ServiceDefaults, "dest2", &destination.EnterpriseMeta)) + require.True(t, watchFired(ws)) + + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Empty(t, gatewayServices) + }) + + t.Run("delete config entry before service instance", func(t *testing.T) { + // Set up a service with both a real instance and destination from a config entry. + destination2 := &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "dest2", + Destination: &structs.DestinationConfig{}, + } + require.NoError(t, s.EnsureConfigEntry(7, destination2)) + + ws = memdb.NewWatchSet() + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Len(t, gatewayServices, 1) + expected := structs.GatewayServices{ + { + Service: structs.NewServiceName("dest2", nil), + Gateway: structs.NewServiceName("Gtwy1", nil), + ServiceKind: structs.GatewayServiceKindDestination, + GatewayKind: structs.ServiceKindTerminatingGateway, + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 7, + ModifyIndex: 7, + }, + }, + } + require.Equal(t, expected, gatewayServices) + + // Register service, only ServiceKind should have changed on the gateway mapping. + require.NoError(t, s.EnsureNode(8, &structs.Node{Node: "foo", Address: "127.0.0.1"})) + require.NoError(t, s.EnsureService(9, "foo", &structs.NodeService{ID: "dest2", Service: "dest2", Tags: nil, Address: "", Port: 5000})) + require.True(t, watchFired(ws)) + + ws = memdb.NewWatchSet() + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Len(t, gatewayServices, 1) + expected = structs.GatewayServices{ + { + Service: structs.NewServiceName("dest2", nil), + Gateway: structs.NewServiceName("Gtwy1", nil), + ServiceKind: structs.GatewayServiceKindService, + GatewayKind: structs.ServiceKindTerminatingGateway, + FromWildcard: true, + RaftIndex: structs.RaftIndex{ + CreateIndex: 7, + ModifyIndex: 9, + }, + }, + } + require.Equal(t, expected, gatewayServices) + + // Delete the config entry, mapping should still exist. + require.NoError(t, s.DeleteConfigEntry(10, structs.ServiceDefaults, "dest2", &destination.EnterpriseMeta)) + require.False(t, watchFired(ws)) + + ws = memdb.NewWatchSet() + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Len(t, gatewayServices, 1) + require.Equal(t, expected, gatewayServices) + + // Delete the service, mapping should be gone. + require.NoError(t, s.DeleteService(11, "foo", "dest2", nil, "")) + require.True(t, watchFired(ws)) + + _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) + require.NoError(t, err) + require.Empty(t, gatewayServices) + }) } func TestStore_Service_TerminatingGateway_Kind_Service_Wildcard(t *testing.T) { @@ -774,74 +902,6 @@ func TestStore_Service_TerminatingGateway_Kind_Service_Wildcard(t *testing.T) { require.Len(t, gatewayServices, 0) } -func TestStore_Service_TerminatingGateway_Kind_Service_Destination_Wildcard(t *testing.T) { - s := testConfigStateStore(t) - - Gtwy := &structs.TerminatingGatewayConfigEntry{ - Kind: structs.TerminatingGateway, - Name: "Gtwy1", - Services: []structs.LinkedService{ - { - Name: "*", - }, - }, - } - - // Create - require.NoError(t, s.EnsureConfigEntry(0, Gtwy)) - - service := &structs.NodeService{ - Kind: structs.ServiceKindTypical, - Service: "web", - } - destination := &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "web", - Destination: &structs.DestinationConfig{}, - } - - _, gatewayServices, err := s.GatewayServices(nil, "Gtwy1", nil) - require.NoError(t, err) - require.Len(t, gatewayServices, 0) - - ws := memdb.NewWatchSet() - _, _, err = s.GatewayServices(ws, "Gtwy1", nil) - require.NoError(t, err) - - // Create - require.NoError(t, s.EnsureConfigEntry(0, destination)) - - _, gatewayServices, err = s.GatewayServices(nil, "Gtwy1", nil) - require.NoError(t, err) - require.Len(t, gatewayServices, 1) - require.Equal(t, gatewayServices[0].ServiceKind, structs.GatewayServiceKindDestination) - - require.NoError(t, s.EnsureNode(0, &structs.Node{Node: "node1"})) - require.NoError(t, s.EnsureService(0, "node1", service)) - - //Watch is fired because we transitioned to a destination, by default we assume it's not. - require.True(t, watchFired(ws)) - - _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) - require.NoError(t, err) - require.Len(t, gatewayServices, 1) - require.Equal(t, gatewayServices[0].ServiceKind, structs.GatewayServiceKindService) - - ws = memdb.NewWatchSet() - _, _, err = s.GatewayServices(ws, "Gtwy1", nil) - require.NoError(t, err) - - require.NoError(t, s.DeleteService(6, "node1", service.ID, &service.EnterpriseMeta, "")) - - //Watch is fired because we transitioned to a destination, by default we assume it's not. - require.True(t, watchFired(ws)) - - _, gatewayServices, err = s.GatewayServices(ws, "Gtwy1", nil) - require.NoError(t, err) - require.Len(t, gatewayServices, 0) - -} - func TestStore_ConfigEntry_GraphValidation(t *testing.T) { ensureConfigEntry := func(s *Store, idx uint64, entry structs.ConfigEntry) error { if err := entry.Normalize(); err != nil { diff --git a/agent/consul/state/peering.go b/agent/consul/state/peering.go index b3e230a70a..287e822919 100644 --- a/agent/consul/state/peering.go +++ b/agent/consul/state/peering.go @@ -175,36 +175,47 @@ func peeringSecretsReadByPeerIDTxn(tx ReadTxn, ws memdb.WatchSet, id string) (*p return secret, nil } -func (s *Store) PeeringSecretsWrite(idx uint64, secret *pbpeering.PeeringSecrets) error { +func (s *Store) PeeringSecretsWrite(idx uint64, req *pbpeering.SecretsWriteRequest) error { tx := s.db.WriteTxn(idx) defer tx.Abort() - if err := s.peeringSecretsWriteTxn(tx, secret); err != nil { + if err := s.peeringSecretsWriteTxn(tx, req); err != nil { return fmt.Errorf("failed to write peering secret: %w", err) } return tx.Commit() } -func (s *Store) peeringSecretsWriteTxn(tx WriteTxn, secret *pbpeering.PeeringSecrets) error { - if secret == nil { +func (s *Store) peeringSecretsWriteTxn(tx WriteTxn, req *pbpeering.SecretsWriteRequest) error { + if req == nil || req.Request == nil { return nil } - if err := secret.Validate(); err != nil { - return err + if err := req.Validate(); err != nil { + return fmt.Errorf("invalid secret write request: %w", err) } - peering, err := peeringReadByIDTxn(tx, nil, secret.PeerID) + peering, err := peeringReadByIDTxn(tx, nil, req.PeerID) if err != nil { return fmt.Errorf("failed to read peering by id: %w", err) } if peering == nil { - return fmt.Errorf("unknown peering %q for secret", secret.PeerID) + return fmt.Errorf("unknown peering %q for secret", req.PeerID) } // If the peering came from a peering token no validation is done for the given secrets. // Dialing peers do not need to validate uniqueness because the secrets were generated elsewhere. if peering.ShouldDial() { - if err := tx.Insert(tablePeeringSecrets, secret); err != nil { + r, ok := req.Request.(*pbpeering.SecretsWriteRequest_Establish) + if !ok { + return fmt.Errorf("invalid request type %T when persisting stream secret for dialing peer", req.Request) + } + + secrets := pbpeering.PeeringSecrets{ + PeerID: req.PeerID, + Stream: &pbpeering.PeeringSecrets_Stream{ + ActiveSecretID: r.Establish.ActiveStreamSecret, + }, + } + if err := tx.Insert(tablePeeringSecrets, &secrets); err != nil { return fmt.Errorf("failed inserting peering: %w", err) } return nil @@ -213,21 +224,16 @@ func (s *Store) peeringSecretsWriteTxn(tx WriteTxn, secret *pbpeering.PeeringSec // If the peering token was generated locally, validate that the newly introduced UUID is still unique. // RPC handlers validate that generated IDs are available, but availability cannot be guaranteed until the state store operation. var newSecretID string - switch { - // Establishment secrets are written when generating peering tokens, and no other secret IDs are included. - case secret.GetEstablishment() != nil: - newSecretID = secret.GetEstablishment().SecretID - // Stream secrets can be written as: - // - A new PendingSecretID from the ExchangeSecret RPC - // - An ActiveSecretID when promoting a pending secret on first use - case secret.GetStream() != nil: - if pending := secret.GetStream().GetPendingSecretID(); pending != "" { - newSecretID = pending - } + switch r := req.Request.(type) { - // We do not need to check the long-lived Stream.ActiveSecretID for uniqueness because: - // - In the cluster that generated it the secret is always introduced as a PendingSecretID, then promoted to ActiveSecretID. - // This means that the promoted secret is already known to be unique. + // Establishment secrets are written when generating peering tokens, and no other secret IDs are included. + case *pbpeering.SecretsWriteRequest_GenerateToken: + newSecretID = r.GenerateToken.EstablishmentSecret + + // When exchanging an establishment secret a new pending stream secret is generated. + // Active stream secrets doesn't need to be checked for uniqueness because it is only ever promoted from pending. + case *pbpeering.SecretsWriteRequest_ExchangeSecret: + newSecretID = r.ExchangeSecret.PendingStreamSecret } if newSecretID != "" { @@ -244,53 +250,106 @@ func (s *Store) peeringSecretsWriteTxn(tx WriteTxn, secret *pbpeering.PeeringSec } } - existing, err := peeringSecretsReadByPeerIDTxn(tx, nil, secret.PeerID) + existing, err := peeringSecretsReadByPeerIDTxn(tx, nil, req.PeerID) if err != nil { return err } + secrets := pbpeering.PeeringSecrets{ + PeerID: req.PeerID, + } + var toDelete []string - if existing != nil { + // Collect any overwritten UUIDs for deletion. + switch r := req.Request.(type) { + case *pbpeering.SecretsWriteRequest_GenerateToken: + // Store the newly-generated establishment secret, overwriting any that existed. + secrets.Establishment = &pbpeering.PeeringSecrets_Establishment{ + SecretID: r.GenerateToken.GetEstablishmentSecret(), + } + // Merge in existing stream secrets when persisting a new establishment secret. // This is to avoid invalidating stream secrets when a new peering token // is generated. - // - // We purposely DO NOT do the reverse of inheriting an existing establishment secret. - // When exchanging establishment secrets for stream secrets, we invalidate the - // establishment secret by deleting it. - if secret.GetEstablishment() != nil && secret.GetStream() == nil && existing.GetStream() != nil { - secret.Stream = existing.Stream - } + secrets.Stream = existing.GetStream() - // Collect any overwritten UUIDs for deletion. - // - // Old establishment secret ID are always cleaned up when they don't match. - // They will either be replaced by a new one or deleted in the secret exchange RPC. - existingEstablishment := existing.GetEstablishment().GetSecretID() - if existingEstablishment != "" && existingEstablishment != secret.GetEstablishment().GetSecretID() { + // When a new token is generated we replace any un-used establishment secrets. + if existingEstablishment := existing.GetEstablishment().GetSecretID(); existingEstablishment != "" { toDelete = append(toDelete, existingEstablishment) } - // Old active secret IDs are always cleaned up when they don't match. - // They are only ever replaced when promoting a pending secret ID. - existingActive := existing.GetStream().GetActiveSecretID() - if existingActive != "" && existingActive != secret.GetStream().GetActiveSecretID() { + case *pbpeering.SecretsWriteRequest_ExchangeSecret: + if existing == nil { + return fmt.Errorf("cannot exchange peering secret: no known secrets for peering") + } + + // Store the newly-generated pending stream secret, overwriting any that existed. + secrets.Stream = &pbpeering.PeeringSecrets_Stream{ + PendingSecretID: r.ExchangeSecret.GetPendingStreamSecret(), + + // Avoid invalidating existing active secrets when exchanging establishment secret for pending. + ActiveSecretID: existing.GetStream().GetActiveSecretID(), + } + + // When exchanging an establishment secret we invalidate the existing establishment secret. + existingEstablishment := existing.GetEstablishment().GetSecretID() + switch { + case existingEstablishment == "": + // When there is no existing establishment secret we must not proceed because another ExchangeSecret + // RPC already invalidated it. Otherwise, this operation would overwrite the pending secret + // from the previous ExchangeSecret. + return fmt.Errorf("invalid establishment secret: peering was already established") + + case existingEstablishment != r.ExchangeSecret.GetEstablishmentSecret(): + // If there is an existing establishment secret but it is not the one from the request then + // we must not proceed because a newer one was generated. + return fmt.Errorf("invalid establishment secret") + + default: + toDelete = append(toDelete, existingEstablishment) + } + + // When exchanging an establishment secret unused pending secrets are overwritten. + if existingPending := existing.GetStream().GetPendingSecretID(); existingPending != "" { + toDelete = append(toDelete, existingPending) + } + + case *pbpeering.SecretsWriteRequest_PromotePending: + if existing == nil { + return fmt.Errorf("cannot promote pending secret: no known secrets for peering") + } + if existing.GetStream().GetPendingSecretID() != r.PromotePending.GetActiveStreamSecret() { + // There is a potential race if multiple dialing clusters send an Open request with a valid + // pending secret. The secret could be validated for all concurrently at the RPC layer, + // but then the pending secret is promoted or otherwise changes for one dialer before the others. + return fmt.Errorf("invalid pending stream secret") + } + + // Store the newly-generated pending stream secret, overwriting any that existed. + secrets.Stream = &pbpeering.PeeringSecrets_Stream{ + // Promoting a pending secret moves it to active. + PendingSecretID: "", + + // Store the newly-promoted pending secret as the active secret. + ActiveSecretID: r.PromotePending.GetActiveStreamSecret(), + } + + // Avoid invalidating existing establishment secrets when promoting pending secrets. + secrets.Establishment = existing.GetEstablishment() + + // If there was previously an active stream secret it gets replaced in favor of the pending secret + // that is being promoted. + if existingActive := existing.GetStream().GetActiveSecretID(); existingActive != "" { toDelete = append(toDelete, existingActive) } - // Pending secrets can change in three ways: - // - Generating a new pending secret: Nothing to delete here since there's no old pending secret being replaced. - // - Re-establishing a peering, and re-generating a pending secret: should delete the old one if both are non-empty. - // - Promoting a pending secret: Nothing to delete here since the pending secret is now active and still in use. - existingPending := existing.GetStream().GetPendingSecretID() - newPending := secret.GetStream().GetPendingSecretID() - if existingPending != "" && - // The value of newPending indicates whether a peering is being generated/re-established (not empty) - // or whether a pending secret is being promoted (empty). - newPending != "" && - newPending != existingPending { - toDelete = append(toDelete, existingPending) - } + case *pbpeering.SecretsWriteRequest_Establish: + // This should never happen. Dialing peers are the only ones that can call Establish, + // and the peering secrets for dialing peers should have been inserted earlier in the function. + return fmt.Errorf("an accepting peer should not have called Establish RPC") + + default: + return fmt.Errorf("got unexpected request type: %T", req.Request) } for _, id := range toDelete { if err := tx.Delete(tablePeeringSecretUUIDs, id); err != nil { @@ -298,23 +357,23 @@ func (s *Store) peeringSecretsWriteTxn(tx WriteTxn, secret *pbpeering.PeeringSec } } - if err := tx.Insert(tablePeeringSecrets, secret); err != nil { + if err := tx.Insert(tablePeeringSecrets, &secrets); err != nil { return fmt.Errorf("failed inserting peering: %w", err) } return nil } -func (s *Store) PeeringSecretsDelete(idx uint64, peerID string) error { +func (s *Store) PeeringSecretsDelete(idx uint64, peerID string, dialer bool) error { tx := s.db.WriteTxn(idx) defer tx.Abort() - if err := peeringSecretsDeleteTxn(tx, peerID); err != nil { + if err := peeringSecretsDeleteTxn(tx, peerID, dialer); err != nil { return fmt.Errorf("failed to write peering secret: %w", err) } return tx.Commit() } -func peeringSecretsDeleteTxn(tx WriteTxn, peerID string) error { +func peeringSecretsDeleteTxn(tx WriteTxn, peerID string, dialer bool) error { secretRaw, err := tx.First(tablePeeringSecrets, indexID, peerID) if err != nil { return fmt.Errorf("failed to fetch secret for peering: %w", err) @@ -326,6 +385,11 @@ func peeringSecretsDeleteTxn(tx WriteTxn, peerID string) error { return fmt.Errorf("failed to delete secret for peering: %w", err) } + // Dialing peers do not track secrets in tablePeeringSecretUUIDs. + if dialer { + return nil + } + secrets, ok := secretRaw.(*pbpeering.PeeringSecrets) if !ok { return fmt.Errorf("invalid type %T", secretRaw) @@ -520,7 +584,7 @@ func (s *Store) PeeringWrite(idx uint64, req *pbpeering.PeeringWriteRequest) err // Ensure associated secrets are cleaned up when a peering is marked for deletion. if req.Peering.State == pbpeering.PeeringState_DELETING { - if err := peeringSecretsDeleteTxn(tx, req.Peering.ID); err != nil { + if err := peeringSecretsDeleteTxn(tx, req.Peering.ID, req.Peering.ShouldDial()); err != nil { return fmt.Errorf("failed to delete peering secrets: %w", err) } } @@ -532,7 +596,7 @@ func (s *Store) PeeringWrite(idx uint64, req *pbpeering.PeeringWriteRequest) err } // Write any secrets generated with the peering. - err = s.peeringSecretsWriteTxn(tx, req.GetSecret()) + err = s.peeringSecretsWriteTxn(tx, req.GetSecretsRequest()) if err != nil { return fmt.Errorf("failed to write peering establishment secret: %w", err) } @@ -918,7 +982,7 @@ func peeringsForServiceTxn(tx ReadTxn, ws memdb.WatchSet, serviceName string, en if idx > maxIdx { maxIdx = idx } - if peering == nil || !peering.IsActive() { + if !peering.IsActive() { continue } peerings = append(peerings, peering) @@ -1097,6 +1161,10 @@ func (s *Snapshot) PeeringTrustBundles() (memdb.ResultIterator, error) { return s.tx.Get(tablePeeringTrustBundles, indexID) } +func (s *Snapshot) PeeringSecrets() (memdb.ResultIterator, error) { + return s.tx.Get(tablePeeringSecrets, indexID) +} + func (r *Restore) Peering(p *pbpeering.Peering) error { if err := r.tx.Insert(tablePeering, p); err != nil { return fmt.Errorf("failed restoring peering: %w", err) @@ -1119,6 +1187,30 @@ func (r *Restore) PeeringTrustBundle(ptb *pbpeering.PeeringTrustBundle) error { return nil } +func (r *Restore) PeeringSecrets(p *pbpeering.PeeringSecrets) error { + if err := r.tx.Insert(tablePeeringSecrets, p); err != nil { + return fmt.Errorf("failed restoring peering secrets: %w", err) + } + + var uuids []string + if establishment := p.GetEstablishment().GetSecretID(); establishment != "" { + uuids = append(uuids, establishment) + } + if pending := p.GetStream().GetPendingSecretID(); pending != "" { + uuids = append(uuids, pending) + } + if active := p.GetStream().GetActiveSecretID(); active != "" { + uuids = append(uuids, active) + } + + for _, id := range uuids { + if err := r.tx.Insert(tablePeeringSecretUUIDs, id); err != nil { + return fmt.Errorf("failed restoring peering secret UUIDs: %w", err) + } + } + return nil +} + // peersForServiceTxn returns the names of all peers that a service is exported to. func peersForServiceTxn( tx ReadTxn, diff --git a/agent/consul/state/peering_test.go b/agent/consul/state/peering_test.go index 73fee261fd..bfce75295c 100644 --- a/agent/consul/state/peering_test.go +++ b/agent/consul/state/peering_test.go @@ -58,7 +58,7 @@ func insertTestPeerings(t *testing.T, s *Store) { require.NoError(t, tx.Commit()) } -func insertTestPeeringSecret(t *testing.T, s *Store, secret *pbpeering.PeeringSecrets) { +func insertTestPeeringSecret(t *testing.T, s *Store, secret *pbpeering.PeeringSecrets, dialer bool) { t.Helper() tx := s.db.WriteTxn(0) @@ -78,9 +78,12 @@ func insertTestPeeringSecret(t *testing.T, s *Store, secret *pbpeering.PeeringSe uuids = append(uuids, active) } - for _, id := range uuids { - err = tx.Insert(tablePeeringSecretUUIDs, id) - require.NoError(t, err) + // Dialing peers do not track secret UUIDs because they don't generate them. + if !dialer { + for _, id := range uuids { + err = tx.Insert(tablePeeringSecretUUIDs, id) + require.NoError(t, err) + } } require.NoError(t, tx.Commit()) @@ -182,7 +185,7 @@ func TestStateStore_PeeringSecretsRead(t *testing.T) { Establishment: &pbpeering.PeeringSecrets_Establishment{ SecretID: testFooSecretID, }, - }) + }, false) type testcase struct { name string @@ -233,24 +236,45 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { return resp } - writeSeed := func(s *Store, req *pbpeering.PeeringWriteRequest) { + var ( + testSecretOne = testUUID() + testSecretTwo = testUUID() + testSecretThree = testUUID() + testSecretFour = testUUID() + ) + + type testSeed struct { + peering *pbpeering.Peering + secrets *pbpeering.PeeringSecrets + } + + type testcase struct { + name string + seed *testSeed + input *pbpeering.SecretsWriteRequest + expect *pbpeering.PeeringSecrets + expectUUIDs []string + expectErr string + } + + writeSeed := func(s *Store, seed *testSeed) { tx := s.db.WriteTxn(1) defer tx.Abort() - if req.Peering != nil { - require.NoError(t, tx.Insert(tablePeering, req.Peering)) + if seed.peering != nil { + require.NoError(t, tx.Insert(tablePeering, seed.peering)) } - if req.Secret != nil { - require.NoError(t, tx.Insert(tablePeeringSecrets, req.Secret)) + if seed.secrets != nil { + require.NoError(t, tx.Insert(tablePeeringSecrets, seed.secrets)) var toInsert []string - if establishment := req.Secret.GetEstablishment().GetSecretID(); establishment != "" { + if establishment := seed.secrets.GetEstablishment().GetSecretID(); establishment != "" { toInsert = append(toInsert, establishment) } - if pending := req.Secret.GetStream().GetPendingSecretID(); pending != "" { + if pending := seed.secrets.GetStream().GetPendingSecretID(); pending != "" { toInsert = append(toInsert, pending) } - if active := req.Secret.GetStream().GetActiveSecretID(); active != "" { + if active := seed.secrets.GetStream().GetActiveSecretID(); active != "" { toInsert = append(toInsert, active) } for _, id := range toInsert { @@ -261,20 +285,6 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { tx.Commit() } - var ( - testSecretOne = testUUID() - testSecretTwo = testUUID() - testSecretThree = testUUID() - ) - - type testcase struct { - name string - seed *pbpeering.PeeringWriteRequest - input *pbpeering.PeeringSecrets - expect *pbpeering.PeeringSecrets - expectUUIDs []string - expectErr string - } run := func(t *testing.T, tc testcase) { s := NewStateStore(nil) @@ -291,7 +301,7 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { require.NoError(t, err) // Validate that we read what we expect - secrets, err := s.PeeringSecretsRead(nil, tc.input.PeerID) + secrets, err := s.PeeringSecretsRead(nil, tc.input.GetPeerID()) require.NoError(t, err) require.NotNil(t, secrets) prototest.AssertDeepEqual(t, tc.expect, secrets) @@ -301,40 +311,131 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { } tcs := []testcase{ { - name: "missing peer id", - input: &pbpeering.PeeringSecrets{}, + name: "missing peer id", + input: &pbpeering.SecretsWriteRequest{ + Request: &pbpeering.SecretsWriteRequest_GenerateToken{}, + }, expectErr: "missing peer ID", }, - { - name: "no secret IDs were embedded", - input: &pbpeering.PeeringSecrets{ - PeerID: testFooPeerID, - }, - expectErr: "no secret IDs were embedded", - }, { name: "unknown peer id", - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Establishment: &pbpeering.PeeringSecrets_Establishment{ - SecretID: testFooSecretID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testFooSecretID, + }, }, }, expectErr: "unknown peering", }, { - name: "dialing peer does not track UUIDs", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "no secret IDs were embedded when generating token", + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{}, + }, + expectErr: "missing secret ID", + }, + { + name: "no secret IDs were embedded when establishing peering", + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_Establish{}, + }, + expectErr: "missing secret ID", + }, + { + name: "no secret IDs were embedded when exchanging secret", + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{}, + }, + expectErr: "missing secret ID", + }, + { + name: "no secret IDs were embedded when promoting pending secret", + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_PromotePending{}, + }, + expectErr: "missing secret ID", + }, + { + name: "dialing peer invalid request type - generate token", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testFooPeerID, PeerServerAddresses: []string{"10.0.0.1:5300"}, }, }, - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - ActiveSecretID: testFooSecretID, + // Dialing peer must only write secrets from Establish + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testFooSecretID, + }, + }, + }, + expectErr: "invalid request type", + }, + { + name: "dialing peer invalid request type - exchange secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + PeerServerAddresses: []string{"10.0.0.1:5300"}, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + // Dialing peer must only write secrets from Establish + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + PendingStreamSecret: testFooSecretID, + }, + }, + }, + expectErr: "invalid request type", + }, + { + name: "dialing peer invalid request type - promote pending", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + PeerServerAddresses: []string{"10.0.0.1:5300"}, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + // Dialing peer must only write secrets from Establish + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + ActiveStreamSecret: testFooSecretID, + }, + }, + }, + expectErr: "invalid request type", + }, + { + name: "dialing peer does not track UUIDs", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + PeerServerAddresses: []string{"10.0.0.1:5300"}, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_Establish{ + Establish: &pbpeering.SecretsWriteRequest_EstablishRequest{ + ActiveStreamSecret: testFooSecretID, + }, }, }, expect: &pbpeering.PeeringSecrets{ @@ -347,13 +448,13 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { expectUUIDs: []string{}, }, { - name: "generate new establishment secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "generate new establishment secret when secrets already existed", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testFooPeerID, }, - Secret: &pbpeering.PeeringSecrets{ + secrets: &pbpeering.PeeringSecrets{ PeerID: testFooPeerID, Stream: &pbpeering.PeeringSecrets_Stream{ PendingSecretID: testSecretOne, @@ -361,10 +462,12 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { }, }, }, - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Establishment: &pbpeering.PeeringSecrets_Establishment{ - SecretID: testSecretThree, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testSecretThree, + }, }, }, expect: &pbpeering.PeeringSecrets{ @@ -381,24 +484,26 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { expectUUIDs: []string{testSecretOne, testSecretTwo, testSecretThree}, }, { - name: "replace establishment secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "generate new token to replace establishment secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testFooPeerID, }, - Secret: &pbpeering.PeeringSecrets{ + secrets: &pbpeering.PeeringSecrets{ PeerID: testFooPeerID, Establishment: &pbpeering.PeeringSecrets_Establishment{ SecretID: testSecretOne, }, }, }, - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Establishment: &pbpeering.PeeringSecrets_Establishment{ - // Two replaces One - SecretID: testSecretTwo, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + // Two replaces One + EstablishmentSecret: testSecretTwo, + }, }, }, expect: &pbpeering.PeeringSecrets{ @@ -410,46 +515,96 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { expectUUIDs: []string{testSecretTwo}, }, { - name: "generate new pending secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "cannot exchange secret without existing secrets", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testFooPeerID, }, + // Do not seed an establishment secret. }, - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - PendingSecretID: testSecretOne, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + PendingStreamSecret: testSecretOne, + }, }, }, - expect: &pbpeering.PeeringSecrets{ - PeerID: testFooPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - PendingSecretID: testSecretOne, - }, - }, - expectUUIDs: []string{testSecretOne}, + expectErr: "no known secrets for peering", }, { - name: "replace pending secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "cannot exchange secret without establishment secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testFooPeerID, }, - Secret: &pbpeering.PeeringSecrets{ + secrets: &pbpeering.PeeringSecrets{ PeerID: testFooPeerID, Stream: &pbpeering.PeeringSecrets_Stream{ PendingSecretID: testSecretOne, }, }, }, - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - // Two replaces One - PendingSecretID: testSecretTwo, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + // Attempt to replace One with Two + PendingStreamSecret: testSecretTwo, + }, + }, + }, + expectErr: "peering was already established", + }, + { + name: "cannot exchange secret without valid establishment secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + }, + secrets: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Establishment: &pbpeering.PeeringSecrets_Establishment{ + SecretID: testSecretOne, + }, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + // Given secret Three does not match One + EstablishmentSecret: testSecretThree, + PendingStreamSecret: testSecretTwo, + }, + }, + }, + expectErr: "invalid establishment secret", + }, + { + name: "exchange secret to generate new pending secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + }, + secrets: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Establishment: &pbpeering.PeeringSecrets_Establishment{ + SecretID: testSecretOne, + }, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + EstablishmentSecret: testSecretOne, + PendingStreamSecret: testSecretTwo, + }, }, }, expect: &pbpeering.PeeringSecrets{ @@ -458,16 +613,101 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { PendingSecretID: testSecretTwo, }, }, + // Establishment secret testSecretOne is discarded when exchanging for a stream secret expectUUIDs: []string{testSecretTwo}, }, { - name: "promote pending secret and delete active", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "exchange secret replaces pending stream secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testFooPeerID, }, - Secret: &pbpeering.PeeringSecrets{ + secrets: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Establishment: &pbpeering.PeeringSecrets_Establishment{ + SecretID: testSecretFour, + }, + Stream: &pbpeering.PeeringSecrets_Stream{ + ActiveSecretID: testSecretOne, + PendingSecretID: testSecretTwo, + }, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + EstablishmentSecret: testSecretFour, + + // Three replaces two + PendingStreamSecret: testSecretThree, + }, + }, + }, + expect: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + // Establishment secret is discarded in favor of new pending secret. + Stream: &pbpeering.PeeringSecrets_Stream{ + // Active secret is not deleted until the new pending secret is promoted + ActiveSecretID: testSecretOne, + PendingSecretID: testSecretThree, + }, + }, + expectUUIDs: []string{testSecretOne, testSecretThree}, + }, + { + name: "cannot promote pending without existing secrets", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + }, + // Do not seed a pending secret. + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + ActiveStreamSecret: testSecretOne, + }, + }, + }, + expectErr: "no known secrets for peering", + }, + { + name: "cannot promote pending without existing pending secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + }, + secrets: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Stream: &pbpeering.PeeringSecrets_Stream{ + ActiveSecretID: testSecretOne, + }, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + // Attempt to replace One with Two + ActiveStreamSecret: testSecretTwo, + }, + }, + }, + expectErr: "invalid pending stream secret", + }, + { + name: "cannot promote pending without valid pending secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + }, + secrets: &pbpeering.PeeringSecrets{ PeerID: testFooPeerID, Stream: &pbpeering.PeeringSecrets_Stream{ PendingSecretID: testSecretTwo, @@ -475,20 +715,55 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { }, }, }, - input: &pbpeering.PeeringSecrets{ + input: &pbpeering.SecretsWriteRequest{ PeerID: testFooPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - // Two gets promoted over One - ActiveSecretID: testSecretTwo, + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + // Attempting to write secret Three, but pending secret is Two + ActiveStreamSecret: testSecretThree, + }, + }, + }, + expectErr: "invalid pending stream secret", + }, + { + name: "promote pending secret and delete active", + seed: &testSeed{ + peering: &pbpeering.Peering{ + Name: "foo", + ID: testFooPeerID, + }, + secrets: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Establishment: &pbpeering.PeeringSecrets_Establishment{ + SecretID: testSecretThree, + }, + Stream: &pbpeering.PeeringSecrets_Stream{ + PendingSecretID: testSecretTwo, + ActiveSecretID: testSecretOne, + }, + }, + }, + input: &pbpeering.SecretsWriteRequest{ + PeerID: testFooPeerID, + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + // Two gets promoted over One + ActiveStreamSecret: testSecretTwo, + }, }, }, expect: &pbpeering.PeeringSecrets{ PeerID: testFooPeerID, + Establishment: &pbpeering.PeeringSecrets_Establishment{ + // Establishment secret remains valid when promoting a stream secret. + SecretID: testSecretThree, + }, Stream: &pbpeering.PeeringSecrets_Stream{ ActiveSecretID: testSecretTwo, }, }, - expectUUIDs: []string{testSecretTwo}, + expectUUIDs: []string{testSecretTwo, testSecretThree}, }, } for _, tc := range tcs { @@ -499,40 +774,67 @@ func TestStore_PeeringSecretsWrite(t *testing.T) { } func TestStore_PeeringSecretsDelete(t *testing.T) { - s := NewStateStore(nil) - insertTestPeerings(t, s) - const ( establishmentID = "b4b9cbae-4bbd-454b-b7ae-441a5c89c3b9" pendingID = "0ba06390-bd77-4c52-8397-f88c0867157d" activeID = "0b8a3817-aca0-4c06-94b6-b0763a5cd013" ) - insertTestPeeringSecret(t, s, &pbpeering.PeeringSecrets{ - PeerID: testFooPeerID, - Establishment: &pbpeering.PeeringSecrets_Establishment{ - SecretID: establishmentID, - }, - Stream: &pbpeering.PeeringSecrets_Stream{ - PendingSecretID: pendingID, - ActiveSecretID: activeID, - }, - }) + type testCase struct { + dialer bool + secret *pbpeering.PeeringSecrets + } - require.NoError(t, s.PeeringSecretsDelete(12, testFooPeerID)) + run := func(t *testing.T, tc testCase) { + s := NewStateStore(nil) - // The secrets should be gone - secrets, err := s.PeeringSecretsRead(nil, testFooPeerID) - require.NoError(t, err) - require.Nil(t, secrets) + insertTestPeerings(t, s) + insertTestPeeringSecret(t, s, tc.secret, tc.dialer) - // The UUIDs should be free - uuids := []string{establishmentID, pendingID, activeID} + require.NoError(t, s.PeeringSecretsDelete(12, testFooPeerID, tc.dialer)) - for _, id := range uuids { - free, err := s.ValidateProposedPeeringSecretUUID(id) + // The secrets should be gone + secrets, err := s.PeeringSecretsRead(nil, testFooPeerID) require.NoError(t, err) - require.True(t, free) + require.Nil(t, secrets) + + uuids := []string{establishmentID, pendingID, activeID} + for _, id := range uuids { + free, err := s.ValidateProposedPeeringSecretUUID(id) + require.NoError(t, err) + require.True(t, free) + } + } + + tt := map[string]testCase{ + "acceptor": { + dialer: false, + secret: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Establishment: &pbpeering.PeeringSecrets_Establishment{ + SecretID: establishmentID, + }, + Stream: &pbpeering.PeeringSecrets_Stream{ + PendingSecretID: pendingID, + ActiveSecretID: activeID, + }, + }, + }, + "dialer": { + dialer: true, + secret: &pbpeering.PeeringSecrets{ + PeerID: testFooPeerID, + Stream: &pbpeering.PeeringSecrets_Stream{ + ActiveSecretID: activeID, + }, + }, + }, + } + + for name, tc := range tt { + t.Run(name, func(t *testing.T) { + run(t, tc) + }) } } @@ -847,10 +1149,12 @@ func TestStore_PeeringWrite(t *testing.T) { Name: "baz", Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(), }, - Secret: &pbpeering.PeeringSecrets{ + SecretsRequest: &pbpeering.SecretsWriteRequest{ PeerID: testBazPeerID, - Establishment: &pbpeering.PeeringSecrets_Establishment{ - SecretID: testBazSecretID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testBazSecretID, + }, }, }, }, @@ -1157,7 +1461,13 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { } newTarget := func(service, serviceSubset, datacenter string) *structs.DiscoveryTarget { - t := structs.NewDiscoveryTarget(service, serviceSubset, "default", "default", datacenter) + t := structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: service, + ServiceSubset: serviceSubset, + Partition: "default", + Namespace: "default", + Datacenter: datacenter, + }) t.SNI = connect.TargetSNI(t, connect.TestTrustDomain) t.Name = t.SNI t.ConnectTimeout = 5 * time.Second // default diff --git a/agent/consul/state/state_store_test.go b/agent/consul/state/state_store_test.go index 4946c50f21..88e5418c8d 100644 --- a/agent/consul/state/state_store_test.go +++ b/agent/consul/state/state_store_test.go @@ -146,13 +146,13 @@ func testRegisterServiceOpts(t *testing.T, s *Store, idx uint64, nodeID, service // testRegisterServiceWithChange registers a service and allow ensuring the consul index is updated // even if service already exists if using `modifyAccordingIndex`. // This is done by setting the transaction ID in "version" meta so service will be updated if it already exists -func testRegisterServiceWithChange(t *testing.T, s *Store, idx uint64, nodeID, serviceID string, modifyAccordingIndex bool) { - testRegisterServiceWithChangeOpts(t, s, idx, nodeID, serviceID, modifyAccordingIndex) +func testRegisterServiceWithChange(t *testing.T, s *Store, idx uint64, nodeID, serviceID string, modifyAccordingIndex bool) *structs.NodeService { + return testRegisterServiceWithChangeOpts(t, s, idx, nodeID, serviceID, modifyAccordingIndex) } // testRegisterServiceWithChangeOpts is the same as testRegisterServiceWithChange with the addition of opts that can // modify the service prior to writing. -func testRegisterServiceWithChangeOpts(t *testing.T, s *Store, idx uint64, nodeID, serviceID string, modifyAccordingIndex bool, opts ...func(service *structs.NodeService)) { +func testRegisterServiceWithChangeOpts(t *testing.T, s *Store, idx uint64, nodeID, serviceID string, modifyAccordingIndex bool, opts ...func(service *structs.NodeService)) *structs.NodeService { meta := make(map[string]string) if modifyAccordingIndex { meta["version"] = fmt.Sprint(idx) @@ -183,14 +183,21 @@ func testRegisterServiceWithChangeOpts(t *testing.T, s *Store, idx uint64, nodeI result.ServiceID != serviceID { t.Fatalf("bad service: %#v", result) } + return svc } // testRegisterService register a service with given transaction idx // If the service already exists, transaction number might not be increased // Use `testRegisterServiceWithChange()` if you want perform a registration that // ensures the transaction is updated by setting idx in Meta of Service -func testRegisterService(t *testing.T, s *Store, idx uint64, nodeID, serviceID string) { - testRegisterServiceWithChange(t, s, idx, nodeID, serviceID, false) +func testRegisterService(t *testing.T, s *Store, idx uint64, nodeID, serviceID string) *structs.NodeService { + return testRegisterServiceWithChange(t, s, idx, nodeID, serviceID, false) +} + +func testRegisterConnectService(t *testing.T, s *Store, idx uint64, nodeID, serviceID string) { + testRegisterServiceWithChangeOpts(t, s, idx, nodeID, serviceID, true, func(service *structs.NodeService) { + service.Connect = structs.ServiceConnect{Native: true} + }) } func testRegisterIngressService(t *testing.T, s *Store, idx uint64, nodeID, serviceID string) { diff --git a/agent/consul/usagemetrics/usagemetrics_oss_test.go b/agent/consul/usagemetrics/usagemetrics_oss_test.go index c860e5b741..8c37fe2695 100644 --- a/agent/consul/usagemetrics/usagemetrics_oss_test.go +++ b/agent/consul/usagemetrics/usagemetrics_oss_test.go @@ -8,10 +8,11 @@ import ( "time" "github.com/armon/go-metrics" - uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/serf/serf" "github.com/stretchr/testify/require" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" @@ -23,371 +24,368 @@ func newStateStore() (*state.Store, error) { return state.NewStateStore(nil), nil } +type testCase struct { + modfiyStateStore func(t *testing.T, s *state.Store) + getMembersFunc getMembersFunc + expectedGauges map[string]metrics.GaugeValue +} + +var baseCases = map[string]testCase{ + "empty-state": { + expectedGauges: map[string]metrics.GaugeValue{ + // --- node --- + "consul.usage.test.consul.state.nodes;datacenter=dc1": { + Name: "consul.usage.test.consul.state.nodes", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- peering --- + "consul.usage.test.consul.state.peerings;datacenter=dc1": { + Name: "consul.usage.test.consul.state.peerings", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- member --- + "consul.usage.test.consul.members.clients;datacenter=dc1": { + Name: "consul.usage.test.consul.members.clients", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + "consul.usage.test.consul.members.servers;datacenter=dc1": { + Name: "consul.usage.test.consul.members.servers", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- service --- + "consul.usage.test.consul.state.services;datacenter=dc1": { + Name: "consul.usage.test.consul.state.services", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + "consul.usage.test.consul.state.service_instances;datacenter=dc1": { + Name: "consul.usage.test.consul.state.service_instances", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- service mesh --- + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "connect-proxy"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "terminating-gateway"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "ingress-gateway"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "mesh-gateway"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "connect-native"}, + }, + }, + // --- kv --- + "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { + Name: "consul.usage.test.consul.state.kv_entries", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- config entries --- + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-intentions"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-resolver"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-router"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-defaults"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "ingress-gateway"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-splitter"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "mesh"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "proxy-defaults"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "terminating-gateway"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "exported-services"}, + }, + }, + }, + getMembersFunc: func() []serf.Member { return []serf.Member{} }, + }, + "nodes": { + modfiyStateStore: func(t *testing.T, s *state.Store) { + require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})) + require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"})) + }, + getMembersFunc: func() []serf.Member { + return []serf.Member{ + { + Name: "foo", + Tags: map[string]string{"role": "consul"}, + Status: serf.StatusAlive, + }, + { + Name: "bar", + Tags: map[string]string{"role": "consul"}, + Status: serf.StatusAlive, + }, + } + }, + expectedGauges: map[string]metrics.GaugeValue{ + // --- node --- + "consul.usage.test.consul.state.nodes;datacenter=dc1": { + Name: "consul.usage.test.consul.state.nodes", + Value: 2, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- peering --- + "consul.usage.test.consul.state.peerings;datacenter=dc1": { + Name: "consul.usage.test.consul.state.peerings", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- member --- + "consul.usage.test.consul.members.servers;datacenter=dc1": { + Name: "consul.usage.test.consul.members.servers", + Value: 2, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + "consul.usage.test.consul.members.clients;datacenter=dc1": { + Name: "consul.usage.test.consul.members.clients", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- service --- + "consul.usage.test.consul.state.services;datacenter=dc1": { + Name: "consul.usage.test.consul.state.services", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + "consul.usage.test.consul.state.service_instances;datacenter=dc1": { + Name: "consul.usage.test.consul.state.service_instances", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- service mesh --- + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "connect-proxy"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "terminating-gateway"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "ingress-gateway"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "mesh-gateway"}, + }, + }, + "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { + Name: "consul.usage.test.consul.state.connect_instances", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "connect-native"}, + }, + }, + // --- kv --- + "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { + Name: "consul.usage.test.consul.state.kv_entries", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + }, + // --- config entries --- + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-intentions"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-resolver"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-router"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-defaults"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "ingress-gateway"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "service-splitter"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "mesh"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "proxy-defaults"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "terminating-gateway"}, + }, + }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "exported-services"}, + }, + }, + }, + }, +} + func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) { - type testCase struct { - modfiyStateStore func(t *testing.T, s *state.Store) - getMembersFunc getMembersFunc - expectedGauges map[string]metrics.GaugeValue - } - cases := map[string]testCase{ - "empty-state": { - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - getMembersFunc: func() []serf.Member { return []serf.Member{} }, - }, - "nodes": { - modfiyStateStore: func(t *testing.T, s *state.Store) { - require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})) - require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"})) - require.NoError(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"})) - }, - getMembersFunc: func() []serf.Member { - return []serf.Member{ - { - Name: "foo", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "bar", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "baz", - Tags: map[string]string{"role": "node"}, - Status: serf.StatusAlive, - }, - } - }, - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 3, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 2, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 1, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - }, - } + cases := baseCases for name, tcase := range cases { t.Run(name, func(t *testing.T) { @@ -426,371 +424,57 @@ func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) { } func TestUsageReporter_emitPeeringUsage_OSS(t *testing.T) { - type testCase struct { - modfiyStateStore func(t *testing.T, s *state.Store) - getMembersFunc getMembersFunc - expectedGauges map[string]metrics.GaugeValue + cases := make(map[string]testCase) + for k, v := range baseCases { + eg := make(map[string]metrics.GaugeValue) + for k, v := range v.expectedGauges { + eg[k] = v + } + cases[k] = testCase{v.modfiyStateStore, v.getMembersFunc, eg} } - cases := map[string]testCase{ - "empty-state": { - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - getMembersFunc: func() []serf.Member { return []serf.Member{} }, - }, - "peerings": { - modfiyStateStore: func(t *testing.T, s *state.Store) { - id, err := uuid.GenerateUUID() - require.NoError(t, err) - require.NoError(t, s.PeeringWrite(1, &pbpeering.PeeringWriteRequest{Peering: &pbpeering.Peering{Name: "foo", ID: id}})) - id, err = uuid.GenerateUUID() - require.NoError(t, err) - require.NoError(t, s.PeeringWrite(2, &pbpeering.PeeringWriteRequest{Peering: &pbpeering.Peering{Name: "bar", ID: id}})) - id, err = uuid.GenerateUUID() - require.NoError(t, err) - require.NoError(t, s.PeeringWrite(3, &pbpeering.PeeringWriteRequest{Peering: &pbpeering.Peering{Name: "baz", ID: id}})) - }, - getMembersFunc: func() []serf.Member { - return []serf.Member{ - { - Name: "foo", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "bar", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - } - }, - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 3, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 2, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - }, + peeringsCase := cases["nodes"] + peeringsCase.modfiyStateStore = func(t *testing.T, s *state.Store) { + id, err := uuid.GenerateUUID() + require.NoError(t, err) + require.NoError(t, s.PeeringWrite(1, &pbpeering.PeeringWriteRequest{Peering: &pbpeering.Peering{Name: "foo", ID: id}})) + id, err = uuid.GenerateUUID() + require.NoError(t, err) + require.NoError(t, s.PeeringWrite(2, &pbpeering.PeeringWriteRequest{Peering: &pbpeering.Peering{Name: "bar", ID: id}})) + id, err = uuid.GenerateUUID() + require.NoError(t, err) + require.NoError(t, s.PeeringWrite(3, &pbpeering.PeeringWriteRequest{Peering: &pbpeering.Peering{Name: "baz", ID: id}})) } + peeringsCase.getMembersFunc = func() []serf.Member { + return []serf.Member{ + { + Name: "foo", + Tags: map[string]string{"role": "consul"}, + Status: serf.StatusAlive, + }, + { + Name: "bar", + Tags: map[string]string{"role": "consul"}, + Status: serf.StatusAlive, + }, + } + } + peeringsCase.expectedGauges["consul.usage.test.consul.state.nodes;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.nodes", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + peeringsCase.expectedGauges["consul.usage.test.consul.state.peerings;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.peerings", + Value: 3, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + peeringsCase.expectedGauges["consul.usage.test.consul.members.clients;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.members.clients", + Value: 0, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + cases["peerings"] = peeringsCase + delete(cases, "nodes") for name, tcase := range cases { t.Run(name, func(t *testing.T) { @@ -829,420 +513,134 @@ func TestUsageReporter_emitPeeringUsage_OSS(t *testing.T) { } func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) { - type testCase struct { - modfiyStateStore func(t *testing.T, s *state.Store) - getMembersFunc getMembersFunc - expectedGauges map[string]metrics.GaugeValue + cases := make(map[string]testCase) + for k, v := range baseCases { + eg := make(map[string]metrics.GaugeValue) + for k, v := range v.expectedGauges { + eg[k] = v + } + cases[k] = testCase{v.modfiyStateStore, v.getMembersFunc, eg} } - cases := map[string]testCase{ - "empty-state": { - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - getMembersFunc: func() []serf.Member { return []serf.Member{} }, - }, - "nodes-and-services": { - modfiyStateStore: func(t *testing.T, s *state.Store) { - require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})) - require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"})) - require.NoError(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"})) - require.NoError(t, s.EnsureNode(4, &structs.Node{Node: "qux", Address: "127.0.0.3"})) - mgw := structs.TestNodeServiceMeshGateway(t) - mgw.ID = "mesh-gateway" + nodesAndSvcsCase := cases["nodes"] + nodesAndSvcsCase.modfiyStateStore = func(t *testing.T, s *state.Store) { + require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})) + require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"})) + require.NoError(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"})) + require.NoError(t, s.EnsureNode(4, &structs.Node{Node: "qux", Address: "127.0.0.3"})) - tgw := structs.TestNodeServiceTerminatingGateway(t, "1.1.1.1") - tgw.ID = "terminating-gateway" - // Typical services and some consul services spread across two nodes - require.NoError(t, s.EnsureService(5, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: nil, Address: "", Port: 5000})) - require.NoError(t, s.EnsureService(6, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000})) - require.NoError(t, s.EnsureService(7, "foo", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil})) - require.NoError(t, s.EnsureService(8, "bar", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil})) - require.NoError(t, s.EnsureService(9, "foo", &structs.NodeService{ID: "db-connect-proxy", Service: "db-connect-proxy", Tags: nil, Address: "", Port: 5000, Kind: structs.ServiceKindConnectProxy})) - require.NoError(t, s.EnsureRegistration(10, structs.TestRegisterIngressGateway(t))) - require.NoError(t, s.EnsureService(11, "foo", mgw)) - require.NoError(t, s.EnsureService(12, "foo", tgw)) - require.NoError(t, s.EnsureService(13, "bar", &structs.NodeService{ID: "db-native", Service: "db", Tags: nil, Address: "", Port: 5000, Connect: structs.ServiceConnect{Native: true}})) - require.NoError(t, s.EnsureConfigEntry(14, &structs.IngressGatewayConfigEntry{ - Kind: structs.IngressGateway, - Name: "foo", - })) - require.NoError(t, s.EnsureConfigEntry(15, &structs.IngressGatewayConfigEntry{ - Kind: structs.IngressGateway, - Name: "bar", - })) - require.NoError(t, s.EnsureConfigEntry(16, &structs.IngressGatewayConfigEntry{ - Kind: structs.IngressGateway, - Name: "baz", - })) - }, - getMembersFunc: func() []serf.Member { - return []serf.Member{ - { - Name: "foo", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "bar", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "baz", - Tags: map[string]string{"role": "node", "segment": "a"}, - Status: serf.StatusAlive, - }, - { - Name: "qux", - Tags: map[string]string{"role": "node", "segment": "b"}, - Status: serf.StatusAlive, - }, - } - }, - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 4, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 2, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 2, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 7, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 9, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - }, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 1, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 1, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 1, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 1, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 1, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 3, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, + mgw := structs.TestNodeServiceMeshGateway(t) + mgw.ID = "mesh-gateway" + + tgw := structs.TestNodeServiceTerminatingGateway(t, "1.1.1.1") + tgw.ID = "terminating-gateway" + // Typical services and some consul services spread across two nodes + require.NoError(t, s.EnsureService(5, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: nil, Address: "", Port: 5000})) + require.NoError(t, s.EnsureService(6, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000})) + require.NoError(t, s.EnsureService(7, "foo", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil})) + require.NoError(t, s.EnsureService(8, "bar", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil})) + require.NoError(t, s.EnsureService(9, "foo", &structs.NodeService{ID: "db-connect-proxy", Service: "db-connect-proxy", Tags: nil, Address: "", Port: 5000, Kind: structs.ServiceKindConnectProxy})) + require.NoError(t, s.EnsureRegistration(10, structs.TestRegisterIngressGateway(t))) + require.NoError(t, s.EnsureService(11, "foo", mgw)) + require.NoError(t, s.EnsureService(12, "foo", tgw)) + require.NoError(t, s.EnsureService(13, "bar", &structs.NodeService{ID: "db-native", Service: "db", Tags: nil, Address: "", Port: 5000, Connect: structs.ServiceConnect{Native: true}})) + require.NoError(t, s.EnsureConfigEntry(14, &structs.IngressGatewayConfigEntry{ + Kind: structs.IngressGateway, + Name: "foo", + })) + require.NoError(t, s.EnsureConfigEntry(15, &structs.IngressGatewayConfigEntry{ + Kind: structs.IngressGateway, + Name: "bar", + })) + require.NoError(t, s.EnsureConfigEntry(16, &structs.IngressGatewayConfigEntry{ + Kind: structs.IngressGateway, + Name: "baz", + })) + } + baseCaseMembers := nodesAndSvcsCase.getMembersFunc() + nodesAndSvcsCase.getMembersFunc = func() []serf.Member { + baseCaseMembers = append(baseCaseMembers, serf.Member{ + Name: "baz", + Tags: map[string]string{"role": "node", "segment": "a"}, + Status: serf.StatusAlive, + }) + baseCaseMembers = append(baseCaseMembers, serf.Member{ + Name: "qux", + Tags: map[string]string{"role": "node", "segment": "b"}, + Status: serf.StatusAlive, + }) + return baseCaseMembers + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.nodes;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.nodes", + Value: 4, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.members.clients;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.members.clients", + Value: 2, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.services;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.services", + Value: 7, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.service_instances;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.service_instances", + Value: 9, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.connect_instances", + Value: 1, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "connect-proxy"}, }, } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.connect_instances", + Value: 1, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "terminating-gateway"}, + }, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.connect_instances", + Value: 1, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "ingress-gateway"}, + }, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.connect_instances", + Value: 1, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "mesh-gateway"}, + }, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.connect_instances", + Value: 1, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "connect-native"}, + }, + } + nodesAndSvcsCase.expectedGauges["consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.config_entries", + Value: 3, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "ingress-gateway"}, + }, + } + cases["nodes-and-services"] = nodesAndSvcsCase + delete(cases, "nodes") for name, tcase := range cases { t.Run(name, func(t *testing.T) { @@ -1280,379 +678,34 @@ func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) { } func TestUsageReporter_emitKVUsage_OSS(t *testing.T) { - type testCase struct { - modfiyStateStore func(t *testing.T, s *state.Store) - getMembersFunc getMembersFunc - expectedGauges map[string]metrics.GaugeValue + cases := make(map[string]testCase) + for k, v := range baseCases { + eg := make(map[string]metrics.GaugeValue) + for k, v := range v.expectedGauges { + eg[k] = v + } + cases[k] = testCase{v.modfiyStateStore, v.getMembersFunc, eg} } - cases := map[string]testCase{ - "empty-state": { - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - getMembersFunc: func() []serf.Member { return []serf.Member{} }, - }, - "nodes": { - modfiyStateStore: func(t *testing.T, s *state.Store) { - require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})) - require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"})) - require.NoError(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"})) - require.NoError(t, s.KVSSet(4, &structs.DirEntry{Key: "a", Value: []byte{1}})) - require.NoError(t, s.KVSSet(5, &structs.DirEntry{Key: "b", Value: []byte{1}})) - require.NoError(t, s.KVSSet(6, &structs.DirEntry{Key: "c", Value: []byte{1}})) - require.NoError(t, s.KVSSet(7, &structs.DirEntry{Key: "d", Value: []byte{1}})) - require.NoError(t, s.KVSDelete(8, "d", &acl.EnterpriseMeta{})) - require.NoError(t, s.KVSDelete(9, "c", &acl.EnterpriseMeta{})) - require.NoError(t, s.KVSSet(10, &structs.DirEntry{Key: "e", Value: []byte{1}})) - require.NoError(t, s.KVSSet(11, &structs.DirEntry{Key: "f", Value: []byte{1}})) - }, - getMembersFunc: func() []serf.Member { - return []serf.Member{ - { - Name: "foo", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "bar", - Tags: map[string]string{"role": "consul"}, - Status: serf.StatusAlive, - }, - { - Name: "baz", - Tags: map[string]string{"role": "node"}, - Status: serf.StatusAlive, - }, - } - }, - expectedGauges: map[string]metrics.GaugeValue{ - // --- node --- - "consul.usage.test.consul.state.nodes;datacenter=dc1": { - Name: "consul.usage.test.consul.state.nodes", - Value: 3, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- peering --- - "consul.usage.test.consul.state.peerings;datacenter=dc1": { - Name: "consul.usage.test.consul.state.peerings", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- member --- - "consul.usage.test.consul.members.servers;datacenter=dc1": { - Name: "consul.usage.test.consul.members.servers", - Value: 2, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.members.clients;datacenter=dc1": { - Name: "consul.usage.test.consul.members.clients", - Value: 1, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service --- - "consul.usage.test.consul.state.services;datacenter=dc1": { - Name: "consul.usage.test.consul.state.services", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - "consul.usage.test.consul.state.service_instances;datacenter=dc1": { - Name: "consul.usage.test.consul.state.service_instances", - Value: 0, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- service mesh --- - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-proxy"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh-gateway"}, - }, - }, - "consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": { - Name: "consul.usage.test.consul.state.connect_instances", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "connect-native"}, - }, - }, - // --- kv --- - "consul.usage.test.consul.state.kv_entries;datacenter=dc1": { - Name: "consul.usage.test.consul.state.kv_entries", - Value: 4, - Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, - }, - // --- config entries --- - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-intentions"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-resolver"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-router"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "ingress-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "service-splitter"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=mesh": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "mesh"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "proxy-defaults"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "terminating-gateway"}, - }, - }, - "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": { - Name: "consul.usage.test.consul.state.config_entries", - Value: 0, - Labels: []metrics.Label{ - {Name: "datacenter", Value: "dc1"}, - {Name: "kind", Value: "exported-services"}, - }, - }, - }, - }, + nodesCase := cases["nodes"] + mss := nodesCase.modfiyStateStore + nodesCase.modfiyStateStore = func(t *testing.T, s *state.Store) { + mss(t, s) + require.NoError(t, s.KVSSet(4, &structs.DirEntry{Key: "a", Value: []byte{1}})) + require.NoError(t, s.KVSSet(5, &structs.DirEntry{Key: "b", Value: []byte{1}})) + require.NoError(t, s.KVSSet(6, &structs.DirEntry{Key: "c", Value: []byte{1}})) + require.NoError(t, s.KVSSet(7, &structs.DirEntry{Key: "d", Value: []byte{1}})) + require.NoError(t, s.KVSDelete(8, "d", &acl.EnterpriseMeta{})) + require.NoError(t, s.KVSDelete(9, "c", &acl.EnterpriseMeta{})) + require.NoError(t, s.KVSSet(10, &structs.DirEntry{Key: "e", Value: []byte{1}})) + require.NoError(t, s.KVSSet(11, &structs.DirEntry{Key: "f", Value: []byte{1}})) } + nodesCase.expectedGauges["consul.usage.test.consul.state.kv_entries;datacenter=dc1"] = metrics.GaugeValue{ + Name: "consul.usage.test.consul.state.kv_entries", + Value: 4, + Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}}, + } + cases["nodes"] = nodesCase for name, tcase := range cases { t.Run(name, func(t *testing.T) { diff --git a/agent/discovery_chain_endpoint_test.go b/agent/discovery_chain_endpoint_test.go index 8b4a7e2723..42c0825916 100644 --- a/agent/discovery_chain_endpoint_test.go +++ b/agent/discovery_chain_endpoint_test.go @@ -27,8 +27,17 @@ func TestDiscoveryChainRead(t *testing.T) { defer a.Shutdown() testrpc.WaitForTestAgent(t, a.RPC, "dc1") - newTarget := func(service, serviceSubset, namespace, partition, datacenter string) *structs.DiscoveryTarget { - t := structs.NewDiscoveryTarget(service, serviceSubset, namespace, partition, datacenter) + newTarget := func(opts structs.DiscoveryTargetOpts) *structs.DiscoveryTarget { + if opts.Namespace == "" { + opts.Namespace = "default" + } + if opts.Partition == "" { + opts.Partition = "default" + } + if opts.Datacenter == "" { + opts.Datacenter = "dc1" + } + t := structs.NewDiscoveryTarget(opts) t.SNI = connect.TargetSNI(t, connect.TestClusterID+".consul") t.Name = t.SNI t.ConnectTimeout = 5 * time.Second // default @@ -99,7 +108,7 @@ func TestDiscoveryChainRead(t *testing.T) { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "web.default.default.dc1": newTarget("web", "", "default", "default", "dc1"), + "web.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "web"}), }, } require.Equal(t, expect, value.Chain) @@ -144,7 +153,7 @@ func TestDiscoveryChainRead(t *testing.T) { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "web.default.default.dc2": newTarget("web", "", "default", "default", "dc2"), + "web.default.default.dc2": newTarget(structs.DiscoveryTargetOpts{Service: "web", Datacenter: "dc2"}), }, } require.Equal(t, expect, value.Chain) @@ -198,7 +207,7 @@ func TestDiscoveryChainRead(t *testing.T) { }, }, Targets: map[string]*structs.DiscoveryTarget{ - "web.default.default.dc1": newTarget("web", "", "default", "default", "dc1"), + "web.default.default.dc1": newTarget(structs.DiscoveryTargetOpts{Service: "web"}), }, } require.Equal(t, expect, value.Chain) @@ -264,11 +273,11 @@ func TestDiscoveryChainRead(t *testing.T) { }, Targets: map[string]*structs.DiscoveryTarget{ "web.default.default.dc1": targetWithConnectTimeout( - newTarget("web", "", "default", "default", "dc1"), + newTarget(structs.DiscoveryTargetOpts{Service: "web"}), 33*time.Second, ), "web.default.default.dc2": targetWithConnectTimeout( - newTarget("web", "", "default", "default", "dc2"), + newTarget(structs.DiscoveryTargetOpts{Service: "web", Datacenter: "dc2"}), 33*time.Second, ), }, @@ -280,7 +289,7 @@ func TestDiscoveryChainRead(t *testing.T) { })) expectTarget_DC1 := targetWithConnectTimeout( - newTarget("web", "", "default", "default", "dc1"), + newTarget(structs.DiscoveryTargetOpts{Service: "web"}), 22*time.Second, ) expectTarget_DC1.MeshGateway = structs.MeshGatewayConfig{ @@ -288,7 +297,7 @@ func TestDiscoveryChainRead(t *testing.T) { } expectTarget_DC2 := targetWithConnectTimeout( - newTarget("web", "", "default", "default", "dc2"), + newTarget(structs.DiscoveryTargetOpts{Service: "web", Datacenter: "dc2"}), 22*time.Second, ) expectTarget_DC2.MeshGateway = structs.MeshGatewayConfig{ diff --git a/agent/grpc-external/server.go b/agent/grpc-external/server.go index 751cca91c8..4ae8c6d652 100644 --- a/agent/grpc-external/server.go +++ b/agent/grpc-external/server.go @@ -1,12 +1,13 @@ package external import ( + "time" + middleware "github.com/grpc-ecosystem/go-grpc-middleware" recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" - "time" agentmiddleware "github.com/hashicorp/consul/agent/grpc-middleware" "github.com/hashicorp/consul/tlsutil" @@ -34,7 +35,7 @@ func NewServer(logger agentmiddleware.Logger, tls *tlsutil.Configurator) *grpc.S MinTime: 15 * time.Second, }), } - if tls != nil && tls.GRPCTLSConfigured() { + if tls != nil && tls.GRPCServerUseTLS() { creds := credentials.NewTLS(tls.IncomingGRPCConfig()) opts = append(opts, grpc.Creds(creds)) } diff --git a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go index bed302d12b..b320559e98 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go @@ -52,13 +52,21 @@ func (s *Server) GetEnvoyBootstrapParams(ctx context.Context, req *pbdataplane.G } // Build out the response + var serviceName string + if svc.ServiceKind == structs.ServiceKindConnectProxy { + serviceName = svc.ServiceProxy.DestinationServiceName + } else { + serviceName = svc.ServiceName + } resp := &pbdataplane.GetEnvoyBootstrapParamsResponse{ - Service: svc.ServiceProxy.DestinationServiceName, + Service: serviceName, Partition: svc.EnterpriseMeta.PartitionOrDefault(), Namespace: svc.EnterpriseMeta.NamespaceOrDefault(), Datacenter: s.Datacenter, ServiceKind: convertToResponseServiceKind(svc.ServiceKind), + NodeName: svc.Node, + NodeId: string(svc.ID), } bootstrapConfig, err := structpb.NewStruct(svc.ServiceProxy.Config) diff --git a/agent/grpc-external/services/dataplane/get_envoy_boostrap_params_test.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go similarity index 96% rename from agent/grpc-external/services/dataplane/get_envoy_boostrap_params_test.go rename to agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go index c3b4fd1468..aa42b0bf13 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_boostrap_params_test.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go @@ -97,14 +97,20 @@ func TestGetEnvoyBootstrapParams_Success(t *testing.T) { resp, err := client.GetEnvoyBootstrapParams(ctx, req) require.NoError(t, err) - require.Equal(t, tc.registerReq.Service.Proxy.DestinationServiceName, resp.Service) + if tc.registerReq.Service.IsGateway() { + require.Equal(t, tc.registerReq.Service.Service, resp.Service) + } else { + require.Equal(t, tc.registerReq.Service.Proxy.DestinationServiceName, resp.Service) + } + require.Equal(t, serverDC, resp.Datacenter) require.Equal(t, tc.registerReq.EnterpriseMeta.PartitionOrDefault(), resp.Partition) require.Equal(t, tc.registerReq.EnterpriseMeta.NamespaceOrDefault(), resp.Namespace) require.Contains(t, resp.Config.Fields, proxyConfigKey) require.Equal(t, structpb.NewStringValue(proxyConfigValue), resp.Config.Fields[proxyConfigKey]) require.Equal(t, convertToResponseServiceKind(tc.registerReq.Service.Kind), resp.ServiceKind) - + require.Equal(t, tc.registerReq.Node, resp.NodeName) + require.Equal(t, string(tc.registerReq.ID), resp.NodeId) } testCases := []testCase{ diff --git a/agent/grpc-external/services/peerstream/server.go b/agent/grpc-external/services/peerstream/server.go index aada8876cf..6568d7bf80 100644 --- a/agent/grpc-external/services/peerstream/server.go +++ b/agent/grpc-external/services/peerstream/server.go @@ -42,8 +42,8 @@ type Config struct { // outgoingHeartbeatInterval is how often we send a heartbeat. outgoingHeartbeatInterval time.Duration - // incomingHeartbeatTimeout is how long we'll wait between receiving heartbeats before we close the connection. - incomingHeartbeatTimeout time.Duration + // IncomingHeartbeatTimeout is how long we'll wait between receiving heartbeats before we close the connection. + IncomingHeartbeatTimeout time.Duration } //go:generate mockery --name ACLResolver --inpackage @@ -63,8 +63,8 @@ func NewServer(cfg Config) *Server { if cfg.outgoingHeartbeatInterval == 0 { cfg.outgoingHeartbeatInterval = defaultOutgoingHeartbeatInterval } - if cfg.incomingHeartbeatTimeout == 0 { - cfg.incomingHeartbeatTimeout = defaultIncomingHeartbeatTimeout + if cfg.IncomingHeartbeatTimeout == 0 { + cfg.IncomingHeartbeatTimeout = defaultIncomingHeartbeatTimeout } return &Server{ Config: cfg, @@ -99,7 +99,7 @@ type Backend interface { GetLeaderAddress() string ValidateProposedPeeringSecret(id string) (bool, error) - PeeringSecretsWrite(req *pbpeering.PeeringSecrets) error + PeeringSecretsWrite(req *pbpeering.SecretsWriteRequest) error PeeringTerminateByID(req *pbpeering.PeeringTerminateByIDRequest) error PeeringTrustBundleWrite(req *pbpeering.PeeringTrustBundleWriteRequest) error CatalogRegister(req *structs.RegisterRequest) error diff --git a/agent/grpc-external/services/peerstream/server_test.go b/agent/grpc-external/services/peerstream/server_test.go index d8a5382e1f..a24d8edc2f 100644 --- a/agent/grpc-external/services/peerstream/server_test.go +++ b/agent/grpc-external/services/peerstream/server_test.go @@ -25,13 +25,17 @@ func TestServer_ExchangeSecret(t *testing.T) { var secret string testutil.RunStep(t, "known establishment secret is accepted", func(t *testing.T) { - require.NoError(t, store.PeeringSecretsWrite(1, &pbpeering.PeeringSecrets{ - PeerID: testPeerID, - Establishment: &pbpeering.PeeringSecrets_Establishment{SecretID: testEstablishmentSecretID}, - Stream: &pbpeering.PeeringSecrets_Stream{ - ActiveSecretID: testActiveStreamSecretID, + // First write the establishment secret so that it can be exchanged + require.NoError(t, store.PeeringSecretsWrite(1, &pbpeering.SecretsWriteRequest{ + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testEstablishmentSecretID, + }, }, })) + + // Exchange the now-valid establishment secret for a stream secret resp, err := srv.ExchangeSecret(context.Background(), &pbpeerstream.ExchangeSecretRequest{ PeerID: testPeerID, EstablishmentSecret: testEstablishmentSecretID, @@ -47,8 +51,5 @@ func TestServer_ExchangeSecret(t *testing.T) { require.NoError(t, err) require.Equal(t, secret, s.GetStream().GetPendingSecretID()) - - // Active stream secret persists until pending secret is promoted during peering establishment. - require.Equal(t, testActiveStreamSecretID, s.GetStream().GetActiveSecretID()) }) } diff --git a/agent/grpc-external/services/peerstream/stream_resources.go b/agent/grpc-external/services/peerstream/stream_resources.go index 2542c2e4aa..0e6b28f45a 100644 --- a/agent/grpc-external/services/peerstream/stream_resources.go +++ b/agent/grpc-external/services/peerstream/stream_resources.go @@ -77,20 +77,21 @@ func (s *Server) ExchangeSecret(ctx context.Context, req *pbpeerstream.ExchangeS return nil, grpcstatus.Errorf(codes.Internal, "failed to generate peering stream secret: %v", err) } - secrets := &pbpeering.PeeringSecrets{ + writeReq := &pbpeering.SecretsWriteRequest{ PeerID: req.PeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - // Overwriting any existing un-utilized pending stream secret. - PendingSecretID: id, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + // Pass the given establishment secret to that it can be re-validated at the state store. + // Validating the establishment secret at the RPC is not enough because there can be + // concurrent callers with the same establishment secret. + EstablishmentSecret: req.EstablishmentSecret, - // If there is an active stream secret ID it is NOT invalidated here. - // It remains active until the pending secret ID is used and promoted to active. - // This allows dialing clusters with the active stream secret to continue to dial successfully until they - // receive the new secret. - ActiveSecretID: existing.GetStream().GetActiveSecretID(), + // Overwrite any existing un-utilized pending stream secret. + PendingStreamSecret: id, + }, }, } - err = s.Backend.PeeringSecretsWrite(secrets) + err = s.Backend.PeeringSecretsWrite(writeReq) if err != nil { return nil, grpcstatus.Errorf(codes.Internal, "failed to persist peering secret: %v", err) } @@ -191,14 +192,13 @@ func (s *Server) StreamResources(stream pbpeerstream.PeerStreamService_StreamRes } authorized = true - promoted := &pbpeering.PeeringSecrets{ - PeerID: req.PeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - ActiveSecretID: pending, - - // The PendingSecretID is intentionally zeroed out since we want to avoid re-triggering this - // promotion process with the same pending secret. - PendingSecretID: "", + promoted := &pbpeering.SecretsWriteRequest{ + PeerID: p.ID, + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + // Overwrite any existing un-utilized pending stream secret. + ActiveStreamSecret: pending, + }, }, } err = s.Backend.PeeringSecretsWrite(promoted) @@ -406,7 +406,7 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { // incomingHeartbeatCtx will complete if incoming heartbeats time out. incomingHeartbeatCtx, incomingHeartbeatCtxCancel := - context.WithTimeout(context.Background(), s.incomingHeartbeatTimeout) + context.WithTimeout(context.Background(), s.IncomingHeartbeatTimeout) // NOTE: It's important that we wrap the call to cancel in a wrapper func because during the loop we're // re-assigning the value of incomingHeartbeatCtxCancel and we want the defer to run on the last assigned // value, not the current value. @@ -447,6 +447,8 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { // exits. After the method exits this code here won't receive any recv errors and those will be handled // by DrainStream(). err = fmt.Errorf("stream ended unexpectedly") + } else { + err = fmt.Errorf("unexpected error receiving from the stream: %w", err) } status.TrackRecvError(err.Error()) return err @@ -603,7 +605,7 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { // They just can't trace the execution properly for some reason (possibly golang/go#29587). //nolint:govet incomingHeartbeatCtx, incomingHeartbeatCtxCancel = - context.WithTimeout(context.Background(), s.incomingHeartbeatTimeout) + context.WithTimeout(context.Background(), s.IncomingHeartbeatTimeout) } case update := <-subCh: @@ -640,6 +642,7 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { if err := streamSend(replResp); err != nil { return fmt.Errorf("failed to push data for %q: %w", update.CorrelationID, err) } + status.TrackSendSuccess() } } } @@ -684,10 +687,29 @@ func logTraceProto(logger hclog.Logger, pb proto.Message, received bool) { dir = "received" } + // Redact the long-lived stream secret to avoid leaking it in trace logs. + pbToLog := pb + switch msg := pb.(type) { + case *pbpeerstream.ReplicationMessage: + clone := &pbpeerstream.ReplicationMessage{} + proto.Merge(clone, msg) + + if clone.GetOpen() != nil { + clone.GetOpen().StreamSecretID = "hidden" + pbToLog = clone + } + case *pbpeerstream.ReplicationMessage_Open: + clone := &pbpeerstream.ReplicationMessage_Open{} + proto.Merge(clone, msg) + + clone.StreamSecretID = "hidden" + pbToLog = clone + } + m := jsonpb.Marshaler{ Indent: " ", } - out, err := m.MarshalToString(pb) + out, err := m.MarshalToString(pbToLog) if err != nil { out = "" } diff --git a/agent/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index e6e26e81d0..be4a44ec87 100644 --- a/agent/grpc-external/services/peerstream/stream_test.go +++ b/agent/grpc-external/services/peerstream/stream_test.go @@ -1,6 +1,7 @@ package peerstream import ( + "bytes" "context" "fmt" "io" @@ -10,13 +11,14 @@ import ( "testing" "time" + "github.com/golang/protobuf/proto" "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" "google.golang.org/genproto/googleapis/rpc/code" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" + newproto "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/consul/acl" @@ -26,6 +28,7 @@ import ( "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/proto/pbcommon" "github.com/hashicorp/consul/proto/pbpeering" "github.com/hashicorp/consul/proto/pbpeerstream" @@ -178,9 +181,13 @@ func TestStreamResources_Server_LeaderBecomesFollower(t *testing.T) { } func TestStreamResources_Server_ActiveSecretValidation(t *testing.T) { + type testSeed struct { + peering *pbpeering.Peering + secrets []*pbpeering.SecretsWriteRequest + } type testCase struct { name string - seed *pbpeering.PeeringWriteRequest + seed *testSeed input *pbpeerstream.ReplicationMessage wantErr error } @@ -191,7 +198,13 @@ func TestStreamResources_Server_ActiveSecretValidation(t *testing.T) { srv, store := newTestServer(t, nil) // Write a seed peering. - require.NoError(t, store.PeeringWrite(1, tc.seed)) + if tc.seed != nil { + require.NoError(t, store.PeeringWrite(1, &pbpeering.PeeringWriteRequest{Peering: tc.seed.peering})) + + for _, s := range tc.seed.secrets { + require.NoError(t, store.PeeringSecretsWrite(1, s)) + } + } // Set the initial roots and CA configuration. _, _ = writeInitialRootsAndCA(t, store) @@ -220,12 +233,14 @@ func TestStreamResources_Server_ActiveSecretValidation(t *testing.T) { } else { require.NoError(t, err) } + + client.Close() } tt := []testCase{ { name: "no secret for peering", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: peeringWithoutSecrets, }, @@ -241,15 +256,19 @@ func TestStreamResources_Server_ActiveSecretValidation(t *testing.T) { }, { name: "unknown secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testPeerID, }, - Secret: &pbpeering.PeeringSecrets{ - PeerID: testPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - ActiveSecretID: testActiveStreamSecretID, + secrets: []*pbpeering.SecretsWriteRequest{ + { + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testEstablishmentSecretID, + }, + }, }, }, }, @@ -264,16 +283,29 @@ func TestStreamResources_Server_ActiveSecretValidation(t *testing.T) { wantErr: status.Error(codes.PermissionDenied, "invalid peering stream secret"), }, { - name: "known active secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "known pending secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testPeerID, }, - Secret: &pbpeering.PeeringSecrets{ - PeerID: testPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - ActiveSecretID: testActiveStreamSecretID, + secrets: []*pbpeering.SecretsWriteRequest{ + { + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testEstablishmentSecretID, + }, + }, + }, + { + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + EstablishmentSecret: testEstablishmentSecretID, + PendingStreamSecret: testPendingStreamSecretID, + }, + }, }, }, }, @@ -281,22 +313,44 @@ func TestStreamResources_Server_ActiveSecretValidation(t *testing.T) { Payload: &pbpeerstream.ReplicationMessage_Open_{ Open: &pbpeerstream.ReplicationMessage_Open{ PeerID: testPeerID, - StreamSecretID: testActiveStreamSecretID, + StreamSecretID: testPendingStreamSecretID, }, }, }, }, { - name: "known pending secret", - seed: &pbpeering.PeeringWriteRequest{ - Peering: &pbpeering.Peering{ + name: "known active secret", + seed: &testSeed{ + peering: &pbpeering.Peering{ Name: "foo", ID: testPeerID, }, - Secret: &pbpeering.PeeringSecrets{ - PeerID: testPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - PendingSecretID: testPendingStreamSecretID, + secrets: []*pbpeering.SecretsWriteRequest{ + { + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testEstablishmentSecretID, + }, + }, + }, + { + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + EstablishmentSecret: testEstablishmentSecretID, + PendingStreamSecret: testPendingStreamSecretID, + }, + }, + }, + { + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_PromotePending{ + PromotePending: &pbpeering.SecretsWriteRequest_PromotePendingRequest{ + // Pending gets promoted to active. + ActiveStreamSecret: testPendingStreamSecretID, + }, + }, }, }, }, @@ -518,7 +572,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { }) }) - var lastSendSuccess time.Time + var lastSendAck, lastSendSuccess time.Time testutil.RunStep(t, "ack tracked as success", func(t *testing.T) { ack := &pbpeerstream.ReplicationMessage{ @@ -533,19 +587,22 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { }, } - lastSendSuccess = it.FutureNow(1) + lastSendAck = time.Date(2000, time.January, 1, 0, 0, 2, 0, time.UTC) + lastSendSuccess = time.Date(2000, time.January, 1, 0, 0, 3, 0, time.UTC) err := client.Send(ack) require.NoError(t, err) expect := Status{ - Connected: true, - LastAck: lastSendSuccess, + Connected: true, + LastAck: lastSendAck, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, + LastSendSuccess: lastSendSuccess, } retry.Run(t, func(r *retry.R) { - status, ok := srv.StreamStatus(testPeerID) + rStatus, ok := srv.StreamStatus(testPeerID) require.True(r, ok) - require.Equal(r, expect, status) + require.Equal(r, expect, rStatus) }) }) @@ -567,23 +624,26 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { }, } - lastNack = it.FutureNow(1) + lastSendAck = time.Date(2000, time.January, 1, 0, 0, 4, 0, time.UTC) + lastNack = time.Date(2000, time.January, 1, 0, 0, 5, 0, time.UTC) err := client.Send(nack) require.NoError(t, err) lastNackMsg = "client peer was unable to apply resource: bad bad not good" expect := Status{ - Connected: true, - LastAck: lastSendSuccess, - LastNack: lastNack, - LastNackMessage: lastNackMsg, + Connected: true, + LastAck: lastSendAck, + LastNack: lastNack, + LastNackMessage: lastNackMsg, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, + LastSendSuccess: lastSendSuccess, } retry.Run(t, func(r *retry.R) { - status, ok := srv.StreamStatus(testPeerID) + rStatus, ok := srv.StreamStatus(testPeerID) require.True(r, ok) - require.Equal(r, expect, status) + require.Equal(r, expect, rStatus) }) }) @@ -640,13 +700,15 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, - LastAck: lastSendSuccess, + LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, LastRecvResourceSuccess: lastRecvResourceSuccess, ImportedServices: map[string]struct{}{ api.String(): {}, }, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, + LastSendSuccess: lastSendSuccess, } retry.Run(t, func(r *retry.R) { @@ -699,7 +761,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, - LastAck: lastSendSuccess, + LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, LastRecvResourceSuccess: lastRecvResourceSuccess, @@ -708,6 +770,8 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { ImportedServices: map[string]struct{}{ api.String(): {}, }, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, + LastSendSuccess: lastSendSuccess, } retry.Run(t, func(r *retry.R) { @@ -731,7 +795,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, - LastAck: lastSendSuccess, + LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, LastRecvResourceSuccess: lastRecvResourceSuccess, @@ -741,6 +805,8 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { ImportedServices: map[string]struct{}{ api.String(): {}, }, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, + LastSendSuccess: lastSendSuccess, } retry.Run(t, func(r *retry.R) { @@ -762,7 +828,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: false, DisconnectErrorMessage: lastRecvErrorMsg, - LastAck: lastSendSuccess, + LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, DisconnectTime: disconnectTime, @@ -773,6 +839,8 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { ImportedServices: map[string]struct{}{ api.String(): {}, }, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, + LastSendSuccess: lastSendSuccess, } retry.Run(t, func(r *retry.R) { @@ -1075,7 +1143,7 @@ func TestStreamResources_Server_DisconnectsOnHeartbeatTimeout(t *testing.T) { srv, store := newTestServer(t, func(c *Config) { c.Tracker.SetClock(it.Now) - c.incomingHeartbeatTimeout = 5 * time.Millisecond + c.IncomingHeartbeatTimeout = 5 * time.Millisecond }) p := writePeeringToBeDialed(t, store, 1, "my-peer") @@ -1182,7 +1250,7 @@ func TestStreamResources_Server_KeepsConnectionOpenWithHeartbeat(t *testing.T) { srv, store := newTestServer(t, func(c *Config) { c.Tracker.SetClock(it.Now) - c.incomingHeartbeatTimeout = incomingHeartbeatTimeout + c.IncomingHeartbeatTimeout = incomingHeartbeatTimeout }) p := writePeeringToBeDialed(t, store, 1, "my-peer") @@ -1387,7 +1455,7 @@ func (b *testStreamBackend) ValidateProposedPeeringSecret(id string) (bool, erro return true, nil } -func (b *testStreamBackend) PeeringSecretsWrite(req *pbpeering.PeeringSecrets) error { +func (b *testStreamBackend) PeeringSecretsWrite(req *pbpeering.SecretsWriteRequest) error { return b.store.PeeringSecretsWrite(1, req) } @@ -1628,12 +1696,25 @@ func writeTestPeering(t *testing.T, store *state.Store, idx uint64, peerName, re if remotePeerID != "" { peering.PeerServerAddresses = []string{"127.0.0.1:5300"} } + require.NoError(t, store.PeeringWrite(idx, &pbpeering.PeeringWriteRequest{ Peering: &peering, - Secret: &pbpeering.PeeringSecrets{ + SecretsRequest: &pbpeering.SecretsWriteRequest{ PeerID: testPeerID, - Stream: &pbpeering.PeeringSecrets_Stream{ - PendingSecretID: testPendingStreamSecretID, + // Simulate generating a stream secret by first generating a token then exchanging for a stream secret. + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: testEstablishmentSecretID, + }, + }, + }, + })) + require.NoError(t, store.PeeringSecretsWrite(idx, &pbpeering.SecretsWriteRequest{ + PeerID: testPeerID, + Request: &pbpeering.SecretsWriteRequest_ExchangeSecret{ + ExchangeSecret: &pbpeering.SecretsWriteRequest_ExchangeSecretRequest{ + EstablishmentSecret: testEstablishmentSecretID, + PendingStreamSecret: testPendingStreamSecretID, }, }, })) @@ -1657,7 +1738,7 @@ func writeInitialRootsAndCA(t *testing.T, store *state.Store) (string, *structs. return clusterID, rootA } -func makeAnyPB(t *testing.T, pb proto.Message) *anypb.Any { +func makeAnyPB(t *testing.T, pb newproto.Message) *anypb.Any { any, err := anypb.New(pb) require.NoError(t, err) return any @@ -2592,6 +2673,51 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { } } +// TestLogTraceProto tests that all PB trace log helpers redact the +// long-lived SecretStreamID. +// We ensure it gets redacted when logging a ReplicationMessage_Open or a ReplicationMessage. +// In the stream handler we only log the ReplicationMessage_Open, but testing both guards against +// a change in that behavior. +func TestLogTraceProto(t *testing.T) { + type testCase struct { + input proto.Message + } + + tt := map[string]testCase{ + "replication message": { + input: &pbpeerstream.ReplicationMessage{ + Payload: &pbpeerstream.ReplicationMessage_Open_{ + Open: &pbpeerstream.ReplicationMessage_Open{ + StreamSecretID: testPendingStreamSecretID, + }, + }, + }, + }, + "open message": { + input: &pbpeerstream.ReplicationMessage_Open{ + StreamSecretID: testPendingStreamSecretID, + }, + }, + } + for name, tc := range tt { + t.Run(name, func(t *testing.T) { + var b bytes.Buffer + logger, err := logging.Setup(logging.Config{ + LogLevel: "TRACE", + }, &b) + require.NoError(t, err) + + logTraceRecv(logger, tc.input) + logTraceSend(logger, tc.input) + logTraceProto(logger, tc.input, false) + + body, err := io.ReadAll(&b) + require.NoError(t, err) + require.NotContains(t, string(body), testPendingStreamSecretID) + }) + } +} + func requireEqualInstances(t *testing.T, expect, got structs.CheckServiceNodes) { t.Helper() diff --git a/agent/grpc-external/services/peerstream/stream_tracker.go b/agent/grpc-external/services/peerstream/stream_tracker.go index f7a451595d..ffde98ba32 100644 --- a/agent/grpc-external/services/peerstream/stream_tracker.go +++ b/agent/grpc-external/services/peerstream/stream_tracker.go @@ -16,6 +16,8 @@ type Tracker struct { // timeNow is a shim for testing. timeNow func() time.Time + + heartbeatTimeout time.Duration } func NewTracker() *Tracker { @@ -33,6 +35,12 @@ func (t *Tracker) SetClock(clock func() time.Time) { } } +func (t *Tracker) SetHeartbeatTimeout(heartbeatTimeout time.Duration) { + t.mu.Lock() + defer t.mu.Unlock() + t.heartbeatTimeout = heartbeatTimeout +} + // Register a stream for a given peer but do not mark it as connected. func (t *Tracker) Register(id string) (*MutableStatus, error) { t.mu.Lock() @@ -44,7 +52,7 @@ func (t *Tracker) Register(id string) (*MutableStatus, error) { func (t *Tracker) registerLocked(id string, initAsConnected bool) (*MutableStatus, bool, error) { status, ok := t.streams[id] if !ok { - status = newMutableStatus(t.timeNow, initAsConnected) + status = newMutableStatus(t.timeNow, t.heartbeatTimeout, initAsConnected) t.streams[id] = status return status, true, nil } @@ -101,7 +109,9 @@ func (t *Tracker) StreamStatus(id string) (resp Status, found bool) { s, ok := t.streams[id] if !ok { - return Status{}, false + return Status{ + NeverConnected: true, + }, false } return s.GetStatus(), true } @@ -142,9 +152,14 @@ type MutableStatus struct { // Status contains information about the replication stream to a peer cluster. // TODO(peering): There's a lot of fields here... type Status struct { + heartbeatTimeout time.Duration + // Connected is true when there is an open stream for the peer. Connected bool + // NeverConnected is true for peerings that have never connected, false otherwise. + NeverConnected bool + // DisconnectErrorMessage tracks the error that caused the stream to disconnect non-gracefully. // If the stream is connected or it disconnected gracefully it will be empty. DisconnectErrorMessage string @@ -167,6 +182,9 @@ type Status struct { // LastSendErrorMessage tracks the last error message when sending into the stream. LastSendErrorMessage string + // LastSendSuccess tracks the time of the last success response sent into the stream. + LastSendSuccess time.Time + // LastRecvHeartbeat tracks when we last received a heartbeat from our peer. LastRecvHeartbeat time.Time @@ -196,10 +214,40 @@ func (s *Status) GetExportedServicesCount() uint64 { return uint64(len(s.ExportedServices)) } -func newMutableStatus(now func() time.Time, connected bool) *MutableStatus { +// IsHealthy is a convenience func that returns true/ false for a peering status. +// We define a peering as unhealthy if its status satisfies one of the following: +// - If heartbeat hasn't been received within the IncomingHeartbeatTimeout +// - If the last sent error is newer than last sent success +// - If the last received error is newer than last received success +// If none of these conditions apply, we call the peering healthy. +func (s *Status) IsHealthy() bool { + if time.Now().Sub(s.LastRecvHeartbeat) > s.heartbeatTimeout { + // 1. If heartbeat hasn't been received for a while - report unhealthy + return false + } + + if s.LastSendError.After(s.LastSendSuccess) { + // 2. If last sent error is newer than last sent success - report unhealthy + return false + } + + if s.LastRecvError.After(s.LastRecvResourceSuccess) { + // 3. If last recv error is newer than last recv success - report unhealthy + return false + } + + return true +} + +func newMutableStatus(now func() time.Time, heartbeatTimeout time.Duration, connected bool) *MutableStatus { + if heartbeatTimeout.Microseconds() == 0 { + heartbeatTimeout = defaultIncomingHeartbeatTimeout + } return &MutableStatus{ Status: Status{ - Connected: connected, + Connected: connected, + heartbeatTimeout: heartbeatTimeout, + NeverConnected: !connected, }, timeNow: now, doneCh: make(chan struct{}), @@ -223,6 +271,12 @@ func (s *MutableStatus) TrackSendError(error string) { s.mu.Unlock() } +func (s *MutableStatus) TrackSendSuccess() { + s.mu.Lock() + s.LastSendSuccess = s.timeNow().UTC() + s.mu.Unlock() +} + // TrackRecvResourceSuccess tracks receiving a replicated resource. func (s *MutableStatus) TrackRecvResourceSuccess() { s.mu.Lock() diff --git a/agent/grpc-external/services/peerstream/stream_tracker_test.go b/agent/grpc-external/services/peerstream/stream_tracker_test.go index f7a9df321d..8cdcbc79a2 100644 --- a/agent/grpc-external/services/peerstream/stream_tracker_test.go +++ b/agent/grpc-external/services/peerstream/stream_tracker_test.go @@ -10,6 +10,97 @@ import ( "github.com/hashicorp/consul/sdk/testutil" ) +const ( + aPeerID = "63b60245-c475-426b-b314-4588d210859d" +) + +func TestStatus_IsHealthy(t *testing.T) { + type testcase struct { + name string + dontConnect bool + modifierFunc func(status *MutableStatus) + expectedVal bool + heartbeatTimeout time.Duration + } + + tcs := []testcase{ + { + name: "never connected, unhealthy", + expectedVal: false, + dontConnect: true, + }, + { + name: "no heartbeat, unhealthy", + expectedVal: false, + }, + { + name: "heartbeat is not received, unhealthy", + expectedVal: false, + modifierFunc: func(status *MutableStatus) { + // set heartbeat + status.LastRecvHeartbeat = time.Now().Add(-1 * time.Second) + }, + heartbeatTimeout: 1 * time.Second, + }, + { + name: "send error before send success", + expectedVal: false, + modifierFunc: func(status *MutableStatus) { + // set heartbeat + status.LastRecvHeartbeat = time.Now() + + status.LastSendSuccess = time.Now() + status.LastSendError = time.Now() + }, + }, + { + name: "received error before received success", + expectedVal: false, + modifierFunc: func(status *MutableStatus) { + // set heartbeat + status.LastRecvHeartbeat = time.Now() + + status.LastRecvResourceSuccess = time.Now() + status.LastRecvError = time.Now() + }, + }, + { + name: "healthy", + expectedVal: true, + modifierFunc: func(status *MutableStatus) { + // set heartbeat + status.LastRecvHeartbeat = time.Now() + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + tracker := NewTracker() + if tc.heartbeatTimeout.Microseconds() != 0 { + tracker.SetHeartbeatTimeout(tc.heartbeatTimeout) + } + + if !tc.dontConnect { + st, err := tracker.Connected(aPeerID) + require.NoError(t, err) + require.True(t, st.Connected) + + if tc.modifierFunc != nil { + tc.modifierFunc(st) + } + + require.Equal(t, tc.expectedVal, st.IsHealthy()) + + } else { + st, found := tracker.StreamStatus(aPeerID) + require.False(t, found) + require.Equal(t, tc.expectedVal, st.IsHealthy()) + } + }) + } +} + func TestTracker_EnsureConnectedDisconnected(t *testing.T) { tracker := NewTracker() peerID := "63b60245-c475-426b-b314-4588d210859d" @@ -29,7 +120,8 @@ func TestTracker_EnsureConnectedDisconnected(t *testing.T) { require.NoError(t, err) expect := Status{ - Connected: true, + Connected: true, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, } status, ok := tracker.StreamStatus(peerID) @@ -55,8 +147,9 @@ func TestTracker_EnsureConnectedDisconnected(t *testing.T) { lastSuccess = it.base.Add(time.Duration(sequence) * time.Second).UTC() expect := Status{ - Connected: true, - LastAck: lastSuccess, + Connected: true, + LastAck: lastSuccess, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, } require.Equal(t, expect, status) }) @@ -66,9 +159,10 @@ func TestTracker_EnsureConnectedDisconnected(t *testing.T) { sequence++ expect := Status{ - Connected: false, - DisconnectTime: it.base.Add(time.Duration(sequence) * time.Second).UTC(), - LastAck: lastSuccess, + Connected: false, + DisconnectTime: it.base.Add(time.Duration(sequence) * time.Second).UTC(), + LastAck: lastSuccess, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, } status, ok := tracker.StreamStatus(peerID) require.True(t, ok) @@ -80,8 +174,9 @@ func TestTracker_EnsureConnectedDisconnected(t *testing.T) { require.NoError(t, err) expect := Status{ - Connected: true, - LastAck: lastSuccess, + Connected: true, + LastAck: lastSuccess, + heartbeatTimeout: defaultIncomingHeartbeatTimeout, // DisconnectTime gets cleared on re-connect. } @@ -96,7 +191,7 @@ func TestTracker_EnsureConnectedDisconnected(t *testing.T) { status, ok := tracker.StreamStatus(peerID) require.False(t, ok) - require.Zero(t, status) + require.Equal(t, Status{NeverConnected: true}, status) }) } diff --git a/agent/local/state.go b/agent/local/state.go index 7909982dba..68a29b3a2e 100644 --- a/agent/local/state.go +++ b/agent/local/state.go @@ -46,10 +46,6 @@ var StateCounters = []prometheus.CounterDefinition{ Name: []string{"acl", "blocked", "node", "registration"}, Help: "Increments whenever a registration fails for a node (blocked by an ACL)", }, - { - Name: []string{"acl", "blocked", "node", "deregistration"}, - Help: "Increments whenever a deregistration fails for a node (blocked by an ACL)", - }, } const fullSyncReadMaxStale = 2 * time.Second diff --git a/agent/proxycfg-glue/glue.go b/agent/proxycfg-glue/glue.go index 86badf67e4..1b22b02bd4 100644 --- a/agent/proxycfg-glue/glue.go +++ b/agent/proxycfg-glue/glue.go @@ -124,15 +124,21 @@ func (c *cacheProxyDataSource[ReqType]) Notify( func dispatchCacheUpdate(ch chan<- proxycfg.UpdateEvent) cache.Callback { return func(ctx context.Context, e cache.UpdateEvent) { - u := proxycfg.UpdateEvent{ - CorrelationID: e.CorrelationID, - Result: e.Result, - Err: e.Err, - } - select { - case ch <- u: + case ch <- newUpdateEvent(e.CorrelationID, e.Result, e.Err): case <-ctx.Done(): } } } + +func newUpdateEvent(correlationID string, result any, err error) proxycfg.UpdateEvent { + // This roughly matches the logic in agent/submatview.LocalMaterializer.isTerminalError. + if acl.IsErrNotFound(err) { + err = proxycfg.TerminalError(err) + } + return proxycfg.UpdateEvent{ + CorrelationID: correlationID, + Result: result, + Err: err, + } +} diff --git a/agent/proxycfg-glue/intention_upstreams.go b/agent/proxycfg-glue/intention_upstreams.go index 186d91b357..a694d033b4 100644 --- a/agent/proxycfg-glue/intention_upstreams.go +++ b/agent/proxycfg-glue/intention_upstreams.go @@ -54,13 +54,8 @@ func (s serverIntentionUpstreams) Notify(ctx context.Context, req *structs.Servi func dispatchBlockingQueryUpdate[ResultType any](ch chan<- proxycfg.UpdateEvent) func(context.Context, string, ResultType, error) { return func(ctx context.Context, correlationID string, result ResultType, err error) { - event := proxycfg.UpdateEvent{ - CorrelationID: correlationID, - Result: result, - Err: err, - } select { - case ch <- event: + case ch <- newUpdateEvent(correlationID, result, err): case <-ctx.Done(): } } diff --git a/agent/proxycfg-glue/intentions.go b/agent/proxycfg-glue/intentions.go index e3474b1f80..69652d922d 100644 --- a/agent/proxycfg-glue/intentions.go +++ b/agent/proxycfg-glue/intentions.go @@ -36,14 +36,11 @@ func (c cacheIntentions) Notify(ctx context.Context, req *structs.ServiceSpecifi }, }, }, + QueryOptions: structs.QueryOptions{Token: req.QueryOptions.Token}, } return c.c.NotifyCallback(ctx, cachetype.IntentionMatchName, query, correlationID, func(ctx context.Context, event cache.UpdateEvent) { - e := proxycfg.UpdateEvent{ - CorrelationID: correlationID, - Err: event.Err, - } - - if e.Err == nil { + var result any + if event.Err == nil { rsp, ok := event.Result.(*structs.IndexedIntentionMatches) if !ok { return @@ -53,11 +50,11 @@ func (c cacheIntentions) Notify(ctx context.Context, req *structs.ServiceSpecifi if len(rsp.Matches) != 0 { matches = rsp.Matches[0] } - e.Result = matches + result = matches } select { - case ch <- e: + case ch <- newUpdateEvent(correlationID, result, event.Err): case <-ctx.Done(): } }) @@ -109,10 +106,7 @@ func (s *serverIntentions) Notify(ctx context.Context, req *structs.ServiceSpeci sort.Sort(structs.IntentionPrecedenceSorter(intentions)) - return proxycfg.UpdateEvent{ - CorrelationID: correlationID, - Result: intentions, - }, true + return newUpdateEvent(correlationID, intentions, nil), true } for subjectIdx, subject := range subjects { diff --git a/agent/proxycfg-sources/catalog/config_source.go b/agent/proxycfg-sources/catalog/config_source.go index 1ee3f7f909..56f222fcda 100644 --- a/agent/proxycfg-sources/catalog/config_source.go +++ b/agent/proxycfg-sources/catalog/config_source.go @@ -125,7 +125,7 @@ func (m *ConfigSource) startSync(closeCh <-chan chan struct{}, proxyID proxycfg. case ns == nil: m.Manager.Deregister(proxyID, source) logger.Trace("service does not exist in catalog, de-registering it with proxycfg manager") - return nil, err + return ws, nil case !ns.Kind.IsProxy(): err := errors.New("service must be a sidecar proxy or gateway") logger.Error(err.Error()) diff --git a/agent/proxycfg/data_sources.go b/agent/proxycfg/data_sources.go index bda0226ffb..3649bed2d3 100644 --- a/agent/proxycfg/data_sources.go +++ b/agent/proxycfg/data_sources.go @@ -2,6 +2,7 @@ package proxycfg import ( "context" + "errors" cachetype "github.com/hashicorp/consul/agent/cache-types" "github.com/hashicorp/consul/agent/structs" @@ -15,6 +16,28 @@ type UpdateEvent struct { Err error } +// TerminalError wraps the given error to indicate that the data source is in +// an irrecoverably broken state (e.g. because the given ACL token has been +// deleted). +// +// Setting UpdateEvent.Err to a TerminalError causes all watches to be canceled +// which, in turn, terminates the xDS streams. +func TerminalError(err error) error { + return terminalError{err} +} + +// IsTerminalError returns whether the given error indicates that the data +// source is in an irrecoverably broken state so watches should be torn down +// and retried at a higher level. +func IsTerminalError(err error) bool { + return errors.As(err, &terminalError{}) +} + +type terminalError struct{ err error } + +func (e terminalError) Error() string { return e.err.Error() } +func (e terminalError) Unwrap() error { return e.err } + // DataSources contains the dependencies used to consume data used to configure // proxies. type DataSources struct { diff --git a/agent/proxycfg/manager.go b/agent/proxycfg/manager.go index 3de11b3f8a..efdfe4b724 100644 --- a/agent/proxycfg/manager.go +++ b/agent/proxycfg/manager.go @@ -127,7 +127,7 @@ func (m *Manager) Register(id ProxyID, ns *structs.NodeService, source ProxySour } // We are updating the proxy, close its old state - state.Close() + state.Close(false) } // TODO: move to a function that translates ManagerConfig->stateConfig @@ -148,14 +148,13 @@ func (m *Manager) Register(id ProxyID, ns *structs.NodeService, source ProxySour return err } - ch, err := state.Watch() - if err != nil { + if _, err = state.Watch(); err != nil { return err } m.proxies[id] = state // Start a goroutine that will wait for changes and broadcast them to watchers. - go m.notifyBroadcast(ch) + go m.notifyBroadcast(id, state) return nil } @@ -175,8 +174,8 @@ func (m *Manager) Deregister(id ProxyID, source ProxySource) { } // Closing state will let the goroutine we started in Register finish since - // watch chan is closed. - state.Close() + // watch chan is closed + state.Close(false) delete(m.proxies, id) // We intentionally leave potential watchers hanging here - there is no new @@ -186,11 +185,17 @@ func (m *Manager) Deregister(id ProxyID, source ProxySource) { // cleaned up naturally. } -func (m *Manager) notifyBroadcast(ch <-chan ConfigSnapshot) { - // Run until ch is closed - for snap := range ch { +func (m *Manager) notifyBroadcast(proxyID ProxyID, state *state) { + // Run until ch is closed (by a defer in state.run). + for snap := range state.snapCh { m.notify(&snap) } + + // If state.run exited because of an irrecoverable error, close all of the + // watchers so that the consumers reconnect/retry at a higher level. + if state.failed() { + m.closeAllWatchers(proxyID) + } } func (m *Manager) notify(snap *ConfigSnapshot) { @@ -281,6 +286,20 @@ func (m *Manager) Watch(id ProxyID) (<-chan *ConfigSnapshot, CancelFunc) { } } +func (m *Manager) closeAllWatchers(proxyID ProxyID) { + m.mu.Lock() + defer m.mu.Unlock() + + watchers, ok := m.watchers[proxyID] + if !ok { + return + } + + for watchID := range watchers { + m.closeWatchLocked(proxyID, watchID) + } +} + // closeWatchLocked cleans up state related to a single watcher. It assumes the // lock is held. func (m *Manager) closeWatchLocked(proxyID ProxyID, watchID uint64) { @@ -309,7 +328,7 @@ func (m *Manager) Close() error { // Then close all states for proxyID, state := range m.proxies { - state.Close() + state.Close(false) delete(m.proxies, proxyID) } return nil diff --git a/agent/proxycfg/naming.go b/agent/proxycfg/naming.go index 3bb0854b04..08ff216edf 100644 --- a/agent/proxycfg/naming.go +++ b/agent/proxycfg/naming.go @@ -63,22 +63,29 @@ func NewUpstreamIDFromServiceID(sid structs.ServiceID) UpstreamID { return id } -// TODO(peering): confirm we don't need peername here func NewUpstreamIDFromTargetID(tid string) UpstreamID { - // Drop the leading subset if one is present in the target ID. - separators := strings.Count(tid, ".") - if separators > 3 { - prefix := tid[:strings.Index(tid, ".")+1] - tid = strings.TrimPrefix(tid, prefix) + var id UpstreamID + split := strings.Split(tid, ".") + + switch { + case split[len(split)-2] == "external": + id = UpstreamID{ + Name: split[0], + EnterpriseMeta: acl.NewEnterpriseMetaWithPartition(split[2], split[1]), + Peer: split[4], + } + case len(split) == 5: + // Drop the leading subset if one is present in the target ID. + split = split[1:] + fallthrough + default: + id = UpstreamID{ + Name: split[0], + EnterpriseMeta: acl.NewEnterpriseMetaWithPartition(split[2], split[1]), + Datacenter: split[3], + } } - split := strings.SplitN(tid, ".", 4) - - id := UpstreamID{ - Name: split[0], - EnterpriseMeta: acl.NewEnterpriseMetaWithPartition(split[2], split[1]), - Datacenter: split[3], - } id.normalize() return id } diff --git a/agent/proxycfg/naming_test.go b/agent/proxycfg/naming_test.go index 23ff241658..2c4f5173a8 100644 --- a/agent/proxycfg/naming_test.go +++ b/agent/proxycfg/naming_test.go @@ -35,6 +35,13 @@ func TestUpstreamIDFromTargetID(t *testing.T) { Datacenter: "dc2", }, }, + "peered": { + tid: "foo.default.default.external.cluster-01", + expect: UpstreamID{ + Name: "foo", + Peer: "cluster-01", + }, + }, } for name, tc := range cases { diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index 13b22c4fd2..34d3364356 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "reflect" + "sync/atomic" "time" "github.com/hashicorp/go-hclog" @@ -70,11 +71,21 @@ type state struct { // in Watch. cancel func() + // failedFlag is (atomically) set to 1 (by Close) when run exits because a data + // source is in an irrecoverable state. It can be read with failed. + failedFlag int32 + ch chan UpdateEvent snapCh chan ConfigSnapshot reqCh chan chan *ConfigSnapshot } +// failed returns whether run exited because a data source is in an +// irrecoverable state. +func (s *state) failed() bool { + return atomic.LoadInt32(&s.failedFlag) == 1 +} + type DNSConfig struct { Domain string AltDomain string @@ -250,10 +261,13 @@ func (s *state) Watch() (<-chan ConfigSnapshot, error) { } // Close discards the state and stops any long-running watches. -func (s *state) Close() error { +func (s *state) Close(failed bool) error { if s.cancel != nil { s.cancel() } + if failed { + atomic.StoreInt32(&s.failedFlag, 1) + } return nil } @@ -300,7 +314,13 @@ func (s *state) run(ctx context.Context, snap *ConfigSnapshot) { case <-ctx.Done(): return case u := <-s.ch: - s.logger.Trace("A blocking query returned; handling snapshot update", "correlationID", u.CorrelationID) + s.logger.Trace("Data source returned; handling snapshot update", "correlationID", u.CorrelationID) + + if IsTerminalError(u.Err) { + s.logger.Error("Data source in an irrecoverable state; exiting", "error", u.Err, "correlationID", u.CorrelationID) + s.Close(true) + return + } if err := s.handler.handleUpdate(ctx, u, snap); err != nil { s.logger.Error("Failed to handle update from watch", diff --git a/agent/proxycfg/testing_tproxy.go b/agent/proxycfg/testing_tproxy.go index 45d0236a0f..2ee3f88256 100644 --- a/agent/proxycfg/testing_tproxy.go +++ b/agent/proxycfg/testing_tproxy.go @@ -555,6 +555,33 @@ func TestConfigSnapshotTransparentProxyDestination(t testing.T) *ConfigSnapshot } ) + serviceNodes := structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Node: "node1", + Address: "172.168.0.1", + Datacenter: "dc1", + }, + Service: &structs.NodeService{ + ID: "tgtw1", + Address: "172.168.0.1", + Port: 8443, + Kind: structs.ServiceKindTerminatingGateway, + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv4: {Address: "172.168.0.1", Port: 8443}, + structs.TaggedAddressVirtualIP: {Address: "240.0.0.1"}, + }, + }, + Checks: []*structs.HealthCheck{ + { + Node: "node1", + ServiceName: "tgtw", + Name: "force", + Status: api.HealthPassing, + }, + }, + }, + } return TestConfigSnapshot(t, func(ns *structs.NodeService) { ns.Proxy.Mode = structs.ProxyModeTransparent }, []UpdateEvent{ @@ -592,65 +619,119 @@ func TestConfigSnapshotTransparentProxyDestination(t testing.T) *ConfigSnapshot { CorrelationID: DestinationGatewayID + googleUID.String(), Result: &structs.IndexedCheckServiceNodes{ - Nodes: structs.CheckServiceNodes{ - { - Node: &structs.Node{ - Node: "node1", - Address: "172.168.0.1", - Datacenter: "dc1", - }, - Service: &structs.NodeService{ - ID: "tgtw1", - Address: "172.168.0.1", - Port: 8443, - Kind: structs.ServiceKindTerminatingGateway, - TaggedAddresses: map[string]structs.ServiceAddress{ - structs.TaggedAddressLANIPv4: {Address: "172.168.0.1", Port: 8443}, - structs.TaggedAddressVirtualIP: {Address: "240.0.0.1"}, - }, - }, - Checks: []*structs.HealthCheck{ - { - Node: "node1", - ServiceName: "tgtw", - Name: "force", - Status: api.HealthPassing, - }, - }, - }, - }, + Nodes: serviceNodes, }, }, { CorrelationID: DestinationGatewayID + kafkaUID.String(), Result: &structs.IndexedCheckServiceNodes{ - Nodes: structs.CheckServiceNodes{ - { - Node: &structs.Node{ - Node: "node1", - Address: "172.168.0.1", - Datacenter: "dc1", - }, - Service: &structs.NodeService{ - ID: "tgtw1", - Address: "172.168.0.1", - Port: 8443, - Kind: structs.ServiceKindTerminatingGateway, - TaggedAddresses: map[string]structs.ServiceAddress{ - structs.TaggedAddressLANIPv4: {Address: "172.168.0.1", Port: 8443}, - structs.TaggedAddressVirtualIP: {Address: "240.0.0.1"}, - }, - }, - Checks: []*structs.HealthCheck{ - { - Node: "node1", - ServiceName: "tgtw", - Name: "force", - Status: api.HealthPassing, - }, - }, - }, - }, + Nodes: serviceNodes, + }, + }, + }) +} + +func TestConfigSnapshotTransparentProxyDestinationHTTP(t testing.T) *ConfigSnapshot { + // DiscoveryChain without an UpstreamConfig should yield a + // filter chain when in transparent proxy mode + var ( + google = structs.NewServiceName("google", nil) + googleUID = NewUpstreamIDFromServiceName(google) + googleCE = structs.ServiceConfigEntry{Name: "google", Destination: &structs.DestinationConfig{Addresses: []string{"www.google.com"}, Port: 443}, Protocol: "http"} + + kafka = structs.NewServiceName("kafka", nil) + kafkaUID = NewUpstreamIDFromServiceName(kafka) + kafkaCE = structs.ServiceConfigEntry{Name: "kafka", Destination: &structs.DestinationConfig{Addresses: []string{"192.168.2.1"}, Port: 9093}, Protocol: "http"} + + kafka2 = structs.NewServiceName("kafka2", nil) + kafka2UID = NewUpstreamIDFromServiceName(kafka2) + kafka2CE = structs.ServiceConfigEntry{Name: "kafka2", Destination: &structs.DestinationConfig{Addresses: []string{"192.168.2.2", "192.168.2.3"}, Port: 9093}, Protocol: "http"} + ) + + serviceNodes := structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Node: "node1", + Address: "172.168.0.1", + Datacenter: "dc1", + }, + Service: &structs.NodeService{ + ID: "tgtw1", + Address: "172.168.0.1", + Port: 8443, + Kind: structs.ServiceKindTerminatingGateway, + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv4: {Address: "172.168.0.1", Port: 8443}, + structs.TaggedAddressVirtualIP: {Address: "240.0.0.1"}, + }, + }, + Checks: []*structs.HealthCheck{ + { + Node: "node1", + ServiceName: "tgtw", + Name: "force", + Status: api.HealthPassing, + }, + }, + }, + } + return TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Mode = structs.ProxyModeTransparent + }, []UpdateEvent{ + { + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: &structs.MeshConfigEntry{ + TransparentProxy: structs.TransparentProxyMeshConfig{ + MeshDestinationsOnly: true, + }, + }, + }, + }, + { + CorrelationID: intentionUpstreamsDestinationID, + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{ + google, + kafka, + kafka2, + }, + }, + }, + { + CorrelationID: DestinationConfigEntryID + googleUID.String(), + Result: &structs.ConfigEntryResponse{ + Entry: &googleCE, + }, + }, + { + CorrelationID: DestinationConfigEntryID + kafkaUID.String(), + Result: &structs.ConfigEntryResponse{ + Entry: &kafkaCE, + }, + }, + { + CorrelationID: DestinationConfigEntryID + kafka2UID.String(), + Result: &structs.ConfigEntryResponse{ + Entry: &kafka2CE, + }, + }, + { + CorrelationID: DestinationGatewayID + googleUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: serviceNodes, + }, + }, + { + CorrelationID: DestinationGatewayID + kafkaUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: serviceNodes, + }, + }, + { + CorrelationID: DestinationGatewayID + kafka2UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: serviceNodes, }, }, }) diff --git a/agent/retry_join.go b/agent/retry_join.go index 8cfb00e22e..b807697e84 100644 --- a/agent/retry_join.go +++ b/agent/retry_join.go @@ -226,7 +226,8 @@ func (r *retryJoiner) retryJoin() error { for { addrs := retryJoinAddrs(disco, r.variant, r.cluster, r.addrs, r.logger) if len(addrs) > 0 { - n, err := r.join(addrs) + n := 0 + n, err = r.join(addrs) if err == nil { if r.variant == retryJoinMeshGatewayVariant { r.logger.Info("Refreshing mesh gateways completed") diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index 79ae815e1a..20bbafc1cf 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -8,7 +8,6 @@ import ( "time" "github.com/armon/go-metrics" - "github.com/hashicorp/consul/proto/pbpeerstream" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" "github.com/hashicorp/go-multierror" @@ -27,6 +26,7 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto/pbpeering" + "github.com/hashicorp/consul/proto/pbpeerstream" ) var ( @@ -260,10 +260,12 @@ func (s *Server) GenerateToken( writeReq := &pbpeering.PeeringWriteRequest{ Peering: peering, - Secret: &pbpeering.PeeringSecrets{ + SecretsRequest: &pbpeering.SecretsWriteRequest{ PeerID: peering.ID, - Establishment: &pbpeering.PeeringSecrets_Establishment{ - SecretID: secretID, + Request: &pbpeering.SecretsWriteRequest_GenerateToken{ + GenerateToken: &pbpeering.SecretsWriteRequest_GenerateTokenRequest{ + EstablishmentSecret: secretID, + }, }, }, } @@ -377,6 +379,7 @@ func (s *Server) Establish( } var id string + serverAddrs := tok.ServerAddresses if existing == nil { id, err = lib.GenerateUUID(s.Backend.CheckPeeringUUID) if err != nil { @@ -384,6 +387,11 @@ func (s *Server) Establish( } } else { id = existing.ID + // If there is a connected stream, assume that the existing ServerAddresses + // are up to date and do not try to overwrite them with the token's addresses. + if status, ok := s.Tracker.StreamStatus(id); ok && status.Connected { + serverAddrs = existing.PeerServerAddresses + } } // validate that this peer name is not being used as an acceptor already @@ -395,7 +403,7 @@ func (s *Server) Establish( ID: id, Name: req.PeerName, PeerCAPems: tok.CA, - PeerServerAddresses: tok.ServerAddresses, + PeerServerAddresses: serverAddrs, PeerServerName: tok.ServerName, PeerID: tok.PeerID, Meta: req.Meta, @@ -416,9 +424,9 @@ func (s *Server) Establish( } var exchangeResp *pbpeerstream.ExchangeSecretResponse - // Loop through the token's addresses once, attempting to fetch the long-lived stream secret. + // Loop through the known server addresses once, attempting to fetch the long-lived stream secret. var dialErrors error - for _, addr := range peering.PeerServerAddresses { + for _, addr := range serverAddrs { exchangeResp, err = exchangeSecret(ctx, addr, tlsOption, &exchangeReq) if err != nil { dialErrors = multierror.Append(dialErrors, fmt.Errorf("failed to exchange peering secret with %q: %w", addr, err)) @@ -431,18 +439,20 @@ func (s *Server) Establish( return nil, dialErrors } - // As soon as a peering is written with a list of ServerAddresses that is - // non-empty, the leader routine will see the peering and attempt to - // establish a connection with the remote peer. + // As soon as a peering is written with a non-empty list of ServerAddresses + // and an active stream secret, a leader routine will see the peering and + // attempt to establish a peering stream with the remote peer. // // This peer now has a record of both the LocalPeerID(ID) and // RemotePeerID(PeerID) but at this point the other peer does not. writeReq := &pbpeering.PeeringWriteRequest{ Peering: peering, - Secret: &pbpeering.PeeringSecrets{ + SecretsRequest: &pbpeering.SecretsWriteRequest{ PeerID: peering.ID, - Stream: &pbpeering.PeeringSecrets_Stream{ - ActiveSecretID: exchangeResp.StreamSecret, + Request: &pbpeering.SecretsWriteRequest_Establish{ + Establish: &pbpeering.SecretsWriteRequest_EstablishRequest{ + ActiveStreamSecret: exchangeResp.StreamSecret, + }, }, }, } @@ -716,7 +726,7 @@ func (s *Server) PeeringDelete(ctx context.Context, req *pbpeering.PeeringDelete return nil, err } - if existing == nil || !existing.IsActive() { + if !existing.IsActive() { // Return early when the Peering doesn't exist or is already marked for deletion. // We don't return nil because the pb will fail to marshal. return &pbpeering.PeeringDeleteResponse{}, nil @@ -729,10 +739,11 @@ func (s *Server) PeeringDelete(ctx context.Context, req *pbpeering.PeeringDelete // We only need to include the name and partition for the peering to be identified. // All other data associated with the peering can be discarded because once marked // for deletion the peering is effectively gone. - ID: existing.ID, - Name: req.Name, - State: pbpeering.PeeringState_DELETING, - DeletedAt: structs.TimeToProto(time.Now().UTC()), + ID: existing.ID, + Name: req.Name, + State: pbpeering.PeeringState_DELETING, + PeerServerAddresses: existing.PeerServerAddresses, + DeletedAt: structs.TimeToProto(time.Now().UTC()), // PartitionOrEmpty is used to avoid writing "default" in OSS. Partition: entMeta.PartitionOrEmpty(), diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index a4acd945e0..54770d6a61 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -1283,6 +1283,7 @@ func newTestServer(t *testing.T, cb func(conf *consul.Config)) testingServer { ports := freeport.GetN(t, 4) // {rpc, serf_lan, serf_wan, grpc} + conf.PeeringEnabled = true conf.Bootstrap = true conf.Datacenter = "dc1" conf.DataDir = dir diff --git a/agent/sidecar_service.go b/agent/sidecar_service.go index 673a02252e..e0cb24a0ea 100644 --- a/agent/sidecar_service.go +++ b/agent/sidecar_service.go @@ -114,9 +114,35 @@ func (a *Agent) sidecarServiceFromNodeService(ns *structs.NodeService, token str } } + if sidecar.Port < 1 { + port, err := a.sidecarPortFromServiceID(sidecar.CompoundServiceID()) + if err != nil { + return nil, nil, "", err + } + sidecar.Port = port + } + + // Setup checks + checks, err := ns.Connect.SidecarService.CheckTypes() + if err != nil { + return nil, nil, "", err + } + // Setup default check if none given + if len(checks) < 1 { + checks = sidecarDefaultChecks(ns.ID, sidecar.Proxy.LocalServiceAddress, sidecar.Port) + } + + return sidecar, checks, token, nil +} + +// sidecarPortFromServiceID is used to allocate a unique port for a sidecar proxy. +// This is called immediately before registration to avoid value collisions. This function assumes the state lock is already held. +func (a *Agent) sidecarPortFromServiceID(sidecarCompoundServiceID structs.ServiceID) (int, error) { + sidecarPort := 0 + // Allocate port if needed (min and max inclusive). rangeLen := a.config.ConnectSidecarMaxPort - a.config.ConnectSidecarMinPort + 1 - if sidecar.Port < 1 && a.config.ConnectSidecarMinPort > 0 && rangeLen > 0 { + if sidecarPort < 1 && a.config.ConnectSidecarMinPort > 0 && rangeLen > 0 { // This did pick at random which was simpler but consul reload would assign // new ports to all the sidecars since it unloads all state and // re-populates. It also made this more difficult to test (have to pin the @@ -130,11 +156,11 @@ func (a *Agent) sidecarServiceFromNodeService(ns *structs.NodeService, token str // Check if other port is in auto-assign range if otherNS.Port >= a.config.ConnectSidecarMinPort && otherNS.Port <= a.config.ConnectSidecarMaxPort { - if otherNS.CompoundServiceID() == sidecar.CompoundServiceID() { + if otherNS.CompoundServiceID() == sidecarCompoundServiceID { // This sidecar is already registered with an auto-port and is just // being updated so pick the same port as before rather than allocate // a new one. - sidecar.Port = otherNS.Port + sidecarPort = otherNS.Port break } usedPorts[otherNS.Port] = struct{}{} @@ -147,54 +173,48 @@ func (a *Agent) sidecarServiceFromNodeService(ns *structs.NodeService, token str // Check we still need to assign a port and didn't find we already had one // allocated. - if sidecar.Port < 1 { + if sidecarPort < 1 { // Iterate until we find lowest unused port for p := a.config.ConnectSidecarMinPort; p <= a.config.ConnectSidecarMaxPort; p++ { _, used := usedPorts[p] if !used { - sidecar.Port = p + sidecarPort = p break } } } } // If no ports left (or auto ports disabled) fail - if sidecar.Port < 1 { + if sidecarPort < 1 { // If ports are set to zero explicitly, config builder switches them to // `-1`. In this case don't show the actual values since we don't know what // was actually in config (zero or negative) and it might be confusing, we // just know they explicitly disabled auto assignment. if a.config.ConnectSidecarMinPort < 1 || a.config.ConnectSidecarMaxPort < 1 { - return nil, nil, "", fmt.Errorf("no port provided for sidecar_service " + + return 0, fmt.Errorf("no port provided for sidecar_service " + "and auto-assignment disabled in config") } - return nil, nil, "", fmt.Errorf("no port provided for sidecar_service and none "+ + return 0, fmt.Errorf("no port provided for sidecar_service and none "+ "left in the configured range [%d, %d]", a.config.ConnectSidecarMinPort, a.config.ConnectSidecarMaxPort) } - // Setup checks - checks, err := ns.Connect.SidecarService.CheckTypes() - if err != nil { - return nil, nil, "", err - } - - // Setup default check if none given - if len(checks) < 1 { - checks = []*structs.CheckType{ - { - Name: "Connect Sidecar Listening", - // Default to localhost rather than agent/service public IP. The checks - // can always be overridden if a non-loopback IP is needed. - TCP: ipaddr.FormatAddressPort(sidecar.Proxy.LocalServiceAddress, sidecar.Port), - Interval: 10 * time.Second, - }, - { - Name: "Connect Sidecar Aliasing " + ns.ID, - AliasService: ns.ID, - }, - } - } - - return sidecar, checks, token, nil + return sidecarPort, nil +} + +func sidecarDefaultChecks(serviceID string, localServiceAddress string, port int) []*structs.CheckType { + // Setup default check if none given + return []*structs.CheckType{ + { + Name: "Connect Sidecar Listening", + // Default to localhost rather than agent/service public IP. The checks + // can always be overridden if a non-loopback IP is needed. + TCP: ipaddr.FormatAddressPort(localServiceAddress, port), + Interval: 10 * time.Second, + }, + { + Name: "Connect Sidecar Aliasing " + serviceID, + AliasService: serviceID, + }, + } } diff --git a/agent/sidecar_service_test.go b/agent/sidecar_service_test.go index a2ffe9af49..f095670ff5 100644 --- a/agent/sidecar_service_test.go +++ b/agent/sidecar_service_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/hashicorp/consul/acl" + "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent/structs" @@ -16,16 +18,13 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { } tests := []struct { - name string - maxPort int - preRegister *structs.ServiceDefinition - sd *structs.ServiceDefinition - token string - autoPortsDisabled bool - wantNS *structs.NodeService - wantChecks []*structs.CheckType - wantToken string - wantErr string + name string + sd *structs.ServiceDefinition + token string + wantNS *structs.NodeService + wantChecks []*structs.CheckType + wantToken string + wantErr string }{ { name: "no sidecar", @@ -141,42 +140,6 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { }, wantToken: "custom-token", }, - { - name: "no auto ports available", - // register another sidecar consuming our 1 and only allocated auto port. - preRegister: &structs.ServiceDefinition{ - Kind: structs.ServiceKindConnectProxy, - Name: "api-proxy-sidecar", - Port: 2222, // Consume the one available auto-port - Proxy: &structs.ConnectProxyConfig{ - DestinationServiceName: "api", - }, - }, - sd: &structs.ServiceDefinition{ - ID: "web1", - Name: "web", - Port: 1111, - Connect: &structs.ServiceConnect{ - SidecarService: &structs.ServiceDefinition{}, - }, - }, - token: "foo", - wantErr: "none left in the configured range [2222, 2222]", - }, - { - name: "auto ports disabled", - autoPortsDisabled: true, - sd: &structs.ServiceDefinition{ - ID: "web1", - Name: "web", - Port: 1111, - Connect: &structs.ServiceConnect{ - SidecarService: &structs.ServiceDefinition{}, - }, - }, - token: "foo", - wantErr: "auto-assignment disabled in config", - }, { name: "inherit tags and meta", sd: &structs.ServiceDefinition{ @@ -252,6 +215,58 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { token: "foo", wantErr: "reserved for internal use", }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hcl := ` + ports { + sidecar_min_port = 2222 + sidecar_max_port = 2222 + } + ` + a := StartTestAgent(t, TestAgent{Name: "jones", HCL: hcl}) + defer a.Shutdown() + + ns := tt.sd.NodeService() + err := ns.Validate() + require.NoError(t, err, "Invalid test case - NodeService must validate") + + gotNS, gotChecks, gotToken, err := a.sidecarServiceFromNodeService(ns, tt.token) + if tt.wantErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + require.Equal(t, tt.wantNS, gotNS) + require.Equal(t, tt.wantChecks, gotChecks) + require.Equal(t, tt.wantToken, gotToken) + }) + } +} + +func TestAgent_SidecarPortFromServiceID(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + tests := []struct { + name string + autoPortsDisabled bool + enterpriseMeta acl.EnterpriseMeta + maxPort int + port int + preRegister *structs.ServiceDefinition + serviceID string + wantPort int + wantErr string + }{ + { + name: "use auto ports", + serviceID: "web1", + wantPort: 2222, + }, { name: "re-registering same sidecar with no port should pick same one", // Allow multiple ports to be sure we get the right one @@ -269,42 +284,27 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { LocalServicePort: 1111, }, }, - // Register same again but with different service port - sd: &structs.ServiceDefinition{ - ID: "web1", - Name: "web", - Port: 1112, - Connect: &structs.ServiceConnect{ - SidecarService: &structs.ServiceDefinition{}, + // Register same again + serviceID: "web1-sidecar-proxy", + wantPort: 2222, // Should claim the same port as before + }, + { + name: "all auto ports already taken", + // register another sidecar consuming our 1 and only allocated auto port. + preRegister: &structs.ServiceDefinition{ + Kind: structs.ServiceKindConnectProxy, + Name: "api-proxy-sidecar", + Port: 2222, // Consume the one available auto-port + Proxy: &structs.ConnectProxyConfig{ + DestinationServiceName: "api", }, }, - token: "foo", - wantNS: &structs.NodeService{ - EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), - Kind: structs.ServiceKindConnectProxy, - ID: "web1-sidecar-proxy", - Service: "web-sidecar-proxy", - Port: 2222, // Should claim the same port as before - LocallyRegisteredAsSidecar: true, - Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "web", - DestinationServiceID: "web1", - LocalServiceAddress: "127.0.0.1", - LocalServicePort: 1112, - }, - }, - wantChecks: []*structs.CheckType{ - { - Name: "Connect Sidecar Listening", - TCP: "127.0.0.1:2222", - Interval: 10 * time.Second, - }, - { - Name: "Connect Sidecar Aliasing web1", - AliasService: "web1", - }, - }, - wantToken: "foo", + wantErr: "none left in the configured range [2222, 2222]", + }, + { + name: "auto ports disabled", + autoPortsDisabled: true, + wantErr: "auto-assignment disabled in config", }, } for _, tt := range tests { @@ -329,7 +329,6 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { } ` } - a := StartTestAgent(t, TestAgent{Name: "jones", HCL: hcl}) defer a.Shutdown() @@ -338,11 +337,8 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { require.NoError(t, err) } - ns := tt.sd.NodeService() - err := ns.Validate() - require.NoError(t, err, "Invalid test case - NodeService must validate") + gotPort, err := a.sidecarPortFromServiceID(structs.ServiceID{ID: tt.serviceID, EnterpriseMeta: tt.enterpriseMeta}) - gotNS, gotChecks, gotToken, err := a.sidecarServiceFromNodeService(ns, tt.token) if tt.wantErr != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.wantErr) @@ -350,9 +346,7 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { } require.NoError(t, err) - require.Equal(t, tt.wantNS, gotNS) - require.Equal(t, tt.wantChecks, gotChecks) - require.Equal(t, tt.wantToken, gotToken) + require.Equal(t, tt.wantPort, gotPort) }) } } diff --git a/agent/structs/config_entry_discoverychain.go b/agent/structs/config_entry_discoverychain.go index aaac8652a7..0ea2609551 100644 --- a/agent/structs/config_entry_discoverychain.go +++ b/agent/structs/config_entry_discoverychain.go @@ -954,6 +954,10 @@ func (e *ServiceResolverConfigEntry) Validate() error { r := e.Redirect + if err := r.ValidateEnterprise(); err != nil { + return fmt.Errorf("Redirect: %s", err.Error()) + } + if len(e.Failover) > 0 { return fmt.Errorf("Redirect and Failover cannot both be set") } @@ -988,18 +992,59 @@ func (e *ServiceResolverConfigEntry) Validate() error { return fmt.Errorf("Cross-datacenter failover is only supported in the default partition") } - if subset != "*" && !isSubset(subset) { - return fmt.Errorf("Bad Failover[%q]: not a valid subset", subset) + errorPrefix := fmt.Sprintf("Bad Failover[%q]: ", subset) + + if err := f.ValidateEnterprise(); err != nil { + return fmt.Errorf(errorPrefix + err.Error()) } - if f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && len(f.Datacenters) == 0 { - return fmt.Errorf("Bad Failover[%q] one of Service, ServiceSubset, Namespace, or Datacenters is required", subset) + if subset != "*" && !isSubset(subset) { + return fmt.Errorf(errorPrefix + "not a valid subset subset") + } + + if f.isEmpty() { + return fmt.Errorf(errorPrefix + "one of Service, ServiceSubset, Namespace, Targets, or Datacenters is required") } if f.ServiceSubset != "" { if f.Service == "" || f.Service == e.Name { if !isSubset(f.ServiceSubset) { - return fmt.Errorf("Bad Failover[%q].ServiceSubset %q is not a valid subset of %q", subset, f.ServiceSubset, f.Service) + return fmt.Errorf("%sServiceSubset %q is not a valid subset of %q", errorPrefix, f.ServiceSubset, f.Service) + } + } + } + + if len(f.Datacenters) != 0 && len(f.Targets) != 0 { + return fmt.Errorf("Bad Failover[%q]: Targets cannot be set with Datacenters", subset) + } + + if f.ServiceSubset != "" && len(f.Targets) != 0 { + return fmt.Errorf("Bad Failover[%q]: Targets cannot be set with ServiceSubset", subset) + } + + if f.Service != "" && len(f.Targets) != 0 { + return fmt.Errorf("Bad Failover[%q]: Targets cannot be set with Service", subset) + } + + for i, target := range f.Targets { + errorPrefix := fmt.Sprintf("Bad Failover[%q].Targets[%d]: ", subset, i) + + if err := target.ValidateEnterprise(); err != nil { + return fmt.Errorf(errorPrefix + err.Error()) + } + + switch { + case target.Peer != "" && target.ServiceSubset != "": + return fmt.Errorf(errorPrefix + "Peer cannot be set with ServiceSubset") + case target.Peer != "" && target.Partition != "": + return fmt.Errorf(errorPrefix + "Partition cannot be set with Peer") + case target.Peer != "" && target.Datacenter != "": + return fmt.Errorf(errorPrefix + "Peer cannot be set with Datacenter") + case target.Partition != "" && target.Datacenter != "": + return fmt.Errorf(errorPrefix + "Partition cannot be set with Datacenter") + case target.ServiceSubset != "" && (target.Service == "" || target.Service == e.Name): + if !isSubset(target.ServiceSubset) { + return fmt.Errorf("%sServiceSubset %q is not a valid subset of %q", errorPrefix, target.ServiceSubset, e.Name) } } } @@ -1107,9 +1152,24 @@ func (e *ServiceResolverConfigEntry) ListRelatedServices() []ServiceID { if len(e.Failover) > 0 { for _, failover := range e.Failover { - failoverID := NewServiceID(defaultIfEmpty(failover.Service, e.Name), failover.GetEnterpriseMeta(&e.EnterpriseMeta)) - if failoverID != svcID { - found[failoverID] = struct{}{} + if len(failover.Targets) == 0 { + failoverID := NewServiceID(defaultIfEmpty(failover.Service, e.Name), failover.GetEnterpriseMeta(&e.EnterpriseMeta)) + if failoverID != svcID { + found[failoverID] = struct{}{} + } + continue + } + + for _, target := range failover.Targets { + // We can't know about related services on cluster peers. + if target.Peer != "" { + continue + } + + failoverID := NewServiceID(defaultIfEmpty(target.Service, e.Name), target.GetEnterpriseMeta(failover.GetEnterpriseMeta(&e.EnterpriseMeta))) + if failoverID != svcID { + found[failoverID] = struct{}{} + } } } } @@ -1173,10 +1233,21 @@ type ServiceResolverRedirect struct { Datacenter string `json:",omitempty"` } +func (r *ServiceResolverRedirect) ToDiscoveryTargetOpts() DiscoveryTargetOpts { + return DiscoveryTargetOpts{ + Service: r.Service, + ServiceSubset: r.ServiceSubset, + Namespace: r.Namespace, + Partition: r.Partition, + Datacenter: r.Datacenter, + } +} + // There are some restrictions on what is allowed in here: // -// - Service, ServiceSubset, Namespace, and Datacenters cannot all be -// empty at once. +// - Service, ServiceSubset, Namespace, Datacenters, and Targets cannot all be +// empty at once. When Targets is defined, the other fields should not be +// populated. // type ServiceResolverFailover struct { // Service is the service to resolve instead of the default as the failover @@ -1205,6 +1276,56 @@ type ServiceResolverFailover struct { // // This is a DESTINATION during failover. Datacenters []string `json:",omitempty"` + + // Targets specifies a fixed list of failover targets to try. We never try a + // target multiple times, so those are subtracted from this list before + // proceeding. + // + // This is a DESTINATION during failover. + Targets []ServiceResolverFailoverTarget `json:",omitempty"` +} + +func (t *ServiceResolverFailover) ToDiscoveryTargetOpts() DiscoveryTargetOpts { + return DiscoveryTargetOpts{ + Service: t.Service, + ServiceSubset: t.ServiceSubset, + Namespace: t.Namespace, + } +} + +func (f *ServiceResolverFailover) isEmpty() bool { + return f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && len(f.Datacenters) == 0 && len(f.Targets) == 0 +} + +type ServiceResolverFailoverTarget struct { + // Service specifies the name of the service to try during failover. + Service string `json:",omitempty"` + + // ServiceSubset specifies the service subset to try during failover. + ServiceSubset string `json:",omitempty" alias:"service_subset"` + + // Partition specifies the partition to try during failover. + Partition string `json:",omitempty"` + + // Namespace specifies the namespace to try during failover. + Namespace string `json:",omitempty"` + + // Datacenter specifies the datacenter to try during failover. + Datacenter string `json:",omitempty"` + + // Peer specifies the name of the cluster peer to try during failover. + Peer string `json:",omitempty"` +} + +func (t *ServiceResolverFailoverTarget) ToDiscoveryTargetOpts() DiscoveryTargetOpts { + return DiscoveryTargetOpts{ + Service: t.Service, + ServiceSubset: t.ServiceSubset, + Namespace: t.Namespace, + Partition: t.Partition, + Datacenter: t.Datacenter, + Peer: t.Peer, + } } // LoadBalancer determines the load balancing policy and configuration for services diff --git a/agent/structs/config_entry_discoverychain_oss.go b/agent/structs/config_entry_discoverychain_oss.go index 0dcf1cc8fe..ef00e39922 100644 --- a/agent/structs/config_entry_discoverychain_oss.go +++ b/agent/structs/config_entry_discoverychain_oss.go @@ -4,6 +4,8 @@ package structs import ( + "fmt" + "github.com/hashicorp/consul/acl" ) @@ -25,12 +27,56 @@ func (redir *ServiceResolverRedirect) GetEnterpriseMeta(_ *acl.EnterpriseMeta) * return DefaultEnterpriseMetaInDefaultPartition() } +// ValidateEnterprise validates that enterprise fields are only set +// with enterprise binaries. +func (redir *ServiceResolverRedirect) ValidateEnterprise() error { + if redir.Partition != "" { + return fmt.Errorf("Setting Partition requires Consul Enterprise") + } + + if redir.Namespace != "" { + return fmt.Errorf("Setting Namespace requires Consul Enterprise") + } + + return nil +} + // GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from // fields in the ServiceResolverFailover func (failover *ServiceResolverFailover) GetEnterpriseMeta(_ *acl.EnterpriseMeta) *acl.EnterpriseMeta { return DefaultEnterpriseMetaInDefaultPartition() } +// ValidateEnterprise validates that enterprise fields are only set +// with enterprise binaries. +func (failover *ServiceResolverFailover) ValidateEnterprise() error { + if failover.Namespace != "" { + return fmt.Errorf("Setting Namespace requires Consul Enterprise") + } + + return nil +} + +// GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from +// fields in the ServiceResolverFailoverTarget +func (target *ServiceResolverFailoverTarget) GetEnterpriseMeta(_ *acl.EnterpriseMeta) *acl.EnterpriseMeta { + return DefaultEnterpriseMetaInDefaultPartition() +} + +// ValidateEnterprise validates that enterprise fields are only set +// with enterprise binaries. +func (redir *ServiceResolverFailoverTarget) ValidateEnterprise() error { + if redir.Partition != "" { + return fmt.Errorf("Setting Partition requires Consul Enterprise") + } + + if redir.Namespace != "" { + return fmt.Errorf("Setting Namespace requires Consul Enterprise") + } + + return nil +} + // GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from // fields in the DiscoveryChainRequest func (req *DiscoveryChainRequest) GetEnterpriseMeta() *acl.EnterpriseMeta { diff --git a/agent/structs/config_entry_discoverychain_oss_test.go b/agent/structs/config_entry_discoverychain_oss_test.go new file mode 100644 index 0000000000..81bf6541a1 --- /dev/null +++ b/agent/structs/config_entry_discoverychain_oss_test.go @@ -0,0 +1,131 @@ +//go:build !consulent +// +build !consulent + +package structs + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestServiceResolverConfigEntry_OSS(t *testing.T) { + type testcase struct { + name string + entry *ServiceResolverConfigEntry + normalizeErr string + validateErr string + // check is called between normalize and validate + check func(t *testing.T, entry *ServiceResolverConfigEntry) + } + + cases := []testcase{ + { + name: "failover with a namespace on OSS", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Service: "backup", + Namespace: "ns1", + }, + }, + }, + validateErr: `Bad Failover["*"]: Setting Namespace requires Consul Enterprise`, + }, + { + name: "failover Targets cannot set Namespace on OSS", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{{Namespace: "ns1"}}, + }, + }, + }, + validateErr: `Bad Failover["*"].Targets[0]: Setting Namespace requires Consul Enterprise`, + }, + { + name: "failover Targets cannot set Partition on OSS", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{{Partition: "ap1"}}, + }, + }, + }, + validateErr: `Bad Failover["*"].Targets[0]: Setting Partition requires Consul Enterprise`, + }, + { + name: "setting failover Namespace on OSS", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": {Namespace: "ns1"}, + }, + }, + validateErr: `Bad Failover["*"]: Setting Namespace requires Consul Enterprise`, + }, + } + + // Bulk add a bunch of similar validation cases. + for _, invalidSubset := range invalidSubsetNames { + tc := testcase{ + name: "invalid subset name: " + invalidSubset, + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Subsets: map[string]ServiceResolverSubset{ + invalidSubset: {OnlyPassing: true}, + }, + }, + validateErr: fmt.Sprintf("Subset %q is invalid", invalidSubset), + } + cases = append(cases, tc) + } + + for _, goodSubset := range validSubsetNames { + tc := testcase{ + name: "valid subset name: " + goodSubset, + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Subsets: map[string]ServiceResolverSubset{ + goodSubset: {OnlyPassing: true}, + }, + }, + } + cases = append(cases, tc) + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.entry.Normalize() + if tc.normalizeErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.normalizeErr) + return + } + require.NoError(t, err) + + if tc.check != nil { + tc.check(t, tc.entry) + } + + err = tc.entry.Validate() + if tc.validateErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.validateErr) + return + } + require.NoError(t, err) + }) + } +} diff --git a/agent/structs/config_entry_discoverychain_test.go b/agent/structs/config_entry_discoverychain_test.go index a3fb49b4ae..2580ed4c1b 100644 --- a/agent/structs/config_entry_discoverychain_test.go +++ b/agent/structs/config_entry_discoverychain_test.go @@ -165,6 +165,34 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) { }, }, }, + { + name: "resolver: failover with targets", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{ + {Service: "other1"}, + {Datacenter: "dc2"}, + {Peer: "cluster-01"}, + }, + }, + }, + }, + expectServices: []ServiceID{NewServiceID("other1", nil)}, + expectACLs: []testACL{ + defaultDenyCase, + readTestCase, + writeTestCaseDenied, + { + name: "can write test (with other1:read)", + authorizer: newServiceACL(t, []string{"other1"}, []string{"test"}), + canRead: true, + canWrite: true, + }, + }, + }, { name: "splitter: self", entry: &ServiceSplitterConfigEntry{ @@ -595,6 +623,15 @@ func TestServiceResolverConfigEntry(t *testing.T) { }, validateErr: "Redirect is empty", }, + { + name: "empty redirect", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Redirect: &ServiceResolverRedirect{}, + }, + validateErr: "Redirect is empty", + }, { name: "redirect subset with no service", entry: &ServiceResolverConfigEntry{ @@ -606,17 +643,6 @@ func TestServiceResolverConfigEntry(t *testing.T) { }, validateErr: "Redirect.ServiceSubset defined without Redirect.Service", }, - { - name: "redirect namespace with no service", - entry: &ServiceResolverConfigEntry{ - Kind: ServiceResolver, - Name: "test", - Redirect: &ServiceResolverRedirect{ - Namespace: "alternate", - }, - }, - validateErr: "Redirect.Namespace defined without Redirect.Service", - }, { name: "self redirect with invalid subset", entry: &ServiceResolverConfigEntry{ @@ -695,7 +721,7 @@ func TestServiceResolverConfigEntry(t *testing.T) { "v1": {}, }, }, - validateErr: `Bad Failover["v1"] one of Service, ServiceSubset, Namespace, or Datacenters is required`, + validateErr: `Bad Failover["v1"]: one of Service, ServiceSubset, Namespace, Targets, or Datacenters is required`, }, { name: "failover to self using invalid subset", @@ -712,7 +738,7 @@ func TestServiceResolverConfigEntry(t *testing.T) { }, }, }, - validateErr: `Bad Failover["v1"].ServiceSubset "gone" is not a valid subset of "test"`, + validateErr: `Bad Failover["v1"]: ServiceSubset "gone" is not a valid subset of "test"`, }, { name: "failover to self using valid subset", @@ -745,6 +771,109 @@ func TestServiceResolverConfigEntry(t *testing.T) { }, validateErr: `Bad Failover["*"].Datacenters: found empty datacenter`, }, + { + name: "failover target with an invalid subset", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{{ServiceSubset: "subset"}}, + }, + }, + }, + validateErr: `Bad Failover["*"].Targets[0]: ServiceSubset "subset" is not a valid subset of "test"`, + }, + { + name: "failover targets can't have Peer and ServiceSubset", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{{Peer: "cluster-01", ServiceSubset: "subset"}}, + }, + }, + }, + validateErr: `Bad Failover["*"].Targets[0]: Peer cannot be set with ServiceSubset`, + }, + { + name: "failover targets can't have Peer and Datacenter", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{{Peer: "cluster-01", Datacenter: "dc1"}}, + }, + }, + }, + validateErr: `Bad Failover["*"].Targets[0]: Peer cannot be set with Datacenter`, + }, + { + name: "failover Targets cannot be set with Datacenters", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Datacenters: []string{"a"}, + Targets: []ServiceResolverFailoverTarget{{Peer: "cluster-01"}}, + }, + }, + }, + validateErr: `Bad Failover["*"]: Targets cannot be set with Datacenters`, + }, + { + name: "failover Targets cannot be set with ServiceSubset", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + ServiceSubset: "v2", + Targets: []ServiceResolverFailoverTarget{{Peer: "cluster-01"}}, + }, + }, + Subsets: map[string]ServiceResolverSubset{ + "v2": {Filter: "Service.Meta.version == v2"}, + }, + }, + validateErr: `Bad Failover["*"]: Targets cannot be set with ServiceSubset`, + }, + { + name: "failover Targets cannot be set with Service", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Service: "another-service", + Targets: []ServiceResolverFailoverTarget{{Peer: "cluster-01"}}, + }, + }, + Subsets: map[string]ServiceResolverSubset{ + "v2": {Filter: "Service.Meta.version == v2"}, + }, + }, + validateErr: `Bad Failover["*"]: Targets cannot be set with Service`, + }, + { + name: "complicated failover targets", + entry: &ServiceResolverConfigEntry{ + Kind: ServiceResolver, + Name: "test", + Failover: map[string]ServiceResolverFailover{ + "*": { + Targets: []ServiceResolverFailoverTarget{ + {Peer: "cluster-01", Service: "test-v2"}, + {Service: "test-v2", ServiceSubset: "test"}, + {Datacenter: "dc2"}, + }, + }, + }, + }, + }, { name: "bad connect timeout", entry: &ServiceResolverConfigEntry{ diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index c3f5c7a982..e462f6aa74 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -216,6 +216,85 @@ func testConfigEntries_ListRelatedServices_AndACLs(t *testing.T, cases []configE } } +func TestDecodeConfigEntry_ServiceDefaults(t *testing.T) { + + for _, tc := range []struct { + name string + camel string + snake string + expect ConfigEntry + expectErr string + }{ + { + name: "service-defaults-with-MaxInboundConnections", + snake: ` + kind = "service-defaults" + name = "external" + protocol = "tcp" + destination { + addresses = [ + "api.google.com", + "web.google.com" + ] + port = 8080 + } + max_inbound_connections = 14 + `, + camel: ` + Kind = "service-defaults" + Name = "external" + Protocol = "tcp" + Destination { + Addresses = [ + "api.google.com", + "web.google.com" + ] + Port = 8080 + } + MaxInboundConnections = 14 + `, + expect: &ServiceConfigEntry{ + Kind: "service-defaults", + Name: "external", + Protocol: "tcp", + Destination: &DestinationConfig{ + Addresses: []string{ + "api.google.com", + "web.google.com", + }, + Port: 8080, + }, + MaxInboundConnections: 14, + }, + }, + } { + tc := tc + + testbody := func(t *testing.T, body string) { + var raw map[string]interface{} + err := hcl.Decode(&raw, body) + require.NoError(t, err) + + got, err := DecodeConfigEntry(raw) + if tc.expectErr != "" { + require.Nil(t, got) + require.Error(t, err) + requireContainsLower(t, err.Error(), tc.expectErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expect, got) + } + } + + t.Run(tc.name+" (snake case)", func(t *testing.T) { + testbody(t, tc.snake) + }) + t.Run(tc.name+" (camel case)", func(t *testing.T) { + testbody(t, tc.camel) + }) + } +} + // TestDecodeConfigEntry is the 'structs' mirror image of // command/config/write/config_write_test.go:TestParseConfigEntry func TestDecodeConfigEntry(t *testing.T) { diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index 9d4a8ef91d..ca64d070d6 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -53,28 +53,15 @@ type CompiledDiscoveryChain struct { Targets map[string]*DiscoveryTarget `json:",omitempty"` } -func (c *CompiledDiscoveryChain) WillFailoverThroughMeshGateway(node *DiscoveryGraphNode) bool { - if node.Type != DiscoveryGraphNodeTypeResolver { - return false - } - failover := node.Resolver.Failover - - if failover != nil && len(failover.Targets) > 0 { - for _, failTargetID := range failover.Targets { - failTarget := c.Targets[failTargetID] - switch failTarget.MeshGateway.Mode { - case MeshGatewayModeLocal, MeshGatewayModeRemote: - return true - } - } - } - return false -} - // 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("", c.ServiceName, c.Namespace, c.Partition, c.Datacenter) + return chainID(DiscoveryTargetOpts{ + Service: c.ServiceName, + Namespace: c.Namespace, + Partition: c.Partition, + Datacenter: c.Datacenter, + }) } func (c *CompiledDiscoveryChain) CompoundServiceName() ServiceName { @@ -203,6 +190,7 @@ type DiscoveryTarget struct { Namespace string `json:",omitempty"` Partition string `json:",omitempty"` Datacenter string `json:",omitempty"` + Peer string `json:",omitempty"` MeshGateway MeshGatewayConfig `json:",omitempty"` Subset ServiceResolverSubset `json:",omitempty"` @@ -258,28 +246,52 @@ func (t *DiscoveryTarget) UnmarshalJSON(data []byte) error { return nil } -func NewDiscoveryTarget(service, serviceSubset, namespace, partition, datacenter string) *DiscoveryTarget { +type DiscoveryTargetOpts struct { + Service string + ServiceSubset string + Namespace string + Partition string + Datacenter string + Peer string +} + +func NewDiscoveryTarget(opts DiscoveryTargetOpts) *DiscoveryTarget { t := &DiscoveryTarget{ - Service: service, - ServiceSubset: serviceSubset, - Namespace: namespace, - Partition: partition, - Datacenter: datacenter, + Service: opts.Service, + ServiceSubset: opts.ServiceSubset, + Namespace: opts.Namespace, + Partition: opts.Partition, + Datacenter: opts.Datacenter, + Peer: opts.Peer, } t.setID() return t } -func chainID(subset, service, namespace, partition, dc string) string { - // NOTE: this format is similar to the SNI syntax for simplicity - if subset == "" { - return fmt.Sprintf("%s.%s.%s.%s", service, namespace, partition, dc) +func (t *DiscoveryTarget) ToDiscoveryTargetOpts() DiscoveryTargetOpts { + return DiscoveryTargetOpts{ + Service: t.Service, + ServiceSubset: t.ServiceSubset, + Namespace: t.Namespace, + Partition: t.Partition, + Datacenter: t.Datacenter, + Peer: t.Peer, } - return fmt.Sprintf("%s.%s.%s.%s.%s", subset, service, namespace, partition, dc) +} + +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) + } + if opts.ServiceSubset == "" { + return fmt.Sprintf("%s.%s.%s.%s", opts.Service, opts.Namespace, opts.Partition, opts.Datacenter) + } + return fmt.Sprintf("%s.%s.%s.%s.%s", opts.ServiceSubset, opts.Service, opts.Namespace, opts.Partition, opts.Datacenter) } func (t *DiscoveryTarget) setID() { - t.ID = chainID(t.ServiceSubset, t.Service, t.Namespace, t.Partition, t.Datacenter) + t.ID = chainID(t.ToDiscoveryTargetOpts()) } func (t *DiscoveryTarget) String() string { diff --git a/agent/structs/structs.go b/agent/structs/structs.go index 2ac57613ca..8301688886 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -353,7 +353,7 @@ func (q QueryOptions) Timeout(rpcHoldTimeout, maxQueryTime, defaultQueryTime tim q.MaxQueryTime = defaultQueryTime } // Timeout after maximum jitter has elapsed. - q.MaxQueryTime += lib.RandomStagger(q.MaxQueryTime / JitterFraction) + q.MaxQueryTime += q.MaxQueryTime / JitterFraction return q.MaxQueryTime + rpcHoldTimeout } @@ -1413,6 +1413,27 @@ func (s *NodeService) IsGateway() bool { func (s *NodeService) Validate() error { var result error + if s.Kind == ServiceKindConnectProxy { + if s.Port == 0 && s.SocketPath == "" { + result = multierror.Append(result, fmt.Errorf("Port or SocketPath must be set for a %s", s.Kind)) + } + } + + commonValidation := s.ValidateForAgent() + if commonValidation != nil { + result = multierror.Append(result, commonValidation) + } + + return result +} + +// ValidateForAgent does a subset validation, with the assumption that a local agent can assist with missing values. +// +// I.e. in the catalog case, a local agent cannot be assumed to facilitate auto-assignment of port or socket path, +// so additional checks are needed. +func (s *NodeService) ValidateForAgent() error { + var result error + // TODO(partitions): remember to double check that this doesn't cross partition boundaries // ConnectProxy validation @@ -1428,10 +1449,6 @@ func (s *NodeService) Validate() error { "services")) } - if s.Port == 0 && s.SocketPath == "" { - result = multierror.Append(result, fmt.Errorf("Port or SocketPath must be set for a %s", s.Kind)) - } - if s.Connect.Native { result = multierror.Append(result, fmt.Errorf( "A Proxy cannot also be Connect Native, only typical services")) @@ -2194,8 +2211,8 @@ type PeeredServiceName struct { } type ServiceName struct { - Name string - acl.EnterpriseMeta + Name string + acl.EnterpriseMeta `mapstructure:",squash"` } func NewServiceName(name string, entMeta *acl.EnterpriseMeta) ServiceName { diff --git a/agent/structs/structs_test.go b/agent/structs/structs_test.go index 0b6efb3309..87402a2d4f 100644 --- a/agent/structs/structs_test.go +++ b/agent/structs/structs_test.go @@ -1157,6 +1157,16 @@ func TestStructs_NodeService_ValidateConnectProxy(t *testing.T) { } } +func TestStructs_NodeService_ValidateConnectProxyWithAgentAutoAssign(t *testing.T) { + t.Run("connect-proxy: no port set", func(t *testing.T) { + ns := TestNodeServiceProxy(t) + ns.Port = 0 + + err := ns.ValidateForAgent() + assert.NoError(t, err) + }) +} + func TestStructs_NodeService_ValidateConnectProxy_In_Partition(t *testing.T) { cases := []struct { Name string diff --git a/agent/submatview/local_materializer.go b/agent/submatview/local_materializer.go index 6e32b36025..b3d4480bda 100644 --- a/agent/submatview/local_materializer.go +++ b/agent/submatview/local_materializer.go @@ -66,6 +66,10 @@ func (m *LocalMaterializer) Run(ctx context.Context) { if ctx.Err() != nil { return } + if m.isTerminalError(err) { + return + } + m.mat.handleError(req, err) if err := m.mat.retryWaiter.Wait(ctx); err != nil { @@ -74,6 +78,14 @@ func (m *LocalMaterializer) Run(ctx context.Context) { } } +// isTerminalError determines whether the given error cannot be recovered from +// and should cause the materializer to halt and be evicted from the view store. +// +// This roughly matches the logic in agent/proxycfg-glue.newUpdateEvent. +func (m *LocalMaterializer) isTerminalError(err error) bool { + return acl.IsErrNotFound(err) +} + // subscribeOnce opens a new subscription to a local backend and runs // for its lifetime or until the view is closed. func (m *LocalMaterializer) subscribeOnce(ctx context.Context, req *pbsubscribe.SubscribeRequest) error { diff --git a/agent/submatview/store.go b/agent/submatview/store.go index 242a0d70d7..dacf2d8bae 100644 --- a/agent/submatview/store.go +++ b/agent/submatview/store.go @@ -47,6 +47,9 @@ type entry struct { // requests is the count of active requests using this entry. This entry will // remain in the store as long as this count remains > 0. requests int + // evicting is used to mark an entry that will be evicted when the current in- + // flight requests finish. + evicting bool } // NewStore creates and returns a Store that is ready for use. The caller must @@ -89,6 +92,7 @@ func (s *Store) Run(ctx context.Context) { // Only stop the materializer if there are no active requests. if e.requests == 0 { + s.logger.Trace("evicting item from store", "key", he.Key()) e.stop() delete(s.byKey, he.Key()) } @@ -187,13 +191,13 @@ func (s *Store) NotifyCallback( "error", err, "request-type", req.Type(), "index", index) - continue } index = result.Index cb(ctx, cache.UpdateEvent{ CorrelationID: correlationID, Result: result.Value, + Err: err, Meta: cache.ResultMeta{Index: result.Index, Hit: result.Cached}, }) } @@ -211,6 +215,9 @@ func (s *Store) readEntry(req Request) (string, Materializer, error) { defer s.lock.Unlock() e, ok := s.byKey[key] if ok { + if e.evicting { + return "", nil, errors.New("item is marked for eviction") + } e.requests++ s.byKey[key] = e return key, e.materializer, nil @@ -222,7 +229,18 @@ func (s *Store) readEntry(req Request) (string, Materializer, error) { } ctx, cancel := context.WithCancel(context.Background()) - go mat.Run(ctx) + go func() { + mat.Run(ctx) + + // Materializers run until they either reach their TTL and are evicted (which + // cancels the given context) or encounter an irrecoverable error. + // + // If the context hasn't been canceled, we know it's the error case so we + // trigger an immediate eviction. + if ctx.Err() == nil { + s.evictNow(key) + } + }() e = entry{ materializer: mat, @@ -233,6 +251,28 @@ func (s *Store) readEntry(req Request) (string, Materializer, error) { return key, e.materializer, nil } +// evictNow causes the item with the given key to be evicted immediately. +// +// If there are requests in-flight, the item is marked for eviction such that +// once the requests have been served releaseEntry will move it to the top of +// the expiry heap. If there are no requests in-flight, evictNow will move the +// item to the top of the expiry heap itself. +// +// In either case, the entry's evicting flag prevents it from being served by +// readEntry (and thereby gaining new in-flight requests). +func (s *Store) evictNow(key string) { + s.lock.Lock() + defer s.lock.Unlock() + + e := s.byKey[key] + e.evicting = true + s.byKey[key] = e + + if e.requests == 0 { + s.expireNowLocked(key) + } +} + // releaseEntry decrements the request count and starts an expiry timer if the // count has reached 0. Must be called once for every call to readEntry. func (s *Store) releaseEntry(key string) { @@ -246,6 +286,11 @@ func (s *Store) releaseEntry(key string) { return } + if e.evicting { + s.expireNowLocked(key) + return + } + if e.expiry.Index() == ttlcache.NotIndexed { e.expiry = s.expiryHeap.Add(key, s.idleTTL) s.byKey[key] = e @@ -255,6 +300,17 @@ func (s *Store) releaseEntry(key string) { s.expiryHeap.Update(e.expiry.Index(), s.idleTTL) } +// expireNowLocked moves the item with the given key to the top of the expiry +// heap, causing it to be picked up by the expiry loop and evicted immediately. +func (s *Store) expireNowLocked(key string) { + e := s.byKey[key] + if idx := e.expiry.Index(); idx != ttlcache.NotIndexed { + s.expiryHeap.Remove(idx) + } + e.expiry = s.expiryHeap.Add(key, time.Duration(0)) + s.byKey[key] = e +} + // makeEntryKey matches agent/cache.makeEntryKey, but may change in the future. func makeEntryKey(typ string, r cache.RequestInfo) string { return fmt.Sprintf("%s/%s/%s/%s", typ, r.Datacenter, r.Token, r.Key) diff --git a/agent/submatview/store_test.go b/agent/submatview/store_test.go index 1d5789c054..aab0995998 100644 --- a/agent/submatview/store_test.go +++ b/agent/submatview/store_test.go @@ -509,3 +509,75 @@ func TestStore_Run_ExpiresEntries(t *testing.T) { require.Len(t, store.byKey, 0) require.Equal(t, ttlcache.NotIndexed, e.expiry.Index()) } + +func TestStore_Run_FailingMaterializer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + store := NewStore(hclog.NewNullLogger()) + store.idleTTL = 24 * time.Hour + go store.Run(ctx) + + t.Run("with an in-flight request", func(t *testing.T) { + req := &failingMaterializerRequest{ + doneCh: make(chan struct{}), + } + + ch := make(chan cache.UpdateEvent) + reqCtx, reqCancel := context.WithCancel(context.Background()) + t.Cleanup(reqCancel) + require.NoError(t, store.Notify(reqCtx, req, "", ch)) + + assertRequestCount(t, store, req, 1) + + // Cause the materializer to "fail" (exit before its context is canceled). + close(req.doneCh) + + // End the in-flight request. + reqCancel() + + // Check that the item was evicted. + retry.Run(t, func(r *retry.R) { + store.lock.Lock() + defer store.lock.Unlock() + + require.Len(r, store.byKey, 0) + }) + }) + + t.Run("with no in-flight requests", func(t *testing.T) { + req := &failingMaterializerRequest{ + doneCh: make(chan struct{}), + } + + // Cause the materializer to "fail" (exit before its context is canceled). + close(req.doneCh) + + // Check that the item was evicted. + retry.Run(t, func(r *retry.R) { + store.lock.Lock() + defer store.lock.Unlock() + + require.Len(r, store.byKey, 0) + }) + }) +} + +type failingMaterializerRequest struct { + doneCh chan struct{} +} + +func (failingMaterializerRequest) CacheInfo() cache.RequestInfo { return cache.RequestInfo{} } +func (failingMaterializerRequest) Type() string { return "test.FailingMaterializerRequest" } + +func (r *failingMaterializerRequest) NewMaterializer() (Materializer, error) { + return &failingMaterializer{doneCh: r.doneCh}, nil +} + +type failingMaterializer struct { + doneCh <-chan struct{} +} + +func (failingMaterializer) Query(context.Context, uint64) (Result, error) { return Result{}, nil } + +func (m *failingMaterializer) Run(context.Context) { <-m.doneCh } diff --git a/agent/testagent.go b/agent/testagent.go index af6ac87dc1..5701834b7c 100644 --- a/agent/testagent.go +++ b/agent/testagent.go @@ -138,6 +138,9 @@ func TestConfigHCL(nodeID string) string { } performance { raft_multiplier = 1 + } + peering { + enabled = true }`, nodeID, connect.TestClusterID, ) } diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index 9f64c5e342..adde810d37 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -10,6 +10,7 @@ import ( 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_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" + envoy_aggregate_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" envoy_upstreams_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" @@ -31,6 +32,7 @@ import ( const ( meshGatewayExportedClusterNamePrefix = "exported~" + failoverClusterNamePrefix = "failover-target~" ) // clustersFromSnapshot returns the xDS API representation of the "clusters" in the snapshot. @@ -1008,180 +1010,174 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain( continue } failover := node.Resolver.Failover - targetID := node.Resolver.Target + // These variables are prefixed with primary to avoid shaddowing bugs. + primaryTargetID := node.Resolver.Target + primaryTarget := chain.Targets[primaryTargetID] + primaryClusterName := CustomizeClusterName(primaryTarget.Name, chain) + if forMeshGateway { + primaryClusterName = meshGatewayExportedClusterNamePrefix + primaryClusterName + } - target := chain.Targets[targetID] - - if forMeshGateway && !cfgSnap.Locality.Matches(target.Datacenter, target.Partition) { + if forMeshGateway && !cfgSnap.Locality.Matches(primaryTarget.Datacenter, primaryTarget.Partition) { s.Logger.Warn("ignoring discovery chain target that crosses a datacenter or partition boundary in a mesh gateway", - "target", target, + "target", primaryTarget, "gatewayLocality", cfgSnap.Locality, ) continue } - // Determine if we have to generate the entire cluster differently. - failoverThroughMeshGateway := chain.WillFailoverThroughMeshGateway(node) && !forMeshGateway - - sni := target.SNI - clusterName := CustomizeClusterName(target.Name, chain) - if forMeshGateway { - clusterName = meshGatewayExportedClusterNamePrefix + clusterName + type targetClusterOptions struct { + targetID string + clusterName string } - // Get the SpiffeID for upstream SAN validation. - // - // For imported services the SpiffeID is embedded in the proxy instances. - // Whereas for local services we can construct the SpiffeID from the chain target. - var targetSpiffeID string - var additionalSpiffeIDs []string - if uid.Peer != "" { - for _, e := range chainEndpoints[targetID] { - targetSpiffeID = e.Service.Connect.PeerMeta.SpiffeID[0] - additionalSpiffeIDs = e.Service.Connect.PeerMeta.SpiffeID[1:] + // Construct the information required to make target clusters. When + // failover is configured, create the aggregate cluster. + var targetClustersOptions []targetClusterOptions + if failover != nil && !forMeshGateway { + var failoverClusterNames []string + for _, tid := range append([]string{primaryTargetID}, failover.Targets...) { + target := chain.Targets[tid] + clusterName := CustomizeClusterName(target.Name, chain) + clusterName = failoverClusterNamePrefix + clusterName - // Only grab the first instance because it is the same for all instances. - break + targetClustersOptions = append(targetClustersOptions, targetClusterOptions{ + targetID: tid, + clusterName: clusterName, + }) + failoverClusterNames = append(failoverClusterNames, clusterName) } + + aggregateClusterConfig, err := anypb.New(&envoy_aggregate_cluster_v3.ClusterConfig{ + Clusters: failoverClusterNames, + }) + if err != nil { + return nil, fmt.Errorf("failed to construct the aggregate cluster %q: %v", primaryClusterName, err) + } + + c := &envoy_cluster_v3.Cluster{ + Name: primaryClusterName, + AltStatName: primaryClusterName, + ConnectTimeout: durationpb.New(node.Resolver.ConnectTimeout), + LbPolicy: envoy_cluster_v3.Cluster_CLUSTER_PROVIDED, + ClusterDiscoveryType: &envoy_cluster_v3.Cluster_ClusterType{ + ClusterType: &envoy_cluster_v3.Cluster_CustomClusterType{ + Name: "envoy.clusters.aggregate", + TypedConfig: aggregateClusterConfig, + }, + }, + } + + out = append(out, c) } else { - targetSpiffeID = connect.SpiffeIDService{ + targetClustersOptions = append(targetClustersOptions, targetClusterOptions{ + targetID: primaryTargetID, + clusterName: primaryClusterName, + }) + } + + // Construct the target clusters. + for _, targetInfo := range targetClustersOptions { + target := chain.Targets[targetInfo.targetID] + sni := target.SNI + var additionalSpiffeIDs []string + + targetSpiffeID := connect.SpiffeIDService{ Host: cfgSnap.Roots.TrustDomain, Namespace: target.Namespace, Partition: target.Partition, Datacenter: target.Datacenter, Service: target.Service, }.URI().String() - } - - if failoverThroughMeshGateway { - actualTargetID := firstHealthyTarget( - chain.Targets, - chainEndpoints, - targetID, - failover.Targets, - ) - - if actualTargetID != targetID { - actualTarget := chain.Targets[actualTargetID] - sni = actualTarget.SNI + if uid.Peer != "" { + return nil, fmt.Errorf("impossible to get a peer discovery chain") } - } - spiffeIDs := append([]string{targetSpiffeID}, additionalSpiffeIDs...) - seenIDs := map[string]struct{}{ - targetSpiffeID: {}, - } - - if failover != nil { - // When failovers are present we need to add them as valid SANs to validate against. - // Envoy makes the failover decision independently based on the endpoint health it has available. - for _, tid := range failover.Targets { - target, ok := chain.Targets[tid] - if !ok { - continue - } - - id := connect.SpiffeIDService{ - Host: cfgSnap.Roots.TrustDomain, - Namespace: target.Namespace, - Partition: target.Partition, - Datacenter: target.Datacenter, - Service: target.Service, - }.URI().String() - - // Failover targets might be subsets of the same service, so these are deduplicated. - if _, ok := seenIDs[id]; ok { - continue - } - seenIDs[id] = struct{}{} - - spiffeIDs = append(spiffeIDs, id) - } - } - sort.Strings(spiffeIDs) - - s.Logger.Trace("generating cluster for", "cluster", clusterName) - c := &envoy_cluster_v3.Cluster{ - Name: clusterName, - AltStatName: clusterName, - ConnectTimeout: durationpb.New(node.Resolver.ConnectTimeout), - ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_EDS}, - CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{ - HealthyPanicThreshold: &envoy_type_v3.Percent{ - Value: 0, // disable panic threshold - }, - }, - EdsClusterConfig: &envoy_cluster_v3.Cluster_EdsClusterConfig{ - EdsConfig: &envoy_core_v3.ConfigSource{ - ResourceApiVersion: envoy_core_v3.ApiVersion_V3, - ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_Ads{ - Ads: &envoy_core_v3.AggregatedConfigSource{}, + s.Logger.Trace("generating cluster for", "cluster", targetInfo.clusterName) + c := &envoy_cluster_v3.Cluster{ + Name: targetInfo.clusterName, + AltStatName: targetInfo.clusterName, + ConnectTimeout: durationpb.New(node.Resolver.ConnectTimeout), + ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_EDS}, + CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{ + HealthyPanicThreshold: &envoy_type_v3.Percent{ + Value: 0, // disable panic threshold }, }, - }, - // TODO(peering): make circuit breakers or outlier detection work? - CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{ - Thresholds: makeThresholdsIfNeeded(cfg.Limits), - }, - OutlierDetection: ToOutlierDetection(cfg.PassiveHealthCheck), - } - - var lb *structs.LoadBalancer - if node.LoadBalancer != nil { - lb = node.LoadBalancer - } - if err := injectLBToCluster(lb, c); err != nil { - return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", clusterName, err) - } - - var proto string - if !forMeshGateway { - proto = cfg.Protocol - } - if proto == "" { - proto = chain.Protocol - } - - if proto == "" { - proto = "tcp" - } - - if proto == "http2" || proto == "grpc" { - if err := s.setHttp2ProtocolOptions(c); err != nil { - return nil, err - } - } - - configureTLS := true - if forMeshGateway { - // We only initiate TLS if we're doing an L7 proxy. - configureTLS = structs.IsProtocolHTTPLike(proto) - } - - if configureTLS { - commonTLSContext := makeCommonTLSContext( - cfgSnap.Leaf(), - cfgSnap.RootPEMs(), - makeTLSParametersFromProxyTLSConfig(cfgSnap.MeshConfigTLSOutgoing()), - ) - - err = injectSANMatcher(commonTLSContext, spiffeIDs...) - if err != nil { - return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err) + EdsClusterConfig: &envoy_cluster_v3.Cluster_EdsClusterConfig{ + EdsConfig: &envoy_core_v3.ConfigSource{ + ResourceApiVersion: envoy_core_v3.ApiVersion_V3, + ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_Ads{ + Ads: &envoy_core_v3.AggregatedConfigSource{}, + }, + }, + }, + // TODO(peering): make circuit breakers or outlier detection work? + CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{ + Thresholds: makeThresholdsIfNeeded(cfg.Limits), + }, + OutlierDetection: ToOutlierDetection(cfg.PassiveHealthCheck), } - tlsContext := &envoy_tls_v3.UpstreamTlsContext{ - CommonTlsContext: commonTLSContext, - Sni: sni, + var lb *structs.LoadBalancer + if node.LoadBalancer != nil { + lb = node.LoadBalancer } - transportSocket, err := makeUpstreamTLSTransportSocket(tlsContext) - if err != nil { - return nil, err + if err := injectLBToCluster(lb, c); err != nil { + return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", targetInfo.clusterName, err) } - c.TransportSocket = transportSocket - } - out = append(out, c) + var proto string + if !forMeshGateway { + proto = cfg.Protocol + } + if proto == "" { + proto = chain.Protocol + } + + if proto == "" { + proto = "tcp" + } + + if proto == "http2" || proto == "grpc" { + if err := s.setHttp2ProtocolOptions(c); err != nil { + return nil, err + } + } + + configureTLS := true + if forMeshGateway { + // We only initiate TLS if we're doing an L7 proxy. + configureTLS = structs.IsProtocolHTTPLike(proto) + } + + if configureTLS { + commonTLSContext := makeCommonTLSContext( + cfgSnap.Leaf(), + cfgSnap.RootPEMs(), + makeTLSParametersFromProxyTLSConfig(cfgSnap.MeshConfigTLSOutgoing()), + ) + + spiffeIDs := append([]string{targetSpiffeID}, additionalSpiffeIDs...) + sort.Strings(spiffeIDs) + err = injectSANMatcher(commonTLSContext, spiffeIDs...) + if err != nil { + return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err) + } + + tlsContext := &envoy_tls_v3.UpstreamTlsContext{ + CommonTlsContext: commonTLSContext, + Sni: sni, + } + transportSocket, err := makeUpstreamTLSTransportSocket(tlsContext) + if err != nil { + return nil, err + } + c.TransportSocket = transportSocket + } + + out = append(out, c) + } } if escapeHatchCluster != nil { @@ -1494,7 +1490,7 @@ func (s *ResourceGenerator) makeTerminatingHostnameCluster(snap *proxycfg.Config // Having an empty config enables outlier detection with default config. OutlierDetection: &envoy_cluster_v3.OutlierDetection{}, ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_LOGICAL_DNS}, - DnsLookupFamily: envoy_cluster_v3.Cluster_AUTO, + DnsLookupFamily: envoy_cluster_v3.Cluster_V4_ONLY, } rate := 10 * time.Second diff --git a/agent/xds/delta.go b/agent/xds/delta.go index 701c04f2ed..71c1edcb0f 100644 --- a/agent/xds/delta.go +++ b/agent/xds/delta.go @@ -81,6 +81,11 @@ const ( ) func (s *Server) processDelta(stream ADSDeltaStream, reqCh <-chan *envoy_discovery_v3.DeltaDiscoveryRequest) error { + // Handle invalid ACL tokens up-front. + if _, err := s.authenticate(stream.Context()); err != nil { + return err + } + // Loop state var ( cfgSnap *proxycfg.ConfigSnapshot @@ -200,7 +205,18 @@ func (s *Server) processDelta(stream ADSDeltaStream, reqCh <-chan *envoy_discove } } - case cfgSnap = <-stateCh: + case cs, ok := <-stateCh: + if !ok { + // stateCh is closed either when *we* cancel the watch (on-exit via defer) + // or by the proxycfg.Manager when an irrecoverable error is encountered + // such as the ACL token getting deleted. + // + // We know for sure that this is the latter case, because in the former we + // would've already exited this loop. + return status.Error(codes.Aborted, "xDS stream terminated due to an irrecoverable error, please try again") + } + cfgSnap = cs + newRes, err := generator.allResourcesFromSnapshot(cfgSnap) if err != nil { return status.Errorf(codes.Unavailable, "failed to generate all xDS resources from the snapshot: %v", err) diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index 2b187a68f3..6c0fca7f25 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -466,11 +466,49 @@ func (s *ResourceGenerator) endpointsFromDiscoveryChain( continue } failover := node.Resolver.Failover + + var numFailoverTargets int + if failover != nil { + numFailoverTargets = len(failover.Targets) + } + clusterNamePrefix := "" + if numFailoverTargets > 0 && !forMeshGateway { + clusterNamePrefix = failoverClusterNamePrefix + for _, failTargetID := range failover.Targets { + target := chain.Targets[failTargetID] + endpointGroup, valid := makeLoadAssignmentEndpointGroup( + chain.Targets, + upstreamEndpoints, + gatewayEndpoints, + failTargetID, + gatewayKey, + forMeshGateway, + ) + if !valid { + continue // skip the failover target if we're still populating the snapshot + } + + clusterName := CustomizeClusterName(target.Name, chain) + clusterName = failoverClusterNamePrefix + clusterName + if escapeHatchCluster != nil { + clusterName = escapeHatchCluster.Name + } + + s.Logger.Debug("generating endpoints for", "cluster", clusterName) + + la := makeLoadAssignment( + clusterName, + []loadAssignmentEndpointGroup{endpointGroup}, + gatewayKey, + ) + resources = append(resources, la) + } + } targetID := node.Resolver.Target target := chain.Targets[targetID] - clusterName := CustomizeClusterName(target.Name, chain) + clusterName = clusterNamePrefix + clusterName if escapeHatchCluster != nil { clusterName = escapeHatchCluster.Name } @@ -478,25 +516,7 @@ func (s *ResourceGenerator) endpointsFromDiscoveryChain( clusterName = meshGatewayExportedClusterNamePrefix + clusterName } s.Logger.Debug("generating endpoints for", "cluster", clusterName) - - // Determine if we have to generate the entire cluster differently. - failoverThroughMeshGateway := chain.WillFailoverThroughMeshGateway(node) && !forMeshGateway - - if failoverThroughMeshGateway { - actualTargetID := firstHealthyTarget( - chain.Targets, - upstreamEndpoints, - targetID, - failover.Targets, - ) - if actualTargetID != targetID { - targetID = actualTargetID - } - - failover = nil - } - - primaryGroup, valid := makeLoadAssignmentEndpointGroup( + endpointGroup, valid := makeLoadAssignmentEndpointGroup( chain.Targets, upstreamEndpoints, gatewayEndpoints, @@ -508,34 +528,9 @@ func (s *ResourceGenerator) endpointsFromDiscoveryChain( continue // skip the cluster if we're still populating the snapshot } - var numFailoverTargets int - if failover != nil { - numFailoverTargets = len(failover.Targets) - } - - endpointGroups := make([]loadAssignmentEndpointGroup, 0, numFailoverTargets+1) - endpointGroups = append(endpointGroups, primaryGroup) - - if failover != nil && len(failover.Targets) > 0 { - for _, failTargetID := range failover.Targets { - failoverGroup, valid := makeLoadAssignmentEndpointGroup( - chain.Targets, - upstreamEndpoints, - gatewayEndpoints, - failTargetID, - gatewayKey, - forMeshGateway, - ) - if !valid { - continue // skip the failover target if we're still populating the snapshot - } - endpointGroups = append(endpointGroups, failoverGroup) - } - } - la := makeLoadAssignment( clusterName, - endpointGroups, + []loadAssignmentEndpointGroup{endpointGroup}, gatewayKey, ) resources = append(resources, la) diff --git a/agent/xds/failover_math_test.go b/agent/xds/failover_math_test.go index 29ac17ffe1..296d1cc77f 100644 --- a/agent/xds/failover_math_test.go +++ b/agent/xds/failover_math_test.go @@ -15,15 +15,40 @@ func TestFirstHealthyTarget(t *testing.T) { warning := proxycfg.TestUpstreamNodesInStatus(t, "warning") critical := proxycfg.TestUpstreamNodesInStatus(t, "critical") - warnOnlyPassingTarget := structs.NewDiscoveryTarget("all-warn", "", "default", "default", "dc1") + warnOnlyPassingTarget := structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "all-warn", + Namespace: "default", + Partition: "default", + Datacenter: "dc1", + }) warnOnlyPassingTarget.Subset.OnlyPassing = true - failOnlyPassingTarget := structs.NewDiscoveryTarget("all-fail", "", "default", "default", "dc1") + failOnlyPassingTarget := structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "all-fail", + Namespace: "default", + Partition: "default", + Datacenter: "dc1", + }) failOnlyPassingTarget.Subset.OnlyPassing = true targets := map[string]*structs.DiscoveryTarget{ - "all-ok.default.dc1": structs.NewDiscoveryTarget("all-ok", "", "default", "default", "dc1"), - "all-warn.default.dc1": structs.NewDiscoveryTarget("all-warn", "", "default", "default", "dc1"), - "all-fail.default.default.dc1": structs.NewDiscoveryTarget("all-fail", "", "default", "default", "dc1"), + "all-ok.default.dc1": structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "all-ok", + Namespace: "default", + Partition: "default", + Datacenter: "dc1", + }), + "all-warn.default.dc1": structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "all-warn", + Namespace: "default", + Partition: "default", + Datacenter: "dc1", + }), + "all-fail.default.default.dc1": structs.NewDiscoveryTarget(structs.DiscoveryTargetOpts{ + Service: "all-fail", + Namespace: "default", + Partition: "default", + Datacenter: "dc1", + }), "all-warn-onlypassing.default.dc1": warnOnlyPassingTarget, "all-fail-onlypassing.default.dc1": failOnlyPassingTarget, } diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index 20f0013b35..33c339c4d8 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -3,6 +3,7 @@ package xds import ( "errors" "fmt" + envoy_extensions_filters_listener_http_inspector_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/http_inspector/v3" "net" "net/url" "regexp" @@ -227,30 +228,53 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. } } requiresTLSInspector := false + requiresHTTPInspector := false + configuredPorts := make(map[int]interface{}) err = cfgSnap.ConnectProxy.DestinationsUpstream.ForEachKeyE(func(uid proxycfg.UpstreamID) error { svcConfig, ok := cfgSnap.ConnectProxy.DestinationsUpstream.Get(uid) if !ok || svcConfig == nil { return nil } - for _, address := range svcConfig.Destination.Addresses { - clusterName := clusterNameForDestination(cfgSnap, uid.Name, address, uid.NamespaceOrDefault(), uid.PartitionOrDefault()) + if structs.IsProtocolHTTPLike(svcConfig.Protocol) { + if _, ok := configuredPorts[svcConfig.Destination.Port]; ok { + return nil + } + configuredPorts[svcConfig.Destination.Port] = struct{}{} + const name = "~http" //name used for the shared route name + routeName := clusterNameForDestination(cfgSnap, name, fmt.Sprintf("%d", svcConfig.Destination.Port), svcConfig.NamespaceOrDefault(), svcConfig.PartitionOrDefault()) filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{ - routeName: uid.EnvoyID(), - clusterName: clusterName, - filterName: clusterName, - protocol: svcConfig.Protocol, - useRDS: structs.IsProtocolHTTPLike(svcConfig.Protocol), + routeName: routeName, + filterName: routeName, + protocol: svcConfig.Protocol, + useRDS: true, }) if err != nil { return err } - - filterChain.FilterChainMatch = makeFilterChainMatchFromAddressWithPort(address, svcConfig.Destination.Port) + filterChain.FilterChainMatch = makeFilterChainMatchFromAddressWithPort("", svcConfig.Destination.Port) outboundListener.FilterChains = append(outboundListener.FilterChains, filterChain) + requiresHTTPInspector = true + } else { + for _, address := range svcConfig.Destination.Addresses { + clusterName := clusterNameForDestination(cfgSnap, uid.Name, address, uid.NamespaceOrDefault(), uid.PartitionOrDefault()) - requiresTLSInspector = len(filterChain.FilterChainMatch.ServerNames) != 0 || requiresTLSInspector + filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{ + routeName: uid.EnvoyID(), + clusterName: clusterName, + filterName: clusterName, + protocol: svcConfig.Protocol, + }) + if err != nil { + return err + } + + filterChain.FilterChainMatch = makeFilterChainMatchFromAddressWithPort(address, svcConfig.Destination.Port) + outboundListener.FilterChains = append(outboundListener.FilterChains, filterChain) + + requiresTLSInspector = len(filterChain.FilterChainMatch.ServerNames) != 0 || requiresTLSInspector + } } return nil }) @@ -266,6 +290,14 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. outboundListener.ListenerFilters = append(outboundListener.ListenerFilters, tlsInspector) } + if requiresHTTPInspector { + httpInspector, err := makeHTTPInspectorListenerFilter() + if err != nil { + return nil, err + } + outboundListener.ListenerFilters = append(outboundListener.ListenerFilters, httpInspector) + } + // Looping over explicit and implicit upstreams is only needed for cross-peer // because they do not have discovery chains. for _, uid := range cfgSnap.ConnectProxy.PeeredUpstreamIDs() { @@ -425,7 +457,10 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. ":" + outboundListener.FilterChains[i].FilterChainMatch.DestinationPort.String() } if len(outboundListener.FilterChains[i].FilterChainMatch.ServerNames) > 0 { - si += outboundListener.FilterChains[i].FilterChainMatch.ServerNames[0] + si += outboundListener.FilterChains[i].FilterChainMatch.ServerNames[0] + + ":" + outboundListener.FilterChains[i].FilterChainMatch.DestinationPort.String() + } else { + si += outboundListener.FilterChains[i].FilterChainMatch.DestinationPort.String() } if len(outboundListener.FilterChains[j].FilterChainMatch.PrefixRanges) > 0 { @@ -434,7 +469,10 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. ":" + outboundListener.FilterChains[j].FilterChainMatch.DestinationPort.String() } if len(outboundListener.FilterChains[j].FilterChainMatch.ServerNames) > 0 { - sj += outboundListener.FilterChains[j].FilterChainMatch.ServerNames[0] + sj += outboundListener.FilterChains[j].FilterChainMatch.ServerNames[0] + + ":" + outboundListener.FilterChains[j].FilterChainMatch.DestinationPort.String() + } else { + sj += outboundListener.FilterChains[j].FilterChainMatch.DestinationPort.String() } return si < sj @@ -572,8 +610,13 @@ func makeFilterChainMatchFromAddressWithPort(address string, port int) *envoy_li ip := net.ParseIP(address) if ip == nil { + if address != "" { + return &envoy_listener_v3.FilterChainMatch{ + ServerNames: []string{address}, + DestinationPort: &wrappers.UInt32Value{Value: uint32(port)}, + } + } return &envoy_listener_v3.FilterChainMatch{ - ServerNames: []string{address}, DestinationPort: &wrappers.UInt32Value{Value: uint32(port)}, } } @@ -1171,16 +1214,38 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot filterOpts.forwardClientPolicy = envoy_http_v3.HttpConnectionManager_APPEND_FORWARD } } + + // If an inbound connect limit is set, inject a connection limit filter on each chain. + if cfg.MaxInboundConnections > 0 { + connectionLimitFilter, err := makeConnectionLimitFilter(cfg.MaxInboundConnections) + if err != nil { + return nil, err + } + l.FilterChains = []*envoy_listener_v3.FilterChain{ + { + Filters: []*envoy_listener_v3.Filter{ + connectionLimitFilter, + }, + }, + } + } + filter, err := makeListenerFilter(filterOpts) if err != nil { return nil, err } - l.FilterChains = []*envoy_listener_v3.FilterChain{ - { - Filters: []*envoy_listener_v3.Filter{ - filter, + + if len(l.FilterChains) > 0 { + // The list of FilterChains has already been initialized + l.FilterChains[0].Filters = append(l.FilterChains[0].Filters, filter) + } else { + l.FilterChains = []*envoy_listener_v3.FilterChain{ + { + Filters: []*envoy_listener_v3.Filter{ + filter, + }, }, - }, + } } err = s.finalizePublicListenerFromConfig(l, cfgSnap, cfg, useHTTPFilter) @@ -1206,17 +1271,6 @@ func (s *ResourceGenerator) finalizePublicListenerFromConfig(l *envoy_listener_v return nil } - // If an inbound connect limit is set, inject a connection limit filter on each chain. - if proxyCfg.MaxInboundConnections > 0 { - filter, err := makeConnectionLimitFilter(proxyCfg.MaxInboundConnections) - if err != nil { - return nil - } - for idx := range l.FilterChains { - l.FilterChains[idx].Filters = append(l.FilterChains[idx].Filters, filter) - } - } - return nil } @@ -1923,6 +1977,10 @@ func makeTLSInspectorListenerFilter() (*envoy_listener_v3.ListenerFilter, error) return makeEnvoyListenerFilter("envoy.filters.listener.tls_inspector", &envoy_tls_inspector_v3.TlsInspector{}) } +func makeHTTPInspectorListenerFilter() (*envoy_listener_v3.ListenerFilter, error) { + return makeEnvoyListenerFilter("envoy.filters.listener.http_inspector", &envoy_extensions_filters_listener_http_inspector_v3.HttpInspector{}) +} + func makeSNIFilterChainMatch(sniMatches ...string) *envoy_listener_v3.FilterChainMatch { return &envoy_listener_v3.FilterChainMatch{ ServerNames: sniMatches, @@ -1943,6 +2001,7 @@ func makeTCPProxyFilter(filterName, cluster, statPrefix string) (*envoy_listener func makeConnectionLimitFilter(limit int) (*envoy_listener_v3.Filter, error) { cfg := &envoy_connection_limit_v3.ConnectionLimit{ + StatPrefix: "inbound_connection_limit", MaxConnections: wrapperspb.UInt64(uint64(limit)), } return makeFilter("envoy.filters.network.connection_limit", cfg) diff --git a/agent/xds/resources_test.go b/agent/xds/resources_test.go index 1e3151e07e..fbb98b1214 100644 --- a/agent/xds/resources_test.go +++ b/agent/xds/resources_test.go @@ -181,6 +181,10 @@ func getConnectProxyTransparentProxyGoldenTestCases() []goldenTestCase { name: "transparent-proxy-destination", create: proxycfg.TestConfigSnapshotTransparentProxyDestination, }, + { + name: "transparent-proxy-destination-http", + create: proxycfg.TestConfigSnapshotTransparentProxyDestinationHTTP, + }, { name: "transparent-proxy-terminating-gateway-destinations-only", create: func(t testinf.T) *proxycfg.ConfigSnapshot { diff --git a/agent/xds/routes.go b/agent/xds/routes.go index 35fc03455a..321faa5cde 100644 --- a/agent/xds/routes.go +++ b/agent/xds/routes.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net" + "sort" "strings" "time" @@ -71,11 +72,62 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh } resources = append(resources, route) } + addressesMap := make(map[string]map[string]string) + err := cfgSnap.ConnectProxy.DestinationsUpstream.ForEachKeyE(func(uid proxycfg.UpstreamID) error { + svcConfig, ok := cfgSnap.ConnectProxy.DestinationsUpstream.Get(uid) + if !ok || svcConfig == nil { + return nil + } + if !structs.IsProtocolHTTPLike(svcConfig.Protocol) { + // Routes can only be defined for HTTP services + return nil + } + + for _, address := range svcConfig.Destination.Addresses { + + routeName := clusterNameForDestination(cfgSnap, "~http", fmt.Sprintf("%d", svcConfig.Destination.Port), svcConfig.NamespaceOrDefault(), svcConfig.PartitionOrDefault()) + if _, ok := addressesMap[routeName]; !ok { + addressesMap[routeName] = make(map[string]string) + } + // cluster name is unique per address/port so we should not be doing any override here + clusterName := clusterNameForDestination(cfgSnap, svcConfig.Name, address, svcConfig.NamespaceOrDefault(), svcConfig.PartitionOrDefault()) + addressesMap[routeName][clusterName] = address + } + return nil + }) + + if err != nil { + return nil, err + } + + for routeName, clusters := range addressesMap { + routes, err := s.makeRoutesForAddresses(routeName, clusters) + if err != nil { + return nil, err + } + if routes != nil { + resources = append(resources, routes...) + } + } // TODO(rb): make sure we don't generate an empty result return resources, nil } +func (s *ResourceGenerator) makeRoutesForAddresses(routeName string, addresses map[string]string) ([]proto.Message, error) { + + var resources []proto.Message + + route, err := makeNamedAddressesRoute(routeName, addresses) + if err != nil { + s.Logger.Error("failed to make route", "cluster", "error", err) + return nil, err + } + resources = append(resources, route) + + return resources, nil +} + // routesFromSnapshotTerminatingGateway returns the xDS API representation of the "routes" in the snapshot. // For any HTTP service we will return a default route. func (s *ResourceGenerator) routesForTerminatingGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { @@ -86,6 +138,20 @@ func (s *ResourceGenerator) routesForTerminatingGateway(cfgSnap *proxycfg.Config var resources []proto.Message for _, svc := range cfgSnap.TerminatingGateway.ValidServices() { clusterName := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) + cfg, err := ParseProxyConfig(cfgSnap.TerminatingGateway.ServiceConfigs[svc].ProxyConfig) + if err != nil { + // Don't hard fail on a config typo, just warn. The parse func returns + // default config if there is an error so it's safe to continue. + s.Logger.Warn( + "failed to parse Proxy.Config", + "service", svc.String(), + "error", err, + ) + } + if !structs.IsProtocolHTTPLike(cfg.Protocol) { + // Routes can only be defined for HTTP services + continue + } routes, err := s.makeRoutes(cfgSnap, svc, clusterName, true) if err != nil { return nil, err @@ -100,6 +166,20 @@ func (s *ResourceGenerator) routesForTerminatingGateway(cfgSnap *proxycfg.Config for _, address := range svcConfig.Destination.Addresses { clusterName := clusterNameForDestination(cfgSnap, svc.Name, address, svc.NamespaceOrDefault(), svc.PartitionOrDefault()) + cfg, err := ParseProxyConfig(cfgSnap.TerminatingGateway.ServiceConfigs[svc].ProxyConfig) + if err != nil { + // Don't hard fail on a config typo, just warn. The parse func returns + // default config if there is an error so it's safe to continue. + s.Logger.Warn( + "failed to parse Proxy.Config", + "service", svc.String(), + "error", err, + ) + } + if !structs.IsProtocolHTTPLike(cfg.Protocol) { + // Routes can only be defined for HTTP services + continue + } routes, err := s.makeRoutes(cfgSnap, svc, clusterName, false) if err != nil { return nil, err @@ -120,23 +200,6 @@ func (s *ResourceGenerator) makeRoutes( autoHostRewrite bool) ([]proto.Message, error) { resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc] - svcConfig := cfgSnap.TerminatingGateway.ServiceConfigs[svc] - - cfg, err := ParseProxyConfig(svcConfig.ProxyConfig) - if err != nil { - // Don't hard fail on a config typo, just warn. The parse func returns - // default config if there is an error so it's safe to continue. - s.Logger.Warn( - "failed to parse Proxy.Config", - "service", svc.String(), - "error", err, - ) - } - if !structs.IsProtocolHTTPLike(cfg.Protocol) { - // Routes can only be defined for HTTP services - return nil, nil - } - if !hasResolver { // Use a zero value resolver with no timeout and no subsets resolver = &structs.ServiceResolverConfigEntry{} @@ -245,6 +308,36 @@ func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, a }, nil } +func makeNamedAddressesRoute(routeName string, addresses map[string]string) (*envoy_route_v3.RouteConfiguration, error) { + route := &envoy_route_v3.RouteConfiguration{ + Name: routeName, + // ValidateClusters defaults to true when defined statically and false + // when done via RDS. Re-set the reasonable value of true to prevent + // null-routing traffic. + ValidateClusters: makeBoolValue(true), + } + for clusterName, address := range addresses { + action := makeRouteActionFromName(clusterName) + virtualHost := &envoy_route_v3.VirtualHost{ + Name: clusterName, + Domains: []string{address}, + Routes: []*envoy_route_v3.Route{ + { + Match: makeDefaultRouteMatch(), + Action: action, + }, + }, + } + route.VirtualHosts = append(route.VirtualHosts, virtualHost) + } + + // sort virtual hosts to have a stable order + sort.SliceStable(route.VirtualHosts, func(i, j int) bool { + return route.VirtualHosts[i].Name > route.VirtualHosts[j].Name + }) + return route, nil +} + // routesForIngressGateway returns the xDS API representation of the // "routes" in the snapshot. func (s *ResourceGenerator) routesForIngressGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { diff --git a/agent/xds/server.go b/agent/xds/server.go index cc27f3fde7..3ee42e77b0 100644 --- a/agent/xds/server.go +++ b/agent/xds/server.go @@ -186,6 +186,18 @@ func (s *Server) Register(srv *grpc.Server) { envoy_discovery_v3.RegisterAggregatedDiscoveryServiceServer(srv, s) } +func (s *Server) authenticate(ctx context.Context) (acl.Authorizer, error) { + authz, err := s.ResolveToken(external.TokenFromContext(ctx)) + if acl.IsErrNotFound(err) { + return nil, status.Errorf(codes.Unauthenticated, "unauthenticated: %v", err) + } else if acl.IsErrPermissionDenied(err) { + return nil, status.Error(codes.PermissionDenied, err.Error()) + } else if err != nil { + return nil, status.Errorf(codes.Internal, "error resolving acl token: %v", err) + } + return authz, nil +} + // authorize the xDS request using the token stored in ctx. This authorization is // a bit different from most interfaces. Instead of explicitly authorizing or // filtering each piece of data in the response, the request is authorized @@ -201,13 +213,9 @@ func (s *Server) authorize(ctx context.Context, cfgSnap *proxycfg.ConfigSnapshot return status.Errorf(codes.Unauthenticated, "unauthenticated: no config snapshot") } - authz, err := s.ResolveToken(external.TokenFromContext(ctx)) - if acl.IsErrNotFound(err) { - return status.Errorf(codes.Unauthenticated, "unauthenticated: %v", err) - } else if acl.IsErrPermissionDenied(err) { - return status.Error(codes.PermissionDenied, err.Error()) - } else if err != nil { - return status.Errorf(codes.Internal, "error resolving acl token: %v", err) + authz, err := s.authenticate(ctx) + if err != nil { + return err } var authzContext acl.AuthorizerContext diff --git a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover.latest.golden index 1ee533e39c..8a6f88a866 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover.latest.golden @@ -5,6 +5,23 @@ "@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~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,14 +68,69 @@ "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": "failover-target~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/fail" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } }, diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden index e2462b7978..e434b71c10 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,10 +69,120 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" } diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden index 947dab839c..e434b71c10 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,17 +69,127 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" } } }, diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden index e2462b7978..e434b71c10 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,10 +69,120 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" } diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden index 947dab839c..e434b71c10 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,17 +69,127 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" } } }, diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden index 1fb114f4ca..8a032b3211 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,7 +68,62 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" } diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden index 8dea5d4a95..8a032b3211 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,14 +68,69 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" } } }, diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden index 1fb114f4ca..8a032b3211 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,7 +68,62 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" } diff --git a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden index 8dea5d4a95..8a032b3211 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,14 +68,69 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" } ] } }, - "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" } } }, diff --git a/agent/xds/testdata/clusters/ingress-with-chain-and-failover.latest.golden b/agent/xds/testdata/clusters/ingress-with-chain-and-failover.latest.golden index 70a32347f0..7db8a7d2ca 100644 --- a/agent/xds/testdata/clusters/ingress-with-chain-and-failover.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-chain-and-failover.latest.golden @@ -5,6 +5,23 @@ "@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~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,9 +68,6 @@ "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/fail" } ] } @@ -61,6 +75,64 @@ "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/fail" + } + ] + } + }, + "sni": "fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } } ], "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden index 3835d6c41f..58962bf83f 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,10 +69,120 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" } diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden index 6df7220982..58962bf83f 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,12 +69,6 @@ "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/dc2/svc/db" - }, - { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc3/svc/db" } ] } @@ -64,6 +76,122 @@ "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" + } + ] + } + }, + "sni": "db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + } + } } ], "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden index 3835d6c41f..58962bf83f 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,10 +69,120 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" } diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden index 6df7220982..58962bf83f 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden @@ -5,6 +5,24 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,12 +69,6 @@ "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/dc2/svc/db" - }, - { - "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc3/svc/db" } ] } @@ -64,6 +76,122 @@ "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc3/svc/db" + } + ] + } + }, + "sni": "db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul" + } + } } ], "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden index e175f5bc8a..787f0d621b 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,7 +68,62 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" } diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden index 7ac10864a8..787f0d621b 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,9 +68,6 @@ "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/dc2/svc/db" } ] } @@ -61,6 +75,64 @@ "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } } ], "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden index e175f5bc8a..787f0d621b 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,7 +68,62 @@ "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": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" } diff --git a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden index 7ac10864a8..787f0d621b 100644 --- a/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden @@ -5,6 +5,23 @@ "@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.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + ] + } + }, + "connectTimeout": "33s", + "lbPolicy": "CLUSTER_PROVIDED" + }, + { + "@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": { @@ -51,9 +68,6 @@ "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/dc2/svc/db" } ] } @@ -61,6 +75,64 @@ "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "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/dc2/svc/db" + } + ] + } + }, + "sni": "db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul" + } + } } ], "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", diff --git a/agent/xds/testdata/clusters/transparent-proxy-destination-http.latest.golden b/agent/xds/testdata/clusters/transparent-proxy-destination-http.latest.golden new file mode 100644 index 0000000000..e3c9f59ff0 --- /dev/null +++ b/agent/xds/testdata/clusters/transparent-proxy-destination-http.latest.golden @@ -0,0 +1,365 @@ +{ + "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": "destination.192-168-2-1.kafka.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "destination.192-168-2-1.kafka.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "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/kafka" + } + ] + } + }, + "sni": "destination.192-168-2-1.kafka.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "destination.192-168-2-2.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "destination.192-168-2-2.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "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/kafka2" + } + ] + } + }, + "sni": "destination.192-168-2-2.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "destination.192-168-2-3.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "destination.192-168-2-3.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "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/kafka2" + } + ] + } + }, + "sni": "destination.192-168-2-3.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "destination.www-google-com.google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "destination.www-google-com.google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "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/google" + } + ] + } + }, + "sni": "destination.www-google-com.google.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/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden b/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden index 0b298c9906..58fa4e7e8f 100644 --- a/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden +++ b/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden @@ -138,6 +138,7 @@ ] }, "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", "outlierDetection": { } @@ -167,6 +168,7 @@ ] }, "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", "outlierDetection": { }, @@ -218,6 +220,7 @@ ] }, "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", "outlierDetection": { } @@ -247,6 +250,7 @@ ] }, "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", "outlierDetection": { } diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover.latest.golden index 86e7209e76..c3e5de8075 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -32,7 +32,13 @@ "loadBalancingWeight": 1 } ] - }, + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ { "lbEndpoints": [ { @@ -59,13 +65,9 @@ "healthStatus": "HEALTHY", "loadBalancingWeight": 1 } - ], - "priority": 1 + ] } - ], - "policy": { - "overprovisioningFactor": 100000 - } + ] }, { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden index 57bbeada33..d8c936d61b 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden @@ -3,7 +3,75 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden index 6e4d37bc32..40ae7a0897 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-local-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -35,6 +35,40 @@ } ] }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8443 + } + } + }, + "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", diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden index 2cc64bbba8..356b5b8476 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden @@ -3,7 +3,75 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.1", + "portValue": 443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.2", + "portValue": 443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden index 6e4d37bc32..1f88cfbb3f 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-double-failover-through-remote-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -35,6 +35,40 @@ } ] }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.38.1.1", + "portValue": 443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.38.1.2", + "portValue": 443 + } + } + }, + "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", diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden index 57bbeada33..dd6083fa24 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden @@ -3,7 +3,41 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden index 6e4d37bc32..5e45cbd43e 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-local-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -35,6 +35,40 @@ } ] }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8443 + } + } + }, + "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", diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden index 554520f7e8..6a840c993d 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden @@ -3,7 +3,41 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden index 6e4d37bc32..29f1a8c59c 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-tcp-chain-failover-through-remote-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -35,6 +35,40 @@ } ] }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.1", + "portValue": 443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.2", + "portValue": 443 + } + } + }, + "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", diff --git a/agent/xds/testdata/endpoints/ingress-with-chain-and-failover.latest.golden b/agent/xds/testdata/endpoints/ingress-with-chain-and-failover.latest.golden index e0685f1ae9..1ef4d0329b 100644 --- a/agent/xds/testdata/endpoints/ingress-with-chain-and-failover.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-chain-and-failover.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -32,7 +32,13 @@ "loadBalancingWeight": 1 } ] - }, + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ { "lbEndpoints": [ { @@ -59,13 +65,9 @@ "healthStatus": "HEALTHY", "loadBalancingWeight": 1 } - ], - "priority": 1 + ] } - ], - "policy": { - "overprovisioningFactor": 100000 - } + ] } ], "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden index 61392962ec..f38f82acfb 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway-triggered.latest.golden @@ -3,7 +3,75 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden index ff38886424..b55f545868 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-local-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -34,6 +34,40 @@ ] } ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] } ], "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden index 961d515a1a..9d5d1c1faf 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway-triggered.latest.golden @@ -3,7 +3,75 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.1", + "portValue": 443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.2", + "portValue": 443 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden index ff38886424..ebbf0281e1 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-double-failover-through-remote-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -34,6 +34,40 @@ ] } ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc3.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.38.1.1", + "portValue": 443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.38.1.2", + "portValue": 443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] } ], "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden index 61392962ec..0aad49182a 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway-triggered.latest.golden @@ -3,7 +3,41 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden index ff38886424..c5ee31d458 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-local-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -34,6 +34,40 @@ ] } ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] } ], "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden index d808b212f0..324a48430d 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway-triggered.latest.golden @@ -3,7 +3,41 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ diff --git a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden index ff38886424..2021f8fab5 100644 --- a/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-tcp-chain-failover-through-remote-gateway.latest.golden @@ -3,7 +3,7 @@ "resources": [ { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", - "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "clusterName": "failover-target~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "endpoints": [ { "lbEndpoints": [ @@ -34,6 +34,40 @@ ] } ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "failover-target~db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.1", + "portValue": 443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "198.18.1.2", + "portValue": 443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] } ], "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", diff --git a/agent/xds/testdata/endpoints/transparent-proxy-destination-http.latest.golden b/agent/xds/testdata/endpoints/transparent-proxy-destination-http.latest.golden new file mode 100644 index 0000000000..284d7722a6 --- /dev/null +++ b/agent/xds/testdata/endpoints/transparent-proxy-destination-http.latest.golden @@ -0,0 +1,163 @@ +{ + "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": "destination.192-168-2-1.kafka.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "172.168.0.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "destination.192-168-2-2.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "172.168.0.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "destination.192-168-2-3.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "172.168.0.1", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "destination.www-google-com.google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "172.168.0.1", + "portValue": 8443 + } + } + }, + "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/listeners/listener-max-inbound-connections.latest.golden b/agent/xds/testdata/listeners/listener-max-inbound-connections.latest.golden index be3b83433a..cbfda69f56 100644 --- a/agent/xds/testdata/listeners/listener-max-inbound-connections.latest.golden +++ b/agent/xds/testdata/listeners/listener-max-inbound-connections.latest.golden @@ -73,6 +73,14 @@ "statPrefix": "connect_authz" } }, + { + "name": "envoy.filters.network.connection_limit", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.connection_limit.v3.ConnectionLimit", + "statPrefix": "inbound_connection_limit", + "maxConnections": "222" + } + }, { "name": "envoy.filters.network.tcp_proxy", "typedConfig": { @@ -80,13 +88,6 @@ "statPrefix": "public_listener", "cluster": "local_app" } - }, - { - "name": "envoy.filters.network.connection_limit", - "typedConfig": { - "@type": "type.googleapis.com/envoy.extensions.filters.network.connection_limit.v3.ConnectionLimit", - "maxConnections": "222" - } } ], "transportSocket": { diff --git a/agent/xds/testdata/listeners/transparent-proxy-destination-http.latest.golden b/agent/xds/testdata/listeners/transparent-proxy-destination-http.latest.golden new file mode 100644 index 0000000000..b621f19ddc --- /dev/null +++ b/agent/xds/testdata/listeners/transparent-proxy-destination-http.latest.golden @@ -0,0 +1,218 @@ +{ + "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": "outbound_listener:127.0.0.1:15001", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 15001 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "destinationPort": 443 + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "upstream.destination.443.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "destination.443.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + }, + { + "filterChainMatch": { + "destinationPort": 9093 + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "upstream.destination.9093.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "destination.9093.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.original_dst", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst" + } + }, + { + "name": "envoy.filters.listener.http_inspector", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector" + } + } + ], + "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/transparent-proxy-destination-http.latest.golden b/agent/xds/testdata/routes/transparent-proxy-destination-http.latest.golden new file mode 100644 index 0000000000..173aa56476 --- /dev/null +++ b/agent/xds/testdata/routes/transparent-proxy-destination-http.latest.golden @@ -0,0 +1,85 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "destination.443.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "virtualHosts": [ + { + "name": "destination.www-google-com.google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "domains": [ + "www.google.com" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "destination.www-google-com.google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + }, + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "destination.9093.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "virtualHosts": [ + { + "name": "destination.192-168-2-3.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "domains": [ + "192.168.2.3" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "destination.192-168-2-3.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "name": "destination.192-168-2-2.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "domains": [ + "192.168.2.2" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "destination.192-168-2-2.kafka2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "name": "destination.192-168-2-1.kafka.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "domains": [ + "192.168.2.1" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "destination.192-168-2-1.kafka.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/api/config_entry.go b/api/config_entry.go index ee55b55ad2..da685b7867 100644 --- a/api/config_entry.go +++ b/api/config_entry.go @@ -218,21 +218,22 @@ type UpstreamLimits struct { } type ServiceConfigEntry struct { - Kind string - Name string - Partition string `json:",omitempty"` - Namespace string `json:",omitempty"` - Protocol string `json:",omitempty"` - Mode ProxyMode `json:",omitempty"` - TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` - MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` - Expose ExposeConfig `json:",omitempty"` - ExternalSNI string `json:",omitempty" alias:"external_sni"` - UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"` - Destination *DestinationConfig `json:",omitempty"` - Meta map[string]string `json:",omitempty"` - CreateIndex uint64 - ModifyIndex uint64 + Kind string + Name string + Partition string `json:",omitempty"` + Namespace string `json:",omitempty"` + Protocol string `json:",omitempty"` + Mode ProxyMode `json:",omitempty"` + TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` + MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` + Expose ExposeConfig `json:",omitempty"` + ExternalSNI string `json:",omitempty" alias:"external_sni"` + UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"` + Destination *DestinationConfig `json:",omitempty"` + MaxInboundConnections int `json:",omitempty" alias:"max_inbound_connections"` + Meta map[string]string `json:",omitempty"` + CreateIndex uint64 + ModifyIndex uint64 } func (s *ServiceConfigEntry) GetKind() string { return s.Kind } diff --git a/api/config_entry_discoverychain.go b/api/config_entry_discoverychain.go index dfb2bcc101..fb22baabdb 100644 --- a/api/config_entry_discoverychain.go +++ b/api/config_entry_discoverychain.go @@ -225,8 +225,18 @@ type ServiceResolverFailover struct { Service string `json:",omitempty"` ServiceSubset string `json:",omitempty" alias:"service_subset"` // Referencing other partitions is not supported. - Namespace string `json:",omitempty"` - Datacenters []string `json:",omitempty"` + Namespace string `json:",omitempty"` + Datacenters []string `json:",omitempty"` + Targets []ServiceResolverFailoverTarget `json:",omitempty"` +} + +type ServiceResolverFailoverTarget struct { + Service string `json:",omitempty"` + ServiceSubset string `json:",omitempty" alias:"service_subset"` + Partition string `json:",omitempty"` + Namespace string `json:",omitempty"` + Datacenter string `json:",omitempty"` + Peer string `json:",omitempty"` } // LoadBalancer determines the load balancing policy and configuration for services diff --git a/api/config_entry_discoverychain_test.go b/api/config_entry_discoverychain_test.go index 95dc542805..672aae1af5 100644 --- a/api/config_entry_discoverychain_test.go +++ b/api/config_entry_discoverychain_test.go @@ -149,6 +149,9 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) { "v2": { Filter: "Service.Meta.version == v2", }, + "v3": { + Filter: "Service.Meta.version == v3", + }, }, Failover: map[string]ServiceResolverFailover{ "*": { @@ -158,6 +161,13 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) { Service: "alternate", Namespace: splitDefaultNamespace, }, + "v3": { + Targets: []ServiceResolverFailoverTarget{ + {Peer: "cluster-01"}, + {Datacenter: "dc1"}, + {Service: "another-service", ServiceSubset: "v1"}, + }, + }, }, ConnectTimeout: 5 * time.Second, Meta: map[string]string{ diff --git a/api/config_entry_test.go b/api/config_entry_test.go index 4249e75471..63aba11b8a 100644 --- a/api/config_entry_test.go +++ b/api/config_entry_test.go @@ -104,6 +104,7 @@ func TestAPI_ConfigEntries(t *testing.T) { "foo": "bar", "gir": "zim", }, + MaxInboundConnections: 5, } dest := &DestinationConfig{ @@ -144,6 +145,7 @@ func TestAPI_ConfigEntries(t *testing.T) { require.Equal(t, service.Protocol, readService.Protocol) require.Equal(t, service.Meta, readService.Meta) require.Equal(t, service.Meta, readService.GetMeta()) + require.Equal(t, service.MaxInboundConnections, readService.MaxInboundConnections) // update it service.Protocol = "tcp" diff --git a/command/agent/agent.go b/command/agent/agent.go index a69e630711..cc08213e1c 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -201,24 +201,29 @@ func (c *cmd) run(args []string) int { if config.ServerMode { segment = "" } - ui.Info(fmt.Sprintf(" Version: '%s'", c.versionHuman)) + ui.Info(fmt.Sprintf(" Version: '%s'", c.versionHuman)) if strings.Contains(c.versionHuman, "dev") { - ui.Info(fmt.Sprintf(" Revision: '%s'", c.revision)) + ui.Info(fmt.Sprintf(" Revision: '%s'", c.revision)) } - ui.Info(fmt.Sprintf(" Build Date: '%s'", c.buildDate)) - ui.Info(fmt.Sprintf(" Node ID: '%s'", config.NodeID)) - ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName)) + ui.Info(fmt.Sprintf(" Build Date: '%s'", c.buildDate)) + ui.Info(fmt.Sprintf(" Node ID: '%s'", config.NodeID)) + ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName)) if ap := config.PartitionOrEmpty(); ap != "" { - ui.Info(fmt.Sprintf(" Partition: '%s'", ap)) + ui.Info(fmt.Sprintf(" Partition: '%s'", ap)) } - ui.Info(fmt.Sprintf(" Datacenter: '%s' (Segment: '%s')", config.Datacenter, segment)) - ui.Info(fmt.Sprintf(" Server: %v (Bootstrap: %v)", config.ServerMode, config.Bootstrap)) - ui.Info(fmt.Sprintf(" Client Addr: %v (HTTP: %d, HTTPS: %d, gRPC: %d, DNS: %d)", config.ClientAddrs, + ui.Info(fmt.Sprintf(" Datacenter: '%s' (Segment: '%s')", config.Datacenter, segment)) + ui.Info(fmt.Sprintf(" Server: %v (Bootstrap: %v)", config.ServerMode, config.Bootstrap)) + ui.Info(fmt.Sprintf(" Client Addr: %v (HTTP: %d, HTTPS: %d, gRPC: %d, DNS: %d)", config.ClientAddrs, config.HTTPPort, config.HTTPSPort, config.GRPCPort, config.DNSPort)) - ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddrLAN, + ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddrLAN, config.SerfPortLAN, config.SerfPortWAN)) - ui.Info(fmt.Sprintf(" Encrypt: Gossip: %v, TLS-Outgoing: %v, TLS-Incoming: %v, Auto-Encrypt-TLS: %t", - config.EncryptKey != "", config.TLS.InternalRPC.VerifyOutgoing, config.TLS.InternalRPC.VerifyIncoming, config.AutoEncryptTLS || config.AutoEncryptAllowTLS)) + ui.Info(fmt.Sprintf("Gossip Encryption: %t", config.EncryptKey != "")) + ui.Info(fmt.Sprintf(" Auto-Encrypt-TLS: %t", config.AutoEncryptTLS || config.AutoEncryptAllowTLS)) + ui.Info(fmt.Sprintf(" HTTPS TLS: Verify Incoming: %t, Verify Outgoing: %t, Min Version: %s", + config.TLS.HTTPS.VerifyIncoming, config.TLS.HTTPS.VerifyOutgoing, config.TLS.HTTPS.TLSMinVersion)) + ui.Info(fmt.Sprintf(" gRPC TLS: Verify Incoming: %t, Min Version: %s", config.TLS.GRPC.VerifyIncoming, config.TLS.GRPC.TLSMinVersion)) + ui.Info(fmt.Sprintf(" Internal RPC TLS: Verify Incoming: %t, Verify Outgoing: %t (Verify Hostname: %t), Min Version: %s", + config.TLS.InternalRPC.VerifyIncoming, config.TLS.InternalRPC.VerifyOutgoing, config.TLS.InternalRPC.VerifyServerHostname, config.TLS.InternalRPC.TLSMinVersion)) // Enable log streaming ui.Output("") ui.Output("Log data will now stream in as it occurs:\n") diff --git a/command/connect/proxy/proxy.go b/command/connect/proxy/proxy.go index d2d0b90cf1..a0477a6a10 100644 --- a/command/connect/proxy/proxy.go +++ b/command/connect/proxy/proxy.go @@ -232,7 +232,7 @@ func LookupProxyIDForSidecar(client *api.Client, sidecarFor string) (string, err var proxyIDs []string for _, svc := range svcs { if svc.Kind == api.ServiceKindConnectProxy && svc.Proxy != nil && - strings.ToLower(svc.Proxy.DestinationServiceID) == sidecarFor { + strings.EqualFold(svc.Proxy.DestinationServiceID, sidecarFor) { proxyIDs = append(proxyIDs, svc.ID) } } diff --git a/command/connect/proxy/proxy_test.go b/command/connect/proxy/proxy_test.go index ae7b1cdfbc..28d5a9da21 100644 --- a/command/connect/proxy/proxy_test.go +++ b/command/connect/proxy/proxy_test.go @@ -110,6 +110,17 @@ func TestCommandConfigWatcher(t *testing.T) { require.Equal(t, 9999, cfg.PublicListener.BindPort) }, }, + + { + Name: "-sidecar-for, one sidecar case-insensitive", + Flags: []string{ + "-sidecar-for", "One-SideCar", + }, + Test: func(t *testing.T, cfg *proxy.Config) { + // Sanity check we got the right instance. + require.Equal(t, 9999, cfg.PublicListener.BindPort) + }, + }, } for _, tc := range cases { diff --git a/command/kv/get/kv_get.go b/command/kv/get/kv_get.go index 099aedb9fc..aa93ef963b 100644 --- a/command/kv/get/kv_get.go +++ b/command/kv/get/kv_get.go @@ -99,6 +99,32 @@ func (c *cmd) Run(args []string) int { } switch { + case c.keys && c.recurse: + pairs, _, err := client.KV().List(key, &api.QueryOptions{ + AllowStale: c.http.Stale(), + }) + if err != nil { + c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) + return 1 + } + + for i, pair := range pairs { + if c.detailed { + var b bytes.Buffer + if err := prettyKVPair(&b, pair, false, true); err != nil { + c.UI.Error(fmt.Sprintf("Error rendering KV key: %s", err)) + return 1 + } + c.UI.Info(b.String()) + + if i < len(pairs)-1 { + c.UI.Info("") + } + } else { + c.UI.Info(fmt.Sprintf("%s", pair.Key)) + } + } + return 0 case c.keys: keys, _, err := client.KV().Keys(key, c.separator, &api.QueryOptions{ AllowStale: c.http.Stale(), @@ -125,7 +151,7 @@ func (c *cmd) Run(args []string) int { for i, pair := range pairs { if c.detailed { var b bytes.Buffer - if err := prettyKVPair(&b, pair, c.base64encode); err != nil { + if err := prettyKVPair(&b, pair, c.base64encode, false); err != nil { c.UI.Error(fmt.Sprintf("Error rendering KV pair: %s", err)) return 1 } @@ -161,7 +187,7 @@ func (c *cmd) Run(args []string) int { if c.detailed { var b bytes.Buffer - if err := prettyKVPair(&b, pair, c.base64encode); err != nil { + if err := prettyKVPair(&b, pair, c.base64encode, false); err != nil { c.UI.Error(fmt.Sprintf("Error rendering KV pair: %s", err)) return 1 } @@ -187,7 +213,7 @@ func (c *cmd) Help() string { return c.help } -func prettyKVPair(w io.Writer, pair *api.KVPair, base64EncodeValue bool) error { +func prettyKVPair(w io.Writer, pair *api.KVPair, base64EncodeValue bool, keysOnly bool) error { tw := tabwriter.NewWriter(w, 0, 2, 6, ' ', 0) fmt.Fprintf(tw, "CreateIndex\t%d\n", pair.CreateIndex) fmt.Fprintf(tw, "Flags\t%d\n", pair.Flags) @@ -205,9 +231,9 @@ func prettyKVPair(w io.Writer, pair *api.KVPair, base64EncodeValue bool) error { if pair.Namespace != "" { fmt.Fprintf(tw, "Namespace\t%s\n", pair.Namespace) } - if base64EncodeValue { + if !keysOnly && base64EncodeValue { fmt.Fprintf(tw, "Value\t%s", base64.StdEncoding.EncodeToString(pair.Value)) - } else { + } else if !keysOnly { fmt.Fprintf(tw, "Value\t%s", pair.Value) } return tw.Flush() diff --git a/command/kv/get/kv_get_test.go b/command/kv/get/kv_get_test.go index 3a7b12d8a7..5143391ef0 100644 --- a/command/kv/get/kv_get_test.go +++ b/command/kv/get/kv_get_test.go @@ -418,3 +418,102 @@ func TestKVGetCommand_DetailedBase64(t *testing.T) { t.Fatalf("bad %#v, value is not base64 encoded", output) } } + +func TestKVGetCommand_KeysRecurse(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := agent.NewTestAgent(t, ``) + defer a.Shutdown() + client := a.Client() + + ui := cli.NewMockUi() + c := New(ui) + keys := map[string]string{ + "foo/": "", + "foo/a": "Hello World 2", + "foo1/a": "Hello World 1", + } + for k, v := range keys { + var pair *api.KVPair + switch v { + case "": + pair = &api.KVPair{Key: k, Value: nil} + default: + pair = &api.KVPair{Key: k, Value: []byte(v)} + } + if _, err := client.KV().Put(pair, nil); err != nil { + t.Fatalf("err: %#v", err) + } + } + args := []string{ + "-http-addr=" + a.HTTPAddr(), + "-recurse", + "-keys", + "foo", + } + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + output := ui.OutputWriter.String() + for key, value := range keys { + if !strings.Contains(output, key) { + t.Fatalf("bad %#v missing %q", output, key) + } + if strings.Contains(output, key+":"+value) { + t.Fatalf("bad %#v expected no values for keys %q but received %q", output, key, value) + } + } +} +func TestKVGetCommand_DetailedKeysRecurse(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := agent.NewTestAgent(t, ``) + defer a.Shutdown() + client := a.Client() + + ui := cli.NewMockUi() + c := New(ui) + keys := map[string]string{ + "foo/": "", + "foo/a": "Hello World 2", + "foo1/a": "Hello World 1", + } + for k, v := range keys { + var pair *api.KVPair + switch v { + case "": + pair = &api.KVPair{Key: k, Value: nil} + default: + pair = &api.KVPair{Key: k, Value: []byte(v)} + } + if _, err := client.KV().Put(pair, nil); err != nil { + t.Fatalf("err: %#v", err) + } + } + args := []string{ + "-http-addr=" + a.HTTPAddr(), + "-recurse", + "-keys", + "-detailed", + "foo", + } + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + output := ui.OutputWriter.String() + for key, value := range keys { + if value != "" && strings.Contains(output, value) { + t.Fatalf("bad %#v expected no values for keys %q but received %q", output, key, value) + } + } +} diff --git a/command/services/deregister/deregister.go b/command/services/deregister/deregister.go index 6c28347c07..3f352b36a6 100644 --- a/command/services/deregister/deregister.go +++ b/command/services/deregister/deregister.go @@ -81,7 +81,7 @@ func (c *cmd) Run(args []string) int { } if err := client.Agent().ServiceDeregister(id); err != nil { - c.UI.Error(fmt.Sprintf("Error registering service %q: %s", + c.UI.Error(fmt.Sprintf("Error deregistering service %q: %s", svc.Name, err)) return 1 } diff --git a/connect/proxy/config_test.go b/connect/proxy/config_test.go index ddef16fb94..b93f4d779d 100644 --- a/connect/proxy/config_test.go +++ b/connect/proxy/config_test.go @@ -4,12 +4,14 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/connect" "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/sdk/testutil/retry" ) func TestUpstreamResolverFuncFromClient(t *testing.T) { @@ -154,9 +156,9 @@ func TestAgentConfigWatcherSidecarProxy(t *testing.T) { reg.Connect.SidecarService.Proxy.Config["local_connect_timeout_ms"] = 444 require.NoError(t, agent.ServiceRegister(reg)) - cfg = testGetConfigValTimeout(t, w, 2*time.Second) - - expectCfg.Upstreams = append(expectCfg.Upstreams, UpstreamConfig{ + updatedCfg := new(Config) + *updatedCfg = *expectCfg + updatedCfg.Upstreams = append(updatedCfg.Upstreams, UpstreamConfig{ DestinationName: "cache", DestinationNamespace: "default", DestinationPartition: "default", @@ -164,12 +166,25 @@ func TestAgentConfigWatcherSidecarProxy(t *testing.T) { LocalBindPort: 9292, LocalBindAddress: "127.10.10.10", }) - expectCfg.PublicListener.LocalConnectTimeoutMs = 444 + updatedCfg.PublicListener.LocalConnectTimeoutMs = 444 - require.Equal(t, expectCfg, cfg) + retry.Run(t, func(r *retry.R) { + cfg := testGetConfigValTimeout(r, w, 500*time.Millisecond) + // TODO: These are debug logs to show the diffs against updatedCfg and expectCfg. + // Once we figure out what event makes this test flake, we should adjust this test to be deterministic. + if !assert.Equal(r, updatedCfg, cfg, "expected config from watcher to match updated") { + assert.Equal(r, expectCfg, cfg, "config does not match original or updated config; something else must have fired watch") + r.FailNow() + } + }) } -func testGetConfigValTimeout(t *testing.T, w ConfigWatcher, +type testingT interface { + Helper() + Fatalf(format string, args ...interface{}) +} + +func testGetConfigValTimeout(t testingT, w ConfigWatcher, timeout time.Duration) *Config { t.Helper() select { diff --git a/go.mod b/go.mod index cb048763d6..1ade7d6de2 100644 --- a/go.mod +++ b/go.mod @@ -45,15 +45,15 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 - github.com/hashicorp/memberlist v0.3.1 + github.com/hashicorp/memberlist v0.4.0 github.com/hashicorp/raft v1.3.9 github.com/hashicorp/raft-autopilot v0.1.6 github.com/hashicorp/raft-boltdb/v2 v2.2.2 - github.com/hashicorp/serf v0.9.8 + github.com/hashicorp/serf v0.10.0 github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086 github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 - github.com/imdario/mergo v0.3.6 + github.com/imdario/mergo v0.3.13 github.com/kr/text v0.2.0 github.com/miekg/dns v1.1.41 github.com/mitchellh/cli v1.1.0 @@ -77,7 +77,7 @@ require ( golang.org/x/net v0.0.0-20211216030914-fe4d6282115f golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb google.golang.org/grpc v1.37.1 @@ -183,7 +183,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 // indirect sigs.k8s.io/structured-merge-diff/v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index 8f2afaa45d..cf7f2afc30 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,9 @@ github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.4.0 h1:k3uda5gZcltmafuFF+UFqNEl5PrH+yPZ4zkjp1f/H/8= +github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= @@ -380,8 +381,8 @@ github.com/hashicorp/raft-boltdb v0.0.0-20211202195631-7d34b9fb3f42/go.mod h1:wc 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/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/serf v0.9.8 h1:JGklO/2Drf1QGa312EieQN3zhxQ+aJg6pG+aC3MFaVo= -github.com/hashicorp/serf v0.9.8/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.10.0 h1:89qvvpfMQnz6c2y4pv7j2vUUmeT1+5TSZMexuTbtsPs= +github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086 h1:OKsyxKi2sNmqm1Gv93adf2AID2FOBFdCbbZn9fGtIdg= github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM= @@ -395,8 +396,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= @@ -793,8 +795,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -967,8 +970,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/proto-public/pbdataplane/dataplane.pb.go b/proto-public/pbdataplane/dataplane.pb.go index 1da1eea15a..8e8a1000f2 100644 --- a/proto-public/pbdataplane/dataplane.pb.go +++ b/proto-public/pbdataplane/dataplane.pb.go @@ -401,12 +401,17 @@ type GetEnvoyBootstrapParamsResponse struct { unknownFields protoimpl.UnknownFields ServiceKind ServiceKind `protobuf:"varint,1,opt,name=service_kind,json=serviceKind,proto3,enum=hashicorp.consul.dataplane.ServiceKind" json:"service_kind,omitempty"` - // The destination service name + // service is be used to identify the service (as the local cluster name and + // in metric tags). If the service is a connect proxy it will be the name of + // the proxy's destination service, for gateways it will be the gateway + // service's name. Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` Partition string `protobuf:"bytes,4,opt,name=partition,proto3" json:"partition,omitempty"` Datacenter string `protobuf:"bytes,5,opt,name=datacenter,proto3" json:"datacenter,omitempty"` Config *structpb.Struct `protobuf:"bytes,6,opt,name=config,proto3" json:"config,omitempty"` + NodeId string `protobuf:"bytes,7,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + NodeName string `protobuf:"bytes,8,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"` } func (x *GetEnvoyBootstrapParamsResponse) Reset() { @@ -483,6 +488,20 @@ func (x *GetEnvoyBootstrapParamsResponse) GetConfig() *structpb.Struct { return nil } +func (x *GetEnvoyBootstrapParamsResponse) GetNodeId() string { + if x != nil { + return x.NodeId + } + return "" +} + +func (x *GetEnvoyBootstrapParamsResponse) GetNodeName() string { + if x != nil { + return x.NodeName + } + return "" +} + var File_proto_public_pbdataplane_dataplane_proto protoreflect.FileDescriptor var file_proto_public_pbdataplane_dataplane_proto_rawDesc = []byte{ @@ -525,7 +544,7 @@ var file_proto_public_pbdataplane_dataplane_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x22, 0x94, + 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x22, 0xca, 0x02, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6b, 0x69, @@ -543,69 +562,73 @@ var file_proto_public_pbdataplane_dataplane_proto_rawDesc = []byte{ 0x6e, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2a, 0xc7, 0x01, 0x0a, 0x11, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, - 0x61, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x44, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x2a, 0xc7, 0x01, 0x0a, 0x11, + 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x44, 0x41, 0x54, 0x41, 0x50, 0x4c, 0x41, 0x4e, 0x45, 0x5f, 0x46, + 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x24, 0x0a, 0x20, 0x44, 0x41, 0x54, 0x41, 0x50, 0x4c, 0x41, + 0x4e, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x5f, 0x57, 0x41, 0x54, 0x43, + 0x48, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, 0x32, 0x0a, 0x2e, 0x44, 0x41, 0x54, 0x41, 0x50, 0x4c, 0x41, 0x4e, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, - 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x24, 0x0a, 0x20, 0x44, 0x41, 0x54, 0x41, 0x50, 0x4c, 0x41, 0x4e, 0x45, 0x5f, 0x46, 0x45, 0x41, - 0x54, 0x55, 0x52, 0x45, 0x53, 0x5f, 0x57, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x53, 0x45, 0x52, 0x56, - 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, 0x32, 0x0a, 0x2e, 0x44, 0x41, 0x54, 0x41, 0x50, 0x4c, 0x41, - 0x4e, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x5f, 0x45, 0x44, 0x47, 0x45, - 0x5f, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, - 0x41, 0x47, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x34, 0x0a, 0x30, 0x44, 0x41, 0x54, - 0x41, 0x50, 0x4c, 0x41, 0x4e, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x5f, - 0x45, 0x4e, 0x56, 0x4f, 0x59, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53, 0x54, 0x52, 0x41, 0x50, 0x5f, - 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, - 0xcc, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x12, - 0x1c, 0x0a, 0x18, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, - 0x14, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x54, 0x59, - 0x50, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x45, 0x52, 0x56, 0x49, - 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x5f, - 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x45, 0x52, 0x56, 0x49, - 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x45, 0x53, 0x48, 0x5f, 0x47, 0x41, 0x54, - 0x45, 0x57, 0x41, 0x59, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, - 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x54, 0x49, - 0x4e, 0x47, 0x5f, 0x47, 0x41, 0x54, 0x45, 0x57, 0x41, 0x59, 0x10, 0x04, 0x12, 0x20, 0x0a, 0x1c, - 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x47, - 0x52, 0x45, 0x53, 0x53, 0x5f, 0x47, 0x41, 0x54, 0x45, 0x57, 0x41, 0x59, 0x10, 0x05, 0x32, 0xd2, - 0x02, 0x0a, 0x10, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0xa6, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x44, - 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x94, 0x01, 0x0a, - 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, - 0x61, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x61, 0x74, 0x61, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x42, 0x6f, - 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, - 0x72, 0x61, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x42, 0xf0, 0x01, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x61, 0x74, - 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x42, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x34, 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, 0x2d, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x2f, 0x70, 0x62, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0xa2, 0x02, - 0x03, 0x48, 0x43, 0x44, 0xaa, 0x02, 0x1a, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0xca, 0x02, 0x1a, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0xe2, 0x02, - 0x26, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1c, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x44, 0x61, 0x74, - 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x53, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, 0x41, 0x47, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, + 0x34, 0x0a, 0x30, 0x44, 0x41, 0x54, 0x41, 0x50, 0x4c, 0x41, 0x4e, 0x45, 0x5f, 0x46, 0x45, 0x41, + 0x54, 0x55, 0x52, 0x45, 0x53, 0x5f, 0x45, 0x4e, 0x56, 0x4f, 0x59, 0x5f, 0x42, 0x4f, 0x4f, 0x54, + 0x53, 0x54, 0x52, 0x41, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0xcc, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, + 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, + 0x49, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1e, 0x0a, + 0x1a, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x02, 0x12, 0x1d, 0x0a, + 0x19, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x45, + 0x53, 0x48, 0x5f, 0x47, 0x41, 0x54, 0x45, 0x57, 0x41, 0x59, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, + 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x54, 0x45, 0x52, + 0x4d, 0x49, 0x4e, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x47, 0x41, 0x54, 0x45, 0x57, 0x41, 0x59, + 0x10, 0x04, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4b, 0x49, + 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x47, 0x41, 0x54, 0x45, 0x57, + 0x41, 0x59, 0x10, 0x05, 0x32, 0xd2, 0x02, 0x0a, 0x10, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa6, 0x01, 0x0a, 0x1d, 0x47, 0x65, + 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x40, 0x2e, 0x68, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x41, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, + 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x94, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x42, + 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x3a, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, + 0x6e, 0x76, 0x6f, 0x79, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x6f, 0x79, + 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xf0, 0x01, 0x0a, 0x1e, 0x63, 0x6f, + 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x42, 0x0e, 0x44, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x34, + 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, 0x2d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x70, 0x62, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0xa2, 0x02, 0x03, 0x48, 0x43, 0x44, 0xaa, 0x02, 0x1a, 0x48, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0xca, 0x02, 0x1a, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0xe2, 0x02, 0x26, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1c, + 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto-public/pbdataplane/dataplane.proto b/proto-public/pbdataplane/dataplane.proto index 0502dcd707..cc95f3a517 100644 --- a/proto-public/pbdataplane/dataplane.proto +++ b/proto-public/pbdataplane/dataplane.proto @@ -68,12 +68,17 @@ enum ServiceKind { message GetEnvoyBootstrapParamsResponse { ServiceKind service_kind = 1; - // The destination service name + // service is be used to identify the service (as the local cluster name and + // in metric tags). If the service is a connect proxy it will be the name of + // the proxy's destination service, for gateways it will be the gateway + // service's name. string service = 2; string namespace = 3; string partition = 4; string datacenter = 5; google.protobuf.Struct config = 6; + string node_id = 7; + string node_name = 8; } service DataplaneService { diff --git a/proto/pbconfigentry/config_entry.gen.go b/proto/pbconfigentry/config_entry.gen.go index 9f4d9fa75e..7c01387dfb 100644 --- a/proto/pbconfigentry/config_entry.gen.go +++ b/proto/pbconfigentry/config_entry.gen.go @@ -630,6 +630,14 @@ func ServiceResolverFailoverToStructs(s *ServiceResolverFailover, t *structs.Ser t.ServiceSubset = s.ServiceSubset t.Namespace = s.Namespace t.Datacenters = s.Datacenters + { + t.Targets = make([]structs.ServiceResolverFailoverTarget, len(s.Targets)) + for i := range s.Targets { + if s.Targets[i] != nil { + ServiceResolverFailoverTargetToStructs(s.Targets[i], &t.Targets[i]) + } + } + } } func ServiceResolverFailoverFromStructs(t *structs.ServiceResolverFailover, s *ServiceResolverFailover) { if s == nil { @@ -639,6 +647,38 @@ func ServiceResolverFailoverFromStructs(t *structs.ServiceResolverFailover, s *S s.ServiceSubset = t.ServiceSubset s.Namespace = t.Namespace s.Datacenters = t.Datacenters + { + s.Targets = make([]*ServiceResolverFailoverTarget, len(t.Targets)) + for i := range t.Targets { + { + var x ServiceResolverFailoverTarget + ServiceResolverFailoverTargetFromStructs(&t.Targets[i], &x) + s.Targets[i] = &x + } + } + } +} +func ServiceResolverFailoverTargetToStructs(s *ServiceResolverFailoverTarget, t *structs.ServiceResolverFailoverTarget) { + if s == nil { + return + } + t.Service = s.Service + t.ServiceSubset = s.ServiceSubset + t.Partition = s.Partition + t.Namespace = s.Namespace + t.Datacenter = s.Datacenter + t.Peer = s.Peer +} +func ServiceResolverFailoverTargetFromStructs(t *structs.ServiceResolverFailoverTarget, s *ServiceResolverFailoverTarget) { + if s == nil { + return + } + s.Service = t.Service + s.ServiceSubset = t.ServiceSubset + s.Partition = t.Partition + s.Namespace = t.Namespace + s.Datacenter = t.Datacenter + s.Peer = t.Peer } func ServiceResolverRedirectToStructs(s *ServiceResolverRedirect, t *structs.ServiceResolverRedirect) { if s == nil { diff --git a/proto/pbconfigentry/config_entry.pb.binary.go b/proto/pbconfigentry/config_entry.pb.binary.go index 5ea1657a5e..8425e31107 100644 --- a/proto/pbconfigentry/config_entry.pb.binary.go +++ b/proto/pbconfigentry/config_entry.pb.binary.go @@ -107,6 +107,16 @@ func (msg *ServiceResolverFailover) UnmarshalBinary(b []byte) error { return proto.Unmarshal(b, msg) } +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *ServiceResolverFailoverTarget) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *ServiceResolverFailoverTarget) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + // MarshalBinary implements encoding.BinaryMarshaler func (msg *LoadBalancer) MarshalBinary() ([]byte, error) { return proto.Marshal(msg) diff --git a/proto/pbconfigentry/config_entry.pb.go b/proto/pbconfigentry/config_entry.pb.go index 9a34e8248c..bfa51c8cc6 100644 --- a/proto/pbconfigentry/config_entry.pb.go +++ b/proto/pbconfigentry/config_entry.pb.go @@ -875,10 +875,11 @@ type ServiceResolverFailover struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Service string `protobuf:"bytes,1,opt,name=Service,proto3" json:"Service,omitempty"` - ServiceSubset string `protobuf:"bytes,2,opt,name=ServiceSubset,proto3" json:"ServiceSubset,omitempty"` - Namespace string `protobuf:"bytes,3,opt,name=Namespace,proto3" json:"Namespace,omitempty"` - Datacenters []string `protobuf:"bytes,4,rep,name=Datacenters,proto3" json:"Datacenters,omitempty"` + Service string `protobuf:"bytes,1,opt,name=Service,proto3" json:"Service,omitempty"` + ServiceSubset string `protobuf:"bytes,2,opt,name=ServiceSubset,proto3" json:"ServiceSubset,omitempty"` + Namespace string `protobuf:"bytes,3,opt,name=Namespace,proto3" json:"Namespace,omitempty"` + Datacenters []string `protobuf:"bytes,4,rep,name=Datacenters,proto3" json:"Datacenters,omitempty"` + Targets []*ServiceResolverFailoverTarget `protobuf:"bytes,5,rep,name=Targets,proto3" json:"Targets,omitempty"` } func (x *ServiceResolverFailover) Reset() { @@ -941,6 +942,105 @@ func (x *ServiceResolverFailover) GetDatacenters() []string { return nil } +func (x *ServiceResolverFailover) GetTargets() []*ServiceResolverFailoverTarget { + if x != nil { + return x.Targets + } + return nil +} + +// mog annotation: +// +// target=github.com/hashicorp/consul/agent/structs.ServiceResolverFailoverTarget +// output=config_entry.gen.go +// name=Structs +type ServiceResolverFailoverTarget struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service string `protobuf:"bytes,1,opt,name=Service,proto3" json:"Service,omitempty"` + ServiceSubset string `protobuf:"bytes,2,opt,name=ServiceSubset,proto3" json:"ServiceSubset,omitempty"` + Partition string `protobuf:"bytes,3,opt,name=Partition,proto3" json:"Partition,omitempty"` + Namespace string `protobuf:"bytes,4,opt,name=Namespace,proto3" json:"Namespace,omitempty"` + Datacenter string `protobuf:"bytes,5,opt,name=Datacenter,proto3" json:"Datacenter,omitempty"` + Peer string `protobuf:"bytes,6,opt,name=Peer,proto3" json:"Peer,omitempty"` +} + +func (x *ServiceResolverFailoverTarget) Reset() { + *x = ServiceResolverFailoverTarget{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServiceResolverFailoverTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceResolverFailoverTarget) ProtoMessage() {} + +func (x *ServiceResolverFailoverTarget) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServiceResolverFailoverTarget.ProtoReflect.Descriptor instead. +func (*ServiceResolverFailoverTarget) Descriptor() ([]byte, []int) { + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{10} +} + +func (x *ServiceResolverFailoverTarget) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *ServiceResolverFailoverTarget) GetServiceSubset() string { + if x != nil { + return x.ServiceSubset + } + return "" +} + +func (x *ServiceResolverFailoverTarget) GetPartition() string { + if x != nil { + return x.Partition + } + return "" +} + +func (x *ServiceResolverFailoverTarget) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *ServiceResolverFailoverTarget) GetDatacenter() string { + if x != nil { + return x.Datacenter + } + return "" +} + +func (x *ServiceResolverFailoverTarget) GetPeer() string { + if x != nil { + return x.Peer + } + return "" +} + // mog annotation: // // target=github.com/hashicorp/consul/agent/structs.LoadBalancer @@ -960,7 +1060,7 @@ type LoadBalancer struct { func (x *LoadBalancer) Reset() { *x = LoadBalancer{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[10] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -973,7 +1073,7 @@ func (x *LoadBalancer) String() string { func (*LoadBalancer) ProtoMessage() {} func (x *LoadBalancer) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[10] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -986,7 +1086,7 @@ func (x *LoadBalancer) ProtoReflect() protoreflect.Message { // Deprecated: Use LoadBalancer.ProtoReflect.Descriptor instead. func (*LoadBalancer) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{10} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{11} } func (x *LoadBalancer) GetPolicy() string { @@ -1034,7 +1134,7 @@ type RingHashConfig struct { func (x *RingHashConfig) Reset() { *x = RingHashConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[11] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1047,7 +1147,7 @@ func (x *RingHashConfig) String() string { func (*RingHashConfig) ProtoMessage() {} func (x *RingHashConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[11] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1060,7 +1160,7 @@ func (x *RingHashConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RingHashConfig.ProtoReflect.Descriptor instead. func (*RingHashConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{11} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{12} } func (x *RingHashConfig) GetMinimumRingSize() uint64 { @@ -1093,7 +1193,7 @@ type LeastRequestConfig struct { func (x *LeastRequestConfig) Reset() { *x = LeastRequestConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[12] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1106,7 +1206,7 @@ func (x *LeastRequestConfig) String() string { func (*LeastRequestConfig) ProtoMessage() {} func (x *LeastRequestConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[12] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1119,7 +1219,7 @@ func (x *LeastRequestConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use LeastRequestConfig.ProtoReflect.Descriptor instead. func (*LeastRequestConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{12} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{13} } func (x *LeastRequestConfig) GetChoiceCount() uint32 { @@ -1149,7 +1249,7 @@ type HashPolicy struct { func (x *HashPolicy) Reset() { *x = HashPolicy{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[13] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1162,7 +1262,7 @@ func (x *HashPolicy) String() string { func (*HashPolicy) ProtoMessage() {} func (x *HashPolicy) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[13] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1175,7 +1275,7 @@ func (x *HashPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use HashPolicy.ProtoReflect.Descriptor instead. func (*HashPolicy) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{13} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{14} } func (x *HashPolicy) GetField() string { @@ -1232,7 +1332,7 @@ type CookieConfig struct { func (x *CookieConfig) Reset() { *x = CookieConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[14] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1245,7 +1345,7 @@ func (x *CookieConfig) String() string { func (*CookieConfig) ProtoMessage() {} func (x *CookieConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[14] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1258,7 +1358,7 @@ func (x *CookieConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CookieConfig.ProtoReflect.Descriptor instead. func (*CookieConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{14} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{15} } func (x *CookieConfig) GetSession() bool { @@ -1301,7 +1401,7 @@ type IngressGateway struct { func (x *IngressGateway) Reset() { *x = IngressGateway{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[15] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1314,7 +1414,7 @@ func (x *IngressGateway) String() string { func (*IngressGateway) ProtoMessage() {} func (x *IngressGateway) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[15] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1327,7 +1427,7 @@ func (x *IngressGateway) ProtoReflect() protoreflect.Message { // Deprecated: Use IngressGateway.ProtoReflect.Descriptor instead. func (*IngressGateway) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{15} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{16} } func (x *IngressGateway) GetTLS() *GatewayTLSConfig { @@ -1374,7 +1474,7 @@ type GatewayTLSConfig struct { func (x *GatewayTLSConfig) Reset() { *x = GatewayTLSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[16] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1387,7 +1487,7 @@ func (x *GatewayTLSConfig) String() string { func (*GatewayTLSConfig) ProtoMessage() {} func (x *GatewayTLSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[16] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1400,7 +1500,7 @@ func (x *GatewayTLSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayTLSConfig.ProtoReflect.Descriptor instead. func (*GatewayTLSConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{16} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{17} } func (x *GatewayTLSConfig) GetEnabled() bool { @@ -1455,7 +1555,7 @@ type GatewayTLSSDSConfig struct { func (x *GatewayTLSSDSConfig) Reset() { *x = GatewayTLSSDSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[17] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1468,7 +1568,7 @@ func (x *GatewayTLSSDSConfig) String() string { func (*GatewayTLSSDSConfig) ProtoMessage() {} func (x *GatewayTLSSDSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[17] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1481,7 +1581,7 @@ func (x *GatewayTLSSDSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayTLSSDSConfig.ProtoReflect.Descriptor instead. func (*GatewayTLSSDSConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{17} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{18} } func (x *GatewayTLSSDSConfig) GetClusterName() string { @@ -1518,7 +1618,7 @@ type IngressListener struct { func (x *IngressListener) Reset() { *x = IngressListener{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1531,7 +1631,7 @@ func (x *IngressListener) String() string { func (*IngressListener) ProtoMessage() {} func (x *IngressListener) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1544,7 +1644,7 @@ func (x *IngressListener) ProtoReflect() protoreflect.Message { // Deprecated: Use IngressListener.ProtoReflect.Descriptor instead. func (*IngressListener) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{18} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{19} } func (x *IngressListener) GetPort() int32 { @@ -1598,7 +1698,7 @@ type IngressService struct { func (x *IngressService) Reset() { *x = IngressService{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1611,7 +1711,7 @@ func (x *IngressService) String() string { func (*IngressService) ProtoMessage() {} func (x *IngressService) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1624,7 +1724,7 @@ func (x *IngressService) ProtoReflect() protoreflect.Message { // Deprecated: Use IngressService.ProtoReflect.Descriptor instead. func (*IngressService) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{19} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{20} } func (x *IngressService) GetName() string { @@ -1692,7 +1792,7 @@ type GatewayServiceTLSConfig struct { func (x *GatewayServiceTLSConfig) Reset() { *x = GatewayServiceTLSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1705,7 +1805,7 @@ func (x *GatewayServiceTLSConfig) String() string { func (*GatewayServiceTLSConfig) ProtoMessage() {} func (x *GatewayServiceTLSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1718,7 +1818,7 @@ func (x *GatewayServiceTLSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayServiceTLSConfig.ProtoReflect.Descriptor instead. func (*GatewayServiceTLSConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{20} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{21} } func (x *GatewayServiceTLSConfig) GetSDS() *GatewayTLSSDSConfig { @@ -1746,7 +1846,7 @@ type HTTPHeaderModifiers struct { func (x *HTTPHeaderModifiers) Reset() { *x = HTTPHeaderModifiers{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1759,7 +1859,7 @@ func (x *HTTPHeaderModifiers) String() string { func (*HTTPHeaderModifiers) ProtoMessage() {} func (x *HTTPHeaderModifiers) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1772,7 +1872,7 @@ func (x *HTTPHeaderModifiers) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPHeaderModifiers.ProtoReflect.Descriptor instead. func (*HTTPHeaderModifiers) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{21} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{22} } func (x *HTTPHeaderModifiers) GetAdd() map[string]string { @@ -1814,7 +1914,7 @@ type ServiceIntentions struct { func (x *ServiceIntentions) Reset() { *x = ServiceIntentions{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1827,7 +1927,7 @@ func (x *ServiceIntentions) String() string { func (*ServiceIntentions) ProtoMessage() {} func (x *ServiceIntentions) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1840,7 +1940,7 @@ func (x *ServiceIntentions) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceIntentions.ProtoReflect.Descriptor instead. func (*ServiceIntentions) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{22} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{23} } func (x *ServiceIntentions) GetSources() []*SourceIntention { @@ -1890,7 +1990,7 @@ type SourceIntention struct { func (x *SourceIntention) Reset() { *x = SourceIntention{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1903,7 +2003,7 @@ func (x *SourceIntention) String() string { func (*SourceIntention) ProtoMessage() {} func (x *SourceIntention) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1916,7 +2016,7 @@ func (x *SourceIntention) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceIntention.ProtoReflect.Descriptor instead. func (*SourceIntention) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{23} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{24} } func (x *SourceIntention) GetName() string { @@ -2021,7 +2121,7 @@ type IntentionPermission struct { func (x *IntentionPermission) Reset() { *x = IntentionPermission{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2034,7 +2134,7 @@ func (x *IntentionPermission) String() string { func (*IntentionPermission) ProtoMessage() {} func (x *IntentionPermission) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2047,7 +2147,7 @@ func (x *IntentionPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use IntentionPermission.ProtoReflect.Descriptor instead. func (*IntentionPermission) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{24} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{25} } func (x *IntentionPermission) GetAction() IntentionAction { @@ -2084,7 +2184,7 @@ type IntentionHTTPPermission struct { func (x *IntentionHTTPPermission) Reset() { *x = IntentionHTTPPermission{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2097,7 +2197,7 @@ func (x *IntentionHTTPPermission) String() string { func (*IntentionHTTPPermission) ProtoMessage() {} func (x *IntentionHTTPPermission) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2110,7 +2210,7 @@ func (x *IntentionHTTPPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use IntentionHTTPPermission.ProtoReflect.Descriptor instead. func (*IntentionHTTPPermission) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{25} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{26} } func (x *IntentionHTTPPermission) GetPathExact() string { @@ -2170,7 +2270,7 @@ type IntentionHTTPHeaderPermission struct { func (x *IntentionHTTPHeaderPermission) Reset() { *x = IntentionHTTPHeaderPermission{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2183,7 +2283,7 @@ func (x *IntentionHTTPHeaderPermission) String() string { func (*IntentionHTTPHeaderPermission) ProtoMessage() {} func (x *IntentionHTTPHeaderPermission) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2196,7 +2296,7 @@ func (x *IntentionHTTPHeaderPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use IntentionHTTPHeaderPermission.ProtoReflect.Descriptor instead. func (*IntentionHTTPHeaderPermission) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{26} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{27} } func (x *IntentionHTTPHeaderPermission) GetName() string { @@ -2433,7 +2533,7 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 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, 0x22, 0x99, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x6e, 0x74, 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, @@ -2443,312 +2543,331 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 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, - 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, + 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, 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, + 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, 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, 0xbf, 0x02, 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, 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, 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, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x22, 0xbe, 0x04, 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, 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, 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, + 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, 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, + 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, 0xbf, 0x02, 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, 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, + 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, 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, 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, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, 0x22, 0xbe, 0x04, 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, 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, + 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, 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, 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, + 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, 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, + 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, 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, 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, + 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, 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, + 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, - 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, 0x2a, 0x77, 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, 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, 0x42, 0xa6, 0x02, 0x0a, 0x29, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, + 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, 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, 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, + 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, 0x2a, 0x77, 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, 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, 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 ( @@ -2764,7 +2883,7 @@ func file_proto_pbconfigentry_config_entry_proto_rawDescGZIP() []byte { } var file_proto_pbconfigentry_config_entry_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_proto_pbconfigentry_config_entry_proto_msgTypes = make([]protoimpl.MessageInfo, 37) +var file_proto_pbconfigentry_config_entry_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_proto_pbconfigentry_config_entry_proto_goTypes = []interface{}{ (Kind)(0), // 0: hashicorp.consul.internal.configentry.Kind (IntentionAction)(0), // 1: hashicorp.consul.internal.configentry.IntentionAction @@ -2779,96 +2898,98 @@ var file_proto_pbconfigentry_config_entry_proto_goTypes = []interface{}{ (*ServiceResolverSubset)(nil), // 10: hashicorp.consul.internal.configentry.ServiceResolverSubset (*ServiceResolverRedirect)(nil), // 11: hashicorp.consul.internal.configentry.ServiceResolverRedirect (*ServiceResolverFailover)(nil), // 12: hashicorp.consul.internal.configentry.ServiceResolverFailover - (*LoadBalancer)(nil), // 13: hashicorp.consul.internal.configentry.LoadBalancer - (*RingHashConfig)(nil), // 14: hashicorp.consul.internal.configentry.RingHashConfig - (*LeastRequestConfig)(nil), // 15: hashicorp.consul.internal.configentry.LeastRequestConfig - (*HashPolicy)(nil), // 16: hashicorp.consul.internal.configentry.HashPolicy - (*CookieConfig)(nil), // 17: hashicorp.consul.internal.configentry.CookieConfig - (*IngressGateway)(nil), // 18: hashicorp.consul.internal.configentry.IngressGateway - (*GatewayTLSConfig)(nil), // 19: hashicorp.consul.internal.configentry.GatewayTLSConfig - (*GatewayTLSSDSConfig)(nil), // 20: hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - (*IngressListener)(nil), // 21: hashicorp.consul.internal.configentry.IngressListener - (*IngressService)(nil), // 22: hashicorp.consul.internal.configentry.IngressService - (*GatewayServiceTLSConfig)(nil), // 23: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig - (*HTTPHeaderModifiers)(nil), // 24: hashicorp.consul.internal.configentry.HTTPHeaderModifiers - (*ServiceIntentions)(nil), // 25: hashicorp.consul.internal.configentry.ServiceIntentions - (*SourceIntention)(nil), // 26: hashicorp.consul.internal.configentry.SourceIntention - (*IntentionPermission)(nil), // 27: hashicorp.consul.internal.configentry.IntentionPermission - (*IntentionHTTPPermission)(nil), // 28: hashicorp.consul.internal.configentry.IntentionHTTPPermission - (*IntentionHTTPHeaderPermission)(nil), // 29: hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission - nil, // 30: hashicorp.consul.internal.configentry.MeshConfig.MetaEntry - nil, // 31: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry - nil, // 32: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry - nil, // 33: hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry - nil, // 34: hashicorp.consul.internal.configentry.IngressGateway.MetaEntry - nil, // 35: hashicorp.consul.internal.configentry.IngressService.MetaEntry - nil, // 36: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry - nil, // 37: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry - nil, // 38: hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry - nil, // 39: hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry - (*pbcommon.EnterpriseMeta)(nil), // 40: hashicorp.consul.internal.common.EnterpriseMeta - (*pbcommon.RaftIndex)(nil), // 41: hashicorp.consul.internal.common.RaftIndex - (*durationpb.Duration)(nil), // 42: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 43: google.protobuf.Timestamp + (*ServiceResolverFailoverTarget)(nil), // 13: hashicorp.consul.internal.configentry.ServiceResolverFailoverTarget + (*LoadBalancer)(nil), // 14: hashicorp.consul.internal.configentry.LoadBalancer + (*RingHashConfig)(nil), // 15: hashicorp.consul.internal.configentry.RingHashConfig + (*LeastRequestConfig)(nil), // 16: hashicorp.consul.internal.configentry.LeastRequestConfig + (*HashPolicy)(nil), // 17: hashicorp.consul.internal.configentry.HashPolicy + (*CookieConfig)(nil), // 18: hashicorp.consul.internal.configentry.CookieConfig + (*IngressGateway)(nil), // 19: hashicorp.consul.internal.configentry.IngressGateway + (*GatewayTLSConfig)(nil), // 20: hashicorp.consul.internal.configentry.GatewayTLSConfig + (*GatewayTLSSDSConfig)(nil), // 21: hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + (*IngressListener)(nil), // 22: hashicorp.consul.internal.configentry.IngressListener + (*IngressService)(nil), // 23: hashicorp.consul.internal.configentry.IngressService + (*GatewayServiceTLSConfig)(nil), // 24: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig + (*HTTPHeaderModifiers)(nil), // 25: hashicorp.consul.internal.configentry.HTTPHeaderModifiers + (*ServiceIntentions)(nil), // 26: hashicorp.consul.internal.configentry.ServiceIntentions + (*SourceIntention)(nil), // 27: hashicorp.consul.internal.configentry.SourceIntention + (*IntentionPermission)(nil), // 28: hashicorp.consul.internal.configentry.IntentionPermission + (*IntentionHTTPPermission)(nil), // 29: hashicorp.consul.internal.configentry.IntentionHTTPPermission + (*IntentionHTTPHeaderPermission)(nil), // 30: hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission + nil, // 31: hashicorp.consul.internal.configentry.MeshConfig.MetaEntry + nil, // 32: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry + nil, // 33: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry + nil, // 34: hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry + nil, // 35: hashicorp.consul.internal.configentry.IngressGateway.MetaEntry + nil, // 36: hashicorp.consul.internal.configentry.IngressService.MetaEntry + nil, // 37: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry + nil, // 38: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry + nil, // 39: hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry + nil, // 40: hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry + (*pbcommon.EnterpriseMeta)(nil), // 41: hashicorp.consul.internal.common.EnterpriseMeta + (*pbcommon.RaftIndex)(nil), // 42: hashicorp.consul.internal.common.RaftIndex + (*durationpb.Duration)(nil), // 43: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 44: google.protobuf.Timestamp } var file_proto_pbconfigentry_config_entry_proto_depIdxs = []int32{ 0, // 0: hashicorp.consul.internal.configentry.ConfigEntry.Kind:type_name -> hashicorp.consul.internal.configentry.Kind - 40, // 1: hashicorp.consul.internal.configentry.ConfigEntry.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 41, // 2: hashicorp.consul.internal.configentry.ConfigEntry.RaftIndex:type_name -> hashicorp.consul.internal.common.RaftIndex + 41, // 1: hashicorp.consul.internal.configentry.ConfigEntry.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 42, // 2: hashicorp.consul.internal.configentry.ConfigEntry.RaftIndex:type_name -> hashicorp.consul.internal.common.RaftIndex 4, // 3: hashicorp.consul.internal.configentry.ConfigEntry.MeshConfig:type_name -> hashicorp.consul.internal.configentry.MeshConfig 9, // 4: hashicorp.consul.internal.configentry.ConfigEntry.ServiceResolver:type_name -> hashicorp.consul.internal.configentry.ServiceResolver - 18, // 5: hashicorp.consul.internal.configentry.ConfigEntry.IngressGateway:type_name -> hashicorp.consul.internal.configentry.IngressGateway - 25, // 6: hashicorp.consul.internal.configentry.ConfigEntry.ServiceIntentions:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions + 19, // 5: hashicorp.consul.internal.configentry.ConfigEntry.IngressGateway:type_name -> hashicorp.consul.internal.configentry.IngressGateway + 26, // 6: hashicorp.consul.internal.configentry.ConfigEntry.ServiceIntentions:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions 5, // 7: hashicorp.consul.internal.configentry.MeshConfig.TransparentProxy:type_name -> hashicorp.consul.internal.configentry.TransparentProxyMeshConfig 6, // 8: hashicorp.consul.internal.configentry.MeshConfig.TLS:type_name -> hashicorp.consul.internal.configentry.MeshTLSConfig 8, // 9: hashicorp.consul.internal.configentry.MeshConfig.HTTP:type_name -> hashicorp.consul.internal.configentry.MeshHTTPConfig - 30, // 10: hashicorp.consul.internal.configentry.MeshConfig.Meta:type_name -> hashicorp.consul.internal.configentry.MeshConfig.MetaEntry + 31, // 10: hashicorp.consul.internal.configentry.MeshConfig.Meta:type_name -> hashicorp.consul.internal.configentry.MeshConfig.MetaEntry 7, // 11: hashicorp.consul.internal.configentry.MeshTLSConfig.Incoming:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig 7, // 12: hashicorp.consul.internal.configentry.MeshTLSConfig.Outgoing:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig - 31, // 13: hashicorp.consul.internal.configentry.ServiceResolver.Subsets:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry + 32, // 13: hashicorp.consul.internal.configentry.ServiceResolver.Subsets:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry 11, // 14: hashicorp.consul.internal.configentry.ServiceResolver.Redirect:type_name -> hashicorp.consul.internal.configentry.ServiceResolverRedirect - 32, // 15: hashicorp.consul.internal.configentry.ServiceResolver.Failover:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry - 42, // 16: hashicorp.consul.internal.configentry.ServiceResolver.ConnectTimeout:type_name -> google.protobuf.Duration - 13, // 17: hashicorp.consul.internal.configentry.ServiceResolver.LoadBalancer:type_name -> hashicorp.consul.internal.configentry.LoadBalancer - 33, // 18: hashicorp.consul.internal.configentry.ServiceResolver.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry - 14, // 19: hashicorp.consul.internal.configentry.LoadBalancer.RingHashConfig:type_name -> hashicorp.consul.internal.configentry.RingHashConfig - 15, // 20: hashicorp.consul.internal.configentry.LoadBalancer.LeastRequestConfig:type_name -> hashicorp.consul.internal.configentry.LeastRequestConfig - 16, // 21: hashicorp.consul.internal.configentry.LoadBalancer.HashPolicies:type_name -> hashicorp.consul.internal.configentry.HashPolicy - 17, // 22: hashicorp.consul.internal.configentry.HashPolicy.CookieConfig:type_name -> hashicorp.consul.internal.configentry.CookieConfig - 42, // 23: hashicorp.consul.internal.configentry.CookieConfig.TTL:type_name -> google.protobuf.Duration - 19, // 24: hashicorp.consul.internal.configentry.IngressGateway.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig - 21, // 25: hashicorp.consul.internal.configentry.IngressGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.IngressListener - 34, // 26: hashicorp.consul.internal.configentry.IngressGateway.Meta:type_name -> hashicorp.consul.internal.configentry.IngressGateway.MetaEntry - 20, // 27: hashicorp.consul.internal.configentry.GatewayTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - 22, // 28: hashicorp.consul.internal.configentry.IngressListener.Services:type_name -> hashicorp.consul.internal.configentry.IngressService - 19, // 29: hashicorp.consul.internal.configentry.IngressListener.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig - 23, // 30: hashicorp.consul.internal.configentry.IngressService.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayServiceTLSConfig - 24, // 31: hashicorp.consul.internal.configentry.IngressService.RequestHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers - 24, // 32: hashicorp.consul.internal.configentry.IngressService.ResponseHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers - 35, // 33: hashicorp.consul.internal.configentry.IngressService.Meta:type_name -> hashicorp.consul.internal.configentry.IngressService.MetaEntry - 40, // 34: hashicorp.consul.internal.configentry.IngressService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 20, // 35: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - 36, // 36: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry - 37, // 37: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry - 26, // 38: hashicorp.consul.internal.configentry.ServiceIntentions.Sources:type_name -> hashicorp.consul.internal.configentry.SourceIntention - 38, // 39: hashicorp.consul.internal.configentry.ServiceIntentions.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry - 1, // 40: hashicorp.consul.internal.configentry.SourceIntention.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction - 27, // 41: hashicorp.consul.internal.configentry.SourceIntention.Permissions:type_name -> hashicorp.consul.internal.configentry.IntentionPermission - 2, // 42: hashicorp.consul.internal.configentry.SourceIntention.Type:type_name -> hashicorp.consul.internal.configentry.IntentionSourceType - 39, // 43: hashicorp.consul.internal.configentry.SourceIntention.LegacyMeta:type_name -> hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry - 43, // 44: hashicorp.consul.internal.configentry.SourceIntention.LegacyCreateTime:type_name -> google.protobuf.Timestamp - 43, // 45: hashicorp.consul.internal.configentry.SourceIntention.LegacyUpdateTime:type_name -> google.protobuf.Timestamp - 40, // 46: hashicorp.consul.internal.configentry.SourceIntention.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 1, // 47: hashicorp.consul.internal.configentry.IntentionPermission.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction - 28, // 48: hashicorp.consul.internal.configentry.IntentionPermission.HTTP:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPPermission - 29, // 49: hashicorp.consul.internal.configentry.IntentionHTTPPermission.Header:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission - 10, // 50: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverSubset - 12, // 51: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailover - 52, // [52:52] is the sub-list for method output_type - 52, // [52:52] is the sub-list for method input_type - 52, // [52:52] is the sub-list for extension type_name - 52, // [52:52] is the sub-list for extension extendee - 0, // [0:52] is the sub-list for field type_name + 33, // 15: hashicorp.consul.internal.configentry.ServiceResolver.Failover:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry + 43, // 16: hashicorp.consul.internal.configentry.ServiceResolver.ConnectTimeout:type_name -> google.protobuf.Duration + 14, // 17: hashicorp.consul.internal.configentry.ServiceResolver.LoadBalancer:type_name -> hashicorp.consul.internal.configentry.LoadBalancer + 34, // 18: hashicorp.consul.internal.configentry.ServiceResolver.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry + 13, // 19: hashicorp.consul.internal.configentry.ServiceResolverFailover.Targets:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailoverTarget + 15, // 20: hashicorp.consul.internal.configentry.LoadBalancer.RingHashConfig:type_name -> hashicorp.consul.internal.configentry.RingHashConfig + 16, // 21: hashicorp.consul.internal.configentry.LoadBalancer.LeastRequestConfig:type_name -> hashicorp.consul.internal.configentry.LeastRequestConfig + 17, // 22: hashicorp.consul.internal.configentry.LoadBalancer.HashPolicies:type_name -> hashicorp.consul.internal.configentry.HashPolicy + 18, // 23: hashicorp.consul.internal.configentry.HashPolicy.CookieConfig:type_name -> hashicorp.consul.internal.configentry.CookieConfig + 43, // 24: hashicorp.consul.internal.configentry.CookieConfig.TTL:type_name -> google.protobuf.Duration + 20, // 25: hashicorp.consul.internal.configentry.IngressGateway.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig + 22, // 26: hashicorp.consul.internal.configentry.IngressGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.IngressListener + 35, // 27: hashicorp.consul.internal.configentry.IngressGateway.Meta:type_name -> hashicorp.consul.internal.configentry.IngressGateway.MetaEntry + 21, // 28: hashicorp.consul.internal.configentry.GatewayTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + 23, // 29: hashicorp.consul.internal.configentry.IngressListener.Services:type_name -> hashicorp.consul.internal.configentry.IngressService + 20, // 30: hashicorp.consul.internal.configentry.IngressListener.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig + 24, // 31: hashicorp.consul.internal.configentry.IngressService.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayServiceTLSConfig + 25, // 32: hashicorp.consul.internal.configentry.IngressService.RequestHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers + 25, // 33: hashicorp.consul.internal.configentry.IngressService.ResponseHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers + 36, // 34: hashicorp.consul.internal.configentry.IngressService.Meta:type_name -> hashicorp.consul.internal.configentry.IngressService.MetaEntry + 41, // 35: hashicorp.consul.internal.configentry.IngressService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 21, // 36: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + 37, // 37: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry + 38, // 38: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry + 27, // 39: hashicorp.consul.internal.configentry.ServiceIntentions.Sources:type_name -> hashicorp.consul.internal.configentry.SourceIntention + 39, // 40: hashicorp.consul.internal.configentry.ServiceIntentions.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry + 1, // 41: hashicorp.consul.internal.configentry.SourceIntention.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction + 28, // 42: hashicorp.consul.internal.configentry.SourceIntention.Permissions:type_name -> hashicorp.consul.internal.configentry.IntentionPermission + 2, // 43: hashicorp.consul.internal.configentry.SourceIntention.Type:type_name -> hashicorp.consul.internal.configentry.IntentionSourceType + 40, // 44: hashicorp.consul.internal.configentry.SourceIntention.LegacyMeta:type_name -> hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry + 44, // 45: hashicorp.consul.internal.configentry.SourceIntention.LegacyCreateTime:type_name -> google.protobuf.Timestamp + 44, // 46: hashicorp.consul.internal.configentry.SourceIntention.LegacyUpdateTime:type_name -> google.protobuf.Timestamp + 41, // 47: hashicorp.consul.internal.configentry.SourceIntention.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 1, // 48: hashicorp.consul.internal.configentry.IntentionPermission.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction + 29, // 49: hashicorp.consul.internal.configentry.IntentionPermission.HTTP:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPPermission + 30, // 50: hashicorp.consul.internal.configentry.IntentionHTTPPermission.Header:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission + 10, // 51: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverSubset + 12, // 52: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailover + 53, // [53:53] is the sub-list for method output_type + 53, // [53:53] is the sub-list for method input_type + 53, // [53:53] is the sub-list for extension type_name + 53, // [53:53] is the sub-list for extension extendee + 0, // [0:53] is the sub-list for field type_name } func init() { file_proto_pbconfigentry_config_entry_proto_init() } @@ -2998,7 +3119,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoadBalancer); i { + switch v := v.(*ServiceResolverFailoverTarget); i { case 0: return &v.state case 1: @@ -3010,7 +3131,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RingHashConfig); i { + switch v := v.(*LoadBalancer); i { case 0: return &v.state case 1: @@ -3022,7 +3143,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LeastRequestConfig); i { + switch v := v.(*RingHashConfig); i { case 0: return &v.state case 1: @@ -3034,7 +3155,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HashPolicy); i { + switch v := v.(*LeastRequestConfig); i { case 0: return &v.state case 1: @@ -3046,7 +3167,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CookieConfig); i { + switch v := v.(*HashPolicy); i { case 0: return &v.state case 1: @@ -3058,7 +3179,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IngressGateway); i { + switch v := v.(*CookieConfig); i { case 0: return &v.state case 1: @@ -3070,7 +3191,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GatewayTLSConfig); i { + switch v := v.(*IngressGateway); i { case 0: return &v.state case 1: @@ -3082,7 +3203,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GatewayTLSSDSConfig); i { + switch v := v.(*GatewayTLSConfig); i { case 0: return &v.state case 1: @@ -3094,7 +3215,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IngressListener); i { + switch v := v.(*GatewayTLSSDSConfig); i { case 0: return &v.state case 1: @@ -3106,7 +3227,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IngressService); i { + switch v := v.(*IngressListener); i { case 0: return &v.state case 1: @@ -3118,7 +3239,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GatewayServiceTLSConfig); i { + switch v := v.(*IngressService); i { case 0: return &v.state case 1: @@ -3130,7 +3251,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTTPHeaderModifiers); i { + switch v := v.(*GatewayServiceTLSConfig); i { case 0: return &v.state case 1: @@ -3142,7 +3263,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServiceIntentions); i { + switch v := v.(*HTTPHeaderModifiers); i { case 0: return &v.state case 1: @@ -3154,7 +3275,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SourceIntention); i { + switch v := v.(*ServiceIntentions); i { case 0: return &v.state case 1: @@ -3166,7 +3287,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IntentionPermission); i { + switch v := v.(*SourceIntention); i { case 0: return &v.state case 1: @@ -3178,7 +3299,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IntentionHTTPPermission); i { + switch v := v.(*IntentionPermission); i { case 0: return &v.state case 1: @@ -3190,6 +3311,18 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IntentionHTTPPermission); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbconfigentry_config_entry_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IntentionHTTPHeaderPermission); i { case 0: return &v.state @@ -3214,7 +3347,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_pbconfigentry_config_entry_proto_rawDesc, NumEnums: 3, - NumMessages: 37, + NumMessages: 38, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/pbconfigentry/config_entry.proto b/proto/pbconfigentry/config_entry.proto index 0a35f4ef1f..37d497a68b 100644 --- a/proto/pbconfigentry/config_entry.proto +++ b/proto/pbconfigentry/config_entry.proto @@ -134,6 +134,21 @@ message ServiceResolverFailover { string ServiceSubset = 2; string Namespace = 3; repeated string Datacenters = 4; + repeated ServiceResolverFailoverTarget Targets = 5; +} + +// mog annotation: +// +// target=github.com/hashicorp/consul/agent/structs.ServiceResolverFailoverTarget +// output=config_entry.gen.go +// name=Structs +message ServiceResolverFailoverTarget { + string Service = 1; + string ServiceSubset = 2; + string Partition = 3; + string Namespace = 4; + string Datacenter = 5; + string Peer = 6; } // mog annotation: diff --git a/proto/pbpeering/peering.go b/proto/pbpeering/peering.go index 172857b1c2..74f5a52f08 100644 --- a/proto/pbpeering/peering.go +++ b/proto/pbpeering/peering.go @@ -143,10 +143,10 @@ func PeeringStateFromAPI(t api.PeeringState) PeeringState { } func (p *Peering) IsActive() bool { - if p != nil && p.State == PeeringState_TERMINATED { + if p == nil || p.State == PeeringState_TERMINATED { return false } - if p == nil || p.DeletedAt == nil { + if p.DeletedAt == nil { return true } @@ -155,17 +155,32 @@ func (p *Peering) IsActive() bool { } // Validate is a validation helper that checks whether a secret ID is embedded in the container type. -func (p *PeeringSecrets) Validate() error { - if p.GetPeerID() == "" { +func (s *SecretsWriteRequest) Validate() error { + if s.PeerID == "" { return errors.New("missing peer ID") } - if p.GetEstablishment().GetSecretID() != "" { - return nil + switch r := s.Request.(type) { + case *SecretsWriteRequest_GenerateToken: + if r != nil && r.GenerateToken.GetEstablishmentSecret() != "" { + return nil + } + case *SecretsWriteRequest_Establish: + if r != nil && r.Establish.GetActiveStreamSecret() != "" { + return nil + } + case *SecretsWriteRequest_ExchangeSecret: + if r != nil && r.ExchangeSecret.GetPendingStreamSecret() != "" { + return nil + } + case *SecretsWriteRequest_PromotePending: + if r != nil && r.PromotePending.GetActiveStreamSecret() != "" { + return nil + } + default: + return fmt.Errorf("unexpected request type %T", s.Request) } - if p.GetStream().GetPendingSecretID() != "" || p.GetStream().GetActiveSecretID() != "" { - return nil - } - return errors.New("no secret IDs were embedded") + + return errors.New("missing secret ID") } // TLSDialOption returns the gRPC DialOption to secure the transport if CAPems diff --git a/proto/pbpeering/peering.pb.binary.go b/proto/pbpeering/peering.pb.binary.go index 526c7cb402..2e9d5c71cc 100644 --- a/proto/pbpeering/peering.pb.binary.go +++ b/proto/pbpeering/peering.pb.binary.go @@ -7,6 +7,56 @@ import ( "github.com/golang/protobuf/proto" ) +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *SecretsWriteRequest) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *SecretsWriteRequest) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *SecretsWriteRequest_GenerateTokenRequest) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *SecretsWriteRequest_GenerateTokenRequest) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *SecretsWriteRequest_ExchangeSecretRequest) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *SecretsWriteRequest_ExchangeSecretRequest) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *SecretsWriteRequest_PromotePendingRequest) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *SecretsWriteRequest_PromotePendingRequest) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *SecretsWriteRequest_EstablishRequest) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *SecretsWriteRequest_EstablishRequest) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + // MarshalBinary implements encoding.BinaryMarshaler func (msg *PeeringSecrets) MarshalBinary() ([]byte, error) { return proto.Marshal(msg) diff --git a/proto/pbpeering/peering.pb.go b/proto/pbpeering/peering.pb.go index 8708b0fd7f..abd0ea186f 100644 --- a/proto/pbpeering/peering.pb.go +++ b/proto/pbpeering/peering.pb.go @@ -96,6 +96,126 @@ func (PeeringState) EnumDescriptor() ([]byte, []int) { return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0} } +// SecretsWriteRequest encodes a request to write a peering secret as the result +// of some operation. Different operations, such as generating a peering token, +// lead to modifying the known secrets associated with a peering. +type SecretsWriteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // PeerID is the local UUID of the peering this request applies to. + PeerID string `protobuf:"bytes,1,opt,name=PeerID,proto3" json:"PeerID,omitempty"` + // Types that are assignable to Request: + // *SecretsWriteRequest_GenerateToken + // *SecretsWriteRequest_ExchangeSecret + // *SecretsWriteRequest_PromotePending + // *SecretsWriteRequest_Establish + Request isSecretsWriteRequest_Request `protobuf_oneof:"Request"` +} + +func (x *SecretsWriteRequest) Reset() { + *x = SecretsWriteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeering_peering_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretsWriteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretsWriteRequest) ProtoMessage() {} + +func (x *SecretsWriteRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeering_peering_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretsWriteRequest.ProtoReflect.Descriptor instead. +func (*SecretsWriteRequest) Descriptor() ([]byte, []int) { + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0} +} + +func (x *SecretsWriteRequest) GetPeerID() string { + if x != nil { + return x.PeerID + } + return "" +} + +func (m *SecretsWriteRequest) GetRequest() isSecretsWriteRequest_Request { + if m != nil { + return m.Request + } + return nil +} + +func (x *SecretsWriteRequest) GetGenerateToken() *SecretsWriteRequest_GenerateTokenRequest { + if x, ok := x.GetRequest().(*SecretsWriteRequest_GenerateToken); ok { + return x.GenerateToken + } + return nil +} + +func (x *SecretsWriteRequest) GetExchangeSecret() *SecretsWriteRequest_ExchangeSecretRequest { + if x, ok := x.GetRequest().(*SecretsWriteRequest_ExchangeSecret); ok { + return x.ExchangeSecret + } + return nil +} + +func (x *SecretsWriteRequest) GetPromotePending() *SecretsWriteRequest_PromotePendingRequest { + if x, ok := x.GetRequest().(*SecretsWriteRequest_PromotePending); ok { + return x.PromotePending + } + return nil +} + +func (x *SecretsWriteRequest) GetEstablish() *SecretsWriteRequest_EstablishRequest { + if x, ok := x.GetRequest().(*SecretsWriteRequest_Establish); ok { + return x.Establish + } + return nil +} + +type isSecretsWriteRequest_Request interface { + isSecretsWriteRequest_Request() +} + +type SecretsWriteRequest_GenerateToken struct { + GenerateToken *SecretsWriteRequest_GenerateTokenRequest `protobuf:"bytes,2,opt,name=generate_token,json=generateToken,proto3,oneof"` +} + +type SecretsWriteRequest_ExchangeSecret struct { + ExchangeSecret *SecretsWriteRequest_ExchangeSecretRequest `protobuf:"bytes,3,opt,name=exchange_secret,json=exchangeSecret,proto3,oneof"` +} + +type SecretsWriteRequest_PromotePending struct { + PromotePending *SecretsWriteRequest_PromotePendingRequest `protobuf:"bytes,4,opt,name=promote_pending,json=promotePending,proto3,oneof"` +} + +type SecretsWriteRequest_Establish struct { + Establish *SecretsWriteRequest_EstablishRequest `protobuf:"bytes,5,opt,name=establish,proto3,oneof"` +} + +func (*SecretsWriteRequest_GenerateToken) isSecretsWriteRequest_Request() {} + +func (*SecretsWriteRequest_ExchangeSecret) isSecretsWriteRequest_Request() {} + +func (*SecretsWriteRequest_PromotePending) isSecretsWriteRequest_Request() {} + +func (*SecretsWriteRequest_Establish) isSecretsWriteRequest_Request() {} + // PeeringSecrets defines a secret used for authenticating/authorizing peer clusters. type PeeringSecrets struct { state protoimpl.MessageState @@ -111,7 +231,7 @@ type PeeringSecrets struct { func (x *PeeringSecrets) Reset() { *x = PeeringSecrets{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[0] + mi := &file_proto_pbpeering_peering_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -124,7 +244,7 @@ func (x *PeeringSecrets) String() string { func (*PeeringSecrets) ProtoMessage() {} func (x *PeeringSecrets) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[0] + mi := &file_proto_pbpeering_peering_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -137,7 +257,7 @@ func (x *PeeringSecrets) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringSecrets.ProtoReflect.Descriptor instead. func (*PeeringSecrets) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{1} } func (x *PeeringSecrets) GetPeerID() string { @@ -215,7 +335,7 @@ type Peering struct { func (x *Peering) Reset() { *x = Peering{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[1] + mi := &file_proto_pbpeering_peering_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -228,7 +348,7 @@ func (x *Peering) String() string { func (*Peering) ProtoMessage() {} func (x *Peering) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[1] + mi := &file_proto_pbpeering_peering_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -241,7 +361,7 @@ func (x *Peering) ProtoReflect() protoreflect.Message { // Deprecated: Use Peering.ProtoReflect.Descriptor instead. func (*Peering) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{1} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{2} } func (x *Peering) GetID() string { @@ -370,7 +490,7 @@ type PeeringTrustBundle struct { func (x *PeeringTrustBundle) Reset() { *x = PeeringTrustBundle{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[2] + mi := &file_proto_pbpeering_peering_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -383,7 +503,7 @@ func (x *PeeringTrustBundle) String() string { func (*PeeringTrustBundle) ProtoMessage() {} func (x *PeeringTrustBundle) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[2] + mi := &file_proto_pbpeering_peering_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -396,7 +516,7 @@ func (x *PeeringTrustBundle) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundle.ProtoReflect.Descriptor instead. func (*PeeringTrustBundle) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{2} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{3} } func (x *PeeringTrustBundle) GetTrustDomain() string { @@ -461,7 +581,7 @@ type PeeringReadRequest struct { func (x *PeeringReadRequest) Reset() { *x = PeeringReadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[3] + mi := &file_proto_pbpeering_peering_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -474,7 +594,7 @@ func (x *PeeringReadRequest) String() string { func (*PeeringReadRequest) ProtoMessage() {} func (x *PeeringReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[3] + mi := &file_proto_pbpeering_peering_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -487,7 +607,7 @@ func (x *PeeringReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringReadRequest.ProtoReflect.Descriptor instead. func (*PeeringReadRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{3} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{4} } func (x *PeeringReadRequest) GetName() string { @@ -515,7 +635,7 @@ type PeeringReadResponse struct { func (x *PeeringReadResponse) Reset() { *x = PeeringReadResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[4] + mi := &file_proto_pbpeering_peering_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -528,7 +648,7 @@ func (x *PeeringReadResponse) String() string { func (*PeeringReadResponse) ProtoMessage() {} func (x *PeeringReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[4] + mi := &file_proto_pbpeering_peering_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -541,7 +661,7 @@ func (x *PeeringReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringReadResponse.ProtoReflect.Descriptor instead. func (*PeeringReadResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{4} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{5} } func (x *PeeringReadResponse) GetPeering() *Peering { @@ -563,7 +683,7 @@ type PeeringListRequest struct { func (x *PeeringListRequest) Reset() { *x = PeeringListRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[5] + mi := &file_proto_pbpeering_peering_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -576,7 +696,7 @@ func (x *PeeringListRequest) String() string { func (*PeeringListRequest) ProtoMessage() {} func (x *PeeringListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[5] + mi := &file_proto_pbpeering_peering_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -589,7 +709,7 @@ func (x *PeeringListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringListRequest.ProtoReflect.Descriptor instead. func (*PeeringListRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{5} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{6} } func (x *PeeringListRequest) GetPartition() string { @@ -610,7 +730,7 @@ type PeeringListResponse struct { func (x *PeeringListResponse) Reset() { *x = PeeringListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[6] + mi := &file_proto_pbpeering_peering_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -623,7 +743,7 @@ func (x *PeeringListResponse) String() string { func (*PeeringListResponse) ProtoMessage() {} func (x *PeeringListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[6] + mi := &file_proto_pbpeering_peering_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -636,7 +756,7 @@ func (x *PeeringListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringListResponse.ProtoReflect.Descriptor instead. func (*PeeringListResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{6} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{7} } func (x *PeeringListResponse) GetPeerings() []*Peering { @@ -653,17 +773,17 @@ type PeeringWriteRequest struct { // Peering is the peering to write with the request. Peering *Peering `protobuf:"bytes,1,opt,name=Peering,proto3" json:"Peering,omitempty"` - // PeeringSecrets contains the optional peering secrets to persist + // SecretsWriteRequest contains the optional peering secrets to persist // with the peering. Peering secrets are not embedded in the peering // object to avoid leaking them. - Secret *PeeringSecrets `protobuf:"bytes,2,opt,name=Secret,proto3" json:"Secret,omitempty"` - Meta map[string]string `protobuf:"bytes,3,rep,name=Meta,proto3" json:"Meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SecretsRequest *SecretsWriteRequest `protobuf:"bytes,2,opt,name=SecretsRequest,proto3" json:"SecretsRequest,omitempty"` + Meta map[string]string `protobuf:"bytes,3,rep,name=Meta,proto3" json:"Meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *PeeringWriteRequest) Reset() { *x = PeeringWriteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[7] + mi := &file_proto_pbpeering_peering_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -676,7 +796,7 @@ func (x *PeeringWriteRequest) String() string { func (*PeeringWriteRequest) ProtoMessage() {} func (x *PeeringWriteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[7] + mi := &file_proto_pbpeering_peering_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -689,7 +809,7 @@ func (x *PeeringWriteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringWriteRequest.ProtoReflect.Descriptor instead. func (*PeeringWriteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{7} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{8} } func (x *PeeringWriteRequest) GetPeering() *Peering { @@ -699,9 +819,9 @@ func (x *PeeringWriteRequest) GetPeering() *Peering { return nil } -func (x *PeeringWriteRequest) GetSecret() *PeeringSecrets { +func (x *PeeringWriteRequest) GetSecretsRequest() *SecretsWriteRequest { if x != nil { - return x.Secret + return x.SecretsRequest } return nil } @@ -723,7 +843,7 @@ type PeeringWriteResponse struct { func (x *PeeringWriteResponse) Reset() { *x = PeeringWriteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[8] + mi := &file_proto_pbpeering_peering_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -736,7 +856,7 @@ func (x *PeeringWriteResponse) String() string { func (*PeeringWriteResponse) ProtoMessage() {} func (x *PeeringWriteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[8] + mi := &file_proto_pbpeering_peering_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -749,7 +869,7 @@ func (x *PeeringWriteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringWriteResponse.ProtoReflect.Descriptor instead. func (*PeeringWriteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{8} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{9} } type PeeringDeleteRequest struct { @@ -764,7 +884,7 @@ type PeeringDeleteRequest struct { func (x *PeeringDeleteRequest) Reset() { *x = PeeringDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[9] + mi := &file_proto_pbpeering_peering_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -777,7 +897,7 @@ func (x *PeeringDeleteRequest) String() string { func (*PeeringDeleteRequest) ProtoMessage() {} func (x *PeeringDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[9] + mi := &file_proto_pbpeering_peering_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -790,7 +910,7 @@ func (x *PeeringDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringDeleteRequest.ProtoReflect.Descriptor instead. func (*PeeringDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{9} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{10} } func (x *PeeringDeleteRequest) GetName() string { @@ -816,7 +936,7 @@ type PeeringDeleteResponse struct { func (x *PeeringDeleteResponse) Reset() { *x = PeeringDeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[10] + mi := &file_proto_pbpeering_peering_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -829,7 +949,7 @@ func (x *PeeringDeleteResponse) String() string { func (*PeeringDeleteResponse) ProtoMessage() {} func (x *PeeringDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[10] + mi := &file_proto_pbpeering_peering_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -842,7 +962,7 @@ func (x *PeeringDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringDeleteResponse.ProtoReflect.Descriptor instead. func (*PeeringDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{10} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{11} } type TrustBundleListByServiceRequest struct { @@ -859,7 +979,7 @@ type TrustBundleListByServiceRequest struct { func (x *TrustBundleListByServiceRequest) Reset() { *x = TrustBundleListByServiceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[11] + mi := &file_proto_pbpeering_peering_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -872,7 +992,7 @@ func (x *TrustBundleListByServiceRequest) String() string { func (*TrustBundleListByServiceRequest) ProtoMessage() {} func (x *TrustBundleListByServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[11] + mi := &file_proto_pbpeering_peering_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -885,7 +1005,7 @@ func (x *TrustBundleListByServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleListByServiceRequest.ProtoReflect.Descriptor instead. func (*TrustBundleListByServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{11} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{12} } func (x *TrustBundleListByServiceRequest) GetServiceName() string { @@ -928,7 +1048,7 @@ type TrustBundleListByServiceResponse struct { func (x *TrustBundleListByServiceResponse) Reset() { *x = TrustBundleListByServiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[12] + mi := &file_proto_pbpeering_peering_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -941,7 +1061,7 @@ func (x *TrustBundleListByServiceResponse) String() string { func (*TrustBundleListByServiceResponse) ProtoMessage() {} func (x *TrustBundleListByServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[12] + mi := &file_proto_pbpeering_peering_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -954,7 +1074,7 @@ func (x *TrustBundleListByServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleListByServiceResponse.ProtoReflect.Descriptor instead. func (*TrustBundleListByServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{12} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{13} } func (x *TrustBundleListByServiceResponse) GetIndex() uint64 { @@ -983,7 +1103,7 @@ type TrustBundleReadRequest struct { func (x *TrustBundleReadRequest) Reset() { *x = TrustBundleReadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[13] + mi := &file_proto_pbpeering_peering_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -996,7 +1116,7 @@ func (x *TrustBundleReadRequest) String() string { func (*TrustBundleReadRequest) ProtoMessage() {} func (x *TrustBundleReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[13] + mi := &file_proto_pbpeering_peering_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1009,7 +1129,7 @@ func (x *TrustBundleReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleReadRequest.ProtoReflect.Descriptor instead. func (*TrustBundleReadRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{13} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{14} } func (x *TrustBundleReadRequest) GetName() string { @@ -1038,7 +1158,7 @@ type TrustBundleReadResponse struct { func (x *TrustBundleReadResponse) Reset() { *x = TrustBundleReadResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[14] + mi := &file_proto_pbpeering_peering_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1051,7 +1171,7 @@ func (x *TrustBundleReadResponse) String() string { func (*TrustBundleReadResponse) ProtoMessage() {} func (x *TrustBundleReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[14] + mi := &file_proto_pbpeering_peering_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1064,7 +1184,7 @@ func (x *TrustBundleReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleReadResponse.ProtoReflect.Descriptor instead. func (*TrustBundleReadResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{14} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{15} } func (x *TrustBundleReadResponse) GetIndex() uint64 { @@ -1093,7 +1213,7 @@ type PeeringTerminateByIDRequest struct { func (x *PeeringTerminateByIDRequest) Reset() { *x = PeeringTerminateByIDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[15] + mi := &file_proto_pbpeering_peering_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1106,7 +1226,7 @@ func (x *PeeringTerminateByIDRequest) String() string { func (*PeeringTerminateByIDRequest) ProtoMessage() {} func (x *PeeringTerminateByIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[15] + mi := &file_proto_pbpeering_peering_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1119,7 +1239,7 @@ func (x *PeeringTerminateByIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTerminateByIDRequest.ProtoReflect.Descriptor instead. func (*PeeringTerminateByIDRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{15} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{16} } func (x *PeeringTerminateByIDRequest) GetID() string { @@ -1138,7 +1258,7 @@ type PeeringTerminateByIDResponse struct { func (x *PeeringTerminateByIDResponse) Reset() { *x = PeeringTerminateByIDResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[16] + mi := &file_proto_pbpeering_peering_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1151,7 +1271,7 @@ func (x *PeeringTerminateByIDResponse) String() string { func (*PeeringTerminateByIDResponse) ProtoMessage() {} func (x *PeeringTerminateByIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[16] + mi := &file_proto_pbpeering_peering_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1164,7 +1284,7 @@ func (x *PeeringTerminateByIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTerminateByIDResponse.ProtoReflect.Descriptor instead. func (*PeeringTerminateByIDResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{16} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{17} } type PeeringTrustBundleWriteRequest struct { @@ -1178,7 +1298,7 @@ type PeeringTrustBundleWriteRequest struct { func (x *PeeringTrustBundleWriteRequest) Reset() { *x = PeeringTrustBundleWriteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[17] + mi := &file_proto_pbpeering_peering_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1191,7 +1311,7 @@ func (x *PeeringTrustBundleWriteRequest) String() string { func (*PeeringTrustBundleWriteRequest) ProtoMessage() {} func (x *PeeringTrustBundleWriteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[17] + mi := &file_proto_pbpeering_peering_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1204,7 +1324,7 @@ func (x *PeeringTrustBundleWriteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleWriteRequest.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleWriteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{17} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{18} } func (x *PeeringTrustBundleWriteRequest) GetPeeringTrustBundle() *PeeringTrustBundle { @@ -1223,7 +1343,7 @@ type PeeringTrustBundleWriteResponse struct { func (x *PeeringTrustBundleWriteResponse) Reset() { *x = PeeringTrustBundleWriteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[18] + mi := &file_proto_pbpeering_peering_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1236,7 +1356,7 @@ func (x *PeeringTrustBundleWriteResponse) String() string { func (*PeeringTrustBundleWriteResponse) ProtoMessage() {} func (x *PeeringTrustBundleWriteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[18] + mi := &file_proto_pbpeering_peering_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1249,7 +1369,7 @@ func (x *PeeringTrustBundleWriteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleWriteResponse.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleWriteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{18} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{19} } type PeeringTrustBundleDeleteRequest struct { @@ -1264,7 +1384,7 @@ type PeeringTrustBundleDeleteRequest struct { func (x *PeeringTrustBundleDeleteRequest) Reset() { *x = PeeringTrustBundleDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[19] + mi := &file_proto_pbpeering_peering_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1277,7 +1397,7 @@ func (x *PeeringTrustBundleDeleteRequest) String() string { func (*PeeringTrustBundleDeleteRequest) ProtoMessage() {} func (x *PeeringTrustBundleDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[19] + mi := &file_proto_pbpeering_peering_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1290,7 +1410,7 @@ func (x *PeeringTrustBundleDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleDeleteRequest.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{19} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{20} } func (x *PeeringTrustBundleDeleteRequest) GetName() string { @@ -1316,7 +1436,7 @@ type PeeringTrustBundleDeleteResponse struct { func (x *PeeringTrustBundleDeleteResponse) Reset() { *x = PeeringTrustBundleDeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[20] + mi := &file_proto_pbpeering_peering_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1329,7 +1449,7 @@ func (x *PeeringTrustBundleDeleteResponse) String() string { func (*PeeringTrustBundleDeleteResponse) ProtoMessage() {} func (x *PeeringTrustBundleDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[20] + mi := &file_proto_pbpeering_peering_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1342,7 +1462,7 @@ func (x *PeeringTrustBundleDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleDeleteResponse.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{20} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{21} } // mog annotation: @@ -1370,7 +1490,7 @@ type GenerateTokenRequest struct { func (x *GenerateTokenRequest) Reset() { *x = GenerateTokenRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[21] + mi := &file_proto_pbpeering_peering_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1383,7 +1503,7 @@ func (x *GenerateTokenRequest) String() string { func (*GenerateTokenRequest) ProtoMessage() {} func (x *GenerateTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[21] + mi := &file_proto_pbpeering_peering_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1396,7 +1516,7 @@ func (x *GenerateTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateTokenRequest.ProtoReflect.Descriptor instead. func (*GenerateTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{21} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{22} } func (x *GenerateTokenRequest) GetPeerName() string { @@ -1445,7 +1565,7 @@ type GenerateTokenResponse struct { func (x *GenerateTokenResponse) Reset() { *x = GenerateTokenResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[22] + mi := &file_proto_pbpeering_peering_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1458,7 +1578,7 @@ func (x *GenerateTokenResponse) String() string { func (*GenerateTokenResponse) ProtoMessage() {} func (x *GenerateTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[22] + mi := &file_proto_pbpeering_peering_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1471,7 +1591,7 @@ func (x *GenerateTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateTokenResponse.ProtoReflect.Descriptor instead. func (*GenerateTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{22} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{23} } func (x *GenerateTokenResponse) GetPeeringToken() string { @@ -1504,7 +1624,7 @@ type EstablishRequest struct { func (x *EstablishRequest) Reset() { *x = EstablishRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[23] + mi := &file_proto_pbpeering_peering_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1517,7 +1637,7 @@ func (x *EstablishRequest) String() string { func (*EstablishRequest) ProtoMessage() {} func (x *EstablishRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[23] + mi := &file_proto_pbpeering_peering_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1530,7 +1650,7 @@ func (x *EstablishRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstablishRequest.ProtoReflect.Descriptor instead. func (*EstablishRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{23} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{24} } func (x *EstablishRequest) GetPeerName() string { @@ -1575,7 +1695,7 @@ type EstablishResponse struct { func (x *EstablishResponse) Reset() { *x = EstablishResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[24] + mi := &file_proto_pbpeering_peering_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1588,7 +1708,7 @@ func (x *EstablishResponse) String() string { func (*EstablishResponse) ProtoMessage() {} func (x *EstablishResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[24] + mi := &file_proto_pbpeering_peering_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1601,7 +1721,223 @@ func (x *EstablishResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstablishResponse.ProtoReflect.Descriptor instead. func (*EstablishResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{24} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{25} +} + +// GenerateTokenRequest encodes a request to persist a peering establishment +// secret. It is triggered by generating a new peering token for a peer cluster. +type SecretsWriteRequest_GenerateTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // establishment_secret is the proposed secret ID to store as the establishment + // secret for this peering. + EstablishmentSecret string `protobuf:"bytes,1,opt,name=establishment_secret,json=establishmentSecret,proto3" json:"establishment_secret,omitempty"` +} + +func (x *SecretsWriteRequest_GenerateTokenRequest) Reset() { + *x = SecretsWriteRequest_GenerateTokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeering_peering_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretsWriteRequest_GenerateTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretsWriteRequest_GenerateTokenRequest) ProtoMessage() {} + +func (x *SecretsWriteRequest_GenerateTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeering_peering_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretsWriteRequest_GenerateTokenRequest.ProtoReflect.Descriptor instead. +func (*SecretsWriteRequest_GenerateTokenRequest) Descriptor() ([]byte, []int) { + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *SecretsWriteRequest_GenerateTokenRequest) GetEstablishmentSecret() string { + if x != nil { + return x.EstablishmentSecret + } + return "" +} + +// ExchangeSecretRequest encodes a request to persist a pending stream secret +// secret. It is triggered by an acceptor peer generating a long-lived stream secret +// in exchange for an establishment secret. +type SecretsWriteRequest_ExchangeSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // establishment_secret is the secret to exchange for the given pending stream secret. + EstablishmentSecret string `protobuf:"bytes,1,opt,name=establishment_secret,json=establishmentSecret,proto3" json:"establishment_secret,omitempty"` + // pending_stream_secret is the proposed secret ID to store as the pending stream + // secret for this peering. + PendingStreamSecret string `protobuf:"bytes,2,opt,name=pending_stream_secret,json=pendingStreamSecret,proto3" json:"pending_stream_secret,omitempty"` +} + +func (x *SecretsWriteRequest_ExchangeSecretRequest) Reset() { + *x = SecretsWriteRequest_ExchangeSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeering_peering_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretsWriteRequest_ExchangeSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretsWriteRequest_ExchangeSecretRequest) ProtoMessage() {} + +func (x *SecretsWriteRequest_ExchangeSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeering_peering_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretsWriteRequest_ExchangeSecretRequest.ProtoReflect.Descriptor instead. +func (*SecretsWriteRequest_ExchangeSecretRequest) Descriptor() ([]byte, []int) { + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *SecretsWriteRequest_ExchangeSecretRequest) GetEstablishmentSecret() string { + if x != nil { + return x.EstablishmentSecret + } + return "" +} + +func (x *SecretsWriteRequest_ExchangeSecretRequest) GetPendingStreamSecret() string { + if x != nil { + return x.PendingStreamSecret + } + return "" +} + +// PromotePendingRequest encodes a request to promote a pending stream secret +// to be an active stream secret. It is triggered when the accepting stream handler +// validates an Open request from a peer with a pending stream secret. +type SecretsWriteRequest_PromotePendingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // active_stream_secret is the proposed secret ID to store as the active stream + // secret for this peering. + ActiveStreamSecret string `protobuf:"bytes,1,opt,name=active_stream_secret,json=activeStreamSecret,proto3" json:"active_stream_secret,omitempty"` +} + +func (x *SecretsWriteRequest_PromotePendingRequest) Reset() { + *x = SecretsWriteRequest_PromotePendingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeering_peering_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretsWriteRequest_PromotePendingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretsWriteRequest_PromotePendingRequest) ProtoMessage() {} + +func (x *SecretsWriteRequest_PromotePendingRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeering_peering_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretsWriteRequest_PromotePendingRequest.ProtoReflect.Descriptor instead. +func (*SecretsWriteRequest_PromotePendingRequest) Descriptor() ([]byte, []int) { + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *SecretsWriteRequest_PromotePendingRequest) GetActiveStreamSecret() string { + if x != nil { + return x.ActiveStreamSecret + } + return "" +} + +// EstablishRequest encodes a request to persist an active stream secret. +// It is triggered after a dialing peer exchanges their establishment secret +// for a long-lived active stream secret. +type SecretsWriteRequest_EstablishRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // active_stream_secret is the proposed secret ID to store as the active stream + // secret for this peering. + ActiveStreamSecret string `protobuf:"bytes,1,opt,name=active_stream_secret,json=activeStreamSecret,proto3" json:"active_stream_secret,omitempty"` +} + +func (x *SecretsWriteRequest_EstablishRequest) Reset() { + *x = SecretsWriteRequest_EstablishRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeering_peering_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretsWriteRequest_EstablishRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretsWriteRequest_EstablishRequest) ProtoMessage() {} + +func (x *SecretsWriteRequest_EstablishRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeering_peering_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretsWriteRequest_EstablishRequest.ProtoReflect.Descriptor instead. +func (*SecretsWriteRequest_EstablishRequest) Descriptor() ([]byte, []int) { + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0, 3} +} + +func (x *SecretsWriteRequest_EstablishRequest) GetActiveStreamSecret() string { + if x != nil { + return x.ActiveStreamSecret + } + return "" } type PeeringSecrets_Establishment struct { @@ -1616,7 +1952,7 @@ type PeeringSecrets_Establishment struct { func (x *PeeringSecrets_Establishment) Reset() { *x = PeeringSecrets_Establishment{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[25] + mi := &file_proto_pbpeering_peering_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1629,7 +1965,7 @@ func (x *PeeringSecrets_Establishment) String() string { func (*PeeringSecrets_Establishment) ProtoMessage() {} func (x *PeeringSecrets_Establishment) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[25] + mi := &file_proto_pbpeering_peering_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1642,7 +1978,7 @@ func (x *PeeringSecrets_Establishment) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringSecrets_Establishment.ProtoReflect.Descriptor instead. func (*PeeringSecrets_Establishment) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0, 0} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{1, 0} } func (x *PeeringSecrets_Establishment) GetSecretID() string { @@ -1675,7 +2011,7 @@ type PeeringSecrets_Stream struct { func (x *PeeringSecrets_Stream) Reset() { *x = PeeringSecrets_Stream{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[26] + mi := &file_proto_pbpeering_peering_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1688,7 +2024,7 @@ func (x *PeeringSecrets_Stream) String() string { func (*PeeringSecrets_Stream) ProtoMessage() {} func (x *PeeringSecrets_Stream) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[26] + mi := &file_proto_pbpeering_peering_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1701,7 +2037,7 @@ func (x *PeeringSecrets_Stream) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringSecrets_Stream.ProtoReflect.Descriptor instead. func (*PeeringSecrets_Stream) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{0, 1} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{1, 1} } func (x *PeeringSecrets_Stream) GetActiveSecretID() string { @@ -1727,319 +2063,375 @@ var file_proto_pbpeering_peering_proto_rawDesc = []byte{ 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x1a, 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, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, 0x12, 0x65, - 0x0a, 0x0d, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0d, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, - 0x03, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, - 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x1a, 0x2b, 0x0a, 0x0d, 0x45, 0x73, 0x74, 0x61, 0x62, - 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x49, 0x44, 0x1a, 0x5a, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x26, - 0x0a, 0x0e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x12, 0x28, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, - 0x22, 0x8d, 0x05, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, - 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, - 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, - 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, 0x38, - 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x04, 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, 0x09, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x48, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, - 0x18, 0x05, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, - 0x74, 0x61, 0x12, 0x45, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x2f, 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, 0x70, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x52, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, - 0x72, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, - 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x41, 0x50, 0x65, 0x6d, 0x73, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x41, 0x50, 0x65, 0x6d, - 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, - 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x50, 0x65, 0x65, - 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x49, - 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x32, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x45, - 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, 0x64, 0x69, - 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 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, 0xfe, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 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, 0x1a, 0x0a, 0x08, 0x52, 0x6f, 0x6f, 0x74, 0x50, 0x45, 0x4d, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x52, 0x6f, 0x6f, 0x74, 0x50, 0x45, 0x4d, 0x73, 0x12, - 0x2c, 0x0a, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, - 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x20, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x22, 0x46, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, - 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x13, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2a, 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, 0x70, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x50, - 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x32, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5d, 0x0a, 0x13, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x46, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2a, 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, - 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, - 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xb5, 0x02, 0x0a, 0x13, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2a, 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, 0x70, - 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x49, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x06, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x12, 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 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, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x48, 0x0a, 0x14, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x17, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x01, 0x0a, - 0x1f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, - 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, - 0x02, 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, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, - 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4b, 0x69, - 0x6e, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4f, 0x0a, - 0x07, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x18, 0x02, 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, 0x70, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x07, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x22, 0x4a, - 0x0a, 0x16, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x17, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4d, 0x0a, 0x06, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, + 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x06, 0x0a, 0x13, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x50, + 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, + 0x72, 0x49, 0x44, 0x12, 0x74, 0x0a, 0x0e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4b, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x52, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x2d, 0x0a, 0x1b, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x42, 0x79, - 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, - 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x1e, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x65, 0x0a, 0x12, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x18, 0x01, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, - 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x0a, 0x1f, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x22, 0x0a, 0x20, 0x50, - 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x9a, 0x02, 0x0a, 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 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, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x17, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 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, 0x3b, 0x0a, 0x15, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xfc, 0x01, 0x0a, 0x10, 0x45, 0x73, - 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x51, 0x0a, 0x04, - 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, - 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, - 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x77, 0x0a, 0x0f, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x4c, 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, 0x70, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x48, 0x00, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x12, 0x77, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4c, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x72, 0x6f, + 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x67, 0x0a, 0x09, 0x65, + 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x47, + 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, 0x70, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x09, 0x65, 0x73, 0x74, 0x61, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x1a, 0x49, 0x0a, 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x14, + 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x65, 0x73, 0x74, 0x61, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, + 0x7e, 0x0a, 0x15, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x14, 0x65, 0x73, 0x74, 0x61, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, + 0x49, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x44, 0x0a, 0x10, 0x45, 0x73, + 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, + 0x0a, 0x14, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x42, 0x09, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xea, 0x02, 0x0a, 0x0e, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, 0x12, 0x65, 0x0a, 0x0d, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0d, + 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, + 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x03, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x1a, + 0x2b, 0x0a, 0x0d, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x1a, 0x5a, 0x0a, 0x06, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x26, 0x0a, 0x0e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x12, 0x28, + 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, + 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x22, 0x8d, 0x05, 0x0a, 0x07, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 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, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x04, 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, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x48, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x05, 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, 0x70, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x05, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x65, 0x65, + 0x72, 0x43, 0x41, 0x50, 0x65, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x50, + 0x65, 0x65, 0x72, 0x43, 0x41, 0x50, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x50, 0x65, 0x65, + 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x30, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, + 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, + 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 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, 0x13, 0x0a, 0x11, 0x45, 0x73, 0x74, 0x61, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x73, 0x0a, - 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, - 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x53, 0x54, - 0x41, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x41, - 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x49, - 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x49, 0x4e, 0x47, - 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x54, 0x45, 0x44, - 0x10, 0x06, 0x32, 0xc0, 0x08, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 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, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x09, 0x45, 0x73, - 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, - 0x64, 0x12, 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, 0x70, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x7c, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, - 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, 0x70, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, - 0x01, 0x0a, 0x0d, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x12, 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, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xfe, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 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, 0x1a, 0x0a, 0x08, 0x52, + 0x6f, 0x6f, 0x74, 0x50, 0x45, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x52, + 0x6f, 0x6f, 0x74, 0x50, 0x45, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, + 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x46, 0x0a, 0x12, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x5b, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, - 0x69, 0x74, 0x65, 0x12, 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, - 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, - 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x61, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x32, + 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x5d, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x08, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xa3, 0x01, 0x0a, 0x18, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 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, 0x70, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x73, 0x22, 0xca, 0x02, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, + 0x5e, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x18, 0x02, 0x20, 0x01, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x0e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x16, + 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x48, 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x17, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x1f, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, + 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 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, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4b, + 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x22, + 0x89, 0x01, 0x0a, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4f, 0x0a, 0x07, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x18, 0x02, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x52, 0x07, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x16, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x17, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4d, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, + 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x2d, 0x0a, 0x1b, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, 0x44, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x1e, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x65, 0x0a, 0x12, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, + 0x01, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x0f, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x12, 0x39, + 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x12, 0x50, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, + 0x22, 0x21, 0x0a, 0x1f, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x53, 0x0a, 0x1f, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x22, 0x0a, 0x20, 0x50, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9a, 0x02, 0x0a, + 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x55, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x17, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 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, 0x3b, 0x0a, 0x15, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xfc, 0x01, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x61, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, + 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, + 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x50, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x51, 0x0a, 0x04, 0x4d, 0x65, 0x74, + 0x61, 0x18, 0x04, 0x20, 0x03, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x13, 0x0a, 0x11, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x73, 0x0a, 0x0c, 0x50, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, + 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x53, 0x54, 0x41, 0x42, 0x4c, + 0x49, 0x53, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, + 0x56, 0x45, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x49, 0x4e, 0x47, 0x10, + 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, + 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x54, 0x45, 0x44, 0x10, 0x06, 0x32, + 0xc0, 0x08, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x09, 0x45, 0x73, 0x74, 0x61, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, + 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x7c, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, - 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x8a, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, + 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, + 0x0b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 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, + 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x7f, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, + 0x12, 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, 0x70, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0xa3, 0x01, 0x0a, 0x18, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 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, 0x70, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x0f, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x12, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x42, - 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, - 0x2b, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0xa2, 0x02, 0x04, 0x48, - 0x43, 0x49, 0x50, 0xaa, 0x02, 0x21, 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, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0xca, 0x02, 0x21, 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, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0xe2, 0x02, 0x2d, 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, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 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, 0x50, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x8a, 0x02, 0x0a, 0x25, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x0c, 0x50, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 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, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x50, + 0xaa, 0x02, 0x21, 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, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0xca, 0x02, 0x21, 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, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0xe2, 0x02, 0x2d, 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, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 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, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2055,79 +2447,88 @@ func file_proto_pbpeering_peering_proto_rawDescGZIP() []byte { } var file_proto_pbpeering_peering_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_pbpeering_peering_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_proto_pbpeering_peering_proto_msgTypes = make([]protoimpl.MessageInfo, 36) var file_proto_pbpeering_peering_proto_goTypes = []interface{}{ - (PeeringState)(0), // 0: hashicorp.consul.internal.peering.PeeringState - (*PeeringSecrets)(nil), // 1: hashicorp.consul.internal.peering.PeeringSecrets - (*Peering)(nil), // 2: hashicorp.consul.internal.peering.Peering - (*PeeringTrustBundle)(nil), // 3: hashicorp.consul.internal.peering.PeeringTrustBundle - (*PeeringReadRequest)(nil), // 4: hashicorp.consul.internal.peering.PeeringReadRequest - (*PeeringReadResponse)(nil), // 5: hashicorp.consul.internal.peering.PeeringReadResponse - (*PeeringListRequest)(nil), // 6: hashicorp.consul.internal.peering.PeeringListRequest - (*PeeringListResponse)(nil), // 7: hashicorp.consul.internal.peering.PeeringListResponse - (*PeeringWriteRequest)(nil), // 8: hashicorp.consul.internal.peering.PeeringWriteRequest - (*PeeringWriteResponse)(nil), // 9: hashicorp.consul.internal.peering.PeeringWriteResponse - (*PeeringDeleteRequest)(nil), // 10: hashicorp.consul.internal.peering.PeeringDeleteRequest - (*PeeringDeleteResponse)(nil), // 11: hashicorp.consul.internal.peering.PeeringDeleteResponse - (*TrustBundleListByServiceRequest)(nil), // 12: hashicorp.consul.internal.peering.TrustBundleListByServiceRequest - (*TrustBundleListByServiceResponse)(nil), // 13: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse - (*TrustBundleReadRequest)(nil), // 14: hashicorp.consul.internal.peering.TrustBundleReadRequest - (*TrustBundleReadResponse)(nil), // 15: hashicorp.consul.internal.peering.TrustBundleReadResponse - (*PeeringTerminateByIDRequest)(nil), // 16: hashicorp.consul.internal.peering.PeeringTerminateByIDRequest - (*PeeringTerminateByIDResponse)(nil), // 17: hashicorp.consul.internal.peering.PeeringTerminateByIDResponse - (*PeeringTrustBundleWriteRequest)(nil), // 18: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest - (*PeeringTrustBundleWriteResponse)(nil), // 19: hashicorp.consul.internal.peering.PeeringTrustBundleWriteResponse - (*PeeringTrustBundleDeleteRequest)(nil), // 20: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteRequest - (*PeeringTrustBundleDeleteResponse)(nil), // 21: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteResponse - (*GenerateTokenRequest)(nil), // 22: hashicorp.consul.internal.peering.GenerateTokenRequest - (*GenerateTokenResponse)(nil), // 23: hashicorp.consul.internal.peering.GenerateTokenResponse - (*EstablishRequest)(nil), // 24: hashicorp.consul.internal.peering.EstablishRequest - (*EstablishResponse)(nil), // 25: hashicorp.consul.internal.peering.EstablishResponse - (*PeeringSecrets_Establishment)(nil), // 26: hashicorp.consul.internal.peering.PeeringSecrets.Establishment - (*PeeringSecrets_Stream)(nil), // 27: hashicorp.consul.internal.peering.PeeringSecrets.Stream - nil, // 28: hashicorp.consul.internal.peering.Peering.MetaEntry - nil, // 29: hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry - nil, // 30: hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry - nil, // 31: hashicorp.consul.internal.peering.EstablishRequest.MetaEntry - (*timestamppb.Timestamp)(nil), // 32: google.protobuf.Timestamp + (PeeringState)(0), // 0: hashicorp.consul.internal.peering.PeeringState + (*SecretsWriteRequest)(nil), // 1: hashicorp.consul.internal.peering.SecretsWriteRequest + (*PeeringSecrets)(nil), // 2: hashicorp.consul.internal.peering.PeeringSecrets + (*Peering)(nil), // 3: hashicorp.consul.internal.peering.Peering + (*PeeringTrustBundle)(nil), // 4: hashicorp.consul.internal.peering.PeeringTrustBundle + (*PeeringReadRequest)(nil), // 5: hashicorp.consul.internal.peering.PeeringReadRequest + (*PeeringReadResponse)(nil), // 6: hashicorp.consul.internal.peering.PeeringReadResponse + (*PeeringListRequest)(nil), // 7: hashicorp.consul.internal.peering.PeeringListRequest + (*PeeringListResponse)(nil), // 8: hashicorp.consul.internal.peering.PeeringListResponse + (*PeeringWriteRequest)(nil), // 9: hashicorp.consul.internal.peering.PeeringWriteRequest + (*PeeringWriteResponse)(nil), // 10: hashicorp.consul.internal.peering.PeeringWriteResponse + (*PeeringDeleteRequest)(nil), // 11: hashicorp.consul.internal.peering.PeeringDeleteRequest + (*PeeringDeleteResponse)(nil), // 12: hashicorp.consul.internal.peering.PeeringDeleteResponse + (*TrustBundleListByServiceRequest)(nil), // 13: hashicorp.consul.internal.peering.TrustBundleListByServiceRequest + (*TrustBundleListByServiceResponse)(nil), // 14: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse + (*TrustBundleReadRequest)(nil), // 15: hashicorp.consul.internal.peering.TrustBundleReadRequest + (*TrustBundleReadResponse)(nil), // 16: hashicorp.consul.internal.peering.TrustBundleReadResponse + (*PeeringTerminateByIDRequest)(nil), // 17: hashicorp.consul.internal.peering.PeeringTerminateByIDRequest + (*PeeringTerminateByIDResponse)(nil), // 18: hashicorp.consul.internal.peering.PeeringTerminateByIDResponse + (*PeeringTrustBundleWriteRequest)(nil), // 19: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest + (*PeeringTrustBundleWriteResponse)(nil), // 20: hashicorp.consul.internal.peering.PeeringTrustBundleWriteResponse + (*PeeringTrustBundleDeleteRequest)(nil), // 21: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteRequest + (*PeeringTrustBundleDeleteResponse)(nil), // 22: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteResponse + (*GenerateTokenRequest)(nil), // 23: hashicorp.consul.internal.peering.GenerateTokenRequest + (*GenerateTokenResponse)(nil), // 24: hashicorp.consul.internal.peering.GenerateTokenResponse + (*EstablishRequest)(nil), // 25: hashicorp.consul.internal.peering.EstablishRequest + (*EstablishResponse)(nil), // 26: hashicorp.consul.internal.peering.EstablishResponse + (*SecretsWriteRequest_GenerateTokenRequest)(nil), // 27: hashicorp.consul.internal.peering.SecretsWriteRequest.GenerateTokenRequest + (*SecretsWriteRequest_ExchangeSecretRequest)(nil), // 28: hashicorp.consul.internal.peering.SecretsWriteRequest.ExchangeSecretRequest + (*SecretsWriteRequest_PromotePendingRequest)(nil), // 29: hashicorp.consul.internal.peering.SecretsWriteRequest.PromotePendingRequest + (*SecretsWriteRequest_EstablishRequest)(nil), // 30: hashicorp.consul.internal.peering.SecretsWriteRequest.EstablishRequest + (*PeeringSecrets_Establishment)(nil), // 31: hashicorp.consul.internal.peering.PeeringSecrets.Establishment + (*PeeringSecrets_Stream)(nil), // 32: hashicorp.consul.internal.peering.PeeringSecrets.Stream + nil, // 33: hashicorp.consul.internal.peering.Peering.MetaEntry + nil, // 34: hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry + nil, // 35: hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry + nil, // 36: hashicorp.consul.internal.peering.EstablishRequest.MetaEntry + (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp } var file_proto_pbpeering_peering_proto_depIdxs = []int32{ - 26, // 0: hashicorp.consul.internal.peering.PeeringSecrets.establishment:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Establishment - 27, // 1: hashicorp.consul.internal.peering.PeeringSecrets.stream:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Stream - 32, // 2: hashicorp.consul.internal.peering.Peering.DeletedAt:type_name -> google.protobuf.Timestamp - 28, // 3: hashicorp.consul.internal.peering.Peering.Meta:type_name -> hashicorp.consul.internal.peering.Peering.MetaEntry - 0, // 4: hashicorp.consul.internal.peering.Peering.State:type_name -> hashicorp.consul.internal.peering.PeeringState - 2, // 5: hashicorp.consul.internal.peering.PeeringReadResponse.Peering:type_name -> hashicorp.consul.internal.peering.Peering - 2, // 6: hashicorp.consul.internal.peering.PeeringListResponse.Peerings:type_name -> hashicorp.consul.internal.peering.Peering - 2, // 7: hashicorp.consul.internal.peering.PeeringWriteRequest.Peering:type_name -> hashicorp.consul.internal.peering.Peering - 1, // 8: hashicorp.consul.internal.peering.PeeringWriteRequest.Secret:type_name -> hashicorp.consul.internal.peering.PeeringSecrets - 29, // 9: hashicorp.consul.internal.peering.PeeringWriteRequest.Meta:type_name -> hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry - 3, // 10: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse.Bundles:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle - 3, // 11: hashicorp.consul.internal.peering.TrustBundleReadResponse.Bundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle - 3, // 12: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest.PeeringTrustBundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle - 30, // 13: hashicorp.consul.internal.peering.GenerateTokenRequest.Meta:type_name -> hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry - 31, // 14: hashicorp.consul.internal.peering.EstablishRequest.Meta:type_name -> hashicorp.consul.internal.peering.EstablishRequest.MetaEntry - 22, // 15: hashicorp.consul.internal.peering.PeeringService.GenerateToken:input_type -> hashicorp.consul.internal.peering.GenerateTokenRequest - 24, // 16: hashicorp.consul.internal.peering.PeeringService.Establish:input_type -> hashicorp.consul.internal.peering.EstablishRequest - 4, // 17: hashicorp.consul.internal.peering.PeeringService.PeeringRead:input_type -> hashicorp.consul.internal.peering.PeeringReadRequest - 6, // 18: hashicorp.consul.internal.peering.PeeringService.PeeringList:input_type -> hashicorp.consul.internal.peering.PeeringListRequest - 10, // 19: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:input_type -> hashicorp.consul.internal.peering.PeeringDeleteRequest - 8, // 20: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:input_type -> hashicorp.consul.internal.peering.PeeringWriteRequest - 12, // 21: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:input_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceRequest - 14, // 22: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:input_type -> hashicorp.consul.internal.peering.TrustBundleReadRequest - 23, // 23: hashicorp.consul.internal.peering.PeeringService.GenerateToken:output_type -> hashicorp.consul.internal.peering.GenerateTokenResponse - 25, // 24: hashicorp.consul.internal.peering.PeeringService.Establish:output_type -> hashicorp.consul.internal.peering.EstablishResponse - 5, // 25: hashicorp.consul.internal.peering.PeeringService.PeeringRead:output_type -> hashicorp.consul.internal.peering.PeeringReadResponse - 7, // 26: hashicorp.consul.internal.peering.PeeringService.PeeringList:output_type -> hashicorp.consul.internal.peering.PeeringListResponse - 11, // 27: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:output_type -> hashicorp.consul.internal.peering.PeeringDeleteResponse - 9, // 28: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:output_type -> hashicorp.consul.internal.peering.PeeringWriteResponse - 13, // 29: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:output_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceResponse - 15, // 30: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:output_type -> hashicorp.consul.internal.peering.TrustBundleReadResponse - 23, // [23:31] is the sub-list for method output_type - 15, // [15:23] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 27, // 0: hashicorp.consul.internal.peering.SecretsWriteRequest.generate_token:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.GenerateTokenRequest + 28, // 1: hashicorp.consul.internal.peering.SecretsWriteRequest.exchange_secret:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.ExchangeSecretRequest + 29, // 2: hashicorp.consul.internal.peering.SecretsWriteRequest.promote_pending:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.PromotePendingRequest + 30, // 3: hashicorp.consul.internal.peering.SecretsWriteRequest.establish:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.EstablishRequest + 31, // 4: hashicorp.consul.internal.peering.PeeringSecrets.establishment:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Establishment + 32, // 5: hashicorp.consul.internal.peering.PeeringSecrets.stream:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Stream + 37, // 6: hashicorp.consul.internal.peering.Peering.DeletedAt:type_name -> google.protobuf.Timestamp + 33, // 7: hashicorp.consul.internal.peering.Peering.Meta:type_name -> hashicorp.consul.internal.peering.Peering.MetaEntry + 0, // 8: hashicorp.consul.internal.peering.Peering.State:type_name -> hashicorp.consul.internal.peering.PeeringState + 3, // 9: hashicorp.consul.internal.peering.PeeringReadResponse.Peering:type_name -> hashicorp.consul.internal.peering.Peering + 3, // 10: hashicorp.consul.internal.peering.PeeringListResponse.Peerings:type_name -> hashicorp.consul.internal.peering.Peering + 3, // 11: hashicorp.consul.internal.peering.PeeringWriteRequest.Peering:type_name -> hashicorp.consul.internal.peering.Peering + 1, // 12: hashicorp.consul.internal.peering.PeeringWriteRequest.SecretsRequest:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest + 34, // 13: hashicorp.consul.internal.peering.PeeringWriteRequest.Meta:type_name -> hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry + 4, // 14: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse.Bundles:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle + 4, // 15: hashicorp.consul.internal.peering.TrustBundleReadResponse.Bundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle + 4, // 16: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest.PeeringTrustBundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle + 35, // 17: hashicorp.consul.internal.peering.GenerateTokenRequest.Meta:type_name -> hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry + 36, // 18: hashicorp.consul.internal.peering.EstablishRequest.Meta:type_name -> hashicorp.consul.internal.peering.EstablishRequest.MetaEntry + 23, // 19: hashicorp.consul.internal.peering.PeeringService.GenerateToken:input_type -> hashicorp.consul.internal.peering.GenerateTokenRequest + 25, // 20: hashicorp.consul.internal.peering.PeeringService.Establish:input_type -> hashicorp.consul.internal.peering.EstablishRequest + 5, // 21: hashicorp.consul.internal.peering.PeeringService.PeeringRead:input_type -> hashicorp.consul.internal.peering.PeeringReadRequest + 7, // 22: hashicorp.consul.internal.peering.PeeringService.PeeringList:input_type -> hashicorp.consul.internal.peering.PeeringListRequest + 11, // 23: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:input_type -> hashicorp.consul.internal.peering.PeeringDeleteRequest + 9, // 24: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:input_type -> hashicorp.consul.internal.peering.PeeringWriteRequest + 13, // 25: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:input_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceRequest + 15, // 26: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:input_type -> hashicorp.consul.internal.peering.TrustBundleReadRequest + 24, // 27: hashicorp.consul.internal.peering.PeeringService.GenerateToken:output_type -> hashicorp.consul.internal.peering.GenerateTokenResponse + 26, // 28: hashicorp.consul.internal.peering.PeeringService.Establish:output_type -> hashicorp.consul.internal.peering.EstablishResponse + 6, // 29: hashicorp.consul.internal.peering.PeeringService.PeeringRead:output_type -> hashicorp.consul.internal.peering.PeeringReadResponse + 8, // 30: hashicorp.consul.internal.peering.PeeringService.PeeringList:output_type -> hashicorp.consul.internal.peering.PeeringListResponse + 12, // 31: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:output_type -> hashicorp.consul.internal.peering.PeeringDeleteResponse + 10, // 32: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:output_type -> hashicorp.consul.internal.peering.PeeringWriteResponse + 14, // 33: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:output_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceResponse + 16, // 34: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:output_type -> hashicorp.consul.internal.peering.TrustBundleReadResponse + 27, // [27:35] is the sub-list for method output_type + 19, // [19:27] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_proto_pbpeering_peering_proto_init() } @@ -2137,7 +2538,7 @@ func file_proto_pbpeering_peering_proto_init() { } if !protoimpl.UnsafeEnabled { file_proto_pbpeering_peering_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringSecrets); i { + switch v := v.(*SecretsWriteRequest); i { case 0: return &v.state case 1: @@ -2149,7 +2550,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Peering); i { + switch v := v.(*PeeringSecrets); i { case 0: return &v.state case 1: @@ -2161,7 +2562,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundle); i { + switch v := v.(*Peering); i { case 0: return &v.state case 1: @@ -2173,7 +2574,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringReadRequest); i { + switch v := v.(*PeeringTrustBundle); i { case 0: return &v.state case 1: @@ -2185,7 +2586,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringReadResponse); i { + switch v := v.(*PeeringReadRequest); i { case 0: return &v.state case 1: @@ -2197,7 +2598,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringListRequest); i { + switch v := v.(*PeeringReadResponse); i { case 0: return &v.state case 1: @@ -2209,7 +2610,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringListResponse); i { + switch v := v.(*PeeringListRequest); i { case 0: return &v.state case 1: @@ -2221,7 +2622,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringWriteRequest); i { + switch v := v.(*PeeringListResponse); i { case 0: return &v.state case 1: @@ -2233,7 +2634,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringWriteResponse); i { + switch v := v.(*PeeringWriteRequest); i { case 0: return &v.state case 1: @@ -2245,7 +2646,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringDeleteRequest); i { + switch v := v.(*PeeringWriteResponse); i { case 0: return &v.state case 1: @@ -2257,7 +2658,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringDeleteResponse); i { + switch v := v.(*PeeringDeleteRequest); i { case 0: return &v.state case 1: @@ -2269,7 +2670,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleListByServiceRequest); i { + switch v := v.(*PeeringDeleteResponse); i { case 0: return &v.state case 1: @@ -2281,7 +2682,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleListByServiceResponse); i { + switch v := v.(*TrustBundleListByServiceRequest); i { case 0: return &v.state case 1: @@ -2293,7 +2694,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleReadRequest); i { + switch v := v.(*TrustBundleListByServiceResponse); i { case 0: return &v.state case 1: @@ -2305,7 +2706,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleReadResponse); i { + switch v := v.(*TrustBundleReadRequest); i { case 0: return &v.state case 1: @@ -2317,7 +2718,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTerminateByIDRequest); i { + switch v := v.(*TrustBundleReadResponse); i { case 0: return &v.state case 1: @@ -2329,7 +2730,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTerminateByIDResponse); i { + switch v := v.(*PeeringTerminateByIDRequest); i { case 0: return &v.state case 1: @@ -2341,7 +2742,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleWriteRequest); i { + switch v := v.(*PeeringTerminateByIDResponse); i { case 0: return &v.state case 1: @@ -2353,7 +2754,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleWriteResponse); i { + switch v := v.(*PeeringTrustBundleWriteRequest); i { case 0: return &v.state case 1: @@ -2365,7 +2766,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleDeleteRequest); i { + switch v := v.(*PeeringTrustBundleWriteResponse); i { case 0: return &v.state case 1: @@ -2377,7 +2778,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleDeleteResponse); i { + switch v := v.(*PeeringTrustBundleDeleteRequest); i { case 0: return &v.state case 1: @@ -2389,7 +2790,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenerateTokenRequest); i { + switch v := v.(*PeeringTrustBundleDeleteResponse); i { case 0: return &v.state case 1: @@ -2401,7 +2802,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenerateTokenResponse); i { + switch v := v.(*GenerateTokenRequest); i { case 0: return &v.state case 1: @@ -2413,7 +2814,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstablishRequest); i { + switch v := v.(*GenerateTokenResponse); i { case 0: return &v.state case 1: @@ -2425,7 +2826,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstablishResponse); i { + switch v := v.(*EstablishRequest); i { case 0: return &v.state case 1: @@ -2437,7 +2838,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringSecrets_Establishment); i { + switch v := v.(*EstablishResponse); i { case 0: return &v.state case 1: @@ -2449,6 +2850,66 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SecretsWriteRequest_GenerateTokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeering_peering_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SecretsWriteRequest_ExchangeSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeering_peering_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SecretsWriteRequest_PromotePendingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeering_peering_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SecretsWriteRequest_EstablishRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeering_peering_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeeringSecrets_Establishment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeering_peering_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeeringSecrets_Stream); i { case 0: return &v.state @@ -2461,13 +2922,19 @@ func file_proto_pbpeering_peering_proto_init() { } } } + file_proto_pbpeering_peering_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*SecretsWriteRequest_GenerateToken)(nil), + (*SecretsWriteRequest_ExchangeSecret)(nil), + (*SecretsWriteRequest_PromotePending)(nil), + (*SecretsWriteRequest_Establish)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_pbpeering_peering_proto_rawDesc, NumEnums: 1, - NumMessages: 31, + NumMessages: 36, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/pbpeering/peering.proto b/proto/pbpeering/peering.proto index 54eafc232e..4283c3c321 100644 --- a/proto/pbpeering/peering.proto +++ b/proto/pbpeering/peering.proto @@ -53,6 +53,59 @@ enum PeeringState { TERMINATED = 6; } +// SecretsWriteRequest encodes a request to write a peering secret as the result +// of some operation. Different operations, such as generating a peering token, +// lead to modifying the known secrets associated with a peering. +message SecretsWriteRequest { + // PeerID is the local UUID of the peering this request applies to. + string PeerID = 1; + + oneof Request { + GenerateTokenRequest generate_token = 2; + ExchangeSecretRequest exchange_secret = 3; + PromotePendingRequest promote_pending = 4; + EstablishRequest establish = 5; + } + + // GenerateTokenRequest encodes a request to persist a peering establishment + // secret. It is triggered by generating a new peering token for a peer cluster. + message GenerateTokenRequest { + // establishment_secret is the proposed secret ID to store as the establishment + // secret for this peering. + string establishment_secret = 1; + } + + // ExchangeSecretRequest encodes a request to persist a pending stream secret + // secret. It is triggered by an acceptor peer generating a long-lived stream secret + // in exchange for an establishment secret. + message ExchangeSecretRequest { + // establishment_secret is the secret to exchange for the given pending stream secret. + string establishment_secret = 1; + + // pending_stream_secret is the proposed secret ID to store as the pending stream + // secret for this peering. + string pending_stream_secret = 2; + } + + // PromotePendingRequest encodes a request to promote a pending stream secret + // to be an active stream secret. It is triggered when the accepting stream handler + // validates an Open request from a peer with a pending stream secret. + message PromotePendingRequest { + // active_stream_secret is the proposed secret ID to store as the active stream + // secret for this peering. + string active_stream_secret = 1; + } + + // EstablishRequest encodes a request to persist an active stream secret. + // It is triggered after a dialing peer exchanges their establishment secret + // for a long-lived active stream secret. + message EstablishRequest { + // active_stream_secret is the proposed secret ID to store as the active stream + // secret for this peering. + string active_stream_secret = 1; + } +} + // PeeringSecrets defines a secret used for authenticating/authorizing peer clusters. message PeeringSecrets { // PeerID is the local UUID of the peering this secret was generated for. @@ -195,10 +248,10 @@ message PeeringWriteRequest { // Peering is the peering to write with the request. Peering Peering = 1; - // PeeringSecrets contains the optional peering secrets to persist + // SecretsWriteRequest contains the optional peering secrets to persist // with the peering. Peering secrets are not embedded in the peering // object to avoid leaking them. - PeeringSecrets Secret = 2; + SecretsWriteRequest SecretsRequest = 2; map Meta = 3; } diff --git a/sdk/freeport/freeport.go b/sdk/freeport/freeport.go index e35d662ada..6eda1d4279 100644 --- a/sdk/freeport/freeport.go +++ b/sdk/freeport/freeport.go @@ -83,6 +83,10 @@ var ( // stopWg is used to keep track of background goroutines that are still // alive. Only really exists for the safety of reset() during unit tests. stopWg sync.WaitGroup + + // portLastUser associates ports with a test name in order to debug + // which test may be leaking unclosed TCP connections. + portLastUser map[int]string ) // initialize is used to initialize freeport. @@ -127,6 +131,8 @@ func initialize() { stopWg.Add(1) stopCh = make(chan struct{}) + + portLastUser = make(map[int]string) // Note: we pass this param explicitly to the goroutine so that we can // freely recreate the underlying stop channel during reset() after closing // the original. @@ -166,6 +172,7 @@ func reset() { freePorts = nil pendingPorts = nil + portLastUser = nil total = 0 } @@ -195,6 +202,8 @@ func checkFreedPortsOnce() { if used := isPortInUse(port); !used { freePorts.PushBack(port) remove = append(remove, elem) + } else { + logf("WARN", "port %d still being used by %q", port, portLastUser[port]) } } @@ -314,7 +323,6 @@ func Take(n int) (ports []int, err error) { ports = append(ports, port) } - // logf("DEBUG", "free ports: %v", ports) return ports, nil } @@ -400,9 +408,10 @@ func logf(severity string, format string, a ...interface{}) { // In the future new methods may be added to this interface, but those methods // should always be implemented by *testing.T type TestingT interface { + Cleanup(func()) Helper() Fatalf(format string, args ...interface{}) - Cleanup(func()) + Name() string } // GetN returns n free ports from the reserved port block, and returns the @@ -413,8 +422,15 @@ func GetN(t TestingT, n int) []int { if err != nil { t.Fatalf("failed to take %v ports: %w", n, err) } + logf("DEBUG", "Test %q took ports %v", t.Name(), ports) + mu.Lock() + for _, p := range ports { + portLastUser[p] = t.Name() + } + mu.Unlock() t.Cleanup(func() { Return(ports) + logf("DEBUG", "Test %q returned ports %v", t.Name(), ports) }) return ports } diff --git a/sdk/testutil/server.go b/sdk/testutil/server.go index 77fd90e824..a657919adc 100644 --- a/sdk/testutil/server.go +++ b/sdk/testutil/server.go @@ -105,6 +105,7 @@ type TestServerConfig struct { Connect map[string]interface{} `json:"connect,omitempty"` EnableDebug bool `json:"enable_debug,omitempty"` SkipLeaveOnInt bool `json:"skip_leave_on_interrupt"` + Peering *TestPeeringConfig `json:"peering,omitempty"` ReadyTimeout time.Duration `json:"-"` StopTimeout time.Duration `json:"-"` Stdout io.Writer `json:"-"` @@ -139,6 +140,10 @@ type TestTokens struct { AgentRecovery string `json:"agent_master,omitempty"` } +type TestPeeringConfig struct { + Enabled bool `json:"enabled,omitempty"` +} + // ServerConfigCallback is a function interface which can be // passed to NewTestServerConfig to modify the server config. type ServerConfigCallback func(c *TestServerConfig) @@ -151,10 +156,7 @@ func defaultServerConfig(t TestingTB) *TestServerConfig { panic(err) } - ports, err := freeport.Take(7) - if err != nil { - t.Fatalf("failed to take ports: %v", err) - } + ports := freeport.GetN(t, 7) logBuffer := NewLogBuffer(t) @@ -189,11 +191,9 @@ func defaultServerConfig(t TestingTB) *TestServerConfig { "cluster_id": "11111111-2222-3333-4444-555555555555", }, }, - ReturnPorts: func() { - freeport.Return(ports) - }, - Stdout: logBuffer, - Stderr: logBuffer, + Stdout: logBuffer, + Stderr: logBuffer, + Peering: &TestPeeringConfig{Enabled: true}, } } @@ -265,7 +265,6 @@ func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, er b, err := json.Marshal(cfg) if err != nil { - cfg.ReturnPorts() os.RemoveAll(tmpdir) return nil, errors.Wrap(err, "failed marshaling json") } @@ -273,7 +272,6 @@ func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, er t.Logf("CONFIG JSON: %s", string(b)) configFile := filepath.Join(tmpdir, "config.json") if err := ioutil.WriteFile(configFile, b, 0644); err != nil { - cfg.ReturnPorts() os.RemoveAll(tmpdir) return nil, errors.Wrap(err, "failed writing config content") } @@ -285,7 +283,6 @@ func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, er cmd.Stdout = cfg.Stdout cmd.Stderr = cfg.Stderr if err := cmd.Start(); err != nil { - cfg.ReturnPorts() os.RemoveAll(tmpdir) return nil, errors.Wrap(err, "failed starting command") } @@ -330,7 +327,6 @@ func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, er // Stop stops the test Consul server, and removes the Consul data // directory once we are done. func (s *TestServer) Stop() error { - defer s.Config.ReturnPorts() defer os.RemoveAll(s.tmpdir) // There was no process diff --git a/sdk/testutil/types.go b/sdk/testutil/types.go index d6b5841c50..1cd018f4da 100644 --- a/sdk/testutil/types.go +++ b/sdk/testutil/types.go @@ -9,4 +9,5 @@ type TestingTB interface { Logf(format string, args ...interface{}) Name() string Fatalf(fmt string, args ...interface{}) + Helper() } diff --git a/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-none/primary/verify.bats b/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-none/primary/verify.bats index 85bb95e1ac..a87c31261f 100644 --- a/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-none/primary/verify.bats +++ b/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-none/primary/verify.bats @@ -40,7 +40,8 @@ load helpers # Note: when failover is configured the cluster is named for the original # service not any destination related to failover. @test "s1 upstream should have healthy endpoints for s2 in both primary and failover" { - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 2 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.secondary HEALTHY 1 } @test "s1 upstream should be able to connect to s2 via upstream s2 to start" { @@ -48,7 +49,7 @@ load helpers } @test "s1 upstream made 1 connection" { - assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary.*cx_total" 1 + assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.failover-target~s2.default.primary.*cx_total" 1 } ################ @@ -63,8 +64,8 @@ load helpers } @test "s1 upstream should have healthy endpoints for s2 secondary and unhealthy endpoints for s2 primary" { - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 1 - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary UNHEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary UNHEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.secondary HEALTHY 1 } @test "reset envoy statistics" { @@ -76,5 +77,5 @@ load helpers } @test "s1 upstream made 1 connection again" { - assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary.*cx_total" 1 + assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.failover-target~s2.default.secondary.*cx_total" 1 } diff --git a/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-remote/primary/verify.bats b/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-remote/primary/verify.bats index f80ba7a197..d46c736401 100644 --- a/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-remote/primary/verify.bats +++ b/test/integration/connect/envoy/case-cfg-resolver-dc-failover-gateways-remote/primary/verify.bats @@ -34,10 +34,6 @@ load helpers retry_long nc -z consul-secondary-client:4432 } -@test "wait until the first cluster is configured" { - assert_envoy_dynamic_cluster_exists 127.0.0.1:19000 s2.default.primary s2.default.primary -} - ################ # PHASE 1: we show that by default requests are served from the primary @@ -45,8 +41,8 @@ load helpers # service not any destination related to failover. @test "s1 upstream should have healthy endpoints for s2 in both primary and failover" { # in mesh gateway remote or local mode only the current leg of failover manifests in the load assignments - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 1 - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary UNHEALTHY 0 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary UNHEALTHY 0 } @test "s1 upstream should be able to connect to s2 via upstream s2 to start" { @@ -54,7 +50,7 @@ load helpers } @test "s1 upstream made 1 connection" { - assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary.*cx_total" 1 + assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.failover-target~s2.default.primary.*cx_total" 1 } ################ @@ -68,14 +64,11 @@ load helpers assert_service_has_healthy_instances s2 0 primary } -@test "wait until the failover cluster is configured" { - assert_envoy_dynamic_cluster_exists 127.0.0.1:19000 s2.default.primary s2.default.secondary -} - @test "s1 upstream should have healthy endpoints for s2 secondary" { # in mesh gateway remote or local mode only the current leg of failover manifests in the load assignments - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 1 - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary UNHEALTHY 0 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary HEALTHY 0 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.secondary HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.secondary UNHEALTHY 0 } @test "reset envoy statistics" { @@ -87,5 +80,5 @@ load helpers } @test "s1 upstream made 1 connection again" { - assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary.*cx_total" 1 + assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.failover-target~s2.default.secondary.*cx_total" 1 } diff --git a/test/integration/connect/envoy/case-cfg-resolver-svc-failover/verify.bats b/test/integration/connect/envoy/case-cfg-resolver-svc-failover/verify.bats index 85fb0aa2c2..6f684e7123 100644 --- a/test/integration/connect/envoy/case-cfg-resolver-svc-failover/verify.bats +++ b/test/integration/connect/envoy/case-cfg-resolver-svc-failover/verify.bats @@ -53,7 +53,7 @@ load helpers # Note: when failover is configured the cluster is named for the original # service not any destination related to failover. @test "s1 upstream should have healthy endpoints for s2 and s3 together" { - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 2 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary HEALTHY 1 } @test "s1 upstream should be able to connect to s2 via upstream s2 to start" { @@ -65,11 +65,10 @@ load helpers } @test "s1 upstream should have healthy endpoints for s3-v1 and unhealthy endpoints for s2" { - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 1 - assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary UNHEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~v1.s3.default.primary HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 failover-target~s2.default.primary UNHEALTHY 1 } @test "s1 upstream should be able to connect to s3-v1 now" { assert_expected_fortio_name s3-v1 } - diff --git a/test/integration/connect/envoy/case-cross-peers-http-router/alpha/base.hcl b/test/integration/connect/envoy/case-cross-peers-http-router/alpha/base.hcl index 68265638f9..f81ab0edd6 100644 --- a/test/integration/connect/envoy/case-cross-peers-http-router/alpha/base.hcl +++ b/test/integration/connect/envoy/case-cross-peers-http-router/alpha/base.hcl @@ -1,2 +1,5 @@ primary_datacenter = "alpha" log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers-http-router/primary/base.hcl b/test/integration/connect/envoy/case-cross-peers-http-router/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-cross-peers-http-router/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers-http/alpha/base.hcl b/test/integration/connect/envoy/case-cross-peers-http/alpha/base.hcl index 68265638f9..f81ab0edd6 100644 --- a/test/integration/connect/envoy/case-cross-peers-http/alpha/base.hcl +++ b/test/integration/connect/envoy/case-cross-peers-http/alpha/base.hcl @@ -1,2 +1,5 @@ primary_datacenter = "alpha" log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers-http/primary/base.hcl b/test/integration/connect/envoy/case-cross-peers-http/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-cross-peers-http/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/base.hcl b/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/base.hcl index 68265638f9..f81ab0edd6 100644 --- a/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/base.hcl +++ b/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/base.hcl @@ -1,2 +1,5 @@ primary_datacenter = "alpha" log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/primary/base.hcl b/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers/alpha/base.hcl b/test/integration/connect/envoy/case-cross-peers/alpha/base.hcl index 68265638f9..f81ab0edd6 100644 --- a/test/integration/connect/envoy/case-cross-peers/alpha/base.hcl +++ b/test/integration/connect/envoy/case-cross-peers/alpha/base.hcl @@ -1,2 +1,5 @@ primary_datacenter = "alpha" log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cross-peers/primary/base.hcl b/test/integration/connect/envoy/case-cross-peers/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-cross-peers/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index cc678b5032..c8c392c34f 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -555,7 +555,7 @@ function suite_setup { # pre-build the verify container echo "Rebuilding 'bats-verify' image..." - docker build -t bats-verify -f Dockerfile-bats . + retry_default docker build -t bats-verify -f Dockerfile-bats . # if this fails on CircleCI your first thing to try would be to upgrade # the machine image to the latest version using this listing: @@ -566,13 +566,13 @@ function suite_setup { # pre-build the consul+envoy container echo "Rebuilding 'consul-dev-envoy:${ENVOY_VERSION}' image..." - docker build -t consul-dev-envoy:${ENVOY_VERSION} \ + retry_default docker build -t consul-dev-envoy:${ENVOY_VERSION} \ --build-arg ENVOY_VERSION=${ENVOY_VERSION} \ -f Dockerfile-consul-envoy . # pre-build the test-sds-server container echo "Rebuilding 'test-sds-server' image..." - docker build -t test-sds-server -f Dockerfile-test-sds-server test-sds-server + retry_default docker build -t test-sds-server -f Dockerfile-test-sds-server test-sds-server } function suite_teardown { @@ -877,7 +877,7 @@ function common_run_container_tcpdump { # we cant run this in circle but its only here to temporarily enable. - docker build -t envoy-tcpdump -f Dockerfile-tcpdump . + retry_default docker build -t envoy-tcpdump -f Dockerfile-tcpdump . docker run -d --name $(container_name_prev) \ $(network_snippet $DC) \ diff --git a/tlsutil/config.go b/tlsutil/config.go index 7c9e6d2ad6..2e1614165e 100644 --- a/tlsutil/config.go +++ b/tlsutil/config.go @@ -102,6 +102,10 @@ type ProtocolConfig struct { // // Note: this setting only applies to the Internal RPC configuration. VerifyServerHostname bool + + // UseAutoCert is used to enable usage of auto_encrypt/auto_config generated + // certificate & key material on external gRPC listener. + UseAutoCert bool } // Config configures the Configurator. @@ -167,6 +171,10 @@ type protocolConfig struct { // combinedCAPool is a pool containing both manualCAPEMs and the certificates // received from auto-config/auto-encrypt. combinedCAPool *x509.CertPool + + // useAutoCert indicates wether we should use auto-encrypt/config data + // for TLS server/listener. NOTE: Only applies to external GRPC Server. + useAutoCert bool } // Configurator provides tls.Config and net.Dial wrappers to enable TLS for @@ -323,6 +331,7 @@ func (c *Configurator) loadProtocolConfig(base Config, pc ProtocolConfig) (*prot manualCAPEMs: pems, manualCAPool: manualPool, combinedCAPool: combinedPool, + useAutoCert: pc.UseAutoCert, }, nil } @@ -620,16 +629,15 @@ func (c *Configurator) Cert() *tls.Certificate { return cert } -// GRPCTLSConfigured returns whether there's a TLS certificate configured for -// gRPC (either manually or by auto-config/auto-encrypt). It is checked, along -// with the presence of an HTTPS port, to determine whether to enable TLS on -// incoming gRPC connections. +// GRPCServerUseTLS returns whether there's a TLS certificate configured for +// (external) gRPC (either manually or by auto-config/auto-encrypt), and use +// of TLS for gRPC has not been explicitly disabled at auto-encrypt. // // This function acquires a read lock because it reads from the config. -func (c *Configurator) GRPCTLSConfigured() bool { +func (c *Configurator) GRPCServerUseTLS() bool { c.lock.RLock() defer c.lock.RUnlock() - return c.grpc.cert != nil || c.autoTLS.cert != nil + return c.grpc.cert != nil || (c.grpc.useAutoCert && c.autoTLS.cert != nil) } // VerifyIncomingRPC returns true if we should verify incoming connnections to diff --git a/tlsutil/config_test.go b/tlsutil/config_test.go index 75fa839458..fc817aec69 100644 --- a/tlsutil/config_test.go +++ b/tlsutil/config_test.go @@ -1465,7 +1465,7 @@ func TestConfigurator_AuthorizeInternalRPCServerConn(t *testing.T) { }) } -func TestConfigurator_GRPCTLSConfigured(t *testing.T) { +func TestConfigurator_GRPCServerUseTLS(t *testing.T) { t.Run("certificate manually configured", func(t *testing.T) { c := makeConfigurator(t, Config{ GRPC: ProtocolConfig{ @@ -1473,22 +1473,47 @@ func TestConfigurator_GRPCTLSConfigured(t *testing.T) { KeyFile: "../test/hostname/Alice.key", }, }) - require.True(t, c.GRPCTLSConfigured()) + require.True(t, c.GRPCServerUseTLS()) }) - t.Run("AutoTLS", func(t *testing.T) { + t.Run("no certificate", func(t *testing.T) { + c := makeConfigurator(t, Config{}) + require.False(t, c.GRPCServerUseTLS()) + }) + + t.Run("AutoTLS (default)", func(t *testing.T) { c := makeConfigurator(t, Config{}) bobCert := loadFile(t, "../test/hostname/Bob.crt") bobKey := loadFile(t, "../test/hostname/Bob.key") require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) - - require.True(t, c.GRPCTLSConfigured()) + require.False(t, c.GRPCServerUseTLS()) }) - t.Run("no certificate", func(t *testing.T) { - c := makeConfigurator(t, Config{}) - require.False(t, c.GRPCTLSConfigured()) + t.Run("AutoTLS w/ UseAutoCert Disabled", func(t *testing.T) { + c := makeConfigurator(t, Config{ + GRPC: ProtocolConfig{ + UseAutoCert: false, + }, + }) + + bobCert := loadFile(t, "../test/hostname/Bob.crt") + bobKey := loadFile(t, "../test/hostname/Bob.key") + require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) + require.False(t, c.GRPCServerUseTLS()) + }) + + t.Run("AutoTLS w/ UseAutoCert Enabled", func(t *testing.T) { + c := makeConfigurator(t, Config{ + GRPC: ProtocolConfig{ + UseAutoCert: true, + }, + }) + + bobCert := loadFile(t, "../test/hostname/Bob.crt") + bobKey := loadFile(t, "../test/hostname/Bob.key") + require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) + require.True(t, c.GRPCServerUseTLS()) }) } diff --git a/ui/.gitignore b/ui/.gitignore index 08df27ddb9..6bd9a0135a 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -12,6 +12,7 @@ node_modules .pnp* .sass-cache .DS_Store +.tool-versions connect.lock coverage coverage_* diff --git a/ui/packages/consul-peerings/app/components/consul/peer/form/generate/chart.xstate.js b/ui/packages/consul-peerings/app/components/consul/peer/form/generate/chart.xstate.js index 1c3b43f9db..7ae1bbf988 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/form/generate/chart.xstate.js +++ b/ui/packages/consul-peerings/app/components/consul/peer/form/generate/chart.xstate.js @@ -20,6 +20,11 @@ export default { } }, success: { + on: { + RESET: { + target: 'idle' + } + } }, error: {}, }, diff --git a/ui/packages/consul-peerings/app/components/consul/peer/form/generate/index.hbs b/ui/packages/consul-peerings/app/components/consul/peer/form/generate/index.hbs index 2d0667a659..0e4e63a658 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/form/generate/index.hbs +++ b/ui/packages/consul-peerings/app/components/consul/peer/form/generate/index.hbs @@ -17,7 +17,20 @@ id={{id}} > - + + + + +

+ Error
+ {{fsm.state.context.error.message}} +

+
+
+
{{yield (hash Fieldsets=(component "consul/peer/form/generate/fieldsets" item=@item @@ -43,6 +56,9 @@ @onchange (pick 'data' (fn fsm.dispatch 'SUCCESS')) }} + @onerror={{queue + (fn fsm.dispatch 'ERROR') + }} />
@@ -52,7 +68,10 @@ item=@item token=fsm.state.context.PeeringToken regenerate=@regenerate - onclick=(queue (set @item 'Name' '')) + onclick=(queue + (set @item 'Name' '') + (fn fsm.dispatch 'RESET') + ) ) Actions=(component "consul/peer/form/token/actions" token=fsm.state.context.PeeringToken diff --git a/ui/packages/consul-peerings/app/components/consul/peer/form/index.hbs b/ui/packages/consul-peerings/app/components/consul/peer/form/index.hbs index 4a9971a97e..b8d09b8c8c 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/form/index.hbs +++ b/ui/packages/consul-peerings/app/components/consul/peer/form/index.hbs @@ -27,7 +27,7 @@ @@ -44,7 +44,7 @@ diff --git a/ui/packages/consul-peerings/app/components/consul/peer/form/initiate/index.hbs b/ui/packages/consul-peerings/app/components/consul/peer/form/initiate/index.hbs index a0b1294bf4..8b29c46b8c 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/form/initiate/index.hbs +++ b/ui/packages/consul-peerings/app/components/consul/peer/form/initiate/index.hbs @@ -17,6 +17,19 @@ @label={{'peer'}} @onchange={{fn (optional @onsubmit) @item}} as |writer|> + + + +

+ Error
+ {{error.message}} +

+
+
+
{{#let (unique-id) @@ -27,15 +40,15 @@ as |id|}} > {{yield (hash Fieldsets=(component "consul/peer/form/initiate/fieldsets" - item=@item + item=@item ) Actions=(component "consul/peer/form/initiate/actions" - item=@item - id=id + item=@item + id=id ) )}} {{/let}} - + diff --git a/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs b/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs index 3282b987c6..16005e0b73 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs +++ b/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs @@ -42,14 +42,6 @@ as |item index|> {{#if (can 'delete peer' item=item)}} - - - View - - {{#if (can "write peer" item=item)}} diff --git a/ui/packages/consul-ui/app/components/composite-row/index.scss b/ui/packages/consul-ui/app/components/composite-row/index.scss index bd66491a7f..1dce70e4bb 100644 --- a/ui/packages/consul-ui/app/components/composite-row/index.scss +++ b/ui/packages/consul-ui/app/components/composite-row/index.scss @@ -95,7 +95,7 @@ } %composite-row-detail .policy::before { - @extend %with-file-fill-mask, %as-pseudo; + @extend %with-file-text-mask, %as-pseudo; margin-right: 3px; } %composite-row-detail .role::before { diff --git a/ui/packages/consul-ui/app/components/consul/bucket/list/index.js b/ui/packages/consul-ui/app/components/consul/bucket/list/index.js index c874ac91be..c1aa8afe92 100644 --- a/ui/packages/consul-ui/app/components/consul/bucket/list/index.js +++ b/ui/packages/consul-ui/app/components/consul/bucket/list/index.js @@ -58,7 +58,7 @@ export default class ConsulBucketList extends Component { get namespacePart() { const { item, nspace } = this.args; - const { abilities, partitionPart } = this; + const { abilities, partitionPart, peerPart } = this; const nspaceItem = { type: 'nspace', @@ -71,15 +71,13 @@ export default class ConsulBucketList extends Component { return [nspaceItem]; } + if (peerPart.length && abilities.can('use nspaces')) { + return [nspaceItem]; + } + if (nspace && abilities.can('use nspaces')) { if (item.Namespace !== nspace) { - return [ - { - type: 'nspace', - label: 'Namespace', - item: item.Namespace, - }, - ]; + return [nspaceItem]; } } diff --git a/ui/packages/consul-ui/app/components/consul/external-source/index.scss b/ui/packages/consul-ui/app/components/consul/external-source/index.scss index b05acb45b2..b876b48fd4 100644 --- a/ui/packages/consul-ui/app/components/consul/external-source/index.scss +++ b/ui/packages/consul-ui/app/components/consul/external-source/index.scss @@ -1,6 +1,11 @@ .consul-external-source { @extend %pill-200, %frame-gray-600, %p1; } + +.consul-external-source::before { + --icon-size: icon-300; +} + .consul-external-source.kubernetes::before { @extend %with-logo-kubernetes-color-icon, %as-pseudo; } @@ -15,10 +20,10 @@ @extend %with-logo-consul-color-icon, %as-pseudo; } .consul-external-source.vault::before { - @extend %with-vault-100; + @extend %with-vault-300; } .consul-external-source.aws::before { - @extend %with-aws-100; + @extend %with-aws-300; } .consul-external-source.leader::before { @extend %with-star-outline-mask, %as-pseudo; diff --git a/ui/packages/consul-ui/app/components/consul/kind/index.scss b/ui/packages/consul-ui/app/components/consul/kind/index.scss index 7467195f2c..0431ac3068 100644 --- a/ui/packages/consul-ui/app/components/consul/kind/index.scss +++ b/ui/packages/consul-ui/app/components/consul/kind/index.scss @@ -3,4 +3,5 @@ } .consul-kind::before { @extend %with-gateway-mask, %as-pseudo; + --icon-size: icon-300; } diff --git a/ui/packages/consul-ui/app/components/consul/service/list/index.hbs b/ui/packages/consul-ui/app/components/consul/service/list/index.hbs index 551cf027cf..a97184b984 100644 --- a/ui/packages/consul-ui/app/components/consul/service/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/service/list/index.hbs @@ -56,6 +56,11 @@ {{format-number item.InstanceCount}} {{pluralize item.InstanceCount 'instance' without-count=true}} {{/if}} + {{#if (eq item.Kind 'terminating-gateway')}} {{format-number item.GatewayConfig.AssociatedServiceCount}} {{pluralize item.GatewayConfig.AssociatedServiceCount 'linked service' without-count=true}} @@ -87,11 +92,6 @@ {{/if}} {{/if}} - \ No newline at end of file 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 f74926e25c..a73d0b6cad 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 @@ -139,27 +139,8 @@ as |key value|}} - {{#let components.Optgroup components.Option as |Optgroup Option|}} -{{#let - (reject-by 'Partition' @partition @partitions) -as |nonDefaultPartitions|}} -{{#if (gt nonDefaultPartitions.length 0)}} - - {{#each @partitions as |partition|}} - - {{/each}} - -{{/if}} -{{/let}} - + {{#let components.Option as |Option|}} {{#if (gt @sources.length 0)}} - {{#each @sources as |source|}} - {{/if}} {{/let}} diff --git a/ui/packages/consul-ui/app/components/copyable-code/index.scss b/ui/packages/consul-ui/app/components/copyable-code/index.scss index 7745aadb68..7fcf821193 100644 --- a/ui/packages/consul-ui/app/components/copyable-code/index.scss +++ b/ui/packages/consul-ui/app/components/copyable-code/index.scss @@ -26,8 +26,16 @@ } .copy-button { position: absolute; - top: 7px; - right: 12px; + top: 0; + right: 0; + z-index: 1; + } + .copy-button button { + width: 40px; + height: 40px; + } + .copy-button button:empty::after { + display: none; } button[aria-expanded] { margin-top: 1px; diff --git a/ui/packages/consul-ui/app/components/data-writer/index.hbs b/ui/packages/consul-ui/app/components/data-writer/index.hbs index 53af6c8565..5f0ecdfcdb 100644 --- a/ui/packages/consul-ui/app/components/data-writer/index.hbs +++ b/ui/packages/consul-ui/app/components/data-writer/index.hbs @@ -19,7 +19,7 @@ @item={{data}} @data={{null}} @onchange={{action dispatch "SUCCESS"}} - @onerror={{queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR")}} + @onerror={{action "error"}} /> @@ -28,7 +28,7 @@ @sink={{sink}} @item={{data}} @onchange={{action dispatch "SUCCESS"}} - @onerror={{queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR")}} + @onerror={{action "error"}} /> diff --git a/ui/packages/consul-ui/app/components/data-writer/index.js b/ui/packages/consul-ui/app/components/data-writer/index.js index 86b3452421..3dd1f8ab94 100644 --- a/ui/packages/consul-ui/app/components/data-writer/index.js +++ b/ui/packages/consul-ui/app/components/data-writer/index.js @@ -21,5 +21,17 @@ export default Component.extend(Slotted, { set(this, 'data', data); this.dispatch('PERSIST'); }, + error: function(data, e) { + if (e && typeof e.preventDefault === 'function') { + e.preventDefault(); + } + set( + this, + 'error', + typeof data.error.errors !== 'undefined' ? + data.error.errors.firstObject : data.error + ); + this.dispatch('ERROR'); + }, }, }); diff --git a/ui/packages/consul-ui/app/components/pill/index.scss b/ui/packages/consul-ui/app/components/pill/index.scss index d08626db8b..c528bd9ff3 100644 --- a/ui/packages/consul-ui/app/components/pill/index.scss +++ b/ui/packages/consul-ui/app/components/pill/index.scss @@ -18,6 +18,9 @@ span.policy-node-identity::before { span.policy-service-identity::before { content: 'Service Identity: '; } +%pill::before { + --icon-size: icon-300; +} %pill.leader::before { @extend %with-star-outline-mask, %as-pseudo; } diff --git a/ui/packages/consul-ui/app/modifiers/validate.js b/ui/packages/consul-ui/app/modifiers/validate.js index 5a74598b47..72c0b8bb93 100644 --- a/ui/packages/consul-ui/app/modifiers/validate.js +++ b/ui/packages/consul-ui/app/modifiers/validate.js @@ -34,10 +34,10 @@ export default class ValidateModifier extends Modifier { } if(Object.keys(errors).length > 0) { state.context.errors = errors; - this.hash.chart.dispatch("ERROR"); + this.hash.chart.dispatch("ERROR", state.context); } else { state.context.errors = null; - this.hash.chart.dispatch("RESET"); + this.hash.chart.dispatch("RESET", state.context); } } @@ -61,7 +61,7 @@ export default class ValidateModifier extends Modifier { }); if(Object.keys(state.context.errors).length === 0) { state.context.errors = null; - this.hash.chart.dispatch("RESET"); + this.hash.chart.dispatch("RESET", state.context); } } } diff --git a/ui/packages/consul-ui/app/services/repository/peer.js b/ui/packages/consul-ui/app/services/repository/peer.js index 1062ed2b69..5f66708e2a 100644 --- a/ui/packages/consul-ui/app/services/repository/peer.js +++ b/ui/packages/consul-ui/app/services/repository/peer.js @@ -2,116 +2,124 @@ import RepositoryService from 'consul-ui/services/repository'; import dataSource from 'consul-ui/decorators/data-source'; export default class PeerService extends RepositoryService { - getModelName() { return 'peer'; } @dataSource('/:partition/:ns/:dc/peering/token-for/:name') - async fetchToken({dc, ns, partition, name}, configuration, request) { - return (await request` + async fetchToken({ dc, ns, partition, name }, configuration, request) { + return ( + await request` POST /v1/peering/token ${{ PeerName: name, - Datacenter: dc, Partition: partition || undefined, }} - `)((headers, body, cache) => body) + ` + )((headers, body, cache) => body); } @dataSource('/:partition/:ns/:dc/peers') async fetchAll({ dc, ns, partition }, { uri }, request) { - return (await request` + return ( + await request` GET /v1/peerings ${{ partition, }} - `)( - (headers, body, cache) => { - return { - meta: { - version: 2, - interval: 10000, - uri: uri, - }, - body: body.map(item => { - return cache( - { - ...item, - Datacenter: dc, - Partition: partition, - }, - uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}` - ); - }) - }; - }); + ` + )((headers, body, cache) => { + return { + meta: { + version: 2, + interval: 10000, + uri: uri, + }, + body: body.map(item => { + return cache( + { + ...item, + Datacenter: dc, + Partition: partition, + }, + uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}` + ); + }), + }; + }); } + @dataSource('/:partition/:ns/:dc/peer-generate/') + @dataSource('/:partition/:ns/:dc/peer-initiate/') @dataSource('/:partition/:ns/:dc/peer/:name') - async fetchOne({partition, ns, dc, name}, { uri }, request) { - if (name === '') { - return this.create({ + async fetchOne({ partition, ns, dc, name }, { uri }, request) { + if (typeof name === 'undefined' || name === '') { + const item = this.create({ Datacenter: dc, Namespace: '', Partition: partition, }); + item.meta = {cacheControl: 'no-store'}; + return item; } - return (await request` + return ( + await request` GET /v1/peering/${name} ${{ partition, }} - `)((headers, body, cache) => { - return { - meta: { - version: 2, - interval: 10000, - uri: uri, + ` + )((headers, body, cache) => { + return { + meta: { + version: 2, + interval: 10000, + uri: uri, + }, + body: cache( + { + ...body, + Datacenter: dc, + Partition: partition, }, - body: cache( - { - ...body, - Datacenter: dc, - Partition: partition, - }, - uri => uri`peer:///${partition}/${ns}/${dc}/peer/${body.Name}` - ) - }; + uri => uri`peer:///${partition}/${ns}/${dc}/peer/${body.Name}` + ), + }; }); } async persist(item, request) { // mark it as ESTABLISHING ourselves as the request is successful // and we don't have blocking queries here to get immediate updates - return (await request` + return ( + await request` POST /v1/peering/establish ${{ PeerName: item.Name, PeeringToken: item.PeeringToken, - Datacenter: item.Datacenter, Partition: item.Partition || undefined, }} - `)((headers, body, cache) => { - const partition = item.Partition; - const ns = item.Namespace; - const dc = item.Datacenter; - return { - meta: { - version: 2, + ` + )((headers, body, cache) => { + const partition = item.Partition; + const ns = item.Namespace; + const dc = item.Datacenter; + return { + meta: { + version: 2, + }, + body: cache( + { + ...item, + State: 'ESTABLISHING', }, - body: cache( - { - ...item, - State: 'ESTABLISHING' - }, - uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}` - ) - }; + uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}` + ), + }; }); } @@ -120,24 +128,26 @@ export default class PeerService extends RepositoryService { // we just return the item we want to delete // but mark it as DELETING ourselves as the request is successful // and we don't have blocking queries here to get immediate updates - return (await request` + return ( + await request` DELETE /v1/peering/${item.Name} - `)((headers, body, cache) => { - const partition = item.Partition; - const ns = item.Namespace; - const dc = item.Datacenter; - return { - meta: { - version: 2, + ` + )((headers, body, cache) => { + const partition = item.Partition; + const ns = item.Namespace; + const dc = item.Datacenter; + return { + meta: { + version: 2, + }, + body: cache( + { + ...item, + State: 'DELETING', }, - body: cache( - { - ...item, - State: 'DELETING' - }, - uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}` - ) - }; + uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}` + ), + }; }); } } diff --git a/ui/packages/consul-ui/app/styles/base/icons/icons/index.scss b/ui/packages/consul-ui/app/styles/base/icons/icons/index.scss index 9d1a5efe3f..20f57edc7a 100644 --- a/ui/packages/consul-ui/app/styles/base/icons/icons/index.scss +++ b/ui/packages/consul-ui/app/styles/base/icons/icons/index.scss @@ -330,7 +330,7 @@ // @import './file-minus/index.scss'; // @import './file-plus/index.scss'; // @import './file-source/index.scss'; -// @import './file-text/index.scss'; +@import './file-text/index.scss'; // @import './file-x/index.scss'; // @import './files/index.scss'; // @import './film/index.scss'; diff --git a/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs b/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs index 2139fd7318..2affe93b28 100644 --- a/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs +++ b/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs @@ -22,7 +22,7 @@ as |route|> {{#let loader.data - (not (can "write intention" item=item)) + (not (can "write intention" item=loader.data)) as |item readOnly|}} diff --git a/ui/packages/consul-ui/tests/integration/components/consul/bucket/list-test.js b/ui/packages/consul-ui/tests/integration/components/consul/bucket/list-test.js index 15d6b81ce8..063f2b086d 100644 --- a/ui/packages/consul-ui/tests/integration/components/consul/bucket/list-test.js +++ b/ui/packages/consul-ui/tests/integration/components/consul/bucket/list-test.js @@ -224,31 +224,31 @@ module('Integration | Component | consul bucket list', function(hooks) { assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not displayed'); }); - test('it displays a peer and no nspace and no service when item.namespace and nspace match', async function(assert) { + test('it displays a peer and nspace when item.namespace and nspace match', async function(assert) { const PEER_NAME = 'Tomster'; const NAMESPACE_NAME = 'Mascot'; - const SERVICE_NAME = 'Ember.js'; this.set('peerName', PEER_NAME); this.set('namespace', NAMESPACE_NAME); - this.set('service', SERVICE_NAME); await render(hbs` `); assert.dom('[data-test-bucket-item="peer"]').hasText(PEER_NAME, 'Peer is displayed'); - assert.dom('[data-test-bucket-item="nspace"]').doesNotExist('namespace is not displayed'); - assert.dom('[data-test-bucket-item="service"]').doesNotExist('service is not displayed'); + assert + .dom('[data-test-bucket-item="nspace"]') + .hasText( + NAMESPACE_NAME, + 'namespace is displayed when peer is displayed and we are not on OSS (i.e. cannot use nspaces)' + ); assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not displayed'); }); }); diff --git a/version/version.go b/version/version.go index edcdca85b3..8930a432a8 100644 --- a/version/version.go +++ b/version/version.go @@ -14,7 +14,7 @@ var ( // // Version must conform to the format expected by github.com/hashicorp/go-version // for tests to work. - Version = "1.13.0" + Version = "1.14.0" // 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 eafbb17c42..785fbce8b3 100644 --- a/website/content/api-docs/agent/check.mdx +++ b/website/content/api-docs/agent/check.mdx @@ -6,7 +6,10 @@ description: The /agent/check endpoints interact with checks on the local agent # Check - Agent HTTP API -The `/agent/check` endpoints interact with checks on the local agent in Consul. +Consul's health check capabilities are described in the +[health checks overview](/docs/discovery/checks). +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. ## List Checks @@ -418,6 +421,10 @@ $ curl \ This endpoint is used with a TTL type check to set the status of the check to `critical` and to reset the TTL clock. +If you want to manually mark a service as unhealthy, +use [maintenance mode](/api-docs/agent#enable-maintenance-mode) +instead of defining a TTL health check and using this endpoint. + | Method | Path | Produces | | ------ | ----------------------------- | ------------------ | | `PUT` | `/agent/check/fail/:check_id` | `application/json` | @@ -456,6 +463,10 @@ $ curl \ This endpoint is used with a TTL type check to set the status of the check and to reset the TTL clock. +If you want to manually mark a service as unhealthy, +use [maintenance mode](/api-docs/agent#enable-maintenance-mode) +instead of defining a TTL health check and using this endpoint. + | Method | Path | Produces | | ------ | ------------------------------- | ------------------ | | `PUT` | `/agent/check/update/:check_id` | `application/json` | diff --git a/website/content/api-docs/catalog.mdx b/website/content/api-docs/catalog.mdx index b259176850..86480ab79b 100644 --- a/website/content/api-docs/catalog.mdx +++ b/website/content/api-docs/catalog.mdx @@ -410,13 +410,64 @@ The corresponding CLI command is [`consul catalog services`](/commands/catalog/s - `dc` `(string: "")` - Specifies the datacenter to query. This will default to the datacenter of the agent being queried. -- `node-meta` `(string: "")` - Specifies a desired node metadata key/value pair +- `node-meta` `(string: "")` **Deprecated** - Use `filter` with the `NodeMeta` selector instead. + This parameter will be removed in a future version of Consul. + Specifies a desired node metadata key/value pair of the form `key:value`. This parameter can be specified multiple times, and filters the results to nodes with the specified key/value pairs. - `ns` `(string: "")` - Specifies the namespace of the services you lookup. You can also [specify the namespace through other methods](#methods-to-specify-namespace). +- `filter` `(string: "")` - Specifies the expression used to filter the + queries results prior to returning the data. + +### Filtering + +The filter will be executed against each Service mapping within the catalog. +The following selectors and filter operations are supported: + +| Selector | Supported Operations | +| ---------------------------------------------------- | -------------------------------------------------- | +| `Address` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `Datacenter` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ID` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `Node` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `NodeMeta.` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `NodeMeta` | Is Empty, Is Not Empty, In, Not In | +| `ServiceAddress` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceConnect.Native` | Equal, Not Equal | +| `ServiceEnableTagOverride` | Equal, Not Equal | +| `ServiceID` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceKind` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceMeta.` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceMeta` | Is Empty, Is Not Empty, In, Not In | +| `ServiceName` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServicePort` | Equal, Not Equal | +| `ServiceProxy.DestinationServiceID` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.DestinationServiceName` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.LocalServiceAddress` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.LocalServicePort` | Equal, Not Equal | +| `ServiceProxy.Mode` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.TransparentProxy.OutboundListenerPort` | Equal, Not Equal | +| `ServiceProxy.MeshGateway.Mode` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams.Datacenter` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams.DestinationName` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams.DestinationNamespace` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams.DestinationType` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams.LocalBindAddress` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams.LocalBindPort` | Equal, Not Equal | +| `ServiceProxy.Upstreams.MeshGateway.Mode` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceProxy.Upstreams` | Is Empty, Is Not Empty | +| `ServiceTaggedAddresses..Address` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `ServiceTaggedAddresses..Port` | Equal, Not Equal | +| `ServiceTaggedAddresses` | Is Empty, Is Not Empty, In, Not In | +| `ServiceTags` | In, Not In, Is Empty, Is Not Empty | +| `ServiceWeights.Passing` | Equal, Not Equal | +| `ServiceWeights.Warning` | Equal, Not Equal | +| `TaggedAddresses.` | Equal, Not Equal, In, Not In, Matches, Not Matches | +| `TaggedAddresses` | Is Empty, Is Not Empty, In, Not In | + ### Sample Request ```shell-session diff --git a/website/content/api-docs/health.mdx b/website/content/api-docs/health.mdx index 898c8ffe41..cad74bbad2 100644 --- a/website/content/api-docs/health.mdx +++ b/website/content/api-docs/health.mdx @@ -14,6 +14,9 @@ optional health checking mechanisms. Additionally, some of the query results from the health endpoints are filtered while the catalog endpoints provide the raw entries. +To modify health check registration or information, +use the [`/agent/check`](/api-docs/agent/check) endpoints. + ## List Checks for Node This endpoint returns the checks specific to the node provided on the path. diff --git a/website/content/api-docs/peering.mdx b/website/content/api-docs/peering.mdx index 607ecf3116..ef50fcb87b 100644 --- a/website/content/api-docs/peering.mdx +++ b/website/content/api-docs/peering.mdx @@ -30,7 +30,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ---------------- | -| `NO` | `none` | `none` | `none` | +| `NO` | `none` | `none` | `peering:write` | ### JSON Request Body Schema @@ -100,7 +100,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ---------------- | -| `NO` | `none` | `none` | `none` | +| `NO` | `none` | `none` | `peering:write` | ### JSON Request Body Schema @@ -168,9 +168,9 @@ The table below shows this endpoint's support for [agent caching](/api-docs/features/caching), and [required ACLs](/api#authentication). -| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | -| ---------------- | ----------------- | ------------- | ------------ | -| `NO` | `consistent` | `none` | `none` | +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | -------------- | +| `NO` | `consistent` | `none` | `peering:read` | ### Path Parameters @@ -224,9 +224,9 @@ The table below shows this endpoint's support for [agent caching](/api-docs/features/caching), and [required ACLs](/api#authentication). -| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | -| ---------------- | ----------------- | ------------- | ------------- | -| `NO` | `none` | `none` | `none` | +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | ---------------- | +| `NO` | `none` | `none` | `peering:write` | ### Path Parameters @@ -276,9 +276,9 @@ The table below shows this endpoint's support for [agent caching](/api-docs/features/caching), and [required ACLs](/api#authentication). -| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | -| ---------------- | ----------------- | ------------- | ------------- | -| `NO` | `consistent` | `none` | `none` | +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | -------------- | +| `NO` | `consistent` | `none` | `peering:read` | ### Sample Request diff --git a/website/content/api-docs/query.mdx b/website/content/api-docs/query.mdx index 85ee3c8b38..4ad4e2e905 100644 --- a/website/content/api-docs/query.mdx +++ b/website/content/api-docs/query.mdx @@ -11,7 +11,7 @@ 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](/docs/discovery/dns) as it allows for much richer queries than +[DNS Interface](/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. Check the [Geo Failover tutorial](https://learn.hashicorp.com/tutorials/consul/automate-geo-failover) for details and @@ -193,14 +193,25 @@ The table below shows this endpoint's support for handling the query to the servers in the remote datacenter is used to determine the priority. - - `Datacenters` `(array: nil)` - Specifies a fixed list of remote - datacenters to forward the query to if there are no healthy nodes in the + - `Datacenters` `(array: nil)` - Specifies a fixed list of WAN federated + datacenters to forward the query to when there are no healthy nodes in the local datacenter. Datacenters are queried in the order given in the list. If this option is combined with `NearestN`, then the `NearestN` queries will be performed first, followed by the list given by `Datacenters`. A given datacenter will only be queried one time during a failover, even if it is selected by both `NearestN` and is listed in - `Datacenters`. + `Datacenters`. Use `Targets` to failover to cluster peers. + + - `Targets` `(array: nil)` - Specifies a sequential list of remote + datacenters and cluster peers to failover to if there are no healthy + service instances in the local datacenter. + This option cannot be used with `NearestN` or `Datacenters`. + + - `PeerName` `(string: "")` - Specifies a [cluster peer](/docs/connect/cluster-peering) to use for + failover. + + - `Datacenter` `(string: "")` - Specifies a WAN federated datacenter to forward the + query to. - `IgnoreCheckIDs` `(array: nil)` - Specifies a list of check IDs that should be ignored when filtering unhealthy instances. This is mostly useful diff --git a/website/content/docs/agent/config/config-files.mdx b/website/content/docs/agent/config/config-files.mdx index c886775084..2631378731 100644 --- a/website/content/docs/agent/config/config-files.mdx +++ b/website/content/docs/agent/config/config-files.mdx @@ -555,7 +555,7 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." The following sub-keys are available: - - `enabled` ((#peering_enabled)) (Defaults to `true`) Controls whether cluster peering is enabled. + - `enabled` ((#peering_enabled)) (Defaults to `false`) Controls whether cluster peering is enabled. When disabled, the UI won't show peering, all peering APIs will return an error, any peerings stored in Consul already will be ignored (but they will not be deleted), and all peering connections from other clusters will be rejected. This was added in Consul 1.13.0. @@ -1998,7 +1998,7 @@ specially crafted certificate signed by the CA can be used to gain full access t Certificate Authority from the [`ca_file`](#tls_defaults_ca_file) or [`ca_path`](#tls_defaults_ca_path). By default, this is false, and Consul will not make use of TLS for outgoing connections. This applies to clients - and servers as both will make outgoing connections. This setting *does not* + and servers as both will make outgoing connections. This setting does not apply to the gRPC interface as Consul makes no outgoing connections on this interface. @@ -2019,6 +2019,8 @@ specially crafted certificate signed by the CA can be used to gain full access t - `verify_incoming` - ((#tls_grpc_verify_incoming)) Overrides [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming). + - `use_auto_cert` - (Defaults to `false`) Enables or disables TLS on gRPC servers. Set to `true` to allow `auto_encrypt` TLS settings to apply to gRPC listeners. We recommend disabling TLS on gRPC servers if you are using `auto_encrypt` for other TLS purposes, such as enabling HTTPS. + - `https` ((#tls_https)) Provides settings for the HTTPS interface. To enable the HTTPS interface you must define a port via [`ports.https`](#https_port). @@ -2071,7 +2073,9 @@ specially crafted certificate signed by the CA can be used to gain full access t set to true, Consul verifies the TLS certificate presented by the servers match the hostname `server..`. By default this is false, and Consul does not verify the hostname of the certificate, only that it - is signed by a trusted CA. This setting *must* be enabled to prevent a + is signed by a trusted CA. + + ~> **Security Note:** `verify_server_hostname` *must* be set to true to prevent a compromised client from gaining full read and write access to all cluster data *including all ACL tokens and Connect CA root keys*. @@ -2082,7 +2086,7 @@ specially crafted certificate signed by the CA can be used to gain full access t ### Deprecated Options ((#tls_deprecated_options)) The following options were deprecated in Consul 1.12, please use the -[`tls`](#tls) stanza instead. +[`tls`](#tls-1) stanza instead. - `ca_file` See: [`tls.defaults.ca_file`](#tls_defaults_ca_file). diff --git a/website/content/docs/agent/telemetry.mdx b/website/content/docs/agent/telemetry.mdx index 575f3d7e5b..8b4f923435 100644 --- a/website/content/docs/agent/telemetry.mdx +++ b/website/content/docs/agent/telemetry.mdx @@ -605,6 +605,10 @@ Any metric in this section can be turned off with the [`prefix_filter`](/docs/ag ## Cluster Health These metrics give insight into the health of the cluster as a whole. +Query for the `consul.memberlist.*` and `consul.serf.*` metrics can be appended +with certain labels to further distinguish data between different gossip pools. +The supported label for OSS is `network`, while `segment`, `partition`, `area` +are allowed for . | Metric | Description | Unit | Type | |----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------|---------| diff --git a/website/content/docs/api-gateway/common-errors.mdx b/website/content/docs/api-gateway/common-errors.mdx deleted file mode 100644 index f49c9fefc5..0000000000 --- a/website/content/docs/api-gateway/common-errors.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: docs -page_title: Common Error Messages ---- - -# Common Error Messages - -Some of the errors messages commonly encountered during installation and operations of Consul API Gateway are listed below, along with suggested methods for resolving them. - -If the error message is not listed on this page, it may be listed on the main [Consul Common errors][consul-common-errors] page. If the error message is not listed on that page either, please consider following our general [Troubleshooting Guide][troubleshooting] or reach out to us in [Discuss](https://discuss.hashicorp.com/). - - - -### Helm installation failed: "no matches for kind" - -``` -Error: INSTALLATION FAILED: unable to build kubernetes objects from release manifest: [unable to recognize "": no matches for kind "GatewayClass" in version "gateway.networking.k8s.io/v1alpha2", unable to recognize "": no matches for kind "GatewayClassConfig" in version "api-gateway.consul.hashicorp.com/v1alpha1"] -``` -**Conditions:** -When this error occurs during the process of installing Consul API Gateway, it is usually caused by not having the required CRD files installed in Kubernetes prior to installing Consul API Gateway. - -**Impact:** -The installation process will typically fail after this error message is generated - -**Recommended Action:** -Install the required CRDs by using the command in Step 1 of the [Consul API Gateway installation instructions][install-instructions] and then retry installing Consul API Gateway. - - - - - -[consul-common-errors]: /docs/troubleshoot/common-errors -[troubleshooting]: https://learn.hashicorp.com/consul/day-2-operations/advanced-operations/troubleshooting -[install-instructions]: /docs/api-gateway/consul-api-gateway-install#installation \ No newline at end of file diff --git a/website/content/docs/api-gateway/configuration/gateway.mdx b/website/content/docs/api-gateway/configuration/gateway.mdx new file mode 100644 index 0000000000..240b19721d --- /dev/null +++ b/website/content/docs/api-gateway/configuration/gateway.mdx @@ -0,0 +1,231 @@ +--- +layout: docs +page_title: Consul API Gateway Gateway +description: >- + This topic describes how to configure the Consul API Gateway Gateway object +--- + +# Gateway + +This topic provides full details about the `Gateway` resource. + +## Introduction + +A `Gateway` is an instance of network infrastructure that determines how service traffic should be handled. A `Gateway` contains one or more [`listeners`](#listeners) that bind to a set of IP addresses. An `HTTPRoute` or `TCPRoute` can then attach to a gateway listener to direct traffic from the gateway to a service. + +Gateway instances derive their configurations from the [`GatewayClass`](/docs/api-gateway/configuration/gatewayclass) resource, which acts as a template for individual `Gateway` deployments. Refer to [GatewayClass](/docs/api-gateway/configuration/gatewayclass) for additional information. + +Specify the following parameters to declare a `Gateway`: + +| Parameter | Description | Required | +| :----------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------- |:-------- | +| `kind` | Specifies the type of configuration object. The value should always be `Gateway`. | Required | +| `description` | Human-readable string that describes the purpose of the `Gateway`. | Optional | +| `version ` | Specifies the Kubernetes API version. The value should always be `gateway.networking.k8s.io/v1alpha2` | Required | +| `scope` | Specifies the effective scope of the Gateway. The value should always be `namespaced`. | Required | +| `fields` | Specifies the configurations for the Gateway. The fields are listed in the [configuration model](#configuration-model). Details for each field are described in the [specification](#specification). | Required | + + + +## Configuration model + +The following outline shows how to format the configurations in the `Gateway` object. Click on a property name to view details about the configuration. + +* [`gatewayClassName`](#gatewayclassname): string | required +* [`listeners`](#listeners): array of objects | required + * [`allowedRoutes`](#listeners-allowedroutes): object | required + * [`namespaces`](#listeners-allowedroutes-namespaces): object | required + * [`from`](#listeners-namespaces-from): string | required + * [`selector`](#listeners-allowedroutes-namespaces-selector): object | required if `from` is configured to `selector` + * [`matchExpressions`](#listeners-allowedroutes-namespaces-selector-matchexpressions): array of objects | required if `matchLabels` is not configured + * [`key`](#listeners-allowedroutes-namespaces-selector-matchexpressions): string | required if `matchExpressions` is declared + * [`operator`](#listeners-allowedroutes-namespaces-selector-matchexpressions): string | required if `matchExpressions` is declared + * [`values`](#listeners-allowedroutes-namespaces-selector-matchexpressions): array of strings | required if `matchExpressions` is declared + * [`matchLabels`](#listeners-allowedroutes-namespaces-selector-matchlabels): map of strings | required if `matchExpressions` is not configured + * [`hostname`](#listeners-hostname): string | required + * [`name`](#listeners-name): string | required + * [`port`](#listeners-port): integer | required + * [`protocol`](#listeners-protocol): string | required + * [`tls`](#listeners-tls): object | required if `protocol` is set to `HTTPS` + * [`certificateRefs`](#listeners-tls): array or objects | required if `tls` is declared + * [`name`](#listeners-tls): string | required if `certificateRefs` is declared + * [`namespace`](#listeners-tls): string | required if `certificateRefs` is declared + * [`mode`](#listeners-tls): string | required if `certificateRefs` is declared + * [`options`](#listeners-tls): map of strings | optional + +## Specification + +This topic provides details about the configuration parameters. + +### gatewayClassName +Specifies the name of the [`GatewayClass`](/docs/api-gateway/configuration/gatewayclass) resource used for the `Gateway` instance. Unless you are using a custom [GatewayClass](/docs/api-gateway/configuration/gatewayclass), this value should be set to `consul-api-gateway`. +* Type: string +* Required: required + +### listeners +Specifies the `listeners` associated with the `Gateway`. At least one `listener` must be specified. Each `listener` within a `Gateway` must have a unique combination of `hostname`, `port`, and `protocol`. +* Type: array of objects +* Required: required + +### listeners.allowedRoutes +Specifies a `namespace` object that defines the types of routes that may be attached to a listener. +* Type: object +* Required: required + +### listeners.allowedRoutes.namespaces +Determines which routes are allowed to attach to the `listener`. Only routes in the same namespace as the `Gateway` may be attached by default. +* Type: string +* Required: optional +* Default: Same namespace as the parent Gateway + +### listeners.allowedRoutes.namespaces.from +Determines which namespaces are allowed to attach a route to the `Gateway`. You can specify one of the following strings: + +* `All`: Routes in all namespaces may be attached to the `Gateway`. +* `Same` (default): Only routes in the same namespace as the `Gateway` may be attached. +* `Selector`: Only routes in namespaces that match the [`selector`](#listeners-allowedroutes-namespaces-selector) may be attached. + +This parameter is required. + +### listeners.allowedRoutes.namespaces.selector +Specifies a method for selecting routes that are allowed to attach to the listener. The `Gateway` checks for namespaces in the network that match either a regular expression or a label. Routes from the matching namespace are allowed to attach to the listener. + +You can configure one of the following objects: + +* [`matchExpressions`](#listeners-allowedroutes-namespaces-selector-matchexpressions) +* [`matchLabels`](#listeners-allowedroutes-namespaces-selector-matchlabels) + +This field is required when [`from`](#listeners-allowedroutes-namespaces-from) is configured to `Selector`. + +### listeners.allowedRoutes.namespaces.selector.matchExpressions +Specifies an array of requirements for matching namespaces. If a match is found, then routes from the matching namespace(s) are allowed to attach to the `Gateway`. The following table describes members of the `matchExpressions` array: + +| Requirement | Description | Type | Required | +|--- |--- |--- |--- | +|`key` | Specifies the label that the `key` applies to. | string | required when `matchExpressions` is declared | +|`operator` | Specifies the key's relation to a set of values. You can use the following keywords:
  • `In`: Only routes in namespaces that contain the strings in the `values` field can attach to the `Gateway`.
  • `NotIn`: Routes in namespaces that do not contain the strings in the `values` field can attach to the `Gateway`.
  • `Exists`: Routes in namespaces that contain the `key` value are allowed to attach to the `Gateway`.
  • `DoesNotExist`: Routes in namespaces that do not contain the `key` value are allowed to attach to the `Gateway`.
| string | required when `matchExpressions` is declared | +|`values` | Specifies an array of string values. If `operator` is configured to `In` or `NotIn`, then the `values` array must contain values. If `operator` is configured to `Exists` or `DoesNotExist`, then the `values` array must be empty. | array of strings | required when `matchExpressions` is declared | + +In the following example, routes in namespaces that contain `foo` and `bar` are allowed to attach routes to the `Gateway`. +```yaml +namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: In + values: + - foo + - bar +``` + +Refer to [Labels and Selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements) in the Kubernetes documentation for additional information about `matchExpressions`. + +### listeners.allowedRoutes.namespaces.selector.matchLabels +Specifies an array of labels and label values. If a match is found, then routes with the matching label(s) are allowed to attach to the `Gateway`. This selector can contain any arbitrary key/value pair. + +In the following example, routes in namespaces that have a `bar` label are allowed to attach to the `Gateway`. + +```yaml +namespaceSelector: + matchLabels: + foo: bar +``` + +Refer to [Labels and Selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) in the Kubernetes documentation for additional information about labels. + +### listeners.hostname +Specifies the `listener`'s hostname. +* Type: string +* Required: required + +### listeners.name +Specifies the `listener`'s name. +* Type: string +* Required: required + +### listeners.port +Specifies the port number that the `listener` attaches to. +* Type: integer +* Required: required + +### listeners.protocol +Specifies the protocol the `listener` communicates on. +* Type: string +* Required: required + +Allowed values are `TCP`, `HTTP`, or `HTTPS` + +### listeners.tls +Specifies the `tls` configurations for the `Gateway`. The `tls` object is required if `protocol` is set to `HTTPS`. The object contains the following fields: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `certificateRefs` |
Specifies Kubernetes `name` and `namespace` objects that contains TLS certificates and private keys.
The certificates establish a TLS handshake for requests that match the `hostname` of the associated `listener`. Each reference must be a Kubernetes Secret. If you are using a Secret in a namespace other than the `Gateway`'s, each reference must also have a corresponding [`ReferenceGrant`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferenceGrant).
| Object or array | Required if `tls` is set | +| `mode` | Specifies the TLS Mode. Should always be set to `Terminate` for `HTTPRoutes` | string | Required if `certificateRefs` is set | +| `options` | Specifies additional Consul API Gateway options. | Map of strings | optional | + +The following keys for `options` are available +* `api-gateway.consul.hashicorp.com/tls_min_version` +* `api-gateway.consul.hashicorp.com/tls_max_version` +* `api-gateway.consul.hashicorp.com/tls_cipher_suites` + +In the following example, `tls` settings are configured to use a secret named `consul-server-cert` in the same namespace as the `Gateway` and the minimum tls version is set to `TLSv1_2`. + +```yaml + +tls: + certificateRefs: + - name: consul-server-cert + group: "" + kind: Secret + mode: Terminate + options: + api-gateway.consul.hashicorp.com/tls_min_version: "TLSv1_2" + +``` + +#### Example cross-namespace certificateRef + +The following example creates a `Gateway` named `example-gateway` in namespace `gateway-namespace` (lines 2-4). The gateway has a `certificateRef` in namespace `secret-namespace` (lines 16-18). The reference is allowed because the `ReferenceGrant` configuration, named `reference-grant` in namespace `secret-namespace` (lines 24-27), allows `Gateways` in `gateway-namespace` to reference `Secrets` in `secret-namespace` (lines 31-35). + + + + ```yaml + apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: example-gateway + namespace: gateway-namespace + spec: + gatewayClassName: consul-api-gateway + listeners: + - protocol: HTTPS + port: 443 + name: https + allowedRoutes: + namespaces: + from: Same + tls: + certificateRefs: + - name: cert + namespace: secret-namespace + group: "" + kind: Secret + --- + + apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + name: reference-grant + namespace: secret-namespace + spec: + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: gateway-namespace + to: + - group: "" + kind: Secret + name: cert + ``` + + diff --git a/website/content/docs/api-gateway/configuration/gatewayclass.mdx b/website/content/docs/api-gateway/configuration/gatewayclass.mdx new file mode 100644 index 0000000000..70684e7308 --- /dev/null +++ b/website/content/docs/api-gateway/configuration/gatewayclass.mdx @@ -0,0 +1,81 @@ +--- +layout: docs +page_title: Consul API Gateway GatewayClass +description: >- + Consul API Gateway GatewayClass +--- + +# GatewayClass + +This topic provides describes how to configure the `GatewayClass` resource, a generic Kubernetes gateway object used as a template for creating `Gateway` resources. + +## Introduction + +The `GatewayClass` specification includes the name of the controller (`controllerName`) and an API object containing controller-specific configuration resources within the cluster (`parametersRef`). The value of the `controllerName` field must be set to `hashicorp.com/consul-api-gateway-controller`. + +When gateways are created from a `GatewayClass`, they use the parameters specified in the `GatewayClass` at the time of instantiation. + +The `GatewayClass` resource is a generic Kubernetes gateway object. For configuration specific to Consul API Gateway, see [GatewayClassConfig](/docs/api-gateway/configuration/gatewayclassconfig). + +## Configuration model +The following outline shows how to format the configurations in the `GatewayClass` object. Click on a property name to view details about the configuration. + +* [`controllerName`](#controllername): string | required +* [`parametersRef`](#parametersref): object | optional + * [`group`](#parametersref): string | required if `parametersRef` is set + * [`kind`](#parametersref): string | required if `parametersRef` is set + * [`name`](#parametersref): string | required if `parametersRef` is set +* [`description`](#description): string | optional + +## Specification + +This topic provides details about the configuration parameters. + +### controllerName +Specifies the name of the controller that manages the gateways generated by this class. +The value must always be `hashicorp.com/consul-api-gateway-controller`. + +* Type: string +* Required: required + +### parametersRef +Defines an API object that references additional configurations required by the gateway controller. The following table describes the fields that you must include in the `parametersRef` configuration. + +* Type: object +* Required: optional + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `group` | Specifies the Kubernetes group that the `parametersRef` is a member of.
The value must always be `api-gateway.consul.hashicorp.com`.
The `parametersRef.group` is always the same across all deployments of Consul API Gateway. | String | Required | +| `kind` | Specifies the type of Kubernetes object that the `parametersRef` configuration defines.
The value must always be `GatewayClassConfig`.
This `parametersRef.kind` is always the same across all deployments of Consul API Gateway. | String | Required | +| `name` | Specfies a name for the `GatewayClassConfig` object. | String | Required | + + +### description +Specifies a human-readable description of the gateway class. We recommend using the description field to describe the gateway class's purpose. + +* Type: string +* Required: optional + +## Example Configuration +The following example creates a gateway class called `test-gateway-class`: + + + + ```yaml + apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GatewayClass + metadata: + name: test-gateway-class + spec: + controllerName: 'hashicorp.com/consul-api-gateway-controller' + parametersRef: + group: api-gateway.consul.hashicorp.com + kind: GatewayClassConfig + name: test-gateway-class-config + description: The gateway class is for creating test gateways class configurations +``` + + +Refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass) for details about configuring gateway classes. + diff --git a/website/content/docs/api-gateway/configuration/gatewayclassconfig.mdx b/website/content/docs/api-gateway/configuration/gatewayclassconfig.mdx new file mode 100644 index 0000000000..58f7af80d4 --- /dev/null +++ b/website/content/docs/api-gateway/configuration/gatewayclassconfig.mdx @@ -0,0 +1,201 @@ +--- +layout: docs +page_title: Consul API Gateway GatewayClassConfig +description: >- + Consul API Gateway GatewayClassConfig +--- + +# GatewayClassConfig + +This topic provides full details about the `GatewayClassConfig` resource. + +## Introduction + +The `GatewayClassConfig` object contains Consul API Gateway-related configuration parameters. Apply the parameters by adding the [`GatewayClass`](#gatewayclass) object to your Kubernetes values file and specifying the name of the `GatewayClassConfig`. + +## Configuration model + +The following outline shows how to format the configurations in the `GatewayClassConfig` object. Click on a property name to view details about the configuration. + +* [`consul`](#consul): object | optional + * [`address`](#consul-address): string | optional + * [`authentication`](#consul-authentication): object | optional + * [`account`]([#consul-authentication-account): string | optional + * [`managed`](#consul-authentication-managed): bool | optional + * [`method`](#consul-authentication-method): string | optional + * [`namespace`](#consul-authentication-namespace): string | optional + * [`ports`](#consul-ports): object | optional + * [`grpc`](#consul-ports-grpc): integer | optional + * [`http`](#consul-ports-http): integer | optional + * [`scheme`](#consul-scheme): string | optional +* [`copyAnnotations`](#copyAnnotations): object | optional + * [`service`](#copyAnnotations-service): array of strings | optional +* [`deployment`](#deployment): object | optional + * [`defaultInstances`](#deployment-defaultinstances): integer | optional + * [`maxInstances`](#deployment-maxinstances): integer | optional + * [`minInstances`](#deployment-mininstances): integer | optional +* [`image`](#image): object | optional + * [`consulAPIGateway`](#image-consulapigateway): string | optional + * [`envoy`](#image-envoy): string | optional +* [`logLevel`](#loglevel): string | optional +* [`nodeSelector`](#nodeselector): string | optional +* [`serviceType`](#servicetype): string | optional +* [`useHostPorts`](#usehostports): boolean | optional + +## Specification + +This topic provides details about the configuration parameters. + +### consul +Specifies configurations that enable an instance of Consul API Gateway to interact with Consul. +* Type: object +* Required: optional + +### consul.address +Specifies the address of the Consul server that the `Gateway` communicates with in the gateway pod. If unspecified, the pod attempts to use a local agent on the host where the pod is running. +* Type: string +* Required: optional +* Default: local agent + +### consul.authentication.account +Specifies the [Kubernetes service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) to use for authentication. +* Type: string +* Required: optional + +### consul.authentication.managed +Set to `true` to enable deployments to run with managed service accounts created by the gateway controller. The `consul.authentication.account` field is ignored when this option is enabled. +* Type: boolean +* Required: optional +* Default: `false` + +### consul.authentication.method +Specifies the [Consul auth method](/docs/security/acl/auth-methods) used for initial authentication by Consul API Gateway. +* Type: string +* Required: optional + +### consul.authentication.namespace +Specifies the Consul namespace to use for authentication. +* Type: string +* Required: optional + +### consul.ports.grpc +Specifies the gRPC port for Consul's xDS server. +* Type: integer +* Required: optional +* Default: `8502` + +### consul.ports.http +Specifies the Consul HTTP port to use for authentication. +* Type: integer +* Required: optional +* Default: `8500` + +### consul.scheme +Specifies the scheme to use for connecting to Consul. +* Type: string +* Required: optional +* Default: `http` + +You can specify the following strings: +* `http` +* `https` + +### copyAnnotations.service +Specifies an array of Kubernetes [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to copy to the gateway service. +* Type: Array of strings +* Required: optional + +### deployment.defaultInstances +Specifies the number of gateway instances to deploy per gateway configuration. +* Type: Integer +* Required: optional +* Default: `1` + +### deployment.maxInstances +Specifies the maximum allowed number of gateway instances per gateway configuration. +* Type: Integer +* Required: optional +* Default: `8` + +### deployment.minInstances +Specifies the minimum allowed number of gateway instances per gateway configuration. +* Type: Integer +* Required: optional +* Default: `1` + +### image.consulAPIGateway +Specifies the Docker image to use for the `consul-api-gateway` container. View available image tags on [DockerHub](https://hub.docker.com/r/hashicorp/consul-api-gateway/tags). + +The default value is suitable for most deployments, but you may require a specific version of the Consul API Gateway depending on your environment. +* Type: string +* Required: optional +* Default: `"hashicorp/consul-api-gateway:RELEASE_VERSION"` + +### image.envoy +Specifies the Docker image to use for the Envoy proxy container. View available image tags on [DockerHub](https://hub.docker.com/r/hashicorp/consul-api-gateway/tags). + +The default value is suitable for most deployments, but you may require a specific version of Envoy depending on your environment. +* Type: string +* Required: optional +* Default: `"envoyproxy/envoy:RELEASE_VERSION"` + +### logLevel +Specifies the error reporting level for logs. +* Type: string +* Required: optional +* Default: `info` + +You can specify the following strings: +* `error` +* `warning` +* `info` +* `debug` +* `trace` + +### nodeSelector +Pods normally run on multiple nodes. You can specify a set of parameters in the `nodeSelector` that constrain the nodes on which the pod can run, enabling the pod to fit on a node. The selector must match a node's labels for the pod to be scheduled on that node. Refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/) for additional information. + +* Type: string +* Required: optional + +### serviceType +Specifies the ingress methods for the gateway's Kubernetes service. +* Type: string +* Required: optional + +You can specify the following strings: +* `ClusterIP`: The gateway is only accessible from inside the cluster. +* `NodePort`: The gateway is exposed on each Kubernetes node at a static port. +* `LoadBalancer`: The gateway is exposed to external traffic by a load balancer. + +For more on Kubernetes services, see [Publishing Services](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types). + +### useHostPorts +If set to `true`, then the Envoy container ports are mapped to host ports. +* Type: boolean +* Required: optional +* Default: `false` + + +## Example Configuration +The following example creates a gateway class configuration called `test-gateway-class-config`. Traffic that passes through gateways created from the class configuration authenticate with Consul over HTTPS on port `8501`. Consul client agents communicate with server agents on port `8502` : + + + + ```yaml + apiVersion: api-gateway.consul.hashicorp.com/v1alpha1 + kind: GatewayClassConfig + metadata: + name: test-gateway-class-config + spec: + useHostPorts: true + logLevel: 'trace' + consul: + scheme: 'https' + ports: + http: 8501 + grpc: 8502 +``` + + +Refer to the [Consul API Gateway repository](https://github.com/hashicorp/consul-api-gateway/blob/main/config/crd/bases/api-gateway.consul.hashicorp.com_gatewayclassconfigs.yaml) for the complete specification. diff --git a/website/content/docs/api-gateway/configuration/index.mdx b/website/content/docs/api-gateway/configuration/index.mdx new file mode 100644 index 0000000000..630f26ab93 --- /dev/null +++ b/website/content/docs/api-gateway/configuration/index.mdx @@ -0,0 +1,23 @@ +--- +layout: docs +page_title: Consul API Gateway Configuration +description: >- + Consul API Gateway Configuration +--- + +# Configuration + +This topic provides an overview of the configuration items that enable Consul API Gateway to manage traffic into your Consul service mesh. + +- [Gateway](/docs/api-gateway/configuration/gateway) defines the main infrastructure resource that links API gateway components. It specifies the name of the `GatewayClass` and one or more [listeners](/docs/api-gateway/configuration/gateway#listeners), which specify the logical endpoints bound to the gateway's addresses. +- [GatewayClass](/docs/api-gateway/configuration/gatewayclass) defines a class of gateway resources that you can use as a template for creating gateways. +- [GatewayClassConfig](/docs/api-gateway/configuration/gatewayclassconfig) describes additional Consul API Gateway-related configuration parameters for the GatewayClass resource. +- [Routes](/docs/api-gateway/configuration/routes) specifies the path from the gateway to the backend service(s) client to the listener. + +You can create a basic Gateway object using the default [`gatewayClassName`](/docs/api-gateway/configuration/gateway#gatewayclassname) (`consul-api-gateway`). If you want to create custom Gateways suitable for your environment, complete the following steps: + +1. Define a [GatewayClassConfig](/docs/api-gateway/configuration/gatewayclassconfig) that contains your custom configurations. +1. Define a [GatewayClass](/docs/api-gateway/configuration/gatewayclass) and configure the [`parametersRef.name`](/docs/api-gateway/configuration/gatewayclass#parametersref-name) to reference the name of your [GatewayClassConfig](/docs/api-gateway/configuration/gatewayclassconfig). +1. Define a [Gateway](/docs/api-gateway/configuration/gateway) and configure the [`gatewayClassName`](/docs/api-gateway/configuration/gateway#gatewayclassname) to reference the name of your [GatewayClass](/docs/api-gateway/configuration/gatewayclass). + + diff --git a/website/content/docs/api-gateway/configuration/meshservice.mdx b/website/content/docs/api-gateway/configuration/meshservice.mdx new file mode 100644 index 0000000000..0307b3a4be --- /dev/null +++ b/website/content/docs/api-gateway/configuration/meshservice.mdx @@ -0,0 +1,31 @@ +--- +layout: docs +page_title: Consul API Gateway MeshService +description: >- + This topic describes how to configure the Consul API Gateway MeshService object +--- + +# MeshService + +This topic provides full details about the `MeshService` resource. + +## Introduction + +A `MeshService` is a resource in the Kubernetes cluster that enables Kubernetes configuration models, such as `HTTPRoute` and `TCPRoute`, to reference services that only exist in Consul. A `MeshService` represents a service in the Consul service mesh outside the Kubernetes cluster where Consul API Gateway is deployed. The service represented by the `MeshService` resource must be in the same Consul datacenter as the Kubernetes cluster. + + +## Configuration Model + +The following outline shows how to format the configurations in the `MeshService` object. Click on a property name to view details about the configuration. + +* [`name`](#name): string | required + + +## Specification + +This topic provides details about the configuration parameters. + +### name +Specifies the name of the service in the Consul service mesh to send traffic to. +* Type: string +* Required: required diff --git a/website/content/docs/api-gateway/configuration/routes.mdx b/website/content/docs/api-gateway/configuration/routes.mdx new file mode 100644 index 0000000000..dced0cfa04 --- /dev/null +++ b/website/content/docs/api-gateway/configuration/routes.mdx @@ -0,0 +1,266 @@ +--- +layout: docs +page_title: Consul API Gateway Routes +description: >- + Consul API Gateway Routes +--- + +# Route + +This topic describes how to create and configure `Route` resources. Routes are independent configuration objects that are associated with specific listeners. + +## Create a `Route` + +Declare a route with either `kind: HTTPRoute` or `kind: TCPRoute` and configure the route parameters in the `spec` block. +Refer to the Kubernetes Gateway API documentation for each object type for details: + +- [HTTPRoute](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute) +- [TCPRoute](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute) + +The following example creates a route named `example-route` associated with a listener defined in `example-gateway`. + + + + ```yaml + apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: HTTPRoute + metadata: + name: example-route + spec: + parentRefs: + - name: example-gateway + rules: + - backendRefs: + - kind: Service + name: echo + port: 8080 + ``` + + + +## Configuration model + +The following outline shows how to format the configurations for the `Route` object. The top-level `spec` field is the root for all configurations. Click on a property name to view details about the configuration. + +* [`parentRefs`](#parentrefs): array of objects | optional + * [`group`](#parentrefs): string | optional + * [`kind`](#parentrefs): string | optional + * [`name`](#parentrefs): string | required + * [`namespace`](#parentrefs): string | optional + * [`sectionName`](#parentrefs): string | optional +* [`rules`](#rules): list of objects | optional + * [`backendRefs`](#rules-backendrefs): list of objects | optional + * [`group`](#rules-backend-refs): string | optional + * [`kind`](#rules-backendrefs): string | optional + * [`name`](#rules-backendrefs): string | required + * [`namespace`](#rules-backendrefs): string | optional + * [`port`](#rules-backendrefs): integer | required + * [`weight`](#rules-backendrefs): integer | optional + * [`filters`](#rules-filters): list of objects | optional + * [`type`](#rules-filters-type): string | required + * [`requestHeaderModifier`](#rules-filters-requestheadermodifier): object | optional + * [`set`](#rules-filters-requestheadermodifier): array of objects | optional + * [`name`](#rules-filters-requestheadermodifier): string | required + * [`value`](#rules-filters-requestheadermodifier): string | required + * [`add`](#rules-filters-requestheadermodifier): array of objects | optional + * [`name`](#rules-filters-requestheadermodifier): string | required + * [`value`](#rules-filters-requestheadermodifier): string | required + * [`remove`](#rules-filters-requestheadermodifier): array of strings | optional + * [`urlRewrite`](#rules-filters-urlrewrite): object | optional + * [`path`](#rules-filters-urlrewrite-path): object | required + * [`replacePrefixMatch`](#rules-filters-urlrewrite-path): string | required + * [`type`](#rules-filters-urlrewrite-path): string | required + * [`matches`](#rules-matches): array of objects | optional + * [`path`](#rules-matches-path): list of objects | optional + * [`type`](#rules-matches-path): string | required + * [`value`](#rules-matches-path): string | required + * [`headers`](#rules-matches-headers): list of objects | optional + * [`type`](#rules-matches-headers): string | required + * [`name`](#rules-matches-headers): string | required + * [`value`](#rules-matches-headers): string | required + * [`queryParams`](#rules-matches-queryparams): list of objects | optional + * [`type`](#rules-matches-queryparams): string | required + * [`name`](#rules-matches-queryparams): string | required + * [`value`](#rules-matches-queryparams): string | required + * [`method`](#rules-matches-method): string | optional + + +## Specification + +This topic provides details about the configuration parameters. + +### parentRefs + +This field contains the list of `Gateways` that the route should attach to. If not set, the route will not attach to a `Gateway`. The following table describes the objects you can configure in the `parentRefs` block: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `group` | Specifies the Kubernetes API group of the `Gateway` to attach to. You can specify the following values:
  • `gateway.networking.k8s.io`
. Defaults to `gateway.networking.k8s.io`. | String | Optional | +| `kind` | Specifies the Kubernetes kind of the `Gateway` to attach to. you can specify the following values:
  • `Gateway`
. Defaults to `Gateway`. | String | Optional | +| `name` | Specifies the name of the `Gateway` the route is attached to. | String | Required | +| `namespace` | Specifies the Kubernetes namespace containing the `Gateway` to attach to. If the `Gateway` is in a different Kubernetes namespace than the `Route`, then you must specify a value. Defaults to the `Route` namespace. | String | Optional | +| `sectionName` | Specifies the name of a specific listener on the `Gateway` to attach to. The `Route` attempts to attach to all listeners on the `Gateway`. | String | Required | + + +### rules + +The `rules` field contains a list of objects that define behaviors for network traffic that goes through the route. The rule configuration contains the following objects: + +* [`backendRefs`](#rules-backendrefs): Specifies which backend services the `Route` references when processing traffic. +* [`filters`](#rules-filters): Specifies which operations Consul API Gateway performs when traffic goes through the `Route`. +* [`matches`](#rules-matches): Deterines which requests Consul API Gateway processes. + +Rules are optional. + +### rules.backendRefs + +This field specifies backend services that the `Route` references. The following table describes the parameters for `backendRefs`: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `group` | Specifies the Kubernetes API Group of the referenced backend. You can specify the following values:
  • `""`: Specifies the core Kubernetes API group. This value must be used when `kind` is set to `Service`. This is the default value if unspecified.
  • `api-gateway.consul.hashicorp.com`: This value must be used when `kind` is set to `MeshService`.
| String | Optional | +| `kind` | Specifies the Kubernetes Kind of the referenced backend. You can specify the following values:
  • `Service` (default): Indicates that the `backendRef` references a Service in the Kubernetes cluster.
  • `MeshService`: Indicates that the `backendRef` references a service in the Consul mesh. Refer to the `MeshService` [documentation](/docs/api-gateway/configuration/meshservice) for additional information.
| String | Optional | +| `name` | Specifies the name of the Kubernetes Service or Consul mesh service resource. | String | Required | +| `namespace` | Specifies the Kubernetes namespace containing the Kubernetes Service or Consul mesh service resource. You must specify a value if the Service or Consul mesh service is defined in a different namespace from the `Route`. Defaults to the namespace of the `Route`.
To create a route for a `backendRef` in a different namespace, you must also create a [ReferenceGrant](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferenceGrant). Refer to the [example route](#example-cross-namespace-backendref) configured to reference across namespaces. | String | Optional | +| `port` | Specifies the port number for accessing the Kubernetes or Consul service. | Integer | Required | +| `weight` | Specifies the proportion of requests sent to the backend. Computed as weight divided by the sum of all weights in this `backendRefs` list. Defaults to `1`. A value of `0` indicates that no requests should be sent to the backend. | Integer | Optional | + +#### Example cross-namespace backendRef + +The following example creates a route named `example-route` in namespace `gateway-namespace`. This route has a `backendRef` in namespace `service-namespace`. Traffic is allowed because the `ReferenceGrant`, named `reference-grant` in namespace `service-namespace`, allows traffic from `HTTPRoutes` in `gateway-namespace` to `Services` in `service-namespace`. + + + + ```yaml + apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: HTTPRoute + metadata: + name: example-route + namespace: gateway-namespace + spec: + parentRefs: + - name: example-gateway + rules: + - backendRefs: + - kind: Service + name: echo + namespace: service-namespace + port: 8080 + --- + + apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + name: reference-grant + namespace: service-namespace + spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: gateway-namespace + to: + - group: "" + kind: Service + name: echo + ``` + + + +### 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](/docs/api-gateway/usage#reroute-http-requests) for additional information). + +* Type: Array of objects +* Required: Optional + +### rules.filters.type + +Specifies the type of filter you want to apply to the route. The parameter is optional and takes a string value. + +You can specify the following values: + +* `RequestHeaderModifier`: The `RequestHeaderModifier` type modifies the HTTP headers on the incoming request. You must define the [`rules.filters.requestHeaderModifier`](#rules-filters-requestheadermodifier) configurations to use this filter type. + +* `URLRewrite`: The `URLRewrite` type modifies the URL path on the incoming request. You must define the [`rules.filters.urlRewrite`](#rules-filters-urlrewrite) configurations to use this filter type. + +### rules.filters.requestHeaderModifier + +Defines operations to perform on matching request headers when `rules.filters.type` is configured to `RequestHeaderModifier`. This field contains the following configuration objects: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `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 objects | Optional | +| `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 value(s) 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 objects | Optional | +| `remove` | Configure this field to specify an array of header names to remove from the request header. | Array of strings | Optional | + + +### rules.filters.urlRewrite + +Specifies rules for rewriting the URL of incoming requests when `rules.filters.type` is configured to `URLRewrite`. + +* Type: Object +* Required: Optional + +### rules.filters.urlRewrite.path + +Specifies a list of objects that determine how Consul API Gateway rewrites URL paths (refer to [Reroute HTTP requests](/docs/api-gateway/usage#reroute-http-requests) for additional information). + +The following table describes the parameters for `path`: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `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 | Required | +| `type` | Specifies the type of replacement to use for the URL path. You can specify the following values:
  • `ReplacePrefixMatch`: Replaces the the matched prefix of the URL path (default).
| String | Optional | + +### rules.matches + +Specifies rules for matching incoming requests. You can apply [`filters`](#rulesfilters) to requests that match the defined rules. You can match incoming requests based on the following elements: + +* [paths](#rules-matches-path) +* [headers](#rules-matches-headers) +* [query parameters](#rules-matches-queryparams) +* [request method](#rules-matches-method) + +Each rule matches requests independently. As a result, a request matching any of the conditions is considered a match. You can configure several matching rules for each type to widen or narrow matches. +### rules.matches.path + +Specifies a list of objects that define matches based on URL path. The following table describes the parameters for the `path` field: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `type` | Specifies the type of comparison to use for matching the path value. You can specify the following types.
  • `Exact`: Returns a match only when the entire path matches the `value` field (default).
  • `PathPrefix`: Returns a match when the path has the prefix defined in the `value` field.
  • `RegularExpression`: Returns a match when the path matches the regex defined in the `value` field.
| String | Required | +| `value` | Specifies the value to match on. You can specify a specific string when `type` is `Exact` or `PathPrefix`. You can specify a regular expression if `type` is `RegularExpression`. | String | Required | + +### rules.matches.headers + +Specifies a list of objects that define matches based HTTP request headers. The following table describes the parameters for the `headers` field: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `type` | Specifies the type of comparison to use for matching the header value. You can specify the following types.
  • `Exact`: Returns a match only when the entire header matches the `value` field (default).
  • `RegularExpression`: Returns a match when the header matches the regex defined in the `value` field.
| String | Required | +| `name` | Specifies the name of the header to match on. | String | Required | +| `value` | Specifies value to match on. You can specify a specific string or a regular expression. | String | Required | + +### rules.matches.queryParams + +Specifies a list of objects that define matches based query parameters. The following table describes the parameters for the `queryParams` field: + +| Parameter | Description | Type | Required | +| --- | --- | --- | --- | +| `type` | Specifies the type of comparison to use for matching a query parameter value. You can specify the following types.
  • `Exact`: Returns a match only when the query parameter match the `value` field (default).
  • `RegularExpression`: Returns a match when the query parameter matches the regex defined in the `value` field.
| String | Required | +| `name` | Specifies the name of the query parameter to match on. | String | Required | +| `value` | Specifies value to match on. You can specify a specific string or a regular expression. | String | Required | + +### rules.matches.method + +Specifies a list of strings that define matches based on HTTP request method. You may specify the following values: + +* `HEAD` +* `POST` +* `PUT` +* `PATCH` +* `GET` +* `DELETE` +* `OPTIONS` +* `TRACE` +* `CONNECT` diff --git a/website/content/docs/api-gateway/consul-api-gateway-install.mdx b/website/content/docs/api-gateway/consul-api-gateway-install.mdx deleted file mode 100644 index a72c5ae5d2..0000000000 --- a/website/content/docs/api-gateway/consul-api-gateway-install.mdx +++ /dev/null @@ -1,431 +0,0 @@ ---- -layout: docs -page_title: Consul API Gateway Install -description: >- - Installing Consul API Gateway ---- - -# Installing Consul API Gateway - -This topic describes how to use the Consul API Gateway add-on module. It includes instructions for installation and configuration. - -## Requirements - -Ensure that the environment you are deploying Consul API Gateway in meets the requirements listed in the [Technical Specifications][tech-specs]. This includes validating that the requirements for minimum versions of software are met. See the [Release Notes][rel-notes] for the version of API Gateway you are deploying. - -## Installation - -1. Set the version of Consul API Gateway you are installing as an environment variable. The following steps use this environment variable in commands and configurations. - - ```shell-session - $ export VERSION=0.3.0 - ``` - -1. Issue the following command to install the CRDs: - - ```shell-session - $ kubectl apply --kustomize="github.com/hashicorp/consul-api-gateway/config/crd?ref=v$VERSION" - ``` - -1. Create a `values.yaml` file for your Consul API Gateway deployment by copying the following content and running it in the environment where you set the `VERSION` environment variable. The Consul Helm chart uses this `values.yaml` file to deploy the API Gateway. Available versions of the [Consul](https://hub.docker.com/r/hashicorp/consul/tags) and [Consul API Gateway](https://hub.docker.com/r/hashicorp/consul-api-gateway/tags) Docker images can be found on DockerHub, with additional context on version compatibility published in [GitHub releases](https://github.com/hashicorp/consul-api-gateway/releases). For more options to configure your Consul API Gateway deployment through the Helm chart, refer to [Helm Chart Configuration - apiGateway](https://www.consul.io/docs/k8s/helm#apigateway). - - - - ```shell - cat < values.yaml - global: - name: consul - connectInject: - enabled: true - controller: - enabled: true - apiGateway: - enabled: true - image: hashicorp/consul-api-gateway:$VERSION - EOF - ``` - - - -1. Install Consul API Gateway using the standard Consul Helm chart and specify the custom values file. Available versions of the [Consul Helm chart](https://github.com/hashicorp/consul-k8s/releases) can be found in GitHub releases. - - ```shell-session - $ helm install consul hashicorp/consul --version 0.45.0 --values values.yaml --create-namespace --namespace consul - ``` - -## Usage - -1. Verify that the [requirements](#requirements) have been met. -1. Verify that the Consul API Gateway CRDs and controller have been installed and applied (see [Installation](#installation)). -1. Configure the artifacts described below in [Configuration](#configuration). - - - - ```yaml - apiGateway: - managedGatewayClass: - enabled: true - ``` - - - -1. Issue the `kubectl apply` command to implement the configurations, e.g.: - - ```shell-session - $ kubectl apply -f gateway.yaml routes.yaml - ``` - - - -## Configuration - -Configure the following artifacts to facilitate ingress into your Consul service mesh: - -- [GatewayClassConfig](#gatewayclassconfig): Describes additional Consul API Gateway-related configuration parameters for the `GatewayClass` resource. -- [GatewayClass](#gatewayclass): Defines a class of gateway resources that you can use as a template for creating gateways. -- [Gateway](#gateway): Defines the main infrastructure resource that links API gateway components. It specifies the name of the `GatewayClass` and one or more `listeners` (see [Listeners](#listeners)), which specify the logical endpoints bound to the gateway's addresses. -- [Routes](#routes): Specifies the path from the client to the listener. - --> **Note:** Add the following `managedGatewayClass` configuration to the `values.yaml` Helm configuration to enable the `GatewayClassConfig` and `GatewayClass` to be created automatically. The gateway, listeners, and routes will need to be configured manually. When `managedGatewayClass` is enabled, the [`serviceType`](/docs/k8s/helm#v-apigateway-managedgatewayclass-servicetype) for a managed `GatewayClass` will also default to `LoadBalancer`, which is appropriate for most deployments to managed Kubernetes cloud offerings (i.e., EKS, GKE, AKS). Other deployments, such as to a [kind](https://kind.sigs.k8s.io/) cluster, may require specifying `NodePort` or `ClusterIP`, instead. - -### GatewayClassConfig - -The `GatewayClassConfig` object describes Consul API Gateway-related configuration parameters for the [`GatewayClass`](#gatewayclass). - -Add the `kind: GatewayClassConfig` option to the gateway values file to declare a gateway class. -The following example creates a gateway class configuration called `test-gateway-class-config`: - - - -```yaml -apiVersion: api-gateway.consul.hashicorp.com/v1alpha1 -kind: GatewayClassConfig -metadata: - name: test-gateway-class-config -spec: - useHostPorts: true - logLevel: 'trace' - consul: - scheme: 'https' - ports: - http: 8501 - grpc: 8502 -``` - - - -The following table describes the allowed parameters for the `spec` array: - -| Parameter | Description | Type | Default | -| --- | --- | ---- | ------- | -| `consul.address` | Specifies the address of the Consul server to communicate with in the gateway pod. If unspecified, the pod will attempt to use a local agent on the host on which the pod is running. | String | N/A | -| `consul.authentication.account` | Specifies the Kubernetes service account to use for authentication. | String | N/A | -| `consul.authentication.managed` | Set to `true` to enable deployments to run with managed service accounts created by the gateway controller. The `consul.authentication.account` field is ignored when this option is enabled. | Boolean | `false` | -| `consul.authentication.method` | Specifies the Consul auth method used for initial authentication by Consul API Gateway. | String | N/A | -| `consul.authentication.namespace` | Specifies the Consul namespace to use for authentication. | String | N/A | -| `consul.ports.grpc` | Specifies the gRPC port for Consul's xDS server. | Integer | `8502` | -| `consul.ports.http` | Specifies the port for Consul's HTTP server. | Integer | `8500` | -| `consul.scheme` | Specifies the scheme to use for connecting to Consul. The supported values are `"http"` and `"https"`. | String | `"http"` | -| `copyAnnotations.service` | List of annotations to copy to the gateway service. | Array | `["external-dns.alpha.kubernetes.io/hostname"]` | -| `deployment.defaultInstances` | Specifies the number of instances to deploy by default for each gateway. | Integer | 1 | -| `deployment.maxInstances` | Specifies the maximum allowed number of instances per gateway. | Integer | 8 | -| `deployment.minInstances` | Specifies the minimum allowed number of instances per gateway. | Integer | 1 | -| `image.consulAPIGateway` | The image to use for consul-api-gateway. View available image tags on [DockerHub](https://hub.docker.com/r/hashicorp/consul-api-gateway/tags). | String | `"hashicorp/consul-api-gateway:RELEASE_VERSION"` | -| `image.envoy` | Specifies the container image to use for Envoy. View available image tags on [DockerHub](https://hub.docker.com/r/envoyproxy/envoy/tags). | String | `"envoyproxy/envoy:RELEASE_VERSION"` | -| `logLevel` | Specifies the error reporting level for logs. You can specify the following values: `error`, `warning`, `info`, `debug`, `trace`. | String | `"info"` | -| `nodeSelector` | Specifies a set of parameters that constrain the nodes on which the pod can run. Defining nodes with the `nodeSelector` enables the pod to fit on a node. The selector must match a node's labels for the pod to be scheduled on that node. Refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/) for additional information. | Object | N/A | -| `serviceType` | Specifies the ingress methods for a service. The following values are supported:
`ClusterIP`
`NodePort`
`LoadBalancer`. | String | N/A | -| `useHostPorts` | If set to `true`, then the Envoy container ports are mapped to host ports. | Boolean | `false` | - -Refer to the [Consul API Gateway repository](https://github.com/hashicorp/consul-api-gateway/blob/main/config/crd/bases/api-gateway.consul.hashicorp.com_gatewayclassconfigs.yaml) for the complete specification. - -### GatewayClass - -The `GatewayClass` resource is used as a template for creating `Gateway` resources. -The specification includes the name of the controller (`controllerName`) and an API object containing controller-specific configuration resources within the cluster (`parametersRef`). -The value of the `controllerName` field must be set to `hashicorp.com/consul-api-gateway-controller`. - -When gateways are created from a `GatewayClass`, they use the parameters specified in the `GatewayClass` at the time of instantiation. - -Add the `kind: GatewayClass` option to the the gateway values file to declare a gateway class. -The following example creates a gateway class called `test-gateway-class`: - - - -```yaml -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: GatewayClass -metadata: - name: test-gateway-class -spec: - controllerName: 'hashicorp.com/consul-api-gateway-controller' - parametersRef: - group: api-gateway.consul.hashicorp.com - kind: GatewayClassConfig - name: test-gateway-class-config -``` - - - -Refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass) for details about configuring gateway classes. - -### Gateway - -The gateway configuration is the main infrastructure resource that links API gateway components. It specifies the name of the `GatewayClass` and one or more `listeners`. - -Add the `kind: Gateway` option to the configuration file to declare a gateway. -The following example creates a gateway called `example-gateway`. -The gateway is based on the `test-gateway-class` and includes a listener called `https` (see [Listeners](#listeners) for details about the `listener` configuration). - - - -```yaml -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: Gateway -metadata: - name: example-gateway - annotations: - 'external-dns.alpha.kubernetes.io/hostname': DNS_HOSTNAME -spec: - gatewayClassName: test-gateway-class - listeners: - - protocol: HTTPS - hostname: DNS_HOSTNAME - port: 443 - name: https - allowedRoutes: - namespaces: - from: Same - tls: - certificateRefs: - - name: gateway-production-certificate -``` - - - -If you configure a listener's `certificateRefs` to reference a secret in a different namespace, you must also create a [ReferencePolicy](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy) in the same namespace as the secret. The `ReferencePolicy` grants the listener the permission to read the secret. - -The following example creates a `Gateway` named `example-gateway` in `gateway-namespace`. This `Gateway` has a `certificateRef` in `secret-namespace`. -The listener can use the certificate because `reference-policy` in `secret-namespace` is configured to allow `Gateways` in `gateway-namespace` to reference `Secrets` in `secret-namespace`. - - - -```yaml -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: Gateway -metadata: - name: example-gateway - namespace: gateway-namespace - annotations: - 'external-dns.alpha.kubernetes.io/hostname': DNS_HOSTNAME -spec: - gatewayClassName: test-gateway-class - listeners: - - protocol: HTTPS - hostname: DNS_HOSTNAME - port: 443 - name: https - allowedRoutes: - namespaces: - from: Same - tls: - certificateRefs: - - name: gateway-production-certificate - namespace: secret-namespace ---- - -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: ReferencePolicy -metadata: - name: reference-policy - namespace: secret-namespace -spec: - from: - - group: gateway.networking.k8s.io - kind: Gateway - namespace: gateway-namespace - to: - - group: "" - kind: Secret - name: gateway-production-certificate -``` - - - -Refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Gateway) for further details about configuring gateways. - -#### Listeners - -Listeners are the logical endpoints bound to the gateway's addresses. -Add the `listener` object to the `gateway` configuration and specify the following properties to define a listener: - -| Parameter | Description | Type | Default | -| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------- | -| `hostname` | Specifies the virtual hostname to match for protocol types. | String | none | -| `port` | Specifies the network port number. | Integer | none | -| `protocol` | Specifies the network protocol expected by the listener. | String | `http` | -| `tls` | Collection of parameters that specify TLS options for the listener. Refer to the [`GatewayTLSConfig`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayTLSConfig) documentation for additional information about configuring TLS. | Object | N/A | -| `tls.mode` | Specifies a mode for operating Consul API Gateway listeners over TLS.
You can only specify the `Terminate` mode, which configures the TLS session between the downstream client and the gateway to terminate at the gateway.
Refer to the [`TLSModeType` documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSModeType) for additional information. | String | `Terminate` | -| `tls.certificateRefs` | Specifies the name of secret object used for Envoy SDS (Secret Discovery Service) to support terminating TLS. Refer to the [`[]*SecretObjectReference` documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.SecretObjectReference) for additional information. | String | N/A | -| `tls.options` | Specifies key/value pairs to enable extended TLS configuration specific to an implementation. | Object | N/A | -| `tls.options.tls_min_version` | Specifies the minimum TLS version supported for the listener. The following values are supported: `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, `TLSv1_3`. | String | `TLS 1.2` | -| `tls.options.tls_max_version` | Specifies the maximum TLS version supported for the listener. The specified version must be greater than or equal to `TLSMinVersion`. The following values are supported: `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, `TLSv1_3`. | String | `TLS 1.3` | -| `tls.options.tls_cipher_suites` | Specifies the list of TLS cipher suites to support when negotiating connections using TLS 1.2 or earlier.
If unspecified, a [more secure set of cipher suites](https://github.com/hashicorp/consul-api-gateway/blob/main/internal/common/tls.go#L3-L10) than Envoy's current [default server cipher list](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) will be used.
The full list of supported cipher suites can seen in [`internal/common/tls.go`](https://github.com/hashicorp/consul-api-gateway/blob/main/internal/common/tls.go) and is dependent on underlying support in Envoy. | String | See description | - -Refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Listener) for details about configuring listeners. - -#### Scaling - -You can scale a logical gateway object to multiple instances with the [`kubectl scale`](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#scaling-a-deployment) command. The object scales according to the bounds set in `GatewayClassConfig`. - -```shell-session -$ kubectl get deployment --selector api-gateway.consul.hashicorp.com/name=example-gateway -NAME READY UP-TO-DATE AVAILABLE -example-gateway 1/1 1 1 -``` -```shell-session -$ kubectl scale deployment/example-gateway --replicas=3 -deployment.apps/example-gateway scaled -``` -```shell-session -$ kubectl get deployment --selector api-gateway.consul.hashicorp.com/name=example-gateway -NAME READY UP-TO-DATE AVAILABLE -example-gateway 3/3 3 3 -``` - -### Route - -Routes are independent configuration objects that are associated with specific listeners. - -Declare a route with either `kind: HTTPRoute` or `kind: TCPRoute` and configure the route parameters in the `spec` block. -Refer to the Kubernetes Gateway API documentation for each object type for details: - -- [HTTPRoute](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute) -- [TCPRoute](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute) - -The following example creates a route named `example-route` associated with a listener defined in `example-gateway`. - - - -```yaml -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: HTTPRoute -metadata: - name: example-route -spec: - parentRefs: - - name: example-gateway - rules: - - backendRefs: - - kind: Service - name: echo - port: 8080 -``` - - - -To create a route for a `backendRef` in a different namespace, you must also -create a [ReferencePolicy](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy). - -The following example creates a route named `example-route` in namespace `gateway-namespace`. This route has a `backendRef` in namespace `service-namespace`. Traffic is allowed because the `ReferencePolicy`, named `reference-policy` in namespace `service-namespace`, allows traffic from `HTTPRoutes` in `gateway-namespace` to `Services` in `service-namespace`. - - - -```yaml -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: HTTPRoute -metadata: - name: example-route - namespace: gateway-namespace -spec: - parentRefs: - - name: example-gateway - rules: - - backendRefs: - - kind: Service - name: echo - namespace: service-namespace - port: 8080 ---- - -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: ReferencePolicy -metadata: - name: reference-policy - namespace: service-namespace -spec: - from: - - group: gateway.networking.k8s.io - kind: HTTPRoute - namespace: gateway-namespace - to: - - group: "" - kind: Service - name: echo -``` - - - -### MeshService - -The `MeshService` configuration holds a reference to an externally-managed Consul service mesh service and can be used as a `backendRef` for a [`Route`](#route). - -| Parameter | Description | Type | Default | -| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------- | -| `name` | Specifies the service name for a Consul service. It is assumed this service will exist in either the `consulDestinationNamespace` or mirrored Consul namespace from where this custom resource is defined, depending on the Helm configuration. - -Refer to the [Consul API Gateway repository](https://github.com/hashicorp/consul-api-gateway/blob/main/config/crd/bases/api-gateway.consul.hashicorp.com_meshservices.yaml) for the complete specification. - - -[tech-specs]: /docs/api-gateway/tech-specs -[rel-notes]: /docs/release-notes diff --git a/website/content/docs/api-gateway/index.mdx b/website/content/docs/api-gateway/index.mdx index 5a6c75af40..6a811fd71b 100644 --- a/website/content/docs/api-gateway/index.mdx +++ b/website/content/docs/api-gateway/index.mdx @@ -18,7 +18,7 @@ Consul API Gateway solves the following primary use cases: - **Controlling access at the point of entry**: Consul API Gateway allows users to set the protocols of external connection requests and provide clients with TLS certificates from trusted providers (e.g., Verisign, Let’s Encrypt). - **Simplifying traffic management**: The Consul API Gateway can 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 type (e.g., GET, POST, PATCH). -## Implementation +## How Does Consul API Gateway Work? Consul API Gateway can be deployed on Kubernetes-based runtime environments and requires that Consul service mesh be deployed on the Kubernetes cluster. @@ -38,7 +38,7 @@ are used, see the [documentation in our GitHub repo](https://github.com/hashicor | [`Gateway`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Gateway) |
  • Supported protocols: `HTTP`, `HTTPS`, `TCP`
  • Header-based hostname matching (no SNI support)
  • Supported filters: header addition, removal, and setting
  • TLS modes supported: `terminate`
  • Certificate types supported: `core/v1/Secret`
  • Extended options: TLS version and cipher constraints
| | [`HTTPRoute`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute) |
  • Weight-based load balancing
  • Supported rules: path, header, query, and method-based matching
  • Supported filters: header addition, removal, and setting
  • Supported backend types:
    1. `core/v1/Service` (must map to a registered Consul service)
    2. `api-gateway.consul.hashicorp.com/v1alpha1/MeshService`
| | [`TCPRoute`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute) |
  • Supported backend types:
    1. `core/v1/Service` (must map to a registered Consul service)
    2. `api-gateway.consul.hashicorp.com/v1alpha1/MeshService`
| -| [`ReferencePolicy`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy) |
  • Required to allow any reference from a `Gateway` to a Kubernetes `core/v1/Secret` in a different namespace.
    • A Gateway with an unpermitted `certificateRefs` caused by the lack of a` ReferencePolicy` sets a `ResolvedRefs` status to `False` with the reason `InvalidCertificateRef`. The Gateway will not become ready in this case.
  • Required to allow any reference from an `HTTPRoute` or `TCPRoute` to a Kubernetes `core/v1/Service` in a different namespace.
    • A route with an unpermitted `backendRefs` caused by the lack of a `ReferencePolicy` sets a `ResolvedRefs` status to `False` with the reason `RefNotPermitted`. The gateway listener rejects routes with an unpermitted `backendRefs`.
    • WARNING: If a route `backendRefs` becomes unpermitted, the entire route is removed from the gateway listener.
      • A `backendRefs` can become unpermitted when you delete a `ReferencePolicy` or add a new unpermitted `backendRefs` to an existing route.
| +| [`ReferenceGrant`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferenceGrant) |
  • Required to allow any reference from a `Gateway` to a Kubernetes `core/v1/Secret` in a different namespace.
    • A Gateway with an unpermitted `certificateRefs` caused by the lack of a` ReferenceGrant` sets a `ResolvedRefs` status to `False` with the reason `InvalidCertificateRef`. The Gateway will not become ready in this case.
  • Required to allow any reference from an `HTTPRoute` or `TCPRoute` to a Kubernetes `core/v1/Service` in a different namespace.
    • A route with an unpermitted `backendRefs` caused by the lack of a `ReferenceGrant` sets a `ResolvedRefs` status to `False` with the reason `RefNotPermitted`. The gateway listener rejects routes with an unpermitted `backendRefs`.
    • WARNING: If a route `backendRefs` becomes unpermitted, the entire route is removed from the gateway listener.
      • A `backendRefs` can become unpermitted when you delete a `ReferenceGrant` or add a new unpermitted `backendRefs` to an existing route.
| ## Additional Resources diff --git a/website/content/docs/api-gateway/install.mdx b/website/content/docs/api-gateway/install.mdx new file mode 100644 index 0000000000..3d1a542ee8 --- /dev/null +++ b/website/content/docs/api-gateway/install.mdx @@ -0,0 +1,89 @@ +--- +layout: docs +page_title: Install Consul API Gateway +description: >- + Installing Consul API Gateway +--- + +# Install Consul API Gateway + +This topic describes how to install and configure Consul API Gateway. + +## Requirements + +Ensure that the environment you are deploying Consul API Gateway in meets the requirements listed in the [Technical Specifications][tech-specs]. This includes validating that the requirements for minimum versions of software are met. Refer to the [Release Notes][rel-notes] for the version of API Gateway you are deploying. + +## Installation + +1. Set the version of Consul API Gateway you are installing as an environment variable. The following steps use this environment variable in commands and configurations. + + ```shell-session + $ export VERSION=0.3.0 + ``` + +1. Issue the following command to install the CRDs: + + ```shell-session + $ kubectl apply --kustomize="github.com/hashicorp/consul-api-gateway/config/crd?ref=v$VERSION" + ``` + +1. Create a `values.yaml` file for your Consul API Gateway deployment by copying the following content and running it in the environment where you set the `VERSION` environment variable. The Consul Helm chart uses this `values.yaml` file to deploy the API Gateway. Available versions of the [Consul](https://hub.docker.com/r/hashicorp/consul/tags) and [Consul API Gateway](https://hub.docker.com/r/hashicorp/consul-api-gateway/tags) Docker images can be found on DockerHub, with additional context on version compatibility published in [GitHub releases](https://github.com/hashicorp/consul-api-gateway/releases). For more options to configure your Consul API Gateway deployment through the Helm chart, refer to [Helm Chart Configuration - apiGateway](https://www.consul.io/docs/k8s/helm#apigateway). + + + + ```shell + cat < values.yaml + global: + name: consul + connectInject: + enabled: true + controller: + enabled: true + apiGateway: + enabled: true + image: hashicorp/consul-api-gateway:$VERSION + EOF + ``` + + +1. Install Consul API Gateway using the standard Consul Helm chart or Consul K8s CLI specify the custom values file. Available versions of the [Consul Helm chart](https://github.com/hashicorp/consul-k8s/releases) can be found in GitHub releases. + + + + + ~> **Note:** Refer to the official [Consul K8S CLI documentation](https://www.consul.io/docs/k8s/k8s-cli) to find additional settings. + + ```shell-session + $ brew tap hashicorp/tap + ``` + + ```shell-session + $ brew install hashicorp/tap/consul-k8s + ``` + + ```shell-session + $ consul-k8s install -config-file=values.yaml -set global.image=hashicorp/consul:1.12.2 + ``` + + + + + Add the HashiCorp Helm repository. + + ```shell-session + $ helm repo add hashicorp https://helm.releases.hashicorp.com + ``` + Install Consul with API Gateway on your Kubernetes cluster by specifying the `values.yaml` file. + + ```shell-session + $ helm install consul hashicorp/consul --version 0.45.0 --values values.yaml --create-namespace --namespace consul + ``` + + + + +[tech-specs]: /docs/api-gateway/tech-specs +[rel-notes]: /docs/release-notes diff --git a/website/content/docs/api-gateway/upgrade-specific-versions.mdx b/website/content/docs/api-gateway/upgrades.mdx similarity index 71% rename from website/content/docs/api-gateway/upgrade-specific-versions.mdx rename to website/content/docs/api-gateway/upgrades.mdx index 59381baa7b..24c29fde17 100644 --- a/website/content/docs/api-gateway/upgrade-specific-versions.mdx +++ b/website/content/docs/api-gateway/upgrades.mdx @@ -9,6 +9,163 @@ description: >- This topic describes how to upgrade Consul API Gateway. +## Upgrade to v0.4.0 + +Consul API Gateway v0.4.0 adds support for [Gateway API v0.5.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v0.5.0) and the following resources: + +- The graduated v1beta1 `GatewayClass`, `Gateway` and `HTTPRoute` resources. + +- The [`ReferenceGrant`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferenceGrant) resource, which replaces the identical [`ReferencePolicy`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy) resource. + +Consul API Gateway v0.4.0 is backward-compatible with existing `ReferencePolicy` resources, but we will remove support for `ReferencePolicy` resources in a future release. We recommend that you migrate to `ReferenceGrant` after upgrading. + +### Requirements + +Ensure that the following requirements are met prior to upgrading: + +- Consul API Gateway should be running version v0.3.0. + +### Procedure + +1. Complete the [standard upgrade](#standard-upgrade). + +1. After completing the upgrade, complete the [post-upgrade configuration changes](#v0.4.0-post-upgrade-configuration-changes). The post-upgrade procedure describes how to replace your `ReferencePolicy` resources with `ReferenceGrant` resources and how to upgrade your `GatewayClass`, `Gateway`, and `HTTPRoute` resources from v1alpha2 to v1beta1. + + + +### Post-upgrade configuration changes +Complete the following steps after performing standard upgrade procedure. +#### Requirements + +- Consul API Gateway should be running version v0.4.0. +- Consul Helm chart should be v0.47.0 or later. +- You should have the ability to run `kubectl` CLI commands. +- `kubectl` should be configured to point to the cluster containing the installation you are upgrading. +- You should have the following permissions for your Kubernetes cluster: + - `Gateway.read` + - `ReferenceGrant.create` (Added in Consul Helm chart v0.47.0) + - `ReferencePolicy.delete` + +#### Procedure + +1. Verify the current version of the `consul-api-gateway-controller` `Deployment`: + + ```shell-session + $ kubectl get deployment --namespace consul consul-api-gateway-controller --output=jsonpath="{@.spec.template.spec.containers[?(@.name=='api-gateway-controller')].image}" + ``` + + You should receive a response similar to the following: + + ```log + "hashicorp/consul-api-gateway:0.4.0" + ``` + + + +1. Issue the following command to get all `ReferencePolicy` resources across all namespaces. + + ```shell-session + $ kubectl get referencepolicy --all-namespaces + ``` +If you have any active `ReferencePolicy` resources, you will receive output similar to the response below. + + ```log + Warning: ReferencePolicy has been renamed to ReferenceGrant. ReferencePolicy will be removed in v0.6.0 in favor of the identical ReferenceGrant resource. + NAMESPACE NAME + default example-reference-policy + ``` + + If your output is empty, upgrade your `GatewayClass`, `Gateway` and `HTTPRoute` resources to v1beta1 as described in [step 7](#v1beta1-gatewayclass-gateway-httproute). + +1. For each `ReferencePolicy` in the source YAML files, change the `kind` field to `ReferenceGrant`. You can optionally update the `metadata.name` field or filename if they include the term "policy". In the following example, the `kind` and `metadata.name` fields and filename have been changed to reflect the new resource. Note that updating the `kind` field prevents you from using the `kubectl edit` command to edit the remote state directly. + + + + ```yaml + apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + name: reference-grant + namespace: web-namespace + spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: example-namesapce + to: + - group: "" + kind: Service + name: web-backend + ``` + + + +1. For each file, apply the updated YAML to your cluster to create a new `ReferenceGrant` resource. + + ```shell-session + $ kubectl apply --filename + ``` + +1. Check to confirm that each new `ReferenceGrant` was created successfully. + + ```shell-session + $ kubectl get referencegrant --namespace + NAME + example-reference-grant + ``` + +1. Finally, delete each corresponding old `ReferencePolicy` resource. Because replacement `ReferenceGrant` resources have already been created, there should be no interruption in the availability of any referenced `Service` or `Secret`. + + ```shell-session + $ kubectl delete referencepolicy --namespace + Warning: ReferencePolicy has been renamed to ReferenceGrant. ReferencePolicy will be removed in v0.6.0 in favor of the identical ReferenceGrant resource. + referencepolicy.gateway.networking.k8s.io "example-reference-policy" deleted + ``` + + + +1. For each `GatewayClass`, `Gateway`, and `HTTPRoute` in the source YAML, update the `apiVersion` field to `gateway.networking.k8s.io/v1beta1`. Note that updating the `apiVersion` field prevents you from using the `kubectl edit` command to edit the remote state directly. + + + + ```yaml + apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: example-gateway + namespace: gateway-namespace + spec: + ... + ``` + + + +1. For each file, apply the updated YAML to your cluster to update the existing `GatewayClass`, `Gateway` or `HTTPRoute` resources. + + ```shell-session + $ kubectl apply --filename + gateway.gateway.networking.k8s.io/example-gateway configured + ``` + + ## Upgrade to v0.3.0 from v0.2.0 or lower @@ -32,7 +189,7 @@ Ensure that the following requirements are met prior to upgrading: 1. Verify the current version of the `consul-api-gateway-controller` `Deployment`: ```shell-session - $ kubectl get deployment --namespace consul consul-api-gateway-controller --output=jsonpath= "{@.spec.template.spec.containers[?(@.name=='api-gateway-controller')].image}" + $ kubectl get deployment --namespace consul consul-api-gateway-controller --output=jsonpath="{@.spec.template.spec.containers[?(@.name=='api-gateway-controller')].image}" ``` You should receive a response similar to the following: diff --git a/website/content/docs/api-gateway/usage.mdx b/website/content/docs/api-gateway/usage.mdx new file mode 100644 index 0000000000..5ba7cfc808 --- /dev/null +++ b/website/content/docs/api-gateway/usage.mdx @@ -0,0 +1,183 @@ +--- +layout: docs +page_title: Consul API Gateway Basic Usage +description: >- + This topic describes how to use Consul API Gateway. +--- + + +# Usage + +This topic describes how to use Consul API Gateway. + +## Basic usage + +Complete the following steps to use Consul API Gateway in your network. + +1. Verify that the [requirements](/docs/api-gateway/tech-specs) have been met. +1. Verify that the Consul API Gateway CRDs and controller have been installed and applied (see [Installation](/docs/api-gateway/consul-api-gateway-install)). +1. Configure your [`Gateway`](/docs/api-gateway/configuration/gateway) and [`Routes`](/docs/api-gateway/configuration/routes) . as describe in the [Configuration](/docs/api-gateway/configuration) section. + + + + ```yaml + apiGateway: + enabled: true + managedGatewayClass: + ``` + + + +1. Issue the `kubectl apply` command to implement the configurations, e.g.: + + ```shell-session + $ kubectl apply -f gateway.yaml routes.yaml + ``` + +## Reroute HTTP requests + +Configure the following fields in your `Route` configuration to use this feature. Refer to the [Route configuration reference](/docs/api-gateway/configuration/routes) for details about the parameters. + +* [`rules.filters.type`](/docs/api-gateway/configuration/routes#rules-filters-type): Set this parameter to `URLRewrite` to instruct Consul API Gateway to rewrite the URL when specific conditions are met. +* [`rules.filters.urlRewrite`](/docs/api-gateway/configuration/routes#rules-filters-urlrewrite): Specify the `path` configuration. +* [`rules.filters.urlRewrite.path`](/docs/api-gateway/configuration/routes#rules-filters-urlrewrite-path): Contains the paths that incoming requests should be rewritten to based on the match conditions. + +Note that if the route is configured to accept paths with and without a trailing slash, you must make two separate routes to handle each case. + +### Example + +In the following example, requests to` /incoming-request-prefix/` are forwarded to the `backendRef` as `/prefix-backend-receives/`. A request to `/incoming-request-prefix/request-path`, for instance, is received by the `backendRef` as `/prefix-backend-receives/request-path`. + + + +```yaml hideClipboard +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example-route + ##... +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: api-gateway + rules: + - backendRefs: + . . . + filters: + - type: URLRewrite + urlRewrite: + path: + replacePrefixMatch: /prefix-backend-receives/ + type: ReplacePrefixMatch + matches: + - path: + type: PathPrefix + value: /incoming–request-prefix/ +``` + + + + + +## Error Messages + +This topic provides information about potential error messages associated with Consul API Gateway. If you receive an error message that does not appear in this section, refer to the following resources: + +* [Common Consul errors](/docs/troubleshoot/common-errors#common-errors-on-kubernetes) +* [Consul troubleshooting guide](/docs/troubleshoot/common-errors) +* [Consul Discuss forum](https://discuss.hashicorp.com/) + + + +### Helm installation failed: "no matches for kind" + +```log +Error: INSTALLATION FAILED: unable to build kubernetes objects from release manifest: [unable to recognize "": no matches for kind "GatewayClass" in version "gateway.networking.k8s.io/v1alpha2", unable to recognize "": no matches for kind "GatewayClassConfig" in version "api-gateway.consul.hashicorp.com/v1alpha1"] +``` +**Conditions:** +Consul API Gateway generates this error when the required CRD files have not been installed in Kubernetes prior to installing Consul API Gateway. + +**Impact:** +The installation process typically fails after this error message is generated. + +**Recommended Action:** +Install the required CRDs by using the command in Step 1 of the [Consul API Gateway installation instructions](/docs/api-gateway/install#installation) and then retry installing Consul API Gateway. + diff --git a/website/content/docs/connect/ca/vault.mdx b/website/content/docs/connect/ca/vault.mdx index e0a9daa6ea..e563a6d83d 100644 --- a/website/content/docs/connect/ca/vault.mdx +++ b/website/content/docs/connect/ca/vault.mdx @@ -201,6 +201,8 @@ If the paths already exist, Consul will use them as configured. ## Vault ACL Policies +Vault PKI can be managed by either Consul or by Vault. If you want to manually create and tune the PKI secret engines used to store the root and intermediate certificates, use Vault Managed PKI Paths. If you want to have the PKI automatically managed for you, use Consul Managed PKI Paths. + ### Vault Managed PKI Paths The following Vault policy allows Consul to use pre-existing PKI paths in Vault. diff --git a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx index 72b872151d..009c60f409 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -7,24 +7,35 @@ description: >- # Create and Manage Peering Connections -~> **Cluster peering is currently in technical preview:** Functionality associated with cluster peering is subject to change. You should never use the technical preview release in secure environments or production scenarios. Features in technical preview may have performance issues, scaling issues, and limited support. +~> **Cluster peering is currently in beta:** Functionality associated with cluster peering is subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support.

Cluster peering is not currently available in the HCP Consul offering. -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 authorize other clusters to call those services. +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. -To peer clusters, you must complete the following steps in order: +## Create a peering connection + +Cluster peering is not enabled by default on Consul servers. To peer clusters, you must first configure all Consul servers so that `peering` is `enabled`. For additional information, refer to [Configuration Files](/docs/agent/config/config-files). + +After enabling peering for all Consul servers, complete the following steps in order: 1. Create a peering token 1. Establish a connection between clusters -1. Export service endpoints -1. Authorize connections between peers +1. Export services between clusters +1. Authorize services for peers -## Create a peering token +You can generate peering tokens and initiate connections on any available agent using either the API or the Consul UI. If you use the API, we recommend performing these operations through a client agent in the partition you want to connect. -You can generate peering tokens and initiate connections using the Consul API on any available agent. However, 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. -In `cluster-01`, issue a request for a peering token using the [HTTP API](/api-docs/peering). +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](/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. ```shell-session $ curl --request POST --data '{"PeerName":"cluster-02"}' --url http://localhost:8500/v1/peering/token @@ -44,10 +55,27 @@ Create a JSON file that contains the first cluster's name and the peering token. ``` + -## Establish a connection between clusters + -Next, use `peering_token.json` to establish a secure connection between the clusters. In the client agents of "cluster-02," establish the peering connection using the HTTP API. This endpoint does not generate an output unless there is an error. +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. Be careful not to lose the token, as you cannot view the token 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](/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 @@ -55,7 +83,22 @@ $ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peerin 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` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/api-docs/peering). -## Export service endpoints + +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 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. @@ -79,6 +122,7 @@ Services = [ ## during the peering process. Peer = "cluster-02" } + } ] ``` @@ -92,7 +136,7 @@ $ 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 connections from peers +### 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. @@ -123,23 +167,124 @@ Then, add the configuration entry to your cluster. $ consul config write peering-intentions.hcl ``` -## Check peered cluster status +## Manage peering connections -To confirm that you peered your clusters, you can [query the `/health/service` endpoint](/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. +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](/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 + }, +] +``` + + + + +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](/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 +} +``` + + + + +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](/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 will include service information in the output. +A successful query includes service information in the output. -## Remove peering connections +### Delete peering connections -After you create a peering connection between clusters in different datacenters, you can disconnect the peered clusters. Deleting a peering connection stops data replication to the peer and deletes imported data, including services and CA certificates. +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 via the HTTP API. + + + +In "cluster-01," request the deletion through the [`/peering/ endpoint`](/api-docs/peering#delete-a-peering-connection). ```shell-session $ curl --request DELETE http://127.0.0.1:8500/v1/peering/cluster-02 ``` + + + + +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. + + + diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 4601e19470..c4b8c7a4ee 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -7,18 +7,19 @@ description: >- # What is Cluster Peering? -~> **Cluster peering is currently in technical preview**: Functionality associated with cluster peering is subject to change. You should never use the technical preview release in secure environments or production scenarios. Features in technical preview may have performance issues, scaling issues, and limited support. +~> **Cluster peering is currently in beta**: Functionality associated with cluster peering is subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support.

Cluster peering is not currently available in the HCP Consul offering. You can create peering connections between two or more independent clusters so that services deployed to different partitions or datacenters can communicate. ## Overview Cluster peering allows Consul clusters in different datacenters to communicate with each other. The cluster peering process consists of the following steps: -1. Create a peering token to share with other clusters -1. Establish a connection between clusters -1. Make services available to other clusters +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. -For detailed instructions on setting up cluster peering with the Consul CLI, refer to [Create and Manage Peering Connections](/docs/connect/cluster-peering/create-manage-peering). +For detailed instructions on setting up cluster peering, refer to [Create and Manage Peering Connections](/docs/connect/cluster-peering/create-manage-peering). ### Differences between WAN federation and cluster peering @@ -26,25 +27,33 @@ WAN federation and cluster peering are different ways to connect clusters. The m 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. -| | WAN Federation | Cluster Peering | -| :----------------------------------------------- | :------------: | :-------------: | -| Connects clusters across datacenters | ✅ | ✅ | -| Shares support queries and service endpoints | ✅ | ✅ | -| Connects clusters owned by different operators | ❌ | ✅ | -| Functions without declaring primary datacenter | ❌ | ✅ | -| Shares key/value stores | ✅ | ❌ | -| Uses gossip protocol | ✅ | ❌ | +| | WAN Federation | Cluster Peering | +| :------------------------------------------------- | :------------: | :-------------: | +| Connects clusters across datacenters | ✅ | ✅ | +| Shares support queries and service endpoints | ✅ | ✅ | +| Connects clusters owned by different operators | ❌ | ✅ | +| Functions without declaring primary datacenter | ❌ | ✅ | +| Replicates exported services for service discovery | ❌ | ✅ | +| Forwards service requests for service discovery | ✅ | ❌ | +| Shares key/value stores | ✅ | ❌ | +| Uses gossip protocol | ✅ | ❌ | -## Technical preview constraints -Not all features and functionality are available in the technical preview release. In particular, consider the following technical constraints: +## Beta release features and constraints -- Consul ACLs must be disabled or the ACL `default_policy` must be set to `allow`. -- Mesh gateways for _server to server traffic_ are not available. However, mesh gateways for _service to service traffic_ between clusters are available. -- Services exported to peered clusters must be configured to use the TCP protcol (not HTTP, HTTP 2 and gRPC). -- Support for dynamic routing such as splits, custom routes, or redirects is not available. -- The `consul intention CLI` command is not supported. To manage intentions that specify services in peered clusters, use [configuration entries](/docs/connect/config-entries/service-intentions). -- [L7 permissions](/docs/connect/l7-traffic) are not supported. -- Configuring service failover across peers is not supported. +The cluster peering beta includes the following features and functionality: + +- Mesh gateways for _service to service traffic_ between clusters are available. For more information on configuring mesh gateways across peers, refer to [Service-to-service Traffic Across Peered Clusters](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers). +- You can generate peering tokens, establish, list, read, and delete peerings, and manage intentions for peering connections with both the API and the UI. +- You can configure [transparent proxies](/docs/connect/transparent-proxy) for peered services. +- You can use the [`peering` rule for ACL enforcement](/docs/security/acl/acl-rules#peering) of peering APIs. + +Not all features and functionality are available in the beta release. In particular, consider the following technical constraints: + +- Mesh gateways for _server to server traffic_ are not available. +- Services with node, instance, and check definitions totaling more than 4MB cannot be exported to a peer. +- Dynamic routing features such as splits, custom routes, and redirects cannot target services in a peered cluster. +- Configuring service failover across peers is not supported for service mesh. +- Consul datacenters that are already federated stay federated. You do not need to migrate WAN federated clusters to cluster peering. +- The `consul intention` CLI command is not supported. To manage intentions that specify services in peered clusters, use [configuration entries](/docs/connect/config-entries/service-intentions). - Accessing key/value stores across peers is not supported. -- Consul datacenters that are already federated stay federated. -- Non-enterprise Consul instances cannot sync services with namespaces outside of the `default` namespace. +- Because non-Enterprise Consul instances are restricted to the `default` namespace, Consul Enterprise instances cannot export services from outside of the `default` namespace to non-Enterprise peers. diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index 529b583fbd..35f17959cb 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -7,25 +7,23 @@ description: >- # Cluster Peering on Kubernetes -~> **Cluster peering is currently in technical preview:** Functionality associated -with cluster peering is subject to change. You should never use the technical -preview release in secure environments or production scenarios. Features in -technical preview may have performance issues, scaling issues, and limited support. +~> **Cluster peering is currently in beta:** Functionality associated +with cluster peering is subject to change. You should never use the beta release in secure environments or production scenarios. Features in +beta may have performance issues, scaling issues, and limited support.

Cluster peering is not currently available in the HCP Consul offering. -To establish a cluster peering connection on Kubernetes, you need to enable the feature in the Helm chart and create custom resource definitions for each side of the peering. +To establish a cluster peering connection on Kubernetes, you need to enable the feature in the Helm chart and create custom resource definitions (CRDs) for each side of the peering. -The following Custom Resource Definitions (CRDs) are used to create and manage a peering connection: +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. ## Prerequisites -You must implement the following requirements to create and use cluster peering connections with Kubernetes: -- Consul 1.13 Alpha 2 or later -- At least two Kubernetes clusters -- The Kubernetes clusters must be running in a flat network -- The network must be running on Consul on Kubernetes v.0.45 or later +You must implement the following requirements to create and use cluster peering connections with Kubernetes: +- Consul version 1.13.1 or later +- At least two Kubernetes clusters +- The installation must be running on Consul on Kubernetes version 0.47.1 or later ### Helm chart configuration @@ -35,12 +33,12 @@ To establish cluster peering through Kubernetes, deploy clusters with the follow ```yaml global: - image: "hashicorp/consul:1.13.0-alpha2" + image: "hashicorp/consul:1.13.1" peering: enabled: true connectInject: enabled: true - controller: + controller: enabled: true meshGateway: enabled: true @@ -48,18 +46,23 @@ To establish cluster peering through Kubernetes, deploy clusters with the follow ``` - -Install Consul on Kubernetes on each Kubernetes cluster by applying `values.yaml` using the Helm CLI. -```shell-session +Install Consul on Kubernetes on each Kubernetes cluster by applying `values.yaml` using the Helm CLI. With these values, +the servers in each cluster will be exposed over a Kubernetes Load balancer service. This service can be customized +using [`server.exposeService`](/docs/k8s/helm#v-server-exposeservice). When generating a peering token from one of the +clusters, the address(es) of the load balancer will be used in the peering token, so the peering stream will go through +the load balancer in front of the servers. For customizing the addresses used in the peering token, see +[`global.peering.tokenGeneration`](/docs/k8s/helm#v-global-peering-tokengeneration). + +```shell-session $ export HELM_RELEASE_NAME=cluster-name ``` ```shell-session -$ helm install ${HELM_RELEASE_NAME} hashicorp/consul --version "0.45.0" --values values.yaml +$ helm install ${HELM_RELEASE_NAME} hashicorp/consul --version "0.47.1" --values values.yaml ``` -## Create a peering connection +## Create a peering token To peer Kubernetes clusters running Consul, you need to create a peering token and share it with the other cluster. @@ -94,6 +97,8 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl get secret peering-token --output yaml > peering-token.yml ``` +## Establish a peering connection between clusters + 1. Apply the peering token to the second cluster. ```shell-session @@ -125,9 +130,9 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl apply --filename dialer.yml ``` -## Deploy and export cluster services +## Export services between clusters -1. For the service in "cluster-02" that you want to export, add the following [annotations](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) to your service's pods. This service is referred to as "backend-service" in the following steps. +1. For the service in "cluster-02" that you want to export, add the following [annotation](/docs/k8s/annotations-and-labels) to your service's pods. @@ -135,7 +140,6 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a ##… annotations: "consul.hashicorp.com/connect-inject": "true" - "consul.hashicorp.com/transparent-proxy": "false" ##… ``` @@ -159,6 +163,14 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a +1. Apply the service file and the `ExportedServices` resource for the second cluster. + + ```shell-session + $ kubectl apply --filename backend-service.yml --filename exportedsvc.yml + ``` + +## Authorize services for peers + 1. Create service intentions for the second cluster. @@ -180,16 +192,10 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a -1. Apply the service file, the `ExportedServices` resource, and the intentions to the second cluster. +1. Apply the intentions to the second cluster. ```shell-session - $ kubectl apply --filename backend-service.yml --filename exportedsvc.yml --filename intention.yml - ``` - -1. To confirm that you peered your clusters, in `cluster-01`, query the `/health` HTTP endpoint. - - ```shell-session - $ curl "localhost:8500/v1/health/connect/backend?peer=cluster-02" + $ kubectl apply --filename intention.yml ``` 1. For the services in `cluster-01` that you want to access the "backend-service," add the following annotations to the service file. @@ -200,8 +206,6 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a ##… annotations: "consul.hashicorp.com/connect-inject": "true" - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/connect-service-upstreams": "backend-service.svc.cluster-02.peer:1234" ##… ``` @@ -213,10 +217,10 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl apply --filename frontend-service.yml ``` -1. Run the following command and check the output to confirm that you peered your clusters successfully. +1. Run the following command in `frontend-service` and check the output to confirm that you peered your clusters successfully. ```shell-session - $ curl localhost:1234 + $ kubectl exec -it $(kubectl get pod -l app=frontend -o name) -- curl localhost:1234 { "name": "backend-service", ##… @@ -234,3 +238,32 @@ To confirm that you deleted your peering connection, in `cluster-01`, query the ```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 on the cluster where you created the `PeeringAcceptor` (in this example, `cluster-01`). + +1. You can do this by creating or updating the annotation `consul.hashicorp.com/peering-version` on the `PeeringAcceptor`. If the annotation already exists, update its value to a version that is higher. + + + + ```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. + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. Once you have done this, repeat the steps in the peering process. This includes saving your peering token so that you can export it to the other cluster. This will re-establish peering with the updated token. + +~> **Note:** A new peering token is only generated upon manually setting and updating the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token will cause the previous token to expire. diff --git a/website/content/docs/connect/config-entries/ingress-gateway.mdx b/website/content/docs/connect/config-entries/ingress-gateway.mdx index 78773188de..fa95c5b197 100644 --- a/website/content/docs/connect/config-entries/ingress-gateway.mdx +++ b/website/content/docs/connect/config-entries/ingress-gateway.mdx @@ -991,14 +991,12 @@ You can specify the following parameters to configure ingress gateway configurat }, { name: 'TLSMinVersion', - yaml: false, type: 'string: ""', description: "Set the default minimum TLS version supported for the gateway's listeners. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.", }, { name: 'TLSMaxVersion', - yaml: false, type: 'string: ""', description: { hcl: @@ -1009,7 +1007,6 @@ You can specify the following parameters to configure ingress gateway configurat }, { name: 'CipherSuites', - yaml: false, type: 'array: ', description: `Set the default list of TLS cipher suites for the gateway's listeners to support when negotiating connections using @@ -1179,21 +1176,18 @@ You can specify the following parameters to configure ingress gateway configurat }, { name: 'TLSMinVersion', - yaml: false, type: 'string: ""', description: 'Set the minimum TLS version supported for this listener. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.', }, { name: 'TLSMaxVersion', - yaml: false, type: 'string: ""', description: 'Set the maximum TLS version supported for this listener. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`.', }, { name: 'CipherSuites', - yaml: false, type: 'array: ', description: `Set the list of TLS cipher suites to support when negotiating connections using TLS 1.2 or earlier. If unspecified, diff --git a/website/content/docs/connect/config-entries/mesh.mdx b/website/content/docs/connect/config-entries/mesh.mdx index 8c9f3e718e..e8d6b4de5f 100644 --- a/website/content/docs/connect/config-entries/mesh.mdx +++ b/website/content/docs/connect/config-entries/mesh.mdx @@ -271,7 +271,6 @@ Note that the Kubernetes example does not include a `partition` field. Configura children: [ { name: 'Incoming', - yaml: false, type: 'TLSDirectionConfig: ', description: `TLS configuration for inbound mTLS connections targeting the public listener on \`connect-proxy\` and \`terminating-gateway\` @@ -279,14 +278,12 @@ Note that the Kubernetes example does not include a `partition` field. Configura children: [ { name: 'TLSMinVersion', - yaml: false, type: 'string: ""', description: "Set the default minimum TLS version supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.", }, { name: 'TLSMaxVersion', - yaml: false, type: 'string: ""', description: { hcl: @@ -297,7 +294,6 @@ Note that the Kubernetes example does not include a `partition` field. Configura }, { name: 'CipherSuites', - yaml: false, type: 'array: ', description: `Set the default list of TLS cipher suites to support when negotiating connections using @@ -315,7 +311,6 @@ Note that the Kubernetes example does not include a `partition` field. Configura }, { name: 'Outgoing', - yaml: false, type: 'TLSDirectionConfig: ', description: `TLS configuration for outbound mTLS connections dialing upstreams from \`connect-proxy\` and \`ingress-gateway\` @@ -323,14 +318,12 @@ Note that the Kubernetes example does not include a `partition` field. Configura children: [ { name: 'TLSMinVersion', - yaml: false, type: 'string: ""', description: "Set the default minimum TLS version supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.", }, { name: 'TLSMaxVersion', - yaml: false, type: 'string: ""', description: { hcl: @@ -341,7 +334,6 @@ Note that the Kubernetes example does not include a `partition` field. Configura }, { name: 'CipherSuites', - yaml: false, type: 'array: ', description: `Set the default list of TLS cipher suites to support when negotiating connections using @@ -366,9 +358,8 @@ Note that the Kubernetes example does not include a `partition` field. Configura children: [ { name: 'SanitizeXForwardedClientCert', - yaml: false, type: 'bool: ', - description: `If configured to \`true\`, the \`forward_client_cert_details\` option will be set to \`SANITIZE\` + description: `If configured to \`true\`, the \`forward_client_cert_details\` option will be set to \`SANITIZE\` for all Envoy proxies. As a result, Consul will not include the \`x-forwarded-client-cert\` header in the next hop. If set to \`false\` (default), the XFCC header is propagated to upstream applications.`, }, diff --git a/website/content/docs/connect/config-entries/proxy-defaults.mdx b/website/content/docs/connect/config-entries/proxy-defaults.mdx index 3be5c850b1..c6f82d7835 100644 --- a/website/content/docs/connect/config-entries/proxy-defaults.mdx +++ b/website/content/docs/connect/config-entries/proxy-defaults.mdx @@ -10,7 +10,7 @@ description: >- # Proxy Defaults -The `proxy-defaults` configuration entry (`ProxyDefaults` on Kubernetes) allows you +The `proxy-defaults` configuration entry (`ProxyDefaults` on Kubernetes) allows you to configure global defaults across all services for Connect proxy configurations. Only one global entry is supported. @@ -28,8 +28,8 @@ service definitions](/docs/connect/registration/sidecar-service). ## Requirements The following Consul binaries are supported: -* Consul 1.8.4+ on Kubernetes. -* Consul 1.5.0+ on other platforms. +* Consul 1.8.4+ on Kubernetes. +* Consul 1.5.0+ on other platforms. ## Usage @@ -321,7 +321,6 @@ spec: \`direct\` represents that the proxy's listeners must be dialed directly by the local application and other proxies. Added in v1.10.0.`, - yaml: false, }, { name: 'TransparentProxy', @@ -333,7 +332,6 @@ spec: type: 'int: "15001"', description: `The port the proxy should listen on for outbound traffic. This must be the port where outbound application traffic is captured and redirected to.`, - yaml: false, }, { name: 'DialedDirectly', diff --git a/website/content/docs/connect/config-entries/service-defaults.mdx b/website/content/docs/connect/config-entries/service-defaults.mdx index c69d131698..b431e43459 100644 --- a/website/content/docs/connect/config-entries/service-defaults.mdx +++ b/website/content/docs/connect/config-entries/service-defaults.mdx @@ -236,6 +236,50 @@ 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. + + + + ```hcl + Kind = "service-defaults" + Name = "test-destination" + Protocol = "tcp" + Destination { + Addresses = ["test.com","test.org"] + Port = 443 + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceDefaults + metadata: + name: test-destination + spec: + destination: + addresses: + - "test.com" + - "test.org" + port: 443 + ``` + + ```json + { + "Kind": "service-defaults", + "Name": "test-destination", + "Protocol": "http", + "Destination": { + "Addresses": ["test.com","test.org"], + "Port": 443 + } + } + ``` + + + ## Available Fields ', + description: `Controls configuration specific to destinations through terminating-gateway. Added in v1.13.0.`, + children: [ + { + name: 'Addresses', + type: 'array: []', + description:`List of addresses associated with the destination. This can be a hostname or an IP address. + Wildcards are not accepted.`, + }, + { + name: 'Port', + type: 'int: 0', + description: `Port number associated with the destination.`, + }, + ] + }, + { + name: 'MaxInboundConnections', + description: 'The maximum number of concurrent inbound connections to each service instance.', + type: 'int: 0', + yaml: true, + }, { name: 'MeshGateway', type: 'MeshGatewayConfig: ', diff --git a/website/content/docs/connect/config-entries/terminating-gateway.mdx b/website/content/docs/connect/config-entries/terminating-gateway.mdx index 80966017a4..c406c5687d 100644 --- a/website/content/docs/connect/config-entries/terminating-gateway.mdx +++ b/website/content/docs/connect/config-entries/terminating-gateway.mdx @@ -153,8 +153,9 @@ spec: Link gateway named "us-west-gateway" with the billing service, and specify a CA file to be used for one-way TLS authentication. --> **Note**: The `CAFile` parameter must be specified _and_ point to a valid CA -bundle in order to properly initiate a TLS connection to the destination service. +-> **Note**: When not using destinations in transparent proxy mode, you must specify the `CAFile` parameter +and point to a valid CA bundle in order to properly initiate a TLS +connection to the destination service. For more information about configuring a gateway for destinations, refer to [Register an External Service as a Destination](/docs/k8s/connect/terminating-gateways#register-an-external-service-as-a-destination). @@ -622,11 +623,12 @@ spec: { name: 'Services', type: 'array: ', - description: `A list of services to link + description: `A list of services or destinations to link with the gateway. The gateway will proxy traffic to these services. These linked services must be registered with Consul for the gateway to discover their addresses. They must also - be registered in the same Consul datacenter as the terminating gateway. If Consul ACLs are - enabled, the Terminating Gateway's ACL token must grant service:write for all linked services.`, + be registered in the same Consul datacenter as the terminating gateway. + Destinations are an exception to this requirement, and only need to be defined as a service-defaults configuration entry in the same datacenter. + If Consul ACLs are enabled, the Terminating Gateway's ACL token must grant service:write for all linked services.`, children: [ { name: 'Name', diff --git a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-partitions.mdx b/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-partitions.mdx index dfd4780c6a..f3542c4d61 100644 --- a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-partitions.mdx +++ b/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-partitions.mdx @@ -3,7 +3,7 @@ layout: docs page_title: Service-to-service Traffic Across Partitions description: >- This topic describes how to configure mesh gateways to route a service's data to upstreams - in other partitions. It describes how to use Envoy and how you can integrate with your preferred gateway. + in other partitions. It describes how to use Envoy and how you can integrate with your preferred gateway. --- # Service-to-service Traffic Across Partitions 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 new file mode 100644 index 0000000000..0163730ba4 --- /dev/null +++ b/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx @@ -0,0 +1,55 @@ +--- +layout: docs +page_title: Service-to-service Traffic Across Peered Clusters +description: >- + This topic describes how to configure mesh gateways to route a service's data to upstreams + in clusters that have a peering connection. +--- + +# Service-to-service Traffic Across Peered Clusters + +~> **Cluster peering is currently in beta**: Functionality associated with cluster peering is subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support. + +Mesh gateways are required for you to route service mesh traffic between different Consul clusters. Clusters can reside in different clouds or runtime environments where general interconnectivity between all services in all clusters is not feasible. + +Unlike mesh gateways for datacenters and partitions, mesh gateways for cluster peering decrypt data to HTTP services within the mTLS session. 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.13.0 or newer. +- A local Consul agent is required to manage mesh gateway configuration. +- [Enable Consul service mesh](/docs/agent/config/config-files#connect-parameters) in all clusters. +- [Enable `peering`](/docs/agent/config/config-files) on all Consul servers. +- Use [Envoy proxies](/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](/docs/connect/proxies/envoy#gateway-options) and [Escape-hatch Overrides](/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](/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](/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](/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. + +### Modes + +Modes are not configurable for mesh gateways that connect peered clusters. By default, all proxies connecting to peered clusters use mesh gateways in [remote mode](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-datacenters#remote). diff --git a/website/content/docs/connect/gateways/terminating-gateway.mdx b/website/content/docs/connect/gateways/terminating-gateway.mdx index 2c8218c38e..6a5b557ece 100644 --- a/website/content/docs/connect/gateways/terminating-gateway.mdx +++ b/website/content/docs/connect/gateways/terminating-gateway.mdx @@ -12,7 +12,7 @@ description: >- -> **1.8.0+:** This feature is available in Consul versions 1.8.0 and newer. Terminating gateways enable connectivity within your organizational network from services in the Consul service mesh to -services outside the mesh. These gateways effectively act as Connect proxies that can +services and [destinations](/docs/connect/config-entries/service-defaults#terminating-gateway-destination) outside the mesh. These gateways effectively act as Connect proxies that can represent more than one service. They terminate Connect mTLS connections, enforce intentions, and forward requests to the appropriate destination. @@ -55,6 +55,7 @@ Each terminating gateway needs: 1. A local Consul client agent to manage its configuration. 2. General network connectivity to services within its local Consul datacenter. +3. General network connectivity to services and destinations outside the mesh that are part of the gateway services list. Terminating gateways also require that your Consul datacenters are configured correctly: @@ -96,7 +97,7 @@ to terminate mTLS connections on behalf of the linked services and then route th If the Consul client agent on the gateway's node is not configured to use the default gRPC port, 8502, then the gateway's token must also provide `agent:read` for its node's name in order to discover the agent's gRPC port. gRPC is used to expose Envoy's xDS API to Envoy proxies. -Linking services to a terminating gateway is done with a `terminating-gateway` +You can link services and destinations to a terminating gateway with a `terminating-gateway` [configuration entry](/docs/connect/config-entries/terminating-gateway). This config entry can be applied via the [CLI](/commands/config/write) or [API](/api-docs/config#apply-configuration). @@ -122,5 +123,10 @@ However, ensure that the [node name](/api-docs/catalog#node) for external servic does not match the node name of any Consul client agent node. If the node name overlaps with the node name of a Consul client agent, Consul's [anti-entropy sync](/docs/architecture/anti-entropy) will delete the services registered via the `/catalog/register` HTTP API endpoint. +Service-defaults [destinations](/docs/connect/config-entries/service-defaults#destination) let you +define endpoints external to the mesh and routable through a terminating gateway in transparent mode. +After you define a service-defaults configuration entry for each destination, you can use the service-default name as part of the terminating gateway services list. +If a service and a destination service-defaults have the same name, the terminating gateway will use the service. + For a complete example of how to register external services review the [external services tutorial](https://learn.hashicorp.com/tutorials/consul/service-registration-external-services). diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index ee0b1b1652..7ada5b6fd0 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -36,9 +36,9 @@ Consul supports **four major Envoy releases** at the beginning of each major Con | Consul Version | Compatible Envoy Versions | | ------------------- | -----------------------------------------------------------------------------------| -| 1.12.x | 1.22.2, 1.21.3, 1.20.4, 1.19.5 | -| 1.11.x | 1.20.2, 1.19.3, 1.18.6, 1.17.41 | -| 1.10.x | 1.18.6, 1.17.41, 1.16.51 , 1.15.51 | +| 1.13.x | 1.23.0, 1.22.2, 1.21.4, 1.20.6 | +| 1.12.x | 1.22.2, 1.21.4, 1.20.6, 1.19.5 | +| 1.11.x | 1.20.6, 1.19.5, 1.18.6, 1.17.41 | 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. diff --git a/website/content/docs/connect/registration/service-registration.mdx b/website/content/docs/connect/registration/service-registration.mdx index 66b1aa96f7..5b6e8ef598 100644 --- a/website/content/docs/connect/registration/service-registration.mdx +++ b/website/content/docs/connect/registration/service-registration.mdx @@ -115,6 +115,7 @@ proxy = { mode = "transparent" transparent_proxy = {} upstreams = [] +} ``` ```json @@ -167,7 +168,7 @@ You can configure the service mesh proxy to create listeners for upstream servic |`destination_name` | String value that specifies the name of the service or prepared query to route the service mesh to. The prepared query should be the name or the ID of the prepared query. | Required | None | | `destination_namespace` | String value that specifies the namespace containing the upstream service. | Optional | `default` | | `destination_peer` | String value that specifies the name of the peer cluster containing the upstream service. | Optional | None | -| `destination_partition` | String value that specifies the name of the admin partition containing the upstream service. | Optional | `default` | +| `destination_partition` | String value that specifies the name of the admin partition containing the upstream service. If `destination_peer` is set, `destination_partition` refers to the local admin partition in which the peering was established. | Optional | `default` | | `local_bind_port` | Integer value that specifies the port to bind a local listener to. The application will make outbound connections to the upstream from the local port. | Required | None | | `local_bind_address` | String value that specifies the address to bind a local listener to. The application will make outbound connections to the upstream service from the local bind address. | Optional | `127.0.0.1` | | `local_bind_socket_path` | String value that specifies the path at which to bind a Unix domain socket listener. The application will make outbound connections to the upstream from the local bind socket path.
This parameter conflicts with the `local_bind_port` or `local_bind_address` parameters.
Supported when using Envoy as a proxy. | Optional | None| @@ -195,7 +196,7 @@ local_bind_socket_path = "/tmp/redis_5678.sock" local_bind_socket_mode = "0700" mesh_gateway = { mode = "local" - } +} ``` ```json @@ -257,6 +258,29 @@ local_bind_port = 9090 } ``` +
+ + +```hcl +destination_peer = "cloud-services" +destination_partition = "finance" +destination_namespace = "default" +destination_type = "service" +destination_name = "api" +local_bind_port = 9090 +``` + +```json +{ + "destination_peer": "cloud-services", + "destination_partition": "finance", + "destination_namespace": "default", + "destination_type": "service", + "destination_name": "api", + "local_bind_port": 9090 +} +``` + ## Proxy Modes @@ -297,7 +321,7 @@ registrations](/docs/discovery/services#service-definition-parameter-case). - `dialed_directly` `(bool: false)` - Determines whether this proxy instance's IP address can be dialed directly by transparent proxies. Transparent proxies typically dial upstreams using the "virtual" tagged address, which load balances across instances. A database cluster with a leader is an example - where dialing individual instances can be helpful. + where dialing individual instances can be helpful. Cannot be used with upstreams which define a `destination_peer`. ~> **Note:** Dynamic routing rules such as failovers and redirects do not apply to services dialed directly. Additionally, the connection is proxied using a TCP proxy with a connection timeout of 5 seconds. @@ -521,7 +545,7 @@ services { "proxy": { "name": "service-2", "local_service_socket_path": "/tmp/socket_service_2" - ... + ... } } } diff --git a/website/content/docs/connect/transparent-proxy.mdx b/website/content/docs/connect/transparent-proxy.mdx index 1091091cfb..57ad48ba7a 100644 --- a/website/content/docs/connect/transparent-proxy.mdx +++ b/website/content/docs/connect/transparent-proxy.mdx @@ -29,9 +29,9 @@ Without transparent proxy, application owners need to: With transparent proxy: -1. Upstreams are inferred from service intentions, so no explicit configuration - is needed. -1. Outbound connections pointing to a KubeDNS name "just work" — network rules +1. Local upstreams are inferred from service intentions and peered upstreams are + inferred from imported services, so no explicit configuration is needed. +1. Outbound connections pointing to a Kubernetes DNS record "just work" — network rules redirect them through the proxy. 1. Inbound traffic is forced to go through the proxy to prevent unauthorized direct access to the application. @@ -45,15 +45,15 @@ and only reaches intended destinations since the proxy can enforce security and Previously, service mesh users would need to explicitly define upstreams for a service as a local listener on the sidecar proxy, and dial the local listener to reach the appropriate upstream. Users would also have to set intentions to allow specific services to talk to one another. Transparent proxying reduces this duplication, by determining upstreams -implicitly from Service Intentions. Explicit upstreams are still supported in the [proxy service +implicitly from Service Intentions and imported services from a peer. Explicit upstreams are still supported in the [proxy service registration](/docs/connect/registration/service-registration) on VMs and via the [annotation](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) in Kubernetes. To support transparent proxying, Consul's CLI now has a command [`consul connect redirect-traffic`](/commands/connect/redirect-traffic) to redirect traffic through an inbound and -outbound listener on the sidecar. Consul also watches Service Intentions and configures the Envoy proxy with the appropriate -upstream IPs. If the default ACL policy is "allow", then Service Intentions are not required. In Consul on Kubernetes, -the traffic redirection command is automatically set up via an init container. +outbound listener on the sidecar. Consul also watches Service Intentions and imported services then configures the Envoy +proxy with the appropriate upstream IPs. If the default ACL policy is "allow", then Service Intentions are not required. +In Consul on Kubernetes, the traffic redirection command is automatically set up via an init container. ## Prerequisites @@ -62,7 +62,7 @@ the traffic redirection command is automatically set up via an init container. * To use transparent proxy on Kubernetes, Consul-helm >= `0.32.0` and Consul-k8s >= `0.26.0` are required in addition to Consul >= `1.10.0`. * If the default policy for ACLs is "deny", then Service Intentions should be set up to allow intended services to connect to each other. Otherwise, all Connect services can talk to all other services. -* If using Transparent Proxy, all worker nodes within a Kubernetes cluster must have the `ip_tables` kernel module running, e.g. `modprobe ip_tables`. +* If using Transparent Proxy, all worker nodes within a Kubernetes cluster must have the `ip_tables` kernel module running, e.g. `modprobe ip_tables`. The Kubernetes integration takes care of registering Kubernetes services with Consul, injecting a sidecar proxy, and enabling traffic redirection. @@ -160,27 +160,43 @@ configure exceptions on a per-Pod basis. The following Pod annotations allow you - [`consul.hashicorp.com/transparent-proxy-exclude-uids`](/docs/k8s/annotations-and-labels#consul-hashicorp-com-transparent-proxy-exclude-uids) +### Dialing Services Across Kubernetes Clusters + +- You cannot use transparent proxy in a deployment configuration with [federation between Kubernetes clusters](/docs/k8s/installation/multi-cluster/kubernetes). + Instead, services in one Kubernetes cluster must explicitly dial a service to a Consul datacenter in another Kubernetes cluster using the + [consul.hashicorp.com/connect-service-upstreams](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) + annotation. For example, an annotation of + `"consul.hashicorp.com/connect-service-upstreams": "my-service:1234:dc2"` reaches an upstream service called `my-service` + in the datacenter `dc2` on port `1234`. + +- You cannot use transparent proxy in a deployment configuration with a + [single Consul datacenter spanning multiple Kubernetes clusters](/docs/k8s/installation/deployment-configurations/single-dc-multi-k8s). Instead, + services in one Kubernetes cluster must explicitly dial a service in another Kubernetes cluster using the + [consul.hashicorp.com/connect-service-upstreams](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) + annotation. For example, an annotation of + `"consul.hashicorp.com/connect-service-upstreams": "my-service:1234"`, + reaches an upstream service called `my-service` in another Kubernetes cluster and on port `1234`. + Although transparent proxy is enabled, Kubernetes DNS is not utilized when communicating between services that exist on separate Kubernetes clusters. + +- In a deployment configuration with [cluster peering](/docs/connect/cluster-peering), + transparent proxy is fully supported and thus dialing services explicitly is not required. + + ## Known Limitations -* Traffic can only be transparently proxied when the address dialed corresponds to the address of a service in the -transparent proxy's datacenter. Services can also dial explicit upstreams in other datacenters without transparent proxy, for example, by adding an -[annotation](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) such as -`"consul.hashicorp.com/connect-service-upstreams": "my-service:1234:dc2"` to reach an upstream service called `my-service` -in the datacenter `dc2`. -* In the deployment configuration where a [single Consul datacenter spans multiple Kubernetes clusters](/docs/k8s/installation/deployment-configurations/single-dc-multi-k8s), services in one Kubernetes cluster must explicitly dial a service in another Kubernetes cluster using the [consul.hashicorp.com/connect-service-upstreams](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) annotation. An example would be -`"consul.hashicorp.com/connect-service-upstreams": "my-service:1234"`, where `my-service` is the service that exists in another Kubernetes cluster and is exposed on port `1234`. Although Transparent Proxy is enabled, KubeDNS is not utilized when communicating between services existing on separate Kubernetes clusters. +- Deployment configurations with federation across or a single datacenter spanning multiple clusters must explicitly dial a + service in another datacenter or cluster using annotations. -* When dialing headless services, the request will be proxied using a plain TCP - proxy. The upstream's protocol is not considered. +- When dialing headless services, the request is proxied using a plain TCP proxy. The upstream's protocol is not considered. ## Using Transparent Proxy In Kubernetes, services can reach other services via their -[KubeDNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) address or via Pod IPs, and that +[Kubernetes DNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) address or through Pod IPs, and that traffic will be transparently sent through the proxy. Connect services in Kubernetes are required to have a Kubernetes service selecting the Pods. -~> Note: In order to use KubeDNS, the Kubernetes service name will need to match the Consul service name. This will be the +~> **Note**: In order to use Kubernetes DNS, the Kubernetes service name needs to match the Consul service name. This is the case by default, unless the service Pods have the annotation `consul.hashicorp.com/connect-service` overriding the Consul service name. @@ -192,7 +208,7 @@ inbound and outbound listener on the sidecar proxy. The proxy will be configured appropriate upstream services based on [Service Intentions](/docs/connect/config-entries/service-intentions). This means Connect services no longer need to use the `consul.hashicorp.com/connect-service-upstreams` annotation to configure upstreams explicitly. Once the -Service Intentions are set, they can simply address the upstream services using KubeDNS. +Service Intentions are set, they can simply address the upstream services using Kubernetes DNS. As of Consul-k8s >= `0.26.0` and Consul-helm >= `0.32.0`, a Kubernetes service that selects application pods is required for Connect applications, i.e: @@ -213,7 +229,7 @@ spec: In the example above, if another service wants to reach `sample-app` via transparent proxying, it can dial `sample-app.default.svc.cluster.local`, using -[KubeDNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/). +[Kubernetes DNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/). If ACLs with default "deny" policy are enabled, it also needs a [ServiceIntention](/docs/connect/config-entries/service-intentions) allowing it to talk to `sample-app`. diff --git a/website/content/docs/discovery/checks.mdx b/website/content/docs/discovery/checks.mdx index 5a21495793..1b4c4faf4b 100644 --- a/website/content/docs/discovery/checks.mdx +++ b/website/content/docs/discovery/checks.mdx @@ -13,144 +13,72 @@ description: >- 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](https://learn.hashicorp.com/tutorials/consul/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. +Review the [service health checks tutorial](https://learn.hashicorp.com/tutorials/consul/service-registration-health-checks) +to get a more complete example on how to leverage health check capabilities in Consul. -There are several different kinds of checks: +## Registering a health check -- Script + Interval - These checks depend on invoking an external application - that performs the health check, exits with an appropriate exit code, and potentially - generates some output. A script is paired with an invocation interval (e.g. - every 30 seconds). This is similar to the Nagios plugin system. The output of - a script check is limited to 4KB. Output larger than this will be truncated. - By default, Script checks will be configured with a timeout equal to 30 seconds. - It is possible to configure a custom Script check timeout value by specifying the - `timeout` field in the check definition. When the timeout is reached on Windows, - Consul will wait for any child processes spawned by the script to finish. For any - other system, Consul will attempt to force-kill the script and any child processes - it has spawned once the timeout has passed. - In Consul 0.9.0 and later, script checks are not enabled by default. To use them you - can either use : +There are three ways to register a service with health checks: - - [`enable_local_script_checks`](/docs/agent/config/cli-flags#_enable_local_script_checks): - enable script checks defined in local config files. Script checks defined via the HTTP - API will not be allowed. - - [`enable_script_checks`](/docs/agent/config/cli-flags#_enable_script_checks): enable - script checks regardless of how they are defined. +1. Start or reload a Consul agent with a service definition file in the + [agent's configuration directory](/docs/agent#configuring-consul-agents). +1. Call the + [`/agent/service/register`](/api-docs/agent/service#register-service) + HTTP API endpoint to register the service. +1. Use the + [`consul services register`](/commands/services/register) + CLI command to register the service. - ~> **Security Warning:** Enabling script checks in some configurations may - introduce a remote execution vulnerability which is known to be targeted by - malware. We strongly recommend `enable_local_script_checks` instead. See [this - blog post](https://www.hashicorp.com/blog/protecting-consul-from-rce-risk-in-specific-configurations) - for more details. +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. -- `HTTP + Interval` - These checks make an HTTP `GET` request to the specified URL, - waiting the specified `interval` amount of time between requests (eg. 30 seconds). - 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 header - fields can be set through the `header` field which is a map of lists of - strings, e.g. `{"x-foo": ["bar", "baz"]}`. By default, HTTP checks will be - configured with a request timeout equal to 10 seconds. +## Types of checks - It is possible to configure a custom HTTP check timeout value by - specifying the `timeout` field in the check definition. The output of the - check is limited to roughly 4KB. Responses larger than this will be 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 will be set - automatically from the URL if it uses a hostname (as opposed to an IP address); - the value can be overridden by setting `tls_server_name`. +This section describes the available types of health checks you can use to +automatically monitor the health of a service instance or node. - Consul follows HTTP redirects by default. Set the `disable_redirects` field to - `true` to disable redirects. +-> **To manually mark a service unhealthy:** Use the maintenance mode + [CLI command](/commands/maint) or + [HTTP API endpoint](/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. -- `TCP + Interval` - These checks make a TCP connection attempt to the specified - IP/hostname and port, waiting `interval` amount of time between attempts - (e.g. 30 seconds). If no hostname - is specified, it defaults to "localhost". The status of the service depends on - whether the connection attempt is successful (ie - the port is currently - accepting connections). If the connection is accepted, the status is - `success`, otherwise the status is `critical`. In the case of a hostname that - resolves to both IPv4 and IPv6 addresses, an attempt will be made to both - addresses, and the first successful connection attempt will result 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 will be configured with a request timeout of 10 seconds. - It is possible to configure a custom TCP check timeout value by specifying the - `timeout` field in the check definition. +### Script check ((#script-interval)) -- `UDP + Interval` - These checks direct the client to periodically send UDP datagrams - to the specified IP/hostname and port. The duration specified in the `interval` field sets the amount of time - between attempts, such as `30s` to indicate 30 seconds. The check is logged as healthy if any response from the UDP server is received. Any other result sets the status to `critical`. - The default interval for, UDP checks is `10s`, but you can configure a custom UDP check timeout value by specifying the - `timeout` field in the check definition. If any timeout on read exists, the check is still considered healthy. +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. -- `Time to Live (TTL)` ((#ttl)) - These checks retain their last known state - for a given TTL. The state of the check must be updated periodically over the HTTP - interface. If an external system fails to update the status within a given TTL, - the check is set to the failed state. This mechanism, conceptually similar to a - dead man's switch, relies on the application to directly report its health. For - example, a healthy app can periodically `PUT` a status update to the HTTP endpoint; - if the app fails, the TTL will expire and the health check enters a critical state. - The endpoints used to update health information for a given check are: [pass](/api-docs/agent/check#ttl-check-pass), - [warn](/api-docs/agent/check#ttl-check-warn), [fail](/api-docs/agent/check#ttl-check-fail), - and [update](/api-docs/agent/check#ttl-check-update). TTL checks also persist their - last known status to disk. This allows the Consul agent to 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. +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. -- `Docker + Interval` - These checks depend on invoking an external application which - is packaged within a Docker Container. The application is triggered within the running - container via 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 which makes it possible to run containers which - have different shells on the same host. Check output for Docker is limited to - 4KB. Any output larger than this will be truncated. In Consul 0.9.0 and later, the agent - must be configured with [`enable_script_checks`](/docs/agent/config/cli-flags#_enable_script_checks) - set to `true` in order to enable Docker health checks. +Script checks are not enabled by default. +To enable a Consul agent to perform script checks, +use one of the following agent configuration options: -- `gRPC + Interval` - 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). - The state of the check will be updated by probing the configured endpoint, waiting `interval` - amount of time between probes (eg. 30 seconds). By default, gRPC checks will be configured - with a default timeout of 10 seconds. - It is possible to configure a custom timeout value by specifying the `timeout` field in - the check definition. gRPC checks will default to not using TLS, but TLS can be enabled by - setting `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 in the following format `/:service_identifier`. +- [`enable_local_script_checks`](/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`](/docs/agent/config/cli-flags#_enable_script_checks): + Enable script checks no matter how they are registered. -- `H2ping + Interval` - These checks test an endpoint that uses http2 - by connecting to the endpoint and sending a ping frame. TLS is assumed to be configured by default. - To disable TLS and use h2c, set `h2ping_use_tls` to `false`. If the ping is successful - within a specified timeout, then the check is updated as passing. - The timeout defaults to 10 seconds, but is configurable using the `timeout` field. If TLS is enabled a valid - certificate is required, unless `tls_skip_verify` is set to `true`. - The check will be run on the interval specified by the `interval` field. + ~> **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). -- `Alias` - These checks alias the health state of another registered - node or service. The state of the check will be updated 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 will be critical. For the blocking query, the - check will use the ACL token set on the service or check definition or otherwise - will fall back to the default ACL token set with the agent (`acl_token`). - -## Check Definition - -A script check: +The following service definition file snippet is an example +of a script check definition: @@ -162,7 +90,6 @@ check = { interval = "10s" timeout = "1s" } - ``` ```json @@ -179,7 +106,47 @@ check = { -A HTTP check: +#### 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](/api-docs/agent/service#by-name-json). + +### HTTP check ((#http-interval)) + +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: @@ -220,7 +187,23 @@ check = { -A TCP check: +### TCP check ((#tcp-interval)) + +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: @@ -232,7 +215,6 @@ check = { interval = "10s" timeout = "1s" } - ``` ```json @@ -249,7 +231,21 @@ check = { -A UDP check: +### UDP check ((#udp-interval)) + +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: @@ -261,7 +257,6 @@ check = { interval = "10s" timeout = "1s" } - ``` ```json @@ -278,7 +273,32 @@ check = { -A TTL check: +### Time to live (TTL) check ((#ttl)) + +TTL checks retain their last known state for the specified `ttl` duration. +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](/api-docs/agent/check#ttl-check-pass), +[warn](/api-docs/agent/check#ttl-check-warn), [fail](/api-docs/agent/check#ttl-check-fail), +and [update](/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](/commands/maint) or +[HTTP API endpoint](/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: @@ -304,7 +324,24 @@ check = { -A Docker check: +### Docker check ((#docker-interval)) + +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. +The agent must be configured with [`enable_script_checks`](/docs/agent/config/cli-flags#_enable_script_checks) +set to `true` in order to enable Docker health checks. + +The following service definition file snippet is an example +of a Docker check definition: @@ -334,7 +371,26 @@ check = { -A gRPC check for the whole application: +### gRPC check ((##grpc-interval)) + +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 in the following format `/:service_identifier`. + +The following service definition file snippet is an example +of a gRPC check for a whole application: @@ -362,7 +418,8 @@ check = { -A gRPC check for the specific `my_service` service: +The following service definition file snippet is an example +of a gRPC check for the specific `my_service` service @@ -390,7 +447,23 @@ check = { -A h2ping check: +### H2ping check ((#h2ping-interval)) + +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: @@ -418,7 +491,29 @@ check = { -An alias check for a local service: +### 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`](/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: @@ -440,72 +535,137 @@ check = { -~> 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. +## Check definition -Each type of definition must include a `name` and may optionally provide an -`id` and `notes` field. The `id` must be unique per _agent_ otherwise only the -last defined check with that `id` will be registered. If the `id` is not set -and the check is embedded within a service definition a unique check id is -generated. Otherwise, `id` will be set to `name`. If names might conflict, -unique IDs should be provided. +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](/api-docs/agent/check#json-request-body-schema). -The `notes` field is opaque to Consul but can be used to provide a human-readable -description of the current state of the check. Similarly, an external process -updating a TTL check via the HTTP interface can set the `notes` value. +-> **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. -Checks may also contain a `token` field to provide an ACL token. This token is -used for any interaction with the catalog for the check, including -[anti-entropy syncs](/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. +#### General options -Script, TCP, UDP, HTTP, Docker, and gRPC checks must include an `interval` field. This -field is parsed by Go's `time` package, and has the following -[formatting specification](https://golang.org/pkg/time/#ParseDuration): +- `name` `(string: )` - Specifies the name of the check. -> 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". +- `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. -In Consul 0.7 and later, checks that are associated with a service may also contain -an optional `deregister_critical_service_after` field, which is a timeout in the -same Go time format as `interval` and `ttl`. If a check is in the critical state -for more than this configured value, then its associated service (and all of its -associated checks) will automatically be deregistered. 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 should generally be configured with a timeout that's much, much longer than -any expected recoverable outage for the given service. +- `interval` `(string: )` - Specifies + the frequency at which to run this check. + Required for all check types except TTL and alias checks. -To configure a check, either provide it as a `-config-file` option to the -agent or place it inside the `-config-dir` of the agent. The file must -end in a ".json" or ".hcl" extension to be loaded by Consul. Check definitions -can also be updated by sending a `SIGHUP` to the agent. Alternatively, the -check can be registered dynamically using the [HTTP API](/api). + The value is parsed by Go's `time` package, and has the following + [formatting specification](https://golang.org/pkg/time/#ParseDuration): -## Check Scripts + > 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". -A check script is generally free to do anything to determine the status -of the check. The only limitations placed are that the exit codes must obey -this convention: +- `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. -- Exit code 0 - Check is passing -- Exit code 1 - Check is warning -- Any other code - Check is failing +- `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. -This is the only convention that Consul depends on. Any output of the script -will be captured and stored in the `output` field. +- `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. -In Consul 0.9.0 and later, the agent must be configured with -[`enable_script_checks`](/docs/agent/config/cli-flags#_enable_script_checks) set to `true` -in order to enable script checks. + 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. -## Initial Health Check Status +- `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](/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 @@ -576,13 +736,13 @@ 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 +## Agent certificates for TLS checks The [enable_agent_tls_for_checks](/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 Multiple check definitions can be defined using the `checks` (plural) key in your configuration file. @@ -640,58 +800,3 @@ checks = [ ``` - -## 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 passing/critical. -The status will 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 HTTP, TCP, gRPC, Docker & Monitor checks. -By default, both passing and critical thresholds will be set to 0 so the check -status will always reflect 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 - } - ] -} -``` - - diff --git a/website/content/docs/discovery/dns.mdx b/website/content/docs/discovery/dns.mdx index 471b9f9a20..b50e8deeeb 100644 --- a/website/content/docs/discovery/dns.mdx +++ b/website/content/docs/discovery/dns.mdx @@ -52,7 +52,7 @@ 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[.datacenter]. +.node[.]. ``` For example, if we have a `foo` node with default settings, we could @@ -79,16 +79,16 @@ $ dig @127.0.0.1 -p 8600 foo.node.consul ANY ;; WARNING: recursion requested but not available ;; QUESTION SECTION: -;foo.node.consul. IN ANY +;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" +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 +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 @@ -121,7 +121,7 @@ it is recommended to use the HTTP API to retrieve the list of nodes. The format of a standard service lookup is: ```text -[tag.].service[.datacenter]. +[.].service[.]. ``` The `tag` is optional, and, as with node lookups, the `datacenter` is as @@ -157,26 +157,37 @@ $ dig @127.0.0.1 -p 8600 consul.service.consul SRV ;; WARNING: recursion requested but not available ;; QUESTION SECTION: -;consul.service.consul. IN SRV +;consul.service.consul. IN SRV ;; ANSWER SECTION: -consul.service.consul. 0 IN SRV 1 1 8300 foobar.node.dc1.consul. +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 +foobar.node.dc1.consul. 0 IN A 10.1.10.12 ``` ### RFC 2782 Lookup -The format for RFC 2782 SRV lookups is: +Valid formats for RFC 2782 SRV lookups depend on +whether you want to filter results based on a service tag: - _._[.service][.datacenter][.domain] +- No filtering on service tag -Per [RFC 2782](https://tools.ietf.org/html/rfc2782), SRV queries should use -underscores, `_`, as a prefix to the `service` and `protocol` values in a query to -prevent DNS collisions. The `protocol` value can be any of the tags for a -service. If the service has no tags, `tcp` should be used. If `tcp` -is specified as the protocol, the query will not perform any tag filtering. + ```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. @@ -196,13 +207,13 @@ $ dig @127.0.0.1 -p 8600 _rabbitmq._amqp.service.consul SRV ;; WARNING: recursion requested but not available ;; QUESTION SECTION: -;_rabbitmq._amqp.service.consul. IN SRV +;_rabbitmq._amqp.service.consul. IN SRV ;; ANSWER SECTION: -_rabbitmq._amqp.service.consul. 0 IN SRV 1 1 5672 rabbitmq.node1.dc1.consul. +_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 +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. @@ -328,7 +339,7 @@ $ echo -n "20010db800010002cafe000000001337" | perl -ne 'printf join(":", unpack The format of a prepared query lookup is: ```text -.query[.datacenter]. +.query[.]. ``` The `datacenter` is optional, and if not provided, the datacenter of this Consul @@ -376,7 +387,7 @@ If you need more complex behavior, please use the To find the unique virtual IP allocated for a service: ```text -.virtual[.peer]. +.virtual[.]. ``` This will return the unique virtual IP for any [Connect-capable](/docs/connect) @@ -439,14 +450,14 @@ The following responses are returned: ``` ;; QUESTION SECTION: -;consul.service.test-domain. IN SRV +;consul.service.test-domain. IN SRV ;; ANSWER SECTION: -consul.service.test-domain. 0 IN SRV 1 1 8300 machine.node.dc1.test-domain. +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=" +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 @@ -479,7 +490,7 @@ resolve services within the `default` namespace and partition. However, for reso services from other namespaces or partitions the following form can be used: ```text -[tag.].service..ns..ap..dc. +[.].service..ns..ap..dc. ``` This sequence is the canonical naming convention of a Consul Enterprise service. At least two of the following @@ -488,18 +499,17 @@ fields must be present: * `partition` * `datacenter` +For imported lookups, only the namespace and peer need to be specified as the partition can be inferred from the peering: + +```text +.virtual[.].. +``` + For node lookups, only the partition and datacenter need to be specified as nodes cannot be namespaced. ```text -[tag.].service..ap..dc. -``` - -For node lookups, only the partition and datacenter need to be specified (nodes cannot be -namespaced): - -```text -[tag.].service..ap..dc. +[.].node..ap..dc. ``` ## DNS with ACLs diff --git a/website/content/docs/ecs/requirements.mdx b/website/content/docs/ecs/requirements.mdx index e7c0a8ea87..0fb0ab451c 100644 --- a/website/content/docs/ecs/requirements.mdx +++ b/website/content/docs/ecs/requirements.mdx @@ -10,6 +10,7 @@ description: >- The following requirements must be met in order to install Consul on ECS: * **Launch Type:** Fargate and EC2 launch types are supported. +* **Network Mode:** Only `awsvpc` mode is supported. * **Subnets:** ECS Tasks can run in private or public subnets. Tasks must have [network access](https://aws.amazon.com/premiumsupport/knowledge-center/ecs-pull-container-api-error-ecr/) to Amazon ECR or other public container registries to pull images. * **Consul Servers:** You can use your own Consul servers running on virtual machines or use [HashiCorp Cloud Platform Consul](https://www.hashicorp.com/cloud-platform) to host the servers for you. For development purposes or testing, you may use the `dev-server` [Terraform module](https://github.com/hashicorp/terraform-aws-consul-ecs/tree/main) that runs the Consul server as an ECS task. The `dev-server` does not support persistent storage. * **ACL Controller:** If you are running a secure Consul installation with ACLs enabled, configure the ACL controller. diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index e7c5bb2228..057e3464cf 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -99,12 +99,13 @@ Here are links to resources, documentation, examples and best practices to guide - [Consul Telemetry Documentation](/docs/agent/telemetry) - [Monitoring Consul with Datadog APM](https://www.datadoghq.com/blog/consul-datadog/) - [Monitoring Consul with Dynatrace APM](https://www.dynatrace.com/news/blog/automatic-intelligent-observability-into-your-hashicorp-consul-service-mesh/) +- [Monitoring Consul with New Relic APM](https://newrelic.com/instant-observability/consul/b65825cc-faee-47b5-8d7c-6d60d6ab3c59) +- [Monitoring HCP Consul with New Relic APM](https://newrelic.com/instant-observability/hcp-consul/bc99ad15-7aba-450e-8236-6ea667d50cae) **Logging** - [Monitor Consul with Logz.io](https://www.hashicorp.com/integrations/logz-io/consul) - [Monitor Consul with Splunk SignalFx](https://www.hashicorp.com/integrations/splunksignalfx/consul) -- [Consul Datacenter Monitoring with New Relic](https://www.hashicorp.com/integrations/new-relic/consul) #### Platform: diff --git a/website/content/docs/intro/usecases/what-is-a-service-mesh.mdx b/website/content/docs/intro/usecases/what-is-a-service-mesh.mdx index ecae8a8274..17fd93e269 100644 --- a/website/content/docs/intro/usecases/what-is-a-service-mesh.mdx +++ b/website/content/docs/intro/usecases/what-is-a-service-mesh.mdx @@ -1,12 +1,12 @@ --- layout: docs -page_title: What is a service mesh? +page_title: Service Mesh description: >- Learn what a service mesh is, its benefits, and how it works. A service mesh can solve many of the modern challenges that exist in multi-platform and multi-cloud application architectures, ranging from security to application resiliency. --- -# What is a Service Mesh? +# What is a service mesh? A _service mesh_ is a dedicated network layer that provides secure service-to-service communication within and across infrastructure, including on-premises and cloud environments. Service meshes are often used with a microservice architectural pattern, but can provide value in any scenario where complex networking is involved. diff --git a/website/content/docs/intro/usecases/what-is-service-discovery.mdx b/website/content/docs/intro/usecases/what-is-service-discovery.mdx new file mode 100644 index 0000000000..820e5b7681 --- /dev/null +++ b/website/content/docs/intro/usecases/what-is-service-discovery.mdx @@ -0,0 +1,96 @@ +--- +layout: docs +page_title: Service Discovery +description: >- + Learn what service discovery is, its benefits, and how it works. + Service mesh can solve many of the modern challenges that exist in multi-platform and multi-cloud application architectures, ranging from security to application resiliency. +--- + +# What is service discovery? + +_Service discovery_ helps you discover, track, and monitor the health of services within a network. Service discovery registers and maintains a record of all your services in a _service catalog_. This service catalog acts as a single source of truth that allows your services to query and communicate with each other. + +## Benefits of service discovery + +Service discovery provides benefits for all organizations, ranging from simplified scalability to improved application resiliency. Some of the benefits of service discovery include: + +- Dynamic IP address and port discovery +- Simplified horizontal service scaling +- Abstracts discovery logic away from applications +- Reliable service communication ensured by health checks +- Load balances requests across healthy service instances +- Faster deployment times achieved by high-speed discovery +- Automated service registration and de-registration + +## How does a service mesh work? + +Service discovery uses a service's identity instead of traditional access information (IP address and port). This allows you to dynamically map services and track any changes within a service catalog. Service consumers (users or other services) then use DNS to dynamically retrieve other service’s access information from the service catalog. The lifecycle of a service may look like the following: + +A service consumer communicates with the “Web” service via a unique Consul DNS entry provided by the service catalog. + +![Example diagram of how service consumers query for services](/img/what_is_service_discovery_1.png) + +A new instance of the “Web” service registers itself to the service catalog with its IP address and port. As new instances of your services are registered to the service catalog, they will participate in the load balancing pool for handling service consumer requests. + +![Example diagram of how a service is registered to the service catalog](/img/what_is_service_discovery_2.png) + +The service catalog is dynamically updated as new instances of the service are added and legacy or unhealthy service instances are removed. Removed services will no longer participate in the load balancing pool for handling service consumer requests. + +![Example diagram of how unhealthy services are removed from the service catalog](/img/what_is_service_discovery_3.png) + +## What is service discovery in microservices? + +In a microservices application, the set of active service instances changes frequently across a large, dynamic environment. These service instances rely on a service catalog to retrieve the most up-to-date access information from the respective services. A reliable service catalog is especially important for service discovery in microservices to ensure healthy, scalable, and highly responsive application operation. + +## What are the two main types of service discovery? + +There are two main service‑discovery patterns: _client-side_ discovery and _server-side_ discovery. + +In systems that use client‑side discovery, the service consumer is responsible for determining the access information of available service instances and load balancing requests between them. + +1. The service consumer queries the service catalog +2. The service catalog retrieves and returns all access information +3. The service consumer selects a healthy downstream service and makes requests directly to it + +![Example diagram of client-side discovery concept](/img/what_is_service_discovery_4.png) + +In systems that use server‑side discovery, the service consumer uses an intermediary to query the service catalog and make requests to them. + +1. The service consumer queries an intermediary (Consul) +2. The intermediary queries the service catalog and routes requests to the available service instances. + +![Example diagram of server-side discovery concept](/img/what_is_service_discovery_5.png) + +For modern applications, this discovery method is advantageous because developers can make their applications faster and more lightweight by decoupling and centralizing service discovery logic. + +## Service discovery vs load balancing + +Service discovery and load balancing share a similarity in distributing requests to back end services, but differ in many important ways. + +Traditional load balancers are not designed for rapid registration and de-registration of services, nor are they designed for high-availability. By contrast, service discovery systems use multiple nodes that maintain the service registry state and a peer-to-peer state management system for increased resilience across any type of infrastructure. + +For modern, cloud-based applications, service discovery is the preferred method for directing traffic to the right service provider due to its ability to scale and remain resilient, independent of infrastructure. + +## How do you implement service discovery? + +You can implement service discovery systems across any type of infrastructure, whether it is on-premise or in the cloud. Service discovery is a native feature of many container orchestrators such as Kubernetes or Nomad. There are also platform-agnostic service discovery methods available for non-container workloads such as VMs and serverless technologies. Implementing a resilient service discovery system involves creating a set of servers that maintain and facilitate service registry operations. You can achieve this by installing a service discovery system or using a managed service discovery service. + +## What is Consul? + +Consul is a service networking solution that lets you automate network configurations, discover services, and enable secure connectivity across any cloud or runtime. With these features, Consul helps you solve the complex networking and security challenges of operating microservices and cloud infrastructure (multi-cloud and hybrid cloud). You can use these features independently or together to achieve [zero trust](https://www.hashicorp.com/solutions/zero-trust-security) security. + +Consul’s service discovery capabilities help you discover, track, and monitor the health of services within a network. Consul acts as a single source of truth that allows your services to query and communicate with each other. + +You can use Consul with virtual machines (VMs), containers, serverless technologies, or with container orchestration platforms, such as [Nomad](https://www.nomadproject.io/) and Kubernetes. Consul is platform agnostic which makes it a great fit for all environments, including legacy platforms. + +Consul is available as a [self-managed](/downloads) project or as a fully managed service mesh solution ([HCP Consul](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs)). HCP Consul enables users to discover and securely connect services without the added operational burden of maintaining a service mesh on their own. + +## Next steps + +Get started with service discovery today by leveraging Consul on HCP, Consul on Kubernetes, or Consul on VMs. Prepare your organization for the future of multi-cloud and embrace a [zero-trust](https://www.hashicorp.com/solutions/zero-trust-security) architecture. + +Feel free to get started with Consul by exploring one of these Consul Learn tutorials: + +[Getting Started with Consul on VMs](https://learn.hashicorp.com/collections/consul/getting-started) +[Getting Started with Consul on HCP](https://learn.hashicorp.com/collections/consul/cloud-get-started) +[Getting Started with Consul on Kubernetes](https://learn.hashicorp.com/collections/consul/gs-consul-service-mesh) \ No newline at end of file diff --git a/website/content/docs/k8s/compatibility.mdx b/website/content/docs/k8s/compatibility.mdx index 2d651401d0..f5e362cb46 100644 --- a/website/content/docs/k8s/compatibility.mdx +++ b/website/content/docs/k8s/compatibility.mdx @@ -10,27 +10,13 @@ For every release of Consul on Kubernetes, a Helm chart, `consul-k8s-control-pla ## Supported Consul versions -### Version 0.33.0 and above - -Starting with Consul Kubernetes 0.33.0, Consul Kubernetes versions all of its components (`consul-k8s` CLI, `consul-k8s-control-plane`, and Helm chart) with a single semantic version. +Consul Kubernetes versions all of its components (`consul-k8s` CLI, `consul-k8s-control-plane`, and Helm chart) with a single semantic version. When installing or upgrading to a specific versions, ensure that you are using the correct Consul version with the compatible `consul-k8s` helm chart and/or CLI. | Consul Version | Compatible consul-k8s Versions | | -------------- | -------------------------------- | +| 1.13.x | 0.47.0 - latest | | 1.12.x | 0.43.0 - latest | | 1.11.x | 0.39.0 - 0.42.0, 0.44.0 - latest | -| 1.10.x | 0.33.0 - 0.38.0 | - -### Prior to version 0.33.0 - -Prior to Consul Kubernetes 0.33.0, a separately versioned Consul Helm chart was distributed to deploy the Consul on Kubernetes binary. The default version of the `consul-k8s` binary specified by the Helm chart should be used to ensure proper compatibility, since the Helm chart is designed and tested with the default `consul-k8s` version. To find the default version for the appropriate Helm chart version, navigate to the corresponding tag (i.e. 0.32.1) in [`values.yaml`](https://github.com/hashicorp/consul-helm/blob/v0.32.1/values.yaml) and retrieve the `imageK8S` global value. - -| Consul Version | Compatible Consul Helm Versions (default `consul-k8s` image) | -| -------------- | -----------------------------------------------------------| -| 1.10.x | 0.32.0 (consul-k8s:0.26.0) - 0.32.1 (consul-k8s:0.26.0) | -| 1.9.x | 0.27.0 (consul-k8s:0.21.0) - 0.31.1 (consul-k8s:0.25.0) | -| 1.8.x | 0.22.0 (consul-k8s:0.16.0) - 0.26.0 (consul-k8s:0.20.0) | -| 1.7.x | 0.17.0 (consul-k8s:0.12.0) - 0.21.0 (consul-k8s:0.15.0) | -| 1.6.x | 0.10.0 (consul-k8s:0.9.2) - 0.16.2 (consul-k8s:0.11.0) | ## Supported Envoy versions diff --git a/website/content/docs/k8s/connect/terminating-gateways.mdx b/website/content/docs/k8s/connect/terminating-gateways.mdx index e8c5ec845f..06316f5f51 100644 --- a/website/content/docs/k8s/connect/terminating-gateways.mdx +++ b/website/content/docs/k8s/connect/terminating-gateways.mdx @@ -6,10 +6,6 @@ description: Configuring Terminating Gateways on Kubernetes # Terminating Gateways on Kubernetes --> 1.9.0+: This feature is available in Consul versions 1.9.0 and higher - -~> This topic requires familiarity with [Terminating Gateways](/docs/connect/gateways/terminating-gateway). - Adding a terminating gateway is a multi-step process: - Update the Helm chart with terminating gateway config options @@ -17,7 +13,13 @@ Adding a terminating gateway is a multi-step process: - Access the Consul agent - Register external services with Consul -## Update the helm chart with terminating gateway config options +## Requirements + +- [Consul](https://www.consul.io/docs/install#install-consul) +- [Consul on Kubernetes CLI](/docs/k8s/k8s-cli) +- Familiarity with [Terminating Gateways](/docs/connect/gateways/terminating-gateway) + +## Update the Helm chart with terminating gateway config options Minimum required Helm options: @@ -38,37 +40,41 @@ terminatingGateways: ## Deploying the Helm chart -Ensure you have the latest consul-helm chart and install Consul via helm using the following -[guide](/docs/k8s/installation/install#installing-consul) while being sure to provide the yaml configuration -as previously discussed. +The Helm chart may be deployed using the [Consul on Kubernetes CLI](/docs/k8s/k8s-cli). + +```shell-session +$ consul-k8s install -f config.yaml +``` ## Accessing the Consul agent -You can access the Consul server directly from your host via `kubectl port-forward`. This is helpful for interacting with your Consul UI locally as well as to validate connectivity of the application. +You can access the Consul server directly from your host via `kubectl port-forward`. This is helpful for interacting with your Consul UI locally as well as for validating the connectivity of the application. + + + ```shell-session $ kubectl port-forward consul-server-0 8500 & ``` +```shell-session +$ export CONSUL_HTTP_ADDR=http://localhost:8500 +``` + + + If TLS is enabled use port 8501: ```shell-session $ kubectl port-forward consul-server-0 8501 & ``` --> Be sure the latest consul binary is installed locally on your host. -[https://releases.hashicorp.com/consul/](https://releases.hashicorp.com/consul/) - -```shell-session -$ export CONSUL_HTTP_ADDR=http://localhost:8500 -``` - -If TLS is enabled set: - ```shell-session $ export CONSUL_HTTP_ADDR=https://localhost:8501 $ export CONSUL_HTTP_SSL_VERIFY=false ``` + + If ACLs are enabled also set: @@ -88,7 +94,63 @@ Registering the external services with Consul is a multi-step process: ### Register external services with Consul --> **Note:** Normal Consul services are registered with the Consul client on the node that +You may register an external service with Consul using `ServiceDefaults` if +[`TransparentProxy`](/docs/connect/transparent-proxy) is enabled. Otherwise, +you may register the service as a node in the Consul catalog. + + + + +The [`destination`](/docs/connect/config-entries/service-defaults#terminating-gateway-destination) field of the `ServiceDefaults` Custom Resource Definition (CRD) allows clients to dial an external service directly. For this method to work, [`TransparentProxy`](/docs/connect/transparent-proxy) must be enabled. +The following table describes traffic behaviors when using the `destination` field to route traffic through a terminating gateway: + +| External Services Layer | Client dials | Client uses TLS | Allowed | Notes | +|--------------------------------------|---------------------------|------------------------------|--------------------------|-----------------------------------------------------------------------------------------------| +| L4 | Hostname | Yes | Allowed | `CAFiles` are not allowed because traffic is already end-to-end encrypted by the client. | +| L4 | IP | Yes | Allowed | `CAFiles` are not allowed because traffic is already end-to-end encrypted by the client. | +| L4 | Hostname | No | Not allowed | The sidecar is not protocol aware and can not identify traffic going to the external service. | +| L4 | IP | No | Allowed | There are no limitations on dialing IPs without TLS. | +| L7 | Hostname | Yes | Not allowed | Because traffic is already encrypted before the sidecar, it cannot route as L7 traffic. | +| L7 | IP | Yes | Not allowed | Because traffic is already encrypted before the sidecar, it cannot route as L7 traffic. | +| L7 | Hostname | No | Allowed | A `Host` or `:authority` header is required. | +| L7 | IP | No | Allowed | There are no limitations on dialing IPs without TLS. | + +You can provide a `caFile` to secure traffic between unencrypted clients that connect to external services through the terminating gateway. +Refer to [Create the configuration entry for the terminating gateway](#create-the-configuration-entry-for-the-terminating-gateway) for details. + +-> **Note:** Regardless of the `protocol` specified in the `ServiceDefaults`, [L7 intentions](/docs/connect/config-entries/service-intentions#permissions) are not currently supported with `ServiceDefaults` destinations. + +Create a `ServiceDefaults` custom resource for the external service: + + + +```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceDefaults + metadata: + name: example-https + spec: + protocol: tcp + destination: + addresses: + - "example.com" + port: 443 +``` + + + +Apply the `ServiceDefaults` resource with `kubectl apply`: + +```shell-session +$ kubectl apply --filename service-defaults.yaml +``` + +All other terminating gateway operations can use the name of the `ServiceDefaults` component, in this case "example-https", as a Consul service name. + + + + +Normally, Consul services are registered with the Consul client on the node that they're running on. Since this is an external service, there is no Consul node to register it onto. Instead, we will make up a node name and register the service to that node. @@ -137,14 +199,15 @@ If ACLs and TLS are enabled : $ curl --request PUT --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" --data @external.json --insecure $CONSUL_HTTP_ADDR/v1/catalog/register true ``` + + ### Update terminating gateway ACL role if ACLs are enabled If ACLs are enabled, update the terminating gateway acl role to have `service: write` permissions on all of the services -being represented by the gateway: +being represented by the gateway. -- Create a new policy that includes these permissions -- Update the existing role to include the new policy +Create a new policy that includes the write permission for the service you created. @@ -168,7 +231,7 @@ service "example-https" { } ``` -Now fetch the ID of the terminating gateway token +Fetch the ID of the terminating gateway token. ```shell-session consul acl role list | grep -B 6 -- "- RELEASE_NAME-terminating-gateway-policy" | grep ID @@ -176,7 +239,7 @@ consul acl role list | grep -B 6 -- "- RELEASE_NAME-terminating-gateway-policy" ID: ``` -Update the terminating gateway acl token with the new policy +Update the terminating gateway ACL token with the new policy. ```shell-session $ consul acl role update -id -policy-name example-https-write-policy @@ -205,16 +268,15 @@ metadata: spec: services: - name: example-https - caFile: /etc/ssl/certs/ca-certificates.crt ``` -If TLS is enabled, you must include the `caFile` parameter that points to the system trust store of the terminating gateway container. By default, the trust store is located in the `/etc/ssl/certs/ca-certificates.crt` directory. - -Configure the `caFile` parameter to point to the `/etc/ssl/cert.pem` directory if TLS is enabled and you are using one of the following components: - * Consul Helm chart 0.43 or older - * Or an Envoy image with an alpine base image +If TLS is enabled for external services registered through the Consul catalog and you are not using [transparent proxy `destination`](#register-an-external-service-as-a-destination), you must include the [`caFile`](/docs/connect/config-entries/terminating-gateway#cafile) parameter that points to the system trust store of the terminating gateway container. +By default, the trust store is located in the `/etc/ssl/certs/ca-certificates.crt` directory. +Configure the [`caFile`](https://www.consul.io/docs/connect/config-entries/terminating-gateway#cafile) parameter in the `TerminatingGateway` config entry to point to the `/etc/ssl/cert.pem` directory if TLS is enabled and you are using one of the following components: +- Consul Helm chart 0.43 or older +- An Envoy image with an alpine base image Apply the `TerminatingGateway` resource with `kubectl apply`: @@ -222,7 +284,7 @@ Apply the `TerminatingGateway` resource with `kubectl apply`: $ kubectl apply --filename terminating-gateway.yaml ``` -If using ACLs and TLS, create a [`ServiceIntentions`](/docs/connect/config-entries/service-intentions) resource to allow access from services in the mesh to the external service +If using ACLs and TLS, create a [`ServiceIntentions`](/docs/connect/config-entries/service-intentions) resource to allow access from services in the mesh to the external service: @@ -241,6 +303,8 @@ spec: +-> **NOTE**: [L7 Intentions](/docs/connect/config-entries/service-intentions#permissions) are not currently supported for `ServiceDefaults` destinations. + Apply the `ServiceIntentions` resource with `kubectl apply`: ```shell-session @@ -249,7 +313,7 @@ $ kubectl apply --filename service-intentions.yaml ### Define the external services as upstreams for services in the mesh -Finally define and deploy the external services as upstreams for the internal mesh services that wish to talk to them. +As a final step, you may define and deploy the external services as upstreams for the internal mesh services that wish to talk to them. An example deployment is provided which will serve as a static client for the terminating gateway service. @@ -298,21 +362,35 @@ spec: -Run the service via `kubectl apply`: +Deploy the service with `kubectl apply`. ```shell-session $ kubectl apply --filename static-client.yaml ``` -Wait for the service to be ready: +Wait for the service to be ready. ```shell-session $ kubectl rollout status deploy static-client --watch deployment "static-client" successfully rolled out ``` -You can verify connectivity of the static-client and terminating gateway via a curl command: +You can verify connectivity of the static-client and terminating gateway via a curl command. + + + + +```shell-session +$ kubectl exec deploy/static-client -- curl -vvvs https://example.com/ +``` + + + ```shell-session $ kubectl exec deploy/static-client -- curl -vvvs --header "Host: example-https.com" http://localhost:1234/ ``` + + + + diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index 14ffbb6ddb..be0c340800 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -66,6 +66,23 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-global-peering-enabled)) (`boolean: false`) - If true, the Helm chart enables Cluster Peering for the cluster. This option enables peering controllers and allows use of the PeeringAcceptor and PeeringDialer CRDs for establishing service mesh peerings. + - `tokenGeneration` ((#v-global-peering-tokengeneration)) + + - `serverAddresses` ((#v-global-peering-tokengeneration-serveraddresses)) + + - `source` ((#v-global-peering-tokengeneration-serveraddresses-source)) (`string: ""`) - Source can be set to "","consul" or "static". + + "" is the default source. If servers are enabled, it will check if `server.exposeService` is enabled, and read + the addresses from that service to use as the peering token server addresses. If using admin partitions and + only Consul client agents are enabled, the addresses in `externalServers.hosts` and `externalServers.grpcPort` + will be used. + + "consul" will use the Consul advertise addresses in the peering token. + + "static" will use the addresses specified in `global.peering.tokenGeneration.serverAddresses.static`. + + - `static` ((#v-global-peering-tokengeneration-serveraddresses-static)) (`array: []`) - Static addresses must be formatted "hostname|ip:port" where the port is the Consul server(s)' grpc port. + - `adminPartitions` ((#v-global-adminpartitions)) - Enabling `adminPartitions` allows creation of Admin Partitions in Kubernetes clusters. It additionally indicates that you are running Consul Enterprise v1.11+ with a valid Consul Enterprise license. Admin partitions enables deploying services across partitions, while sharing @@ -253,14 +270,14 @@ Use these links to navigate to a particular top-level stanza. - `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. - Please see https://www.consul.io/docs/connect/ca/vault#rootpkipath. + For more details, [Vault Connect CA configuration](https://www.consul.io/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. - Please see https://www.consul.io/docs/connect/ca/vault#intermediatepkipath. + For more details, [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#intermediatepkipath). - `additionalConfig` ((#v-global-secretsbackend-vault-connectca-additionalconfig)) (`string: {}`) - Additional Connect CA configuration in JSON format. - Please see https://www.consul.io/docs/connect/ca/vault#common-ca-config-options - for additional configuration options. + Please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#configuration) + for all configuration options available for that provider. Example: @@ -269,7 +286,8 @@ Use these links to navigate to a particular top-level stanza. { "connect": [{ "ca_config": [{ - "leaf_cert_ttl": "36h" + "leaf_cert_ttl": "36h", + "namespace": "my-vault-ns" }] }] } @@ -846,6 +864,33 @@ Use these links to navigate to a particular top-level stanza. "sample/annotation2": "bar" ``` + - `exposeService` ((#v-server-exposeservice)) - Configures a service to expose ports on the Consul servers over a Kubernetes Service. + + - `enabled` ((#v-server-exposeservice-enabled)) (`boolean: -`) - When enabled, deploys a Kubernetes Service to reach the Consul servers. + + - `type` ((#v-server-exposeservice-type)) (`string: LoadBalancer`) - Type of service, supports LoadBalancer or NodePort. + + - `nodePort` ((#v-server-exposeservice-nodeport)) - If service is of type NodePort, configures the nodePorts. + + - `http` ((#v-server-exposeservice-nodeport-http)) (`integer: null`) - Configures the nodePort to expose the Consul server http port. + + - `https` ((#v-server-exposeservice-nodeport-https)) (`integer: null`) - Configures the nodePort to expose the Consul server https port. + + - `serf` ((#v-server-exposeservice-nodeport-serf)) (`integer: null`) - Configures the nodePort to expose the Consul server serf port. + + - `rpc` ((#v-server-exposeservice-nodeport-rpc)) (`integer: null`) - Configures the nodePort to expose the Consul server rpc port. + + - `grpc` ((#v-server-exposeservice-nodeport-grpc)) (`integer: null`) - Configures the nodePort to expose the Consul server grpc port. + + - `annotations` ((#v-server-exposeservice-annotations)) (`string: null`) - This value defines additional annotations for + server pods. This should be formatted as a multi-line string. + + ```yaml + annotations: | + "sample/annotation1": "foo" + "sample/annotation2": "bar" + ``` + - `service` ((#v-server-service)) - Server service properties. - `annotations` ((#v-server-service-annotations)) (`string: null`) - Annotations to apply to the server service. @@ -881,6 +926,8 @@ Use these links to navigate to a particular top-level stanza. - `httpsPort` ((#v-externalservers-httpsport)) (`integer: 8501`) - The HTTPS port of the Consul servers. + - `grpcPort` ((#v-externalservers-grpcport)) (`integer: 8503`) - The GRPC port of the Consul servers. + - `tlsServerName` ((#v-externalservers-tlsservername)) (`string: null`) - The server name to use as the SNI host header when connecting with HTTPS. - `useSystemRoots` ((#v-externalservers-usesystemroots)) (`boolean: false`) - If true, consul-k8s-control-plane components will ignore the CA set in diff --git a/website/content/docs/k8s/k8s-cli.mdx b/website/content/docs/k8s/k8s-cli.mdx index f3757748b8..8f6467cbb0 100644 --- a/website/content/docs/k8s/k8s-cli.mdx +++ b/website/content/docs/k8s/k8s-cli.mdx @@ -2,40 +2,44 @@ layout: docs page_title: Consul K8s CLI Reference description: >- - Consul K8s CLI is a tool for quickly installing and interacting with Consul on Kubernetes. + The Consul on Kubernetes CLI (consul-k8s) is a tool for installing and managing Consul on Kubernetes. --- -# Consul K8s CLI Reference +# Consul on Kubernetes CLI Reference -Consul K8s CLI is a tool for quickly installing and interacting with Consul on Kubernetes. -The Consul K8s CLI allows you to manage the lifecycle of Consul without requiring the usage of `Helm`, [Consul CLI](/commands/index), and `kubectl`. -The Consul K8s CLI offers a Kubernetes native experience for managing Consul. +The Consul on Kubernetes CLI, `consul-k8s`, is a tool for managing Consul +that does not require direct interaction with Helm, the [Consul CLI](/commands/index), +or `kubectl`. --> **Note**: For guidance on how to install the Consul K8s CLI, visit the [Installing the Consul K8s CLI](/docs/k8s/installation/install-cli) documentation. +For guidance on how to install `consul-k8s`, refer to the +[Installing the Consul K8s CLI](/docs/k8s/installation/install-cli) documentation. -This topic describes the subcommands and available options for using Consul K8s CLI. +This topic describes the commands and available options for using `consul-k8s`. ## Usage -Consul K8s CLI uses the following syntax: +The Consul on Kubernetes CLI uses the following syntax: ```shell-session -$ consul-k8s +$ consul-k8s ``` -## Subcommands +## Commands -You can use the following subcommands with `consul-k8s`. +You can use the following commands with `consul-k8s`. - - [install](#install) - - [uninstall](#uninstall) - - [status](#status) - - [upgrade](#upgrade) BETA - - [version](#version) + - [`install`](#install): Install Consul on Kubernetes. + - [`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. + - [`status`](#status): Check the status of a Consul installation on Kubernetes. + - [`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. ### `install` -The `install` command installs Consul on Kubernetes. +The `install` command installs Consul on your Kubernetes cluster. ```shell-session $ consul-k8s install @@ -43,84 +47,409 @@ $ consul-k8s install The following options are available. -| Flag | Description | Default | Required | -| --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------- | -| `-auto-approve`                                     | Boolean value that enables you to skip the installation confirmation prompt. | `false` | Optional | -| `-dry-run` | Boolean value that validates the installation and returns a summary. | `false` | Optional | -| `-config-file` | String value that specifies the path to a file containing custom installation configurations, e.g., Consul Helm chart values file.
You can use the `-config-file` flag multiple times to specify multiple files. | none | Optional | -| `-namespace` | String value that specifies the namespace of the Consul installation. | `consul` | Optional | -| `-preset` | String value that installs Consul based on a preset configuration. You can specify the following values:
`demo`: Installs a single replica server with sidecar injection enabled; useful for testing service mesh functionality.
`secure`: Installs a single replica server with sidecar injection, ACLs, and TLS enabled; useful for testing service mesh functionality. | Configuration of the Consul Helm chart. | Optional | -| `-set` | String value that enables you to set a customizable value. This flag is comparable to the `helm install --set` flag.
You can use the `-set` flag multiple times to set multiple values.
Consul Helm chart values are supported. | none | Optional | -| `-set-file` | String value that specifies the name of an arbitrary config file. This flag is comparable to the `helm install --set-file`
flag. The contents of the file will be used to set a customizable value. You can use the `-set-file` flag multiple times to specify multiple files.
Consul Helm chart values are supported. | none | Optional | -| `-set-string` | String value that enables you to set a customizable string value. This flag is comparable to the `helm install --set-string`
flag. You can use the `-set-string` flag multiple times to specify multiple strings.
Consul Helm chart values are supported. | none | Optional | -| `-timeout` | Specifies how long to wait for the installation process to complete before timing out. The value is specified with an integer and string value indicating a unit of time.
The following units are supported:
`ms` (milliseconds)
`s` (seconds)
`m` (minutes)
In the following example, installation will timeout after one minute:
`consul-k8s install -timeout 1m` | `10m` | Optional | -| `-wait` | Boolean value that determines if Consul should wait for resources in the installation to be ready before exiting the command. | `true` | Optional | -| `-verbose`, `-v` | Boolean value that specifies whether to output verbose logs from the install command with the status of resources being installed. | `false` | Optional | -| `--help` | Prints usage information for this option. | none | Optional | +| Flag | Description | Default | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------| +| `-auto-approve` | Boolean value that enables you to skip the installation confirmation prompt. | `false` | +| `-dry-run` | Boolean value that validates the installation and returns a summary. | `false` | +| `-config-file` | String value that specifies the path to a file containing custom installation configurations, e.g., Consul Helm chart values file.
You can use the `-config-file` flag multiple times to specify multiple files. | none | +| `-namespace` | String value that specifies the namespace of the Consul installation. | `consul` | +| `-preset` | String value that installs Consul based on a preset configuration. You can specify the following values:
`demo`: Installs a single replica server with sidecar injection enabled; useful for testing service mesh functionality.
`secure`: Installs a single replica server with sidecar injection, ACLs, and TLS enabled; useful for testing service mesh functionality. | Configuration of the Consul Helm chart. | +| `-set` | String value that enables you to set a customizable value. This flag is comparable to the `helm install --set` flag.
You can use the `-set` flag multiple times to set multiple values.
Consul Helm chart values are supported. | none | +| `-set-file` | String value that specifies the name of an arbitrary config file. This flag is comparable to the `helm install --set-file`
flag. The contents of the file will be used to set a customizable value. You can use the `-set-file` flag multiple times to specify multiple files.
Consul Helm chart values are supported. | none | +| `-set-string` | String value that enables you to set a customizable string value. This flag is comparable to the `helm install --set-string`
flag. You can use the `-set-string` flag multiple times to specify multiple strings.
Consul Helm chart values are supported. | none | +| `-timeout` | Specifies how long to wait for the installation process to complete before timing out. The value is specified with an integer and string value indicating a unit of time.
The following units are supported:
`ms` (milliseconds)
`s` (seconds)
`m` (minutes)
In the following example, installation will timeout after one minute:
`consul-k8s install -timeout 1m` | `10m` | +| `-wait` | Boolean value that determines if Consul should wait for resources in the installation to be ready before exiting the command. | `true` | +| `-verbose`, `-v` | Boolean value that specifies whether to output verbose logs from the install command with the status of resources being installed. | `false` | +| `-help`, `-h` | Prints usage information for this option. | none | See [Global Options](#global-options) for additional commands that you can use when installing Consul on Kubernetes. #### Example Commands -The following example command installs Consul according in the `myNS` namespace according to the `secure` preset. +The following example command installs Consul in the `myNS` namespace according to the `secure` preset. ```shell-session $ consul-k8s install -preset=secure -namespace=myNS ``` -The following example commands install Consul on Kubernetes using custom values, files, or strings that are set via flags. The underlying Consul-on-Kubernetes Helm chart uses the flags to customize the installation. The flags are comparable to the `helm install` [flags](https://helm.sh/docs/helm/helm_install/#helm-install). +The following example commands install Consul on Kubernetes using custom values, files, or strings that are set via flags. The underlying Consul-on-Kubernetes Helm chart uses the flags to customize the installation. The flags are comparable to the `helm install` [flags](https://helm.sh/docs/helm/helm_install/#helm-install). ```shell-session - $ consul-k8s install -set key=value +$ consul-k8s install -set key=value ``` ```shell-session - $ consul-k8s install -set key1=value1 -set key2=value2 +$ consul-k8s install -set key1=value1 -set key2=value2 ``` ```shell-session - $ consul-k8s install -set-file config1=value1.conf +$ consul-k8s install -set-file config1=value1.conf ``` ```shell-session - $ consul-k8s install -set-file config1=value1.conf -set-file config2=value2.conf +$ consul-k8s install -set-file config1=value1.conf -set-file config2=value2.conf ``` ```shell-session - $ consul-k8s install -set-string key=value-bool +$ consul-k8s install -set-string key=value-bool ``` -### `uninstall` +### `proxy` -The `uninstall` command removes Consul from Kubernetes. +The `proxy` command exposes two subcommands for interacting with proxies managed by +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 list` ```shell-session -$ consul-k8s uninstall +$ consul-k8s proxy list ``` +| Flag | Description | Default | +| ------------------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `-all-namespaces`, `-A` | `Boolean` List pods in all Kubernetes namespaces. | `false` | +| `-namespace`, `-n` | `String` The Kubernetes namespace to list proxies in. | Current [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) namespace. | + +Refer to the [Global Options](#global-options) for additional options that you can use +when installing Consul on Kubernetes. + +This command lists proxies and their `Type`. Types of proxies include: + +- `Sidecar`: The majority of pods in the cluster are `Sidecar` types. They run the + proxy as a sidecar to connect the pod as a service in the mesh. +- `API Gateway`: These pods run a proxy to manage connections with networks + outside of the Consul cluster. Read more about [API gateways](/docs/api-gateway). +- `Ingress Gateway`: These pods run a proxy to manage ingress into the + Kubernetes cluster. Read more about [ingress gateways](/docs/k8s/connect/ingress-gateways). +- `Terminating Gateway`: These pods run a proxy to control connections to + external services. Read more about [terminating gateways](/docs/k8s/connect/terminating-gateways). +- `Mesh Gateway`: These pods run a proxy to manage connections between + Consul clusters connected using mesh federation. Read more about [Consul Mesh Federation](/docs/k8s/installation/multi-cluster/kubernetes). + +#### Example Commands + +Display all pods in the current Kubernetes namespace that run proxies managed +by Consul. + +```shell-session +$ consul-k8s proxy list +``` + +``` +Namespace: default + +Name Type +backend-658b679b45-d5xlb Sidecar +client-767ccfc8f9-6f6gx Sidecar +client-767ccfc8f9-f8nsn Sidecar +client-767ccfc8f9-ggrtx Sidecar +frontend-676564547c-v2mfq Sidecar +``` + +Display all pods in the `consul` Kubernetes namespace that run proxies managed +by Consul. + +```shell-session +$ consul-k8s proxy list -n consul +``` + +``` +Namespace: consul + +Name Type +consul-ingress-gateway-6fb5544485-br6fl Ingress Gateway +consul-ingress-gateway-6fb5544485-m54sp Ingress Gateway +``` + +Display all Pods across all namespaces that run proxies managed by Consul. + +```shell-session +$ consul-k8s proxy list -A +Namespace: All namespaces + +Namespace Name Type +consul consul-ingress-gateway-6fb5544485-br6fl Ingress Gateway +consul consul-ingress-gateway-6fb5544485-m54sp Ingress Gateway +default backend-658b679b45-d5xlb Sidecar +default client-767ccfc8f9-6f6gx Sidecar +default client-767ccfc8f9-f8nsn Sidecar +default client-767ccfc8f9-ggrtx Sidecar +default frontend-676564547c-v2mfq Sidecar +``` + +### `proxy read` + +The `proxy read` command allows you to inspect the configuration of Envoy proxies running on a given Pod. + +```shell-session +$ consul-k8s proxy read +``` + +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](https://www.consul.io/docs/k8s/connect#kubernetes-pods-with-multiple-ports), +configuration for all proxies in the Pod will be displayed. + The following options are available. -| Flag | Description | Default | Required | -| --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------- | -| `-auto-approve`                                     | Boolean value that enables you to skip the removal confirmation prompt. | `false` | Optional | -| `-name` | String value for the name of the installation to remove. | none | Optional | -| `-namespace` | String value that specifies the namespace of the Consul installation to remove. | `consul` | Optional | -| `-timeout` | Specifies how long to wait for the removal process to complete before timing out. The value is specified with an integer and string value indicating a unit of time.
The following units are supported:
`ms` (milliseconds)
`s` (seconds)
`m` (minutes)
`h` (hours)
In the following example, removal will timeout after one minute:
`consul-k8s uninstall -timeout 1m` | `10m` | Optional | -| `-wipe-data` | Boolean value that deletes PVCs and secrets associated with the Consul installation during installation.
Data will be removed without a verification prompt if the `-auto-approve` flag is set to `true`. | `false`
Instructions for removing data will be printed to the console. | Optional | -| `--help` | Prints usage information for this option. | none | Optional | +| Flag | Description | Default | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| `-namespace`, `-n` | `String` The namespace where the target Pod can be found. | Current [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) namespace. | +| `-output`, `-o` | `String` Output the Envoy configuration as 'table', 'json', or 'raw'. | `'table'` | +| `-clusters` | `Boolean` Filter output to only show clusters. | `false` | +| `-endpoints` | `Boolean` Filter output to only show endpoints. | `false` | +| `-listeners` | `Boolean` Filter output to only show listeners. | `false` | +| `-routes` | `Boolean` Filter output to only show routes. | `false` | +| `-secrets` | `Boolean` Filter output to only show secrets. | `false` | +| `-address` | `String` Filter clusters, endpoints, and listeners output to only those with endpoint addresses which contain the given value. | `""` | +| `-fqdn` | `String` Filter cluster output to only clusters with a fully qualified domain name which contains the given value. | `""` | +| `-port` | `Int` Filter endpoints output to only endpoints with the given port number. | `-1` which does not filter by port | -See [Global Options](#global-options) for additional commands that you can use when uninstalling Consul from Kubernetes. +#### Example commands -#### Example Command - -The following example command immediately uninstalls Consul from the `my-ns` namespace with the name `my-consul` and removes PVCs and secrets associated with the installation without asking for verification: +Get the configuration summary for the Envoy proxy running on the Pod +`backend-658b679b45-d5xlb`. ```shell-session -$ consul-k8s uninstall -namespace=my-ns -name=my-consul -wipe-data=true -auto-approve=true +$ consul-k8s proxy read backend-658b679b45-d5xlb +Envoy configuration for backend-658b679b45-d5xlb in namespace default: + +==> Clusters (5) +Name FQDN Endpoints Type Last Updated +local_agent local_agent 192.168.79.187:8502 STATIC 2022-05-13T04:22:39.553Z +client client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 192.168.18.110:20000, 192.168.52.101:20000, 192.168.65.131:20000 EDS 2022-08-08T12:02:07.471Z +frontend frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 192.168.63.120:20000 EDS 2022-08-08T12:02:07.354Z +local_app local_app 127.0.0.1:8080 STATIC 2022-05-13T04:22:39.655Z +original-destination original-destination ORIGINAL_DST 2022-05-13T04:22:39.743Z + + +==> Endpoints (6) +Address:Port Cluster Weight Status +192.168.79.187:8502 local_agent 1.00 HEALTHY +192.168.18.110:20000 client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 1.00 HEALTHY +192.168.52.101:20000 client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 1.00 HEALTHY +192.168.65.131:20000 client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 1.00 HEALTHY +192.168.63.120:20000 frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 1.00 HEALTHY +127.0.0.1:8080 local_app 1.00 HEALTHY + +==> Listeners (2) +Name Address:Port Direction Filter Chain Match Filters Last Updated +public_listener 192.168.69.179:20000 INBOUND Any * to local_app/ 2022-08-08T12:02:22.261Z +outbound_listener 127.0.0.1:15001 OUTBOUND 10.100.134.173/32, 240.0.0.3/32 to client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 2022-07-18T15:31:03.246Z + 10.100.31.2/32, 240.0.0.5/32 to frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul + Any to original-destination + +==> Routes (1) +Name Destination Cluster Last Updated +public_listener local_app/ 2022-08-08T12:02:22.260Z + +==> Secrets (0) +Name Type Last Updated + + +``` + +Get the Envoy configuration summary for all clusters with a fully qualified +domain name that includes `"default"`. Display only clusters and listeners. + +```shell-session +$ consul-k8s proxy read backend-658b679b45-d5xlb -fqdn default -clusters -listeners +==> Filters applied + Fully qualified domain names containing: default + +Envoy configuration for backend-658b679b45-d5xlb in namespace default: + +==> Clusters (2) +Name FQDN Endpoints Type Last Updated +client client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 192.168.18.110:20000, 192.168.52.101:20000, 192.168.65.131:20000 EDS 2022-08-08T12:02:07.471Z +frontend frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 192.168.63.120:20000 EDS 2022-08-08T12:02:07.354Z + + +==> Listeners (2) +Name Address:Port Direction Filter Chain Match Filters Last Updated +public_listener 192.168.69.179:20000 INBOUND Any * to local_app/ 2022-08-08T12:02:22.261Z +outbound_listener 127.0.0.1:15001 OUTBOUND 10.100.134.173/32, 240.0.0.3/32 to client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul 2022-07-18T15:31:03.246Z + 10.100.31.2/32, 240.0.0.5/32 to frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul + Any to original-destination + +``` + +Get the Envoy configuration summary in a JSON format. Note that this is not the +same as the raw configuration dump from the admin API. This information is the +same as what is displayed in the table output above, but in a JSON format. + +```shell-session +$ consul-k8s proxy read backend-658b679b45-d5xlb -o json +{ + "backend-658b679b45-d5xlb": { + "clusters": [ + { + "Name": "local_agent", + "FullyQualifiedDomainName": "local_agent", + "Endpoints": [ + "192.168.79.187:8502" + ], + "Type": "STATIC", + "LastUpdated": "2022-05-13T04:22:39.553Z" + }, + { + "Name": "client", + "FullyQualifiedDomainName": "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", + "Endpoints": [ + "192.168.18.110:20000", + "192.168.52.101:20000", + "192.168.65.131:20000" + ], + "Type": "EDS", + "LastUpdated": "2022-08-08T12:02:07.471Z" + }, + { + "Name": "frontend", + "FullyQualifiedDomainName": "frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", + "Endpoints": [ + "192.168.63.120:20000" + ], + "Type": "EDS", + "LastUpdated": "2022-08-08T12:02:07.354Z" + }, + { + "Name": "local_app", + "FullyQualifiedDomainName": "local_app", + "Endpoints": [ + "127.0.0.1:8080" + ], + "Type": "STATIC", + "LastUpdated": "2022-05-13T04:22:39.655Z" + }, + { + "Name": "original-destination", + "FullyQualifiedDomainName": "original-destination", + "Endpoints": [], + "Type": "ORIGINAL_DST", + "LastUpdated": "2022-05-13T04:22:39.743Z" + } + ], + "endpoints": [ + { + "Address": "192.168.79.187:8502", + "Cluster": "local_agent", + "Weight": 1, + "Status": "HEALTHY" + }, + { + "Address": "192.168.18.110:20000", + "Cluster": "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", + "Weight": 1, + "Status": "HEALTHY" + }, + { + "Address": "192.168.52.101:20000", + "Cluster": "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", + "Weight": 1, + "Status": "HEALTHY" + }, + { + "Address": "192.168.65.131:20000", + "Cluster": "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", + "Weight": 1, + "Status": "HEALTHY" + }, + { + "Address": "192.168.63.120:20000", + "Cluster": "frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", + "Weight": 1, + "Status": "HEALTHY" + }, + { + "Address": "127.0.0.1:8080", + "Cluster": "local_app", + "Weight": 1, + "Status": "HEALTHY" + } + ], + "listeners": [ + { + "Name": "public_listener", + "Address": "192.168.69.179:20000", + "FilterChain": [ + { + "Filters": [ + "* to local_app/" + ], + "FilterChainMatch": "Any" + } + ], + "Direction": "INBOUND", + "LastUpdated": "2022-08-08T12:02:22.261Z" + }, + { + "Name": "outbound_listener", + "Address": "127.0.0.1:15001", + "FilterChain": [ + { + "Filters": [ + "to client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul" + ], + "FilterChainMatch": "10.100.134.173/32, 240.0.0.3/32" + }, + { + "Filters": [ + "to frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul" + ], + "FilterChainMatch": "10.100.31.2/32, 240.0.0.5/32" + }, + { + "Filters": [ + "to original-destination" + ], + "FilterChainMatch": "Any" + } + ], + "Direction": "OUTBOUND", + "LastUpdated": "2022-07-18T15:31:03.246Z" + } + ], + "routes": [ + { + "Name": "public_listener", + "DestinationCluster": "local_app/", + "LastUpdated": "2022-08-08T12:02:22.260Z" + } + ], + "secrets": [] + } +} +``` + +Get the raw Envoy configuration dump and clusters information for the Envoy +proxy running on the Pod `backend-658b679b45-d5xlb`. The example command returns +the raw configuration for each service as JSON. You can use the +[JQ command line tool](https://stedolan.github.io/jq/) to index into +the configuration for the service you want to inspect. + +Refer to the [Envoy config dump documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/admin/v3/config_dump.proto) +for more information on the structure of the config dump. + +The following output is truncated for brevity. + +```shell-session +$ consul-k8s proxy read backend-658b679b45-d5xlb -o raw +{ + "backend-658b679b45-d5xlb": { + "clusters": { + // [-- snip 372 lines --] output from the Envoy admin interface's /clusters endpoint. + }, + "config_dump": { + // [-- snip 1816 lines --] output from the Envoy admin interface's /config_dump?include_eds endpoint. + } +} ``` ### `status` -The `status` command provides an overall status summary of the Consul on Kubernetes installation. It also provides the config that was used to deploy Consul K8s and provides a quick glance at the health of both Consul servers and clients. This command does not take in any flags. +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. ```shell-session $ consul-k8s status @@ -163,9 +492,36 @@ $ consul-k8s status ✓ Consul clients healthy (3/3) ``` -### `upgrade` +### `uninstall` --> The `consul-k8s upgrade` **subcommand is currently in beta**: This subcommand is not recommended for production environments. +The `uninstall` command removes Consul from Kubernetes. + +```shell-session +$ consul-k8s uninstall +``` + +The following options are available. + +| Flag | Description | Default | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `-auto-approve` | Boolean value that enables you to skip the removal confirmation prompt. | `false` | +| `-name` | String value for the name of the installation to remove. | none | +| `-namespace` | String value that specifies the namespace of the Consul installation to remove. | `consul` | +| `-timeout` | Specifies how long to wait for the removal process to complete before timing out. The value is specified with an integer and string value indicating a unit of time.
The following units are supported:
`ms` (milliseconds)
`s` (seconds)
`m` (minutes)
`h` (hours)
In the following example, removal will timeout after one minute:
`consul-k8s uninstall -timeout 1m` | `10m` | +| `-wipe-data` | Boolean value that deletes PVCs and secrets associated with the Consul installation during installation.
Data will be removed without a verification prompt if the `-auto-approve` flag is set to `true`. | `false`
Instructions for removing data will be printed to the console. | +| `--help` | Prints usage information for this option. | none | + +See [Global Options](#global-options) for additional commands that you can use when uninstalling Consul from Kubernetes. + +#### Example Command + +The following example command immediately uninstalls Consul from the `my-ns` namespace with the name `my-consul` and removes PVCs and secrets associated with the installation without asking for verification: + +```shell-session +$ consul-k8s uninstall -namespace=my-ns -name=my-consul -wipe-data=true -auto-approve=true +``` + +### `upgrade` The `upgrade` command upgrades the Consul on Kubernetes components to the current version of the `consul-k8s` cli. Prior to running `consul-k8s upgrade`, the `consul-k8s` CLI should first be upgraded to the latest version as described [Upgrade the Consul K8s CLI](#upgrade-the-consul-k8s-cli) @@ -175,20 +531,20 @@ $ consul-k8s upgrade The following options are available. -| Flag | Description | Default | Required | -| --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------- | -| `-auto-approve`                                     | Boolean value that enables you to skip the upgrade confirmation prompt. | `false` | Optional | -| `-dry-run` | Boolean value that allows you to run pre-upgrade checks and returns a summary of the upgrade. | `false` | Optional | -| `-config-file` | String value that specifies the path to a file containing custom upgrade configurations, e.g., Consul Helm chart values file.
You can use the `-config-file` flag multiple times to specify multiple files. | none | Optional | -| `-namespace` | String value that specifies the namespace of the Consul installation. | `consul` | Optional | -| `-preset` | String value that upgrades Consul based on a preset configuration. | Configuration of the Consul Helm chart. | Optional | -| `-set` | String value that enables you to set a customizable value. This flag is comparable to the `helm upgrade --set` flag.
You can use the `-set` flag multiple times to set multiple values.
Consul Helm chart values are supported. | none | Optional | -| `-set-file` | String value that specifies the name of an arbitrary config file. This flag is comparable to the `helm upgrade --set-file`
flag. The contents of the file will be used to set a customizable value. You can use the `-set-file` flag multiple times to specify multiple files.
Consul Helm chart values are supported. | none | Optional | -| `-set-string` | String value that enables you to set a customizable string value. This flag is comparable to the `helm upgrade --set-string`
flag. You can use the `-set-string` flag multiple times to specify multiple strings.
Consul Helm chart values are supported. | none | Optional | -| `-timeout` | Specifies how long to wait for the upgrade process to complete before timing out. The value is specified with an integer and string value indicating a unit of time.
The following units are supported:
`ms` (milliseconds)
`s` (seconds)
`m` (minutes)
In the following example, the upgrade will timeout after one minute:
`consul-k8s upgrade -timeout 1m` | `10m` | Optional | -| `-wait` | Boolean value that determines if Consul should wait for resources in the upgrade to be ready before exiting the command. | `true` | Optional | -| `-verbose`, `-v` | Boolean value that specifies whether to output verbose logs from the upgrade command with the status of resources being upgraded. | `false` | Optional | -| `--help` | Prints usage information for this option. | none | Optional | +| Flag | Description | Default | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `-auto-approve` | Boolean value that enables you to skip the upgrade confirmation prompt. | `false` | +| `-dry-run` | Boolean value that allows you to run pre-upgrade checks and returns a summary of the upgrade. | `false` | +| `-config-file` | String value that specifies the path to a file containing custom upgrade configurations, e.g., Consul Helm chart values file.
You can use the `-config-file` flag multiple times to specify multiple files. | none | +| `-namespace` | String value that specifies the namespace of the Consul installation. | `consul` | +| `-preset` | String value that upgrades Consul based on a preset configuration. | Configuration of the Consul Helm chart. | +| `-set` | String value that enables you to set a customizable value. This flag is comparable to the `helm upgrade --set` flag.
You can use the `-set` flag multiple times to set multiple values.
Consul Helm chart values are supported. | none | +| `-set-file` | String value that specifies the name of an arbitrary config file. This flag is comparable to the `helm upgrade --set-file`
flag. The contents of the file will be used to set a customizable value. You can use the `-set-file` flag multiple times to specify multiple files.
Consul Helm chart values are supported. | none | +| `-set-string` | String value that enables you to set a customizable string value. This flag is comparable to the `helm upgrade --set-string`
flag. You can use the `-set-string` flag multiple times to specify multiple strings.
Consul Helm chart values are supported. | none | +| `-timeout` | Specifies how long to wait for the upgrade process to complete before timing out. The value is specified with an integer and string value indicating a unit of time.
The following units are supported:
`ms` (milliseconds)
`s` (seconds)
`m` (minutes)
In the following example, the upgrade will timeout after one minute:
`consul-k8s upgrade -timeout 1m` | `10m` | +| `-wait` | Boolean value that determines if Consul should wait for resources in the upgrade to be ready before exiting the command. | `true` | +| `-verbose`, `-v` | Boolean value that specifies whether to output verbose logs from the upgrade command with the status of resources being upgraded. | `false` | +| `--help` | Prints usage information for this option. | none | See [Global Options](#global-options) for additional commands that you can use when installing Consul on Kubernetes. @@ -210,7 +566,7 @@ $ consul-k8s --version The following global options are available. -| Flag | Description | Default | Required | -| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------- | -------- | -| `-context`                   | String value that sets the Kubernetes context to use for Consul K8s CLI operations. | none | Optional | -| `-kubeconfig`
Alias: `-c` | String value that specifies the path to the `kubeconfig` file.
| none | Optional | +| Flag | Description | Default | +| -------------------------------- | ----------------------------------------------------------------------------------- | ------- | +| `-context` | String value that sets the Kubernetes context to use for Consul K8s CLI operations. | none | +| `-kubeconfig`, `-c` | String value that specifies the path to the `kubeconfig` file.
| none | diff --git a/website/content/docs/lambda/invocation.mdx b/website/content/docs/lambda/invocation.mdx index 991c8eb92d..4789c0adac 100644 --- a/website/content/docs/lambda/invocation.mdx +++ b/website/content/docs/lambda/invocation.mdx @@ -72,7 +72,7 @@ service mesh. } ``` 1. Issue the `consul services register` command to store the configuration: - ```shell-sesion + ```shell-session $ consul services register api-sidecar-proxy.hcl ``` 1. Call the upstream service to invoke the Lambda function. In the following example, the `api` service invokes the `authentication` service at `localhost:2345`: diff --git a/website/content/docs/nia/configuration.mdx b/website/content/docs/nia/configuration.mdx index 28b6063b48..90e5905153 100644 --- a/website/content/docs/nia/configuration.mdx +++ b/website/content/docs/nia/configuration.mdx @@ -102,7 +102,7 @@ To read more on suggestions for configuring the Consul agent, see [run an agent] ```hcl consul { - address = "consul.example.com" + address = "localhost:8500" auth {} tls {} token = null diff --git a/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx b/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx index 357fd7032d..b191e77fd2 100644 --- a/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx +++ b/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx @@ -8,7 +8,7 @@ description: >- # Consul API Gateway 0.1.0 -## OVerview +## Overview This is the first general availability (GA) release of Consul API Gateway. It provides controlled access for network traffic from outside a Consul service 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 new file mode 100644 index 0000000000..b0c336af72 --- /dev/null +++ b/website/content/docs/release-notes/consul-api-gateway/v0_4_x.mdx @@ -0,0 +1,79 @@ +--- +layout: docs +page_title: 0.4.x +description: >- + Consul API Gateway release notes for version 0.4.x +--- + +# Consul API Gateway 0.4.0 + +## Release Highlights + +- **Support for Kubernetes Gateway API Version 0.5.0 and v1beta1 APIs:** + The `v0.5.0` release of the Kubernetes Gateway API is significant because it + marks the growth in maturity to a beta API version (v1beta1) release for some + of the key APIs: + - GatewayClass + - Gateway + - HTTPRoute + + The other APIs (e.g. TCPRoute) are still at the `v1alpha2` stage. + + Reaching `v1beta1` status has several benefits for users, including greater + stability and backward compatibility requirements. Existing fields and allowed + options can not be removed or renamed except in a new, major version of the + API. Once an API reaches `v1beta1` status, future versions must comply with + several backward compatibility requirements. + +- **URL Path Prefix Rewrite** This release introduces support for rewriting a + URL's path prefix when routing HTTP traffic. To use this functionality, add a + `URLRewrite` filter to an `HTTPRoute` configuration. This enables the gateway + 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](/docs/api-gateway/usage) for additional information. + +## What has Changed + +- **Reference Policy Renamed to Reference Grant** In v0.5.0 of the Kubernetes + Gateway API, `ReferencePolicy` has been renamed to `ReferenceGrant`. This + release supports both but `ReferencePolicy` is deprecated and will be removed + in a future version of the standard. + + After upgrading to this version of Consul API Gateway, you should rename all + existing `ReferencePolicy` to `ReferenceGrant`. Refer to the [Upgrades](/docs/api-gateway/upgrades) + instructions for additional details. + +## Supported Software + +- Consul 1.11.2+ +- HashiCorp Consul Helm chart 0.47.1+ +- Kubernetes 1.21+ + - Kubernetes 1.24 is not supported at this time. +- Kubectl 1.21+ +- Envoy proxy support is determined by the Consul version deployed. Refer to + [Envoy Integration](/docs/connect/proxies/envoy) for details. + +## Kubernetes Gateway API Specification + +Supported version of the [Gateway API](https://gateway-api.sigs.k8s.io/) spec: v0.5.0 + +## Upgrading + +For detailed information on upgrading, please refer to the [Upgrades page](/docs/api-gateway/upgrades) + +## Known Issues +The following issues are know to exist in the v0.4.0 release + +- API Gateway pods fail to start if namespace mirroring enabled and destination + namespace doesn't exist. See GitHub Issue + [#248](https://github.com/hashicorp/consul-api-gateway/issues/248) for + details. + +## Changelogs + +The changelogs for this major release version and any maintenance versions are listed below. + +~> **Note:** The following link will take you to the changelogs on the GitHub website. + +- [0.4.0](https://github.com/hashicorp/consul-api-gateway/releases/tag/v0.4.0) diff --git a/website/content/docs/release-notes/consul-ecs/v0_4_x.mdx b/website/content/docs/release-notes/consul-ecs/v0_4_x.mdx index 18e6028a94..5ada947226 100644 --- a/website/content/docs/release-notes/consul-ecs/v0_4_x.mdx +++ b/website/content/docs/release-notes/consul-ecs/v0_4_x.mdx @@ -5,7 +5,7 @@ description: >- Consul ECS release notes for version 0.4.x --- -# Consul ECS 0.4.0 +# Consul ECS 0.4.x ## Release Highlights @@ -23,6 +23,6 @@ The changelogs for this major release version and any maintenance versions are l -> **Note**: These links will take you to the changelogs on the GitHub website. -- [0.4.0](https://github.com/hashicorp/consul-ecs/releases/tag/v0.4.0) - - [0.4.1](https://github.com/hashicorp/consul-ecs/releases/tag/v0.4.1) + +- [0.4.0](https://github.com/hashicorp/consul-ecs/releases/tag/v0.4.0) diff --git a/website/content/docs/release-notes/consul-ecs/v0_5_x.mdx b/website/content/docs/release-notes/consul-ecs/v0_5_x.mdx new file mode 100644 index 0000000000..54b29b3b30 --- /dev/null +++ b/website/content/docs/release-notes/consul-ecs/v0_5_x.mdx @@ -0,0 +1,30 @@ +--- +layout: docs +page_title: 0.5.x +description: >- + Consul ECS release notes for version 0.5.x +--- + +# Consul ECS 0.5.x + +## Release Highlights + +- **Audit Logging (Enterprise) :** Consul on ECS now captures authentication events and processes them with the HTTP API. Audit logging provides insight into access and usage patterns. Refer to [Audit Logging](/docs/ecs/enterprise#audit-logging) for usage information. + +- **AWS IAM Auth Method :** This feature provides support for Consul's AWS IAM auth method. This allows AWS IAM roles and users to authenticate with Consul to obtain ACL tokens. Refer to [ECS Configuration Reference](/docs/ecs/configuration-reference#consullogin) for configuration information. + +- **Mesh Gateways :** This feature introduces support for running mesh gateways as ECS tasks. Mesh gateways enable service mesh communication across datacenter and admin partition boundaries. Refer to [ECS Installation with Terraform](/docs/ecs/terraform/install#configure-the-gateway-task-module) for usage information. + +## Supported Software Versions + +- Consul: 1.12.x + +## Changelogs + +The changelogs for this major release version and any maintenance versions are listed below. + +-> **Note**: These links will take you to the changelogs on the GitHub website. + +- [0.5.1](https://github.com/hashicorp/consul-ecs/releases/tag/v0.5.1) + +- [0.5.0](https://github.com/hashicorp/consul-ecs/releases/tag/v0.5.0) 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 new file mode 100644 index 0000000000..b040787c5c --- /dev/null +++ b/website/content/docs/release-notes/consul-k8s/v0_47_x.mdx @@ -0,0 +1,48 @@ +--- +layout: docs +page_title: 0.47.x +description: >- + Consul on Kubernetes release notes for version 0.47.x +--- + +# Consul on Kubernetes 0.47.0 + +## 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](/docs/connect/cluster-peering/k8s) 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. + * Add `consul-k8s proxy read podname` command for displaying Envoy configuration for a given pod + +- **Transparent Proxy Egress**: Adds support for destinations on the Service Defaults CRD when using transparent proxy for terminating gateways. + +## Supported Software + +- Consul 1.11.x, Consul 1.12.x and Consul 1.13.1+ +- Kubernetes 1.19+ + - Kubernetes 1.24 is not supported at this time. +- Kubectl 1.21+ +- Envoy proxy support is determined by the Consul version deployed. Refer to + [Envoy Integration](/docs/connect/proxies/envoy) for details. + +## Upgrading + +For detailed information on upgrading, please refer to the [Upgrades page](/docs/k8s/upgrade) + +## Known Issues + +The following issues are know to exist in the v0.47.0 and v0.47.1 releases + +- Kubernetes 1.24 is not supported because secret-based tokens are no longer autocreated by default for service accounts. Refer to GitHub issue + [[GH-1145](https://github.com/hashicorp/consul-k8s/issues/1145)] for + details. + +## 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. + +- [0.47.0](https://github.com/hashicorp/consul-k8s/releases/tag/v0.47.0) +- [0.47.1](https://github.com/hashicorp/consul-k8s/releases/tag/v0.47.1) diff --git a/website/content/docs/release-notes/consul/v1_10_x.mdx b/website/content/docs/release-notes/consul/v1_10_x.mdx index e4531dec08..e55b2ce32c 100644 --- a/website/content/docs/release-notes/consul/v1_10_x.mdx +++ b/website/content/docs/release-notes/consul/v1_10_x.mdx @@ -24,6 +24,8 @@ description: >- - Drops support for Envoy version 1.13.x. - (Enterprise Only) Consul Enterprise has removed support for temporary licensing. All server agents must have a valid license at startup and client agents must have a license at startup or be able to retrieve one from the servers. +## Upgrading + For more detailed information, please refer to the [upgrade details page](/docs/upgrading/upgrade-specific#consul-1-10-0) and the changelogs. ## Changelogs diff --git a/website/content/docs/release-notes/consul/v1_11_x.mdx b/website/content/docs/release-notes/consul/v1_11_x.mdx index eeb468003d..d26cd6a804 100644 --- a/website/content/docs/release-notes/consul/v1_11_x.mdx +++ b/website/content/docs/release-notes/consul/v1_11_x.mdx @@ -27,6 +27,8 @@ description: >- - Drops support for Envoy versions 1.15.x and 1.16.x +## Upgrading + For more detailed information, please refer to the [upgrade details page](/docs/upgrading/upgrade-specific#consul-1-11-0) and the changelogs. ## Changelogs diff --git a/website/content/docs/release-notes/consul/v1_12_x.mdx b/website/content/docs/release-notes/consul/v1_12_x.mdx new file mode 100644 index 0000000000..842dfb31c8 --- /dev/null +++ b/website/content/docs/release-notes/consul/v1_12_x.mdx @@ -0,0 +1,54 @@ +--- +layout: docs +page_title: 1.12.x +description: >- + Consul release notes for version 1.12.x +--- + +# Consul 1.12.0 + +## Release Highlights + +- **AWS IAM Auth Method**: Consul now provides an AWS IAM auth method that allows AWS IAM roles and users to authenticate with Consul to obtain ACL tokens. Refer to [AWS IAM Auth Method](/docs/security/acl/auth-methods/aws-iam) for detailed configuration information. + +- **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](/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](/docs]/lambda) and [Invoke Lambda Functions](/docs/lambda/invocation) for more details. + +- **Mesh-wide TLS min/max versions and cipher suites:** Using the [Mesh](/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. + +- **Expanded details for ACL Permission Denied errors**: Details are now provided when a permission denied errors surface for RPC calls. Details include the accessor ID of the ACL token, the missing permission, and any namespace or partition that the error occurred on. + +- **ACL token read**: The `consul acl token read -rules` command now includes an `-expanded` option to display detailed info about any policies and rules affecting the token. Refer to [Consul ACL Token read](/commands/acl/token/read) for more details. + +- **Automatically reload agent config when watching agent config file changes**: When using the `auto-reload-config` CLI flag or `auto_reload_config` agent config option, Consul now automatically reloads the [reloadable configuration options](/docs/agent/config#reloadable-configuration) when configuration files change. Refer to [auto_reload_config](/docs/agent/config/cli-flags#_auto_reload_config) for more details. + + +## What's Changed + +- Removes support for Envoy 1.17.x and Envoy 1.18.x, and adds support for Envoy 1.21.x and Envoy 1.22.x. Refer to the [Envoy Compatibility matrix](/docs/connect/proxies/envoy) for more details. + +- The `disable_compat_1.9` option now defaults to true. Metrics formatted in the style of version 1.9, such as `consul.http...`, can still be enabled by setting disable_compat_1.9 = false. However, these metrics will be removed in 1.13. + +- The `agent_master` ACL token has been renamed to `agent_recovery` ACL token. In addition, the `consul acl set-agent-token master` command has been replaced with `consul acl set-agent-token recovery`. Refer to [ACL Agent Recovery Token](/docs/security/acl/acl-tokens#acl-agent-recovery-token) and [Consul ACL Set Agent Token](/commands/acl/set-agent-token) for more information. + +- If TLS min versions and max versions are not specified, the TLS min/max versions default to the following values. For details on how to configure TLS min and max, refer to the [Mesh TLS config entry](/docs/connect/config-entries/mesh#tls) or CRD documentation. + - Incoming connections: TLS 1.2 for min0 version, TLS 1.3 for max version + - Outgoing connections: TLS 1.2 for both TLS min and TLS max versions. + +## Upgrading + +For more detailed information, please refer to the [upgrade details page](/docs/upgrading/upgrade-specific#consul-1-12-0) and the changelogs. + +## 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.12.0](https://github.com/hashicorp/consul/releases/tag/v1.12.0) +- [1.12.1](https://github.com/hashicorp/consul/releases/tag/v1.12.1) +- [1.12.2](https://github.com/hashicorp/consul/releases/tag/v1.12.2) +- [1.12.3](https://github.com/hashicorp/consul/releases/tag/v1.12.3) +- [1.12.4](https://github.com/hashicorp/consul/releases/tag/v1.12.4) + diff --git a/website/content/docs/release-notes/consul/v1_13_x.mdx b/website/content/docs/release-notes/consul/v1_13_x.mdx new file mode 100644 index 0000000000..23b694a913 --- /dev/null +++ b/website/content/docs/release-notes/consul/v1_13_x.mdx @@ -0,0 +1,44 @@ +--- +layout: docs +page_title: 1.13.x +description: >- + Consul release notes for version 1.13.x +--- + +# Consul 1.13.0 + +## Release Highlights + +- **Cluster Peering (Beta)**: This version adds a new model to federate Consul clusters for both service mesh and traditional service discovery. Cluster peering allows for service interconnectivity with looser coupling than the existing WAN federation. For more information, refer to the [cluster peering](/docs/connect/cluster-peering) documentation. + +- **Transparent proxying through terminating gateways**: This version adds egress traffic control to destinations outside of Consul's catalog, such as APIs on the public internet. Transparent proxies can dial [destinations defined in service-defaults](/docs/connect/config-entries/service-defaults#destination) and have the traffic routed through terminating gateways. For more information, refer to the [terminating gateway](/docs/connect/gateways/terminating-gateway#terminating-gateway-configuration) documentation. + +- **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](/docs/discovery/checks#udp-interval). + +## What's Changed + +- Removes support for Envoy 1.19.x and adds suport for Envoy 1.23. Refer to the [Envoy Compatibility matrix](/docs/connect/proxies/envoy) for more details. + +- The [`disable_compat_19`](/docs/agent/options#telemetry-disable_compat_1.9) telemetry configuration option is now removed. In Consul versions 1.10.x through 1.11.x, the config defaulted to `false`. In version 1.12.x it defaulted to `true`. Before upgrading you should remove this flag from your config if the flag is being used. + +## Upgrading + +For more detailed information, please refer to the [upgrade details page](/docs/upgrading/upgrade-specific#consul-1-13-0) and the changelogs. + +## Known Issues +The following issues are know to exist in the 1.13.0 release: + +- Consul 1.13.1 fixes a compatibility issue when restoring snapshots from pre-1.13.0 versions of Consul. Refer to GitHub issue [[GH-14149](https://github.com/hashicorp/consul/issues/14149)] for more details. +- Consul 1.13.0 and Consul 1.13.1 default to requiring TLS for gRPC communication with Envoy proxies when auto-encrypt and auto-config are enabled. In environments where Envoy proxies are not already configured to use TLS for gRPC, upgrading Consul 1.13 will cause Envoy proxies to disconnect from the control plane (Consul agents). A future patch release will default to disabling TLS by default for GRPC communication with Envoy proxies when using Service Mesh and auto-config or auto-encrypt. Refer to GitHub issue [GH-14253](https://github.com/hashicorp/consul/issues/14253) and [Service Mesh deployments using auto-config and auto-enrypt](https://www.consul.io/docs/upgrading/upgrade-specific#service-mesh-deployments-using-auto-encrypt-or-auto-config) for more details. + + +## 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.13.0](https://github.com/hashicorp/consul/releases/tag/v1.13.0) +- [1.13.1](https://github.com/hashicorp/consul/releases/tag/v1.13.1) diff --git a/website/content/docs/security/acl/acl-rules.mdx b/website/content/docs/security/acl/acl-rules.mdx index fc8806fd4b..c836117061 100644 --- a/website/content/docs/security/acl/acl-rules.mdx +++ b/website/content/docs/security/acl/acl-rules.mdx @@ -22,6 +22,7 @@ The following table provides an overview of the resources you can use to create | `key`
`key_prefix`   | Controls access to key/value store operations in the [KV API](/api-docs/kv).
Can also use the `list` access level when setting the policy disposition.
Has additional value options in Consul Enterprise for integrating with [Sentinel](https://docs.hashicorp.com/sentinel/consul).
See [Key/Value Rules](#key-value-rules) for details. | Yes | | `keyring`       | Controls access to keyring operations in the [Keyring API](/api-docs/keyring).
See [Keyring Rules](#keyring-rules) for details. | No | | `mesh`       | Provides operator-level permissions for resources in the admin partition, such as ingress gateways or mesh proxy defaults. See [Mesh Rules](#mesh-rules) for details. | No | +| `peering`       | Controls access to cluster peerings in the [Cluster Peering API](/api-docs/peering). For more details, refer to [Peering Rules](#peering-rules). | No | | `namespace`
`namespace_prefix` | Controls access to one or more namespaces.
See [Namespace Rules](#namespace-rules) for details. | Yes | | `node`
`node_prefix`   | Controls access to node-level operations in the [Catalog API](/api-docs/catalog), [Health API](/api-docs/health), [Prepared Query API](/api-docs/query), [Network Coordinate API](/api-docs/coordinate), and [Agent API](/api-docs/agent)
See [Node Rules](#node-rules) for details. | Yes | | `operator`       | Controls access to cluster-level operations available in the [Operator API](/api-docs/operator) excluding keyring API endpoints.
See [Operator Rules](#operator-rules) for details. | No | @@ -71,25 +72,29 @@ You can include any number of namespace rules inside the admin partition. In the following example, the policy grants `write` access to the `ex-namespace` namespace, as well as namespaces prefixed with `exns-` in the `example` partition. -The `mesh` resource is also scoped to the admin partition rule, which grants -`write` access to mesh-level resources in the `example` partition. +The `mesh` and `peering` resources are also scoped to the admin partition rule, which grants +`write` access to the `mesh` and `peering` resources in the `example` partition. In addition, the policy grants `read` access to the `ex-namespace` namespace, as well as namespaces prefixed with `exns-` in all partitions containing the -`example-` prefix. Read access is granted for `mesh` resource scoped within the -associated partition. +`example-` prefix. Read access is granted for the `mesh` and `peering` resources +scoped within the associated partition. ```hcl partition "example" { mesh = "write" + peering = "write" + node "my-node" { policy = "write" } + namespace "ex-namespace" { policy = "write" } + namespace_prefix "exns-" { policy = "write" } @@ -97,6 +102,7 @@ partition "example" { partition_prefix "example-" { mesh = "read" + peering = "read" node "my-node" { policy = "read" @@ -367,9 +373,14 @@ keyring = "write" -### Mesh Rules +## Mesh Rules -The `mesh` resource controls access to ingress gateways, terminating gateways, and mesh configuration entries. The following rule grants read and write access: +The `mesh` resource controls access to ingress gateways, terminating gateways, and mesh configuration entries. + +In Consul Enterprise, mesh rules are scoped to an admin partition. Therefore, they can be nested in an +[admin partition rule](#admin-partition-rules) but not a [namespace rule](#namespace-rules). + +The following rule grants read and write access: @@ -600,6 +611,30 @@ operator = "read" +## Peering Rules +The `peering` resource controls access to cluster peerings in the [Cluster Peering API](/api-docs/peering). + +In Consul Enterprise, peering rules are scoped to an admin partition. Therefore, they can be nested in an +[admin partition rule](#admin-partition-rules) but not a [namespace rule](#namespace-rules). + +The following rule grants read and write access: + + + +```hcl +peering = "write" +``` + +```json +{ + "peering": "write" +} +``` + + + +For an example of how to apply rules for the `peering` resource alongside other rules, refer to the example configuration in [Admin Partition Rules](#admin-partition-rules). + ## Prepared Query Rules The `query` and `query_prefix` resources control access to create, update, and delete prepared queries in the diff --git a/website/content/docs/upgrading/instructions/index.mdx b/website/content/docs/upgrading/instructions/index.mdx index 9bae599d97..4ddc86b07c 100644 --- a/website/content/docs/upgrading/instructions/index.mdx +++ b/website/content/docs/upgrading/instructions/index.mdx @@ -13,12 +13,14 @@ This document is intended to help users who find themselves many versions behind ## 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) - Latest 1.10.x -- Latest current version (1.11.x or 1.12.x) +- Latest 1.12.x +- Latest 1.13.x ([at least 1.13.1](/docs/upgrading/upgrade-specific#service-mesh-compatibility)) ## Getting Started @@ -29,6 +31,9 @@ we recommend reviewing the changelog for versions between the one you are on and one you are upgrading to at each step to familiarize yourself with changes. Select your _currently installed_ release series: +- 1.12.x: work upwards from [1.13 upgrade notes](/docs/upgrading/upgrade-specific#consul-1-13-x) +- 1.11.x: work upwards from [1.12 upgrade notes](/docs/upgrading/upgrade-specific#consul-1-12-0) +- 1.10.x: work upwards from [1.11 upgrade notes](/docs/upgrading/upgrade-specific#consul-1-11-0) - [1.9.x](/docs/upgrading/instructions/upgrade-to-1-10-x) - [1.8.x](/docs/upgrading/instructions/upgrade-to-1-10-x) - [1.7.x](/docs/upgrading/instructions/upgrade-to-1-8-x) diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index ec0cf54d5a..2732ffe4f5 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -14,19 +14,139 @@ provided for their upgrades as a result of new features or changed behavior. This page is used to document those details separately from the standard upgrade flow. -## Consul 1.13.0 +## Consul 1.13.x -### gRPC TLS +### Service Mesh Compatibility -In prior Consul versions if HTTPS was enabled for the client API and exposed -via `ports { https = NUMBER }` then the same TLS material was used to encrypt -the gRPC port used for xDS. Now this is decoupled and activating TLS on the -gRPC endpoint is controlled solely with the gRPC section of the new -[`tls` stanza](/docs/agent/config/config-files#tls-configuration-reference). +Before upgrading existing Consul deployments using service mesh to Consul 1.13.x, +review the following guidances relevant to your deployment: +- [All service mesh deployments](#all-service-mesh-deployments) +- [Service mesh deployments using auto-encrypt or auto-config](#service-mesh-deployments-using-auto-encrypt-or-auto-config) +- [Service mesh deployments without the HTTPS port enabled on Consul agents](#service-mesh-deployments-without-the-https-port-enabled-on-consul-agents) -If you have not yet switched to the new `tls` stanza and were NOT using HTTPS -for the API then updating to Consul 1.13 will activate TLS for gRPC since the -deprecated TLS settings are used as defaults. +#### All service mesh deployments + +Upgrade to **Consul version 1.13.1 or later**. + +Consul 1.13.0 contains a bug that prevents Consul server agents from restoring +saved state on startup if the state + +1. was generated before Consul 1.13 (such as during an upgrade), and +2. contained any Connect proxy registrations. + +This bug is fixed in Consul versions 1.13.1 and newer. + +#### Service mesh deployments using auto-encrypt or auto-config + +**Do not upgrade to Consul 1.13 yet** if using +[auto-encrypt](/docs/agent/config/config-files#auto_encrypt) or +[auto-config](/docs/agent/config/config-files#auto_config). + +In Consul 1.13, auto-encrypt and auto-config both cause Consul +to require TLS for gRPC communication with Envoy proxies. +In environments where Envoy proxies are not already configured +to use TLS for gRPC, upgrading Consul 1.13 will cause +Envoy proxies to disconnect from the control plane (Consul agents). + +The underlying cause is the same as discussed in +[deployments without the HTTPS port enabled on Consul agents](#service-mesh-deployments-without-the-https-port-enabled-on-consul-agents). +However, when using auto-encrypt or auto-config, +the problem **cannot** currently be avoided by +[modifying the agent's TLS configuration](#modify-the-consul-agent-s-tls-configuration) +because auto-encrypt and auto-config automatically set +interface-generic TLS configuration in a manner similar to +[`tls.defaults`](/docs/agent/config/config-files#tls_defaults). +We are working to address this problem in an upcoming 1.13 patch release. + +#### Service mesh deployments without the HTTPS port enabled on Consul agents ((#grpc-tls)) + +If the HTTPS port is not enabled +([`ports { https = POSITIVE_INTEGER }`](/docs/agent/config/config-files#https_port)) +on a pre-1.13 Consul agent, +**[modify the agent's TLS configuration before upgrading](#modify-the-consul-agent-s-tls-configuration)** +to avoid Envoy proxies disconnecting from the control plane (Consul agents). +Envoy proxies include service mesh sidecars and gateways. + +##### Changes to gRPC and HTTP interface configuration + +If a Consul agent's HTTP API is exposed externally, +enabling HTTPS (TLS encryption for HTTP) is important. + +The gRPC interface is used for xDS communication between Consul and +Envoy proxies when using Consul service mesh. +A Consul agent's gRPC traffic is often loopback-only, +which TLS encryption is not important for. + +Prior to Consul 1.13, if [`ports { https = POSITIVE_INTEGER }`](/docs/agent/config/config-files#https_port) +was configured, TLS was enabled for both HTTP *and* gRPC. +This was inconvenient for deployments that +needed TLS for HTTP, but not for gRPC. +Enabling HTTPS also required launching Envoy proxies +with the necessary TLS material for xDS communication +with its Consul agent via TLS over gRPC. + +Consul 1.13 addresses this inconvenience by fully decoupling the TLS configuration for HTTP and gRPC interfaces. +TLS for gRPC is no longer enabled by setting +[`ports { https = POSITIVE_INTEGER }`](/docs/agent/config/config-files#https_port). +TLS configuration for gRPC is now determined exclusively by: + +1. [`tls.grpc`](/docs/agent/config/config-files#tls_grpc), which overrides +1. [`tls.defaults`](/docs/agent/config/config-files#tls_defaults), which overrides +1. [Deprecated TLS options](/docs/agent/config/config-files#tls_deprecated_options) such as + [`ca_file`](/docs/agent/config/config-files#ca_file-4), + [`cert_file`](/docs/agent/config/config-files#cert_file-4), and + [`key_file`](/docs/agent/config/config-files#key_file-4). + +This decoupling has a side effect that requires a +[TLS configuration change](#modify-the-consul-agent-s-tls-configuration) +for pre-1.13 agents without the HTTPS port enabled. +Without a TLS configuration change, +Consul 1.13 agents may now expect gRPC *with* TLS, +causing communication to fail with Envoy proxies +that continue to use gRPC *without* TLS. + +##### Modify the Consul agent's TLS configuration + +If [`tls.grpc`](/docs/agent/config/config-files#tls_grpc), +[`tls.defaults`](/docs/agent/config/config-files#tls_defaults), +or the [deprecated TLS options](/docs/agent/config/config-files#tls_deprecated_options) +define TLS material in their +`ca_file`, `ca_path`, `cert_file`, or `key_file` fields, +TLS for gRPC will be enabled in Consul 1.13, even if +[`ports { https = POSITIVE_INTEGER }`](/docs/agent/config/config-files#https_port) +is not set. + +This will cause Envoy proxies to disconnect from the control plane +after upgrading to Consul 1.13 if associated pre-1.13 Consul agents +have **not** set +[`ports { https = POSITIVE_INTEGER }`](/docs/agent/config/config-files#https_port). +To avoid this problem, make the following agent configuration changes: + +1. Remove TLS material from the Consul agents' + interface-generic TLS configuration options: + [`tls.defaults`](/docs/agent/config/config-files#tls_grpc) and + [deprecated TLS options](/docs/agent/config/config-files#tls_deprecated_options) +1. Reapply TLS material to the non-gRPC interfaces that need it with the + interface-specific TLS configuration stanzas + [introduced in Consul 1.12](/docs/upgrading/upgrade-specific#tls-configuration): + [`tls.https`](/docs/agent/config/config-files#tls_https) and + [`tls.internal_rpc`](/docs/agent/config/config-files#tls_internal_rpc). + + If upgrading directly from pre-1.12 Consul, + the above configuration change cannot be made before upgrading. + Therefore, consider upgrading agents to Consul 1.12 before upgrading to 1.13. + +If pre-1.13 Consul agents have set +[`ports { https = POSITIVE_INTEGER }`](/docs/agent/config/config-files#https_port), +this configuration change is not required to upgrade. +That setting means the pre-1.13 Consul agent requires TLS for gRPC *already*, +and will continue to do so after upgrading to 1.13. +If your pre-1.13 service mesh is working, you have already +configured your Envoy proxies to use TLS for gRPC when bootstrapping Envoy +via [`consul connect envoy`](/commands/connect/envoy), +such as with flags or environment variables like +[`-ca-file`](/commands/connect/envoy#ca-file) and +[`CONSUL_CACERT`](/commands#consul_cacert). ### 1.9 Telemetry Compatibility @@ -50,9 +170,11 @@ style `consul.api.http...` metrics and removing the configuration flag from your ### Nomad Namespace Incompatibility -Nomad Enterprise users should not upgrade to Consul Enterprise 1.12.0. +Nomad Enterprise users should not upgrade to Consul Enterprise 1.12.0, and instead should upgrade to 1.12.1 or later. -Consul 1.12.0 Enterprise introduced a change that prevents Nomad Enterprise from removing services from non-default Consul namespaces. To avoid errors, we recommend that Nomad Enterprise users wait to update Consul Enterprise until we fix this issue in a future release. +Consul 1.12.0 Enterprise introduced a change that prevents Nomad Enterprise from removing services from non-default Consul namespaces. + +The Consul Enterprise codebase was updated with a fix for this issue in version 1.12.1. ### TLS Configuration diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 446a835640..fb01baa19d 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -15,7 +15,11 @@ "hidden": true }, { - "title": "What is a Service Mesh?", + "title": "Service Discovery", + "path": "intro/usecases/what-is-service-discovery" + }, + { + "title": "Service Mesh", "path": "intro/usecases/what-is-a-service-mesh" } ] @@ -263,6 +267,10 @@ { "title": "Enabling Service-to-service Traffic Across Admin Partitions", "path": "connect/gateways/mesh-gateway/service-to-service-traffic-partitions" + }, + { + "title": "Enabling Service-to-service Traffic Across Peered Clusters", + "path": "connect/gateways/mesh-gateway/service-to-service-traffic-peers" } ] }, @@ -280,7 +288,7 @@ "title": "Cluster Peering", "routes": [ { - "title": "What is Cluster Peering", + "title": "What is Cluster Peering?", "path": "connect/cluster-peering" }, { @@ -355,19 +363,48 @@ }, { "title": "Installation", - "path": "api-gateway/consul-api-gateway-install" + "path": "api-gateway/install" }, { "title": "Technical Specifications", "path": "api-gateway/tech-specs" }, { - "title": "Common Errors", - "path": "api-gateway/common-errors" + "title": "Upgrades", + "path": "api-gateway/upgrades" }, { - "title": "Upgrades", - "path": "api-gateway/upgrade-specific-versions" + "title": "Usage", + "path": "api-gateway/usage" + }, + { + "title": "Configuration", + "routes": [ + { + "title": "Overview", + "path": "api-gateway/configuration" + }, + { + "title": "Gateway", + "path": "api-gateway/configuration/gateway" + }, + { + "title": "GatewayClass", + "path": "api-gateway/configuration/gatewayclass" + }, + { + "title": "GatewayClassConfig", + "path": "api-gateway/configuration/gatewayclassconfig" + }, + { + "title": "Routes", + "path": "api-gateway/configuration/routes" + }, + { + "title": "MeshService", + "path": "api-gateway/configuration/meshservice" + } + ] } ] }, @@ -1224,6 +1261,14 @@ { "title": "Consul", "routes": [ + { + "title": "v1.13.x", + "path": "release-notes/consul/v1_13_x" + }, + { + "title": "v1.12.x", + "path": "release-notes/consul/v1_12_x" + }, { "title": "v1.11.x", "path": "release-notes/consul/v1_11_x" @@ -1238,9 +1283,22 @@ } ] }, + { + "title": "Consul K8s", + "routes": [ + { + "title": "v0.47.x", + "path": "release-notes/consul-k8s/v0_47_x" + } + ] + }, { "title": "Consul API Gateway", "routes": [ + { + "title": "v0.4.x", + "path": "release-notes/consul-api-gateway/v0_4_x" + }, { "title": "v0.3.x", "path": "release-notes/consul-api-gateway/v0_3_x" @@ -1258,6 +1316,10 @@ { "title": "Consul ECS", "routes": [ + { + "title": "v0.5.x", + "path": "release-notes/consul-ecs/v0_5_x" + }, { "title": "v0.4.x", "path": "release-notes/consul-ecs/v0_4_x" diff --git a/website/public/img/what_is_service_discovery_1.png b/website/public/img/what_is_service_discovery_1.png new file mode 100644 index 0000000000..9b2ecc0b36 Binary files /dev/null and b/website/public/img/what_is_service_discovery_1.png differ diff --git a/website/public/img/what_is_service_discovery_2.png b/website/public/img/what_is_service_discovery_2.png new file mode 100644 index 0000000000..19c10e135b Binary files /dev/null and b/website/public/img/what_is_service_discovery_2.png differ diff --git a/website/public/img/what_is_service_discovery_3.png b/website/public/img/what_is_service_discovery_3.png new file mode 100644 index 0000000000..312c3e5348 Binary files /dev/null and b/website/public/img/what_is_service_discovery_3.png differ diff --git a/website/public/img/what_is_service_discovery_4.png b/website/public/img/what_is_service_discovery_4.png new file mode 100644 index 0000000000..d8a5b1ce35 Binary files /dev/null and b/website/public/img/what_is_service_discovery_4.png differ diff --git a/website/public/img/what_is_service_discovery_5.png b/website/public/img/what_is_service_discovery_5.png new file mode 100644 index 0000000000..b7704e8786 Binary files /dev/null and b/website/public/img/what_is_service_discovery_5.png differ diff --git a/website/redirects.js b/website/redirects.js index d6564754b8..d686aafd7c 100644 --- a/website/redirects.js +++ b/website/redirects.js @@ -1265,7 +1265,12 @@ module.exports = [ }, { source: '/docs/api-gateway/api-gateway-usage', - destination: '/docs/api-gateway/consul-api-gateway-install', + destination: '/docs/api-gateway/install', + permanent: true, + }, + { + source: '/docs/api-gateway/consul-api-gateway-install', + destination: '/docs/api-gateway/install', permanent: true, }, { @@ -1283,4 +1288,14 @@ module.exports = [ destination: '/docs/nia/usage/requirements', permanent: true, }, + { + source: '/docs/api-gateway/common-errors', + destination: '/docs/api-gateway/usage#error-messages', + permanent: true, + }, + { + source: '/docs/api-gateway/upgrade-specific-versions', + destination: '/docs/api-gateway/upgrades', + permanent: true, + }, ]