From 8c1e9734ec64672b968100d9fa88a663f0338ed1 Mon Sep 17 00:00:00 2001 From: "H.E. Pennypacker" <115990865+pennypacker-he@users.noreply.github.com> Date: Wed, 15 Jul 2026 07:17:33 +0000 Subject: [PATCH 1/3] [patch] Harden Vault initialization durability --- .dockerignore | 5 + .github/workflows/github-release.yaml | 5 +- .github/workflows/goreleaser.yaml | 49 +- .github/workflows/lint-test-build-push.yml | 110 ++- .goreleaser.yml | 15 +- Dockerfile | 23 +- README.md | 92 ++- ci/publication_contract_test.go | 114 +++ go.mod | 77 +- go.sum | 660 +++-------------- main.go | 746 ++++++++++++++++--- main_test.go | 787 +++++++++++++++++++++ 12 files changed, 1927 insertions(+), 756 deletions(-) create mode 100644 .dockerignore create mode 100644 ci/publication_contract_test.go create mode 100644 main_test.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e3dab2f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.github +ci +main_test.go +vault-init diff --git a/.github/workflows/github-release.yaml b/.github/workflows/github-release.yaml index e7103bb..9aa368e 100644 --- a/.github/workflows/github-release.yaml +++ b/.github/workflows/github-release.yaml @@ -8,8 +8,9 @@ on: jobs: release: if: github.event.pull_request.merged == true && !contains(github.event.pull_request.title, 'skip-release') - uses: libops/.github/.github/workflows/bump-release.yaml@main + uses: libops/.github/.github/workflows/bump-release.yaml@578137212ead4ab4059e95df17fa30e9b7ac4aed + with: + workflow_file: goreleaser.yaml permissions: contents: write actions: write - secrets: inherit diff --git a/.github/workflows/goreleaser.yaml b/.github/workflows/goreleaser.yaml index 41becb3..ecf8a23 100644 --- a/.github/workflows/goreleaser.yaml +++ b/.github/workflows/goreleaser.yaml @@ -1,4 +1,5 @@ name: goreleaser + on: workflow_dispatch: push: @@ -6,27 +7,65 @@ on: - "*" permissions: - contents: write + contents: read jobs: + test: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version-file: go.mod + + - name: Verify tagged source + run: | + go mod download + go mod verify + go mod tidy -diff + go test -race ./... + goreleaser: + needs: test runs-on: ubuntu-24.04 + permissions: + contents: write steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 with: fetch-depth: 0 + persist-credentials: false - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: - go-version: ">=1.25.6" + go-version-file: go.mod - name: Run GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6 + uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7 with: distribution: goreleaser - version: latest + version: v2.17.0 args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-images: + if: github.ref_type == 'tag' + needs: + - test + - goreleaser + uses: ./.github/workflows/lint-test-build-push.yml + permissions: + contents: read + id-token: write + packages: write + secrets: + GCLOUD_OIDC_POOL: ${{ secrets.GCLOUD_OIDC_POOL }} + GSA: ${{ secrets.GSA }} diff --git a/.github/workflows/lint-test-build-push.yml b/.github/workflows/lint-test-build-push.yml index 54c7aa9..3c814d7 100644 --- a/.github/workflows/lint-test-build-push.yml +++ b/.github/workflows/lint-test-build-push.yml @@ -1,30 +1,116 @@ name: build-push + on: + workflow_call: + secrets: + GCLOUD_OIDC_POOL: + required: false + GSA: + required: false + pull_request: push: + branches: + - main workflow_dispatch: + +permissions: + contents: read + jobs: test: - permissions: - contents: read runs-on: ubuntu-24.04 steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + with: + persist-credentials: false + + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version-file: go.mod + + - name: Verify dependencies + run: | + go mod download + go mod verify + go mod tidy -diff + + - name: Check GoReleaser configuration + uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7 + with: + distribution: goreleaser + version: v2.17.0 + args: check + + - name: Lint + uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9 + with: + version: v2.12.2 - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6 + - name: Test + run: go test -race ./... + + image-check: + if: github.event_name == 'pull_request' + strategy: + fail-fast: false + matrix: + runner: + - ubuntu-24.04 + - ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 with: - go-version: ">=1.25.3" + persist-credentials: false + + - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 - - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 + - name: Resolve native platform + id: platform + run: | + set -euo pipefail + case "$RUNNER_ARCH" in + X64) platform=amd64 ;; + ARM64) platform=arm64 ;; + *) echo "Unsupported runner architecture: $RUNNER_ARCH" >&2; exit 1 ;; + esac + echo "name=$platform" >> "$GITHUB_OUTPUT" - - name: Test with the Go CLI - run: go test -v -race ./... + - name: Build native image without credentials + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 + with: + context: . + load: true + platforms: linux/${{ steps.platform.outputs.name }} + provenance: false + push: false + tags: vault-init:ci-${{ steps.platform.outputs.name }} + + - name: Scan native image + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: vault-init:ci-${{ steps.platform.outputs.name }} + format: table + exit-code: "1" + ignore-unfixed: true + severity: HIGH,CRITICAL + vuln-type: os,library - build-push: - needs: [test] - uses: libops/.github/.github/workflows/build-push.yaml@main + publish: + if: github.event_name != 'pull_request' + needs: test + uses: libops/.github/.github/workflows/build-push.yaml@578137212ead4ab4059e95df17fa30e9b7ac4aed with: - docker-registry: "libops" + ref: ${{ github.sha }} + expected-main-sha: ${{ github.ref == 'refs/heads/main' && github.sha || '' }} + additional-gar-registry: us-docker.pkg.dev/libops-images/public + scan: true + sign: true + certificate-identity: https://github.com/libops/.github/.github/workflows/build-push.yaml@578137212ead4ab4059e95df17fa30e9b7ac4aed permissions: contents: read + id-token: write packages: write - secrets: inherit + secrets: + GCLOUD_OIDC_POOL: ${{ secrets.GCLOUD_OIDC_POOL }} + GSA: ${{ secrets.GSA }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 65649c2..dd0ea4c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,17 +1,19 @@ -before: - hooks: - - go mod tidy +version: 2 + builds: - binary: vault-init env: - CGO_ENABLED=0 + ldflags: + - -s -w -X main.version={{ .Version }} goos: - linux - windows - darwin archives: - - format: tar.gz + - formats: + - tar.gz # this name template makes the OS and Arch compatible with the results of uname. name_template: >- {{ .ProjectName }}_ @@ -23,11 +25,12 @@ archives: # use zip for windows archives format_overrides: - goos: windows - format: zip + formats: + - zip checksum: name_template: "checksums.txt" snapshot: - name_template: "{{ incpatch .Version }}-next" + version_template: "{{ incpatch .Version }}-next" changelog: sort: asc filters: diff --git a/Dockerfile b/Dockerfile index 54e31ba..63ca43d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ -FROM golang:1.25.6@sha256:ce63a16e0f7063787ebb4eb28e72d477b00b4726f79874b3205a965ffd797ab2 AS builder +FROM ghcr.io/libops/go:1.26.5@sha256:ea764e85e42a243217c621891123b3fda9374674c29d59785414fc6b15815b3d AS builder -ENV GO111MODULE=on \ - CGO_ENABLED=0 \ - GOOS=linux \ - GOARCH=amd64 +SHELL ["/bin/ash", "-o", "pipefail", "-ex", "-c"] + +ENV CGO_ENABLED=0 \ + GOOS=linux + +ARG GIT_BRANCH=devel WORKDIR /src @@ -12,15 +14,16 @@ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY main.go . -RUN go build \ - -a \ +RUN --mount=type=cache,target=/root/.cache/go-build \ + go build \ -trimpath \ - -ldflags "-s -w -extldflags '-static'" \ - -tags 'osusergo netgo static_build' \ + -ldflags "-s -w -X main.version=${GIT_BRANCH}" \ + -tags "osusergo netgo" \ -o /bin/vault-init \ . FROM scratch COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /bin/vault-init /bin/vault-init -CMD ["/bin/vault-init"] +USER 65532:65532 +ENTRYPOINT ["/bin/vault-init"] diff --git a/README.md b/README.md index bd229af..d52d1d2 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,75 @@ The `vault-init` service automates the process of [initializing](https://www.vau After `vault-init` initializes a Vault server it stores master keys and root tokens, encrypted using [Google Cloud KMS](https://cloud.google.com/kms), to a user defined [Google Cloud Storage](https://cloud.google.com/storage) bucket. +Vault returns its initial recovery material only once. Before sending the +initialization request, this service verifies that neither destination object +already exists, that KMS can encrypt and decrypt a round-trip probe, and that +the workload can commit and read back a unique non-secret, create-only GCS +marker. The retained `vault-init-preflight/` marker is intentional because the +runtime has no object-delete permission. A graceful shutdown received before +the initialization request is sent aborts safely and leaves a one-shot job +retryable. Once the request is committed, the service ignores graceful +shutdown, retains a successful response in memory, and retries KMS and GCS with +bounded exponential backoff. GCS writes use a create-only precondition. A run +is complete only after each write is confirmed committed: either the writer +`Close` succeeds or a byte-identical create-only object is read back after an +ambiguous close result. The +complete encrypted initialization response is stored before the convenience +root-token object, so a partial two-object write still preserves all recovery +material. If the successful response is malformed or fails partway through a +read, the bytes received are encrypted and stored before the process reports +failure, and the convenience object is not created. Operators must avoid +forcibly terminating the initializer between +Vault accepting `/v1/sys/init` and the "Initialization complete" log entry. + +Every run that finds Vault already initialized verifies that both encrypted +objects exist and are non-empty before exiting successfully. This makes a +Cloud Run retry idempotent when the first task stored the response but lost its +final client response. If only `root-token.enc` is missing, the retry decrypts +the complete response and recreates that convenience object with the same +create-only protection. If `unseal-keys.json.enc` is missing, the one-time Vault +response cannot be reconstructed: the process exits nonzero and repeated job +attempts will continue to fail rather than silently claiming success. The +default Cloud Run Job policy [retries a failed task three +times](https://cloud.google.com/run/docs/configuring/max-retries). Those retries +can bridge transient GCS errors, but they cannot repair a missing recovery +bundle. Configure a task timeout long enough for the post-initialization retry +loop because forced termination or task timeout can still destroy the only +in-memory copy. + ## Usage The `vault-init` service is designed to be run alongside a Vault server and communicate over local host. -You can download the code and compile the binary with Go. Alternatively, a -Docker container is available via the Docker Hub: +You can download the code and compile the binary with Go. Release images are +published as one signed multi-platform manifest to both GHCR and the public +LibOps Google Artifact Registry repository: ```text -$ docker pull jcorall/vault-init +docker pull ghcr.io/libops/vault-init:1.0.2 +docker pull us-docker.pkg.dev/libops-images/public/vault-init:1.0.2 ``` +Both references are assembled from the same scanned amd64 and arm64 image +digests. Production Terraform must resolve the reviewed version tag and deploy +an immutable `@sha256:...` reference. GHCR is the general distribution source; +the GAR copy exists for Cloud Run. + +Pull requests test the Go code and build path without publisher credentials. +Main and release publication use the SHA-pinned LibOps shared workflow, the +repository-scoped `github@libops-images` workload identity, and explicit GitHub +secrets. The workflow scans each native image before any stable tag is written, +checks GHCR/GAR manifest parity, then keylessly signs and verifies both final +manifests. It does not publish build-provenance attestations. + To use this as part of a Kubernetes Vault Deployment: ```yaml containers: - name: vault-init - image: registry.hub.docker.com/jcorall/vault-init:0.3.0 - imagePullPolicy: Always + image: ghcr.io/libops/vault-init@sha256:REVIEWED_MANIFEST_DIGEST + imagePullPolicy: IfNotPresent env: - name: GCS_BUCKET_NAME value: my-gcs-bucket @@ -35,7 +85,24 @@ containers: The `vault-init` service supports the following environment variables for configuration: - `CHECK_INTERVAL` ("10s") - The time duration between Vault health checks. Set - this to a negative number to unseal once and exit. + this to zero or a negative number to check, initialize or unseal once and + exit. One-shot failures return a nonzero status. + +- `VAULT_ADDR` ("https://127.0.0.1:8200") - Vault API address. HTTPS is required + because the service sends its Google access token in the `X-Admin-Token` + header. The address must not contain credentials, a path, query, or fragment. + Metadata token retrieval bypasses environment-configured HTTP proxies and does + not follow redirects. + +- `VAULT_ALLOW_PLAINTEXT` (false) - Permit an `http://` Vault address. This is an + explicit development-only escape hatch because it exposes the Google access + token and Vault initialization traffic to interception. + +- `VAULT_CLIENT_TIMEOUT` ("60s") - Timeout for Vault API requests. An overly + short timeout can make the result of the one-time initialization request + ambiguous. Redirects are never followed because every Vault request carries + the privileged Google access token and initialization redirects would also + make commit state ambiguous. - `GCS_BUCKET_NAME` - The Google Cloud Storage Bucket where the Vault master key and root token is stored. @@ -60,7 +127,7 @@ The `vault-init` service supports the following environment variables for config Only applies to Vault 1.0 native auto-unseal. - `VAULT_SKIP_VERIFY` (false) - Disable TLS validation when connecting. Setting - to true is highly discouraged. + to true is highly discouraged. TLS 1.2 or newer is required by default. - `VAULT_CACERT` ("") - Path on disk to the CA _file_ to use for verifying TLS connections to Vault. @@ -96,9 +163,18 @@ Additionally, the service account must have the following minimum role(s): ```text roles/cloudkms.cryptoKeyEncrypterDecrypter -roles/storage.objectAdmin OR roles/storage.legacyBucketWriter +roles/storage.objectCreator +roles/storage.objectViewer ``` +Object read access is intentional: it lets the initializer refuse to overwrite +older recovery material and verify an idempotent retry when a successful GCS +commit loses its client response. Object Creator plus Object Viewer provides +the required create/read permissions without granting this workload delete or +overwrite access. Use a dedicated bucket for each Vault deployment and protect +it with retention, versioning, restricted administration, and an independently +tested recovery procedure. + For more information on service accounts, please see the [Google Cloud Service Accounts documentation][service-accounts]. diff --git a/ci/publication_contract_test.go b/ci/publication_contract_test.go new file mode 100644 index 0000000..08f29c0 --- /dev/null +++ b/ci/publication_contract_test.go @@ -0,0 +1,114 @@ +package ci + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +const sharedWorkflowSHA = "578137212ead4ab4059e95df17fa30e9b7ac4aed" + +func repositoryRoot(t *testing.T) string { + t.Helper() + _, current, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("resolve test source path") + } + return filepath.Dir(filepath.Dir(current)) +} + +func readFile(t *testing.T, path ...string) string { + t.Helper() + content, err := os.ReadFile(filepath.Join(append([]string{repositoryRoot(t)}, path...)...)) + if err != nil { + t.Fatalf("read %s: %v", filepath.Join(path...), err) + } + return string(content) +} + +func TestPublicationUsesSharedGHCRAndGARContract(t *testing.T) { + workflow := readFile(t, ".github", "workflows", "lint-test-build-push.yml") + for _, required := range []string{ + "libops/.github/.github/workflows/build-push.yaml@" + sharedWorkflowSHA, + "image-check:", + "if: github.event_name == 'pull_request'", + "ubuntu-24.04-arm", + "aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25", + "severity: HIGH,CRITICAL", + "goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94", + "args: check", + "additional-gar-registry: us-docker.pkg.dev/libops-images/public", + "expected-main-sha:", + "scan: true", + "sign: true", + "certificate-identity: https://github.com/libops/.github/.github/workflows/build-push.yaml@" + sharedWorkflowSHA, + "GCLOUD_OIDC_POOL: ${{ secrets.GCLOUD_OIDC_POOL }}", + "GSA: ${{ secrets.GSA }}", + } { + if !strings.Contains(workflow, required) { + t.Errorf("publisher workflow must contain %q", required) + } + } + for _, forbidden := range []string{"build-push.yaml@main", "secrets: inherit"} { + if strings.Contains(workflow, forbidden) { + t.Errorf("publisher workflow must not contain %q", forbidden) + } + } +} + +func TestReleasePublishesTheTaggedImage(t *testing.T) { + release := readFile(t, ".github", "workflows", "goreleaser.yaml") + for _, required := range []string{ + "if: github.ref_type == 'tag'", + "uses: ./.github/workflows/lint-test-build-push.yml", + "version: v2.17.0", + } { + if !strings.Contains(release, required) { + t.Errorf("release workflow must contain %q", required) + } + } + + bump := readFile(t, ".github", "workflows", "github-release.yaml") + if !strings.Contains(bump, "bump-release.yaml@"+sharedWorkflowSHA) { + t.Fatal("release bump workflow must be pinned to the reviewed shared commit") + } + if !strings.Contains(bump, "workflow_file: goreleaser.yaml") { + t.Fatal("release bump workflow must dispatch the tag workflow explicitly") + } + if strings.Contains(bump, "secrets: inherit") { + t.Fatal("release bump workflow must not inherit repository secrets") + } + + config := readFile(t, ".goreleaser.yml") + for _, required := range []string{"version: 2", "formats:", "-X main.version={{ .Version }}"} { + if !strings.Contains(config, required) { + t.Errorf("GoReleaser config must contain %q", required) + } + } + for _, forbidden := range []string{"before:", "format: tar.gz", "format: zip"} { + if strings.Contains(config, forbidden) { + t.Errorf("GoReleaser config must not contain %q", forbidden) + } + } +} + +func TestDockerfileBuildsNativeNonRootScratchImages(t *testing.T) { + dockerfile := readFile(t, "Dockerfile") + for _, required := range []string{ + "FROM ghcr.io/libops/go:1.26.5@sha256:", + "FROM scratch", + "USER 65532:65532", + "ENTRYPOINT [\"/bin/vault-init\"]", + "ARG GIT_BRANCH=devel", + "-X main.version=${GIT_BRANCH}", + } { + if !strings.Contains(dockerfile, required) { + t.Errorf("Dockerfile must contain %q", required) + } + } + if strings.Contains(dockerfile, "GOARCH=") { + t.Fatal("Dockerfile must build for the native shared-workflow architecture") + } +} diff --git a/go.mod b/go.mod index fe18700..5e13cf5 100644 --- a/go.mod +++ b/go.mod @@ -1,65 +1,56 @@ module github.com/libops/vault-init -go 1.24.0 +go 1.26.5 require ( - cloud.google.com/go/storage v1.59.1 - google.golang.org/api v0.262.0 + cloud.google.com/go/storage v1.63.1 + google.golang.org/api v0.288.0 ) require ( cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.18.1 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/monitoring v1.24.3 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect + cloud.google.com/go/iam v1.11.0 // indirect + cloud.google.com/go/monitoring v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20260121142036-a486691bba94 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect - github.com/googleapis/gax-go/v2 v2.16.0 // indirect - github.com/jstemmer/go-junit-report v1.0.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.17 // indirect + github.com/googleapis/gax-go/v2 v2.23.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/lint v0.0.0-20241112194109-818c5a804067 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.41.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20260122232226-8e98ce8d340d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260122232226-8e98ce8d340d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d // indirect - google.golang.org/grpc v1.78.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 // indirect + google.golang.org/grpc v1.82.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index d57777f..dc737f4 100644 --- a/go.sum +++ b/go.sum @@ -1,599 +1,127 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0 h1:oKpsiyKMfVpwR3zSAkQixGzlVE5ovitBuO0qSmCf0bI= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= -cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= -cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.13.0 h1:amPvhCOI+Hltp6rPu+62YdwhIrjf+34PKVAL4HwgYwk= -cloud.google.com/go/storage v1.13.0/go.mod h1:pqFyBUK3zZqMIIU5+8NaZq6/Ma3ClgUg9Hv5jfuJnvo= -cloud.google.com/go/storage v1.59.1 h1:DXAZLcTimtiXdGqDSnebROVPd9QvRsFVVlptz02Wk58= -cloud.google.com/go/storage v1.59.1/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= +cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= +cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k= +cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI= +cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM= +cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0= +cloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY= +cloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM= +cloud.google.com/go/storage v1.63.1 h1:CYXILV9G4CH0C18IQ9+V0h4XiqD2LhKnMLO0o7uJWNs= +cloud.google.com/go/storage v1.63.1/go.mod h1:lWyAtwvDZHdL3k68WVKbESP6bmWaV23ZJJ/JEVw/ZaQ= +cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E= +cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20260121142036-a486691bba94 h1:kkHPnzHm5Ln7WA0XYjrr2ITA0l9Vs6H++Ni//P+SZso= -github.com/cncf/xds/go v0.0.0-20260121142036-a486691bba94/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= -github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jstemmer/go-junit-report v1.0.0 h1:8X1gzZpR+nVQLAht+L/foqOeX2l9DTZoaIPbEQHxsds= -github.com/jstemmer/go-junit-report v1.0.0/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/googleapis/enterprise-certificate-proxy v0.3.17 h1:73NfMHdiqo9JFU9+7a5ExpVa10/R29pXfZIaW559nrg= +github.com/googleapis/enterprise-certificate-proxy v0.3.17/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k= +github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE= +github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok= -go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA= -golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210222171744-9060382bd457 h1:hMm9lBjyNLe/c9C6bElQxp4wsrleaJn1vXMZIQkNN44= -golang.org/x/net v0.0.0-20210222171744-9060382bd457/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210223095934-7937bea0104d h1:u0GOGnBJ3EKE/tNqREhhGiCzE9jFXydDo2lf7hOwGuc= -golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.38.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.40.0 h1:uWrpz12dpVPn7cojP82mk02XDgTJLDPc2KbVTxrWb4A= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.262.0 h1:4B+3u8He2GwyN8St3Jhnd3XRHlIvc//sBmgHSp78oNY= -google.golang.org/api v0.262.0/go.mod h1:jNwmH8BgUBJ/VrUG6/lIl9YiildyLd09r9ZLHiQ6cGI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210203152818-3206188e46ba/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210223151946-22b48be4551b h1:GXCSqFSSKq+L4Pi31A2Ba7j8BZCwHN8oJkREab1VokI= -google.golang.org/genproto v0.0.0-20210223151946-22b48be4551b/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20260122232226-8e98ce8d340d h1:hUplc9kLwH374NIY3PreRUK3Unc0xLm/W7MDsm0gCNo= -google.golang.org/genproto v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:SpjiK7gGN2j/djoQMxLl3QOe/J/XxNzC5M+YLecVVWU= -google.golang.org/genproto/googleapis/api v0.0.0-20260122232226-8e98ce8d340d h1:tUKoKfdZnSjTf5LW7xpG4c6SZ3Ozisn5eumcoTuMEN4= -google.golang.org/genproto/googleapis/api v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d h1:xXzuihhT3gL/ntduUZwHECzAn57E8dA6l8SOtYWdD8Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU= +go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.288.0 h1:glhO/J88obKP5I269W3hB73dvBKrjU56ZfmNlNXpgTU= +google.golang.org/api v0.288.0/go.mod h1:lM2kYRzYUCBY91P9h6VF1PYmvhxii3O5hji37qRvIcY= +google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94 h1:YJjbgu+dkp5kUJLfpMyCLfBIWZb/FcJyuLeo1gVBOuo= +google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94/go.mod h1:RRHjglSYABVCWpQ7USCpdfhcd9t4PkajvVwyynZizTc= +google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU= +google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 h1:eM/YSd5bBFagF51o1E745Ta7RwzpW0h+z+QDNZOgmQ8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU= +google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index 887dc60..06a20a6 100644 --- a/main.go +++ b/main.go @@ -7,18 +7,24 @@ package main import ( "bytes" "context" + "crypto/rand" "crypto/tls" "crypto/x509" "encoding/base64" + "encoding/hex" "encoding/json" + "errors" "fmt" "io" "log" "net/http" + "net/url" "os" "os/signal" + "path/filepath" "runtime" "strconv" + "strings" "syscall" "time" @@ -41,11 +47,32 @@ var ( kmsService *cloudkms.Service kmsKeyId string - storageClient *storage.Client + storageClient *storage.Client + metadataClient = newMetadataHTTPClient() - userAgent = fmt.Sprintf("vault-init/1.0.0 (%s)", runtime.Version()) + version = "devel" + userAgent = fmt.Sprintf("vault-init/%s (%s)", version, runtime.Version()) + + errShutdownBeforeInitialization = errors.New("shutdown requested before Vault initialization") +) + +const ( + unsealKeysObjectName = "unseal-keys.json.enc" + rootTokenObjectName = "root-token.enc" // #nosec G101 -- fixed GCS object name, not a credential value + kmsPreflightPlaintext = "vault-init initialization preflight" + secretWriteTimeout = 2 * time.Minute + maxSecretRetryDelay = time.Minute + maxEncryptedBundle = 256 << 10 ) +type secretEncrypter func(context.Context, []byte) ([]byte, error) +type secretDecrypter func(context.Context, []byte) ([]byte, error) +type encryptedSecretStore func(context.Context, string, []byte) error +type encryptedSecretStat func(context.Context, string) (int64, error) +type encryptedSecretReader func(context.Context, string) ([]byte, error) +type storagePermissionTester func(context.Context, []string) ([]string, error) +type storageRoundTripTester func(context.Context) error + // InitRequest holds a Vault init request. type InitRequest struct { SecretShares int `json:"secret_shares"` @@ -57,9 +84,11 @@ type InitRequest struct { // InitResponse holds a Vault init response. type InitResponse struct { - Keys []string `json:"keys"` - KeysBase64 []string `json:"keys_base64"` - RootToken string `json:"root_token"` + Keys []string `json:"keys"` + KeysBase64 []string `json:"keys_base64"` + RecoveryKeys []string `json:"recovery_keys"` + RecoveryKeysBase64 []string `json:"recovery_keys_base64"` + RootToken string `json:"root_token"` } // UnsealRequest holds a Vault unseal request. @@ -89,6 +118,10 @@ func main() { if vaultAddr == "" { vaultAddr = "https://127.0.0.1:8200" } + vaultAddr, err = validateVaultAddress(vaultAddr, boolFromEnv("VAULT_ALLOW_PLAINTEXT", false)) + if err != nil { + log.Fatal(err) + } vaultSecretShares = intFromEnv("VAULT_SECRET_SHARES", 5) vaultSecretThreshold = intFromEnv("VAULT_SECRET_THRESHOLD", 3) @@ -111,6 +144,7 @@ func main() { vaultServerName := stringFromEnv("VAULT_TLS_SERVER_NAME", "") checkInterval := durFromEnv("CHECK_INTERVAL", 10*time.Second) + oneShot := checkInterval <= 0 gcsBucketName = os.Getenv("GCS_BUCKET_NAME") if gcsBucketName == "" { @@ -126,8 +160,7 @@ func main() { defer kmsCtxCancel() kmsService, err = cloudkms.NewService(kmsCtx) if err != nil { - log.Println(err) - return + log.Fatal(err) } kmsService.UserAgent = userAgent @@ -141,14 +174,16 @@ func main() { } tlsConfig := &tls.Config{ - InsecureSkipVerify: vaultInsecureSkipVerify, + InsecureSkipVerify: vaultInsecureSkipVerify, // #nosec G402 -- explicit, documented operator escape hatch + MinVersion: tls.VersionTLS12, } if err := processTLSConfig(tlsConfig, vaultServerName, vaultCaCert, vaultCaPath); err != nil { log.Fatal(err) } httpClient = &http.Client{ - Timeout: vaultClientTimeout, + Timeout: vaultClientTimeout, + CheckRedirect: refuseHTTPRedirect, Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, @@ -157,11 +192,11 @@ func main() { signalCh := make(chan os.Signal, 1) signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) - stop := func() { + stop := func(exitCode int) { log.Printf("Shutting down") kmsCtxCancel() storageCtxCancel() - os.Exit(0) + os.Exit(exitCode) } retryDelay := time.Second @@ -172,17 +207,17 @@ func main() { for { select { case <-signalCh: - stop() + stop(shutdownExitCode(oneShot)) default: } request, err := newVaultRequest(http.MethodHead, vaultAddr+"/v1/sys/health", nil) if err != nil { log.Println(err) - if checkInterval < 0 { + if oneShot { retryAttempts++ if retryAttempts >= maxRetryAttempts { log.Printf("Health check failed after %d attempts; exiting with failure", retryAttempts) - os.Exit(1) + stop(1) } log.Printf( "Retrying health check in %s (exponential backoff), attempt %d/%d", @@ -202,16 +237,16 @@ func main() { response, err := httpClient.Do(request) if response != nil && response.Body != nil { - response.Body.Close() + _ = response.Body.Close() } if err != nil { log.Println(err) - if checkInterval < 0 { + if oneShot { retryAttempts++ if retryAttempts >= maxRetryAttempts { log.Printf("Health check failed after %d attempts; exiting with failure", retryAttempts) - os.Exit(1) + stop(1) } log.Printf( "Retrying health check in %s (exponential backoff), attempt %d/%d", @@ -231,45 +266,97 @@ func main() { retryDelay = time.Second retryAttempts = 0 + if healthStatusIndicatesInitialized(response.StatusCode) { + if err := verifyDurableInitialization(); err != nil { + log.Printf("Vault is initialized but its recovery material is not durable: %v", err) + stop(1) + } + } + switch response.StatusCode { case 200: log.Println("Vault is initialized and unsealed.") case 429: log.Println("Vault is unsealed and in standby mode.") + case 472: + log.Println("Vault is in disaster-recovery replication mode.") + case 473: + log.Println("Vault is a performance standby.") case 501: log.Println("Vault is not initialized.") log.Println("Initializing...") - initialize() + if err := initialize(signalCh); err != nil { + log.Printf("Initialization failed: %v", err) + if errors.Is(err, errShutdownBeforeInitialization) { + stop(shutdownExitCode(oneShot)) + } + if oneShot { + stop(1) + } + break + } if !vaultAutoUnseal { log.Println("Unsealing...") - unseal() + if err := unseal(); err != nil { + log.Printf("Unseal failed: %v", err) + if oneShot { + stop(1) + } + } } case 503: log.Println("Vault is sealed.") if !vaultAutoUnseal { log.Println("Unsealing...") - unseal() + if err := unseal(); err != nil { + log.Printf("Unseal failed: %v", err) + if oneShot { + stop(1) + } + } } default: log.Printf("Vault is in an unknown state. Status code: %d", response.StatusCode) + if oneShot { + stop(1) + } } - if checkInterval <= 0 { - log.Printf("Check interval set to less than 0, exiting.") - stop() + if oneShot { + log.Printf("Check interval is non-positive, exiting.") + stop(0) } log.Printf("Next check in %s", checkInterval) select { case <-signalCh: - stop() + stop(shutdownExitCode(oneShot)) case <-time.After(checkInterval): } } } -func initialize() { +func shutdownExitCode(oneShot bool) int { + if oneShot { + // A Cloud Run Job task terminated before it can finish must remain + // retryable. Reporting success here could leave Vault uninitialized while + // exhausting the job's retry policy. + return 1 + } + return 0 +} + +func healthStatusIndicatesInitialized(statusCode int) bool { + switch statusCode { + case 200, 429, 472, 473, 503: + return true + default: + return false + } +} + +func initialize(shutdown <-chan os.Signal) error { initRequest := InitRequest{ StoredShares: vaultStoredShares, RecoveryShares: vaultRecoveryShares, @@ -286,144 +373,531 @@ func initialize() { initRequestData, err := json.Marshal(&initRequest) if err != nil { - log.Println(err) - return + return fmt.Errorf("marshal initialization request: %w", err) + } + + checkCtx, checkCancel := context.WithTimeout(context.Background(), secretWriteTimeout) + err = preflightInitialization( + checkCtx, + statEncryptedSecret, + testStoragePermissions, + encryptSecretWithKMS, + decryptSecretWithKMS, + testStorageRoundTrip, + ) + checkCancel() + if err != nil { + return err + } + if shutdownRequested(shutdown) { + return errShutdownBeforeInitialization } r := bytes.NewReader(initRequestData) request, err := newVaultRequest(http.MethodPut, vaultAddr+"/v1/sys/init", r) if err != nil { - log.Println(err) - return + return fmt.Errorf("create initialization request: %w", err) + } + if shutdownRequested(shutdown) { + return errShutdownBeforeInitialization } response, err := httpClient.Do(request) if err != nil { - log.Println(err) - return + return fmt.Errorf("initialize Vault: %w", err) } defer response.Body.Close() - initRequestResponseBody, err := io.ReadAll(response.Body) - if err != nil { - log.Println(err) - return + if response.StatusCode != http.StatusOK { + return fmt.Errorf("init: non-200 status code: %d", response.StatusCode) } - if response.StatusCode != 200 { - log.Printf("init: non 200 status code: %d", response.StatusCode) - return + initRequestResponseBody, err := io.ReadAll(response.Body) + if err != nil { + if len(initRequestResponseBody) > 0 { + persistenceErr := retryInitializationPersistence( + initRequestResponseBody, + "", + encryptSecretWithKMS, + storeEncryptedSecretOnce, + time.Sleep, + ) + return fmt.Errorf("read initialization response: %w (partial encrypted response preserved: %v)", err, persistenceErr) + } + return fmt.Errorf("read initialization response: %w", err) } var initResponse InitResponse if err := json.Unmarshal(initRequestResponseBody, &initResponse); err != nil { - log.Println(err) - return + // Preserve every byte returned by the one-time endpoint even when its + // shape is unexpected. Passing an empty root token makes the persistence + // routine stop after the complete encrypted response is durable. + persistenceErr := retryInitializationPersistence( + initRequestResponseBody, + "", + encryptSecretWithKMS, + storeEncryptedSecretOnce, + time.Sleep, + ) + return fmt.Errorf("decode initialization response: %w (%v)", err, persistenceErr) + } + + // Vault returns its recovery material exactly once. Once the server accepts + // the init request, do not honor shutdown or return to the health loop until + // that material is durably encrypted and stored. Retrying in this process is + // the only safe response to a transient KMS or GCS failure. + log.Println("Encrypting and durably storing unseal keys and the root token...") + if err := retryInitializationPersistence( + initRequestResponseBody, + initResponse.RootToken, + encryptSecretWithKMS, + storeEncryptedSecretOnce, + time.Sleep, + ); err != nil { + return err } - log.Println("Encrypting unseal keys and the root token...") + log.Println("Initialization complete.") + return nil +} - rootTokenEncryptRequest := &cloudkms.EncryptRequest{ - Plaintext: base64.StdEncoding.EncodeToString([]byte(initResponse.RootToken)), +func shutdownRequested(shutdown <-chan os.Signal) bool { + select { + case <-shutdown: + return true + default: + return false } +} - rootTokenEncryptResponse, err := kmsService.Projects.Locations.KeyRings.CryptoKeys.Encrypt(kmsKeyId, rootTokenEncryptRequest).Do() +func preflightInitialization( + ctx context.Context, + stat encryptedSecretStat, + testPermissions storagePermissionTester, + encrypt secretEncrypter, + decrypt secretDecrypter, + storageRoundTrip storageRoundTripTester, +) error { + if err := ensureInitializationTargetsEmpty(ctx, stat); err != nil { + return err + } + requiredPermissions := []string{"storage.objects.create", "storage.objects.get"} + grantedPermissions, err := testPermissions(ctx, requiredPermissions) + if err != nil { + return fmt.Errorf("verify GCS permissions before Vault initialization: %w", err) + } + granted := make(map[string]struct{}, len(grantedPermissions)) + for _, permission := range grantedPermissions { + granted[permission] = struct{}{} + } + for _, permission := range requiredPermissions { + if _, ok := granted[permission]; !ok { + return fmt.Errorf("verify GCS permissions before Vault initialization: missing %s", permission) + } + } + ciphertext, err := encrypt(ctx, []byte(kmsPreflightPlaintext)) + if err != nil { + return fmt.Errorf("verify KMS encryption before Vault initialization: %w", err) + } + plaintext, err := decrypt(ctx, ciphertext) if err != nil { - log.Println(err) - return + return fmt.Errorf("verify KMS decryption before Vault initialization: %w", err) } + if !bytes.Equal(plaintext, []byte(kmsPreflightPlaintext)) { + return fmt.Errorf("verify KMS before Vault initialization: round-trip plaintext mismatch") + } + if err := storageRoundTrip(ctx); err != nil { + return fmt.Errorf("verify GCS create/read before Vault initialization: %w", err) + } + return nil +} + +func testStoragePermissions(ctx context.Context, permissions []string) ([]string, error) { + return storageClient.Bucket(gcsBucketName).IAM().TestPermissions(ctx, permissions) +} - unsealKeysEncryptRequest := &cloudkms.EncryptRequest{ - Plaintext: base64.StdEncoding.EncodeToString(initRequestResponseBody), +func testStorageRoundTrip(ctx context.Context) error { + marker := make([]byte, 32) + if _, err := rand.Read(marker); err != nil { + return fmt.Errorf("generate preflight marker: %w", err) } + name := "vault-init-preflight/" + hex.EncodeToString(marker) + return verifyStorageRoundTrip(ctx, name, marker, storeEncryptedSecretOnce, readEncryptedSecret) +} - unsealKeysEncryptResponse, err := kmsService.Projects.Locations.KeyRings.CryptoKeys.Encrypt(kmsKeyId, unsealKeysEncryptRequest).Do() +func verifyStorageRoundTrip( + ctx context.Context, + name string, + marker []byte, + store encryptedSecretStore, + read encryptedSecretReader, +) error { + if len(marker) == 0 { + return fmt.Errorf("preflight marker is empty") + } + if err := store(ctx, name, marker); err != nil { + return fmt.Errorf("create marker object: %w", err) + } + stored, err := read(ctx, name) if err != nil { - log.Println(err) - return + return fmt.Errorf("read marker object: %w", err) + } + if !bytes.Equal(stored, marker) { + return fmt.Errorf("marker object content mismatch") + } + return nil +} + +func retryInitializationPersistence( + initResponse []byte, + rootToken string, + encrypt secretEncrypter, + store encryptedSecretStore, + wait func(time.Duration), +) error { + retryDelay := time.Second + + // Protect and store the complete response before doing any work on the + // redundant root-token object. This minimizes the interval in which a forced + // task termination could destroy Vault's one-time recovery response. + protectedResponse := retrySecretEncryption( + initResponse, + "initialization response", + encrypt, + wait, + &retryDelay, + ) + retrySecretStore( + unsealKeysObjectName, + protectedResponse, + "initialization response", + store, + wait, + &retryDelay, + ) + log.Printf("Unseal keys written to gs://%s/%s", gcsBucketName, unsealKeysObjectName) + + if rootToken == "" { + return fmt.Errorf("initialization response did not contain a root token; the complete encrypted response is durable at gs://%s/%s", gcsBucketName, unsealKeysObjectName) } - bucket := storageClient.Bucket(gcsBucketName) + protectedRootToken := retrySecretEncryption( + []byte(rootToken), + "root token", + encrypt, + wait, + &retryDelay, + ) + retrySecretStore( + rootTokenObjectName, + protectedRootToken, + "root token", + store, + wait, + &retryDelay, + ) + + log.Printf("Root token written to gs://%s/%s", gcsBucketName, rootTokenObjectName) + return nil +} + +func retrySecretEncryption( + plaintext []byte, + description string, + encrypt secretEncrypter, + wait func(time.Duration), + retryDelay *time.Duration, +) []byte { + for { + ctx, cancel := context.WithTimeout(context.Background(), secretWriteTimeout) + ciphertext, err := encrypt(ctx, plaintext) + cancel() + if err == nil { + return ciphertext + } - // Save the encrypted unseal keys. - ctx := context.Background() - unsealKeysObject := bucket.Object("unseal-keys.json.enc").NewWriter(ctx) - defer unsealKeysObject.Close() + log.Printf("Protecting Vault %s failed; retaining it in memory and retrying in %s: %v", description, *retryDelay, err) + wait(*retryDelay) + *retryDelay = nextRetryDelay(*retryDelay) + } +} - _, err = unsealKeysObject.Write([]byte(unsealKeysEncryptResponse.Ciphertext)) - if err != nil { - log.Println(err) +func retrySecretStore( + name string, + ciphertext []byte, + description string, + store encryptedSecretStore, + wait func(time.Duration), + retryDelay *time.Duration, +) { + for { + ctx, cancel := context.WithTimeout(context.Background(), secretWriteTimeout) + err := store(ctx, name, ciphertext) + cancel() + if err == nil { + return + } + + log.Printf("Persisting Vault %s failed; retaining it in memory and retrying in %s: %v", description, *retryDelay, err) + wait(*retryDelay) + *retryDelay = nextRetryDelay(*retryDelay) } +} - log.Printf("Unseal keys written to gs://%s/%s", gcsBucketName, "unseal-keys.json.enc") +func nextRetryDelay(current time.Duration) time.Duration { + next := current * 2 + if next > maxSecretRetryDelay { + return maxSecretRetryDelay + } + return next +} - // Save the encrypted root token. - rootTokenObject := bucket.Object("root-token.enc").NewWriter(ctx) - defer rootTokenObject.Close() +func encryptSecretWithKMS(ctx context.Context, plaintext []byte) ([]byte, error) { + request := &cloudkms.EncryptRequest{ + Plaintext: base64.StdEncoding.EncodeToString(plaintext), + } + response, err := kmsService.Projects.Locations.KeyRings.CryptoKeys. + Encrypt(kmsKeyId, request). + Context(ctx). + Do() + if err != nil { + return nil, err + } + if response.Ciphertext == "" { + return nil, fmt.Errorf("KMS returned empty ciphertext") + } + return []byte(response.Ciphertext), nil +} - _, err = rootTokenObject.Write([]byte(rootTokenEncryptResponse.Ciphertext)) +func decryptSecretWithKMS(ctx context.Context, ciphertext []byte) ([]byte, error) { + request := &cloudkms.DecryptRequest{Ciphertext: string(ciphertext)} + response, err := kmsService.Projects.Locations.KeyRings.CryptoKeys. + Decrypt(kmsKeyId, request). + Context(ctx). + Do() + if err != nil { + return nil, err + } + if response.Plaintext == "" { + return nil, fmt.Errorf("KMS returned empty plaintext") + } + plaintext, err := base64.StdEncoding.DecodeString(response.Plaintext) if err != nil { - log.Println(err) + return nil, fmt.Errorf("decode KMS plaintext: %w", err) } + return plaintext, nil +} - log.Printf("Root token written to gs://%s/%s", gcsBucketName, "root-token.enc") +func storeEncryptedSecretOnce(ctx context.Context, name string, ciphertext []byte) error { + object := storageClient.Bucket(gcsBucketName).Object(name) + writeCtx, cancelWrite := context.WithCancel(ctx) + writer := object.If(storage.Conditions{DoesNotExist: true}).NewWriter(writeCtx) + commitUncertain, err := writeObjectOnce(writer, cancelWrite, ciphertext) + if err == nil { + return nil + } + if !commitUncertain { + return fmt.Errorf("write object: %w", err) + } - log.Println("Initialization complete.") + // A successful object commit can race with a lost Close response. Treat an + // existing byte-identical object as success, while refusing to overwrite + // recovery material from any other initialization. + reader, readErr := object.NewReader(ctx) + if readErr != nil { + return fmt.Errorf("commit object: %w (verify existing object: %v)", err, readErr) + } + existing, readErr := io.ReadAll(io.LimitReader(reader, int64(len(ciphertext))+1)) + closeErr := reader.Close() + if readErr != nil { + return fmt.Errorf("commit object: %w (read existing object: %v)", err, readErr) + } + if closeErr != nil { + return fmt.Errorf("commit object: %w (close existing object: %v)", err, closeErr) + } + if !bytes.Equal(existing, ciphertext) { + return fmt.Errorf("commit object: %w (an object with different recovery material already exists)", err) + } + return nil } -func unseal() { - bucket := storageClient.Bucket(gcsBucketName) +func writeObjectOnce(writer io.WriteCloser, cancel func(), data []byte) (commitUncertain bool, err error) { + written, writeErr := writer.Write(data) + if writeErr != nil || written != len(data) { + if writeErr == nil { + writeErr = io.ErrShortWrite + } + // Cancel before closing so Close cannot finalize a partial object. + cancel() + _ = writer.Close() + return false, writeErr + } + closeErr := writer.Close() + cancel() + if closeErr != nil { + // The server might have committed the object even though Close's response + // was lost, so the caller must verify the create-only destination. + return true, closeErr + } + return false, nil +} - ctx := context.Background() - unsealKeysObject, err := bucket.Object("unseal-keys.json.enc").NewReader(ctx) +func statEncryptedSecret(ctx context.Context, name string) (int64, error) { + attrs, err := storageClient.Bucket(gcsBucketName).Object(name).Attrs(ctx) if err != nil { - log.Println(err) - return + return 0, err + } + return attrs.Size, nil +} + +func ensureInitializationTargetsEmpty(ctx context.Context, stat encryptedSecretStat) error { + for _, name := range []string{unsealKeysObjectName, rootTokenObjectName} { + _, err := stat(ctx, name) + if errors.Is(err, storage.ErrObjectNotExist) { + continue + } + if err != nil { + return fmt.Errorf("check gs://%s/%s before Vault initialization: %w", gcsBucketName, name, err) + } + return fmt.Errorf("refusing to initialize Vault because gs://%s/%s already exists", gcsBucketName, name) } + return nil +} - defer unsealKeysObject.Close() +func verifyDurableInitialization() error { + ctx, cancel := context.WithTimeout(context.Background(), secretWriteTimeout) + defer cancel() - unsealKeysData, err := io.ReadAll(unsealKeysObject) - if err != nil { - log.Println(err) - return + initialErr := verifyStoredRecoveryMaterial(ctx, statEncryptedSecret) + if initialErr == nil { + return nil + } + + bundleSize, bundleErr := statEncryptedSecret(ctx, unsealKeysObjectName) + _, rootErr := statEncryptedSecret(ctx, rootTokenObjectName) + if bundleErr != nil || bundleSize <= 0 || !errors.Is(rootErr, storage.ErrObjectNotExist) { + return initialErr } - unsealKeysDecryptRequest := &cloudkms.DecryptRequest{ - Ciphertext: string(unsealKeysData), + if err := restoreRootTokenObject( + ctx, + readEncryptedSecret, + decryptSecretWithKMS, + encryptSecretWithKMS, + storeEncryptedSecretOnce, + ); err != nil { + return fmt.Errorf("%w; restore missing root-token object from durable response: %v", initialErr, err) } + log.Printf("Restored root token to gs://%s/%s from the durable initialization response", gcsBucketName, rootTokenObjectName) + return verifyStoredRecoveryMaterial(ctx, statEncryptedSecret) +} - unsealKeysDecryptResponse, err := kmsService.Projects.Locations.KeyRings.CryptoKeys.Decrypt(kmsKeyId, unsealKeysDecryptRequest).Do() +func restoreRootTokenObject( + ctx context.Context, + read encryptedSecretReader, + decrypt secretDecrypter, + encrypt secretEncrypter, + store encryptedSecretStore, +) error { + protectedResponse, err := read(ctx, unsealKeysObjectName) + if err != nil { + return fmt.Errorf("read encrypted initialization response: %w", err) + } + initResponseJSON, err := decrypt(ctx, protectedResponse) if err != nil { - log.Println(err) - return + return fmt.Errorf("decrypt initialization response: %w", err) } var initResponse InitResponse + if err := json.Unmarshal(initResponseJSON, &initResponse); err != nil { + return fmt.Errorf("decode initialization response: %w", err) + } + if initResponse.RootToken == "" { + return fmt.Errorf("initialization response did not contain a root token") + } + + protectedRootToken, err := encrypt(ctx, []byte(initResponse.RootToken)) + if err != nil { + return fmt.Errorf("encrypt root token: %w", err) + } + if err := store(ctx, rootTokenObjectName, protectedRootToken); err != nil { + return fmt.Errorf("store encrypted root token: %w", err) + } + return nil +} + +func readEncryptedSecret(ctx context.Context, name string) ([]byte, error) { + reader, err := storageClient.Bucket(gcsBucketName).Object(name).NewReader(ctx) + if err != nil { + return nil, err + } + data, readErr := io.ReadAll(io.LimitReader(reader, maxEncryptedBundle+1)) + closeErr := reader.Close() + if readErr != nil { + return nil, readErr + } + if closeErr != nil { + return nil, closeErr + } + if len(data) == 0 { + return nil, fmt.Errorf("encrypted object is empty") + } + if len(data) > maxEncryptedBundle { + return nil, fmt.Errorf("encrypted object exceeds %d bytes", maxEncryptedBundle) + } + return data, nil +} + +func verifyStoredRecoveryMaterial(ctx context.Context, stat encryptedSecretStat) error { + for _, name := range []string{unsealKeysObjectName, rootTokenObjectName} { + size, err := stat(ctx, name) + if errors.Is(err, storage.ErrObjectNotExist) { + return fmt.Errorf("gs://%s/%s is missing", gcsBucketName, name) + } + if err != nil { + return fmt.Errorf("inspect gs://%s/%s: %w", gcsBucketName, name, err) + } + if size <= 0 { + return fmt.Errorf("gs://%s/%s is empty", gcsBucketName, name) + } + } + return nil +} + +func unseal() error { + ctx, cancel := context.WithTimeout(context.Background(), secretWriteTimeout) + defer cancel() + unsealKeysData, err := readEncryptedSecret(ctx, unsealKeysObjectName) + if err != nil { + return fmt.Errorf("read encrypted initialization response: %w", err) + } - unsealKeysPlaintext, err := base64.StdEncoding.DecodeString(unsealKeysDecryptResponse.Plaintext) + unsealKeysPlaintext, err := decryptSecretWithKMS(ctx, unsealKeysData) if err != nil { - log.Println(err) - return + return fmt.Errorf("decrypt initialization response: %w", err) } + var initResponse InitResponse + if err := json.Unmarshal(unsealKeysPlaintext, &initResponse); err != nil { - log.Println(err) - return + return fmt.Errorf("decode initialization response: %w", err) + } + if len(initResponse.KeysBase64) == 0 { + return fmt.Errorf("initialization response contains no unseal keys") } for _, key := range initResponse.KeysBase64 { done, err := unsealOne(key) - if done { - return - } - if err != nil { - log.Println(err) - return + return err + } + if done { + return nil } } + + return fmt.Errorf("vault remains sealed after submitting %d unseal keys", len(initResponse.KeysBase64)) } func unsealOne(key string) (bool, error) { @@ -474,7 +948,7 @@ func processTLSConfig(cfg *tls.Config, serverName, caCert, caPath string) error // If a CA cert is provided, trust only that cert if caCert != "" { - b, err := os.ReadFile(caCert) + b, err := os.ReadFile(caCert) // #nosec G304 -- explicit operator-supplied CA file if err != nil { return fmt.Errorf("failed to read CA cert: %w", err) } @@ -498,7 +972,10 @@ func processTLSConfig(cfg *tls.Config, serverName, caCert, caPath string) error root := x509.NewCertPool() for _, f := range files { - b, err := os.ReadFile(f.Name()) + if f.IsDir() { + continue + } + b, err := os.ReadFile(filepath.Join(caPath, f.Name())) // #nosec G304 -- name came from ReadDir of this directory if err != nil { return fmt.Errorf("failed to read cert: %w", err) } @@ -520,22 +997,71 @@ func newVaultRequest(method, url string, body io.Reader) (*http.Request, error) return nil, err } + request.Header.Set("Accept", "application/json") + if method == http.MethodPut { + request.Header.Set("Content-Type", "application/json") + } + + // The proxy deliberately exposes the health endpoint without credentials. + // Avoid minting and transmitting a privileged Google token for the frequent + // readiness poll; only initialization and unseal operations require it. + if request.URL.Path == "/v1/sys/health" { + return request, nil + } + accessToken, err := accessTokenFromMetadata() if err != nil { return nil, fmt.Errorf("failed to get access token from metadata server: %w", err) } request.Header.Set("X-Admin-Token", accessToken) - request.Header.Set("Accept", "application/json") - if method == http.MethodPut { - request.Header.Set("Content-Type", "application/json") - } return request, nil } +func refuseHTTPRedirect(_ *http.Request, _ []*http.Request) error { + // Never let a redirect move a privileged Vault header to another URL, turn an + // initialization PUT into a GET with ambiguous commit semantics, or move the + // metadata token exchange away from the link-local metadata service. + return http.ErrUseLastResponse +} + +func validateVaultAddress(raw string, allowPlaintext bool) (string, error) { + address, err := url.Parse(strings.TrimSpace(raw)) + if err != nil { + return "", fmt.Errorf("parse VAULT_ADDR: %w", err) + } + if address.Scheme == "" || address.Host == "" || address.Opaque != "" { + return "", fmt.Errorf("VAULT_ADDR must be an absolute URL") + } + if address.User != nil { + return "", fmt.Errorf("VAULT_ADDR must not contain credentials") + } + if address.RawQuery != "" || address.Fragment != "" { + return "", fmt.Errorf("VAULT_ADDR must not contain a query or fragment") + } + if address.Path != "" && address.Path != "/" { + return "", fmt.Errorf("VAULT_ADDR must not contain a path") + } + + address.Scheme = strings.ToLower(address.Scheme) + switch address.Scheme { + case "https": + case "http": + if !allowPlaintext { + return "", fmt.Errorf("VAULT_ADDR uses plaintext HTTP; set VAULT_ALLOW_PLAINTEXT=true only for an explicitly accepted development risk") + } + default: + return "", fmt.Errorf("VAULT_ADDR must use HTTPS") + } + + address.Path = "" + address.RawPath = "" + return address.String(), nil +} + func accessTokenFromMetadata() (string, error) { - const metadataTokenURL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" + const metadataTokenURL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" // #nosec G101 -- metadata endpoint, not a credential value request, err := http.NewRequest(http.MethodGet, metadataTokenURL, nil) if err != nil { @@ -543,8 +1069,7 @@ func accessTokenFromMetadata() (string, error) { } request.Header.Set("Metadata-Flavor", "Google") - client := &http.Client{Timeout: 5 * time.Second} - response, err := client.Do(request) + response, err := metadataClient.Do(request) if err != nil { return "", err } @@ -566,6 +1091,19 @@ func accessTokenFromMetadata() (string, error) { return tokenResponse.AccessToken, nil } +func newMetadataHTTPClient() *http.Client { + return &http.Client{ + Timeout: 5 * time.Second, + CheckRedirect: refuseHTTPRedirect, + // The metadata endpoint is necessarily plain HTTP. A nil Proxy function + // deliberately prevents HTTP_PROXY from observing its token response. + Transport: &http.Transport{ + Proxy: nil, + DisableKeepAlives: true, + }, + } +} + func boolFromEnv(env string, def bool) bool { val := os.Getenv(env) if val == "" { @@ -604,7 +1142,7 @@ func durFromEnv(env string, def time.Duration) time.Duration { return def } r := val[len(val)-1] - if r >= '0' || r <= '9' { + if r >= '0' && r <= '9' { val = val + "s" // assume seconds } d, err := time.ParseDuration(val) diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..4135a82 --- /dev/null +++ b/main_test.go @@ -0,0 +1,787 @@ +package main + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + "time" + + "cloud.google.com/go/storage" +) + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) { + return f(request) +} + +type scriptedWriteCloser struct { + written int + writeErr error + closeErr error + closed int + callOrder *[]string +} + +func (w *scriptedWriteCloser) Write(_ []byte) (int, error) { + if w.callOrder != nil { + *w.callOrder = append(*w.callOrder, "write") + } + return w.written, w.writeErr +} + +func (w *scriptedWriteCloser) Close() error { + w.closed++ + if w.callOrder != nil { + *w.callOrder = append(*w.callOrder, "close") + } + return w.closeErr +} + +func TestRetryInitializationPersistenceRetainsOneTimeResponse(t *testing.T) { + initResponse := []byte(`{"keys_base64":["key"],"root_token":"root"}`) + encryptCalls := 0 + encrypt := func(_ context.Context, plaintext []byte) ([]byte, error) { + encryptCalls++ + if encryptCalls == 1 { + return nil, errors.New("transient KMS failure") + } + return append([]byte("encrypted:"), plaintext...), nil + } + + var writes []struct { + name string + data []byte + } + rootWriteAttempts := 0 + store := func(_ context.Context, name string, ciphertext []byte) error { + writes = append(writes, struct { + name string + data []byte + }{name: name, data: bytes.Clone(ciphertext)}) + if name == rootTokenObjectName { + rootWriteAttempts++ + if rootWriteAttempts == 1 { + return errors.New("transient GCS failure") + } + } + return nil + } + + var waits []time.Duration + err := retryInitializationPersistence( + initResponse, + "root", + encrypt, + store, + func(delay time.Duration) { waits = append(waits, delay) }, + ) + if err != nil { + t.Fatal(err) + } + + if encryptCalls != 3 { + t.Fatalf("encrypt calls = %d, want 3", encryptCalls) + } + if !reflect.DeepEqual(waits, []time.Duration{time.Second, 2 * time.Second}) { + t.Fatalf("retry delays = %v", waits) + } + wantWrites := []struct { + name string + data []byte + }{ + {name: unsealKeysObjectName, data: append([]byte("encrypted:"), initResponse...)}, + {name: rootTokenObjectName, data: []byte("encrypted:root")}, + {name: rootTokenObjectName, data: []byte("encrypted:root")}, + } + if !reflect.DeepEqual(writes, wantWrites) { + t.Fatalf("writes = %#v, want %#v", writes, wantWrites) + } +} + +func TestRetryInitializationPersistenceMakesRecoveryBundleDurableBeforeRootTokenWork(t *testing.T) { + initResponse := []byte(`{"keys_base64":["key"],"root_token":"root"}`) + var events []string + err := retryInitializationPersistence( + initResponse, + "root", + func(_ context.Context, plaintext []byte) ([]byte, error) { + if bytes.Equal(plaintext, initResponse) { + events = append(events, "encrypt bundle") + return []byte("bundle"), nil + } + events = append(events, "encrypt root") + return []byte("token"), nil + }, + func(_ context.Context, name string, _ []byte) error { + events = append(events, "store "+name) + return nil + }, + func(time.Duration) { t.Fatal("unexpected retry") }, + ) + if err != nil { + t.Fatal(err) + } + want := []string{ + "encrypt bundle", + "store " + unsealKeysObjectName, + "encrypt root", + "store " + rootTokenObjectName, + } + if !reflect.DeepEqual(events, want) { + t.Fatalf("events = %v, want %v", events, want) + } +} + +func TestRetryInitializationPersistencePreservesUnexpectedResponse(t *testing.T) { + response := []byte(`{"unexpected":"response"}`) + var writes []string + err := retryInitializationPersistence( + response, + "", + func(_ context.Context, plaintext []byte) ([]byte, error) { + return append([]byte("encrypted:"), plaintext...), nil + }, + func(_ context.Context, name string, _ []byte) error { + writes = append(writes, name) + return nil + }, + func(time.Duration) { t.Fatal("unexpected retry") }, + ) + if err == nil || !strings.Contains(err.Error(), "complete encrypted response is durable") { + t.Fatalf("error = %v, want durable-bundle warning", err) + } + if !reflect.DeepEqual(writes, []string{unsealKeysObjectName}) { + t.Fatalf("writes = %v, want only recovery bundle", writes) + } +} + +func TestProcessTLSConfigReadsCertificatesFromCAPath(t *testing.T) { + server := httptest.NewTLSServer(nil) + server.Close() + certificate := server.Certificate() + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw}) + + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "ca.pem"), certPEM, 0o600); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(dir, "ignored"), 0o700); err != nil { + t.Fatal(err) + } + + cfg := &tls.Config{} + if err := processTLSConfig(cfg, "vault.internal", "", dir); err != nil { + t.Fatal(err) + } + if cfg.ServerName != "vault.internal" { + t.Fatalf("server name = %q", cfg.ServerName) + } + if cfg.RootCAs == nil { + t.Fatal("root CA pool was not configured") + } + wantRoots := x509.NewCertPool() + if !wantRoots.AppendCertsFromPEM(certPEM) { + t.Fatal("failed to construct expected root CA pool") + } + if !cfg.RootCAs.Equal(wantRoots) { + t.Fatal("configured root CA pool does not match the CA path") + } +} + +func TestDurationFromEnvironmentAcceptsSecondsAndGoDurations(t *testing.T) { + t.Setenv("TEST_DURATION", "30") + if got := durFromEnv("TEST_DURATION", 0); got != 30*time.Second { + t.Fatalf("numeric duration = %s", got) + } + t.Setenv("TEST_DURATION", "1500ms") + if got := durFromEnv("TEST_DURATION", 0); got != 1500*time.Millisecond { + t.Fatalf("Go duration = %s", got) + } +} + +func TestValidateVaultAddress(t *testing.T) { + tests := []struct { + name string + address string + allowPlaintext bool + want string + wantError string + }{ + {name: "https", address: "https://vault.internal:8200", want: "https://vault.internal:8200"}, + {name: "trim and normalize", address: " HTTPS://vault.internal:8200/ ", want: "https://vault.internal:8200"}, + {name: "explicit plaintext", address: "http://127.0.0.1:8200", allowPlaintext: true, want: "http://127.0.0.1:8200"}, + {name: "plaintext rejected", address: "http://vault.internal:8200", wantError: "plaintext HTTP"}, + {name: "relative rejected", address: "vault.internal:8200", wantError: "absolute URL"}, + {name: "credentials rejected", address: "https://userinfo@vault.internal", wantError: "must not contain credentials"}, + {name: "path rejected", address: "https://vault.internal/base", wantError: "must not contain a path"}, + {name: "query rejected", address: "https://vault.internal?mode=test", wantError: "must not contain a query"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validateVaultAddress(tt.address, tt.allowPlaintext) + if tt.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tt.wantError) { + t.Fatalf("error = %v, want text %q", err, tt.wantError) + } + return + } + if err != nil { + t.Fatal(err) + } + if got != tt.want { + t.Fatalf("address = %q, want %q", got, tt.want) + } + }) + } +} + +func TestVaultClientRefusesRedirects(t *testing.T) { + redirected := false + target := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + redirected = true + })) + defer target.Close() + + source := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) { + response.Header().Set("Location", target.URL) + response.WriteHeader(http.StatusTemporaryRedirect) + })) + defer source.Close() + + client := &http.Client{CheckRedirect: refuseHTTPRedirect} + request, err := http.NewRequest(http.MethodPut, source.URL, strings.NewReader(`{"initialize":true}`)) + if err != nil { + t.Fatal(err) + } + request.Header.Set("X-Admin-Token", "privileged-token") + + response, err := client.Do(request) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + if response.StatusCode != http.StatusTemporaryRedirect { + t.Fatalf("status = %d, want %d", response.StatusCode, http.StatusTemporaryRedirect) + } + if redirected { + t.Fatal("Vault client followed a redirect carrying the admin token") + } +} + +func TestMetadataClientDoesNotUseProxyOrRedirects(t *testing.T) { + client := newMetadataHTTPClient() + transport, ok := client.Transport.(*http.Transport) + if !ok { + t.Fatalf("metadata transport type = %T", client.Transport) + } + if transport.Proxy != nil { + t.Fatal("metadata client permits an environment-configured HTTP proxy") + } + if !transport.DisableKeepAlives { + t.Fatal("metadata client retains an idle plain-HTTP connection") + } + if client.CheckRedirect == nil { + t.Fatal("metadata client permits redirects") + } + if err := client.CheckRedirect(nil, nil); !errors.Is(err, http.ErrUseLastResponse) { + t.Fatalf("redirect error = %v, want http.ErrUseLastResponse", err) + } +} + +func TestVaultHealthRequestDoesNotMintOrSendAdminToken(t *testing.T) { + originalMetadataClient := metadataClient + t.Cleanup(func() { + metadataClient = originalMetadataClient + }) + + metadataCalls := 0 + metadataClient = &http.Client{ + Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { + metadataCalls++ + return nil, errors.New("metadata must not be called for health") + }), + } + + request, err := newVaultRequest(http.MethodHead, "https://vault.internal/v1/sys/health", nil) + if err != nil { + t.Fatal(err) + } + if metadataCalls != 0 { + t.Fatalf("metadata calls = %d, want 0", metadataCalls) + } + if token := request.Header.Get("X-Admin-Token"); token != "" { + t.Fatalf("health request carries X-Admin-Token %q", token) + } + if accept := request.Header.Get("Accept"); accept != "application/json" { + t.Fatalf("Accept = %q, want application/json", accept) + } +} + +func TestRetryDelayIsCapped(t *testing.T) { + if got := nextRetryDelay(45 * time.Second); got != maxSecretRetryDelay { + t.Fatalf("next retry delay = %s", got) + } +} + +func TestHealthStatusIndicatesInitialized(t *testing.T) { + tests := map[int]bool{ + 200: true, + 429: true, + 472: true, + 473: true, + 501: false, + 503: true, + 500: false, + } + for statusCode, want := range tests { + if got := healthStatusIndicatesInitialized(statusCode); got != want { + t.Errorf("status %d: initialized = %t, want %t", statusCode, got, want) + } + } +} + +func TestShutdownRequestedConsumesPendingSignal(t *testing.T) { + shutdown := make(chan os.Signal, 1) + if shutdownRequested(shutdown) { + t.Fatal("empty signal channel reported shutdown") + } + shutdown <- os.Interrupt + if !shutdownRequested(shutdown) { + t.Fatal("pending signal was not observed") + } + if shutdownRequested(shutdown) { + t.Fatal("signal was not consumed") + } +} + +func TestShutdownExitCodeKeepsOneShotJobsRetryable(t *testing.T) { + if got := shutdownExitCode(true); got != 1 { + t.Fatalf("shutdownExitCode(true) = %d, want 1", got) + } + if got := shutdownExitCode(false); got != 0 { + t.Fatalf("shutdownExitCode(false) = %d, want 0", got) + } +} + +func TestVerifyStoredRecoveryMaterial(t *testing.T) { + lookupFailure := errors.New("storage unavailable") + tests := []struct { + name string + sizes map[string]int64 + errors map[string]error + wantError string + }{ + { + name: "complete", + sizes: map[string]int64{ + unsealKeysObjectName: 512, + rootTokenObjectName: 128, + }, + }, + { + name: "missing mandatory recovery bundle", + sizes: map[string]int64{rootTokenObjectName: 128}, + wantError: unsealKeysObjectName + " is missing", + }, + { + name: "missing convenience root token", + sizes: map[string]int64{unsealKeysObjectName: 512}, + wantError: rootTokenObjectName + " is missing", + }, + { + name: "empty mandatory recovery bundle", + sizes: map[string]int64{ + unsealKeysObjectName: 0, + rootTokenObjectName: 128, + }, + wantError: unsealKeysObjectName + " is empty", + }, + { + name: "empty convenience root token", + sizes: map[string]int64{ + unsealKeysObjectName: 512, + rootTokenObjectName: 0, + }, + wantError: rootTokenObjectName + " is empty", + }, + { + name: "lookup failure", + errors: map[string]error{unsealKeysObjectName: lookupFailure}, + wantError: lookupFailure.Error(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stat := func(_ context.Context, name string) (int64, error) { + if err := tt.errors[name]; err != nil { + return 0, err + } + if size, ok := tt.sizes[name]; ok { + return size, nil + } + return 0, storage.ErrObjectNotExist + } + + err := verifyStoredRecoveryMaterial(context.Background(), stat) + if tt.wantError == "" { + if err != nil { + t.Fatal(err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tt.wantError) { + t.Fatalf("error = %v, want text %q", err, tt.wantError) + } + }) + } +} + +func TestRestoreRootTokenObjectFromDurableResponse(t *testing.T) { + response := []byte(`{"keys_base64":["key"],"root_token":"root"}`) + var storedName string + var storedData []byte + err := restoreRootTokenObject( + context.Background(), + func(_ context.Context, name string) ([]byte, error) { + if name != unsealKeysObjectName { + t.Fatalf("read object = %q", name) + } + return append([]byte("encrypted:"), response...), nil + }, + func(_ context.Context, ciphertext []byte) ([]byte, error) { + return bytes.TrimPrefix(ciphertext, []byte("encrypted:")), nil + }, + func(_ context.Context, plaintext []byte) ([]byte, error) { + return append([]byte("encrypted:"), plaintext...), nil + }, + func(_ context.Context, name string, data []byte) error { + storedName = name + storedData = bytes.Clone(data) + return nil + }, + ) + if err != nil { + t.Fatal(err) + } + if storedName != rootTokenObjectName { + t.Fatalf("stored object = %q", storedName) + } + if string(storedData) != "encrypted:root" { + t.Fatalf("stored data = %q", storedData) + } +} + +func TestRestoreRootTokenObjectRejectsUnusableResponse(t *testing.T) { + err := restoreRootTokenObject( + context.Background(), + func(context.Context, string) ([]byte, error) { return []byte("encrypted"), nil }, + func(context.Context, []byte) ([]byte, error) { return []byte(`{"keys_base64":["key"]}`), nil }, + func(context.Context, []byte) ([]byte, error) { + t.Fatal("root token encryption ran for unusable response") + return nil, nil + }, + func(context.Context, string, []byte) error { + t.Fatal("root token write ran for unusable response") + return nil + }, + ) + if err == nil || !strings.Contains(err.Error(), "did not contain a root token") { + t.Fatalf("error = %v, want missing root token", err) + } +} + +func TestPreflightInitializationChecksTargetsBeforeKMS(t *testing.T) { + encryptCalls := 0 + stat := func(_ context.Context, name string) (int64, error) { + if name == unsealKeysObjectName { + return 10, nil + } + return 0, storage.ErrObjectNotExist + } + encrypt := func(_ context.Context, _ []byte) ([]byte, error) { + encryptCalls++ + return []byte("ciphertext"), nil + } + decrypt := func(_ context.Context, _ []byte) ([]byte, error) { + t.Fatal("KMS decrypt ran after existing recovery material was found") + return nil, nil + } + testPermissions := func(_ context.Context, _ []string) ([]string, error) { + t.Fatal("permission check ran after existing recovery material was found") + return nil, nil + } + + err := preflightInitialization(context.Background(), stat, testPermissions, encrypt, decrypt, func(context.Context) error { + t.Fatal("storage round trip ran after existing recovery material was found") + return nil + }) + if err == nil || !strings.Contains(err.Error(), "already exists") { + t.Fatalf("error = %v, want existing-object refusal", err) + } + if encryptCalls != 0 { + t.Fatalf("KMS called %d times after storage preflight failure", encryptCalls) + } +} + +func TestPreflightInitializationRequiresWorkingKMS(t *testing.T) { + stat := func(_ context.Context, _ string) (int64, error) { + return 0, storage.ErrObjectNotExist + } + testPermissions := func(_ context.Context, permissions []string) ([]string, error) { + return permissions, nil + } + encrypt := func(_ context.Context, plaintext []byte) ([]byte, error) { + if string(plaintext) != kmsPreflightPlaintext { + t.Fatalf("preflight plaintext = %q", plaintext) + } + return nil, errors.New("KMS unavailable") + } + decrypt := func(_ context.Context, _ []byte) ([]byte, error) { + t.Fatal("KMS decrypt ran after encryption failed") + return nil, nil + } + + err := preflightInitialization(context.Background(), stat, testPermissions, encrypt, decrypt, func(context.Context) error { + t.Fatal("storage round trip ran after KMS failure") + return nil + }) + if err == nil || !strings.Contains(err.Error(), "verify KMS encryption") { + t.Fatalf("error = %v, want KMS preflight failure", err) + } +} + +func TestPreflightInitializationRequiresCreateAndReadPermissions(t *testing.T) { + stat := func(_ context.Context, _ string) (int64, error) { + return 0, storage.ErrObjectNotExist + } + testPermissions := func(_ context.Context, _ []string) ([]string, error) { + return []string{"storage.objects.get"}, nil + } + encryptCalls := 0 + encrypt := func(_ context.Context, _ []byte) ([]byte, error) { + encryptCalls++ + return []byte("ciphertext"), nil + } + decrypt := func(_ context.Context, _ []byte) ([]byte, error) { + t.Fatal("KMS decrypt ran after permission failure") + return nil, nil + } + + err := preflightInitialization(context.Background(), stat, testPermissions, encrypt, decrypt, func(context.Context) error { + t.Fatal("storage round trip ran after permission failure") + return nil + }) + if err == nil || !strings.Contains(err.Error(), "missing storage.objects.create") { + t.Fatalf("error = %v, want missing create permission", err) + } + if encryptCalls != 0 { + t.Fatalf("KMS called %d times after permission failure", encryptCalls) + } +} + +func TestPreflightInitializationRequiresWorkingKMSDecryption(t *testing.T) { + stat := func(_ context.Context, _ string) (int64, error) { + return 0, storage.ErrObjectNotExist + } + testPermissions := func(_ context.Context, permissions []string) ([]string, error) { + return permissions, nil + } + encrypt := func(_ context.Context, _ []byte) ([]byte, error) { + return []byte("ciphertext"), nil + } + decrypt := func(_ context.Context, ciphertext []byte) ([]byte, error) { + if string(ciphertext) != "ciphertext" { + t.Fatalf("ciphertext = %q", ciphertext) + } + return nil, errors.New("decrypt permission denied") + } + + err := preflightInitialization(context.Background(), stat, testPermissions, encrypt, decrypt, func(context.Context) error { + t.Fatal("storage round trip ran after KMS decrypt failure") + return nil + }) + if err == nil || !strings.Contains(err.Error(), "verify KMS decryption") { + t.Fatalf("error = %v, want KMS decryption preflight failure", err) + } +} + +func TestPreflightInitializationRejectsKMSRoundTripMismatch(t *testing.T) { + stat := func(_ context.Context, _ string) (int64, error) { + return 0, storage.ErrObjectNotExist + } + testPermissions := func(_ context.Context, permissions []string) ([]string, error) { + return permissions, nil + } + encrypt := func(_ context.Context, _ []byte) ([]byte, error) { + return []byte("ciphertext"), nil + } + decrypt := func(_ context.Context, _ []byte) ([]byte, error) { + return []byte("different plaintext"), nil + } + + err := preflightInitialization(context.Background(), stat, testPermissions, encrypt, decrypt, func(context.Context) error { + t.Fatal("storage round trip ran after KMS mismatch") + return nil + }) + if err == nil || !strings.Contains(err.Error(), "round-trip plaintext mismatch") { + t.Fatalf("error = %v, want KMS round-trip failure", err) + } +} + +func TestPreflightInitializationSucceeds(t *testing.T) { + stat := func(_ context.Context, _ string) (int64, error) { + return 0, storage.ErrObjectNotExist + } + testPermissions := func(_ context.Context, permissions []string) ([]string, error) { + return permissions, nil + } + encrypt := func(_ context.Context, plaintext []byte) ([]byte, error) { + return append([]byte("encrypted:"), plaintext...), nil + } + decrypt := func(_ context.Context, ciphertext []byte) ([]byte, error) { + return bytes.TrimPrefix(ciphertext, []byte("encrypted:")), nil + } + + storageRoundTrips := 0 + if err := preflightInitialization(context.Background(), stat, testPermissions, encrypt, decrypt, func(context.Context) error { + storageRoundTrips++ + return nil + }); err != nil { + t.Fatal(err) + } + if storageRoundTrips != 1 { + t.Fatalf("storage round trips = %d, want 1", storageRoundTrips) + } +} + +func TestVerifyStorageRoundTrip(t *testing.T) { + tests := []struct { + name string + storeErr error + readData []byte + readErr error + wantError string + }{ + {name: "success", readData: []byte("marker")}, + {name: "create failure", storeErr: errors.New("close failed"), wantError: "create marker object"}, + {name: "read failure", readErr: errors.New("read failed"), wantError: "read marker object"}, + {name: "content mismatch", readData: []byte("different"), wantError: "content mismatch"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + storeCalls := 0 + readCalls := 0 + err := verifyStorageRoundTrip( + context.Background(), + "vault-init-preflight/test", + []byte("marker"), + func(_ context.Context, name string, data []byte) error { + storeCalls++ + if name != "vault-init-preflight/test" || string(data) != "marker" { + t.Fatalf("store(%q, %q)", name, data) + } + return tt.storeErr + }, + func(_ context.Context, name string) ([]byte, error) { + readCalls++ + if name != "vault-init-preflight/test" { + t.Fatalf("read(%q)", name) + } + return tt.readData, tt.readErr + }, + ) + if tt.wantError == "" && err != nil { + t.Fatal(err) + } + if tt.wantError != "" && (err == nil || !strings.Contains(err.Error(), tt.wantError)) { + t.Fatalf("error = %v, want %q", err, tt.wantError) + } + if storeCalls != 1 { + t.Fatalf("store calls = %d, want 1", storeCalls) + } + wantReadCalls := 1 + if tt.storeErr != nil { + wantReadCalls = 0 + } + if readCalls != wantReadCalls { + t.Fatalf("read calls = %d, want %d", readCalls, wantReadCalls) + } + }) + } +} + +func TestVerifyStorageRoundTripRejectsEmptyMarker(t *testing.T) { + err := verifyStorageRoundTrip( + context.Background(), + "vault-init-preflight/test", + nil, + func(context.Context, string, []byte) error { + t.Fatal("empty marker was stored") + return nil + }, + func(context.Context, string) ([]byte, error) { + t.Fatal("empty marker was read") + return nil, nil + }, + ) + if err == nil || !strings.Contains(err.Error(), "marker is empty") { + t.Fatalf("error = %v, want empty marker", err) + } +} + +func TestWriteObjectOnce(t *testing.T) { + tests := []struct { + name string + writer scriptedWriteCloser + wantCommitUncertain bool + wantError error + }{ + {name: "success", writer: scriptedWriteCloser{written: 6}}, + {name: "short write", writer: scriptedWriteCloser{written: 3}, wantError: io.ErrShortWrite}, + {name: "write failure", writer: scriptedWriteCloser{writeErr: errors.New("write failed")}, wantError: errors.New("write failed")}, + {name: "ambiguous close failure", writer: scriptedWriteCloser{written: 6, closeErr: errors.New("close failed")}, wantCommitUncertain: true, wantError: errors.New("close failed")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + callOrder := []string{} + tt.writer.callOrder = &callOrder + cancelCalls := 0 + commitUncertain, err := writeObjectOnce(&tt.writer, func() { + cancelCalls++ + callOrder = append(callOrder, "cancel") + }, []byte("marker")) + if commitUncertain != tt.wantCommitUncertain { + t.Fatalf("commit uncertainty = %v, want %v", commitUncertain, tt.wantCommitUncertain) + } + if tt.wantError == nil && err != nil { + t.Fatal(err) + } + if tt.wantError != nil && (err == nil || err.Error() != tt.wantError.Error()) { + t.Fatalf("error = %v, want %v", err, tt.wantError) + } + if cancelCalls != 1 || tt.writer.closed != 1 { + t.Fatalf("cancel calls = %d, close calls = %d; want one each", cancelCalls, tt.writer.closed) + } + if tt.name == "short write" || tt.name == "write failure" { + wantOrder := []string{"write", "cancel", "close"} + if !reflect.DeepEqual(callOrder, wantOrder) { + t.Fatalf("call order = %v, want %v", callOrder, wantOrder) + } + } + }) + } +} From f9190dd00c1a1693eddd882ef842d447cfd382b0 Mon Sep 17 00:00:00 2001 From: Joe Corall Date: Thu, 16 Jul 2026 17:25:23 +0000 Subject: [PATCH 2/3] [patch] Request verified email metadata tokens --- README.md | 5 ++++- main.go | 12 ++++++++++-- main_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d52d1d2..3fbf94f 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,10 @@ The `vault-init` service supports the following environment variables for config because the service sends its Google access token in the `X-Admin-Token` header. The address must not contain credentials, a path, query, or fragment. Metadata token retrieval bypasses environment-configured HTTP proxies and does - not follow redirects. + not follow redirects. The token requests only the + `https://www.googleapis.com/auth/userinfo.email` scope required for Vault + Proxy to verify the service-account email; KMS and GCS use separate + application-default credentials. - `VAULT_ALLOW_PLAINTEXT` (false) - Permit an `http://` Vault address. This is an explicit development-only escape hatch because it exposes the Google access diff --git a/main.go b/main.go index 06a20a6..604b51b 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,8 @@ const ( unsealKeysObjectName = "unseal-keys.json.enc" rootTokenObjectName = "root-token.enc" // #nosec G101 -- fixed GCS object name, not a credential value kmsPreflightPlaintext = "vault-init initialization preflight" + metadataTokenURL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" // #nosec G101 -- metadata endpoint, not a credential value + vaultProxyEmailScope = "https://www.googleapis.com/auth/userinfo.email" secretWriteTimeout = 2 * time.Minute maxSecretRetryDelay = time.Minute maxEncryptedBundle = 256 << 10 @@ -1061,9 +1063,15 @@ func validateVaultAddress(raw string, allowPlaintext bool) (string, error) { } func accessTokenFromMetadata() (string, error) { - const metadataTokenURL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" // #nosec G101 -- metadata endpoint, not a credential value + tokenURL, err := url.Parse(metadataTokenURL) + if err != nil { + return "", fmt.Errorf("parse metadata token URL: %w", err) + } + query := tokenURL.Query() + query.Set("scopes", vaultProxyEmailScope) + tokenURL.RawQuery = query.Encode() - request, err := http.NewRequest(http.MethodGet, metadataTokenURL, nil) + request, err := http.NewRequest(http.MethodGet, tokenURL.String(), nil) if err != nil { return "", err } diff --git a/main_test.go b/main_test.go index 4135a82..c3fbb05 100644 --- a/main_test.go +++ b/main_test.go @@ -301,6 +301,46 @@ func TestMetadataClientDoesNotUseProxyOrRedirects(t *testing.T) { } } +func TestAccessTokenFromMetadataRequestsVerifiedEmailScope(t *testing.T) { + originalMetadataClient := metadataClient + t.Cleanup(func() { + metadataClient = originalMetadataClient + }) + + metadataClient = &http.Client{ + Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) { + if request.Method != http.MethodGet { + t.Fatalf("metadata method = %q, want GET", request.Method) + } + if request.URL.Scheme != "http" || request.URL.Host != "metadata.google.internal" { + t.Fatalf("metadata origin = %q, want http://metadata.google.internal", request.URL.Scheme+"://"+request.URL.Host) + } + if request.URL.Path != "/computeMetadata/v1/instance/service-accounts/default/token" { + t.Fatalf("metadata path = %q", request.URL.Path) + } + if got := request.URL.Query()["scopes"]; !reflect.DeepEqual(got, []string{vaultProxyEmailScope}) { + t.Fatalf("metadata scopes = %v, want only %q", got, vaultProxyEmailScope) + } + if flavor := request.Header.Get("Metadata-Flavor"); flavor != "Google" { + t.Fatalf("Metadata-Flavor = %q, want Google", flavor) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`{"access_token":"scoped-token"}`)), + Header: make(http.Header), + }, nil + }), + } + + token, err := accessTokenFromMetadata() + if err != nil { + t.Fatal(err) + } + if token != "scoped-token" { + t.Fatalf("access token = %q, want scoped-token", token) + } +} + func TestVaultHealthRequestDoesNotMintOrSendAdminToken(t *testing.T) { originalMetadataClient := metadataClient t.Cleanup(func() { From bf456ee132d1d67794b86208a6b2633b7633d890 Mon Sep 17 00:00:00 2001 From: "H.E. Pennypacker" <115990865+pennypacker-he@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:37:43 +0000 Subject: [PATCH 3/3] [skip-release] Restore required PR status gate --- .github/workflows/lint-test-build-push.yml | 10 ++++++++++ ci/publication_contract_test.go | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/.github/workflows/lint-test-build-push.yml b/.github/workflows/lint-test-build-push.yml index 3c814d7..950b513 100644 --- a/.github/workflows/lint-test-build-push.yml +++ b/.github/workflows/lint-test-build-push.yml @@ -96,6 +96,16 @@ jobs: severity: HIGH,CRITICAL vuln-type: os,library + build-push: + if: always() && github.event_name == 'pull_request' + needs: + - test + - image-check + uses: libops/.github/.github/workflows/pr-status.yaml@578137212ead4ab4059e95df17fa30e9b7ac4aed + permissions: {} + with: + needs-json: ${{ toJSON(needs) }} + publish: if: github.event_name != 'pull_request' needs: test diff --git a/ci/publication_contract_test.go b/ci/publication_contract_test.go index 08f29c0..6bf49d4 100644 --- a/ci/publication_contract_test.go +++ b/ci/publication_contract_test.go @@ -32,8 +32,13 @@ func TestPublicationUsesSharedGHCRAndGARContract(t *testing.T) { workflow := readFile(t, ".github", "workflows", "lint-test-build-push.yml") for _, required := range []string{ "libops/.github/.github/workflows/build-push.yaml@" + sharedWorkflowSHA, + "libops/.github/.github/workflows/pr-status.yaml@" + sharedWorkflowSHA, + "\n build-push:\n", "image-check:", "if: github.event_name == 'pull_request'", + "if: always() && github.event_name == 'pull_request'", + "needs-json: ${{ toJSON(needs) }}", + "permissions: {}", "ubuntu-24.04-arm", "aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25", "severity: HIGH,CRITICAL",