├── .go-version ├── .changelog ├── 411.txt ├── 890.txt ├── 384.txt ├── 501.txt ├── 529.txt ├── 696.txt ├── 800.txt ├── 808.txt ├── 839.txt ├── 857.txt ├── 125.txt ├── 533.txt ├── 609.txt ├── 883.txt ├── 521.txt ├── 759.txt ├── 803.txt ├── 142.txt ├── 685.txt ├── 71.txt ├── 22.txt ├── 47.txt ├── 738.txt ├── 15.txt ├── 175.txt ├── 235.txt ├── 64.txt ├── 66.txt ├── 74.txt ├── 917.txt ├── 97.txt ├── 585.txt ├── 106.txt ├── 565.txt ├── 807.txt ├── 844.txt ├── 131.txt ├── 687.txt ├── 373.txt ├── 153.txt ├── 164.txt ├── 184.txt ├── 242.txt ├── 115.txt ├── 698.txt ├── 434.txt ├── 573.txt ├── 130.txt ├── 188.txt ├── 608.txt ├── 65.txt ├── 578.txt ├── 104.txt ├── 140.txt ├── 460.txt ├── 496.txt ├── 602.txt ├── 357.txt ├── 702.txt ├── 239.txt ├── 81.txt ├── 310.txt ├── 172.txt ├── 323.txt ├── 219.txt ├── 238.txt ├── note.tmpl ├── 122.txt ├── 133.txt ├── 595.txt ├── 100.txt ├── 78.txt ├── 90.txt ├── 487.txt ├── 764.txt ├── 299.txt ├── 227.txt ├── 372.txt ├── 799.txt ├── 652.txt ├── 94.txt ├── 624.txt ├── 261.txt ├── 465.txt ├── 474.txt ├── 571.txt ├── 353.txt ├── 877.txt ├── 416.txt └── changelog.tmpl ├── .gitignore ├── pkg ├── consuldp │ ├── testdata │ │ ├── certs │ │ │ ├── server │ │ │ │ ├── key.pem │ │ │ │ └── cert.pem │ │ │ └── ca │ │ │ │ └── cert.pem │ │ └── TestBootstrapConfig │ │ │ ├── unix-socket-xds-server.golden │ │ │ ├── basic.golden │ │ │ ├── non-default_tenancy.golden │ │ │ ├── access-logs.golden │ │ │ └── central-telemetry-config.golden │ ├── stats.go │ ├── bootstrap.go │ ├── config_test.go │ ├── xds_test.go │ ├── xds.go │ └── mock_dataplane_service_client.go ├── dns │ ├── .mockery.yaml │ └── mocks │ │ ├── mock_UnsafeDNSServiceServer.go │ │ ├── mock_DNSServiceServer.go │ │ └── mock_DNSServiceClient.go ├── envoy │ ├── get_process_attr.go │ ├── get_process_attr_windows.go │ └── testdata │ │ └── fake-envoy ├── version │ ├── non_fips_build.go │ ├── fips_build.go │ └── version.go └── metrics-cache │ ├── metricscache_test.go │ └── metricscache.go ├── .github ├── CODEOWNERS ├── workflows │ ├── bot-auto-approve.yaml │ ├── stale-branches.yaml │ ├── backport-assistant.yml │ ├── reusable-get-go-version.yml │ ├── changelog-checker.yml │ ├── consul-dataplane-checks.yaml │ ├── security-scan.yml │ ├── reusable-conditional-skip.yml │ ├── jira-issues.yaml │ └── jira-pr.yaml └── pull_request_template.md ├── Dockerfile.dev ├── .release ├── release-metadata.hcl ├── security-scan.hcl └── ci.hcl ├── .golangci.yml ├── integration-tests ├── helpers │ ├── service.go │ ├── tls.go │ ├── pod.go │ ├── dataplane.go │ ├── auth_method.go │ ├── helpers.go │ └── server.go ├── README.md └── go.mod ├── cmd └── consul-dataplane │ ├── map_flag.go │ ├── duration.go │ ├── duration_test.go │ ├── map_flag_test.go │ ├── env.go │ ├── main_test.go │ └── flags.go ├── internal ├── mocks │ ├── pbresourcemock │ │ ├── is_watch_event__event.go │ │ ├── unsafe_resource_service_server.go │ │ ├── is_cloning_resource_service_client.go │ │ ├── resource_service__watch_list_server.go │ │ ├── server_stream.go │ │ └── resource_service__watch_list_client.go │ └── pbdnsmock │ │ ├── unsafe_dns_service_server.go │ │ ├── is_cloning_dns_service_client.go │ │ ├── dns_service_server.go │ │ └── dns_service_client.go └── bootstrap │ └── helpers_test.go ├── scan.hcl ├── go.mod ├── README.md └── _doc └── logo.svg /.go-version: -------------------------------------------------------------------------------- 1 | 1.25.3 -------------------------------------------------------------------------------- /.changelog/411.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Upgrade to use Go 1.21.7. 3 | ``` -------------------------------------------------------------------------------- /.changelog/890.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.25.3 3 | ``` -------------------------------------------------------------------------------- /.changelog/384.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Upgrade to use Go 1.21.6. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/501.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Upgrade Go to use 1.22.3. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/529.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Upgrade Go to use 1.22.4. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/696.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Upgrade to use Go 1.23.6 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/800.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | go: upgrade go version to 1.24.5 3 | ``` -------------------------------------------------------------------------------- /.changelog/808.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update Envoy version to 1.34.4 3 | ``` -------------------------------------------------------------------------------- /.changelog/839.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | go: upgrade go version to 1.25.1 3 | ``` -------------------------------------------------------------------------------- /.changelog/857.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update Envoy version to 1.35.3 3 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/consul-dataplane/main 2 | /consul-dataplane 3 | 4 | .idea 5 | .vscode -------------------------------------------------------------------------------- /.changelog/125.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update to UBI base image to 9.2. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/533.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Upgrade to support Envoy `1.29.5`. 3 | ``` -------------------------------------------------------------------------------- /.changelog/609.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to support Envoy `1.31.0`. 3 | ``` -------------------------------------------------------------------------------- /.changelog/883.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | security: Upgrade golang to 1.25.2. 3 | ``` -------------------------------------------------------------------------------- /.changelog/521.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Upgrade to support Envoy `1.29.4`. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/759.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | security: upgraded go version to 1.23.10 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/803.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | build: upgrade go-discover version to 1.1.0 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/142.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update to Envoy 1.26.2 within the Dockerfile. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/685.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update Envoy version from 1.32.1 to 1.33.0 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/71.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update Envoy to 1.25.1 within the Dockerfile. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/22.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | dns: support for dns proxying to consul for both tcp and udp 3 | ``` -------------------------------------------------------------------------------- /.changelog/47.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | support environment variables for configuration options 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/738.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | CVE: update tj-actions/changed-files to fix CVE-2025-30066 3 | ``` -------------------------------------------------------------------------------- /.changelog/15.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Container image is now "distroless" for better security 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/175.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Fix a bug where exiting envoy would inadvertently throw an error 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/235.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update to Go 1.20.7 and Envoy 1.26.4 within the Dockerfile. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/64.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update to Go 1.19.4 and Envoy 1.24.1 within the Dockerfile. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/66.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update consul-server-connection-manager to version 0.1.1. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/74.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update consul-server-connection-manager to version 0.1.2. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/917.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Add microdnf upgrade in dockerfile to include future security fixes 3 | ``` -------------------------------------------------------------------------------- /.changelog/97.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update to Go 1.20.4 and Envoy 1.26.1 within the Dockerfile. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/585.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Update `Update `github.com/hashicorp/consul/proto-public` to v0.6.2. 3 | ``` -------------------------------------------------------------------------------- /.changelog/106.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Fix a bug that threw an error when trying to use `$HOST_IP` with metrics URLs. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/565.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Removes the dependence on the v2 catalog and "resource-apis" experiment. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/807.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | go: upgrade go-discover version to 40c38fd658f0fd07ce74f2ee51b8abd3bfed01b3 3 | ``` -------------------------------------------------------------------------------- /.changelog/844.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | ipv6: Addition of IPv6 flags, which are related to consul-dataplane and envoy. 3 | ``` -------------------------------------------------------------------------------- /.changelog/131.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Reverts #104 fix that caused a downstream error for Ingress/Mesh/Terminating GWs 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/687.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Triggering graceful startup if startup-grace-period-seconds is greater than 0 3 | ``` -------------------------------------------------------------------------------- /.changelog/373.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade OpenShift container images to use `ubi9-minimal:9.3` as the base image. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/153.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update go-discover to 214571b6a5309addf3db7775f4ee8cf4d264fd5f within the Dockerfile. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/164.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Add the `-config-file` flag to support reading configuration options from a JSON file. 3 | ``` -------------------------------------------------------------------------------- /.changelog/184.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | connect: Add capture group labels from Envoy cluster FQDNs to Envoy exported metric labels 3 | ``` -------------------------------------------------------------------------------- /.changelog/242.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Make consul dataplane handle bootstrap param response for Catalog and Mesh V2 resources 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/115.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Add HTTP server with configurable port and endpoint path for initiating graceful shutdown. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/698.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgraded `x/net` to 0.34.0. This resolves [GO-2024-3333](https://pkg.go.dev/vuln/GO-2024-3333) 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/434.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade `consul-dataplane-fips` OpenShift container image to use `ubi9-minimal:9.3` as the base image. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/573.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade go version to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) 3 | ``` -------------------------------------------------------------------------------- /.changelog/130.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Catch SIGTERM and SIGINT to initate graceful shutdown in accordance with proxy lifecycle management configuration. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/188.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | In order to support Windows, write Envoy bootstrap configuration to a regular file instead of a named pipe. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/608.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade Go to use 1.22.7. This addresses CVE 3 | [CVE-2024-34155](https://nvd.nist.gov/vuln/detail/CVE-2024-34155) 4 | ``` -------------------------------------------------------------------------------- /.changelog/65.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | support Envoy admin [access logs](https://developer.hashicorp.com/consul/docs/connect/observability/access-logs). 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/578.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade envoy version to 1.29.7 to address [CVE-2024-39305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39305) 3 | ``` -------------------------------------------------------------------------------- /.changelog/104.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Fix a bug with Envoy potentially starting with incomplete configuration by not waiting enough for initial xDS configuration. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/140.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Fix a bug with Envoy potentially starting with incomplete configuration by not waiting enough for initial xDS configuration. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/460.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update `google.golang.org/protobuf` to v1.33.0 to address [CVE-2024-24786](https://nvd.nist.gov/vuln/detail/CVE-2024-24786). 3 | ``` -------------------------------------------------------------------------------- /.changelog/496.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to support Envoy `1.28.3`. This resolves CVE 3 | [CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475). 4 | ``` 5 | -------------------------------------------------------------------------------- /.changelog/602.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Update multiple dependencies. 3 | ``` 4 | 5 | ```release-note:enhancement 6 | Update ubi base image to `ubi9-minimal:9.4`. 7 | ``` -------------------------------------------------------------------------------- /.changelog/357.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Fix issue where the internal grpc-proxy would hit the max message size limit for xDS streams with a large amount of configuration. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/702.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | `Update `golang.org/x/net` to v0.37.0.` 3 | `Update `golang.org/x/sys` to v0.31.0.` 4 | `Update `golang.org/x/text` to v0.23.0.` 5 | ``` -------------------------------------------------------------------------------- /.changelog/239.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Add graceful_startup endpoint and postStart hook in order to guarantee that dataplane starts up before application container. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/81.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade golang/x/net to 0.7.0 3 | This resolves vulnerability [CVE-2022-41723](https://github.com/golang/go/issues/57855) in `x/net` 4 | ``` 5 | -------------------------------------------------------------------------------- /.changelog/310.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Update Envoy version to 1.27.2 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/172.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | dns: queries proxied by consul-dataplane now assume the same namespace/partition/ACL token as the service registered to the dataplane instance. 3 | ``` -------------------------------------------------------------------------------- /.changelog/323.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade `google.golang.org/grpc` to 1.56.3. 3 | This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). 4 | ``` 5 | -------------------------------------------------------------------------------- /.changelog/219.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. 3 | This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). 4 | ``` 5 | -------------------------------------------------------------------------------- /.changelog/238.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | Fix a bug where container user was unable to bind to privileged ports (< 1024). The consul-dataplane container now requires the NET_BIND_SERVICE capability. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/note.tmpl: -------------------------------------------------------------------------------- 1 | {{- define "note" -}} 2 | {{.Body}}{{if not (stringHasPrefix .Issue "_")}} [[GH-{{- .Issue -}}](https://github.com/hashicorp/consul-dataplane/pull/{{- .Issue -}})]{{end}} 3 | {{- end -}} 4 | -------------------------------------------------------------------------------- /.changelog/122.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update bootstrap configuration to rename envoy_hcp_metrics_bind_socket_dir to envoy_telemetry_collector_bind_socket_dir to remove HCP naming references. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/133.txt: -------------------------------------------------------------------------------- 1 | ```release-note:bug 2 | * Add support for envoy-extra-args. Fixes [Envoy extra-args annotation crashing consul-dataplane container](https://github.com/hashicorp/consul-k8s/issues/1846). 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/595.txt: -------------------------------------------------------------------------------- 1 | ```release-note:enhancement 2 | Update `github.com/hashicorp/consul-server-connection-manager` to v0.1.9. 3 | ``` 4 | 5 | ```release-note:enhancement 6 | Update `github.com/hashicorp/go-hclog` to v1.5.0. 7 | ``` -------------------------------------------------------------------------------- /.changelog/100.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Add -shutdown-drain-listeners, -shutdown-grace-period, -graceful-shutdown-path and -graceful-port flags to configure proxy lifecycle management settings for the Envoy container. 3 | ``` 4 | -------------------------------------------------------------------------------- /.changelog/78.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.1. 3 | This resolves vulnerabilities [CVE-2022-41724](https://go.dev/issue/58001) in `crypto/tls` and [CVE-2022-41723](https://go.dev/issue/57855) in `net/http`. 4 | ``` 5 | -------------------------------------------------------------------------------- /.changelog/90.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Add envoy_hcp_metrics_bind_socket_dir flag to configure a directory where a unix socket is created. 3 | This enables Envoy metrics collection, which will be forwarded to a HCP metrics collector. 4 | ``` -------------------------------------------------------------------------------- /.changelog/487.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade Go to use 1.21.10. This addresses CVEs 3 | [CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and 4 | [CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) 5 | ``` 6 | -------------------------------------------------------------------------------- /.changelog/764.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | cve: upgrade golang.org/x/net package to address CVE: 3 | - [GO-2025-3595](https://pkg.go.dev/vuln/GO-2025-3595) 4 | - [GHSA-vvgc-356p-c3xw](https://osv.dev/vulnerability/GHSA-vvgc-356p-c3xw) 5 | ``` 6 | -------------------------------------------------------------------------------- /.changelog/299.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.10 and `x/net` 0.17.0. 3 | This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) 4 | / [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). 5 | ``` 6 | -------------------------------------------------------------------------------- /.changelog/227.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.7 and `x/net` 0.13.0. 3 | This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) 4 | and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). 5 | ``` 6 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/certs/server/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIKPQSWaeHP2KM0YkypRSfAf2wVNkqVXDvY1qVjMEi3a+oAoGCCqGSM49 3 | AwEHoUQDQgAEahb1b2nyIkoN3iROoXobbov7QULmDcNmqXqEUuRrXPtjhJfPY3fM 4 | Gdk5xNm5DkVq9lCsaRQrXMBhwa8r88rXnw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /.changelog/372.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Propagate merged metrics request query params to Envoy to enable metrics filtering. 3 | ``` 4 | ```release-note:bug 5 | Exclude Prometheus scrape path query params from Envoy path match s.t. it does not break merged metrics request routing. 6 | ``` 7 | -------------------------------------------------------------------------------- /pkg/dns/.mockery.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | with-expecter: true 5 | recursive: true 6 | include-regex: ".*" 7 | dir: "mocks/" 8 | outpkg: "mocks" 9 | mockname: "{{.InterfaceName}}" 10 | packages: 11 | github.com/hashicorp/consul/proto-public/pbdns: 12 | -------------------------------------------------------------------------------- /.changelog/799.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Implemented a subcommand "check-proxy-health" which checks whether locally running envoy proxy is ready or not by calling http endpoint /ready on evoy admin URL. This is implemented for kubelet startup and liveness probes when consul-dataplane is registered as sidecar container. 3 | ``` 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/consul-selfmanage-maintainers 2 | 3 | # release configuration 4 | /.release/ @hashicorp/consul-selfmanage-maintainers @hashicorp/team-selfmanaged-releng 5 | /.github/workflows/build.yml @hashicorp/consul-selfmanage-maintainers @hashicorp/team-selfmanaged-releng 6 | -------------------------------------------------------------------------------- /pkg/envoy/get_process_attr.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build !windows 5 | // +build !windows 6 | 7 | package envoy 8 | 9 | import "syscall" 10 | 11 | func getProcessAttr() *syscall.SysProcAttr { 12 | return &syscall.SysProcAttr{ 13 | Setpgid: true, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.changelog/652.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | The Docker image now includes both privileged and non-privileged binaries for both consul-dataplane and envoy. You can use `privileged-consul-dataplane` as the container command to run with the necessary capabilities for binding to privileged ports, or use the default binaries for standard use cases. 3 | ``` -------------------------------------------------------------------------------- /pkg/version/non_fips_build.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build !fips 5 | 6 | package version 7 | 8 | // IsFIPS returns true if consul-dataplane is operating in FIPS-140-2 mode. 9 | func IsFIPS() bool { 10 | return false 11 | } 12 | 13 | func GetFIPSInfo() string { 14 | return "" 15 | } 16 | -------------------------------------------------------------------------------- /pkg/envoy/get_process_attr_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build windows 5 | // +build windows 6 | 7 | package envoy 8 | 9 | import "syscall" 10 | 11 | func getProcessAttr() *syscall.SysProcAttr { 12 | return &syscall.SysProcAttr{ 13 | CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | # DANGER: this dockerfile is experimental and could be modified/removed at any time. 2 | # A simple image for testing changes to consul-dataplane 3 | # 4 | # Meant to be used with the following make target 5 | # DEV_IMAGE= make skaffold 6 | 7 | FROM hashicorp/consul-dataplane as cache 8 | ARG TARGETARCH 9 | 10 | COPY dist/linux/${TARGETARCH}/consul-dataplane /usr/local/bin/ 11 | -------------------------------------------------------------------------------- /pkg/consuldp/stats.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package consuldp 5 | 6 | import "github.com/armon/go-metrics/prometheus" 7 | 8 | var gauges = []prometheus.GaugeDefinition{ 9 | { 10 | Name: []string{"envoy_connected"}, 11 | Help: "This will either be 0 or 1 depending on whether Envoy is currently running and connected to the local xDS listeners.", 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.changelog/94.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.4. 3 | This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), 4 | [CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), 5 | [CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and 6 | [CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). 7 | ``` 8 | -------------------------------------------------------------------------------- /.github/workflows/bot-auto-approve.yaml: -------------------------------------------------------------------------------- 1 | name: Bot Auto Approve 2 | 3 | on: pull_request_target 4 | 5 | jobs: 6 | auto-approve: 7 | runs-on: ubuntu-latest 8 | if: github.actor == 'hc-github-team-consul-core' 9 | steps: 10 | - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0 11 | with: 12 | review-message: "Auto approved Consul Bot automated PR" 13 | github-token: ${{ secrets.MERGE_APPROVE_TOKEN }} 14 | -------------------------------------------------------------------------------- /.changelog/624.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade envoy version to 1.31.2 to address [CVE-2024-45807](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-45807),[CVE-2024-45808](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-45808),[CVE-2024-45806](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-45806),[CVE-2024-45809](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-45809) and [CVE-2024-45810](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-45810) 3 | ``` -------------------------------------------------------------------------------- /.changelog/261.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.8. This resolves CVEs 3 | [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), 4 | [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), 5 | [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), 6 | [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and 7 | [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) 8 | ``` 9 | -------------------------------------------------------------------------------- /.changelog/465.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go `1.21.8`. This resolves CVEs 3 | [CVE-2024-24783](https://nvd.nist.gov/vuln/detail/CVE-2024-24783) (`crypto/x509`). 4 | [CVE-2023-45290](https://nvd.nist.gov/vuln/detail/CVE-2023-45290) (`net/http`). 5 | [CVE-2023-45289](https://nvd.nist.gov/vuln/detail/CVE-2023-45289) (`net/http`, `net/http/cookiejar`). 6 | [CVE-2024-24785](https://nvd.nist.gov/vuln/detail/CVE-2024-24785) (`html/template`). 7 | [CVE-2024-24784](https://nvd.nist.gov/vuln/detail/CVE-2024-24784) (`net/mail`). 8 | ``` 9 | -------------------------------------------------------------------------------- /.changelog/474.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go `1.21.9`. This resolves CVE 3 | [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`http2`). 4 | ``` 5 | 6 | ```release-note:security 7 | Upgrade to support Envoy `1.28.2`. This resolves CVE 8 | [CVE-2024-27919](https://nvd.nist.gov/vuln/detail/CVE-2024-27919) (`http2`). 9 | ``` 10 | 11 | ```release-note:security 12 | Upgrade to use golang.org/x/net `v0.24.0`. This resolves CVE 13 | [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`x/net`). 14 | ``` -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## PCI review checklist 2 | 3 | 4 | 5 | - [ ] I have documented a clear reason for, and description of, the change I am making. 6 | 7 | - [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request. 8 | 9 | - [ ] If applicable, I've documented the impact of any changes to security controls. 10 | 11 | Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc. 12 | -------------------------------------------------------------------------------- /.changelog/571.txt: -------------------------------------------------------------------------------- 1 | ```release-note:feature 2 | Added the ability to set the `-mode` flag. Options available are `sidecar` and `dns-proxy`. The system defaults to `sidecar`. 3 | When set to `sidecar`: 4 | - DNS Server, xDS Server, and Envoy are enabled. 5 | - The system validates that `-consul-dns-bind-addr` and equivalent environment variable must be set to the loopback address. 6 | When set to `dns-proxy`: 7 | - Only DNS Server is enabled. xDS Server and Envoy are disabled. 8 | - `consul-dns-bind-addr` and equivalent environment variable can be set to other values besides the loopback address. 9 | ``` -------------------------------------------------------------------------------- /.release/release-metadata.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | url_docker_registry_dockerhub = "https://hub.docker.com/r/hashicorp/consul-dataplane" 5 | url_docker_registry_ecr = "https://gallery.ecr.aws/hashicorp/consul-dataplane" 6 | url_source_repository = "https://github.com/hashicorp/consul-dataplane" 7 | url_license = "https://github.com/hashicorp/consul-dataplane/blob/main/LICENSE" 8 | url_project_website = "https://www.consul.io" 9 | url_release_notes = "https://www.consul.io/docs/release-notes" 10 | -------------------------------------------------------------------------------- /.changelog/353.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Upgrade to use Go 1.20.12. This resolves CVEs 3 | [CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) 4 | [CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) 5 | [CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead 6 | [CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git 7 | ``` -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | exclusions: 4 | generated: lax 5 | presets: 6 | - comments 7 | - common-false-positives 8 | - legacy 9 | - std-error-handling 10 | rules: 11 | - linters: 12 | - staticcheck 13 | path: (pkg/consuldp/bootstrap.go) 14 | text: 'SA1019:' 15 | - linters: 16 | - staticcheck 17 | text: 'ST1005:' 18 | 19 | paths: 20 | - third_party$ 21 | - builtin$ 22 | - examples$ 23 | formatters: 24 | exclusions: 25 | generated: lax 26 | paths: 27 | - third_party$ 28 | - builtin$ 29 | - examples$ 30 | -------------------------------------------------------------------------------- /.changelog/877.txt: -------------------------------------------------------------------------------- 1 | ```release-note:security 2 | Updated integration-tests dependencies and added CVE suppressions. Fixed docker/distribution compatibility issue by pinning to v2.8.2+incompatible to resolve reference.SplitHostname undefined error. This addresses CVEs 3 | 4 | [CVE-2024-28180] (https://github.com/advisories/GHSA-c5q2-7r4c-mv6g) (`go-jose v2`), 5 | [GHSA-c5q2-7r4c-mv6g] (https://github.com/advisories/GHSA-c5q2-7r4c-mv6g) (`go-jose v2`), and 6 | [GO-2024-2631] (https://pkg.go.dev/vuln/GO-2024-2631) (`go-jose v2`) 7 | 8 | in integration test dependencies. These vulnerabilities affect only test environments and do not impact production consul-dataplane deployments. 9 | ``` -------------------------------------------------------------------------------- /.changelog/416.txt: -------------------------------------------------------------------------------- 1 | ```release-note:improvement 2 | Update Envoy version from 1.27 to 1.28 3 | ``` 4 | ```release-note:security 5 | Update Envoy version to 1.28.1 to address [CVE-2024-23324](https://github.com/envoyproxy/envoy/security/advisories/GHSA-gq3v-vvhj-96j6), [CVE-2024-23325](https://github.com/envoyproxy/envoy/security/advisories/GHSA-5m7c-mrwr-pm26), [CVE-2024-23322](https://github.com/envoyproxy/envoy/security/advisories/GHSA-6p83-mfmh-qv38), [CVE-2024-23323](https://github.com/envoyproxy/envoy/security/advisories/GHSA-x278-4w4x-r7ch), [CVE-2024-23327](https://github.com/envoyproxy/envoy/security/advisories/GHSA-4h5x-x9vh-m29j), and [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) 6 | ``` 7 | -------------------------------------------------------------------------------- /integration-tests/helpers/service.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | const echoServiceImage = "hashicorp/http-echo:0.2.3" 12 | 13 | // RunService runs an HTTP echo server in the given pod's network, running on 14 | // port :8080. 15 | func RunService(t *testing.T, suite *Suite, pod *Pod, serviceName string) *Container { 16 | t.Helper() 17 | 18 | return suite.RunContainer(t, serviceName, true, ContainerRequest{ 19 | NetworkMode: pod.Network(), 20 | Image: echoServiceImage, 21 | Cmd: []string{"-listen", ":8080", "-text", fmt.Sprintf("service_metric{service_name=%q} 1", serviceName)}, 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/stale-branches.yaml: -------------------------------------------------------------------------------- 1 | name: Stale Branches 2 | on: 3 | schedule: 4 | - cron: "0 6 * * 1-5" 5 | 6 | permissions: 7 | issues: write 8 | contents: write 9 | jobs: 10 | stale_branches: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Stale Branches 14 | uses: crs-k/stale-branches@c6e09a3de1046d68b21eccdca23321d0ec277964 # v7.0.0 15 | with: 16 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 17 | days-before-stale: 120 18 | days-before-delete: 180 19 | comment-updates: false 20 | tag-committer: false 21 | stale-branch-label: "stale branch 🗑️" 22 | compare-branches: "info" 23 | ignore-issue-interaction: true 24 | pr-check: true 25 | -------------------------------------------------------------------------------- /pkg/version/fips_build.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build fips 5 | 6 | package version 7 | 8 | // This validates during compilation that we are being built with a FIPS enabled go toolchain 9 | import ( 10 | _ "crypto/tls/fipsonly" 11 | "runtime" 12 | "strings" 13 | ) 14 | 15 | // IsFIPS returns true if consul-dataplane is operating in FIPS-140-2 mode. 16 | func IsFIPS() bool { 17 | return true 18 | } 19 | 20 | func GetFIPSInfo() string { 21 | str := "Enabled" 22 | // Try to get the crypto module name 23 | gover := strings.Split(runtime.Version(), "X:") 24 | if len(gover) >= 2 { 25 | gover_last := gover[len(gover)-1] 26 | // Able to find crypto module name; add that to status string. 27 | str = "FIPS 140-2 Enabled, crypto module " + gover_last 28 | } 29 | return str 30 | } 31 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/map_flag.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | var _ flag.Value = (*FlagMapValue)(nil) 13 | 14 | // FlagMapValue is a flag implementation used to provide key=value semantics 15 | // multiple times. 16 | type FlagMapValue map[string]string 17 | 18 | func (h *FlagMapValue) String() string { 19 | return fmt.Sprintf("%v", *h) 20 | } 21 | 22 | func (h *FlagMapValue) Set(value string) error { 23 | idx := strings.Index(value, "=") 24 | if idx == -1 { 25 | return fmt.Errorf("Missing \"=\" value in argument: %s", value) 26 | } 27 | 28 | key, value := value[0:idx], value[idx+1:] 29 | 30 | if *h == nil { 31 | *h = make(map[string]string) 32 | } 33 | 34 | headers := *h 35 | headers[key] = value 36 | *h = headers 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/duration.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | // Duration wraps the time.duration field to support 13 | // unmarshalling JSON values to the time.duration fields 14 | // in destination structs 15 | type Duration struct { 16 | Duration time.Duration 17 | } 18 | 19 | func (d *Duration) UnmarshalJSON(b []byte) error { 20 | var unmarshalledJson interface{} 21 | 22 | err := json.Unmarshal(b, &unmarshalledJson) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | switch value := unmarshalledJson.(type) { 28 | case float64: 29 | d.Duration = time.Duration(value) 30 | case string: 31 | d.Duration, err = time.ParseDuration(value) 32 | if err != nil { 33 | return err 34 | } 35 | default: 36 | return fmt.Errorf("invalid duration: %#v", unmarshalledJson) 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/backport-assistant.yml: -------------------------------------------------------------------------------- 1 | # This action creates downstream PRs for PRs with backport labels defined. 2 | # See docs here: https://github.com/hashicorp/backport-assistant 3 | 4 | name: Backport Assistant Runner 5 | 6 | on: 7 | pull_request_target: 8 | types: 9 | - closed 10 | - labeled 11 | branches: 12 | - main 13 | - 'release/*.*.x' 14 | 15 | jobs: 16 | backport: 17 | if: github.event.pull_request.merged 18 | runs-on: ubuntu-latest 19 | container: hashicorpdev/backport-assistant:v0.5.8 20 | steps: 21 | - name: Run Backport Assistant for release branches 22 | run: | 23 | backport-assistant backport -merge-method=squash -gh-automerge 24 | env: 25 | BACKPORT_LABEL_REGEXP: "backport/(?P\\d+\\.\\d+)" 26 | BACKPORT_TARGET_TEMPLATE: "release/{{.target}}.x" 27 | GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} 28 | BACKPORT_MERGE_COMMIT: true 29 | -------------------------------------------------------------------------------- /internal/mocks/pbresourcemock/is_watch_event__event.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbresourcemock 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // isWatchEvent_Event is an autogenerated mock type for the isWatchEvent_Event type 8 | type isWatchEvent_Event struct { 9 | mock.Mock 10 | } 11 | 12 | // isWatchEvent_Event provides a mock function with given fields: 13 | func (_m *isWatchEvent_Event) isWatchEvent_Event() { 14 | _m.Called() 15 | } 16 | 17 | // newIsWatchEvent_Event creates a new instance of isWatchEvent_Event. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 18 | // The first argument is typically a *testing.T value. 19 | func newIsWatchEvent_Event(t interface { 20 | mock.TestingT 21 | Cleanup(func()) 22 | }) *isWatchEvent_Event { 23 | mock := &isWatchEvent_Event{} 24 | mock.Mock.Test(t) 25 | 26 | t.Cleanup(func() { mock.AssertExpectations(t) }) 27 | 28 | return mock 29 | } 30 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/certs/server/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICmzCCAkKgAwIBAgIQZsgOGinyQvnQv/4P3vqT1DAKBggqhkjOPQQDAjCBuTEL 3 | MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2Nv 4 | MRowGAYDVQQJExExMDEgU2Vjb25kIFN0cmVldDEOMAwGA1UEERMFOTQxMDUxFzAV 5 | BgNVBAoTDkhhc2hpQ29ycCBJbmMuMUAwPgYDVQQDEzdDb25zdWwgQWdlbnQgQ0Eg 6 | MzM2NzYxMDUxNzA0NzA2MTY0NjkxMjMzMDAzNjQxMDU0Mzc0NDEwMB4XDTIyMDkw 7 | ODA5MDExOFoXDTIzMDkwODA5MDExOFowHDEaMBgGA1UEAxMRc2VydmVyLmRjMS5j 8 | b25zdWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqFvVvafIiSg3eJE6hehtu 9 | i/tBQuYNw2apeoRS5Gtc+2OEl89jd8wZ2TnE2bkORWr2UKxpFCtcwGHBryvzytef 10 | o4HHMIHEMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB 11 | BQUHAwIwDAYDVR0TAQH/BAIwADApBgNVHQ4EIgQguoJeLJBVuyk+PuYRLvzT9zoE 12 | TWV6zYx8XZzOxRNf+70wKwYDVR0jBCQwIoAg6t7SJkVc4rvY8WD6a79DwQk7UDLq 13 | wqjVZX0/dnG8tFEwLQYDVR0RBCYwJIIRc2VydmVyLmRjMS5jb25zdWyCCWxvY2Fs 14 | aG9zdIcEfwAAATAKBggqhkjOPQQDAgNHADBEAiBGSWVu/SavnS7+febUfm48tzbu 15 | wPOq2cnJHrIx54mb1gIgJLtNcXPSV7sJzCh16E/l5fF2iiYBRjHnMDYDTHeZ0Bk= 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /internal/mocks/pbdnsmock/unsafe_dns_service_server.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbdnsmock 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // UnsafeDNSServiceServer is an autogenerated mock type for the UnsafeDNSServiceServer type 8 | type UnsafeDNSServiceServer struct { 9 | mock.Mock 10 | } 11 | 12 | // mustEmbedUnimplementedDNSServiceServer provides a mock function with given fields: 13 | func (_m *UnsafeDNSServiceServer) mustEmbedUnimplementedDNSServiceServer() { 14 | _m.Called() 15 | } 16 | 17 | // NewUnsafeDNSServiceServer creates a new instance of UnsafeDNSServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 18 | // The first argument is typically a *testing.T value. 19 | func NewUnsafeDNSServiceServer(t interface { 20 | mock.TestingT 21 | Cleanup(func()) 22 | }) *UnsafeDNSServiceServer { 23 | mock := &UnsafeDNSServiceServer{} 24 | mock.Mock.Test(t) 25 | 26 | t.Cleanup(func() { mock.AssertExpectations(t) }) 27 | 28 | return mock 29 | } 30 | -------------------------------------------------------------------------------- /scan.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # Configuration for security scanner. 5 | # Run on PRs and pushes to `main` and `release/**` branches. 6 | # See .github/workflows/security-scan.yml for CI config. 7 | 8 | # To run manually, install scanner and then run `scan repository .` 9 | 10 | # Scan results are triaged via the GitHub Security tab for this repo. 11 | # See `security-scanner` docs for more information on how to add `triage` config 12 | # for specific results or to exclude paths. 13 | 14 | # .release/security-scan.hcl controls scanner config for release artifacts, which 15 | # unlike the scans configured here, will block releases in CRT. 16 | 17 | repository { 18 | go_modules = true 19 | npm = true 20 | osv = true 21 | 22 | secrets { 23 | all = true 24 | } 25 | 26 | triage { 27 | suppress { 28 | paths = [ 29 | # Ignore test and local tool modules, which are not included in published 30 | # artifacts. 31 | "integration-tests/*", 32 | ] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/bootstrap/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package bootstrap 5 | 6 | import ( 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | // testSetAndResetEnv sets the env vars passed as KEY=value strings in the 15 | // current ENV and returns a func() that will undo it's work at the end of the 16 | // test for use with defer. 17 | func testSetAndResetEnv(t *testing.T, env []string) func() { 18 | old := make(map[string]*string) 19 | for _, e := range env { 20 | pair := strings.SplitN(e, "=", 2) 21 | current := os.Getenv(pair[0]) 22 | if current != "" { 23 | old[pair[0]] = ¤t 24 | } else { 25 | // save it as a nil so we know to remove again 26 | old[pair[0]] = nil 27 | } 28 | require.NoError(t, os.Setenv(pair[0], pair[1])) 29 | } 30 | // Return a func that will reset to old values 31 | return func() { 32 | for k, v := range old { 33 | if v == nil { 34 | os.Unsetenv(k) 35 | } else { 36 | os.Setenv(k, *v) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/mocks/pbresourcemock/unsafe_resource_service_server.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbresourcemock 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // UnsafeResourceServiceServer is an autogenerated mock type for the UnsafeResourceServiceServer type 8 | type UnsafeResourceServiceServer struct { 9 | mock.Mock 10 | } 11 | 12 | // mustEmbedUnimplementedResourceServiceServer provides a mock function with given fields: 13 | func (_m *UnsafeResourceServiceServer) mustEmbedUnimplementedResourceServiceServer() { 14 | _m.Called() 15 | } 16 | 17 | // NewUnsafeResourceServiceServer creates a new instance of UnsafeResourceServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 18 | // The first argument is typically a *testing.T value. 19 | func NewUnsafeResourceServiceServer(t interface { 20 | mock.TestingT 21 | Cleanup(func()) 22 | }) *UnsafeResourceServiceServer { 23 | mock := &UnsafeResourceServiceServer{} 24 | mock.Mock.Test(t) 25 | 26 | t.Cleanup(func() { mock.AssertExpectations(t) }) 27 | 28 | return mock 29 | } 30 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/certs/ca/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7TCCApSgAwIBAgIRAP1Z0cF0jKuFLwHfe+vXKgowCgYIKoZIzj0EAwIwgbkx 3 | CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj 4 | bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw 5 | FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB 6 | IDMzNjc2MTA1MTcwNDcwNjE2NDY5MTIzMzAwMzY0MTA1NDM3NDQxMDAeFw0yMjA5 7 | MDgwOTAxMDZaFw0yNzA5MDcwOTAxMDZaMIG5MQswCQYDVQQGEwJVUzELMAkGA1UE 8 | CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGjAYBgNVBAkTETEwMSBTZWNv 9 | bmQgU3RyZWV0MQ4wDAYDVQQREwU5NDEwNTEXMBUGA1UEChMOSGFzaGlDb3JwIElu 10 | Yy4xQDA+BgNVBAMTN0NvbnN1bCBBZ2VudCBDQSAzMzY3NjEwNTE3MDQ3MDYxNjQ2 11 | OTEyMzMwMDM2NDEwNTQzNzQ0MTAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATo 12 | IgE4sG18GbDA21ATHGa5CAlcej0IGfKFmPLdhmYhZb0sKt+kB+/bsbpTiV2yrmBp 13 | AJRSjx1oIk+ZlIOqreOMo3sweTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw 14 | AwEB/zApBgNVHQ4EIgQg6t7SJkVc4rvY8WD6a79DwQk7UDLqwqjVZX0/dnG8tFEw 15 | KwYDVR0jBCQwIoAg6t7SJkVc4rvY8WD6a79DwQk7UDLqwqjVZX0/dnG8tFEwCgYI 16 | KoZIzj0EAwIDRwAwRAIgQQ0gteEkbhvhVIJg9/JXvNyGGl7bpn7qm3A6iGe08FYC 17 | IHkVVKnKmsUgbXzwq1+wQ2q9kQBtOdtmB0nxNji94PpH 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /integration-tests/README.md: -------------------------------------------------------------------------------- 1 | # Integration Tests 2 | 3 | These tests validate that Consul Dataplane correctly integrates with Envoy and 4 | the Consul server to implement service mesh features. 5 | 6 | They are intentionally not exhaustive. For full coverage of Consul's mesh 7 | capabilities, we rely on the existing [suite of integration tests](https://github.com/hashicorp/consul/tree/main/test/integration/connect/envoy) 8 | that do a good job of ensuring servers generate correct Envoy configuration in 9 | different scenarios, but cannot currently accommodate Consul Dataplane without 10 | significant structural changes. 11 | 12 | ## Running the tests 13 | 14 | They're run automatically as part of our [build workflow](https://github.com/hashicorp/consul-dataplane/actions/workflows/build.yml). 15 | 16 | If you have Docker, you can also run them locally with: 17 | 18 | ```bash 19 | # From the project root 20 | $ make integration-tests 21 | 22 | # Additional options 23 | $ make integration-tests \ 24 | INTEGRATION_TESTS_OUTPUT_DIR=/path/to/output \ 25 | INTEGRATION_TESTS_SERVER_IMAGE=hashicorp/consul:some-version \ 26 | INTEGRATION_TESTS_DATAPLANE_IMAGE=hashicorp/consul-dataplane:some-version 27 | ``` 28 | -------------------------------------------------------------------------------- /pkg/envoy/testdata/fake-envoy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script pretends to be Envoy in unit tests. It captures the flags and the 4 | # bootstrap config specified via `--config-yaml`, and writes them to the file at 5 | # `--test-output` (which is read and checked in the test). 6 | # It then sleeps for 10 minutes to check we're correctly killing the process. 7 | 8 | set -e 9 | 10 | config_yaml="" 11 | test_output="" 12 | 13 | prev_arg="" 14 | for arg in "$@"; do 15 | case "$prev_arg" in 16 | --config-yaml) 17 | config_yaml="$arg" 18 | ;; 19 | --test-output) 20 | test_output="$arg" 21 | ;; 22 | esac 23 | prev_arg="$arg" 24 | done 25 | 26 | if [ -z "$config_yaml" ]; then 27 | >&2 echo "--config-yaml is required" 28 | exit 1 29 | fi 30 | 31 | if [ -z "$test_output" ]; then 32 | >&2 echo "--test-output is required" 33 | exit 1 34 | fi 35 | 36 | # Base64 encode the data to avoid having to escape it in the JSON output. 37 | args=$(echo "$@" | base64 | tr -d \\n) 38 | config_data=$(echo -n "$config_yaml" | base64 | tr -d \\n) 39 | 40 | cat < "$test_output" 41 | { 42 | "Args": "$args", 43 | "ConfigData": "$config_data" 44 | } 45 | EOF 46 | 47 | sleep 600 48 | -------------------------------------------------------------------------------- /.changelog/changelog.tmpl: -------------------------------------------------------------------------------- 1 | {{- if index .NotesByType "breaking-change" -}} 2 | BREAKING CHANGES: 3 | 4 | {{range index .NotesByType "breaking-change" -}} 5 | * {{ template "note" . }} 6 | {{ end -}} 7 | {{- end -}} 8 | 9 | {{- if .NotesByType.security }} 10 | SECURITY: 11 | 12 | {{range .NotesByType.security -}} 13 | * {{ template "note" . }} 14 | {{ end -}} 15 | {{- end -}} 16 | 17 | {{- if .NotesByType.feature }} 18 | FEATURES: 19 | 20 | {{range .NotesByType.feature -}} 21 | * {{ template "note" . }} 22 | {{ end -}} 23 | {{- end -}} 24 | 25 | {{- $improvements := combineTypes .NotesByType.improvement .NotesByType.enhancement -}} 26 | {{- if $improvements }} 27 | IMPROVEMENTS: 28 | 29 | {{range $improvements | sort -}} 30 | * {{ template "note" . }} 31 | {{ end -}} 32 | {{- end -}} 33 | 34 | {{- if .NotesByType.deprecation }} 35 | DEPRECATIONS: 36 | 37 | {{range .NotesByType.deprecation -}} 38 | * {{ template "note" . }} 39 | {{ end -}} 40 | {{- end -}} 41 | 42 | {{- if .NotesByType.bug }} 43 | BUG FIXES: 44 | 45 | {{range .NotesByType.bug -}} 46 | * {{ template "note" . }} 47 | {{ end -}} 48 | {{- end -}} 49 | 50 | {{- if .NotesByType.note }} 51 | NOTES: 52 | 53 | {{range .NotesByType.note -}} 54 | * {{ template "note" . }} 55 | {{ end -}} 56 | {{- end -}} 57 | 58 | -------------------------------------------------------------------------------- /integration-tests/helpers/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | "github.com/testcontainers/testcontainers-go" 12 | ) 13 | 14 | // GenerateServerTLS generates CA and server certificates/key material and 15 | // copies them to the suite volume to be used by the server and dataplanes. 16 | func GenerateServerTLS(t *testing.T, suite *Suite) { 17 | t.Helper() 18 | 19 | ctx := suite.Context(t) 20 | volume := suite.Volume(t) 21 | 22 | container := suite.RunContainer(t, "generate-tls-certs", false, ContainerRequest{ 23 | Image: suite.opts.ServerImage, 24 | Cmd: []string{"sleep", "infinity"}, 25 | Mounts: []testcontainers.ContainerMount{ 26 | testcontainers.VolumeMount(volume.Name, "/data"), 27 | }, 28 | }) 29 | 30 | cmds := []string{ 31 | "consul tls ca create", 32 | "cp consul-agent-ca.pem /data/ca-cert.pem", 33 | "consul tls cert create -server", 34 | "cp dc1-server-consul-0.pem /data/server-cert.pem", 35 | "cp dc1-server-consul-0-key.pem /data/server-key.pem", 36 | "chmod 444 /data/server-key.pem", 37 | } 38 | for _, cmd := range cmds { 39 | _, _, err := container.Exec(ctx, strings.Split(cmd, " ")) 40 | require.NoError(t, err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/mocks/pbdnsmock/is_cloning_dns_service_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbdnsmock 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // IsCloningDNSServiceClient is an autogenerated mock type for the IsCloningDNSServiceClient type 8 | type IsCloningDNSServiceClient struct { 9 | mock.Mock 10 | } 11 | 12 | // IsCloningDNSServiceClient provides a mock function with given fields: 13 | func (_m *IsCloningDNSServiceClient) IsCloningDNSServiceClient() bool { 14 | ret := _m.Called() 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for IsCloningDNSServiceClient") 18 | } 19 | 20 | var r0 bool 21 | if rf, ok := ret.Get(0).(func() bool); ok { 22 | r0 = rf() 23 | } else { 24 | r0 = ret.Get(0).(bool) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // NewIsCloningDNSServiceClient creates a new instance of IsCloningDNSServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 31 | // The first argument is typically a *testing.T value. 32 | func NewIsCloningDNSServiceClient(t interface { 33 | mock.TestingT 34 | Cleanup(func()) 35 | }) *IsCloningDNSServiceClient { 36 | mock := &IsCloningDNSServiceClient{} 37 | mock.Mock.Test(t) 38 | 39 | t.Cleanup(func() { mock.AssertExpectations(t) }) 40 | 41 | return mock 42 | } 43 | -------------------------------------------------------------------------------- /internal/mocks/pbresourcemock/is_cloning_resource_service_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbresourcemock 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // IsCloningResourceServiceClient is an autogenerated mock type for the IsCloningResourceServiceClient type 8 | type IsCloningResourceServiceClient struct { 9 | mock.Mock 10 | } 11 | 12 | // IsCloningResourceServiceClient provides a mock function with given fields: 13 | func (_m *IsCloningResourceServiceClient) IsCloningResourceServiceClient() bool { 14 | ret := _m.Called() 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for IsCloningResourceServiceClient") 18 | } 19 | 20 | var r0 bool 21 | if rf, ok := ret.Get(0).(func() bool); ok { 22 | r0 = rf() 23 | } else { 24 | r0 = ret.Get(0).(bool) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // NewIsCloningResourceServiceClient creates a new instance of IsCloningResourceServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 31 | // The first argument is typically a *testing.T value. 32 | func NewIsCloningResourceServiceClient(t interface { 33 | mock.TestingT 34 | Cleanup(func()) 35 | }) *IsCloningResourceServiceClient { 36 | mock := &IsCloningResourceServiceClient{} 37 | mock.Mock.Test(t) 38 | 39 | t.Cleanup(func() { mock.AssertExpectations(t) }) 40 | 41 | return mock 42 | } 43 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package version 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | var ( 12 | // The git commit that was compiled. These will be filled in by the 13 | // compiler. 14 | GitCommit string 15 | 16 | // The main version number that is being run at the moment. 17 | // 18 | // Version must conform to the format expected by github.com/hashicorp/go-version 19 | // for tests to work. 20 | Version = "1.10.0" 21 | 22 | // A pre-release marker for the version. If this is "" (empty string) 23 | // then it means that it is a final release. Otherwise, this is a pre-release 24 | // such as "dev" (in development), "beta", "rc1", etc. 25 | VersionPrerelease = "dev" 26 | ) 27 | 28 | // GetHumanVersion composes the parts of the version in a way that's suitable 29 | // for displaying to humans. 30 | func GetHumanVersion() string { 31 | version := Version 32 | release := VersionPrerelease 33 | 34 | if release != "" { 35 | if !strings.HasSuffix(version, "-"+release) { 36 | // if we tagged a prerelease version then the release is in the version already 37 | version += fmt.Sprintf("-%s", release) 38 | } 39 | } 40 | 41 | if IsFIPS() { 42 | version = fmt.Sprintf("%s+fips1402", version) 43 | } 44 | 45 | // Strip off any single quotes added by the git information. 46 | return strings.ReplaceAll(version, "'", "") 47 | } 48 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/duration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | type DurationTestInput struct { 15 | Key1 string `json:"key1"` 16 | Key2 string `json:"key2"` 17 | Duration *Duration `json:"duration,omitempty"` 18 | } 19 | 20 | func TestUnmarshalForStringBasedDurationInput(t *testing.T) { 21 | data := ` 22 | { 23 | "key1": "value1", 24 | "key2": "value2", 25 | "duration": "100s" 26 | } 27 | ` 28 | 29 | d, err := time.ParseDuration("100s") 30 | require.NoError(t, err) 31 | 32 | expectedDuration := &Duration{ 33 | Duration: d, 34 | } 35 | 36 | var durationTestInput *DurationTestInput 37 | err = json.Unmarshal([]byte(data), &durationTestInput) 38 | require.NoError(t, err) 39 | 40 | require.NotNil(t, durationTestInput) 41 | require.NotNil(t, durationTestInput.Duration) 42 | require.Equal(t, expectedDuration.Duration, durationTestInput.Duration.Duration) 43 | } 44 | 45 | func TestUnmarshalForFloatBasedDurationInput(t *testing.T) { 46 | data := ` 47 | { 48 | "key1": "value1", 49 | "key2": "value2", 50 | "duration": 4.5 51 | } 52 | ` 53 | 54 | in := 4.5 55 | expectedDuration := &Duration{ 56 | Duration: time.Duration(in), 57 | } 58 | 59 | var durationTestInput *DurationTestInput 60 | err := json.Unmarshal([]byte(data), &durationTestInput) 61 | require.NoError(t, err) 62 | 63 | require.NotNil(t, durationTestInput) 64 | require.NotNil(t, durationTestInput.Duration) 65 | require.Equal(t, expectedDuration.Duration, durationTestInput.Duration.Duration) 66 | } 67 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/map_flag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestFlagMapValueSet(t *testing.T) { 12 | t.Parallel() 13 | 14 | t.Run("missing =", func(t *testing.T) { 15 | 16 | f := new(FlagMapValue) 17 | if err := f.Set("foo"); err == nil { 18 | t.Fatal("expected error, got nil") 19 | } 20 | }) 21 | 22 | t.Run("sets", func(t *testing.T) { 23 | 24 | f := new(FlagMapValue) 25 | if err := f.Set("foo=bar"); err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | r, ok := (*f)["foo"] 30 | if !ok { 31 | t.Errorf("missing value: %#v", f) 32 | } 33 | if exp := "bar"; r != exp { 34 | t.Errorf("expected %q to be %q", r, exp) 35 | } 36 | }) 37 | 38 | t.Run("sets multiple", func(t *testing.T) { 39 | 40 | f := new(FlagMapValue) 41 | 42 | r := map[string]string{ 43 | "foo": "bar", 44 | "zip": "zap", 45 | "cat": "dog", 46 | } 47 | 48 | for k, v := range r { 49 | if err := f.Set(fmt.Sprintf("%s=%s", k, v)); err != nil { 50 | t.Fatal(err) 51 | } 52 | } 53 | 54 | for k, v := range r { 55 | r, ok := (*f)[k] 56 | if !ok { 57 | t.Errorf("missing value %q: %#v", k, f) 58 | } 59 | if exp := v; r != exp { 60 | t.Errorf("expected %q to be %q", r, exp) 61 | } 62 | } 63 | }) 64 | 65 | t.Run("overwrites", func(t *testing.T) { 66 | 67 | f := new(FlagMapValue) 68 | if err := f.Set("foo=bar"); err != nil { 69 | t.Fatal(err) 70 | } 71 | if err := f.Set("foo=zip"); err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | r, ok := (*f)["foo"] 76 | if !ok { 77 | t.Errorf("missing value: %#v", f) 78 | } 79 | if exp := "zip"; r != exp { 80 | t.Errorf("expected %q to be %q", r, exp) 81 | } 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /internal/mocks/pbdnsmock/dns_service_server.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbdnsmock 4 | 5 | import ( 6 | context "context" 7 | 8 | pbdns "github.com/hashicorp/consul/proto-public/pbdns" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // DNSServiceServer is an autogenerated mock type for the DNSServiceServer type 13 | type DNSServiceServer struct { 14 | mock.Mock 15 | } 16 | 17 | // Query provides a mock function with given fields: _a0, _a1 18 | func (_m *DNSServiceServer) Query(_a0 context.Context, _a1 *pbdns.QueryRequest) (*pbdns.QueryResponse, error) { 19 | ret := _m.Called(_a0, _a1) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for Query") 23 | } 24 | 25 | var r0 *pbdns.QueryResponse 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest) (*pbdns.QueryResponse, error)); ok { 28 | return rf(_a0, _a1) 29 | } 30 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest) *pbdns.QueryResponse); ok { 31 | r0 = rf(_a0, _a1) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(*pbdns.QueryResponse) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(context.Context, *pbdns.QueryRequest) error); ok { 39 | r1 = rf(_a0, _a1) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // NewDNSServiceServer creates a new instance of DNSServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 48 | // The first argument is typically a *testing.T value. 49 | func NewDNSServiceServer(t interface { 50 | mock.TestingT 51 | Cleanup(func()) 52 | }) *DNSServiceServer { 53 | mock := &DNSServiceServer{} 54 | mock.Mock.Test(t) 55 | 56 | t.Cleanup(func() { mock.AssertExpectations(t) }) 57 | 58 | return mock 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/reusable-get-go-version.yml: -------------------------------------------------------------------------------- 1 | name: get-go-version 2 | 3 | on: 4 | workflow_call: 5 | outputs: 6 | go-version: 7 | description: "The Go version detected by this workflow" 8 | value: ${{ jobs.get-go-version.outputs.go-version }} 9 | go-version-previous: 10 | description: "The Go version (MAJOR.MINOR) prior to the current one, used for backwards compatibility testing" 11 | value: ${{ jobs.get-go-version.outputs.go-version-previous }} 12 | 13 | jobs: 14 | get-go-version: 15 | name: "Determine Go toolchain version" 16 | runs-on: ubuntu-latest 17 | outputs: 18 | go-version: ${{ steps.get-go-version.outputs.go-version }} 19 | go-version-previous: ${{ steps.get-go-version.outputs.go-version-previous }} 20 | steps: 21 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 22 | - name: Determine Go version 23 | id: get-go-version 24 | # We use .go-version as our source of truth for current Go 25 | # version, because "goenv" can react to it automatically. 26 | # 27 | # In the future, we can transition from .go-version and goenv to 28 | # Go 1.21 `toolchain` directives by updating this workflow rather 29 | # than individually setting `go-version-file` in each `setup-go` 30 | # job (as of 2024-01-03, `setup-go` does not support `toolchain`). 31 | # 32 | # When changing the method of Go version detection, also update 33 | # GOLANG_VERSION detection in the root Makefile; this is used for 34 | # setting the Dockerfile Go version. 35 | run: | 36 | GO_VERSION=$(head -n 1 .go-version) 37 | echo "Building with Go ${GO_VERSION}" 38 | echo "go-version=${GO_VERSION}" >> $GITHUB_OUTPUT 39 | GO_MINOR_VERSION=${GO_VERSION%.*} 40 | GO_VERSION_PREVIOUS="${GO_MINOR_VERSION%.*}.$((${GO_MINOR_VERSION#*.}-1))" 41 | echo "Previous version ${GO_VERSION_PREVIOUS}" 42 | echo "go-version-previous=${GO_VERSION_PREVIOUS}" >> $GITHUB_OUTPUT 43 | -------------------------------------------------------------------------------- /internal/mocks/pbdnsmock/dns_service_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbdnsmock 4 | 5 | import ( 6 | context "context" 7 | 8 | grpc "google.golang.org/grpc" 9 | 10 | mock "github.com/stretchr/testify/mock" 11 | 12 | pbdns "github.com/hashicorp/consul/proto-public/pbdns" 13 | ) 14 | 15 | // DNSServiceClient is an autogenerated mock type for the DNSServiceClient type 16 | type DNSServiceClient struct { 17 | mock.Mock 18 | } 19 | 20 | // Query provides a mock function with given fields: ctx, in, opts 21 | func (_m *DNSServiceClient) Query(ctx context.Context, in *pbdns.QueryRequest, opts ...grpc.CallOption) (*pbdns.QueryResponse, error) { 22 | _va := make([]interface{}, len(opts)) 23 | for _i := range opts { 24 | _va[_i] = opts[_i] 25 | } 26 | var _ca []interface{} 27 | _ca = append(_ca, ctx, in) 28 | _ca = append(_ca, _va...) 29 | ret := _m.Called(_ca...) 30 | 31 | if len(ret) == 0 { 32 | panic("no return value specified for Query") 33 | } 34 | 35 | var r0 *pbdns.QueryResponse 36 | var r1 error 37 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) (*pbdns.QueryResponse, error)); ok { 38 | return rf(ctx, in, opts...) 39 | } 40 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) *pbdns.QueryResponse); ok { 41 | r0 = rf(ctx, in, opts...) 42 | } else { 43 | if ret.Get(0) != nil { 44 | r0 = ret.Get(0).(*pbdns.QueryResponse) 45 | } 46 | } 47 | 48 | if rf, ok := ret.Get(1).(func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) error); ok { 49 | r1 = rf(ctx, in, opts...) 50 | } else { 51 | r1 = ret.Error(1) 52 | } 53 | 54 | return r0, r1 55 | } 56 | 57 | // NewDNSServiceClient creates a new instance of DNSServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 58 | // The first argument is typically a *testing.T value. 59 | func NewDNSServiceClient(t interface { 60 | mock.TestingT 61 | Cleanup(func()) 62 | }) *DNSServiceClient { 63 | mock := &DNSServiceClient{} 64 | mock.Mock.Test(t) 65 | 66 | t.Cleanup(func() { mock.AssertExpectations(t) }) 67 | 68 | return mock 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/consul-dataplane 2 | 3 | go 1.25.3 4 | 5 | require ( 6 | dario.cat/mergo v1.0.0 7 | github.com/armon/go-metrics v0.4.1 8 | github.com/hashi-derek/grpc-proxy v0.0.0-20231207191910-191266484d75 9 | github.com/hashicorp/consul-server-connection-manager v0.1.12 10 | github.com/hashicorp/consul/proto-public v0.7.0 11 | github.com/hashicorp/go-hclog v1.5.0 12 | github.com/hashicorp/go-multierror v1.1.1 13 | github.com/hashicorp/go-rootcerts v1.0.2 14 | github.com/mitchellh/mapstructure v1.5.0 15 | github.com/prometheus/client_golang v1.23.2 16 | github.com/stretchr/testify v1.11.1 17 | google.golang.org/grpc v1.75.0 18 | google.golang.org/protobuf v1.36.8 19 | ) 20 | 21 | require ( 22 | github.com/DataDog/datadog-go v3.2.0+incompatible // indirect 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/cenkalti/backoff/v4 v4.1.3 // indirect 25 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 27 | github.com/fatih/color v1.16.0 // indirect 28 | github.com/hashicorp/errwrap v1.0.0 // indirect 29 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 30 | github.com/hashicorp/go-metrics v0.5.4 // indirect 31 | github.com/hashicorp/go-netaddrs v0.1.0 // indirect 32 | github.com/hashicorp/golang-lru v0.5.1 // indirect 33 | github.com/mattn/go-colorable v0.1.13 // indirect 34 | github.com/mattn/go-isatty v0.0.20 // indirect 35 | github.com/mitchellh/go-homedir v1.1.0 // indirect 36 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 37 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 38 | github.com/prometheus/client_model v0.6.2 // indirect 39 | github.com/prometheus/common v0.66.1 // indirect 40 | github.com/prometheus/procfs v0.16.1 // indirect 41 | github.com/stretchr/objx v0.5.2 // indirect 42 | go.yaml.in/yaml/v2 v2.4.2 // indirect 43 | golang.org/x/net v0.43.0 // indirect 44 | golang.org/x/sys v0.35.0 // indirect 45 | golang.org/x/text v0.28.0 // indirect 46 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect 47 | gopkg.in/yaml.v3 v3.0.1 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /.github/workflows/changelog-checker.yml: -------------------------------------------------------------------------------- 1 | # This workflow checks that there is either a 'pr/no-changelog' label applied to a PR 2 | # or there is a .changelog/.txt file associated with a PR for a changelog entry 3 | 4 | name: Changelog Checker 5 | 6 | on: 7 | pull_request: 8 | types: [opened, synchronize, labeled] 9 | # Runs on PRs to main and all release branches 10 | branches: 11 | - main 12 | - release/* 13 | 14 | jobs: 15 | # checks that a .changelog entry is present for a PR 16 | changelog-check: 17 | # If there a `pr/no-changelog` label we ignore this check. 18 | if: "! contains(github.event.pull_request.labels.*.name, 'pr/no-changelog')" 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 23 | with: 24 | ref: ${{ github.event.pull_request.head.sha }} 25 | fetch-depth: 0 # by default the checkout action doesn't checkout all branches 26 | - name: Check for changelog entry in diff 27 | run: | 28 | # check if there is a diff in the .changelog directory 29 | # for PRs against the main branch, the changelog file name should match the PR number 30 | if [ "${{ github.event.pull_request.base.ref }}" = "${{ github.event.repository.default_branch }}" ]; then 31 | enforce_matching_pull_request_number="matching this PR number " 32 | changelog_file_path=".changelog/${{ github.event.pull_request.number }}.txt" 33 | else 34 | changelog_file_path=".changelog/*.txt" 35 | fi 36 | 37 | changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/main")" -- ${changelog_file_path}) 38 | 39 | # If we do not find a file in .changelog/, we fail the check 40 | if [ -z "$changelog_files" ]; then 41 | # Fail status check when no .changelog entry was found on the PR 42 | echo "Did not find a .changelog entry ${enforce_matching_pull_request_number}and the 'pr/no-changelog' label was not applied." 43 | exit 1 44 | else 45 | echo "Found .changelog entry in PR!" 46 | fi 47 | -------------------------------------------------------------------------------- /.github/workflows/consul-dataplane-checks.yaml: -------------------------------------------------------------------------------- 1 | name: consul-dataplane-checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/** 8 | pull_request: 9 | 10 | jobs: 11 | conditional-skip: 12 | uses: ./.github/workflows/reusable-conditional-skip.yml 13 | 14 | get-go-version: 15 | # Cascades down to test jobs 16 | needs: [conditional-skip] 17 | if: needs.conditional-skip.outputs.skip-ci != 'true' 18 | uses: ./.github/workflows/reusable-get-go-version.yml 19 | 20 | unit-tests: 21 | name: unit-tests 22 | needs: 23 | - get-go-version 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 27 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 28 | with: 29 | go-version: ${{ needs.get-go-version.outputs.go-version }} 30 | - run: go test ./... -p 1 # disable parallelism to avoid port conflicts from default metrics and lifecycle server configuration 31 | 32 | integration-tests: 33 | name: integration-tests 34 | needs: 35 | - get-go-version 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 39 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 40 | with: 41 | go-version: ${{ needs.get-go-version.outputs.go-version }} 42 | - run: make docker 43 | # Currently the server version below is set to 1.15-dev: integration-tests/main_test.go 44 | - run: echo "VERSION=$(make version)" >> $GITHUB_ENV 45 | - run: cd integration-tests && go test -dataplane-image="consul-dataplane:${{ env.VERSION }}" 46 | 47 | golangci: 48 | name: lint 49 | needs: 50 | - get-go-version 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 54 | with: 55 | go-version: ${{ needs.get-go-version.outputs.go-version }} 56 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 57 | - name: golangci-lint 58 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 59 | with: 60 | version: "v2.4.0" 61 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/env.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | var ( 15 | asInt = func(s string) (*int, error) { 16 | if s == "" { 17 | return nil, nil 18 | } 19 | 20 | n, err := strconv.Atoi(s) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | return &n, nil 26 | } 27 | 28 | asBool = func(s string) (*bool, error) { 29 | if s == "" { 30 | return nil, nil 31 | } 32 | 33 | b, err := strconv.ParseBool(s) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return &b, nil 39 | } 40 | 41 | asDuration = func(s string) (*Duration, error) { 42 | if s == "" { 43 | return nil, nil 44 | } 45 | 46 | t, err := time.ParseDuration(s) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return &Duration{Duration: t}, nil 52 | } 53 | 54 | asString = func(s string) (*string, error) { 55 | if s == "" { 56 | return nil, nil 57 | } 58 | 59 | return &s, nil 60 | } 61 | ) 62 | 63 | func parseEnv[T any](name string, parseFn func(string) (*T, error)) *T { 64 | val, err := parseEnvError(name, parseFn) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | return val 69 | } 70 | 71 | func parseEnvError[T any](name string, parseFn func(string) (*T, error)) (*T, error) { 72 | valStr, ok := os.LookupEnv(name) 73 | if !ok { 74 | // Env var is not present in the environment. 75 | return nil, nil 76 | } 77 | valT, err := parseFn(valStr) 78 | if err != nil { 79 | return nil, fmt.Errorf("unable to parse environment variable %s=%s as %T", name, valStr, valT) 80 | } 81 | return valT, nil 82 | } 83 | 84 | // Read multiple environment variables of the form VAR{1,9}. 85 | // 86 | // For example, if these variables are set 87 | // 88 | // VAR1=a VAR2=b VAR3=c 89 | // 90 | // then calling multiValueEnv("VAR") returns [a, b, c]. 91 | func multiValueEnv(baseName string) map[string]string { 92 | result := map[string]string{} 93 | for i := 1; i < 10; i++ { 94 | name := fmt.Sprintf("%s%d", baseName, i) 95 | val := os.Getenv(name) 96 | if val == "" { 97 | // Ignore empty vars. 98 | continue 99 | } 100 | result[name] = val 101 | } 102 | return result 103 | } 104 | -------------------------------------------------------------------------------- /integration-tests/helpers/pod.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/docker/go-connections/nat" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | const pauseContainerImage = "registry.k8s.io/pause" 16 | 17 | type Pod struct { 18 | *Container 19 | 20 | suite *Suite 21 | } 22 | 23 | // RunPod runs a "pause" container with the given ports mapped to the host, the 24 | // network namespace of which will be joined by other containers. This allows 25 | // us to know the IP address before starting the "real" container (as is needed 26 | // to register a sidecar proxy before running consul-dataplane). 27 | func RunPod(t *testing.T, suite *Suite, podName string, mappedPorts []nat.Port) *Pod { 28 | t.Helper() 29 | 30 | exposedPorts := make([]string, len(mappedPorts)) 31 | for idx, port := range mappedPorts { 32 | exposedPorts[idx] = string(port) 33 | } 34 | 35 | container := suite.RunContainer(t, fmt.Sprintf("%s-pod", podName), false, ContainerRequest{ 36 | Image: pauseContainerImage, 37 | ExposedPorts: exposedPorts, 38 | }) 39 | 40 | return &Pod{ 41 | Container: container, 42 | suite: suite, 43 | } 44 | } 45 | 46 | // ExposeInternalPorts creates iptables rules to forward traffic from 47 | // public-facing ports to those bound only on the loopback interface. This 48 | // is useful for testing our DNS proxy which can **only** be bound to the 49 | // loopback interace. 50 | func (p *Pod) ExposeInternalPorts(t *testing.T, ports []nat.Port) { 51 | t.Helper() 52 | 53 | container := p.suite.RunContainer(t, "expose-internal-pods", false, ContainerRequest{ 54 | NetworkMode: p.Network(), 55 | Image: "alpine:3.16.2", 56 | Cmd: []string{"sleep", "infinity"}, 57 | Privileged: true, 58 | }) 59 | 60 | cmds := []string{ 61 | "sysctl -w net.ipv4.conf.all.route_localnet=1", 62 | "apk add iptables", 63 | } 64 | 65 | for _, port := range ports { 66 | cmds = append(cmds, fmt.Sprintf( 67 | "iptables -t nat -A PREROUTING -p %s --dport %s -j DNAT --to-destination 127.0.0.1:%s", 68 | port.Proto(), 69 | port.Port(), 70 | port.Port(), 71 | )) 72 | } 73 | 74 | for _, cmd := range cmds { 75 | _, _, err := container.Exec(p.suite.Context(t), strings.Split(cmd, " ")) 76 | require.NoError(t, err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.release/security-scan.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | # These scan results are run as part of CRT workflows. 5 | 6 | # Un-triaged results will block release. See `security-scanner` docs for more 7 | # information on how to add `triage` config to unblock releases for specific results. 8 | # In most cases, we should not need to disable the entire scanner to unblock a release. 9 | 10 | # To run manually, install scanner and then from the repository root run 11 | # `SECURITY_SCANNER_CONFIG_FILE=.release/security-scan.hcl scan ...` 12 | # To scan a local container, add `local_daemon = true` to the `container` block below. 13 | # See `security-scanner` docs or run with `--help` for scan target syntax. 14 | 15 | container { 16 | dependencies = true 17 | alpine_security = true 18 | osv = true 19 | go_modules = true 20 | 21 | secrets { 22 | all = true 23 | } 24 | 25 | # Triage items that are _safe_ to ignore here. Note that this list should be 26 | # periodically cleaned up to remove items that are no longer found by the scanner. 27 | triage { 28 | suppress { 29 | vulnerabilities = [ 30 | "CVE-2025-6965", 31 | "CVE-2025-6395", 32 | "CVE-2024-12797", 33 | "CVE-2025-5702", 34 | "CVE-2025-8058", 35 | "CVE-2024-4067", 36 | "CVE-2025-31115", 37 | "CVE-2025-3576", 38 | "CVE-2025-6021", 39 | "CVE-2025-25724", 40 | "CVE-2024-57970", 41 | "CVE-2025-32414", 42 | "CVE-2024-52533", 43 | "CVE-2025-5914", 44 | "CVE-2025-3277", 45 | "CVE-2024-40896" 46 | ] 47 | } 48 | } 49 | } 50 | 51 | binary { 52 | go_modules = true 53 | osv = true 54 | 55 | secrets { 56 | all = true 57 | } 58 | } 59 | 60 | repository { 61 | go_modules = true 62 | npm = true 63 | osv = true 64 | 65 | secrets { 66 | all = true 67 | } 68 | 69 | triage { 70 | suppress { 71 | # Only remaining vulnerabilities in integration tests (archived go-jose v2) 72 | vulnerabilities = [ 73 | "CVE-2024-28180", 74 | "GHSA-c5q2-7r4c-mv6g", 75 | "GO-2024-2631" 76 | ] 77 | paths = [ 78 | # SHA1 usage in bootstrap config is for non-security purposes 79 | "internal/bootstrap/bootstrap_config.go" 80 | ] 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /.github/workflows/security-scan.yml: -------------------------------------------------------------------------------- 1 | # This job runs a non-blocking informational security scan on the repository. 2 | # For release-blocking security scans, see .release/security-scan.hcl. 3 | name: Security Scan 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | - release/** 10 | pull_request: 11 | branches: 12 | - main 13 | - release/** 14 | # paths-ignore only works for non-required checks. 15 | # Jobs that are required for merge must use reusable-conditional-skip.yml. 16 | paths-ignore: 17 | - '_doc/**' 18 | - '.changelog/**' 19 | 20 | # cancel existing runs of the same workflow on the same ref 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | 27 | get-go-version: 28 | # Cascades down to test jobs 29 | uses: ./.github/workflows/reusable-get-go-version.yml 30 | 31 | scan: 32 | needs: 33 | - get-go-version 34 | runs-on: ubuntu-latest 35 | # The first check ensures this doesn't run on community-contributed PRs, who 36 | # won't have the permissions to run this job. 37 | if: ${{ (github.repository != 'hashicorp/consul-dataplane' || (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) 38 | && (github.actor != 'dependabot[bot]') && (github.actor != 'hc-github-team-consul-core') }} 39 | 40 | steps: 41 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 42 | 43 | - name: Set up Go 44 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 45 | with: 46 | go-version: ${{ needs.get-go-version.outputs.go-version }} 47 | 48 | - name: Clone Security Scanner repo 49 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 50 | with: 51 | repository: hashicorp/security-scanner 52 | token: ${{ secrets.PRODSEC_SCANNER_READ_ONLY }} 53 | path: security-scanner 54 | ref: main 55 | 56 | - name: Scan 57 | id: scan 58 | uses: ./security-scanner 59 | with: 60 | repository: "$PWD" 61 | # See scan.hcl at repository root for config. 62 | 63 | - name: SARIF Output 64 | shell: bash 65 | run: | 66 | cat results.sarif | jq 67 | 68 | - name: Upload SARIF file 69 | uses: github/codeql-action/upload-sarif@c4fb451437765abf5018c6fbf22cce1a7da1e5cc # codeql-bundle-v2.17.1 70 | with: 71 | sarif_file: results.sarif 72 | -------------------------------------------------------------------------------- /pkg/dns/mocks/mock_UnsafeDNSServiceServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.37.1. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // UnsafeDNSServiceServer is an autogenerated mock type for the UnsafeDNSServiceServer type 8 | type UnsafeDNSServiceServer struct { 9 | mock.Mock 10 | } 11 | 12 | type UnsafeDNSServiceServer_Expecter struct { 13 | mock *mock.Mock 14 | } 15 | 16 | func (_m *UnsafeDNSServiceServer) EXPECT() *UnsafeDNSServiceServer_Expecter { 17 | return &UnsafeDNSServiceServer_Expecter{mock: &_m.Mock} 18 | } 19 | 20 | // mustEmbedUnimplementedDNSServiceServer provides a mock function with given fields: 21 | func (_m *UnsafeDNSServiceServer) mustEmbedUnimplementedDNSServiceServer() { 22 | _m.Called() 23 | } 24 | 25 | // UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'mustEmbedUnimplementedDNSServiceServer' 26 | type UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call struct { 27 | *mock.Call 28 | } 29 | 30 | // mustEmbedUnimplementedDNSServiceServer is a helper method to define mock.On call 31 | func (_e *UnsafeDNSServiceServer_Expecter) mustEmbedUnimplementedDNSServiceServer() *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call { 32 | return &UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call{Call: _e.mock.On("mustEmbedUnimplementedDNSServiceServer")} 33 | } 34 | 35 | func (_c *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call) Run(run func()) *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call { 36 | _c.Call.Run(func(args mock.Arguments) { 37 | run() 38 | }) 39 | return _c 40 | } 41 | 42 | func (_c *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call) Return() *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call { 43 | _c.Call.Return() 44 | return _c 45 | } 46 | 47 | func (_c *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call) RunAndReturn(run func()) *UnsafeDNSServiceServer_mustEmbedUnimplementedDNSServiceServer_Call { 48 | _c.Call.Return(run) 49 | return _c 50 | } 51 | 52 | // NewUnsafeDNSServiceServer creates a new instance of UnsafeDNSServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 53 | // The first argument is typically a *testing.T value. 54 | func NewUnsafeDNSServiceServer(t interface { 55 | mock.TestingT 56 | Cleanup(func()) 57 | }) *UnsafeDNSServiceServer { 58 | mock := &UnsafeDNSServiceServer{} 59 | mock.Mock.Test(t) 60 | 61 | t.Cleanup(func() { mock.AssertExpectations(t) }) 62 | 63 | return mock 64 | } 65 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/stretchr/testify/require" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "os" 11 | "strconv" 12 | "testing" 13 | ) 14 | 15 | func TestDoHealthCheck(t *testing.T) { 16 | tests := []struct { 17 | name string 18 | statusCode int 19 | serverErr bool 20 | expectedExit int 21 | expectedOutput string 22 | }{ 23 | { 24 | name: "success with 200", 25 | statusCode: http.StatusOK, 26 | expectedExit: 0, 27 | expectedOutput: "Envoy proxy is ready.\n", 28 | }, 29 | { 30 | name: "success with 204", 31 | statusCode: http.StatusNoContent, 32 | expectedExit: 0, 33 | expectedOutput: "Envoy proxy is ready.\n", 34 | }, 35 | { 36 | name: "failure with 404", 37 | statusCode: http.StatusNotFound, 38 | expectedExit: 1, 39 | expectedOutput: "Envoy proxy is not ready. Received status code: 404\n", 40 | }, 41 | { 42 | name: "failure with 500", 43 | statusCode: http.StatusInternalServerError, 44 | expectedExit: 1, 45 | expectedOutput: "Envoy proxy is not ready. Received status code: 500\n", 46 | }, 47 | { 48 | name: "server error", 49 | serverErr: true, 50 | expectedExit: 1, 51 | expectedOutput: "Error connecting to Envoy admin endpoint: ", 52 | }, 53 | } 54 | 55 | for _, tc := range tests { 56 | t.Run(tc.name, func(t *testing.T) { 57 | var exitCode int 58 | mockExit := func(code int) { 59 | exitCode = code 60 | } 61 | 62 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 | require.Equal(t, "/ready", r.URL.Path) 64 | require.Equal(t, "GET", r.Method) 65 | 66 | if tc.serverErr { 67 | panic("simulated server error") 68 | } 69 | 70 | w.WriteHeader(tc.statusCode) 71 | })) 72 | defer ts.Close() 73 | 74 | client := ts.Client() 75 | u, _ := url.Parse(ts.URL) 76 | port, _ := strconv.Atoi(u.Port()) 77 | 78 | // Capture stdout/stderr 79 | stdout := captureOutput(t, func() { 80 | doHealthCheck(port, client, mockExit) 81 | }) 82 | 83 | require.Contains(t, stdout, tc.expectedOutput) 84 | require.Equal(t, tc.expectedExit, exitCode) 85 | 86 | }) 87 | } 88 | } 89 | 90 | func captureOutput(t *testing.T, f func()) string { 91 | oldStdout := os.Stdout 92 | oldStderr := os.Stderr 93 | r, w, _ := os.Pipe() 94 | os.Stdout = w 95 | os.Stderr = w 96 | 97 | f() 98 | 99 | w.Close() 100 | os.Stdout = oldStdout 101 | os.Stderr = oldStderr 102 | 103 | var buf bytes.Buffer 104 | if _, err := io.Copy(&buf, r); err != nil { 105 | t.Fatalf("failed to copy output: %v", err) 106 | } 107 | return buf.String() 108 | } 109 | -------------------------------------------------------------------------------- /integration-tests/helpers/dataplane.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/testcontainers/testcontainers-go" 11 | "github.com/testcontainers/testcontainers-go/wait" 12 | ) 13 | 14 | // EnvoyAdminPort is the port Consul Dataplane will bind the Envoy admin server to. 15 | var EnvoyAdminPort = TCP(30000) 16 | 17 | type DataplaneConfig struct { 18 | Addresses string 19 | ServiceNodeName string 20 | ProxyServiceID string 21 | LoginAuthMethod string 22 | LoginBearerToken string 23 | DNSBindPort string 24 | ServiceMetricsURL string 25 | ShutdownGracePeriodSeconds string 26 | ShutdownDrainListenersEnabled bool 27 | DumpEnvoyConfigOnExitEnabled bool 28 | } 29 | 30 | func (cfg DataplaneConfig) ToArgs() []string { 31 | args := []string{ 32 | "-addresses", cfg.Addresses, 33 | "-service-node-name", cfg.ServiceNodeName, 34 | "-proxy-service-id", cfg.ProxyServiceID, 35 | "-envoy-admin-bind-address", "0.0.0.0", 36 | "-envoy-admin-bind-port", EnvoyAdminPort.Port(), 37 | "-credential-type", "login", 38 | "-login-auth-method", cfg.LoginAuthMethod, 39 | "-login-bearer-token", cfg.LoginBearerToken, 40 | "-ca-certs", "/data/ca-cert.pem", 41 | "-tls-server-name", "server.dc1.consul", 42 | "-log-level", "debug", 43 | "-consul-dns-bind-port", cfg.DNSBindPort, 44 | "-telemetry-use-central-config", 45 | "-telemetry-prom-scrape-path", "/metrics", 46 | "-telemetry-prom-service-metrics-url", cfg.ServiceMetricsURL, 47 | } 48 | 49 | if cfg.ShutdownGracePeriodSeconds != "" { 50 | args = append(args, "-shutdown-grace-period-seconds", cfg.ShutdownGracePeriodSeconds) 51 | } 52 | 53 | if cfg.ShutdownDrainListenersEnabled { 54 | args = append(args, "-shutdown-drain-listeners") 55 | } 56 | 57 | if cfg.DumpEnvoyConfigOnExitEnabled { 58 | args = append(args, "-dump-envoy-config-on-exit") 59 | } 60 | 61 | return args 62 | } 63 | 64 | // RunDataplane runs consul-dataplane in the given pod's network. It captures 65 | // the Envoy proxy's config as an artifact at the end of the test. 66 | func RunDataplane(t *testing.T, pod *Pod, suite *Suite, cfg DataplaneConfig) *Container { 67 | t.Helper() 68 | 69 | volume := suite.Volume(t) 70 | 71 | container := suite.RunContainer(t, fmt.Sprintf("%s-dataplane", cfg.ProxyServiceID), true, ContainerRequest{ 72 | NetworkMode: pod.Network(), 73 | Image: suite.opts.DataplaneImage, 74 | Cmd: cfg.ToArgs(), 75 | Mounts: []testcontainers.ContainerMount{ 76 | testcontainers.VolumeMount(volume.Name, "/data"), 77 | }, 78 | WaitingFor: wait.ForLog("starting main dispatch loop"), // https://github.com/envoyproxy/envoy/blob/ce49966ecb5f2d530117a29ae60b88198746fd74/source/server/server.cc#L906-L907 79 | }) 80 | 81 | return container 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Consul logo 3 | Consul Dataplane 4 |

5 | 6 | Consul Dataplane is a lightweight process that manages Envoy for Consul service mesh workloads. 7 | 8 | Consul Dataplane's design removes the need to run Consul client agents. Removing Consul client 9 | agents results in the following benefits: 10 | 11 | - **Fewer networking requirements**: Without client agents, Consul does not require bidirectional 12 | network connectivity across multiple protocols to enable gossip communication. Instead, it 13 | requires a single gRPC connection to the Consul servers, which significantly simplifies 14 | requirements for the operator. 15 | - **Simplified set up**: Because there are no client agents to engage in gossip, you do not have to 16 | generate and distribute a gossip encryption key to agents during the initial bootstrapping 17 | process. Securing agent communication also becomes simpler, with fewer tokens to track, 18 | distribute, and rotate. 19 | - **Additional environment and runtime support**: Current Consul on Kubernetes deployments require 20 | using hostPorts and DaemonSets for client agents, which limits Consul’s ability to be deployed in 21 | environments where those features are not supported. As a result, Consul Dataplane supports AWS 22 | Fargate and GKE Autopilot. 23 | - **Easier upgrades**: With Consul Dataplane, updating Consul to a new version no longer requires 24 | upgrading client agents. Consul Dataplane also has better compatibility across Consul server 25 | versions, so the process to upgrade Consul servers becomes easier. 26 | 27 | Refer to the [Documentation](https://developer.hashicorp.com/consul/docs/connect/dataplane) for more 28 | information on Consul Dataplane. 29 | 30 | **Note**: We take Consul's security and our users' trust seriously. If you believe you have 31 | found a security issue in Consul, please responsibly disclose by contacting us at 32 | security@hashicorp.com. 33 | 34 | ## Development 35 | 36 | ### Build 37 | 38 | #### Binary 39 | 40 | ``` 41 | make dev 42 | ``` 43 | 44 | #### Docker Image 45 | 46 | ``` 47 | make docker 48 | ``` 49 | 50 | ### Testing 51 | 52 | #### Unit Tests 53 | 54 | ``` 55 | make unit-tests 56 | ``` 57 | 58 | ### Extending the Container Image 59 | 60 | The official `hashicorp/consul-dataplane` container image is ["distroless"](https://github.com/GoogleContainerTools/distroless) 61 | and only includes the bare-minimum runtime dependencies, for greater security. 62 | 63 | You may want to add a shell that can be used by the `-addresses exec=...` flag 64 | to resolve Consul servers with a custom script. 65 | 66 | Here's an example of how you might do that, copying `sh` from the busybox image: 67 | 68 | ```Dockerfile 69 | FROM hashicorp/consul-dataplane:latest 70 | COPY --from=busybox:uclibc /bin/sh /bin/sh 71 | ``` 72 | 73 | ## Releasing 74 | 75 | See: engineering docs 76 | -------------------------------------------------------------------------------- /integration-tests/helpers/auth_method.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "crypto/ecdsa" 8 | "crypto/elliptic" 9 | "crypto/rand" 10 | "crypto/x509" 11 | "encoding/pem" 12 | "testing" 13 | "time" 14 | 15 | "github.com/stretchr/testify/require" 16 | "gopkg.in/square/go-jose.v2" 17 | "gopkg.in/square/go-jose.v2/jwt" 18 | 19 | "github.com/hashicorp/consul/api" 20 | ) 21 | 22 | // AuthMethod is a JWT ACL auth-method, that allows us to easily generate 23 | // bearer tokens in tests. 24 | type AuthMethod struct { 25 | Name string 26 | 27 | key *ecdsa.PrivateKey 28 | } 29 | 30 | func NewAuthMethod(t *testing.T) *AuthMethod { 31 | t.Helper() 32 | 33 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 34 | require.NoError(t, err) 35 | 36 | return &AuthMethod{Name: "auth-method", key: key} 37 | } 38 | 39 | // GenerateToken generates a JWT bearer token for the given service's identity. 40 | func (am *AuthMethod) GenerateToken(t *testing.T, service string) string { 41 | t.Helper() 42 | 43 | signer, err := jose.NewSigner(jose.SigningKey{ 44 | Algorithm: jose.ES256, 45 | Key: am.key, 46 | }, nil) 47 | require.NoError(t, err) 48 | 49 | claims := &jwt.Claims{ 50 | Subject: service, 51 | IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)), 52 | Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)), 53 | Issuer: am.jwtIssuer(), 54 | Audience: am.jwtAudience(), 55 | } 56 | 57 | token, err := jwt.Signed(signer). 58 | Claims(claims). 59 | CompactSerialize() 60 | require.NoError(t, err) 61 | 62 | return token 63 | } 64 | 65 | // Register the auth-method and binding rules with the Consul server. 66 | func (am *AuthMethod) Register(t *testing.T, server *ConsulServer) { 67 | t.Helper() 68 | 69 | _, _, err := server.Client.ACL().AuthMethodCreate(&api.ACLAuthMethod{ 70 | Name: am.Name, 71 | Type: "jwt", 72 | Config: map[string]any{ 73 | "BoundIssuer": am.jwtIssuer(), 74 | "BoundAudiences": am.jwtAudience(), 75 | "JWTValidationPubKeys": []string{am.publicKeyPEM(t)}, 76 | "ClaimMappings": map[string]string{"sub": "service"}, 77 | }, 78 | }, nil) 79 | require.NoError(t, err) 80 | 81 | _, _, err = server.Client.ACL().BindingRuleCreate(&api.ACLBindingRule{ 82 | AuthMethod: am.Name, 83 | BindType: api.BindingRuleBindTypeService, 84 | BindName: "${value.service}", 85 | }, nil) 86 | require.NoError(t, err) 87 | } 88 | 89 | func (am *AuthMethod) publicKeyPEM(t *testing.T) string { 90 | t.Helper() 91 | 92 | der, err := x509.MarshalPKIXPublicKey(&am.key.PublicKey) 93 | require.NoError(t, err) 94 | 95 | keyPem := pem.EncodeToMemory(&pem.Block{ 96 | Type: "PUBLIC KEY", 97 | Bytes: der, 98 | }) 99 | return string(keyPem) 100 | } 101 | 102 | func (*AuthMethod) jwtIssuer() string { 103 | return "issuer" 104 | } 105 | 106 | func (*AuthMethod) jwtAudience() jwt.Audience { 107 | return jwt.Audience{"audience"} 108 | } 109 | -------------------------------------------------------------------------------- /pkg/dns/mocks/mock_DNSServiceServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.37.1. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | 8 | pbdns "github.com/hashicorp/consul/proto-public/pbdns" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // DNSServiceServer is an autogenerated mock type for the DNSServiceServer type 13 | type DNSServiceServer struct { 14 | mock.Mock 15 | } 16 | 17 | type DNSServiceServer_Expecter struct { 18 | mock *mock.Mock 19 | } 20 | 21 | func (_m *DNSServiceServer) EXPECT() *DNSServiceServer_Expecter { 22 | return &DNSServiceServer_Expecter{mock: &_m.Mock} 23 | } 24 | 25 | // Query provides a mock function with given fields: _a0, _a1 26 | func (_m *DNSServiceServer) Query(_a0 context.Context, _a1 *pbdns.QueryRequest) (*pbdns.QueryResponse, error) { 27 | ret := _m.Called(_a0, _a1) 28 | 29 | var r0 *pbdns.QueryResponse 30 | var r1 error 31 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest) (*pbdns.QueryResponse, error)); ok { 32 | return rf(_a0, _a1) 33 | } 34 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest) *pbdns.QueryResponse); ok { 35 | r0 = rf(_a0, _a1) 36 | } else { 37 | if ret.Get(0) != nil { 38 | r0 = ret.Get(0).(*pbdns.QueryResponse) 39 | } 40 | } 41 | 42 | if rf, ok := ret.Get(1).(func(context.Context, *pbdns.QueryRequest) error); ok { 43 | r1 = rf(_a0, _a1) 44 | } else { 45 | r1 = ret.Error(1) 46 | } 47 | 48 | return r0, r1 49 | } 50 | 51 | // DNSServiceServer_Query_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Query' 52 | type DNSServiceServer_Query_Call struct { 53 | *mock.Call 54 | } 55 | 56 | // Query is a helper method to define mock.On call 57 | // - _a0 context.Context 58 | // - _a1 *pbdns.QueryRequest 59 | func (_e *DNSServiceServer_Expecter) Query(_a0 interface{}, _a1 interface{}) *DNSServiceServer_Query_Call { 60 | return &DNSServiceServer_Query_Call{Call: _e.mock.On("Query", _a0, _a1)} 61 | } 62 | 63 | func (_c *DNSServiceServer_Query_Call) Run(run func(_a0 context.Context, _a1 *pbdns.QueryRequest)) *DNSServiceServer_Query_Call { 64 | _c.Call.Run(func(args mock.Arguments) { 65 | run(args[0].(context.Context), args[1].(*pbdns.QueryRequest)) 66 | }) 67 | return _c 68 | } 69 | 70 | func (_c *DNSServiceServer_Query_Call) Return(_a0 *pbdns.QueryResponse, _a1 error) *DNSServiceServer_Query_Call { 71 | _c.Call.Return(_a0, _a1) 72 | return _c 73 | } 74 | 75 | func (_c *DNSServiceServer_Query_Call) RunAndReturn(run func(context.Context, *pbdns.QueryRequest) (*pbdns.QueryResponse, error)) *DNSServiceServer_Query_Call { 76 | _c.Call.Return(run) 77 | return _c 78 | } 79 | 80 | // NewDNSServiceServer creates a new instance of DNSServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 81 | // The first argument is typically a *testing.T value. 82 | func NewDNSServiceServer(t interface { 83 | mock.TestingT 84 | Cleanup(func()) 85 | }) *DNSServiceServer { 86 | mock := &DNSServiceServer{} 87 | mock.Mock.Test(t) 88 | 89 | t.Cleanup(func() { mock.AssertExpectations(t) }) 90 | 91 | return mock 92 | } 93 | -------------------------------------------------------------------------------- /.github/workflows/reusable-conditional-skip.yml: -------------------------------------------------------------------------------- 1 | name: conditional-skip 2 | 3 | on: 4 | workflow_call: 5 | outputs: 6 | skip-ci: 7 | description: "Whether we should skip build and test jobs" 8 | value: ${{ jobs.check-skip.outputs.skip-ci }} 9 | 10 | jobs: 11 | check-skip: 12 | runs-on: ubuntu-latest 13 | name: Check whether to skip build and tests 14 | outputs: 15 | skip-ci: ${{ steps.maybe-skip-ci.outputs.skip-ci }} 16 | steps: 17 | # We only allow use of conditional skip in two scenarios: 18 | # 1. PRs 19 | # 2. Pushes (merges) to protected branches (`main`, `release/**`) 20 | # 21 | # The second scenario is the only place we can be sure that checking just the 22 | # latest change on the branch is sufficient. In PRs, we need to check _all_ commits. 23 | # The ability to do this is ultimately determined by the triggers of the calling 24 | # workflow, since `base_ref` (the target branch of a PR) is only available in 25 | # `pull_request` events, not `push`. 26 | - name: Error if conditional check is not allowed 27 | if: ${{ !github.base_ref && !github.ref_protected }} 28 | run: | 29 | echo "Conditional skip requires a PR event with 'base_ref' or 'push' to a protected branch." 30 | echo "github.base_ref: ${{ github.base_ref }}" 31 | echo "github.ref_protected: ${{ github.ref_protected }}" 32 | echo "github.ref_name: ${{ github.ref_name }}" 33 | echo "Check the triggers of the calling workflow to ensure that these requirements are met." 34 | exit 1 35 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 36 | with: 37 | fetch-depth: 0 38 | - name: Check for skippable file changes 39 | id: changed-files 40 | uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 # v46.0.1 41 | with: 42 | # This is a multi-line YAML string with one match pattern per line. 43 | # Do not use quotes around values, as it's not supported. 44 | # See https://github.com/tj-actions/changed-files/blob/main/README.md#inputs-%EF%B8%8F 45 | # for usage, options, and more details on match syntax. 46 | files: | 47 | .github/workflows/reusable-conditional-skip.yml 48 | **.md 49 | _doc/** 50 | .changelog/** 51 | - name: Print changed files 52 | env: 53 | SKIPPABLE_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} 54 | NON_SKIPPABLE_FILES: ${{ steps.changed-files.outputs.other_changed_files }} 55 | run: | 56 | echo "Skippable changed files:" 57 | for file in ${SKIPPABLE_CHANGED_FILES}; do echo " $file"; done 58 | echo 59 | echo "Non-skippable files:" 60 | for file in ${NON_SKIPPABLE_FILES}; do echo " $file"; done 61 | - name: Skip tests and build if only skippable files changed 62 | id: maybe-skip-ci 63 | if: ${{ steps.changed-files.outputs.only_changed == 'true' }} 64 | run: | 65 | echo "Skipping tests and build because only skippable files changed" 66 | echo "skip-ci=true" >> $GITHUB_OUTPUT -------------------------------------------------------------------------------- /.release/ci.hcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | schema = "1" 5 | 6 | project "consul-dataplane" { 7 | team = "consul" 8 | slack { 9 | notification_channel = "C9KPKPKRN" # feed-consul-ci 10 | } 11 | github { 12 | organization = "hashicorp" 13 | repository = "consul-dataplane" 14 | release_branches = [ 15 | "main", 16 | "release/**", 17 | ] 18 | } 19 | } 20 | 21 | event "merge" { 22 | // "entrypoint" to use if build is not run automatically 23 | // i.e. send "merge" complete signal to orchestrator to trigger build 24 | } 25 | 26 | event "build" { 27 | depends = ["merge"] 28 | action "build" { 29 | organization = "hashicorp" 30 | repository = "consul-dataplane" 31 | workflow = "build" 32 | } 33 | } 34 | 35 | event "prepare" { 36 | depends = ["build"] 37 | action "prepare" { 38 | organization = "hashicorp" 39 | repository = "crt-workflows-common" 40 | workflow = "prepare" 41 | depends = ["build"] 42 | } 43 | 44 | notification { 45 | on = "fail" 46 | } 47 | } 48 | 49 | ## These are promotion and post-publish events 50 | ## they should be added to the end of the file after the verify event stanza. 51 | 52 | event "trigger-staging" { 53 | // This event is dispatched by the bob trigger-promotion command 54 | // and is required - do not delete. 55 | } 56 | 57 | event "promote-staging" { 58 | depends = ["trigger-staging"] 59 | action "promote-staging" { 60 | organization = "hashicorp" 61 | repository = "crt-workflows-common" 62 | workflow = "promote-staging" 63 | config = "release-metadata.hcl" 64 | } 65 | 66 | notification { 67 | on = "always" 68 | } 69 | } 70 | 71 | event "promote-staging-docker" { 72 | depends = ["promote-staging"] 73 | action "promote-staging-docker" { 74 | organization = "hashicorp" 75 | repository = "crt-workflows-common" 76 | workflow = "promote-staging-docker" 77 | } 78 | 79 | notification { 80 | on = "always" 81 | } 82 | } 83 | 84 | event "trigger-production" { 85 | // This event is dispatched by the bob trigger-promotion command 86 | // and is required - do not delete. 87 | } 88 | 89 | event "promote-production" { 90 | depends = ["trigger-production"] 91 | action "promote-production" { 92 | organization = "hashicorp" 93 | repository = "crt-workflows-common" 94 | workflow = "promote-production" 95 | } 96 | 97 | notification { 98 | on = "always" 99 | } 100 | } 101 | 102 | event "promote-production-docker" { 103 | depends = ["promote-production"] 104 | action "promote-production-docker" { 105 | organization = "hashicorp" 106 | repository = "crt-workflows-common" 107 | workflow = "promote-production-docker" 108 | } 109 | 110 | notification { 111 | on = "always" 112 | } 113 | } 114 | 115 | event "promote-production-packaging" { 116 | depends = ["promote-production-docker"] 117 | action "promote-production-packaging" { 118 | organization = "hashicorp" 119 | repository = "crt-workflows-common" 120 | workflow = "promote-production-packaging" 121 | } 122 | 123 | notification { 124 | on = "always" 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /integration-tests/helpers/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "strconv" 13 | "testing" 14 | "time" 15 | 16 | "github.com/docker/go-connections/nat" 17 | "github.com/miekg/dns" 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | var httpClient = &http.Client{ 22 | Timeout: 1 * time.Second, 23 | } 24 | 25 | func TCP(n int) nat.Port { 26 | port, err := nat.NewPort("tcp", strconv.Itoa(n)) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return port 31 | } 32 | 33 | func UDP(n int) nat.Port { 34 | port, err := nat.NewPort("udp", strconv.Itoa(n)) 35 | if err != nil { 36 | panic(err) 37 | } 38 | return port 39 | } 40 | 41 | func ExpectNoHTTPAccess(t *testing.T, ip string, port int) { 42 | t.Helper() 43 | 44 | require.Eventually(t, func() bool { 45 | ok, _ := canAccess(ip, port) 46 | return !ok 47 | }, time.Minute, 1*time.Second) 48 | } 49 | 50 | func ExpectHTTPAccess(t *testing.T, ip string, port int) { 51 | t.Helper() 52 | 53 | require.Eventually(t, func() bool { 54 | ok, err := canAccess(ip, port) 55 | if err != nil { 56 | t.Logf("HTTP access check failed: %v\n", err) 57 | } 58 | return ok 59 | }, time.Minute, 1*time.Second) 60 | } 61 | 62 | func canAccess(ip string, port int) (bool, error) { 63 | url := fmt.Sprintf("http://%s/", net.JoinHostPort(ip, strconv.Itoa(port))) 64 | rsp, err := httpClient.Get(url) 65 | if err != nil { 66 | return false, err 67 | } 68 | defer rsp.Body.Close() 69 | 70 | if rsp.StatusCode == http.StatusOK { 71 | return true, nil 72 | } 73 | 74 | bytes, err := io.ReadAll(rsp.Body) 75 | return false, fmt.Errorf("unexpected response status: %d - body: %s", rsp.StatusCode, bytes) 76 | } 77 | 78 | func DNSLookup(t *testing.T, suite *Suite, protocol string, serverIP string, serverPort int, host string) []string { 79 | t.Helper() 80 | 81 | ctx, cancel := context.WithTimeout(suite.Context(t), 1*time.Second) 82 | defer cancel() 83 | 84 | req := new(dns.Msg) 85 | req.SetQuestion(host, dns.TypeA) 86 | 87 | c := new(dns.Client) 88 | c.Net = protocol 89 | rsp, _, err := c.ExchangeContext( 90 | ctx, 91 | req, 92 | net.JoinHostPort(serverIP, strconv.Itoa(serverPort)), 93 | ) 94 | require.NoError(t, err) 95 | 96 | results := make([]string, len(rsp.Answer)) 97 | for idx, rr := range rsp.Answer { 98 | results[idx] = rr.(*dns.A).A.String() 99 | } 100 | return results 101 | } 102 | 103 | func GetMetrics(t *testing.T, ip string, port int) string { 104 | t.Helper() 105 | 106 | url := fmt.Sprintf("http://%s/metrics", net.JoinHostPort(ip, strconv.Itoa(port))) 107 | 108 | rsp, err := httpClient.Get(url) 109 | require.NoError(t, err) 110 | defer rsp.Body.Close() 111 | 112 | bytes, err := io.ReadAll(rsp.Body) 113 | require.NoError(t, err) 114 | 115 | if rsp.StatusCode != http.StatusOK { 116 | t.Fatalf("unexpected response status: %d - body: %s", rsp.StatusCode, bytes) 117 | } 118 | 119 | return string(bytes) 120 | 121 | } 122 | 123 | func GetEnvoyClusters(t *testing.T, ip string, port int) { 124 | t.Helper() 125 | 126 | url := fmt.Sprintf("http://%s/clusters", net.JoinHostPort(ip, strconv.Itoa(port))) 127 | 128 | _, err := httpClient.Get(url) 129 | require.NoError(t, err) 130 | } 131 | -------------------------------------------------------------------------------- /integration-tests/helpers/server.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package helpers 5 | 6 | import ( 7 | "net" 8 | "strconv" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | "github.com/testcontainers/testcontainers-go" 13 | "github.com/testcontainers/testcontainers-go/wait" 14 | 15 | "github.com/hashicorp/consul/api" 16 | ) 17 | 18 | const ( 19 | // SyntheticNodeName is the name given to the "synthetic" node services are 20 | // registered to. 21 | SyntheticNodeName = "synthetic-node" 22 | 23 | rootACLToken = "1e7038d4-53ff-4c18-a0c0-1e72d9c101dc" 24 | ) 25 | 26 | var ( 27 | serverHTTPPort = TCP(8500) 28 | serverGRPCPort = TCP(8502) 29 | 30 | serverConfig = ` 31 | server = true 32 | data_dir = "/consul/data" 33 | log_level = "debug" 34 | 35 | bootstrap_expect = 1 36 | 37 | acl { 38 | enabled = true 39 | default_policy = "deny" 40 | 41 | tokens { 42 | initial_management = "` + rootACLToken + `" 43 | default = "` + rootACLToken + `" 44 | } 45 | } 46 | 47 | connect { 48 | enabled = true 49 | } 50 | 51 | ports { 52 | http = ` + serverHTTPPort.Port() + ` 53 | grpc_tls = ` + serverGRPCPort.Port() + ` 54 | } 55 | 56 | tls { 57 | grpc { 58 | ca_file = "/data/ca-cert.pem" 59 | cert_file = "/data/server-cert.pem" 60 | key_file = "/data/server-key.pem" 61 | } 62 | } 63 | ` 64 | ) 65 | 66 | type ConsulServer struct { 67 | Container *Container 68 | Client *api.Client 69 | } 70 | 71 | // RunServer runs a Consul server. 72 | func RunServer(t *testing.T, suite *Suite) *ConsulServer { 73 | t.Helper() 74 | 75 | GenerateServerTLS(t, suite) 76 | 77 | volume := suite.Volume(t) 78 | volume.WriteFile(t, "server.hcl", []byte(serverConfig)) 79 | 80 | container := suite.RunContainer(t, "server", true, ContainerRequest{ 81 | Image: suite.opts.ServerImage, 82 | Mounts: []testcontainers.ContainerMount{ 83 | testcontainers.VolumeMount(volume.Name, "/data"), 84 | }, 85 | ExposedPorts: []string{string(serverHTTPPort)}, 86 | WaitingFor: wait.ForLog("successfully established leadership"), 87 | Cmd: []string{"consul", "agent", "-config-file", "/data/server.hcl", "-client", "0.0.0.0"}, 88 | }) 89 | 90 | client, err := api.NewClient(&api.Config{ 91 | Address: net.JoinHostPort(container.HostIP, strconv.Itoa(container.MappedPorts[serverHTTPPort])), 92 | Token: rootACLToken, 93 | }) 94 | require.NoError(t, err) 95 | 96 | return &ConsulServer{ 97 | Container: container, 98 | Client: client, 99 | } 100 | } 101 | 102 | func (s *ConsulServer) RegisterSyntheticNode(t *testing.T) { 103 | t.Helper() 104 | 105 | _, err := s.Client.Catalog().Register(&api.CatalogRegistration{ 106 | Node: SyntheticNodeName, 107 | Address: "127.0.0.1", 108 | }, nil) 109 | require.NoError(t, err) 110 | } 111 | 112 | func (s *ConsulServer) SetConfigEntry(t *testing.T, entry api.ConfigEntry) { 113 | t.Helper() 114 | 115 | _, _, err := s.Client.ConfigEntries().Set(entry, nil) 116 | require.NoError(t, err) 117 | } 118 | 119 | func (s *ConsulServer) RegisterService(t *testing.T, service *api.AgentService) { 120 | t.Helper() 121 | 122 | _, err := s.Client.Catalog().Register(&api.CatalogRegistration{ 123 | Node: SyntheticNodeName, 124 | SkipNodeUpdate: true, 125 | Service: service, 126 | }, nil) 127 | require.NoError(t, err) 128 | } 129 | -------------------------------------------------------------------------------- /_doc/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /pkg/metrics-cache/metricscache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package metricscache 5 | 6 | import ( 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/armon/go-metrics" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestMetricsCache_BasicPath(t *testing.T) { 16 | sink := NewSink() 17 | 18 | sink.SetGauge([]string{"mygauge"}, 1) 19 | sink.AddSample([]string{"mysample"}, 10) 20 | sink.AddSample([]string{"mysample"}, 100) 21 | sink.EmitKey([]string{"mykey"}, 3) 22 | sink.IncrCounter([]string{"mycounter"}, 4) 23 | sink.IncrCounter([]string{"mycounter"}, 8) 24 | sink.IncrCounter([]string{"mycounter"}, 16) 25 | 26 | realSink := metrics.NewInmemSink(time.Second, time.Second*1) 27 | sink.SetSink(realSink) 28 | sink.IncrCounter([]string{"mycounter"}, 32) 29 | 30 | data := realSink.Data() 31 | 32 | // Check before the interval is up if setting values will increase the metrics 33 | 34 | mygauge := data[0].Gauges["mygauge"] 35 | require.EqualValues(t, mygauge.Value, 1) 36 | 37 | // Samples based off setting twice: once at 10, once at 110 38 | mysamples := data[0].Samples["mysample"] 39 | require.EqualValues(t, 2, mysamples.Count) 40 | require.EqualValues(t, 110, mysamples.Sum) 41 | require.EqualValues(t, 10, mysamples.Min) 42 | require.EqualValues(t, 100, mysamples.Max) 43 | 44 | // Keys based off a single value of 3 45 | mykey := data[0].Points["mykey"] 46 | require.EqualValues(t, mykey[0], 3) 47 | 48 | // counter's based off being set four times with values 4, 8, 16, 32 49 | mycounter := data[0].Counters["mycounter"] 50 | require.EqualValues(t, 4, mycounter.Count) 51 | require.EqualValues(t, 60, mycounter.Sum) 52 | require.EqualValues(t, 4, mycounter.Min) 53 | require.EqualValues(t, 32, mycounter.Max) 54 | 55 | sink.IncrCounter([]string{"mycounter"}, 2) 56 | data = realSink.Data() 57 | mycounter = data[0].Counters["mycounter"] 58 | require.EqualValues(t, 5, mycounter.Count) 59 | require.EqualValues(t, 62, mycounter.Sum) 60 | require.EqualValues(t, 2, mycounter.Min) 61 | require.EqualValues(t, 32, mycounter.Max) 62 | 63 | } 64 | 65 | func TestMetricsCache_ParallelTest(t *testing.T) { 66 | sink := NewSink() 67 | // make interval so big we never get metrics split into multiple intervals 68 | realSink := metrics.NewInmemSink(time.Second, time.Second) 69 | 70 | wg := sync.WaitGroup{} 71 | wg.Add(4) 72 | go func() { 73 | for i := 0; i < 100; i++ { 74 | sink.SetGauge([]string{"mygauge"}, float32(i+1)) 75 | } 76 | wg.Done() 77 | }() 78 | 79 | go func() { 80 | for i := 0; i < 100; i++ { 81 | sink.AddSample([]string{"mysample"}, 1) 82 | } 83 | wg.Done() 84 | }() 85 | 86 | go func() { 87 | for i := 0; i < 100; i++ { 88 | sink.EmitKey([]string{"mykey"}, 1) 89 | } 90 | wg.Done() 91 | }() 92 | 93 | go func() { 94 | for i := 0; i < 100; i++ { 95 | sink.IncrCounter([]string{"mycounter"}, 1) 96 | } 97 | wg.Done() 98 | }() 99 | 100 | sink.SetSink(realSink) 101 | wg.Wait() 102 | 103 | data := realSink.Data() 104 | 105 | require.NotEmpty(t, data) 106 | 107 | mygauge := data[0].Gauges["mygauge"] 108 | mysamples := data[0].Samples["mysample"] 109 | mykey := data[0].Points["mykey"] 110 | mycounter := data[0].Counters["mycounter"] 111 | require.EqualValues(t, 100, mysamples.Count) 112 | require.EqualValues(t, 100, mygauge.Value) 113 | 114 | require.EqualValues(t, 100, len(mykey)) 115 | require.EqualValues(t, 100, mycounter.Count) 116 | 117 | } 118 | -------------------------------------------------------------------------------- /integration-tests/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/consul-dataplane/integration-tests 2 | 3 | go 1.25.3 4 | 5 | require ( 6 | github.com/docker/docker v27.3.1+incompatible 7 | github.com/docker/go-connections v0.4.0 8 | github.com/hashicorp/consul/api v1.33.0 9 | github.com/miekg/dns v1.1.50 10 | github.com/stretchr/testify v1.11.1 11 | github.com/testcontainers/testcontainers-go v0.17.0 12 | golang.org/x/mod v0.28.0 13 | gopkg.in/square/go-jose.v2 v2.6.0 14 | ) 15 | 16 | require ( 17 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 18 | github.com/Microsoft/go-winio v0.6.2 // indirect 19 | github.com/Microsoft/hcsshim v0.13.0 // indirect 20 | github.com/armon/go-metrics v0.4.1 // indirect 21 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 22 | github.com/containerd/containerd v1.7.28 // indirect 23 | github.com/containerd/continuity v0.4.5 // indirect 24 | github.com/containerd/log v0.1.0 // indirect 25 | github.com/containerd/platforms v0.2.1 // indirect 26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 27 | github.com/docker/distribution v2.8.3+incompatible // indirect 28 | github.com/docker/go-units v0.5.0 // indirect 29 | github.com/fatih/color v1.16.0 // indirect 30 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/google/go-cmp v0.7.0 // indirect 33 | github.com/google/uuid v1.6.0 // indirect 34 | github.com/hashicorp/errwrap v1.1.0 // indirect 35 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 36 | github.com/hashicorp/go-hclog v1.5.0 // indirect 37 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 38 | github.com/hashicorp/go-multierror v1.1.1 // indirect 39 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 40 | github.com/hashicorp/golang-lru v0.5.4 // indirect 41 | github.com/hashicorp/serf v0.10.1 // indirect 42 | github.com/klauspost/compress v1.18.0 // indirect 43 | github.com/kr/pretty v0.3.1 // indirect 44 | github.com/magiconair/properties v1.8.7 // indirect 45 | github.com/mattn/go-colorable v0.1.13 // indirect 46 | github.com/mattn/go-isatty v0.0.20 // indirect 47 | github.com/mitchellh/go-homedir v1.1.0 // indirect 48 | github.com/moby/patternmatcher v0.5.0 // indirect 49 | github.com/moby/sys/sequential v0.6.0 // indirect 50 | github.com/moby/sys/user v0.4.0 // indirect 51 | github.com/moby/sys/userns v0.1.0 // indirect 52 | github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f // indirect 53 | github.com/morikuni/aec v1.0.0 // indirect 54 | github.com/opencontainers/go-digest v1.0.0 // indirect 55 | github.com/opencontainers/image-spec v1.1.1 // indirect 56 | github.com/opencontainers/runc v1.2.3 // indirect 57 | github.com/pkg/errors v0.9.1 // indirect 58 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 59 | github.com/rogpeppe/go-internal v1.14.1 // indirect 60 | github.com/sirupsen/logrus v1.9.3 // indirect 61 | golang.org/x/crypto v0.43.0 // indirect 62 | golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect 63 | golang.org/x/net v0.46.0 // indirect 64 | golang.org/x/sync v0.17.0 // indirect 65 | golang.org/x/sys v0.37.0 // indirect 66 | golang.org/x/tools v0.37.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | gotest.tools/v3 v3.5.2 // indirect 69 | ) 70 | 71 | // Hack for the latest version of testcontainers to work 72 | // https://golang.testcontainers.org/quickstart/#2-install-testcontainers-for-go 73 | replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221013203545-33ab36d6b304+incompatible // 22.06 branch 74 | 75 | // Fix compatibility issue with docker/distribution and reference packages 76 | replace github.com/docker/distribution => github.com/docker/distribution v2.8.2+incompatible 77 | -------------------------------------------------------------------------------- /pkg/dns/mocks/mock_DNSServiceClient.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.37.1. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | 8 | grpc "google.golang.org/grpc" 9 | 10 | mock "github.com/stretchr/testify/mock" 11 | 12 | pbdns "github.com/hashicorp/consul/proto-public/pbdns" 13 | ) 14 | 15 | // DNSServiceClient is an autogenerated mock type for the DNSServiceClient type 16 | type DNSServiceClient struct { 17 | mock.Mock 18 | } 19 | 20 | type DNSServiceClient_Expecter struct { 21 | mock *mock.Mock 22 | } 23 | 24 | func (_m *DNSServiceClient) EXPECT() *DNSServiceClient_Expecter { 25 | return &DNSServiceClient_Expecter{mock: &_m.Mock} 26 | } 27 | 28 | // Query provides a mock function with given fields: ctx, in, opts 29 | func (_m *DNSServiceClient) Query(ctx context.Context, in *pbdns.QueryRequest, opts ...grpc.CallOption) (*pbdns.QueryResponse, error) { 30 | _va := make([]interface{}, len(opts)) 31 | for _i := range opts { 32 | _va[_i] = opts[_i] 33 | } 34 | var _ca []interface{} 35 | _ca = append(_ca, ctx, in) 36 | _ca = append(_ca, _va...) 37 | ret := _m.Called(_ca...) 38 | 39 | var r0 *pbdns.QueryResponse 40 | var r1 error 41 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) (*pbdns.QueryResponse, error)); ok { 42 | return rf(ctx, in, opts...) 43 | } 44 | if rf, ok := ret.Get(0).(func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) *pbdns.QueryResponse); ok { 45 | r0 = rf(ctx, in, opts...) 46 | } else { 47 | if ret.Get(0) != nil { 48 | r0 = ret.Get(0).(*pbdns.QueryResponse) 49 | } 50 | } 51 | 52 | if rf, ok := ret.Get(1).(func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) error); ok { 53 | r1 = rf(ctx, in, opts...) 54 | } else { 55 | r1 = ret.Error(1) 56 | } 57 | 58 | return r0, r1 59 | } 60 | 61 | // DNSServiceClient_Query_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Query' 62 | type DNSServiceClient_Query_Call struct { 63 | *mock.Call 64 | } 65 | 66 | // Query is a helper method to define mock.On call 67 | // - ctx context.Context 68 | // - in *pbdns.QueryRequest 69 | // - opts ...grpc.CallOption 70 | func (_e *DNSServiceClient_Expecter) Query(ctx interface{}, in interface{}, opts ...interface{}) *DNSServiceClient_Query_Call { 71 | return &DNSServiceClient_Query_Call{Call: _e.mock.On("Query", 72 | append([]interface{}{ctx, in}, opts...)...)} 73 | } 74 | 75 | func (_c *DNSServiceClient_Query_Call) Run(run func(ctx context.Context, in *pbdns.QueryRequest, opts ...grpc.CallOption)) *DNSServiceClient_Query_Call { 76 | _c.Call.Run(func(args mock.Arguments) { 77 | variadicArgs := make([]grpc.CallOption, len(args)-2) 78 | for i, a := range args[2:] { 79 | if a != nil { 80 | variadicArgs[i] = a.(grpc.CallOption) 81 | } 82 | } 83 | run(args[0].(context.Context), args[1].(*pbdns.QueryRequest), variadicArgs...) 84 | }) 85 | return _c 86 | } 87 | 88 | func (_c *DNSServiceClient_Query_Call) Return(_a0 *pbdns.QueryResponse, _a1 error) *DNSServiceClient_Query_Call { 89 | _c.Call.Return(_a0, _a1) 90 | return _c 91 | } 92 | 93 | func (_c *DNSServiceClient_Query_Call) RunAndReturn(run func(context.Context, *pbdns.QueryRequest, ...grpc.CallOption) (*pbdns.QueryResponse, error)) *DNSServiceClient_Query_Call { 94 | _c.Call.Return(run) 95 | return _c 96 | } 97 | 98 | // NewDNSServiceClient creates a new instance of DNSServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 99 | // The first argument is typically a *testing.T value. 100 | func NewDNSServiceClient(t interface { 101 | mock.TestingT 102 | Cleanup(func()) 103 | }) *DNSServiceClient { 104 | mock := &DNSServiceClient{} 105 | mock.Mock.Test(t) 106 | 107 | t.Cleanup(func() { mock.AssertExpectations(t) }) 108 | 109 | return mock 110 | } 111 | -------------------------------------------------------------------------------- /internal/mocks/pbresourcemock/resource_service__watch_list_server.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbresourcemock 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | metadata "google.golang.org/grpc/metadata" 10 | 11 | pbresource "github.com/hashicorp/consul/proto-public/pbresource" 12 | ) 13 | 14 | // ResourceService_WatchListServer is an autogenerated mock type for the ResourceService_WatchListServer type 15 | type ResourceService_WatchListServer struct { 16 | mock.Mock 17 | } 18 | 19 | // Context provides a mock function with given fields: 20 | func (_m *ResourceService_WatchListServer) Context() context.Context { 21 | ret := _m.Called() 22 | 23 | if len(ret) == 0 { 24 | panic("no return value specified for Context") 25 | } 26 | 27 | var r0 context.Context 28 | if rf, ok := ret.Get(0).(func() context.Context); ok { 29 | r0 = rf() 30 | } else { 31 | if ret.Get(0) != nil { 32 | r0 = ret.Get(0).(context.Context) 33 | } 34 | } 35 | 36 | return r0 37 | } 38 | 39 | // RecvMsg provides a mock function with given fields: m 40 | func (_m *ResourceService_WatchListServer) RecvMsg(m interface{}) error { 41 | ret := _m.Called(m) 42 | 43 | if len(ret) == 0 { 44 | panic("no return value specified for RecvMsg") 45 | } 46 | 47 | var r0 error 48 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 49 | r0 = rf(m) 50 | } else { 51 | r0 = ret.Error(0) 52 | } 53 | 54 | return r0 55 | } 56 | 57 | // Send provides a mock function with given fields: _a0 58 | func (_m *ResourceService_WatchListServer) Send(_a0 *pbresource.WatchEvent) error { 59 | ret := _m.Called(_a0) 60 | 61 | if len(ret) == 0 { 62 | panic("no return value specified for Send") 63 | } 64 | 65 | var r0 error 66 | if rf, ok := ret.Get(0).(func(*pbresource.WatchEvent) error); ok { 67 | r0 = rf(_a0) 68 | } else { 69 | r0 = ret.Error(0) 70 | } 71 | 72 | return r0 73 | } 74 | 75 | // SendHeader provides a mock function with given fields: _a0 76 | func (_m *ResourceService_WatchListServer) SendHeader(_a0 metadata.MD) error { 77 | ret := _m.Called(_a0) 78 | 79 | if len(ret) == 0 { 80 | panic("no return value specified for SendHeader") 81 | } 82 | 83 | var r0 error 84 | if rf, ok := ret.Get(0).(func(metadata.MD) error); ok { 85 | r0 = rf(_a0) 86 | } else { 87 | r0 = ret.Error(0) 88 | } 89 | 90 | return r0 91 | } 92 | 93 | // SendMsg provides a mock function with given fields: m 94 | func (_m *ResourceService_WatchListServer) SendMsg(m interface{}) error { 95 | ret := _m.Called(m) 96 | 97 | if len(ret) == 0 { 98 | panic("no return value specified for SendMsg") 99 | } 100 | 101 | var r0 error 102 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 103 | r0 = rf(m) 104 | } else { 105 | r0 = ret.Error(0) 106 | } 107 | 108 | return r0 109 | } 110 | 111 | // SetHeader provides a mock function with given fields: _a0 112 | func (_m *ResourceService_WatchListServer) SetHeader(_a0 metadata.MD) error { 113 | ret := _m.Called(_a0) 114 | 115 | if len(ret) == 0 { 116 | panic("no return value specified for SetHeader") 117 | } 118 | 119 | var r0 error 120 | if rf, ok := ret.Get(0).(func(metadata.MD) error); ok { 121 | r0 = rf(_a0) 122 | } else { 123 | r0 = ret.Error(0) 124 | } 125 | 126 | return r0 127 | } 128 | 129 | // SetTrailer provides a mock function with given fields: _a0 130 | func (_m *ResourceService_WatchListServer) SetTrailer(_a0 metadata.MD) { 131 | _m.Called(_a0) 132 | } 133 | 134 | // NewResourceService_WatchListServer creates a new instance of ResourceService_WatchListServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 135 | // The first argument is typically a *testing.T value. 136 | func NewResourceService_WatchListServer(t interface { 137 | mock.TestingT 138 | Cleanup(func()) 139 | }) *ResourceService_WatchListServer { 140 | mock := &ResourceService_WatchListServer{} 141 | mock.Mock.Test(t) 142 | 143 | t.Cleanup(func() { mock.AssertExpectations(t) }) 144 | 145 | return mock 146 | } 147 | -------------------------------------------------------------------------------- /.github/workflows/jira-issues.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: [opened, closed, deleted, reopened] 4 | issue_comment: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | name: Jira Community Issue Sync 9 | 10 | jobs: 11 | sync: 12 | runs-on: ubuntu-latest 13 | name: Jira Community Issue sync 14 | steps: 15 | - name: Login 16 | uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 17 | env: 18 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 19 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 20 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 21 | 22 | - name: Set ticket type 23 | id: set-ticket-type 24 | run: | 25 | echo "TYPE=GH Issue" >> $GITHUB_OUTPUT 26 | 27 | - name: Set ticket labels 28 | if: github.event.action == 'opened' 29 | id: set-ticket-labels 30 | run: | 31 | LABELS="[" 32 | if [[ "${{ contains(github.event.issue.labels.*.name, 'type/bug') }}" == "true" ]]; then LABELS+="\"type/bug\", "; fi 33 | if [[ "${{ contains(github.event.issue.labels.*.name, 'type/enhancement') }}" == "true" ]]; then LABELS+="\"type/enhancement\", "; fi 34 | if [[ "${{ contains(github.event.issue.labels.*.name, 'type/docs') }}" == "true" ]]; then LABELS+="\"type/docs\", "; fi 35 | if [[ ${#LABELS} != 1 ]]; then LABELS=${LABELS::-2}"]"; else LABELS+="]"; fi 36 | echo "LABELS=${LABELS}" >> $GITHUB_OUTPUT 37 | 38 | - name: Create ticket if an issue is filed, or if PR not by a team member is opened 39 | if: github.event.action == 'opened' 40 | uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1 41 | with: 42 | project: CSL 43 | issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" 44 | summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.issue.number }}]: ${{ github.event.issue.title }}" 45 | description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" 46 | # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) 47 | extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", 48 | "customfield_10371": { "value": "GitHub" }, 49 | "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' 50 | env: 51 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 52 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 53 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 54 | 55 | - name: Search 56 | if: github.event.action != 'opened' 57 | id: search 58 | uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 59 | with: 60 | # cf[10089] is Issue Link (use JIRA API to retrieve) 61 | jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' 62 | 63 | - name: Sync comment 64 | uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0 65 | with: 66 | issue: ${{ steps.search.outputs.issue }} 67 | comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" 68 | 69 | - name: Close ticket 70 | if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue 71 | uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 72 | with: 73 | issue: ${{ steps.search.outputs.issue }} 74 | transition: "Closed" 75 | 76 | - name: Reopen ticket 77 | if: github.event.action == 'reopened' && steps.search.outputs.issue 78 | uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 79 | with: 80 | issue: ${{ steps.search.outputs.issue }} 81 | transition: "To Do" 82 | -------------------------------------------------------------------------------- /internal/mocks/pbresourcemock/server_stream.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbresourcemock 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | metadata "google.golang.org/grpc/metadata" 10 | 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | ) 13 | 14 | // serverStream is an autogenerated mock type for the serverStream type 15 | type serverStream[T protoreflect.ProtoMessage] struct { 16 | mock.Mock 17 | } 18 | 19 | // CloseSend provides a mock function with given fields: 20 | func (_m *serverStream[T]) CloseSend() error { 21 | ret := _m.Called() 22 | 23 | if len(ret) == 0 { 24 | panic("no return value specified for CloseSend") 25 | } 26 | 27 | var r0 error 28 | if rf, ok := ret.Get(0).(func() error); ok { 29 | r0 = rf() 30 | } else { 31 | r0 = ret.Error(0) 32 | } 33 | 34 | return r0 35 | } 36 | 37 | // Context provides a mock function with given fields: 38 | func (_m *serverStream[T]) Context() context.Context { 39 | ret := _m.Called() 40 | 41 | if len(ret) == 0 { 42 | panic("no return value specified for Context") 43 | } 44 | 45 | var r0 context.Context 46 | if rf, ok := ret.Get(0).(func() context.Context); ok { 47 | r0 = rf() 48 | } else { 49 | if ret.Get(0) != nil { 50 | r0 = ret.Get(0).(context.Context) 51 | } 52 | } 53 | 54 | return r0 55 | } 56 | 57 | // Header provides a mock function with given fields: 58 | func (_m *serverStream[T]) Header() (metadata.MD, error) { 59 | ret := _m.Called() 60 | 61 | if len(ret) == 0 { 62 | panic("no return value specified for Header") 63 | } 64 | 65 | var r0 metadata.MD 66 | var r1 error 67 | if rf, ok := ret.Get(0).(func() (metadata.MD, error)); ok { 68 | return rf() 69 | } 70 | if rf, ok := ret.Get(0).(func() metadata.MD); ok { 71 | r0 = rf() 72 | } else { 73 | if ret.Get(0) != nil { 74 | r0 = ret.Get(0).(metadata.MD) 75 | } 76 | } 77 | 78 | if rf, ok := ret.Get(1).(func() error); ok { 79 | r1 = rf() 80 | } else { 81 | r1 = ret.Error(1) 82 | } 83 | 84 | return r0, r1 85 | } 86 | 87 | // Recv provides a mock function with given fields: 88 | func (_m *serverStream[T]) Recv() (T, error) { 89 | ret := _m.Called() 90 | 91 | if len(ret) == 0 { 92 | panic("no return value specified for Recv") 93 | } 94 | 95 | var r0 T 96 | var r1 error 97 | if rf, ok := ret.Get(0).(func() (T, error)); ok { 98 | return rf() 99 | } 100 | if rf, ok := ret.Get(0).(func() T); ok { 101 | r0 = rf() 102 | } else { 103 | r0 = ret.Get(0).(T) 104 | } 105 | 106 | if rf, ok := ret.Get(1).(func() error); ok { 107 | r1 = rf() 108 | } else { 109 | r1 = ret.Error(1) 110 | } 111 | 112 | return r0, r1 113 | } 114 | 115 | // RecvMsg provides a mock function with given fields: m 116 | func (_m *serverStream[T]) RecvMsg(m interface{}) error { 117 | ret := _m.Called(m) 118 | 119 | if len(ret) == 0 { 120 | panic("no return value specified for RecvMsg") 121 | } 122 | 123 | var r0 error 124 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 125 | r0 = rf(m) 126 | } else { 127 | r0 = ret.Error(0) 128 | } 129 | 130 | return r0 131 | } 132 | 133 | // SendMsg provides a mock function with given fields: m 134 | func (_m *serverStream[T]) SendMsg(m interface{}) error { 135 | ret := _m.Called(m) 136 | 137 | if len(ret) == 0 { 138 | panic("no return value specified for SendMsg") 139 | } 140 | 141 | var r0 error 142 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 143 | r0 = rf(m) 144 | } else { 145 | r0 = ret.Error(0) 146 | } 147 | 148 | return r0 149 | } 150 | 151 | // Trailer provides a mock function with given fields: 152 | func (_m *serverStream[T]) Trailer() metadata.MD { 153 | ret := _m.Called() 154 | 155 | if len(ret) == 0 { 156 | panic("no return value specified for Trailer") 157 | } 158 | 159 | var r0 metadata.MD 160 | if rf, ok := ret.Get(0).(func() metadata.MD); ok { 161 | r0 = rf() 162 | } else { 163 | if ret.Get(0) != nil { 164 | r0 = ret.Get(0).(metadata.MD) 165 | } 166 | } 167 | 168 | return r0 169 | } 170 | 171 | // newServerStream creates a new instance of serverStream. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 172 | // The first argument is typically a *testing.T value. 173 | func newServerStream[T protoreflect.ProtoMessage](t interface { 174 | mock.TestingT 175 | Cleanup(func()) 176 | }) *serverStream[T] { 177 | mock := &serverStream[T]{} 178 | mock.Mock.Test(t) 179 | 180 | t.Cleanup(func() { mock.AssertExpectations(t) }) 181 | 182 | return mock 183 | } 184 | -------------------------------------------------------------------------------- /.github/workflows/jira-pr.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request_target: 3 | types: [opened, closed, reopened] 4 | workflow_dispatch: 5 | 6 | name: Jira Community PR Sync 7 | 8 | jobs: 9 | sync: 10 | runs-on: ubuntu-latest 11 | name: Jira sync 12 | steps: 13 | - name: Login 14 | uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 15 | env: 16 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 17 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 18 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 19 | 20 | - name: Set ticket type 21 | id: set-ticket-type 22 | run: | 23 | echo "TYPE=GH Issue" >> $GITHUB_OUTPUT 24 | 25 | - name: Set ticket labels 26 | if: github.event.action == 'opened' 27 | id: set-ticket-labels 28 | run: | 29 | LABELS="[" 30 | if [[ "${{ contains(github.event.issue.labels.*.name, 'type/bug') }}" == "true" ]]; then LABELS+="\"type/bug\", "; fi 31 | if [[ "${{ contains(github.event.issue.labels.*.name, 'type/enhancement') }}" == "true" ]]; then LABELS+="\"type/enhancement\", "; fi 32 | if [[ ${#LABELS} != 1 ]]; then LABELS=${LABELS::-2}"]"; else LABELS+="]"; fi 33 | echo "LABELS=${LABELS}" >> $GITHUB_OUTPUT 34 | 35 | - name: Check if team member 36 | if: github.event.action == 'opened' 37 | id: is-team-member 38 | run: | 39 | TEAM=consul 40 | ROLE="$(gh api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" 41 | if [[ -n ${ROLE} ]]; then 42 | echo "Actor ${{ github.actor }} is a ${TEAM} team member" 43 | echo "MESSAGE=true" >> $GITHUB_OUTPUT 44 | else 45 | echo "Actor ${{ github.actor }} is NOT a ${TEAM} team member" 46 | echo "MESSAGE=false" >> $GITHUB_OUTPUT 47 | fi 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }} 50 | 51 | - name: Create ticket if an issue is filed, or if PR not by a team member is opened 52 | if: ( github.event.action == 'opened' && steps.is-team-member.outputs.MESSAGE == 'false' ) 53 | uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1 54 | with: 55 | project: CSL 56 | issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" 57 | summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.pull_request.number }}]: ${{ github.event.pull_request.title }}" 58 | description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" 59 | # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) 60 | extraFields: '{ "customfield_10089": "${{ github.event.pull_request.html_url }}", 61 | "customfield_10371": { "value": "GitHub" }, 62 | "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' 63 | env: 64 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 65 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 66 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 67 | 68 | - name: Search 69 | if: github.event.action != 'opened' 70 | id: search 71 | uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 72 | with: 73 | # cf[10089] is Issue Link (use JIRA API to retrieve) 74 | jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' 75 | 76 | - name: Sync comment 77 | if: github.event.action == 'created' && steps.search.outputs.issue 78 | uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0 79 | with: 80 | issue: ${{ steps.search.outputs.issue }} 81 | comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" 82 | 83 | - name: Close ticket 84 | if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue 85 | uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 86 | with: 87 | issue: ${{ steps.search.outputs.issue }} 88 | transition: "Closed" 89 | 90 | - name: Reopen ticket 91 | if: github.event.action == 'reopened' && steps.search.outputs.issue 92 | uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 93 | with: 94 | issue: ${{ steps.search.outputs.issue }} 95 | transition: "To Do" 96 | -------------------------------------------------------------------------------- /pkg/metrics-cache/metricscache.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package metricscache 5 | 6 | import ( 7 | "sync" 8 | "sync/atomic" 9 | 10 | "github.com/armon/go-metrics" 11 | ) 12 | 13 | type metric struct { 14 | key []string 15 | val float32 16 | labels []metrics.Label 17 | } 18 | 19 | // Sink is a temporary sink that caches metrics until a real sink is set in SetSink. 20 | // it implements the metrics.MetricSink interface 21 | type Sink struct { 22 | gauges []metric 23 | counters []metric 24 | samples []metric 25 | keys []metric 26 | 27 | realSink metrics.MetricSink 28 | 29 | mu sync.Mutex 30 | checkLock *atomic.Bool 31 | once sync.Once 32 | } 33 | 34 | // NewSink returns a pointer to a sink with empty cache 35 | func NewSink() *Sink { 36 | checkLock := &atomic.Bool{} 37 | checkLock.Store(true) 38 | 39 | return &Sink{ 40 | gauges: []metric{}, 41 | counters: []metric{}, 42 | samples: []metric{}, 43 | keys: []metric{}, 44 | mu: sync.Mutex{}, // this lock is used to control access around the slices of metrics above 45 | checkLock: checkLock, // we only need to check the lock if we haven't yet set the real sink 46 | } 47 | } 48 | 49 | // SetGauge defaults to SetGaugeWithLabels 50 | func (s *Sink) SetGauge(key []string, val float32) { 51 | s.SetGaugeWithLabels(key, val, nil) 52 | } 53 | 54 | // SetGaugeWithLabels sends metrics to the real sink otherwise caches them 55 | func (s *Sink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) { 56 | if ok := s.checkLock.Load(); ok { 57 | s.mu.Lock() 58 | defer s.mu.Unlock() 59 | } 60 | 61 | if s.realSink != nil { 62 | s.realSink.SetGaugeWithLabels(key, val, labels) 63 | return 64 | } 65 | 66 | s.gauges = append(s.gauges, metric{key: key, val: val, labels: labels}) 67 | } 68 | 69 | // EmitKey sends metrics to the real sink otherwise caches them 70 | func (s *Sink) EmitKey(key []string, val float32) { 71 | if ok := s.checkLock.Load(); ok { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | } 75 | 76 | if s.realSink != nil { 77 | s.realSink.EmitKey(key, val) 78 | return 79 | } 80 | 81 | s.keys = append(s.keys, metric{key: key, val: val}) 82 | } 83 | 84 | // IncrCounter defaults to IncrCounterWithLabels 85 | func (s *Sink) IncrCounter(key []string, val float32) { 86 | s.IncrCounterWithLabels(key, val, nil) 87 | 88 | } 89 | 90 | // IncrCounterWithLabels sends metrics to the real sink otherwise caches them 91 | func (s *Sink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) { 92 | if ok := s.checkLock.Load(); ok { 93 | s.mu.Lock() 94 | defer s.mu.Unlock() 95 | } 96 | 97 | if s.realSink != nil { 98 | s.realSink.IncrCounterWithLabels(key, val, labels) 99 | return 100 | } 101 | s.counters = append(s.counters, metric{key: key, val: val, labels: labels}) 102 | } 103 | 104 | // AddSample defaults to AddSampleWithLabels 105 | func (s *Sink) AddSample(key []string, val float32) { 106 | s.AddSampleWithLabels(key, val, nil) 107 | } 108 | 109 | // AddSampleWithLabels sends metrics to the real sink otherwise caches them 110 | func (s *Sink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) { 111 | if ok := s.checkLock.Load(); ok { 112 | s.mu.Lock() 113 | defer s.mu.Unlock() 114 | } 115 | 116 | if s.realSink != nil { 117 | s.realSink.AddSampleWithLabels(key, val, labels) 118 | return 119 | } 120 | s.samples = append(s.samples, metric{key: key, val: val, labels: labels}) 121 | } 122 | 123 | // SetSink takes a sink and will ensure that the sink sets the value 124 | // and then starts forwarding metrics on to the realSink once called. 125 | // It will also replay all the cached metrics and send them to the realSink 126 | func (s *Sink) SetSink(newSink metrics.MetricSink) { 127 | s.once.Do(func() { 128 | s.mu.Lock() 129 | defer s.mu.Unlock() 130 | s.checkLock.Store(false) 131 | s.realSink = newSink 132 | s.replay() 133 | }) 134 | } 135 | 136 | // replay will send cached metrics to the realSink. Once done it will empty the cached store. 137 | func (s *Sink) replay() { 138 | if s.realSink != nil { 139 | for _, sample := range s.samples { 140 | s.realSink.AddSampleWithLabels(sample.key, sample.val, sample.labels) 141 | } 142 | s.samples = []metric{} // empty out after replaying samples 143 | for _, gauge := range s.gauges { 144 | s.realSink.SetGaugeWithLabels(gauge.key, gauge.val, gauge.labels) 145 | } 146 | s.gauges = []metric{} // empty out after replaying gauges 147 | for _, counter := range s.counters { 148 | s.realSink.IncrCounterWithLabels(counter.key, counter.val, counter.labels) 149 | } 150 | s.counters = []metric{} // empty out after replaying counters 151 | for _, key := range s.keys { 152 | s.realSink.EmitKey(key.key, key.val) 153 | } 154 | s.keys = []metric{} // empty out after replaying keys 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /internal/mocks/pbresourcemock/resource_service__watch_list_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.41.0. DO NOT EDIT. 2 | 3 | package pbresourcemock 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | metadata "google.golang.org/grpc/metadata" 10 | 11 | pbresource "github.com/hashicorp/consul/proto-public/pbresource" 12 | ) 13 | 14 | // ResourceService_WatchListClient is an autogenerated mock type for the ResourceService_WatchListClient type 15 | type ResourceService_WatchListClient struct { 16 | mock.Mock 17 | } 18 | 19 | // CloseSend provides a mock function with given fields: 20 | func (_m *ResourceService_WatchListClient) CloseSend() error { 21 | ret := _m.Called() 22 | 23 | if len(ret) == 0 { 24 | panic("no return value specified for CloseSend") 25 | } 26 | 27 | var r0 error 28 | if rf, ok := ret.Get(0).(func() error); ok { 29 | r0 = rf() 30 | } else { 31 | r0 = ret.Error(0) 32 | } 33 | 34 | return r0 35 | } 36 | 37 | // Context provides a mock function with given fields: 38 | func (_m *ResourceService_WatchListClient) Context() context.Context { 39 | ret := _m.Called() 40 | 41 | if len(ret) == 0 { 42 | panic("no return value specified for Context") 43 | } 44 | 45 | var r0 context.Context 46 | if rf, ok := ret.Get(0).(func() context.Context); ok { 47 | r0 = rf() 48 | } else { 49 | if ret.Get(0) != nil { 50 | r0 = ret.Get(0).(context.Context) 51 | } 52 | } 53 | 54 | return r0 55 | } 56 | 57 | // Header provides a mock function with given fields: 58 | func (_m *ResourceService_WatchListClient) Header() (metadata.MD, error) { 59 | ret := _m.Called() 60 | 61 | if len(ret) == 0 { 62 | panic("no return value specified for Header") 63 | } 64 | 65 | var r0 metadata.MD 66 | var r1 error 67 | if rf, ok := ret.Get(0).(func() (metadata.MD, error)); ok { 68 | return rf() 69 | } 70 | if rf, ok := ret.Get(0).(func() metadata.MD); ok { 71 | r0 = rf() 72 | } else { 73 | if ret.Get(0) != nil { 74 | r0 = ret.Get(0).(metadata.MD) 75 | } 76 | } 77 | 78 | if rf, ok := ret.Get(1).(func() error); ok { 79 | r1 = rf() 80 | } else { 81 | r1 = ret.Error(1) 82 | } 83 | 84 | return r0, r1 85 | } 86 | 87 | // Recv provides a mock function with given fields: 88 | func (_m *ResourceService_WatchListClient) Recv() (*pbresource.WatchEvent, error) { 89 | ret := _m.Called() 90 | 91 | if len(ret) == 0 { 92 | panic("no return value specified for Recv") 93 | } 94 | 95 | var r0 *pbresource.WatchEvent 96 | var r1 error 97 | if rf, ok := ret.Get(0).(func() (*pbresource.WatchEvent, error)); ok { 98 | return rf() 99 | } 100 | if rf, ok := ret.Get(0).(func() *pbresource.WatchEvent); ok { 101 | r0 = rf() 102 | } else { 103 | if ret.Get(0) != nil { 104 | r0 = ret.Get(0).(*pbresource.WatchEvent) 105 | } 106 | } 107 | 108 | if rf, ok := ret.Get(1).(func() error); ok { 109 | r1 = rf() 110 | } else { 111 | r1 = ret.Error(1) 112 | } 113 | 114 | return r0, r1 115 | } 116 | 117 | // RecvMsg provides a mock function with given fields: m 118 | func (_m *ResourceService_WatchListClient) RecvMsg(m interface{}) error { 119 | ret := _m.Called(m) 120 | 121 | if len(ret) == 0 { 122 | panic("no return value specified for RecvMsg") 123 | } 124 | 125 | var r0 error 126 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 127 | r0 = rf(m) 128 | } else { 129 | r0 = ret.Error(0) 130 | } 131 | 132 | return r0 133 | } 134 | 135 | // SendMsg provides a mock function with given fields: m 136 | func (_m *ResourceService_WatchListClient) SendMsg(m interface{}) error { 137 | ret := _m.Called(m) 138 | 139 | if len(ret) == 0 { 140 | panic("no return value specified for SendMsg") 141 | } 142 | 143 | var r0 error 144 | if rf, ok := ret.Get(0).(func(interface{}) error); ok { 145 | r0 = rf(m) 146 | } else { 147 | r0 = ret.Error(0) 148 | } 149 | 150 | return r0 151 | } 152 | 153 | // Trailer provides a mock function with given fields: 154 | func (_m *ResourceService_WatchListClient) Trailer() metadata.MD { 155 | ret := _m.Called() 156 | 157 | if len(ret) == 0 { 158 | panic("no return value specified for Trailer") 159 | } 160 | 161 | var r0 metadata.MD 162 | if rf, ok := ret.Get(0).(func() metadata.MD); ok { 163 | r0 = rf() 164 | } else { 165 | if ret.Get(0) != nil { 166 | r0 = ret.Get(0).(metadata.MD) 167 | } 168 | } 169 | 170 | return r0 171 | } 172 | 173 | // NewResourceService_WatchListClient creates a new instance of ResourceService_WatchListClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 174 | // The first argument is typically a *testing.T value. 175 | func NewResourceService_WatchListClient(t interface { 176 | mock.TestingT 177 | Cleanup(func()) 178 | }) *ResourceService_WatchListClient { 179 | mock := &ResourceService_WatchListClient{} 180 | mock.Mock.Test(t) 181 | 182 | t.Cleanup(func() { mock.AssertExpectations(t) }) 183 | 184 | return mock 185 | } 186 | -------------------------------------------------------------------------------- /pkg/consuldp/bootstrap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package consuldp 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "os" 11 | "strconv" 12 | 13 | "github.com/hashicorp/consul/proto-public/pbdataplane" 14 | "github.com/mitchellh/mapstructure" 15 | 16 | "github.com/hashicorp/consul-dataplane/internal/bootstrap" 17 | ) 18 | 19 | const ( 20 | // This is the name of the Envoy cluster used to communicate with the 21 | // dataplane process via xDS. 22 | localClusterName = "consul-dataplane" 23 | 24 | // By default we send logs from Envoy's admin interface to /dev/null. 25 | defaultAdminAccessLogsPath = os.DevNull 26 | ) 27 | 28 | // getBootstrapParams makes a call using the service client to get the bootstrap params for eventually getting the Envoy bootstrap config. 29 | func (cdp *ConsulDataplane) getBootstrapParams(ctx context.Context) (*pbdataplane.GetEnvoyBootstrapParamsResponse, error) { 30 | svc := cdp.cfg.Proxy 31 | 32 | req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ 33 | ServiceId: svc.ProxyID, 34 | ProxyId: svc.ProxyID, 35 | Namespace: svc.Namespace, 36 | Partition: svc.Partition, 37 | } 38 | 39 | if svc.NodeID != "" { 40 | req.NodeSpec = &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeId{ 41 | NodeId: svc.NodeID, 42 | } 43 | } else { 44 | req.NodeSpec = &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeName{ 45 | NodeName: svc.NodeName, 46 | } 47 | } 48 | 49 | rsp, err := cdp.dpServiceClient.GetEnvoyBootstrapParams(ctx, req) 50 | if err != nil { 51 | return nil, fmt.Errorf("failed to get envoy bootstrap params: %w", err) 52 | } 53 | 54 | return rsp, nil 55 | } 56 | 57 | // bootstrapConfig generates the Envoy bootstrap config in JSON format. 58 | func (cdp *ConsulDataplane) bootstrapConfig( 59 | bootstrapParams *pbdataplane.GetEnvoyBootstrapParamsResponse) (*bootstrap.BootstrapConfig, []byte, error) { 60 | svc := cdp.cfg.Proxy 61 | envoy := cdp.cfg.Envoy 62 | 63 | prom := cdp.cfg.Telemetry.Prometheus 64 | args := &bootstrap.BootstrapTplArgs{ 65 | GRPC: bootstrap.GRPC{ 66 | AgentAddress: cdp.cfg.XDSServer.BindAddress, 67 | AgentPort: strconv.Itoa(cdp.cfg.XDSServer.BindPort), 68 | AgentTLS: false, 69 | }, 70 | ProxyCluster: bootstrapParams.Service, 71 | ProxyID: svc.ProxyID, 72 | NodeName: bootstrapParams.NodeName, 73 | ProxySourceService: bootstrapParams.Service, 74 | AdminAccessLogConfig: bootstrapParams.AccessLogs, 75 | AdminAccessLogPath: defaultAdminAccessLogsPath, 76 | AdminBindAddress: envoy.AdminBindAddress, 77 | AdminBindPort: strconv.Itoa(envoy.AdminBindPort), 78 | LocalAgentClusterName: localClusterName, 79 | Namespace: bootstrapParams.Namespace, 80 | Partition: bootstrapParams.Partition, 81 | Datacenter: bootstrapParams.Datacenter, 82 | PrometheusCertFile: prom.CertFile, 83 | PrometheusKeyFile: prom.KeyFile, 84 | PrometheusScrapePath: prom.ScrapePath, 85 | } 86 | 87 | if bootstrapParams.Identity != "" { 88 | args.ProxyCluster = bootstrapParams.Identity 89 | args.ProxySourceService = bootstrapParams.Identity 90 | } 91 | 92 | if cdp.xdsServer.listenerNetwork == "unix" { 93 | args.AgentSocket = cdp.xdsServer.listenerAddress 94 | } else { 95 | h, p, err := net.SplitHostPort(cdp.xdsServer.listenerAddress) 96 | if err != nil { 97 | cdp.logger.Error("error splitting listenerAddress to host and port with error", err) 98 | } 99 | args.AgentAddress = h 100 | args.AgentPort = p 101 | } 102 | 103 | if path := prom.CACertsPath; path != "" { 104 | fi, err := os.Stat(path) 105 | if err != nil { 106 | return nil, nil, err 107 | } 108 | if fi.IsDir() { 109 | args.PrometheusCAPath = path 110 | } else { 111 | args.PrometheusCAFile = path 112 | } 113 | } 114 | 115 | var bootstrapConfig bootstrap.BootstrapConfig 116 | if envoy.ReadyBindAddress != "" && envoy.ReadyBindPort != 0 { 117 | bootstrapConfig.ReadyBindAddr = net.JoinHostPort(envoy.ReadyBindAddress, strconv.Itoa(envoy.ReadyBindPort)) 118 | } 119 | 120 | if cdp.cfg.Telemetry.UseCentralConfig { 121 | if err := mapstructure.WeakDecode(bootstrapParams.Config.AsMap(), &bootstrapConfig); err != nil { 122 | return nil, nil, fmt.Errorf("failed parsing Proxy.Config: %w", err) 123 | } 124 | 125 | // Envoy is configured with a listener that proxies metrics from its 126 | // own admin endpoint (localhost:19000/stats/prometheus). When central 127 | // config is enabled, we set the PrometheusBackendPort to instead have 128 | // Envoy proxy metrics from Consul Dataplane which serves merged 129 | // metrics (Envoy + Dataplane + service metrics). 130 | // Documentation: https://www.consul.io/commands/connect/envoy#prometheus-backend-port 131 | args.PrometheusBackendPort = strconv.Itoa(prom.MergePort) 132 | } 133 | 134 | bootstrapConfig.Logger = cdp.logger.Named("bootstrap-config") 135 | 136 | // Note: we pass true for omitDeprecatedTags here - consul-dataplane is clean 137 | // slate, and we don't need to maintain this legacy behavior. 138 | cfg, err := bootstrapConfig.GenerateJSON(args, true) 139 | return &bootstrapConfig, cfg, err 140 | } 141 | -------------------------------------------------------------------------------- /pkg/consuldp/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package consuldp 5 | 6 | import ( 7 | "crypto/x509" 8 | "encoding/pem" 9 | "os" 10 | "testing" 11 | 12 | "github.com/hashicorp/consul-server-connection-manager/discovery" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestConfig_TLS(t *testing.T) { 17 | t.Run("disabled", func(t *testing.T) { 18 | cfg := TLSConfig{Disabled: true} 19 | 20 | out, err := cfg.Load() 21 | require.NoError(t, err) 22 | require.Nil(t, out) 23 | }) 24 | 25 | t.Run("CACertsPath is a file", func(t *testing.T) { 26 | cfg := TLSConfig{ 27 | CACertsPath: "testdata/certs/ca/cert.pem", 28 | } 29 | 30 | out, err := cfg.Load() 31 | require.NoError(t, err) 32 | 33 | cert := loadCertificate(t, "testdata/certs/server/cert.pem") 34 | _, err = cert.Verify(x509.VerifyOptions{ 35 | Roots: out.RootCAs, 36 | CurrentTime: cert.NotBefore, 37 | }) 38 | require.NoError(t, err) 39 | }) 40 | 41 | t.Run("CACertsPath is a directory", func(t *testing.T) { 42 | cfg := TLSConfig{ 43 | CACertsPath: "testdata/certs/ca", 44 | } 45 | 46 | out, err := cfg.Load() 47 | require.NoError(t, err) 48 | 49 | cert := loadCertificate(t, "testdata/certs/server/cert.pem") 50 | _, err = cert.Verify(x509.VerifyOptions{ 51 | Roots: out.RootCAs, 52 | CurrentTime: cert.NotBefore, 53 | }) 54 | require.NoError(t, err) 55 | }) 56 | 57 | t.Run("setting a client certificate", func(t *testing.T) { 58 | cfg := TLSConfig{ 59 | CertFile: "testdata/certs/server/cert.pem", 60 | KeyFile: "testdata/certs/server/key.pem", 61 | } 62 | 63 | out, err := cfg.Load() 64 | require.NoError(t, err) 65 | 66 | require.Len(t, out.Certificates, 1) 67 | 68 | cert, err := x509.ParseCertificate(out.Certificates[0].Certificate[0]) 69 | require.NoError(t, err) 70 | require.Equal(t, "server.dc1.consul", cert.Subject.CommonName) 71 | 72 | require.NotNil(t, out.Certificates[0].PrivateKey) 73 | }) 74 | } 75 | 76 | func TestConfig_Credentials(t *testing.T) { 77 | tokFile, err := os.CreateTemp(os.TempDir(), "bearer-token") 78 | require.NoError(t, err) 79 | t.Cleanup(func() { _ = os.Remove(tokFile.Name()) }) 80 | t.Cleanup(func() { _ = tokFile.Close() }) 81 | 82 | _, err = tokFile.WriteString("bearer-token-from-file") 83 | require.NoError(t, err) 84 | 85 | testCases := map[string]struct { 86 | in CredentialsConfig 87 | out discovery.Credentials 88 | }{ 89 | "no credentials": { 90 | in: CredentialsConfig{Type: CredentialsTypeNone}, 91 | out: discovery.Credentials{}, 92 | }, 93 | "static credentials": { 94 | in: CredentialsConfig{ 95 | Type: CredentialsTypeStatic, 96 | Static: StaticCredentialsConfig{ 97 | Token: "my-acl-token", 98 | }, 99 | }, 100 | out: discovery.Credentials{ 101 | Type: discovery.CredentialsTypeStatic, 102 | Static: discovery.StaticTokenCredential{ 103 | Token: "my-acl-token", 104 | }, 105 | }, 106 | }, 107 | "login credentials (bearer token)": { 108 | in: CredentialsConfig{ 109 | Type: CredentialsTypeLogin, 110 | Login: LoginCredentialsConfig{ 111 | AuthMethod: "jwt", 112 | Namespace: "namespace-1", 113 | Partition: "partition-a", 114 | Datacenter: "primary-dc", 115 | BearerToken: "bearer-token", 116 | Meta: map[string]string{"foo": "bar"}, 117 | }, 118 | }, 119 | out: discovery.Credentials{ 120 | Type: discovery.CredentialsTypeLogin, 121 | Login: discovery.LoginCredential{ 122 | AuthMethod: "jwt", 123 | Namespace: "namespace-1", 124 | Partition: "partition-a", 125 | Datacenter: "primary-dc", 126 | BearerToken: "bearer-token", 127 | Meta: map[string]string{"foo": "bar"}, 128 | }, 129 | }, 130 | }, 131 | "login credentials (bearer file)": { 132 | in: CredentialsConfig{ 133 | Type: CredentialsTypeLogin, 134 | Login: LoginCredentialsConfig{ 135 | AuthMethod: "jwt", 136 | Namespace: "namespace-1", 137 | Partition: "partition-a", 138 | Datacenter: "primary-dc", 139 | BearerTokenPath: tokFile.Name(), 140 | Meta: map[string]string{"foo": "bar"}, 141 | }, 142 | }, 143 | out: discovery.Credentials{ 144 | Type: discovery.CredentialsTypeLogin, 145 | Login: discovery.LoginCredential{ 146 | AuthMethod: "jwt", 147 | Namespace: "namespace-1", 148 | Partition: "partition-a", 149 | Datacenter: "primary-dc", 150 | BearerToken: "bearer-token-from-file", 151 | Meta: map[string]string{"foo": "bar"}, 152 | }, 153 | }, 154 | }, 155 | } 156 | for desc, tc := range testCases { 157 | t.Run(desc, func(t *testing.T) { 158 | got, err := tc.in.ToDiscoveryCredentials() 159 | require.NoError(t, err) 160 | require.Equal(t, tc.out, got) 161 | }) 162 | } 163 | } 164 | 165 | func loadCertificate(t *testing.T, path string) *x509.Certificate { 166 | t.Helper() 167 | 168 | pemBytes, err := os.ReadFile(path) 169 | require.NoError(t, err) 170 | 171 | block, _ := pem.Decode(pemBytes) 172 | require.Equal(t, "CERTIFICATE", block.Type) 173 | 174 | cert, err := x509.ParseCertificate(block.Bytes) 175 | require.NoError(t, err) 176 | 177 | return cert 178 | } 179 | -------------------------------------------------------------------------------- /pkg/consuldp/xds_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package consuldp 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "os" 11 | "path/filepath" 12 | "strconv" 13 | "testing" 14 | "time" 15 | 16 | "github.com/hashicorp/go-hclog" 17 | "github.com/stretchr/testify/require" 18 | "google.golang.org/grpc/codes" 19 | "google.golang.org/grpc/metadata" 20 | "google.golang.org/grpc/status" 21 | ) 22 | 23 | const ( 24 | testToken = "test-token" 25 | additionalTestMetaKey = "additional-meta-key" 26 | additionalTestMetaValue = "additional-meta-value" 27 | ) 28 | 29 | func TestDirector(t *testing.T) { 30 | type testCase struct { 31 | name string 32 | incomingContext context.Context 33 | methodName string 34 | expectedErr error 35 | } 36 | 37 | incomingMetadata := metadata.MD{} 38 | incomingMetadata[additionalTestMetaKey] = []string{additionalTestMetaValue} 39 | 40 | testCases := []testCase{ 41 | { 42 | name: "empty metdata in incoming ctx", 43 | incomingContext: context.Background(), 44 | methodName: envoyADSMethodName, 45 | }, 46 | { 47 | name: "non-empty metdata in incoming ctx", 48 | incomingContext: metadata.NewIncomingContext(context.Background(), incomingMetadata), 49 | methodName: envoyADSMethodName, 50 | }, 51 | { 52 | name: "invalid method name", 53 | incomingContext: metadata.NewIncomingContext(context.Background(), incomingMetadata), 54 | methodName: "unknownrpcmethod", 55 | expectedErr: status.Errorf(codes.Unimplemented, "Unknown method unknownrpcmethod"), 56 | }, 57 | } 58 | 59 | for _, tc := range testCases { 60 | t.Run(tc.name, func(t *testing.T) { 61 | cdp := &ConsulDataplane{aclToken: testToken} 62 | outctx, targetConn, err := cdp.director(tc.incomingContext, tc.methodName) 63 | if tc.expectedErr != nil { 64 | require.ErrorIs(t, err, tc.expectedErr) 65 | } else { 66 | require.NoError(t, err) 67 | require.Equal(t, cdp.serverConn, targetConn) 68 | outMD, ok := metadata.FromOutgoingContext(outctx) 69 | require.True(t, ok) 70 | require.Equal(t, []string{testToken}, outMD.Get(metadataKeyToken)) 71 | // validate additional metadata in the incoming context is forwarded 72 | if _, ok := metadata.FromIncomingContext(tc.incomingContext); ok { 73 | require.Equal(t, []string{additionalTestMetaValue}, outMD.Get(additionalTestMetaKey)) 74 | } 75 | } 76 | }) 77 | } 78 | } 79 | 80 | func TestContextXDSServerShutdown(t *testing.T) { 81 | localhost := "127.0.0.1" 82 | cdp := &ConsulDataplane{ 83 | cfg: &Config{XDSServer: &XDSServer{BindAddress: "127.0.0.1", BindPort: 0}}, 84 | logger: hclog.Default(), 85 | } 86 | _ = cdp.setupXDSServer() 87 | ctx, cancel := context.WithCancel(context.Background()) 88 | go cdp.startXDSServer(ctx) 89 | port := cdp.xdsServer.listener.Addr().(*net.TCPAddr).Port 90 | addr := fmt.Sprintf("%v:%v", localhost, port) 91 | _, err := net.Dial("tcp", addr) 92 | require.NoError(t, err) 93 | cancel() 94 | require.Eventually(t, func() bool { 95 | port := cdp.xdsServer.listener.Addr().(*net.TCPAddr).Port 96 | addr := fmt.Sprintf("%v:%v", localhost, port) 97 | _, err := net.Dial("tcp", addr) 98 | t.Logf("dial error: %v", err) 99 | return err != nil 100 | }, time.Second*5, time.Second, "Failure to shut down tcp") 101 | } 102 | 103 | func TestSetupXDSServer(t *testing.T) { 104 | type testCase struct { 105 | name string 106 | xdsBindAddress string 107 | xdsBindPort int 108 | expectedListenerNetwork string 109 | expectedListenerAddress string 110 | } 111 | 112 | dir := os.TempDir() 113 | unixSocketPath := filepath.Join(dir, fmt.Sprintf("%d.sock", time.Now().UnixNano())) 114 | defer func() { 115 | os.Remove(unixSocketPath) 116 | }() 117 | 118 | testCases := []testCase{ 119 | {name: "localhost with no port", xdsBindAddress: "127.0.0.1", expectedListenerNetwork: "tcp", expectedListenerAddress: "127.0.0.1"}, 120 | {name: "localhost with port", xdsBindAddress: "127.0.0.1", xdsBindPort: 51804, expectedListenerNetwork: "tcp", expectedListenerAddress: "127.0.0.1:51804"}, 121 | {name: "unix socket", xdsBindAddress: fmt.Sprintf("unix://%s", unixSocketPath), expectedListenerNetwork: "unix", expectedListenerAddress: unixSocketPath}, 122 | } 123 | for _, tc := range testCases { 124 | t.Run(tc.name, func(t *testing.T) { 125 | cdp := &ConsulDataplane{ 126 | cfg: &Config{XDSServer: &XDSServer{BindAddress: tc.xdsBindAddress, BindPort: tc.xdsBindPort}}, 127 | logger: hclog.NewNullLogger(), 128 | } 129 | 130 | err := cdp.setupXDSServer() 131 | 132 | require.NoError(t, err) 133 | require.NotNil(t, cdp.xdsServer.listener) 134 | t.Cleanup(func() { cdp.xdsServer.listener.Close() }) 135 | require.NotNil(t, cdp.xdsServer.gRPCServer) 136 | require.Equal(t, tc.expectedListenerNetwork, cdp.xdsServer.listenerNetwork) 137 | require.Contains(t, cdp.xdsServer.listenerAddress, tc.expectedListenerAddress) 138 | if tc.expectedListenerNetwork == "tcp" && tc.xdsBindPort == 0 { 139 | listenerPort := cdp.xdsServer.listenerAddress[len(tc.xdsBindAddress)+1:] 140 | _, err = strconv.Atoi(listenerPort) 141 | require.NoError(t, err) 142 | } 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /pkg/consuldp/xds.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package consuldp 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/armon/go-metrics" 13 | "github.com/hashi-derek/grpc-proxy/proxy" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/metadata" 17 | "google.golang.org/grpc/status" 18 | ) 19 | 20 | const ( 21 | metadataKeyToken = "x-consul-token" 22 | envoyADSMethodName = "envoy.service.discovery.v3.AggregatedDiscoveryService/DeltaAggregatedResources" 23 | maxRecvSize = 50 * 1024 * 1024 24 | ) 25 | 26 | // director is the helper called by the unknown service gRPC handler. This helper is responsible for injecting the ACL token 27 | // into the outgoing Consul server request and returning the target consul server gRPC connection. 28 | func (cdp *ConsulDataplane) director(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) { 29 | // check to ensure other unknown/unregistered RPCs are not proxied to the target consul server. 30 | if !strings.Contains(fullMethodName, envoyADSMethodName) { 31 | return ctx, nil, status.Errorf(codes.Unimplemented, "Unknown method %s", fullMethodName) 32 | } 33 | 34 | var mdCopy metadata.MD 35 | md, ok := metadata.FromIncomingContext(ctx) 36 | if !ok { 37 | mdCopy = metadata.MD{} 38 | } else { 39 | mdCopy = md.Copy() 40 | } 41 | mdCopy.Set(metadataKeyToken, cdp.aclToken) 42 | outCtx := metadata.NewOutgoingContext(ctx, mdCopy) 43 | return outCtx, cdp.serverConn, nil 44 | } 45 | 46 | // setupXDSServer sets up the consul-dataplane xDS server 47 | func (cdp *ConsulDataplane) setupXDSServer() error { 48 | cdp.logger.Trace("setting up envoy xDS server") 49 | 50 | // create listener to accept envoy xDS connections 51 | var network, address string 52 | if strings.HasPrefix(cdp.cfg.XDSServer.BindAddress, "unix://") { 53 | network = "unix" 54 | address = cdp.cfg.XDSServer.BindAddress[len("unix://"):] 55 | } else { 56 | network = "tcp" 57 | address = net.JoinHostPort(cdp.cfg.XDSServer.BindAddress, strconv.Itoa(cdp.cfg.XDSServer.BindPort)) 58 | } 59 | 60 | lis, err := net.Listen(network, address) 61 | if err != nil { 62 | cdp.logger.Error("failed to create envoy xDS listener: %v", err) 63 | return err 64 | } 65 | 66 | // create gRPC server to serve envoy gRPC xDS requests 67 | // one main role of this gRPC server in consul-dataplane is to proxy envoy ADS requests 68 | // to the connected Consul server. 69 | 70 | // Note on the underlying library: 71 | // It has most of the scaffolding to proxy grpc requests to a desired target. 72 | // The main proxy logic is here - https://github.com/adamthesax/grpc-proxy/blob/master/proxy/handler.go 73 | // The core library being used is actually this - https://github.com/mwitkow/grpc-proxy. 74 | // However, we needed this fix (https://github.com/mwitkow/grpc-proxy/pull/62) which was available on the fork we are using. 75 | // TODO: Switch to the main library once the fix is merged to keep upto date. 76 | newGRPCServer := grpc.NewServer( 77 | // Increase the maximum message size due to large proxies sometimes exceeding the default 4MB limit. 78 | grpc.MaxRecvMsgSize(maxRecvSize), 79 | grpc.UnknownServiceHandler(proxy.TransparentHandlerWithOpts(cdp.director, grpc.MaxCallRecvMsgSize(maxRecvSize))), 80 | grpc.StreamInterceptor(cdp.streamInterceptor()), 81 | ) 82 | 83 | cdp.xdsServer = &xdsServer{ 84 | listener: lis, 85 | listenerAddress: lis.Addr().String(), 86 | listenerNetwork: lis.Addr().Network(), 87 | gRPCServer: newGRPCServer, 88 | exitedCh: make(chan struct{}), 89 | } 90 | 91 | cdp.logger.Trace("created xDS server", "address", lis.Addr().String()) 92 | return nil 93 | } 94 | 95 | func (cdp *ConsulDataplane) startXDSServer(ctx context.Context) { 96 | cdp.logger.Info("starting envoy xDS server", "address", cdp.xdsServer.listener.Addr().String()) 97 | 98 | go func() { 99 | <-ctx.Done() 100 | cdp.logger.Info("context done stopping xds server") 101 | cdp.stopXDSServer() 102 | }() 103 | 104 | if err := cdp.xdsServer.gRPCServer.Serve(cdp.xdsServer.listener); err != nil { 105 | cdp.logger.Error("failed to serve xDS requests", "error", err) 106 | close(cdp.xdsServer.exitedCh) 107 | } 108 | } 109 | 110 | func (cdp *ConsulDataplane) stopXDSServer() { 111 | if cdp.xdsServer.gRPCServer != nil { 112 | cdp.logger.Debug("stopping xDS server") 113 | cdp.xdsServer.gRPCServer.Stop() 114 | } 115 | } 116 | 117 | func (cdp *ConsulDataplane) xdsServerExited() chan struct{} { return cdp.xdsServer.exitedCh } 118 | 119 | func (cdp *ConsulDataplane) streamInterceptor() grpc.StreamServerInterceptor { 120 | return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 121 | return handler(srv, &metricServerStream{ss}) 122 | } 123 | } 124 | 125 | type metricServerStream struct { 126 | grpc.ServerStream 127 | } 128 | 129 | func (s *metricServerStream) SendMsg(m interface{}) error { 130 | err := s.ServerStream.SendMsg(m) 131 | if err == nil { 132 | metrics.SetGauge([]string{"envoy_connected"}, 1) 133 | return nil 134 | } 135 | metrics.SetGauge([]string{"envoy_connected"}, 0) 136 | return err 137 | } 138 | 139 | func (s *metricServerStream) RecvMsg(m interface{}) error { 140 | err := s.ServerStream.RecvMsg(m) 141 | if err == nil { 142 | metrics.SetGauge([]string{"envoy_connected"}, 1) 143 | return nil 144 | } 145 | metrics.SetGauge([]string{"envoy_connected"}, 0) 146 | return err 147 | } 148 | -------------------------------------------------------------------------------- /cmd/consul-dataplane/flags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | // This file contains flag wrappers that support reading from an environment 7 | // variable. We want flags to take precedence over environment variables, so 8 | // flag parsing must occur after calling the functions here, so that 9 | // environment variable are processed prior to flags. 10 | 11 | import ( 12 | "flag" 13 | "fmt" 14 | "log" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | func StringVar(fs *flag.FlagSet, p **string, name, env, usage string) { 20 | usage = includeEnvUsage(env, usage) 21 | // The order here is important. The flag will sets the value to the default 22 | // value, prior to flag parsing. So after the flag is created, we override 23 | // the value to the env var, if it is set, or otherwise the defaultVal. 24 | fs.Var(newStringPtrValue(p), name, usage) 25 | *p = parseEnv(env, asString) 26 | } 27 | 28 | func IntVar(fs *flag.FlagSet, p **int, name, env, usage string) { 29 | usage = includeEnvUsage(env, usage) 30 | fs.Var(newIntPtrValue(p), name, usage) 31 | *p = parseEnv(env, asInt) 32 | } 33 | 34 | func BoolVar(fs *flag.FlagSet, p **bool, name, env, usage string) { 35 | usage = includeEnvUsage(env, usage) 36 | fs.Var(newBoolPtrValue(p), name, usage) 37 | *p = parseEnv(env, asBool) 38 | } 39 | 40 | func DurationVar(fs *flag.FlagSet, p **Duration, name, env, usage string) { 41 | usage = includeEnvUsage(env, usage) 42 | fs.Var(newDurationPtrValue(p), name, usage) 43 | *p = parseEnv(env, asDuration) 44 | } 45 | 46 | // MapVar supports repeated flags and the environment variables numbered {1,9}. 47 | func MapVar(fs *flag.FlagSet, v flag.Value, name, env, usage string) { 48 | usage = includeEnvUsage(fmt.Sprintf("%s{1,9}", env), usage) 49 | fs.Var(v, name, usage) 50 | for varName, value := range multiValueEnv(env) { 51 | err := v.Set(value) 52 | if err != nil { 53 | log.Fatalf("error in environment variable %s: %s", varName, err) 54 | } 55 | } 56 | } 57 | 58 | func includeEnvUsage(env, usage string) string { 59 | return fmt.Sprintf("%s Environment variable: %s.", usage, env) 60 | } 61 | 62 | // stringPtrValue is a flag.Value which stores the value in a *string. 63 | // If the value was not set the pointer is nil. 64 | type stringPtrValue struct { 65 | v **string 66 | b bool 67 | } 68 | 69 | func newStringPtrValue(p **string) *stringPtrValue { 70 | return &stringPtrValue{p, false} 71 | } 72 | 73 | func (s *stringPtrValue) Set(val string) error { 74 | *s.v, s.b = &val, true 75 | return nil 76 | } 77 | 78 | func (s *stringPtrValue) Get() interface{} { 79 | if s.b { 80 | return *s.v 81 | } 82 | return (*string)(nil) 83 | } 84 | 85 | func (s *stringPtrValue) String() string { 86 | if s.b { 87 | return **s.v 88 | } 89 | return "" 90 | } 91 | 92 | // intPtrValue is a flag.Value which stores the value in a *int if it 93 | // can be parsed with strconv.Atoi. If the value was not set the pointer 94 | // is nil. 95 | type intPtrValue struct { 96 | v **int 97 | b bool 98 | } 99 | 100 | func newIntPtrValue(p **int) *intPtrValue { 101 | return &intPtrValue{p, false} 102 | } 103 | 104 | func (s *intPtrValue) Set(val string) error { 105 | n, err := strconv.Atoi(val) 106 | if err != nil { 107 | return err 108 | } 109 | *s.v, s.b = &n, true 110 | return nil 111 | } 112 | 113 | func (s *intPtrValue) Get() interface{} { 114 | if s.b { 115 | return *s.v 116 | } 117 | return (*int)(nil) 118 | } 119 | 120 | func (s *intPtrValue) String() string { 121 | if s.b { 122 | return strconv.Itoa(**s.v) 123 | } 124 | return "" 125 | } 126 | 127 | // boolPtrValue is a flag.Value which stores the value in a *bool if it 128 | // can be parsed with strconv.ParseBool. If the value was not set the 129 | // pointer is nil. 130 | type boolPtrValue struct { 131 | v **bool 132 | b bool 133 | } 134 | 135 | func newBoolPtrValue(p **bool) *boolPtrValue { 136 | return &boolPtrValue{p, false} 137 | } 138 | 139 | func (s *boolPtrValue) IsBoolFlag() bool { return true } 140 | 141 | func (s *boolPtrValue) Set(val string) error { 142 | b, err := strconv.ParseBool(val) 143 | if err != nil { 144 | return err 145 | } 146 | *s.v, s.b = &b, true 147 | return nil 148 | } 149 | 150 | func (s *boolPtrValue) Get() interface{} { 151 | if s.b { 152 | return *s.v 153 | } 154 | return (*bool)(nil) 155 | } 156 | 157 | func (s *boolPtrValue) String() string { 158 | if s.b { 159 | return strconv.FormatBool(**s.v) 160 | } 161 | return "" 162 | } 163 | 164 | // durationPtrValue is a flag.Value which stores the value in a 165 | // *time.Duration if it can be parsed with time.ParseDuration. If the 166 | // value was not set the pointer is nil. 167 | type durationPtrValue struct { 168 | v **Duration 169 | b bool 170 | } 171 | 172 | func newDurationPtrValue(p **Duration) *durationPtrValue { 173 | return &durationPtrValue{p, false} 174 | } 175 | 176 | func (s *durationPtrValue) Set(val string) error { 177 | d, err := time.ParseDuration(val) 178 | if err != nil { 179 | return err 180 | } 181 | *s.v, s.b = &Duration{Duration: d}, true 182 | return nil 183 | } 184 | 185 | func (s *durationPtrValue) Get() interface{} { 186 | if s.b { 187 | return *s.v 188 | } 189 | return (*time.Duration)(nil) 190 | } 191 | 192 | func (s *durationPtrValue) String() string { 193 | if s.b { 194 | return (*(*s).v).Duration.String() 195 | } 196 | return "" 197 | } 198 | 199 | func durationVal(t *Duration) time.Duration { 200 | if t == nil { 201 | return 0 202 | } 203 | 204 | return t.Duration 205 | } 206 | 207 | func stringVal(s *string) string { 208 | if s == nil { 209 | return "" 210 | } 211 | 212 | return *s 213 | } 214 | 215 | func intVal(v *int) int { 216 | if v == nil { 217 | return 0 218 | } 219 | return *v 220 | } 221 | 222 | func boolVal(v *bool) bool { 223 | if v == nil { 224 | return false 225 | } 226 | return *v 227 | } 228 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/TestBootstrapConfig/unix-socket-xds-server.golden: -------------------------------------------------------------------------------- 1 | { 2 | "admin": { 3 | "access_log_path": "/dev/null", 4 | "address": { 5 | "socket_address": { 6 | "address": "127.0.0.1", 7 | "port_value": 19000 8 | } 9 | } 10 | }, 11 | "node": { 12 | "cluster": "web", 13 | "id": "web-proxy", 14 | "metadata": { 15 | "node_name": "agentless-node", 16 | "namespace": "default", 17 | "partition": "default" 18 | } 19 | }, 20 | "layered_runtime": { 21 | "layers": [ 22 | { 23 | "name": "base", 24 | "static_layer": { 25 | "re2.max_program_size.error_level": 1048576 26 | } 27 | } 28 | ] 29 | }, 30 | "static_resources": { 31 | "clusters": [ 32 | { 33 | "name": "consul-dataplane", 34 | "ignore_health_on_host_removal": false, 35 | "connect_timeout": "1s", 36 | "type": "STATIC", 37 | "http2_protocol_options": {}, 38 | "loadAssignment": { 39 | "clusterName": "consul-dataplane", 40 | "endpoints": [ 41 | { 42 | "lbEndpoints": [ 43 | { 44 | "endpoint": { 45 | "address": { 46 | "pipe": { 47 | "path": "/var/run/xds.sock" 48 | } 49 | } 50 | } 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | }, 59 | "stats_config": { 60 | "stats_tags": [ 61 | { 62 | "regex": "^cluster\\.(?:passthrough~)?((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 63 | "tag_name": "consul.destination.custom_hash" 64 | }, 65 | { 66 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 67 | "tag_name": "consul.destination.service_subset" 68 | }, 69 | { 70 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 71 | "tag_name": "consul.destination.service" 72 | }, 73 | { 74 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 75 | "tag_name": "consul.destination.namespace" 76 | }, 77 | { 78 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:([^.]+)\\.)?[^.]+\\.internal[^.]*\\.[^.]+\\.consul\\.)", 79 | "tag_name": "consul.destination.partition" 80 | }, 81 | { 82 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", 83 | "tag_name": "consul.destination.datacenter" 84 | }, 85 | { 86 | "regex": "^cluster\\.([^.]+\\.(?:[^.]+\\.)?([^.]+)\\.external\\.[^.]+\\.consul\\.)", 87 | "tag_name": "consul.destination.peer" 88 | }, 89 | { 90 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", 91 | "tag_name": "consul.destination.routing_type" 92 | }, 93 | { 94 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", 95 | "tag_name": "consul.destination.trust_domain" 96 | }, 97 | { 98 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", 99 | "tag_name": "consul.destination.target" 100 | }, 101 | { 102 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", 103 | "tag_name": "consul.destination.full_target" 104 | }, 105 | { 106 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.(([^.]+)(?:\\.[^.]+)?(?:\\.[^.]+)?\\.[^.]+\\.)", 107 | "tag_name": "consul.upstream.service" 108 | }, 109 | { 110 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.[^.]+)?\\.([^.]+)\\.)", 111 | "tag_name": "consul.upstream.datacenter" 112 | }, 113 | { 114 | "regex": "^(?:tcp|http)\\.upstream_peered\\.([^.]+(?:\\.[^.]+)?\\.([^.]+)\\.)", 115 | "tag_name": "consul.upstream.peer" 116 | }, 117 | { 118 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.([^.]+(?:\\.([^.]+))?(?:\\.[^.]+)?\\.[^.]+\\.)", 119 | "tag_name": "consul.upstream.namespace" 120 | }, 121 | { 122 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.([^.]+))?\\.[^.]+\\.)", 123 | "tag_name": "consul.upstream.partition" 124 | }, 125 | { 126 | "tag_name": "local_cluster", 127 | "fixed_value": "web" 128 | }, 129 | { 130 | "tag_name": "consul.source.service", 131 | "fixed_value": "web" 132 | }, 133 | { 134 | "tag_name": "consul.source.namespace", 135 | "fixed_value": "default" 136 | }, 137 | { 138 | "tag_name": "consul.source.partition", 139 | "fixed_value": "default" 140 | } 141 | ], 142 | "use_all_default_tags": true 143 | }, 144 | "dynamic_resources": { 145 | "lds_config": { 146 | "ads": {}, 147 | "initial_fetch_timeout": "0s", 148 | "resource_api_version": "V3" 149 | }, 150 | "cds_config": { 151 | "ads": {}, 152 | "initial_fetch_timeout": "0s", 153 | "resource_api_version": "V3" 154 | }, 155 | "ads_config": { 156 | "api_type": "DELTA_GRPC", 157 | "transport_api_version": "V3", 158 | "grpc_services": { 159 | "envoy_grpc": { 160 | "cluster_name": "consul-dataplane" 161 | } 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/TestBootstrapConfig/basic.golden: -------------------------------------------------------------------------------- 1 | { 2 | "admin": { 3 | "access_log_path": "/dev/null", 4 | "address": { 5 | "socket_address": { 6 | "address": "127.0.0.1", 7 | "port_value": 19000 8 | } 9 | } 10 | }, 11 | "node": { 12 | "cluster": "web", 13 | "id": "web-proxy", 14 | "metadata": { 15 | "node_name": "agentless-node", 16 | "namespace": "default", 17 | "partition": "default" 18 | } 19 | }, 20 | "layered_runtime": { 21 | "layers": [ 22 | { 23 | "name": "base", 24 | "static_layer": { 25 | "re2.max_program_size.error_level": 1048576 26 | } 27 | } 28 | ] 29 | }, 30 | "static_resources": { 31 | "clusters": [ 32 | { 33 | "name": "consul-dataplane", 34 | "ignore_health_on_host_removal": false, 35 | "connect_timeout": "1s", 36 | "type": "STATIC", 37 | "http2_protocol_options": {}, 38 | "loadAssignment": { 39 | "clusterName": "consul-dataplane", 40 | "endpoints": [ 41 | { 42 | "lbEndpoints": [ 43 | { 44 | "endpoint": { 45 | "address": { 46 | "socket_address": { 47 | "address": "127.0.0.1", 48 | "port_value": 1234 49 | } 50 | } 51 | } 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | } 58 | ] 59 | }, 60 | "stats_config": { 61 | "stats_tags": [ 62 | { 63 | "regex": "^cluster\\.(?:passthrough~)?((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 64 | "tag_name": "consul.destination.custom_hash" 65 | }, 66 | { 67 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 68 | "tag_name": "consul.destination.service_subset" 69 | }, 70 | { 71 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 72 | "tag_name": "consul.destination.service" 73 | }, 74 | { 75 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 76 | "tag_name": "consul.destination.namespace" 77 | }, 78 | { 79 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:([^.]+)\\.)?[^.]+\\.internal[^.]*\\.[^.]+\\.consul\\.)", 80 | "tag_name": "consul.destination.partition" 81 | }, 82 | { 83 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", 84 | "tag_name": "consul.destination.datacenter" 85 | }, 86 | { 87 | "regex": "^cluster\\.([^.]+\\.(?:[^.]+\\.)?([^.]+)\\.external\\.[^.]+\\.consul\\.)", 88 | "tag_name": "consul.destination.peer" 89 | }, 90 | { 91 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", 92 | "tag_name": "consul.destination.routing_type" 93 | }, 94 | { 95 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", 96 | "tag_name": "consul.destination.trust_domain" 97 | }, 98 | { 99 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", 100 | "tag_name": "consul.destination.target" 101 | }, 102 | { 103 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", 104 | "tag_name": "consul.destination.full_target" 105 | }, 106 | { 107 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.(([^.]+)(?:\\.[^.]+)?(?:\\.[^.]+)?\\.[^.]+\\.)", 108 | "tag_name": "consul.upstream.service" 109 | }, 110 | { 111 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.[^.]+)?\\.([^.]+)\\.)", 112 | "tag_name": "consul.upstream.datacenter" 113 | }, 114 | { 115 | "regex": "^(?:tcp|http)\\.upstream_peered\\.([^.]+(?:\\.[^.]+)?\\.([^.]+)\\.)", 116 | "tag_name": "consul.upstream.peer" 117 | }, 118 | { 119 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.([^.]+(?:\\.([^.]+))?(?:\\.[^.]+)?\\.[^.]+\\.)", 120 | "tag_name": "consul.upstream.namespace" 121 | }, 122 | { 123 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.([^.]+))?\\.[^.]+\\.)", 124 | "tag_name": "consul.upstream.partition" 125 | }, 126 | { 127 | "tag_name": "local_cluster", 128 | "fixed_value": "web" 129 | }, 130 | { 131 | "tag_name": "consul.source.service", 132 | "fixed_value": "web" 133 | }, 134 | { 135 | "tag_name": "consul.source.namespace", 136 | "fixed_value": "default" 137 | }, 138 | { 139 | "tag_name": "consul.source.partition", 140 | "fixed_value": "default" 141 | } 142 | ], 143 | "use_all_default_tags": true 144 | }, 145 | "dynamic_resources": { 146 | "lds_config": { 147 | "ads": {}, 148 | "initial_fetch_timeout": "0s", 149 | "resource_api_version": "V3" 150 | }, 151 | "cds_config": { 152 | "ads": {}, 153 | "initial_fetch_timeout": "0s", 154 | "resource_api_version": "V3" 155 | }, 156 | "ads_config": { 157 | "api_type": "DELTA_GRPC", 158 | "transport_api_version": "V3", 159 | "grpc_services": { 160 | "envoy_grpc": { 161 | "cluster_name": "consul-dataplane" 162 | } 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/TestBootstrapConfig/non-default_tenancy.golden: -------------------------------------------------------------------------------- 1 | { 2 | "admin": { 3 | "access_log_path": "/dev/null", 4 | "address": { 5 | "socket_address": { 6 | "address": "127.0.0.1", 7 | "port_value": 19000 8 | } 9 | } 10 | }, 11 | "node": { 12 | "cluster": "web", 13 | "id": "web-proxy", 14 | "metadata": { 15 | "node_name": "agentless-node", 16 | "namespace": "test-namespace", 17 | "partition": "test-partition" 18 | } 19 | }, 20 | "layered_runtime": { 21 | "layers": [ 22 | { 23 | "name": "base", 24 | "static_layer": { 25 | "re2.max_program_size.error_level": 1048576 26 | } 27 | } 28 | ] 29 | }, 30 | "static_resources": { 31 | "clusters": [ 32 | { 33 | "name": "consul-dataplane", 34 | "ignore_health_on_host_removal": false, 35 | "connect_timeout": "1s", 36 | "type": "STATIC", 37 | "http2_protocol_options": {}, 38 | "loadAssignment": { 39 | "clusterName": "consul-dataplane", 40 | "endpoints": [ 41 | { 42 | "lbEndpoints": [ 43 | { 44 | "endpoint": { 45 | "address": { 46 | "socket_address": { 47 | "address": "127.0.0.1", 48 | "port_value": 1234 49 | } 50 | } 51 | } 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | } 58 | ] 59 | }, 60 | "stats_config": { 61 | "stats_tags": [ 62 | { 63 | "regex": "^cluster\\.(?:passthrough~)?((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 64 | "tag_name": "consul.destination.custom_hash" 65 | }, 66 | { 67 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 68 | "tag_name": "consul.destination.service_subset" 69 | }, 70 | { 71 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 72 | "tag_name": "consul.destination.service" 73 | }, 74 | { 75 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 76 | "tag_name": "consul.destination.namespace" 77 | }, 78 | { 79 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:([^.]+)\\.)?[^.]+\\.internal[^.]*\\.[^.]+\\.consul\\.)", 80 | "tag_name": "consul.destination.partition" 81 | }, 82 | { 83 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", 84 | "tag_name": "consul.destination.datacenter" 85 | }, 86 | { 87 | "regex": "^cluster\\.([^.]+\\.(?:[^.]+\\.)?([^.]+)\\.external\\.[^.]+\\.consul\\.)", 88 | "tag_name": "consul.destination.peer" 89 | }, 90 | { 91 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", 92 | "tag_name": "consul.destination.routing_type" 93 | }, 94 | { 95 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", 96 | "tag_name": "consul.destination.trust_domain" 97 | }, 98 | { 99 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", 100 | "tag_name": "consul.destination.target" 101 | }, 102 | { 103 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", 104 | "tag_name": "consul.destination.full_target" 105 | }, 106 | { 107 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.(([^.]+)(?:\\.[^.]+)?(?:\\.[^.]+)?\\.[^.]+\\.)", 108 | "tag_name": "consul.upstream.service" 109 | }, 110 | { 111 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.[^.]+)?\\.([^.]+)\\.)", 112 | "tag_name": "consul.upstream.datacenter" 113 | }, 114 | { 115 | "regex": "^(?:tcp|http)\\.upstream_peered\\.([^.]+(?:\\.[^.]+)?\\.([^.]+)\\.)", 116 | "tag_name": "consul.upstream.peer" 117 | }, 118 | { 119 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.([^.]+(?:\\.([^.]+))?(?:\\.[^.]+)?\\.[^.]+\\.)", 120 | "tag_name": "consul.upstream.namespace" 121 | }, 122 | { 123 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.([^.]+))?\\.[^.]+\\.)", 124 | "tag_name": "consul.upstream.partition" 125 | }, 126 | { 127 | "tag_name": "local_cluster", 128 | "fixed_value": "web" 129 | }, 130 | { 131 | "tag_name": "consul.source.service", 132 | "fixed_value": "web" 133 | }, 134 | { 135 | "tag_name": "consul.source.namespace", 136 | "fixed_value": "test-namespace" 137 | }, 138 | { 139 | "tag_name": "consul.source.partition", 140 | "fixed_value": "test-partition" 141 | } 142 | ], 143 | "use_all_default_tags": true 144 | }, 145 | "dynamic_resources": { 146 | "lds_config": { 147 | "ads": {}, 148 | "initial_fetch_timeout": "0s", 149 | "resource_api_version": "V3" 150 | }, 151 | "cds_config": { 152 | "ads": {}, 153 | "initial_fetch_timeout": "0s", 154 | "resource_api_version": "V3" 155 | }, 156 | "ads_config": { 157 | "api_type": "DELTA_GRPC", 158 | "transport_api_version": "V3", 159 | "grpc_services": { 160 | "envoy_grpc": { 161 | "cluster_name": "consul-dataplane" 162 | } 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/TestBootstrapConfig/access-logs.golden: -------------------------------------------------------------------------------- 1 | { 2 | "admin": { 3 | "access_log": [ 4 | { 5 | "name": "Consul Listener Filter Log", 6 | "typedConfig": { 7 | "@type": "type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog", 8 | "logFormat": { 9 | "jsonFormat": { 10 | "custom_field": "%START_TIME%" 11 | } 12 | } 13 | } 14 | } 15 | ], 16 | "address": { 17 | "socket_address": { 18 | "address": "127.0.0.1", 19 | "port_value": 19000 20 | } 21 | } 22 | }, 23 | "node": { 24 | "cluster": "web", 25 | "id": "web-proxy", 26 | "metadata": { 27 | "node_name": "agentless-node", 28 | "namespace": "default", 29 | "partition": "default" 30 | } 31 | }, 32 | "layered_runtime": { 33 | "layers": [ 34 | { 35 | "name": "base", 36 | "static_layer": { 37 | "re2.max_program_size.error_level": 1048576 38 | } 39 | } 40 | ] 41 | }, 42 | "static_resources": { 43 | "clusters": [ 44 | { 45 | "name": "consul-dataplane", 46 | "ignore_health_on_host_removal": false, 47 | "connect_timeout": "1s", 48 | "type": "STATIC", 49 | "http2_protocol_options": {}, 50 | "loadAssignment": { 51 | "clusterName": "consul-dataplane", 52 | "endpoints": [ 53 | { 54 | "lbEndpoints": [ 55 | { 56 | "endpoint": { 57 | "address": { 58 | "socket_address": { 59 | "address": "127.0.0.1", 60 | "port_value": 1234 61 | } 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | ] 68 | } 69 | } 70 | ] 71 | }, 72 | "stats_config": { 73 | "stats_tags": [ 74 | { 75 | "regex": "^cluster\\.(?:passthrough~)?((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 76 | "tag_name": "consul.destination.custom_hash" 77 | }, 78 | { 79 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 80 | "tag_name": "consul.destination.service_subset" 81 | }, 82 | { 83 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 84 | "tag_name": "consul.destination.service" 85 | }, 86 | { 87 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 88 | "tag_name": "consul.destination.namespace" 89 | }, 90 | { 91 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:([^.]+)\\.)?[^.]+\\.internal[^.]*\\.[^.]+\\.consul\\.)", 92 | "tag_name": "consul.destination.partition" 93 | }, 94 | { 95 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", 96 | "tag_name": "consul.destination.datacenter" 97 | }, 98 | { 99 | "regex": "^cluster\\.([^.]+\\.(?:[^.]+\\.)?([^.]+)\\.external\\.[^.]+\\.consul\\.)", 100 | "tag_name": "consul.destination.peer" 101 | }, 102 | { 103 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", 104 | "tag_name": "consul.destination.routing_type" 105 | }, 106 | { 107 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", 108 | "tag_name": "consul.destination.trust_domain" 109 | }, 110 | { 111 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", 112 | "tag_name": "consul.destination.target" 113 | }, 114 | { 115 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", 116 | "tag_name": "consul.destination.full_target" 117 | }, 118 | { 119 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.(([^.]+)(?:\\.[^.]+)?(?:\\.[^.]+)?\\.[^.]+\\.)", 120 | "tag_name": "consul.upstream.service" 121 | }, 122 | { 123 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.[^.]+)?\\.([^.]+)\\.)", 124 | "tag_name": "consul.upstream.datacenter" 125 | }, 126 | { 127 | "regex": "^(?:tcp|http)\\.upstream_peered\\.([^.]+(?:\\.[^.]+)?\\.([^.]+)\\.)", 128 | "tag_name": "consul.upstream.peer" 129 | }, 130 | { 131 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.([^.]+(?:\\.([^.]+))?(?:\\.[^.]+)?\\.[^.]+\\.)", 132 | "tag_name": "consul.upstream.namespace" 133 | }, 134 | { 135 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.([^.]+))?\\.[^.]+\\.)", 136 | "tag_name": "consul.upstream.partition" 137 | }, 138 | { 139 | "tag_name": "local_cluster", 140 | "fixed_value": "web" 141 | }, 142 | { 143 | "tag_name": "consul.source.service", 144 | "fixed_value": "web" 145 | }, 146 | { 147 | "tag_name": "consul.source.namespace", 148 | "fixed_value": "default" 149 | }, 150 | { 151 | "tag_name": "consul.source.partition", 152 | "fixed_value": "default" 153 | } 154 | ], 155 | "use_all_default_tags": true 156 | }, 157 | "dynamic_resources": { 158 | "lds_config": { 159 | "ads": {}, 160 | "initial_fetch_timeout": "0s", 161 | "resource_api_version": "V3" 162 | }, 163 | "cds_config": { 164 | "ads": {}, 165 | "initial_fetch_timeout": "0s", 166 | "resource_api_version": "V3" 167 | }, 168 | "ads_config": { 169 | "api_type": "DELTA_GRPC", 170 | "transport_api_version": "V3", 171 | "grpc_services": { 172 | "envoy_grpc": { 173 | "cluster_name": "consul-dataplane" 174 | } 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /pkg/consuldp/testdata/TestBootstrapConfig/central-telemetry-config.golden: -------------------------------------------------------------------------------- 1 | { 2 | "admin": { 3 | "access_log_path": "/dev/null", 4 | "address": { 5 | "socket_address": { 6 | "address": "127.0.0.1", 7 | "port_value": 19000 8 | } 9 | } 10 | }, 11 | "node": { 12 | "cluster": "web", 13 | "id": "web-proxy", 14 | "metadata": { 15 | "node_name": "agentless-node", 16 | "namespace": "default", 17 | "partition": "default" 18 | } 19 | }, 20 | "layered_runtime": { 21 | "layers": [ 22 | { 23 | "name": "base", 24 | "static_layer": { 25 | "re2.max_program_size.error_level": 1048576 26 | } 27 | } 28 | ] 29 | }, 30 | "static_resources": { 31 | "clusters": [ 32 | { 33 | "name": "consul-dataplane", 34 | "ignore_health_on_host_removal": false, 35 | "connect_timeout": "1s", 36 | "type": "STATIC", 37 | "http2_protocol_options": {}, 38 | "loadAssignment": { 39 | "clusterName": "consul-dataplane", 40 | "endpoints": [ 41 | { 42 | "lbEndpoints": [ 43 | { 44 | "endpoint": { 45 | "address": { 46 | "socket_address": { 47 | "address": "127.0.0.1", 48 | "port_value": 1234 49 | } 50 | } 51 | } 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | } 58 | ] 59 | }, 60 | "stats_sinks": [ 61 | { 62 | "name": "envoy.stat_sinks.dog_statsd", 63 | "typedConfig": { 64 | "@type": "type.googleapis.com/envoy.config.metrics.v3.DogStatsdSink", 65 | "address": { 66 | "socket_address": { 67 | "address": "127.0.0.1", 68 | "port_value": 9125 69 | } 70 | } 71 | } 72 | } 73 | ], 74 | "stats_config": { 75 | "stats_tags": [ 76 | { 77 | "regex": "^cluster\\.(?:passthrough~)?((?:([^.]+)~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 78 | "tag_name": "consul.destination.custom_hash" 79 | }, 80 | { 81 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:([^.]+)\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 82 | "tag_name": "consul.destination.service_subset" 83 | }, 84 | { 85 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?([^.]+)\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 86 | "tag_name": "consul.destination.service" 87 | }, 88 | { 89 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+\\.consul\\.)", 90 | "tag_name": "consul.destination.namespace" 91 | }, 92 | { 93 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:([^.]+)\\.)?[^.]+\\.internal[^.]*\\.[^.]+\\.consul\\.)", 94 | "tag_name": "consul.destination.partition" 95 | }, 96 | { 97 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?([^.]+)\\.internal[^.]*\\.[^.]+\\.consul\\.)", 98 | "tag_name": "consul.destination.datacenter" 99 | }, 100 | { 101 | "regex": "^cluster\\.([^.]+\\.(?:[^.]+\\.)?([^.]+)\\.external\\.[^.]+\\.consul\\.)", 102 | "tag_name": "consul.destination.peer" 103 | }, 104 | { 105 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.([^.]+)\\.[^.]+\\.consul\\.)", 106 | "tag_name": "consul.destination.routing_type" 107 | }, 108 | { 109 | "regex": "^cluster\\.(?:passthrough~)?((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.([^.]+)\\.consul\\.)", 110 | "tag_name": "consul.destination.trust_domain" 111 | }, 112 | { 113 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+)\\.[^.]+\\.[^.]+\\.consul\\.)", 114 | "tag_name": "consul.destination.target" 115 | }, 116 | { 117 | "regex": "^cluster\\.(?:passthrough~)?(((?:[^.]+~)?(?:[^.]+\\.)?[^.]+\\.[^.]+\\.(?:[^.]+\\.)?[^.]+\\.[^.]+\\.[^.]+)\\.consul\\.)", 118 | "tag_name": "consul.destination.full_target" 119 | }, 120 | { 121 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.(([^.]+)(?:\\.[^.]+)?(?:\\.[^.]+)?\\.[^.]+\\.)", 122 | "tag_name": "consul.upstream.service" 123 | }, 124 | { 125 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.[^.]+)?\\.([^.]+)\\.)", 126 | "tag_name": "consul.upstream.datacenter" 127 | }, 128 | { 129 | "regex": "^(?:tcp|http)\\.upstream_peered\\.([^.]+(?:\\.[^.]+)?\\.([^.]+)\\.)", 130 | "tag_name": "consul.upstream.peer" 131 | }, 132 | { 133 | "regex": "^(?:tcp|http)\\.upstream(?:_peered)?\\.([^.]+(?:\\.([^.]+))?(?:\\.[^.]+)?\\.[^.]+\\.)", 134 | "tag_name": "consul.upstream.namespace" 135 | }, 136 | { 137 | "regex": "^(?:tcp|http)\\.upstream\\.([^.]+(?:\\.[^.]+)?(?:\\.([^.]+))?\\.[^.]+\\.)", 138 | "tag_name": "consul.upstream.partition" 139 | }, 140 | { 141 | "tag_name": "local_cluster", 142 | "fixed_value": "web" 143 | }, 144 | { 145 | "tag_name": "consul.source.service", 146 | "fixed_value": "web" 147 | }, 148 | { 149 | "tag_name": "consul.source.namespace", 150 | "fixed_value": "default" 151 | }, 152 | { 153 | "tag_name": "consul.source.partition", 154 | "fixed_value": "default" 155 | } 156 | ], 157 | "use_all_default_tags": true 158 | }, 159 | "dynamic_resources": { 160 | "lds_config": { 161 | "ads": {}, 162 | "initial_fetch_timeout": "0s", 163 | "resource_api_version": "V3" 164 | }, 165 | "cds_config": { 166 | "ads": {}, 167 | "initial_fetch_timeout": "0s", 168 | "resource_api_version": "V3" 169 | }, 170 | "ads_config": { 171 | "api_type": "DELTA_GRPC", 172 | "transport_api_version": "V3", 173 | "grpc_services": { 174 | "envoy_grpc": { 175 | "cluster_name": "consul-dataplane" 176 | } 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /pkg/consuldp/mock_dataplane_service_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.14.0. DO NOT EDIT. 2 | 3 | package consuldp 4 | 5 | import ( 6 | context "context" 7 | 8 | grpc "google.golang.org/grpc" 9 | 10 | mock "github.com/stretchr/testify/mock" 11 | 12 | pbdataplane "github.com/hashicorp/consul/proto-public/pbdataplane" 13 | ) 14 | 15 | // MockDataplaneServiceClient is an autogenerated mock type for the DataplaneServiceClient type 16 | type MockDataplaneServiceClient struct { 17 | mock.Mock 18 | } 19 | 20 | type MockDataplaneServiceClient_Expecter struct { 21 | mock *mock.Mock 22 | } 23 | 24 | func (_m *MockDataplaneServiceClient) EXPECT() *MockDataplaneServiceClient_Expecter { 25 | return &MockDataplaneServiceClient_Expecter{mock: &_m.Mock} 26 | } 27 | 28 | // GetEnvoyBootstrapParams provides a mock function with given fields: ctx, in, opts 29 | func (_m *MockDataplaneServiceClient) GetEnvoyBootstrapParams(ctx context.Context, in *pbdataplane.GetEnvoyBootstrapParamsRequest, opts ...grpc.CallOption) (*pbdataplane.GetEnvoyBootstrapParamsResponse, error) { 30 | _va := make([]interface{}, len(opts)) 31 | for _i := range opts { 32 | _va[_i] = opts[_i] 33 | } 34 | var _ca []interface{} 35 | _ca = append(_ca, ctx, in) 36 | _ca = append(_ca, _va...) 37 | ret := _m.Called(_ca...) 38 | 39 | var r0 *pbdataplane.GetEnvoyBootstrapParamsResponse 40 | if rf, ok := ret.Get(0).(func(context.Context, *pbdataplane.GetEnvoyBootstrapParamsRequest, ...grpc.CallOption) *pbdataplane.GetEnvoyBootstrapParamsResponse); ok { 41 | r0 = rf(ctx, in, opts...) 42 | } else { 43 | if ret.Get(0) != nil { 44 | r0 = ret.Get(0).(*pbdataplane.GetEnvoyBootstrapParamsResponse) 45 | } 46 | } 47 | 48 | var r1 error 49 | if rf, ok := ret.Get(1).(func(context.Context, *pbdataplane.GetEnvoyBootstrapParamsRequest, ...grpc.CallOption) error); ok { 50 | r1 = rf(ctx, in, opts...) 51 | } else { 52 | r1 = ret.Error(1) 53 | } 54 | 55 | return r0, r1 56 | } 57 | 58 | // MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEnvoyBootstrapParams' 59 | type MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call struct { 60 | *mock.Call 61 | } 62 | 63 | // GetEnvoyBootstrapParams is a helper method to define mock.On call 64 | // - ctx context.Context 65 | // - in *pbdataplane.GetEnvoyBootstrapParamsRequest 66 | // - opts ...grpc.CallOption 67 | func (_e *MockDataplaneServiceClient_Expecter) GetEnvoyBootstrapParams(ctx interface{}, in interface{}, opts ...interface{}) *MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call { 68 | return &MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call{Call: _e.mock.On("GetEnvoyBootstrapParams", 69 | append([]interface{}{ctx, in}, opts...)...)} 70 | } 71 | 72 | func (_c *MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call) Run(run func(ctx context.Context, in *pbdataplane.GetEnvoyBootstrapParamsRequest, opts ...grpc.CallOption)) *MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call { 73 | _c.Call.Run(func(args mock.Arguments) { 74 | variadicArgs := make([]grpc.CallOption, len(args)-2) 75 | for i, a := range args[2:] { 76 | if a != nil { 77 | variadicArgs[i] = a.(grpc.CallOption) 78 | } 79 | } 80 | run(args[0].(context.Context), args[1].(*pbdataplane.GetEnvoyBootstrapParamsRequest), variadicArgs...) 81 | }) 82 | return _c 83 | } 84 | 85 | func (_c *MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call) Return(_a0 *pbdataplane.GetEnvoyBootstrapParamsResponse, _a1 error) *MockDataplaneServiceClient_GetEnvoyBootstrapParams_Call { 86 | _c.Call.Return(_a0, _a1) 87 | return _c 88 | } 89 | 90 | // GetSupportedDataplaneFeatures provides a mock function with given fields: ctx, in, opts 91 | func (_m *MockDataplaneServiceClient) GetSupportedDataplaneFeatures(ctx context.Context, in *pbdataplane.GetSupportedDataplaneFeaturesRequest, opts ...grpc.CallOption) (*pbdataplane.GetSupportedDataplaneFeaturesResponse, error) { 92 | _va := make([]interface{}, len(opts)) 93 | for _i := range opts { 94 | _va[_i] = opts[_i] 95 | } 96 | var _ca []interface{} 97 | _ca = append(_ca, ctx, in) 98 | _ca = append(_ca, _va...) 99 | ret := _m.Called(_ca...) 100 | 101 | var r0 *pbdataplane.GetSupportedDataplaneFeaturesResponse 102 | if rf, ok := ret.Get(0).(func(context.Context, *pbdataplane.GetSupportedDataplaneFeaturesRequest, ...grpc.CallOption) *pbdataplane.GetSupportedDataplaneFeaturesResponse); ok { 103 | r0 = rf(ctx, in, opts...) 104 | } else { 105 | if ret.Get(0) != nil { 106 | r0 = ret.Get(0).(*pbdataplane.GetSupportedDataplaneFeaturesResponse) 107 | } 108 | } 109 | 110 | var r1 error 111 | if rf, ok := ret.Get(1).(func(context.Context, *pbdataplane.GetSupportedDataplaneFeaturesRequest, ...grpc.CallOption) error); ok { 112 | r1 = rf(ctx, in, opts...) 113 | } else { 114 | r1 = ret.Error(1) 115 | } 116 | 117 | return r0, r1 118 | } 119 | 120 | // MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSupportedDataplaneFeatures' 121 | type MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call struct { 122 | *mock.Call 123 | } 124 | 125 | // GetSupportedDataplaneFeatures is a helper method to define mock.On call 126 | // - ctx context.Context 127 | // - in *pbdataplane.GetSupportedDataplaneFeaturesRequest 128 | // - opts ...grpc.CallOption 129 | func (_e *MockDataplaneServiceClient_Expecter) GetSupportedDataplaneFeatures(ctx interface{}, in interface{}, opts ...interface{}) *MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call { 130 | return &MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call{Call: _e.mock.On("GetSupportedDataplaneFeatures", 131 | append([]interface{}{ctx, in}, opts...)...)} 132 | } 133 | 134 | func (_c *MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call) Run(run func(ctx context.Context, in *pbdataplane.GetSupportedDataplaneFeaturesRequest, opts ...grpc.CallOption)) *MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call { 135 | _c.Call.Run(func(args mock.Arguments) { 136 | variadicArgs := make([]grpc.CallOption, len(args)-2) 137 | for i, a := range args[2:] { 138 | if a != nil { 139 | variadicArgs[i] = a.(grpc.CallOption) 140 | } 141 | } 142 | run(args[0].(context.Context), args[1].(*pbdataplane.GetSupportedDataplaneFeaturesRequest), variadicArgs...) 143 | }) 144 | return _c 145 | } 146 | 147 | func (_c *MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call) Return(_a0 *pbdataplane.GetSupportedDataplaneFeaturesResponse, _a1 error) *MockDataplaneServiceClient_GetSupportedDataplaneFeatures_Call { 148 | _c.Call.Return(_a0, _a1) 149 | return _c 150 | } 151 | 152 | type mockConstructorTestingTNewMockDataplaneServiceClient interface { 153 | mock.TestingT 154 | Cleanup(func()) 155 | } 156 | 157 | // NewMockDataplaneServiceClient creates a new instance of MockDataplaneServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 158 | func NewMockDataplaneServiceClient(t mockConstructorTestingTNewMockDataplaneServiceClient) *MockDataplaneServiceClient { 159 | mock := &MockDataplaneServiceClient{} 160 | mock.Mock.Test(t) 161 | 162 | t.Cleanup(func() { mock.AssertExpectations(t) }) 163 | 164 | return mock 165 | } 166 | --------------------------------------------------------------------------------