Repository: Azure/kubernetes-kms Branch: master Commit: 1a9b8f1fcd7f Files: 79 Total size: 299.5 KB Directory structure: gitextract_y5iov8qc/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ ├── semantic.yml │ └── workflows/ │ ├── codeql.yaml │ ├── create-release.yml │ ├── dependency-review.yml │ └── scorecards.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .pipelines/ │ ├── nightly.yml │ ├── pr.yml │ └── templates/ │ ├── cleanup-template.yml │ ├── cluster-health-template.yml │ ├── e2e-kind-template.yml │ ├── e2e-upgrade-template.yml │ ├── kind-debug-template.yml │ ├── manifest-template.yml │ ├── prepare-deps.yaml │ ├── scan-images-template.yml │ └── unit-tests-template.yml ├── AUTHORS ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── cmd/ │ └── server/ │ └── main.go ├── developers.md ├── docs/ │ ├── manual-install.md │ ├── metrics.md │ ├── rotation.md │ └── testing.md ├── go.mod ├── go.sum ├── pkg/ │ ├── auth/ │ │ ├── auth.go │ │ └── auth_test.go │ ├── config/ │ │ └── azure_config.go │ ├── consts/ │ │ └── consts.go │ ├── metrics/ │ │ ├── exporter.go │ │ ├── exporter_test.go │ │ ├── prometheus_exporter.go │ │ └── stats_reporter.go │ ├── plugin/ │ │ ├── healthz.go │ │ ├── healthz_test.go │ │ ├── keyvault.go │ │ ├── keyvault_test.go │ │ ├── kms_v2_server.go │ │ ├── kms_v2_server_test.go │ │ ├── mock_keyvault/ │ │ │ └── keyvault_mock.go │ │ ├── server.go │ │ └── server_test.go │ ├── utils/ │ │ ├── grpc.go │ │ ├── grpc_test.go │ │ ├── sanitize.go │ │ └── sanitize_test.go │ └── version/ │ ├── version.go │ └── version_test.go ├── scripts/ │ ├── connect-registry.sh │ ├── setup-kind-cluster.sh │ ├── setup-kmsv2-kind-cluster.sh │ └── setup-local-registry.sh ├── tests/ │ ├── client/ │ │ └── client_test.go │ └── e2e/ │ ├── azure.json │ ├── encryption-config.yaml │ ├── helpers.bash │ ├── kind-config.yaml │ ├── kms.yaml │ ├── kmsv2-encryption-config.yaml │ ├── test.bats │ └── testkmsv2.bats └── tools/ ├── go.mod ├── go.sum └── tools.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help KMS Plugin for Key Vault improve title: '' labels: bug assignees: '' --- **Describe the bug** **Steps To Reproduce** **Expected behavior** **KMS Plugin for Key Vault version** **Kubernetes version** **Additional context** ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for KMS Plugin for Key Vault title: '' labels: enhancement assignees: '' --- **Describe the request** **Explain why KMS Plugin for Key Vault needs it** **Describe the solution you'd like** **Describe alternatives you've considered** **Additional context** ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ **Reason for Change**: **Issue Fixed**: **Notes for Reviewers**: ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" commit-message: prefix: "chore" ignore: - dependency-name: "*" update-types: - "version-update:semver-major" - "version-update:semver-minor" - package-ecosystem: github-actions directory: / schedule: interval: daily commit-message: prefix: "chore" - package-ecosystem: docker directory: / schedule: interval: daily commit-message: prefix: "chore" - package-ecosystem: gomod directory: /tools schedule: interval: daily commit-message: prefix: "chore" ================================================ FILE: .github/semantic.yml ================================================ titleOnly: true types: - chore - ci - docs - feat - fix - perf - refactor - release - revert - security - test ================================================ FILE: .github/workflows/codeql.yaml ================================================ name: "CodeQL" on: push: branches: - master pull_request: branches: - master schedule: - cron: "0 15 * * 1" # Mondays at 7:00 AM PST permissions: read-all jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: security-events: write steps: - name: Harden Runner uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - name: Initialize CodeQL uses: github/codeql-action/init@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 with: languages: go - name: Autobuild uses: github/codeql-action/autobuild@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 ================================================ FILE: .github/workflows/create-release.yml ================================================ name: create_release on: push: tags: - 'v*' permissions: contents: write jobs: create-release: runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 with: egress-policy: audit - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: submodules: true fetch-depth: 0 - name: Goreleaser uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 with: version: "~> v2" args: release --clean --fail-fast --timeout 60m --verbose env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/dependency-review.yml ================================================ # Dependency Review Action # # This Action will scan dependency manifest files that change as part of a Pull Request, # surfacing known-vulnerable versions of the packages declared or updated in the PR. # Once installed, if the workflow run is marked as required, # PRs introducing known-vulnerable packages will be blocked from merging. # # Source repository: https://github.com/actions/dependency-review-action name: 'Dependency Review' on: [pull_request] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1 with: egress-policy: audit - name: 'Checkout Repository' uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: 'Dependency Review' uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c # v2.5.1 ================================================ FILE: .github/workflows/scorecards.yml ================================================ # This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '20 7 * * 2' push: branches: ["master"] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write contents: read actions: read steps: - name: Harden Runner uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1 with: egress-policy: audit - name: "Checkout code" uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecards on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@8662eabe0e9f338a07350b7fd050732745f93848 # v2.3.1 with: sarif_file: results.sarif ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ setenv.sh kubernetes-kms vendor *.env # Vscode files .vscode # OSX trash .DS_Store .idea/ _output/ # e2e output tests/e2e/generated_manifests/* # Go tools .tools/ ================================================ FILE: .golangci.yml ================================================ version: "2" run: go: "1.26" linters: default: none enable: - errorlint - goconst - gocyclo - gosec - govet - ineffassign - misspell - nakedret - prealloc - revive - staticcheck - unconvert - unused - whitespace exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - staticcheck text: "SA1019: .*(v1beta1|KMSv1 is deprecated)" paths: - third_party$ - builtin$ - examples$ settings: revive: rules: - name: var-naming disabled: true staticcheck: checks: - all formatters: enable: - gofmt - goimports exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ ================================================ FILE: .goreleaser.yml ================================================ # refer to https://goreleaser.com for more options version: 2 builds: - skip: true release: prerelease: auto header: | ## {{.Tag}} - {{ time "2006-01-02" }} changelog: disable: false groups: - title: Bug Fixes 🐞 regexp: ^.*fix[(\\w)]*:+.*$ - title: Build 🏭 regexp: ^.*build[(\\w)]*:+.*$ - title: Code Refactoring 💎 regexp: ^.*refactor[(\\w)]*:+.*$ - title: Code Style 🎶 regexp: ^.*style[(\\w)]*:+.*$ - title: Continuous Integration 💜 regexp: ^.*ci[(\\w)]*:+.*$ - title: Documentation 📘 regexp: ^.*docs[(\\w)]*:+.*$ - title: Features 🌈 regexp: ^.*feat[(\\w)]*:+.*$ - title: Maintenance 🔧 regexp: ^.*chore[(\\w)]*:+.*$ - title: Performance Improvements 🚀 regexp: ^.*perf[(\\w)]*:+.*$ - title: Revert Change ◀️ regexp: ^.*revert[(\\w)]*:+.*$ - title: Security Fix 🛡️ regexp: ^.*security[(\\w)]*:+.*$ - title: Testing 💚 regexp: ^.*test[(\\w)]*:+.*$ ================================================ FILE: .pipelines/nightly.yml ================================================ trigger: none schedules: - cron: "0 0 * * *" always: true displayName: "Nightly Build & Test" branches: include: - master pool: staging-pool-amd64-mariner-2 jobs: - template: templates/unit-tests-template.yml - template: templates/e2e-upgrade-template.yml ================================================ FILE: .pipelines/pr.yml ================================================ trigger: branches: include: - master pr: branches: include: - master paths: exclude: - docs/* - README.md - .github/* pool: staging-pool-amd64-mariner-2 jobs: - template: templates/unit-tests-template.yml - template: templates/e2e-kind-template.yml ================================================ FILE: .pipelines/templates/cleanup-template.yml ================================================ steps: - script: | kubectl logs -l component=azure-kms-provider -n kube-system --tail -1 kubectl get pods -o wide -A displayName: "Get logs" - script: make e2e-delete-kind displayName: "Delete cluster" ================================================ FILE: .pipelines/templates/cluster-health-template.yml ================================================ steps: - script: | kubectl wait --for=condition=ready node --all kubectl wait pod -n kube-system --for=condition=Ready --all kubectl get nodes -owide displayName: "Check cluster health" ================================================ FILE: .pipelines/templates/e2e-kind-template.yml ================================================ jobs: - job: timeoutInMinutes: 15 cancelTimeoutInMinutes: 5 workspace: clean: all variables: - name: REGISTRY_NAME value: kind-registry - name: REGISTRY_PORT value: 5000 - name: KUBERNETES_VERSION value: v1.32.3 - name: KIND_CLUSTER_NAME value: kms - name: KIND_NETWORK value: kind # contains the following environment variables: # - AZURE_TENANT_ID # - KEYVAULT_NAME # - KEY_NAME # - KEY_VERSION # - USER_ASSIGNED_IDENTITY_ID - group: kubernetes-kms strategy: matrix: kmsv1_kind_v1_33_7: KUBERNETES_VERSION: v1.33.7 kmsv1_kind_v1_34_3: KUBERNETES_VERSION: v1.34.3 kmsv1_kind_v1_35_0: KUBERNETES_VERSION: v1.35.0 steps: - task: GoTool@0 inputs: version: 1.26.2 - template: prepare-deps.yaml - script: make e2e-install-prerequisites displayName: "Install e2e test prerequisites" - script: | make e2e-setup-kind displayName: "Setup kind cluster with azure kms plugin" env: REGISTRY_NAME: $(REGISTRY_NAME) REGISTRY_PORT: $(REGISTRY_PORT) KUBERNETES_VERSION: $(KUBERNETES_VERSION) KIND_CLUSTER_NAME: $(KIND_CLUSTER_NAME) KIND_NETWORK: $(KIND_NETWORK) - template: cluster-health-template.yml - template: kind-debug-template.yml - script: make e2e-test displayName: "Run e2e tests for KMS v1" - template: cleanup-template.yml - job: timeoutInMinutes: 15 cancelTimeoutInMinutes: 5 workspace: clean: all variables: - name: REGISTRY_NAME value: kind-registry - name: REGISTRY_PORT value: 5000 - name: KUBERNETES_VERSION value: v1.32.3 - name: KIND_CLUSTER_NAME value: kms - name: KIND_NETWORK value: kind # contains the following environment variables: # - AZURE_TENANT_ID # - KEYVAULT_NAME # - KEY_NAME # - KEY_VERSION # - USER_ASSIGNED_IDENTITY_ID - group: kubernetes-kms strategy: matrix: kmsv2_kind_v1_33_7: KUBERNETES_VERSION: v1.33.7 kmsv2_kind_v1_34_3: KUBERNETES_VERSION: v1.34.3 kmsv2_kind_v1_35_0: KUBERNETES_VERSION: v1.35.0 steps: - task: GoTool@0 inputs: version: 1.26.2 - template: prepare-deps.yaml - script: make e2e-install-prerequisites displayName: "Install e2e test prerequisites" - script: | make e2e-kmsv2-setup-kind displayName: "Setup kind cluster with azure kms plugin" env: REGISTRY_NAME: $(REGISTRY_NAME) REGISTRY_PORT: $(REGISTRY_PORT) KUBERNETES_VERSION: $(KUBERNETES_VERSION) KIND_CLUSTER_NAME: $(KIND_CLUSTER_NAME) KIND_NETWORK: $(KIND_NETWORK) - template: cluster-health-template.yml - template: kind-debug-template.yml - script: make e2e-kmsv2-test displayName: "Run e2e tests for KMS v2" - template: cleanup-template.yml ================================================ FILE: .pipelines/templates/e2e-upgrade-template.yml ================================================ jobs: - job: e2e_upgrade_tests timeoutInMinutes: 10 cancelTimeoutInMinutes: 5 workspace: clean: all variables: - name: REGISTRY_NAME value: kind-registry - name: REGISTRY_PORT value: 5000 - name: KUBERNETES_VERSION value: v1.23.5 - name: KIND_CLUSTER_NAME value: kms - name: KIND_NETWORK value: kind # contains the following environment variables: # - AZURE_TENANT_ID # - KEYVAULT_NAME # - KEY_NAME # - KEY_VERSION # - USER_ASSIGNED_IDENTITY_ID - group: kubernetes-kms steps: - task: GoTool@0 inputs: version: 1.26.2 - template: prepare-deps.yaml - script: make e2e-install-prerequisites displayName: "Install e2e test prerequisites" - script: | . scripts/setup-local-registry.sh displayName: "Setup local registry" env: REGISTRY_NAME: $(REGISTRY_NAME) REGISTRY_PORT: $(REGISTRY_PORT) - script: | version=$(git tag -l --sort=v:refname | tail -n 1) echo "##vso[task.setvariable variable=LATEST_KMS_VERSION]$version" echo "Latest released kms version - $version" displayName: "Get latest released version" - template: manifest-template.yml parameters: registry: mcr.microsoft.com/oss/v2/azure/kms imageName: keyvault imageVersion: $(LATEST_KMS_VERSION) - script: | . scripts/setup-kind-cluster.sh & . scripts/connect-registry.sh & wait displayName: "Setup kind cluster with azure kms plugin" env: REGISTRY_NAME: $(REGISTRY_NAME) REGISTRY_PORT: $(REGISTRY_PORT) KUBERNETES_VERSION: $(KUBERNETES_VERSION) KIND_CLUSTER_NAME: $(KIND_CLUSTER_NAME) KIND_NETWORK: $(KIND_NETWORK) - template: cluster-health-template.yml - template: kind-debug-template.yml - script: make e2e-test displayName: "Run e2e tests" - script: | echo "##vso[task.setvariable variable=LOCAL_IMAGE_VERSION]$(git rev-parse --short HEAD)" displayName: "Update Image version" # This stage will upgrade kms plugin. The path (./tests/e2e/generated_manifests) is mounted in kind cluster. # Any changes in the host will automatically be reflected in /etc/kubernetes/manifests mount path and that static pod is restarted with new changes. # manifest-template updates these files with registry, imageName and version to desired upgrade values. - template: manifest-template.yml parameters: registry: localhost:$(REGISTRY_PORT) imageName: keyvault imageVersion: e2e-$(LOCAL_IMAGE_VERSION) - script: | # wait for the kind network to exist echo "waiting for upgraded kms pod to be Running" for i in $(seq 1 25); do image=$(kubectl get pods -n kube-system azure-kms-provider-kms-control-plane -o jsonpath="{.spec.containers[*].image}") phase=$(kubectl get pods -n kube-system azure-kms-provider-kms-control-plane -o jsonpath="{.status.phase}") echo "image - $image phase - $phase" if [ "${image}" == "${REGISTRY}/${IMAGE_NAME}:e2e-${LOCAL_IMAGE_VERSION}" ] && [ "${phase}" == "Running" ]; then break else sleep 5 fi done # Give additional 5s for plugin to start. Remove this once https://github.com/Azure/kubernetes-kms/issues/113 is fixed. sleep 5 displayName: "Wait for kms upgrade" - template: cluster-health-template.yml - template: kind-debug-template.yml - script: make e2e-test displayName: "Run e2e tests" - template: cleanup-template.yml ================================================ FILE: .pipelines/templates/kind-debug-template.yml ================================================ steps: - script: | docker exec kms-control-plane bash -c "cat /etc/kubernetes/manifests/kubernetes-kms.yaml" docker exec kms-control-plane bash -c "cat /etc/kubernetes/manifests/kube-apiserver.yaml" docker exec kms-control-plane bash -c "cat /etc/kubernetes/encryption-config.yaml" docker exec kms-control-plane bash -c "journalctl -u kubelet > kubelet.log && cat kubelet.log" docker exec kms-control-plane bash -c "cd /var/log/containers ; cat *" docker network ls displayName: "Debug logs" condition: failed() ================================================ FILE: .pipelines/templates/manifest-template.yml ================================================ parameters: - name: registry type: string - name: imageName type: string - name: imageVersion type: string steps: - script: | export REGISTRY=${{ parameters.registry }} export IMAGE_NAME=${{ parameters.imageName }} export IMAGE_VERSION=${{ parameters.imageVersion }} make e2e-generate-manifests echo "##vso[task.setvariable variable=REGISTRY]${{ parameters.registry }}" echo "##vso[task.setvariable variable=IMAGE_NAME]${{ parameters.imageName }}" displayName: "Generate Manifests" ================================================ FILE: .pipelines/templates/prepare-deps.yaml ================================================ steps: - bash: | for i in {1..10}; do if sudo tdnf install -y kernel-headers make gcc glibc-devel binutils gettext; then exit 0 fi echo "waiting until rpm lock is free" sleep 5 done exit 1 ================================================ FILE: .pipelines/templates/scan-images-template.yml ================================================ steps: - script: | export REGISTRY="e2e" export IMAGE_VERSION="test" export OUTPUT_TYPE="type=docker" make docker-init-buildx docker-build wget https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION:-0.70.0}/trivy_${TRIVY_VERSION:-0.70.0}_Linux-64bit.tar.gz tar zxvf trivy_${TRIVY_VERSION:-0.70.0}_Linux-64bit.tar.gz # show all vulnerabilities in the logs ./trivy image "${REGISTRY}/keyvault:${IMAGE_VERSION}" ./trivy image --exit-code 1 --ignore-unfixed --severity MEDIUM,HIGH,CRITICAL "${REGISTRY}/keyvault:${IMAGE_VERSION}" || exit 1 displayName: "Scan images for vulnerability" ================================================ FILE: .pipelines/templates/unit-tests-template.yml ================================================ jobs: - job: unit_tests timeoutInMinutes: 10 cancelTimeoutInMinutes: 5 workspace: clean: all variables: # contains the following environment variables: # - AZURE_TENANT_ID # - KEYVAULT_NAME # - KEY_NAME # - KEY_VERSION # - USER_ASSIGNED_IDENTITY_ID - group: kubernetes-kms steps: - task: GoTool@0 inputs: version: 1.26.2 - template: prepare-deps.yaml - script: make lint displayName: Run lint - script: make unit-test displayName: Run unit tests - script: make build displayName: Build - script: | sudo ./_output/kubernetes-kms --version displayName: Check binary version - script: | sudo mkdir /etc/kubernetes echo -e '{\n "tenantId": "'$AZURE_TENANT_ID'",\n "useManagedIdentityExtension": true,\n "userAssignedIdentityID": "'$USER_ASSIGNED_IDENTITY_ID'",\n}' | sudo tee --append /etc/kubernetes/azure.json > /dev/null sudo chown root:root /etc/kubernetes/azure.json && sudo chmod 600 /etc/kubernetes/azure.json displayName: Setup azure.json on host - script: | sudo ./_output/kubernetes-kms --keyvault-name $KEYVAULT_NAME --key-name $KEY_NAME --key-version $KEY_VERSION --listen-addr "unix:///opt/azurekms.sock" > /dev/null & echo Waiting 2 seconds for the server to start sleep 2 sudo env "PATH=$PATH" make integration-test displayName: Run integration tests - template: scan-images-template.yml ================================================ FILE: AUTHORS ================================================ Rita Zhang ================================================ FILE: CODEOWNERS ================================================ # Ref: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners * @aramase @enj ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Microsoft Open Source Code of Conduct This code of conduct outlines expectations for participation in Microsoft-managed open source communities, as well as steps for reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all. People violating this code of conduct may be banned from the community. Our open source communities strive to: - **Be friendly and patient:** Remember you might not be communicating in someone else's primary spoken or programming language, and others may not have your level of understanding. - **Be welcoming:** Our communities welcome and support people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. - **Be respectful:** We are a world-wide community of professionals, and we conduct ourselves professionally. Disagreement is no excuse for poor behavior and poor manners. Disrespectful and unacceptable behavior includes, but is not limited to: Violent threats or language. Discriminatory or derogatory jokes and language. Posting sexually explicit or violent material. Posting, or threatening to post, people's personally identifying information ("doxing"). Insults, especially those using discriminatory terms or slurs. Behavior that could be perceived as sexual attention. Advocating for or encouraging any of the above behaviors. - **Understand disagreements:** Disagreements, both social and technical, are useful learning opportunities. Seek to understand the other viewpoints and resolve differences constructively. - This code is not exhaustive or complete. It serves to capture our common understanding of a productive, collaborative environment. We expect the code to be followed in spirit as much as in the letter. ## Scope This code of conduct applies to all repos and communities for Microsoft-managed open source projects regardless of whether or not the repo explicitly calls out its use of this code. The code also applies in public spaces when an individual is representing a project or its community. Examples include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Note: Some Microsoft-managed communities have codes of conduct that pre-date this document and issue resolution process. While communities are not required to change their code, they are expected to use the resolution process outlined here. The review team will coordinate with the communities involved to address your concerns. ## Reporting Code of Conduct Issues We encourage all communities to resolve issues on their own whenever possible. This builds a broader and deeper understanding and ultimately a healthier interaction. In the event that an issue cannot be resolved locally, please feel free to report your concerns by contacting opencode@microsoft.com. Your report will be handled in accordance with the issue resolution process described in the [Code of Conduct FAQ]. In your report please include: - Your contact information. - Names (real, usernames or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. - Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public chat log), please include a link or attachment. - Any additional information that may be helpful. All reports will be reviewed by a multi-person team and will result in a response that is deemed necessary and appropriate to the circumstances. Where additional perspectives are needed, the team may seek insight from others with relevant expertise or experience. The confidentiality of the person reporting the incident will be kept at all times. Involved parties are never part of the review team. Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the review team may take any action they deem appropriate, including a permanent ban from the community. *This code of conduct is based on the [template] established by the [TODO Group] and used by numerous other large communities (e.g., [Facebook], [Yahoo], [Twitter], [GitHub]) and the Scope section from the [Contributor Covenant version 1.4].* [Code of Conduct FAQ]: https://opensource.microsoft.com/codeofconduct/faq/ [template]: http://todogroup.org/opencodeofconduct [TODO Group]: http://todogroup.org/ [Facebook]: https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct [Yahoo]: https://yahoo.github.io/codeofconduct [Twitter]: https://engineering.twitter.com/opensource/code-of-conduct [GitHub]: http://todogroup.org/opencodeofconduct/#opensource@github.com [Contributor Covenant version 1.4]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. ================================================ FILE: Dockerfile ================================================ FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.2-bookworm@sha256:61e607875d60ae21a7a4a49110fe7098355473fbc74ab13091e3c1160cc92f18 AS builder WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum # cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer RUN go mod download # Copy the go source COPY cmd/server/main.go main.go COPY pkg/ pkg/ ARG TARGETARCH ARG TARGETPLATFORM ARG LDFLAGS RUN MS_GO_NOSYSTEMCRYPTO=1 CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -a -ldflags "${LDFLAGS:--X github.com/Azure/kubernetes-kms/pkg/version.BuildVersion=latest}" -o _output/kubernetes-kms main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM --platform=${TARGETPLATFORM:-linux/amd64} mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0-nonroot.20250402@sha256:c5e349966c9a8ffe5af65970300d2b6899592da1714490b46561f5d86a0ab1e0 WORKDIR / COPY --from=builder /workspace/_output/kubernetes-kms . ENTRYPOINT [ "/kubernetes-kms" ] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Microsoft Corporation. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ================================================ FILE: Makefile ================================================ ORG_PATH=github.com/Azure PROJECT_NAME := kubernetes-kms REPO_PATH="$(ORG_PATH)/$(PROJECT_NAME)" REGISTRY_NAME ?= upstreamk8sci REPO_PREFIX ?= oss/azure/kms REGISTRY ?= $(REGISTRY_NAME).azurecr.io/$(REPO_PREFIX) LOCAL_REGISTRY_NAME ?= kind-registry LOCAL_REGISTRY_PORT ?= 5000 IMAGE_NAME ?= keyvault IMAGE_VERSION ?= v0.10.0 IMAGE_TAG := $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION) CGO_ENABLED_FLAG := 0 # build variables BUILD_VERSION_VAR := $(REPO_PATH)/pkg/version.BuildVersion BUILD_DATE_VAR := $(REPO_PATH)/pkg/version.BuildDate BUILD_DATE := $$(date +%Y-%m-%d-%H:%M) GIT_VAR := $(REPO_PATH)/pkg/version.GitCommit GIT_HASH := $$(git rev-parse --short HEAD) LDFLAGS ?= "-X $(BUILD_DATE_VAR)=$(BUILD_DATE) -X $(BUILD_VERSION_VAR)=$(IMAGE_VERSION) -X $(GIT_VAR)=$(GIT_HASH)" GO_FILES=$(shell go list ./... | grep -v /test/e2e) TOOLS_MOD_DIR := ./tools TOOLS_DIR := $(abspath ./.tools) # docker env var DOCKER_BUILDKIT = 1 export DOCKER_BUILDKIT # Testing var KIND_VERSION ?= 0.31.0 KUBERNETES_VERSION ?= v1.35.0 BATS_VERSION ?= 1.4.1 ## -------------------------------------- ## Linting ## -------------------------------------- $(TOOLS_DIR)/golangci-lint: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go cd $(TOOLS_MOD_DIR) && \ go build -o $(TOOLS_DIR)/golangci-lint github.com/golangci/golangci-lint/v2/cmd/golangci-lint .PHONY: lint lint: $(TOOLS_DIR)/golangci-lint $(TOOLS_DIR)/golangci-lint run --timeout=5m -v ## -------------------------------------- ## Images ## -------------------------------------- ALL_LINUX_ARCH ?= amd64 arm64 # Output type of docker buildx build OUTPUT_TYPE ?= type=registry BUILDX_BUILDER_NAME ?= img-builder QEMU_VERSION ?= 5.2.0-2 # The architecture of the image ARCH ?= amd64 .PHONY: build build: go build -a -ldflags $(LDFLAGS) -o _output/kubernetes-kms ./cmd/server/ .PHONY: docker-init-buildx docker-init-buildx: @if ! docker buildx ls | grep $(BUILDX_BUILDER_NAME); then \ docker run --rm --privileged mirror.gcr.io/multiarch/qemu-user-static:$(QEMU_VERSION) --reset -p yes; \ docker buildx create --name $(BUILDX_BUILDER_NAME) --use; \ docker buildx inspect $(BUILDX_BUILDER_NAME) --bootstrap; \ fi .PHONY: docker-build docker-build: docker buildx build \ --build-arg LDFLAGS=$(LDFLAGS) \ --no-cache \ --platform="linux/$(ARCH)" \ --output=$(OUTPUT_TYPE) \ -t $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$(ARCH) . \ --progress=plain; \ @if [ "$(ARCH)" = "amd64" ] && [ "$(OUTPUT_TYPE)" = "type=docker" ]; then \ docker tag $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$(ARCH) $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION); \ fi .PHONY: docker-build-all docker-build-all: @for arch in $(ALL_LINUX_ARCH); do \ $(MAKE) ARCH=$${arch} docker-build; \ done .PHONY: docker-push-manifest docker-push-manifest: docker manifest create --amend $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION) $(foreach arch,$(ALL_LINUX_ARCH),$(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$(arch)); \ for arch in $(ALL_LINUX_ARCH); do \ docker manifest annotate --os linux --arch $${arch} $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION) $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$${arch}; \ done; \ docker manifest push --purge $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION); \ ## -------------------------------------- ## Testing ## -------------------------------------- .PHONY: integration-test integration-test: go test -v -count=1 -failfast github.com/Azure/kubernetes-kms/tests/client .PHONY: unit-test unit-test: go test -race -v -count=1 -failfast `go list ./... | grep -v client` ## -------------------------------------- ## E2E Testing ## -------------------------------------- e2e-install-prerequisites: # Download and install kind curl -L https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-linux-amd64 --output kind && chmod +x kind && sudo mv kind /usr/local/bin/ # Download and install kubectl curl -LO https://dl.k8s.io/release/${KUBERNETES_VERSION}/bin/linux/amd64/kubectl && chmod +x ./kubectl && sudo mv kubectl /usr/local/bin/ # Download and install bats curl -sSLO https://github.com/bats-core/bats-core/archive/v${BATS_VERSION}.tar.gz && tar -zxvf v${BATS_VERSION}.tar.gz && sudo bash bats-core-${BATS_VERSION}/install.sh /usr/local e2e-setup-kind: setup-local-registry ./scripts/setup-kind-cluster.sh & ./scripts/connect-registry.sh & sleep 90s e2e-kmsv2-setup-kind: setup-local-registry ./scripts/setup-kmsv2-kind-cluster.sh & ./scripts/connect-registry.sh & sleep 90s .PHONY: setup-local-registry setup-local-registry: ./scripts/setup-local-registry.sh e2e-generate-manifests: @mkdir -p tests/e2e/generated_manifests envsubst < tests/e2e/azure.json > tests/e2e/generated_manifests/azure.json envsubst < tests/e2e/kms.yaml > tests/e2e/generated_manifests/kms.yaml e2e-delete-kind: # delete kind e2e cluster created for tests kind delete cluster --name kms e2e-test: # Run test suite with kind cluster bats -t tests/e2e/test.bats e2e-kmsv2-test: # Run test suite with kind cluster bats -t tests/e2e/testkmsv2.bats ================================================ FILE: README.md ================================================ # KMS Plugin for Key Vault [![Build Status](https://dev.azure.com/AzureContainerUpstream/Kubernetes%20KMS/_apis/build/status/Kubernetes%20KMS%20CI?branchName=master)](https://dev.azure.com/AzureContainerUpstream/Kubernetes%20KMS/_build/latest?definitionId=442&branchName=master) [![Go Report Card](https://goreportcard.com/badge/Azure/kubernetes-kms)](https://goreportcard.com/report/Azure/kubernetes-kms) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Azure/kubernetes-kms) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Azure/kubernetes-kms) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Azure/kubernetes-kms/badge)](https://api.securityscorecards.dev/projects/github.com/Azure/kubernetes-kms) Enables encryption at rest of your Kubernetes data in etcd using Azure Key Vault. From the Kubernetes documentation on [Encrypting Secret Data at Rest]: > _[KMS Plugin for Key Vault is]_ the recommended choice for using a third party tool for key management. Simplifies key rotation, with a new data encryption key (DEK) generated for each encryption, and key encryption key (KEK) rotation controlled by the user. ⚠️ **NOTE**: Currently, KMS plugin for Key Vault does not support key rotation. If you create a new key version in KMS, decryption will fail since it won't match the key used for encryption when the cluster was created. 💡 **NOTE**: To integrate your application secrets from a key management system outside of Kubernetes, use [Azure Key Vault Provider for Secrets Store CSI Driver]. ## Features - Use a key in Key Vault for etcd encryption - Use a key in Key Vault protected by a Hardware Security Module (HSM) - Bring your own keys - Store secrets, keys, and certs in etcd, but manage them as part of Kubernetes ## Getting Started ### Prerequisites 💡 Make sure you have a Kubernetes cluster version 1.10 or later, the minimum version that is supported by KMS Plugin for Key Vault. ### Azure Kubernetes Service (AKS) Azure Kubernetes Service ([AKS]) creates managed, supported Kubernetes clusters on Azure. To enable encryption at rest for Kubernetes resources in etcd, check out the KMS plugin for Key Vault on AKS feature in this [doc](https://docs.microsoft.com/en-us/azure/aks/use-kms-etcd-encryption). ### Setting up KMS Plugin manually Refer to [doc](docs/manual-install.md) for steps to setup the KMS Key Vault plugin on an existing cluster. ## Verifying that Data is Encrypted Now that Azure KMS provider is running in your cluster and the encryption configuration is setup, it will encrypt the data in etcd. Let's verify that is working: 1. Create a new secret: ```bash kubectl create secret generic secret1 -n default --from-literal=mykey=mydata ``` 2. Using `etcdctl`, read the secret from etcd: ```bash sudo ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/certs/ca.crt --cert=/etc/kubernetes/certs/etcdclient.crt --key=/etc/kubernetes/certs/etcdclient.key get /registry/secrets/default/secret1 ``` 3. Check that the stored secret is prefixed with `k8s:enc:kms:v1:azurekmsprovider` when KMSv1 is used for encryption, or with `k8s:enc:kms:v2:azurekmsprovider` when KMSv2 is used. This prefix indicates that the data has been encrypted by the Azure KMS provider. 4. Verify the secret is decrypted correctly when retrieved via the Kubernetes API: ```bash kubectl get secrets secret1 -o yaml ``` The output should match `mykey: bXlkYXRh`, which is the encoded data of `mydata`. ## Rotation Refer to [doc](docs/rotation.md) for steps to rotate the KMS Key on an existing cluster. ## Metrics Refer to [doc](docs/metrics.md) for details on the metrics exposed by the KMS Key Vault plugin. ## Contributing The KMS Plugin for Key Vault project welcomes contributions and suggestions. Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ## Roadmap You can view the public roadmap for the KMS plugin for Azure KeyVault on the GitHub Project [here](https://github.com/orgs/Azure/projects/440). Note that all target dates are aspirational and subject to change. ## Release Currently, this project releases monthly to patch security vulnerabilities, and bi-monthly for new features. We target the **first week** of the month for release. ## Code of conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Support KMS Plugin for Key Vault is an open source project that is [**not** covered by the Microsoft Azure support policy](https://support.microsoft.com/en-us/help/2941892/support-for-linux-and-open-source-technology-in-azure). [Please search open issues here](https://github.com/Azure/kubernetes-kms/issues), and if your issue isn't already represented please [open a new one](https://github.com/Azure/kubernetes-kms/issues/new/choose). The project maintainers will respond to the best of their abilities. [aks]: https://azure.microsoft.com/services/kubernetes-service/ [encrypting secret data at rest]: https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/#providers [azure key vault provider for secrets store csi driver]: https://github.com/Azure/secrets-store-csi-driver-provider-azure ================================================ FILE: SECURITY.md ================================================ ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). ================================================ FILE: cmd/server/main.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package main import ( "context" "flag" "fmt" "math" "net" "net/url" "os" "os/signal" "strconv" "syscall" "time" "github.com/Azure/kubernetes-kms/pkg/config" "github.com/Azure/kubernetes-kms/pkg/metrics" "github.com/Azure/kubernetes-kms/pkg/plugin" "github.com/Azure/kubernetes-kms/pkg/utils" "github.com/Azure/kubernetes-kms/pkg/version" "google.golang.org/grpc" "k8s.io/klog/v2" kmsv1 "k8s.io/kms/apis/v1beta1" kmsv2 "k8s.io/kms/apis/v2" "monis.app/mlog" ) var ( listenAddr = flag.String("listen-addr", "unix:///opt/azurekms.socket", "gRPC listen address") keyvaultName = flag.String("keyvault-name", "", "Azure Key Vault name") keyName = flag.String("key-name", "", "Azure Key Vault KMS key name") keyVersion = flag.String("key-version", "", "Azure Key Vault KMS key version") managedHSM = flag.Bool("managed-hsm", false, "Azure Key Vault Managed HSM. Refer to https://docs.microsoft.com/en-us/azure/key-vault/managed-hsm/overview for more details.") logFormatJSON = flag.Bool("log-format-json", false, "set log formatter to json") logLevel = flag.Uint("v", 0, "In order of increasing verbosity: 0=warning/error, 2=info, 4=debug, 6=trace, 10=all") // TODO remove this flag in future release. _ = flag.String("configFilePath", "/etc/kubernetes/azure.json", "[DEPRECATED] Path for Azure Cloud Provider config file") configFilePath = flag.String("config-file-path", "/etc/kubernetes/azure.json", "Path for Azure Cloud Provider config file") versionInfo = flag.Bool("version", false, "Prints the version information") healthzPort = flag.Uint("healthz-port", 8787, "port for health check") healthzPath = flag.String("healthz-path", "/healthz", "path for health check") healthzTimeout = flag.Duration("healthz-timeout", 20*time.Second, "RPC timeout for health check") metricsBackend = flag.String("metrics-backend", "prometheus", "Backend used for metrics") metricsAddress = flag.String("metrics-addr", "8095", "The address the metric endpoint binds to") proxyMode = flag.Bool("proxy-mode", false, "Proxy mode") proxyAddress = flag.String("proxy-address", "", "proxy address") proxyPort = flag.Int("proxy-port", 7788, "port for proxy") ) func main() { if err := setupKMSPlugin(); err != nil { mlog.Fatal(err) } } func setupKMSPlugin() error { defer mlog.Setup()() // set up log flushing and attempt to flush on exit flag.Parse() ctx := withShutdownSignal(context.Background()) logFormat := mlog.FormatText if *logFormatJSON { logFormat = mlog.FormatJSON } if *logLevel > math.MaxUint8 { return fmt.Errorf("invalid log level: %d", *logLevel) } if err := mlog.ValidateAndSetKlogLevelAndFormatGlobally(ctx, klog.Level(uint8(*logLevel)), logFormat); err != nil { return fmt.Errorf("invalid --log-level set: %w", err) } if *versionInfo { if err := version.PrintVersion(); err != nil { return fmt.Errorf("failed to print version: %w", err) } return nil } // initialize metrics exporter err := metrics.InitMetricsExporter(*metricsBackend, *metricsAddress) if err != nil { return fmt.Errorf("failed to initialize metrics exporter: %w", err) } mlog.Always("Starting KeyManagementServiceServer service", "version", version.BuildVersion, "buildDate", version.BuildDate) pluginConfig := &plugin.Config{ KeyVaultName: *keyvaultName, KeyName: *keyName, KeyVersion: *keyVersion, ManagedHSM: *managedHSM, ProxyMode: *proxyMode, ProxyAddress: *proxyAddress, ProxyPort: *proxyPort, ConfigFilePath: *configFilePath, } azureConfig, err := config.GetAzureConfig(pluginConfig.ConfigFilePath) if err != nil { return fmt.Errorf("failed to get azure config: %w", err) } kvClient, err := plugin.NewKeyVaultClient( azureConfig, pluginConfig.KeyVaultName, pluginConfig.KeyName, pluginConfig.KeyVersion, pluginConfig.ProxyMode, pluginConfig.ProxyAddress, pluginConfig.ProxyPort, pluginConfig.ManagedHSM, ) if err != nil { return fmt.Errorf("failed to create key vault client: %w", err) } // Initialize and run the GRPC server proto, addr, err := utils.ParseEndpoint(*listenAddr) if err != nil { return fmt.Errorf("failed to parse endpoint: %w", err) } if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove socket file %s: %w", addr, err) } listener, err := net.Listen(proto, addr) if err != nil { return fmt.Errorf("failed to listen addr: %s, proto: %s: %w", addr, proto, err) } opts := []grpc.ServerOption{ grpc.UnaryInterceptor(utils.UnaryServerInterceptor), } s := grpc.NewServer(opts...) // register kms v1 server kmsV1Server, err := plugin.NewKMSv1Server(kvClient) if err != nil { return fmt.Errorf("failed to create server: %w", err) } kmsv1.RegisterKeyManagementServiceServer(s, kmsV1Server) // register kms v2 server kmsV2Server, err := plugin.NewKMSv2Server(kvClient) if err != nil { return fmt.Errorf("failed to create kms V2 server: %w", err) } kmsv2.RegisterKeyManagementServiceServer(s, kmsV2Server) mlog.Always("Listening for connections", "addr", listener.Addr().String()) go func() { if err := s.Serve(listener); err != nil { mlog.Fatal(fmt.Errorf("failed to serve kms server: %w", err)) } }() // Health check for kms v1 and v2 healthz := &plugin.HealthZ{ KMSv1Server: kmsV1Server, KMSv2Server: kmsV2Server, HealthCheckURL: &url.URL{ Host: net.JoinHostPort("", strconv.FormatUint(uint64(*healthzPort), 10)), Path: *healthzPath, }, UnixSocketPath: listener.Addr().String(), RPCTimeout: *healthzTimeout, } go healthz.Serve() <-ctx.Done() // gracefully stop the grpc server mlog.Always("terminating the server") s.GracefulStop() return nil } // withShutdownSignal returns a copy of the parent context that will close if // the process receives termination signals. func withShutdownSignal(ctx context.Context) context.Context { signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, os.Interrupt) nctx, cancel := context.WithCancel(ctx) go func() { <-signalChan mlog.Always("received shutdown signal") cancel() }() return nctx } ================================================ FILE: developers.md ================================================ # Developers Guide This guide explains how to set up your environment for developing the Azure kubernetes kms service. ## Prerequisites - Go 1.9.0 or later - dep - kubectl 1.9 or later - An Azure account (needed for creating Azure key vault) - Git - make ### Structure of the Code The code for the kubernetes-kms project is organized as follows: - The built binary is located in root `./kubernetes-kms` - The `test/` directory contains `client.go`, which creates a connection against the grpc unix service at `/opt/azurekms.socket` then executes client-side API calls against the `KeyManagementService` service. This is used by the CI/CD pipeline. Go dependencies are managed with [dep](https://github.com/golang/dep) and stored in the `vendor/` directory. ### Git Conventions We use Git for our version control system. The `master` branch is the home of the current development candidate. Releases are tagged. We accept changes to the code via GitHub Pull Requests (PRs). One workflow for doing this is as follows: 1. Use `go get` to clone this repository: `go get github.com/Azure/kubernetes-kms` 2. Fork that repository into your GitHub account 3. Add your repository as a remote for `$GOPATH/github.com/Azure/kubernetes-kms` 4. Create a new working branch (`git checkout -b feat/my-feature`) and do your work on that branch. 5. When you are ready for us to review, push your branch to GitHub, and then open a new pull request with us. ### Build the Code We use `make` and `Makefile` to build the binary and the Docker image. To start the build process: 1. Run `make build` to build the binary `/kubernetes-kms` for your OS ### Run the Code Locally To test your code locally: 1. On a linux machine, you can run `sudo ./kubernetes-kms --configFilePath ` to create the gRPC unix domain socket running at `/opt/azurekms.socket`. This will start the gRPC server. 2. Create an Azure resource group, a Key Vault, and update the key vault's access policy with: ```bash az group create -n mykeyvaultrg -l eastus az keyvault create -n k8skv -g mykeyvaultrg az keyvault set-policy -n k8skv --key-permissions create decrypt encrypt get list --spn ``` If you do not have a service principal, please refer to this [doc](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest). 3. Populate a `azure.json` file locally. The gRPC server will look for this file in the path provided by `configFilePath`. By default, `configFilePath` is set to `etc/kubernetes/azure.json`. ```json { "tenantId": "", "subscriptionId": "", "aadClientId": "", "aadClientSecret": "", "resourceGroup": "mykeyvaultrg", "location": "eastus", "providerVaultName": "k8skv", "providerKeyName": "mykey" } ``` 4. Test with the gRPC client, run `sudo GOPATH=[YOUR GOPATH] GOCACHE=off go test tests/client/client_test.go`. 5. Test racing condition with the gRPC client, run `sudo GOPATH=[YOUR GOPATH] go test test/client/client_test.go & sudo GOPATH=[YOUR GOPATH] go test test/client/client_test.go &`. ### Build image 1. Run `make build-image` to build the binary `/kubernetes-kms` for linux and Docker image `mcr.microsoft.com/k8s/kms/keyvault:latest` ================================================ FILE: docs/manual-install.md ================================================ # 🛠 Manual Configurations # This guide demonstrates steps required to enable the KMS Plugin for Key Vault in an existing cluster. ### 1. Create a Keyvault If you're bringing your own keys, skip this step. ```bash KEYVAULT_NAME=k8skv RG=mykubernetesrg LOC=eastus # create resource group that'll contain the keyvault instance az group create -n $RG -l $LOC # create keyvault az keyvault create -n $KV_NAME -g $RG # create key that will be used for encryption az keyvault key create -n k8s --vault-name $KV_NAME --kty RSA --size 2048 ``` ### 2. Give the cluster identity permissions to access the keys in keyvault The KMS Plugin uses the cluster service principal or managed identity to access the keyvault instance. #### More on authentication methods [`/etc/kubernetes/azure.json`](https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/) is a well-known JSON file in each node that provides the details about which method KMS Plugin uses for access to Keyvault: | Authentication method | `/etc/kubernetes/azure.json` fields used | | -------------------------------- | ------------------------------------------------------------------------------------------- | | System-assigned managed identity | `useManagedIdentityExtension: true` and `userAssignedIdentityID:""` | | User-assigned managed identity | `useManagedIdentityExtension: true` and `userAssignedIdentityID:""` | | Service principal (default) | `aadClientID: ""` and `aadClientSecret: ""` | #### Obtaining the ID of the cluster managed identity/service principal After your cluster is provisioned, depending on your cluster identity configuration, run one of the following commands to retrieve the **ID** of your managed identity or service principal, which will be used for role assignment to access Keyvault: | Cluster configuration | Command | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- | | AKS cluster with service principal | `az aks show -g -n --query servicePrincipalProfile.clientId -otsv` | | AKS cluster with managed identity | `az aks show -g -n --query identityProfile.kubeletidentity.clientId -otsv` | Assign the following permissions: ```bash az keyvault set-policy -n $KEYVAULT_NAME --key-permissions decrypt encrypt --spn ``` ### 3. Deploy the KMS Plugin For all Kubernetes control plane nodes, add the static pod manifest to `/etc/kubernetes/manifests` ```yaml apiVersion: v1 kind: Pod metadata: name: azure-kms-provider namespace: kube-system labels: tier: control-plane component: azure-kms-provider spec: priorityClassName: system-node-critical hostNetwork: true containers: - name: azure-kms-provider image: mcr.microsoft.com/oss/v2/azure/kms/keyvault:v0.10.0 imagePullPolicy: IfNotPresent args: - --listen-addr=unix:///opt/azurekms.socket # [OPTIONAL] gRPC listen address. Default is unix:///opt/azurekms.socket - --keyvault-name=${KV_NAME} # [REQUIRED] Name of the keyvault. Must match criteria specified at https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name - --key-name=${KEY_NAME} # [REQUIRED] Name of the keyvault key used for encrypt/decrypt - --key-version=${KEY_VERSION} # [REQUIRED] Version of the key to use - --log-format-json=false # [OPTIONAL] Set log formatter to json. Default is false. - --healthz-port=8787 # [OPTIONAL] port for health check. Default is 8787 - --healthz-path=/healthz # [OPTIONAL] path for health check. Default is /healthz - --healthz-timeout=20s # [OPTIONAL] RPC timeout for health check. Default is 20s - --managed-hsm=false # [OPTIONAL] Use Azure Key Vault managed HSM. Default is false. - -v=1 securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsUser: 0 ports: - containerPort: 8787 # Must match the value defined in --healthz-port protocol: TCP livenessProbe: httpGet: path: /healthz # Must match the value defined in --healthz-path port: 8787 # Must match the value defined in --healthz-port failureThreshold: 2 periodSeconds: 10 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 4 memory: 2Gi volumeMounts: - name: etc-kubernetes mountPath: /etc/kubernetes - name: etc-ssl mountPath: /etc/ssl readOnly: true - name: sock mountPath: /opt volumes: - name: etc-kubernetes hostPath: path: /etc/kubernetes - name: etc-ssl hostPath: path: /etc/ssl - name: sock hostPath: path: /opt ``` View logs from the kms pod: ```bash kubectl logs -l component=azure-kms-provider -n kube-system I0219 17:35:33.608840 1 main.go:60] "Starting KeyManagementServiceServer service" version="v0.0.11" buildDate="2021-02-19-17:33" I0219 17:35:33.609090 1 azure_config.go:27] populating AzureConfig from /etc/kubernetes/azure.json I0219 17:35:33.609420 1 auth.go:66] "azure: using client_id+client_secret to retrieve access token" clientID="9a7a##### REDACTED #####bb26" clientSecret="23T.##### REDACTED #####vw-r" I0219 17:35:33.609568 1 keyvault.go:66] "using kms key for encrypt/decrypt" vaultName="k8skmskv" keyName="key1" keyVersion="5cdf48ea6bb9456ebf637e1130b7751a" I0219 17:35:33.609897 1 main.go:86] Listening for connections on address: /opt/azurekms.socket ... ``` ### 4. Create encryption configuration Create a new encryption configuration file `/etc/kubernetes/manifests/encryptionconfig.yaml` using the appropriate properties for the `kms` provider: ```yaml kind: EncryptionConfiguration apiVersion: apiserver.config.k8s.io/v1 resources: - resources: # List of kubernetes resources that will be encrypted in etcd using the KMS plugin - secrets providers: - kms: name: azurekmsprovider endpoint: unix:///opt/azurekms.socket # This endpoint must match the value defined in --listen-addr for the KMS plugin cachesize: 1000 - identity: {} ``` The encryption configuration file needs to be accessible by all the api servers. ### 5. Modify `/etc/kubernetes/kube-apiserver.yaml` Add the following flag: ```yaml --encryption-provider-config=/etc/kubernetes/encryptionconfig.yaml ``` Mount `/opt` to access the socket: ```yaml ... volumeMounts: - name: "sock" mountPath: "/opt" ... volumes: - name: "sock" hostPath: path: "/opt" ``` ### 6. Restart your API server ================================================ FILE: docs/metrics.md ================================================ # Metrics provided by KMS plugin for Key Vault This project uses [opentelemetry](https://opentelemetry.io/) for reporting metrics. Please refer to it's status [here](https://github.com/open-telemetry/opentelemetry-go#project-status). Prometheus is the only exporter that's currently supported. ## List of metrics provided by the kms plugin | Metric | Description | Tags | | ------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | kms_request | Distribution of how long it took for an operation | `status=success OR error`

`operation=encrypt OR decrypt OR grpc_encrypt OR grpc_decrypt`

`error_message` | ### Sample Metrics output ```shell # HELP kms_request Distribution of how long it took for an operation # TYPE kms_request histogram kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.1"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.13492828476735633"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.18205642030260802"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.24564560522315804"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.3314454017339986"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.4472135954999578"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.6034176336545162"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.8141810630738084"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.0985605433061172"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.4822688982138947"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.9999999999999991"} 18 kms_request_bucket{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="+Inf"} 18 kms_request_sum{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 1.010053082 kms_request_count{operation="decrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 18 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.1"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.13492828476735633"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.18205642030260802"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.24564560522315804"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.3314454017339986"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.4472135954999578"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.6034176336545162"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.8141810630738084"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.0985605433061172"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.4822688982138947"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.9999999999999991"} 19 kms_request_bucket{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="+Inf"} 19 kms_request_sum{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 1.021080768 kms_request_count{operation="encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 19 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.1"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.13492828476735633"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.18205642030260802"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.24564560522315804"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.3314454017339986"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.4472135954999578"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.6034176336545162"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.8141810630738084"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.0985605433061172"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.4822688982138947"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.9999999999999991"} 1 kms_request_bucket{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="+Inf"} 1 kms_request_sum{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 0.053279316 kms_request_count{operation="grpc_encrypt",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 1 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.1"} 0 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.13492828476735633"} 11 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.18205642030260802"} 13 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.24564560522315804"} 13 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.3314454017339986"} 13 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.4472135954999578"} 13 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.6034176336545162"} 14 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.8141810630738084"} 14 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.0985605433061172"} 14 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.4822688982138947"} 14 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.9999999999999991"} 14 kms_request_bucket{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="+Inf"} 14 kms_request_sum{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 2.1240865880000004 kms_request_count{operation="grpc_status",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 14 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.1"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.13492828476735633"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.18205642030260802"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.24564560522315804"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.3314454017339986"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.4472135954999578"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.6034176336545162"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="0.8141810630738084"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.0985605433061172"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.4822688982138947"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="1.9999999999999991"} 9 kms_request_bucket{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success",le="+Inf"} 9 kms_request_sum{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 0.0007254060000000001 kms_request_count{operation="grpc_version",otel_scope_name="keyvaultkms",otel_scope_version="",status="success"} 9 ``` ================================================ FILE: docs/rotation.md ================================================ # Rotating KMS key This guide demonstrates steps required to update your cluster to use a new KMS key for encryption. > NOTE: Ensure to read the Kubernetes documentation on [Rotating a decryption key](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/#rotating-a-decryption-key) before proceeding with the guide. ### 1. Generate a new key or rotate the existing key * If this is a new key in a different keyvault, then give the cluster identity permissions to access the keys in keyvault. Refer to [doc](./manual-install.md#2-give-the-cluster-identity-permissions-to-access-the-keys-in-keyvault) for details. * If this is a new version of the same key that's already being used, then proceed to the next step. ### 2. Deploy another instance of KMS plugin with new key To rotate the encrypt/decrypt key in the cluster, you'll need to run 2 kms plugin pods simultaneously listening on different unix sockets before making the transition. For all Kubernetes control plane nodes, add the static pod manifest to `/etc/kubernetes/manifests` ```yaml apiVersion: v1 kind: Pod metadata: name: azure-kms-provider-2 namespace: kube-system labels: tier: control-plane component: azure-kms-provider spec: priorityClassName: system-node-critical hostNetwork: true containers: - name: azure-kms-provider image: mcr.microsoft.com/oss/v2/azure/kms/keyvault:v0.10.0 imagePullPolicy: IfNotPresent args: - --listen-addr=unix:///opt/azurekms2.socket # unix:///opt/azurekms.socket is used by the primary kms plugin pod. So use a different listen address here for the new kms plugin pod. - --keyvault-name=${KV_NAME} # [REQUIRED] Name of the keyvault - --key-name=${KEY_NAME} # [REQUIRED] Name of the keyvault key used for encrypt/decrypt - --key-version=${KEY_VERSION} # [REQUIRED] Version of the key to use - --log-format-json=false # [OPTIONAL] Set log formatter to json. Default is false. - --healthz-port=8788 # The port used here should be different than the one used by the primary kms plugin pod. - --healthz-path=/healthz # [OPTIONAL] path for health check. Default is /healthz - --healthz-timeout=20s # [OPTIONAL] RPC timeout for health check. Default is 20s - --managed-hsm=false # [OPTIONAL] Use Azure Key Vault managed HSM. Default is false. - -v=5 securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsUser: 0 ports: - containerPort: 8788 # Must match the value defined in --healthz-port protocol: TCP livenessProbe: httpGet: path: /healthz # Must match the value defined in --healthz-path port: 8788 # Must match the value defined in --healthz-port failureThreshold: 2 periodSeconds: 10 resources: requests: cpu: 100m memory: 128Mi limits: cpu: "4" memory: 2Gi volumeMounts: - name: etc-kubernetes mountPath: /etc/kubernetes - name: etc-ssl mountPath: /etc/ssl readOnly: true - name: sock mountPath: /opt volumes: - name: etc-kubernetes hostPath: path: /etc/kubernetes - name: etc-ssl hostPath: path: /etc/ssl - name: sock hostPath: path: /opt nodeSelector: kubernetes.io/os: linux ``` View logs from the kms pod: ```bash kubectl logs -l component=azure-kms-provider -n kube-system I0219 17:35:33.608840 1 main.go:60] "Starting KeyManagementServiceServer service" version="v0.0.11" buildDate="2021-02-19-17:33" I0219 17:35:33.609090 1 azure_config.go:27] populating AzureConfig from /etc/kubernetes/azure.json I0219 17:35:33.609420 1 auth.go:66] "azure: using client_id+client_secret to retrieve access token" clientID="9a7a##### REDACTED #####bb26" clientSecret="23T.##### REDACTED #####vw-r" I0219 17:35:33.609568 1 keyvault.go:66] "using kms key for encrypt/decrypt" vaultName="k8skmskv" keyName="key1" keyVersion="5cdf48ea6bb9456ebf637e1130b7751a" I0219 17:35:33.609897 1 main.go:86] Listening for connections on address: /opt/azurekms2.socket ... ``` ### 3. Add the new provider to encryption configuration in `/etc/kubernetes/manifests/encryptionconfig.yaml` ```yaml kind: EncryptionConfiguration apiVersion: apiserver.config.k8s.io/v1 resources: - resources: # List of kubernetes resources that will be encrypted in etcd using the KMS plugin - secrets providers: - kms: name: azurekmsprovider endpoint: unix:///opt/azurekms.socket # This endpoint must match the value defined in --listen-addr for the KMS plugin using old key cachesize: 1000 - kms: name: azurekmsprovider2 endpoint: unix:///opt/azurekms2.socket # This endpoint must match the value defined in --listen-addr for the KMS plugin using new key cachesize: 1000 ``` ### 4. Restart all `kube-apiserver` * Proceed to the next step if using a single `kube-apiserver` * If using multiple control plane nodes, restart the `kube-apiserver` to ensure each server can still decrypt using the new key in the encryption config. * To validate the decryption still works, run `kubectl get secret -o yaml` with one of the existing secrets to confirm the data is returned and is valid. ### 5. Switch the order of provider in the encryption config ```yaml kind: EncryptionConfiguration apiVersion: apiserver.config.k8s.io/v1 resources: - resources: # List of kubernetes resources that will be encrypted in etcd using the KMS plugin - secrets providers: # kms provider with new key - kms: name: azurekmsprovider2 endpoint: unix:///opt/azurekms2.socket # This endpoint must match the value defined in --listen-addr for the KMS plugin using new key cachesize: 1000 # kms provider with old key - kms: name: azurekmsprovider endpoint: unix:///opt/azurekms.socket # This endpoint must match the value defined in --listen-addr for the KMS plugin using old key cachesize: 1000 ``` ### 6. Restart all `kube-apiserver` again Refer to [step 4](#4-restart-all-kube-apiserver) to again restart the `kube-apiserver` for the encryption config changes to take effect. ### 7. Decrypt and re-encrypt existing secrets with new key Since secrets are encrypted on write, performing an update on a secret will encrypt that content. Run `kubectl get secrets --all-namespaces -o json | kubectl replace -f -` to encrypt all existing secrets with the new key. > NOTE: For larger clusters, you may wish to subdivide the secrets by namespace or script an update. #### How does this work? The first provider in the encryption configuration is used for new encrypt calls. For decrypt, all existing kms providers in encryption configuration will be tried until one of the decrypt call succeeds. ### 8. Remove the old provider from encryption configuration Now that all the secrets have been re-encrypted with the new key, we can safely remove the old kms provider from the encryption configuration. ```yaml kind: EncryptionConfiguration apiVersion: apiserver.config.k8s.io/v1 resources: - resources: # List of kubernetes resources that will be encrypted in etcd using the KMS plugin - secrets providers: # kms provider with new key - kms: name: azurekmsprovider2 endpoint: unix:///opt/azurekms2.socket # This endpoint must match the value defined in --listen-addr for the KMS plugin using new key cachesize: 1000 ``` ================================================ FILE: docs/testing.md ================================================ # End-to-end testing for KMS Plugin for Keyvault ## Prerequisites To run tests locally, following components are required: 1. [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 1. [bats](https://bats-core.readthedocs.io/en/latest/installation.html) 1. [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) To install the prerequisites, run the following command: ```bash make e2e-install-prerequisites ``` The E2E test suite extracts runtime configurations through environment variables. Below is a list of environment variables to set before running the E2E test suite. | Variable | Description | | ------------------- | --------------------------------------------------------------------------------------------------- | | AZURE_CLIENT_ID | The client ID of your service principal that has `encrypt, decrypt` access to the keyvault key. | | AZURE_CLIENT_SECRET | The client secret of your service principal that has `encrypt, decrypt` access to the keyvault key. | | AZURE_TENANT_ID | The Azure tenant ID. | | KEYVAULT_NAME | The Azure Keyvault name. | | KEY_NAME | The name of Keyvault key that will be used by the kms plugin. | | KEY_VERSION | The version of Keyvault key that will be used by the kms plugin. | ## Running the tests The e2e tests are run against a [kind](https://kind.sigs.k8s.io/) cluster that's created as part of the test script. The script also creates a local docker registry that's used for test images. 1. Setup cluster, registry and build image: ```bash make e2e-setup-kind ``` - This creates the local registry - Builds a kms plugin image with the latest changes and pushes to local registry - Creates a kind cluster with connectivity to local registry and kms plugin enabled with custom image 1. Run the end-to-end tests: ```bash make e2e-test ``` 1. To delete the kind cluster after running tests: ```bash make e2e-delete-kind ``` ================================================ FILE: go.mod ================================================ module github.com/Azure/kubernetes-kms go 1.26.2 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/go-autorest/autorest v0.11.28 github.com/Azure/go-autorest/autorest/adal v0.9.23 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/prometheus v0.60.0 go.opentelemetry.io/otel/metric v1.43.0 golang.org/x/crypto v0.48.0 google.golang.org/grpc v1.79.3 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.27.1 k8s.io/klog/v2 v2.100.1 k8s.io/kms v0.35.1 monis.app/mlog v0.0.4 ) require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/otlptranslator v0.0.2 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.11.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.27.1 // indirect k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect ) ================================================ FILE: go.sum ================================================ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 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/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.1.27/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.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/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= 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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20210423082822-04245dca01da/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= k8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kms v0.35.1 h1:kjv2r9g1mY7uL+l1RhyAZvWVZIA/4qIfBHXyjFGLRhU= k8s.io/kms v0.35.1/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ= k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= monis.app/mlog v0.0.4 h1:YEzh5sguG4ApywaRWnBU+mGP6SA4WxOqiJ36u+KtoeE= monis.app/mlog v0.0.4/go.mod h1:LtOpnndFuRGqnLBwzBvpA1DaoKuud2/moLzYXIiNl1s= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= ================================================ FILE: pkg/auth/auth.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package auth import ( "crypto/rsa" "crypto/x509" "fmt" "net/http" "os" "regexp" "github.com/Azure/kubernetes-kms/pkg/config" "github.com/Azure/kubernetes-kms/pkg/consts" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "golang.org/x/crypto/pkcs12" "monis.app/mlog" ) // GetKeyvaultToken() returns token for Keyvault endpoint. func GetKeyvaultToken(config *config.AzureConfig, env *azure.Environment, resource string, proxyMode bool) (authorizer autorest.Authorizer, err error) { servicePrincipalToken, err := GetServicePrincipalToken(config, env.ActiveDirectoryEndpoint, resource, proxyMode) if err != nil { return nil, err } authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) return authorizer, nil } // GetServicePrincipalToken creates a new service principal token based on the configuration. func GetServicePrincipalToken(config *config.AzureConfig, aadEndpoint, resource string, proxyMode bool) (adal.OAuthTokenProvider, error) { oauthConfig, err := adal.NewOAuthConfig(aadEndpoint, config.TenantID) if err != nil { return nil, fmt.Errorf("failed to create OAuth config, error: %w", err) } if config.UseManagedIdentityExtension { mlog.Info("using managed identity extension to retrieve access token") msiEndpoint, err := adal.GetMSIVMEndpoint() if err != nil { return nil, fmt.Errorf("failed to get managed service identity endpoint, error: %w", err) } // using user-assigned managed identity to access keyvault if len(config.UserAssignedIdentityID) > 0 { mlog.Info("using User-assigned managed identity to retrieve access token", "clientID", redactClientCredentials(config.UserAssignedIdentityID)) return adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource, config.UserAssignedIdentityID) } mlog.Info("using system-assigned managed identity to retrieve access token") // using system-assigned managed identity to access keyvault return adal.NewServicePrincipalTokenFromMSI( msiEndpoint, resource) } if len(config.ClientSecret) > 0 && len(config.ClientID) > 0 { mlog.Info("azure: using client_id+client_secret to retrieve access token", "clientID", redactClientCredentials(config.ClientID), "clientSecret", redactClientCredentials(config.ClientSecret)) spt, err := adal.NewServicePrincipalToken( *oauthConfig, config.ClientID, config.ClientSecret, resource) if err != nil { return nil, err } if proxyMode { return addTargetTypeHeader(spt), nil } return spt, nil } if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 { mlog.Info("using jwt client_assertion (client_cert+client_private_key) to retrieve access token") certData, err := os.ReadFile(config.AADClientCertPath) if err != nil { return nil, fmt.Errorf("failed to read client certificate from file %s, error: %w", config.AADClientCertPath, err) } certificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword) if err != nil { return nil, fmt.Errorf("failed to decode the client certificate, error: %w", err) } spt, err := adal.NewServicePrincipalTokenFromCertificate( *oauthConfig, config.ClientID, certificate, privateKey, resource) if err != nil { return nil, err } if proxyMode { return addTargetTypeHeader(spt), nil } return spt, nil } return nil, fmt.Errorf("no credentials provided for accessing keyvault") } // ParseAzureEnvironment returns azure environment by name. func ParseAzureEnvironment(cloudName string) (*azure.Environment, error) { var env azure.Environment var err error if cloudName == "" { env = azure.PublicCloud } else { env, err = azure.EnvironmentFromName(cloudName) } return &env, err } // decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and // the private RSA key. func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { privateKey, certificate, err := pkcs12.Decode(pkcs, password) if err != nil { return nil, nil, fmt.Errorf("decoding the PKCS#12 client certificate: %w", err) } rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) if !isRsaKey { return nil, nil, fmt.Errorf("PKCS#12 certificate must contain a RSA private key") } return certificate, rsaPrivateKey, nil } // redactClientCredentials applies regex to a sensitive string and return the redacted value. func redactClientCredentials(sensitiveString string) string { r := regexp.MustCompile(`^(\S{4})(\S|\s)*(\S{4})$`) return r.ReplaceAllString(sensitiveString, "$1##### REDACTED #####$3") } // addTargetTypeHeader adds the target header if proxy mode is enabled. func addTargetTypeHeader(spt *adal.ServicePrincipalToken) *adal.ServicePrincipalToken { spt.SetSender(autorest.CreateSender( (func() autorest.SendDecorator { return func(s autorest.Sender) autorest.Sender { return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { r.Header.Set(consts.RequestHeaderTargetType, consts.TargetTypeAzureActiveDirectory) return s.Do(r) }) } })())) return spt } ================================================ FILE: pkg/auth/auth_test.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package auth import ( "reflect" "strings" "testing" "github.com/Azure/kubernetes-kms/pkg/config" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" ) func TestParseAzureEnvironment(t *testing.T) { envNamesArray := []string{"AZURECHINACLOUD", "AZUREGERMANCLOUD", "AZUREPUBLICCLOUD", "AZUREUSGOVERNMENTCLOUD", ""} for _, envName := range envNamesArray { azureEnv, err := ParseAzureEnvironment(envName) if err != nil { t.Fatalf("expected no error, got %v", err) } if strings.EqualFold(envName, "") && !strings.EqualFold(azureEnv.Name, "AZUREPUBLICCLOUD") { t.Fatalf("string doesn't match, expected AZUREPUBLICCLOUD, got %s", azureEnv.Name) } else if !strings.EqualFold(envName, "") && !strings.EqualFold(envName, azureEnv.Name) { t.Fatalf("string doesn't match, expected %s, got %s", envName, azureEnv.Name) } } wrongEnvName := "AZUREWRONGCLOUD" _, err := ParseAzureEnvironment(wrongEnvName) if err == nil { t.Fatalf("expected error for wrong azure environment name") } } func TestRedactClientCredentials(t *testing.T) { tests := []struct { name string clientID string expected string }{ { name: "should redact client id", clientID: "aabc0000-a83v-9h4m-000j-2c0a66b0c1f9", expected: "aabc##### REDACTED #####c1f9", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := redactClientCredentials(test.clientID) if actual != test.expected { t.Fatalf("expected: %s, got %s", test.expected, actual) } }) } } func TestGetServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) { tests := []struct { name string config *config.AzureConfig proxyMode bool // The proxy mode doesn't matter if user-assigned managed identity is used to get service principal token }{ { name: "using user-assigned managed identity to access keyvault", config: &config.AzureConfig{ UseManagedIdentityExtension: true, UserAssignedIdentityID: "clientID", TenantID: "TenantID", ClientID: "AADClientID", ClientSecret: "AADClientSecret", }, proxyMode: false, }, // The Azure service principal is ignored when // UseManagedIdentityExtension is set to true { name: "using user-assigned managed identity over service principal if set to true", config: &config.AzureConfig{ UseManagedIdentityExtension: true, UserAssignedIdentityID: "clientID", }, proxyMode: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { token, err := GetServicePrincipalToken(test.config, "https://login.microsoftonline.com/", "https://vault.azure.net", test.proxyMode) if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } msiEndpoint, err := adal.GetMSIVMEndpoint() if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } spt, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, "https://vault.azure.net", "clientID") if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } if !reflect.DeepEqual(token, spt) { t.Fatalf("expected: %v, got: %v", spt, token) } }) } } func TestGetServicePrincipalTokenFromMSI(t *testing.T) { tests := []struct { name string config *config.AzureConfig proxyMode bool // The proxy mode doesn't matter if MSI is used to get service principal token }{ { name: "using system-assigned managed identity to access keyvault", config: &config.AzureConfig{ UseManagedIdentityExtension: true, }, proxyMode: false, }, // The Azure service principal is ignored when // UseManagedIdentityExtension is set to true { name: "using system-assigned managed identity over service principal if set to true", config: &config.AzureConfig{ UseManagedIdentityExtension: true, TenantID: "TenantID", ClientID: "AADClientID", ClientSecret: "AADClientSecret", }, proxyMode: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { token, err := GetServicePrincipalToken(test.config, "https://login.microsoftonline.com/", "https://vault.azure.net", test.proxyMode) if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } msiEndpoint, err := adal.GetMSIVMEndpoint() if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } spt, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, "https://vault.azure.net") if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } if !reflect.DeepEqual(token, spt) { t.Fatalf("expected: %v, got: %v", spt, token) } }) } } func TestGetServicePrincipalToken(t *testing.T) { tests := []struct { name string config *config.AzureConfig }{ { name: "using service-principal credentials to access keyvault", config: &config.AzureConfig{ TenantID: "TenantID", ClientID: "AADClientID", ClientSecret: "AADClientSecret", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { token, err := GetServicePrincipalToken(test.config, "https://login.microsoftonline.com/", "https://vault.azure.net", false) if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } env := &azure.PublicCloud oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, test.config.TenantID) if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } spt, err := adal.NewServicePrincipalToken(*oauthConfig, test.config.ClientID, test.config.ClientSecret, "https://vault.azure.net") if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } if !reflect.DeepEqual(token, spt) { t.Fatalf("expected: %+v, got: %+v", spt, token) } }) } } ================================================ FILE: pkg/config/azure_config.go ================================================ package config import ( "fmt" "os" "gopkg.in/yaml.v3" "monis.app/mlog" ) // AzureConfig is representing /etc/kubernetes/azure.json. type AzureConfig struct { Cloud string `json:"cloud" yaml:"cloud"` TenantID string `json:"tenantId" yaml:"tenantId"` ClientID string `json:"aadClientId" yaml:"aadClientId"` ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` UseManagedIdentityExtension bool `json:"useManagedIdentityExtension,omitempty" yaml:"useManagedIdentityExtension,omitempty"` UserAssignedIdentityID string `json:"userAssignedIdentityID,omitempty" yaml:"userAssignedIdentityID,omitempty"` AADClientCertPath string `json:"aadClientCertPath" yaml:"aadClientCertPath"` AADClientCertPassword string `json:"aadClientCertPassword" yaml:"aadClientCertPassword"` } // GetAzureConfig returns configs in the azure.json cloud provider file. func GetAzureConfig(configFile string) (config *AzureConfig, err error) { cfg := AzureConfig{} mlog.Trace("populating AzureConfig from config file", "configFile", configFile) bytes, err := os.ReadFile(configFile) if err != nil { return nil, fmt.Errorf("failed to load config file %s, error: %w", configFile, err) } if err = yaml.Unmarshal(bytes, &cfg); err != nil { return nil, fmt.Errorf("failed to unmarshal azure.json, error: %w", err) } return &cfg, nil } ================================================ FILE: pkg/consts/consts.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package consts const ( // In proxy mode, the header is added into the requests from kms-plugin. // The proxy will check the header and forward the request to different destinations. // e.g. When the value of the header "x-azure-proxy-target" is "KeyVault", the request // is forwared to Azure Key Vault by the proxy. RequestHeaderTargetType = "x-azure-proxy-target" TargetTypeAzureActiveDirectory = "AzureActiveDirectory" TargetTypeKeyVault = "KeyVault" ) ================================================ FILE: pkg/metrics/exporter.go ================================================ package metrics import ( "fmt" "strings" "monis.app/mlog" ) const ( prometheusExporter = "prometheus" ) // InitMetricsExporter initializes new exporter. func InitMetricsExporter(metricsBackend, metricsAddress string) error { exporter := strings.ToLower(metricsBackend) mlog.Always("metrics backend", "exporter", exporter) switch exporter { // Prometheus is the only exporter supported for now case prometheusExporter: return initPrometheusExporter(metricsAddress) default: return fmt.Errorf("unsupported metrics backend %v", metricsBackend) } } ================================================ FILE: pkg/metrics/exporter_test.go ================================================ package metrics import ( "net/http" "testing" ) func TestInitMetricsExporter(t *testing.T) { testCases := []struct { name string metricsBackend string metricsAddress string expectedError bool }{ { name: "With_Prometheus_Backend", metricsBackend: "prometheus", metricsAddress: "8095", expectedError: false, }, { name: "With_Non_Prometheus_Backend", metricsBackend: "nonprometheus", expectedError: true, }, { name: "With_Uppercase_Backend_Name", metricsBackend: "Prometheus", metricsAddress: "8096", expectedError: false, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { err := InitMetricsExporter(testCase.metricsBackend, testCase.metricsAddress) if testCase.expectedError && err == nil || !testCase.expectedError && err != nil { t.Fatalf("expected error: %v, found: %v", testCase.expectedError, err) } // Reset handler to test /metrics repeatedly. http.DefaultServeMux = new(http.ServeMux) }) } } ================================================ FILE: pkg/metrics/prometheus_exporter.go ================================================ package metrics import ( "fmt" "net/http" "time" promclient "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/prometheus" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "monis.app/mlog" ) const ( metricsEndpoint = "metrics" ) func initPrometheusExporter(metricsAddress string) error { registry := promclient.NewRegistry() exporter, err := prometheus.New( prometheus.WithRegisterer(registry)) if err != nil { return err } mp := sdkmetric.NewMeterProvider( sdkmetric.WithReader(exporter), sdkmetric.WithView(sdkmetric.NewView( sdkmetric.Instrument{Kind: sdkmetric.InstrumentKindHistogram}, sdkmetric.Stream{ Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: promclient.ExponentialBucketsRange(0.1, 2, 11), }, }, )), ) otel.SetMeterProvider(mp) http.Handle(fmt.Sprintf("/%s", metricsEndpoint), promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) go func() { server := &http.Server{ Addr: fmt.Sprintf(":%s", metricsAddress), ReadHeaderTimeout: 5 * time.Second, } if err := server.ListenAndServe(); err != nil { mlog.Fatal(err, "failed to register prometheus endpoint", "metricsAddress", metricsAddress) } }() mlog.Always("Prometheus metrics server running", "address", metricsAddress) return nil } ================================================ FILE: pkg/metrics/stats_reporter.go ================================================ package metrics import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) const ( instrumentationName = "keyvaultkms" errorMessageKey = "error_message" statusTypeKey = "status" operationTypeKey = "operation" kmsRequestMetricName = "kms_request" // ErrorStatusTypeValue sets status tag to "error". ErrorStatusTypeValue = "error" // SuccessStatusTypeValue sets status tag to "success". SuccessStatusTypeValue = "success" // EncryptOperationTypeValue sets operation tag to "encrypt". EncryptOperationTypeValue = "encrypt" // DecryptOperationTypeValue sets operation tag to "decrypt". DecryptOperationTypeValue = "decrypt" // GrpcOperationTypeValue sets operation tag to "grpc". GrpcOperationTypeValue = "grpc" ) type reporter struct { histogram metric.Float64Histogram } // StatsReporter reports metrics. type StatsReporter interface { ReportRequest(ctx context.Context, operationType, status string, duration float64, errors ...string) } // NewStatsReporter instantiates otel reporter. func NewStatsReporter() (StatsReporter, error) { meter := otel.GetMeterProvider().Meter(instrumentationName) metricCounter, err := meter.Float64Histogram( kmsRequestMetricName, metric.WithDescription("Distribution of how long it took for an operation"), ) if err != nil { return nil, err } return &reporter{ histogram: metricCounter, }, nil } func (r *reporter) ReportRequest(ctx context.Context, operationType, status string, duration float64, errors ...string) { labels := []attribute.KeyValue{ attribute.String(operationTypeKey, operationType), attribute.String(statusTypeKey, status), } // Add errors if (status == ErrorStatusTypeValue) && len(errors) > 0 { for _, err := range errors { labels = append(labels, attribute.String(errorMessageKey, err)) } } r.histogram.Record(ctx, duration, metric.WithAttributes(labels...)) } ================================================ FILE: pkg/plugin/healthz.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "context" "fmt" "net/http" "net/url" "time" "github.com/Azure/kubernetes-kms/pkg/version" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "k8s.io/apimachinery/pkg/util/uuid" kmsv1 "k8s.io/kms/apis/v1beta1" kmsv2 "k8s.io/kms/apis/v2" "monis.app/mlog" ) const ( healthCheckPlainText = "healthcheck" ) // HealthZ is the health check server for the KMS plugin. type HealthZ struct { KMSv1Server *KeyManagementServiceServer KMSv2Server *KeyManagementServiceV2Server HealthCheckURL *url.URL UnixSocketPath string RPCTimeout time.Duration } // Serve creates the http handler for serving health requests. func (h *HealthZ) Serve() { serveMux := http.NewServeMux() serveMux.HandleFunc(h.HealthCheckURL.EscapedPath(), h.ServeHTTP) server := &http.Server{ Addr: h.HealthCheckURL.Host, ReadHeaderTimeout: 5 * time.Second, Handler: serveMux, } if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { mlog.Fatal(err, "failed to start health check server", "url", h.HealthCheckURL.String()) } } func (h *HealthZ) ServeHTTP(w http.ResponseWriter, _ *http.Request) { mlog.Trace("Started health check") ctx, cancel := context.WithTimeout(context.Background(), h.RPCTimeout) defer cancel() conn, err := h.dialUnixSocket() if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) return } defer conn.Close() // create the kms client for v1 kmsClient := kmsv1.NewKeyManagementServiceClient(conn) // create the kms client for v2 kmsV2Client := kmsv2.NewKeyManagementServiceClient(conn) // check version response against KMS-Plugin's gRPC endpoint. err = h.checkRPC(ctx, kmsClient, kmsV2Client) if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) return } // Both encryption and decryption calls are made for each version, // resulting in a total of 4 calls to the keyvault. // Additionally, a health check is performed every 10 seconds. // v1 checks // check the configured keyvault, key, key version and permissions are still // valid to encrypt and decrypt with test data. enc, err := h.KMSv1Server.Encrypt(ctx, &kmsv1.EncryptRequest{Plain: []byte(healthCheckPlainText)}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } dec, err := h.KMSv1Server.Decrypt(ctx, &kmsv1.DecryptRequest{Cipher: enc.Cipher}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if string(dec.Plain) != healthCheckPlainText { http.Error(w, "plain text mismatch after decryption", http.StatusInternalServerError) return } // v2 checks. // appending a string to UUID allows us to differentiate the UUIDs generated by us from those generated by the API server. uid := "local-healthz-check-" + string(uuid.NewUUID()) v2EncryptResponse, err := h.KMSv2Server.Encrypt( ctx, &kmsv2.EncryptRequest{ Plaintext: []byte(healthCheckPlainText), Uid: uid, }, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } v2DecryptResponse, err := h.KMSv2Server.Decrypt(ctx, &kmsv2.DecryptRequest{ Ciphertext: v2EncryptResponse.Ciphertext, KeyId: v2EncryptResponse.KeyId, Uid: uid, // passing the same uid to track roundtrip encrypt/decrypt calls Annotations: v2EncryptResponse.Annotations, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if string(v2DecryptResponse.Plaintext) != healthCheckPlainText { http.Error(w, "plain text mismatch after decryption with KMSv2", http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) if _, err = w.Write([]byte("ok")); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } mlog.Trace("Completed health check") } // checkRPC initiates a grpc request to validate the socket is responding // sends a KMS VersionRequest and checks if the VersionResponse is valid. func (h *HealthZ) checkRPC( ctx context.Context, kmsV1Client kmsv1.KeyManagementServiceClient, kmsV2Client kmsv2.KeyManagementServiceClient, ) error { v, err := kmsV1Client.Version(ctx, &kmsv1.VersionRequest{}) if err != nil { return err } if v.Version != version.KMSv1APIVersion || v.RuntimeName != version.Runtime || v.RuntimeVersion != version.BuildVersion { return fmt.Errorf("failed to get correct version response") } v2Status, err := kmsV2Client.Status(ctx, &kmsv2.StatusRequest{}) if err != nil { return err } if v2Status.Version != version.KMSv2APIVersion { return fmt.Errorf( "failed to get correct version response for v2 expected: %s, got: %s", version.KMSv2APIVersion, v2Status.Version, ) } return nil } func (h *HealthZ) dialUnixSocket() (*grpc.ClientConn, error) { return grpc.NewClient("unix://"+h.UnixSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials())) } ================================================ FILE: pkg/plugin/healthz_test.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "context" "fmt" "io" "net" "net/http" "net/http/httptest" "net/url" "os" "testing" "time" "github.com/Azure/kubernetes-kms/pkg/metrics" mockkeyvault "github.com/Azure/kubernetes-kms/pkg/plugin/mock_keyvault" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "google.golang.org/grpc" kmsv1 "k8s.io/kms/apis/v1beta1" kmsv2 "k8s.io/kms/apis/v2" "monis.app/mlog" ) func TestServe(t *testing.T) { tests := []struct { desc string setEncryptResponse string setDecryptResponse string setEncryptError error setDecryptError error expectedHTTPStatusCode int }{ { desc: "failed to encrypt in health check", setEncryptResponse: "", setEncryptError: fmt.Errorf("failed to encrypt"), expectedHTTPStatusCode: http.StatusServiceUnavailable, }, { desc: "failed to decrypt in health check", setEncryptResponse: "", setEncryptError: nil, setDecryptResponse: "", setDecryptError: fmt.Errorf("failed to decrypt"), expectedHTTPStatusCode: http.StatusServiceUnavailable, }, { desc: "encrypt-decrypt mismatch", setEncryptResponse: "bar", setEncryptError: nil, setDecryptResponse: "foo", setDecryptError: nil, expectedHTTPStatusCode: http.StatusServiceUnavailable, }, { desc: "successful health check", setEncryptResponse: "bar", setDecryptResponse: "healthcheck", expectedHTTPStatusCode: http.StatusOK, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { socketPath := fmt.Sprintf("%s/kms.sock", getTempTestDir(t)) defer os.Remove(socketPath) fakeKMSServer, fakeKMSV2Server, mockKVClient, err := setupFakeKMSServer(socketPath) if err != nil { t.Fatalf("failed to create fake kms server, err: %+v", err) } mockKVClient.SetEncryptResponse([]byte(test.setEncryptResponse), test.setEncryptError) mockKVClient.SetDecryptResponse([]byte(test.setDecryptResponse), test.setDecryptError) healthz := &HealthZ{ KMSv1Server: fakeKMSServer, KMSv2Server: fakeKMSV2Server, UnixSocketPath: socketPath, RPCTimeout: 20 * time.Second, HealthCheckURL: &url.URL{ Scheme: "http", Host: net.JoinHostPort("localhost", "8080"), Path: "/healthz", }, } server := httptest.NewServer(healthz) defer server.Close() respCode, body := doHealthCheck(t, server.URL) if respCode != test.expectedHTTPStatusCode { t.Fatalf("expected status code: %v, got: %v", test.expectedHTTPStatusCode, respCode) } if test.expectedHTTPStatusCode == http.StatusOK && string(body) != "ok" { t.Fatalf("expected response body to be 'ok', got: %s", string(body)) } }) } } func TestCheckRPC(t *testing.T) { socketPath := fmt.Sprintf("%s/kms.sock", getTempTestDir(t)) defer os.Remove(socketPath) fakeKMSV1Server, fakeKMSV2Server, mockKVClient, err := setupFakeKMSServer(socketPath) if err != nil { t.Fatalf("failed to create fake kms server, err: %+v", err) } healthz := &HealthZ{ KMSv1Server: fakeKMSV1Server, KMSv2Server: fakeKMSV2Server, UnixSocketPath: socketPath, } mockKVClient.SetEncryptResponse([]byte(healthCheckPlainText), nil) mockKVClient.SetDecryptResponse([]byte(healthCheckPlainText), nil) conn, err := healthz.dialUnixSocket() if err != nil { t.Fatalf("failed to create connection, err: %+v", err) } err = healthz.checkRPC( context.TODO(), kmsv1.NewKeyManagementServiceClient(conn), kmsv2.NewKeyManagementServiceClient(conn), ) if err != nil { t.Fatalf("expected err to be nil, got: %+v", err) } } func getTempTestDir(t *testing.T) string { tmpDir, err := os.MkdirTemp("", "ut") if err != nil { t.Fatalf("expected err to be nil, got: %+v", err) } return tmpDir } func setupFakeKMSServer(socketPath string) ( *KeyManagementServiceServer, *KeyManagementServiceV2Server, *mockkeyvault.KeyVaultClient, error, ) { listener, err := net.Listen("unix", socketPath) if err != nil { return nil, nil, nil, err } statsReporter, err := metrics.NewStatsReporter() if err != nil { return nil, nil, nil, err } kvClient := &mockkeyvault.KeyVaultClient{ KeyID: "mock-key-id", Algorithm: keyvault.RSA15, } fakeKMSV1Server := &KeyManagementServiceServer{ kvClient: kvClient, reporter: statsReporter, } fakeKMSV2Server := &KeyManagementServiceV2Server{ kvClient: kvClient, reporter: statsReporter, } s := grpc.NewServer() kmsv1.RegisterKeyManagementServiceServer(s, fakeKMSV1Server) kmsv2.RegisterKeyManagementServiceServer(s, fakeKMSV2Server) go func() { if err := s.Serve(listener); err != nil { mlog.Fatal(err, "failed to serve fake kms server") } }() return fakeKMSV1Server, fakeKMSV2Server, kvClient, nil } func doHealthCheck(t *testing.T, url string) (int, []byte) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { t.Fatalf("failed to create new http request, err: %+v", err) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("failed to invoke http request, err: %+v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("failed to read response body, err: %+v", err) } return resp.StatusCode, body } ================================================ FILE: pkg/plugin/keyvault.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "context" "crypto/sha256" "encoding/base64" "fmt" "net/url" "path" "regexp" "strings" "github.com/Azure/kubernetes-kms/pkg/auth" "github.com/Azure/kubernetes-kms/pkg/config" "github.com/Azure/kubernetes-kms/pkg/consts" "github.com/Azure/kubernetes-kms/pkg/utils" "github.com/Azure/kubernetes-kms/pkg/version" kv "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "k8s.io/kms/pkg/service" "monis.app/mlog" ) // encryptionResponseVersion is validated prior to decryption. // This is helpful in case we want to change anything about the data we send in the future. var encryptionResponseVersion = "1" const ( dateAnnotationKey = "date.azure.akv.io" requestIDAnnotationKey = "x-ms-request-id.azure.akv.io" keyvaultRegionAnnotationKey = "x-ms-keyvault-region.azure.akv.io" versionAnnotationKey = "version.azure.akv.io" algorithmAnnotationKey = "algorithm.azure.akv.io" dateAnnotationValue = "Date" requestIDAnnotationValue = "X-Ms-Request-Id" keyvaultRegionAnnotationValue = "X-Ms-Keyvault-Region" ) // Client interface for interacting with Keyvault. type Client interface { Encrypt( ctx context.Context, plain []byte, encryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm, ) (*service.EncryptResponse, error) Decrypt( ctx context.Context, cipher []byte, encryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm, apiVersion string, annotations map[string][]byte, decryptRequestKeyID string, ) ([]byte, error) GetUserAgent() string GetVaultURL() string } // KeyVaultClient is a client for interacting with Keyvault. type KeyVaultClient struct { baseClient kv.BaseClient config *config.AzureConfig vaultName string keyName string keyVersion string vaultURL string keyIDHash string azureEnvironment *azure.Environment } // NewKeyVaultClient returns a new key vault client to use for kms operations. func NewKeyVaultClient( config *config.AzureConfig, vaultName, keyName, keyVersion string, proxyMode bool, proxyAddress string, proxyPort int, managedHSM bool, ) (Client, error) { // Sanitize vaultName, keyName, keyVersion. (https://github.com/Azure/kubernetes-kms/issues/85) vaultName = utils.SanitizeString(vaultName) keyName = utils.SanitizeString(keyName) keyVersion = utils.SanitizeString(keyVersion) // this should be the case for bring your own key, clusters bootstrapped with // aks-engine or aks and standalone kms plugin deployments if len(vaultName) == 0 || len(keyName) == 0 || len(keyVersion) == 0 { return nil, fmt.Errorf("key vault name, key name and key version are required") } kvClient := kv.New() err := kvClient.AddToUserAgent(version.GetUserAgent()) if err != nil { return nil, fmt.Errorf("failed to add user agent to keyvault client, error: %w", err) } env, err := auth.ParseAzureEnvironment(config.Cloud) if err != nil { return nil, fmt.Errorf("failed to parse cloud environment: %s, error: %w", config.Cloud, err) } if proxyMode { env.ActiveDirectoryEndpoint = fmt.Sprintf("http://%s:%d/", proxyAddress, proxyPort) } vaultResourceURL := getVaultResourceIdentifier(managedHSM, env) if vaultResourceURL == azure.NotAvailable { return nil, fmt.Errorf("keyvault resource identifier not available for cloud: %s", env.Name) } token, err := auth.GetKeyvaultToken(config, env, vaultResourceURL, proxyMode) if err != nil { return nil, fmt.Errorf("failed to get key vault token, error: %w", err) } kvClient.Authorizer = token vaultURL, err := getVaultURL(vaultName, managedHSM, env) if err != nil { return nil, fmt.Errorf("failed to get vault url, error: %w", err) } keyIDHash, err := getKeyIDHash(*vaultURL, keyName, keyVersion) if err != nil { return nil, fmt.Errorf("failed to get key id hash, error: %w", err) } if proxyMode { kvClient.RequestInspector = autorest.WithHeader(consts.RequestHeaderTargetType, consts.TargetTypeKeyVault) vaultURL = getProxiedVaultURL(vaultURL, proxyAddress, proxyPort) } mlog.Always("using kms key for encrypt/decrypt", "vaultURL", *vaultURL, "keyName", keyName, "keyVersion", keyVersion) client := &KeyVaultClient{ baseClient: kvClient, config: config, vaultName: vaultName, keyName: keyName, keyVersion: keyVersion, vaultURL: *vaultURL, azureEnvironment: env, keyIDHash: keyIDHash, } return client, nil } // Encrypt encrypts the given plain text using the keyvault key. func (kvc *KeyVaultClient) Encrypt( ctx context.Context, plain []byte, encryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm, ) (*service.EncryptResponse, error) { value := base64.RawURLEncoding.EncodeToString(plain) params := kv.KeyOperationsParameters{ Algorithm: encryptionAlgorithm, Value: &value, } result, err := kvc.baseClient.Encrypt(ctx, kvc.vaultURL, kvc.keyName, kvc.keyVersion, params) if err != nil { return nil, fmt.Errorf("failed to encrypt, error: %w", err) } if kvc.keyIDHash != fmt.Sprintf("%x", sha256.Sum256([]byte(*result.Kid))) { return nil, fmt.Errorf( "key id initialized does not match with the key id from encryption result, expected: %s, got: %s", kvc.keyIDHash, *result.Kid, ) } annotations := map[string][]byte{ dateAnnotationKey: []byte(result.Header.Get(dateAnnotationValue)), requestIDAnnotationKey: []byte(result.Header.Get(requestIDAnnotationValue)), keyvaultRegionAnnotationKey: []byte(result.Header.Get(keyvaultRegionAnnotationValue)), versionAnnotationKey: []byte(encryptionResponseVersion), algorithmAnnotationKey: []byte(encryptionAlgorithm), } return &service.EncryptResponse{ Ciphertext: []byte(*result.Result), KeyID: kvc.keyIDHash, Annotations: annotations, }, nil } // Decrypt decrypts the given cipher text using the keyvault key. func (kvc *KeyVaultClient) Decrypt( ctx context.Context, cipher []byte, encryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm, apiVersion string, annotations map[string][]byte, decryptRequestKeyID string, ) ([]byte, error) { if apiVersion == version.KMSv2APIVersion { err := kvc.validateAnnotations(annotations, decryptRequestKeyID, encryptionAlgorithm) if err != nil { return nil, err } } value := string(cipher) params := kv.KeyOperationsParameters{ Algorithm: encryptionAlgorithm, Value: &value, } result, err := kvc.baseClient.Decrypt(ctx, kvc.vaultURL, kvc.keyName, kvc.keyVersion, params) if err != nil { return nil, fmt.Errorf("failed to decrypt, error: %w", err) } bytes, err := base64.RawURLEncoding.DecodeString(*result.Result) if err != nil { return nil, fmt.Errorf("failed to base64 decode result, error: %w", err) } return bytes, nil } func (kvc *KeyVaultClient) GetUserAgent() string { return kvc.baseClient.UserAgent } func (kvc *KeyVaultClient) GetVaultURL() string { return kvc.vaultURL } // ValidateAnnotations validates following annotations before decryption: // - Algorithm. // - Version. // It also validates keyID that the API server checks. func (kvc *KeyVaultClient) validateAnnotations( annotations map[string][]byte, keyID string, encryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm, ) error { if len(annotations) == 0 { return fmt.Errorf("invalid annotations, annotations cannot be empty") } if keyID != kvc.keyIDHash { return fmt.Errorf( "key id %s does not match expected key id %s used for encryption", keyID, kvc.keyIDHash, ) } algorithm := string(annotations[algorithmAnnotationKey]) if algorithm != string(encryptionAlgorithm) { return fmt.Errorf( "algorithm %s does not match expected algorithm %s used for encryption", algorithm, encryptionAlgorithm, ) } version := string(annotations[versionAnnotationKey]) if version != encryptionResponseVersion { return fmt.Errorf( "version %s does not match expected version %s used for encryption", version, encryptionResponseVersion, ) } return nil } func getVaultURL(vaultName string, managedHSM bool, env *azure.Environment) (vaultURL *string, err error) { // Key Vault name must be a 3-24 character string if len(vaultName) < 3 || len(vaultName) > 24 { return nil, fmt.Errorf("invalid vault name: %q, must be between 3 and 24 chars", vaultName) } // See docs for validation spec: https://docs.microsoft.com/en-us/azure/key-vault/about-keys-secrets-and-certificates#objects-identifiers-and-versioning isValid := regexp.MustCompile(`^[-A-Za-z0-9]+$`).MatchString if !isValid(vaultName) { return nil, fmt.Errorf("invalid vault name: %q, must match [-a-zA-Z0-9]{3,24}", vaultName) } vaultDNSSuffixValue := getVaultDNSSuffix(managedHSM, env) if vaultDNSSuffixValue == azure.NotAvailable { return nil, fmt.Errorf("vault dns suffix not available for cloud: %s", env.Name) } vaultURI := fmt.Sprintf("https://%s.%s/", vaultName, vaultDNSSuffixValue) return &vaultURI, nil } func getProxiedVaultURL(vaultURL *string, proxyAddress string, proxyPort int) *string { proxiedVaultURL := fmt.Sprintf("http://%s:%d/%s", proxyAddress, proxyPort, strings.TrimPrefix(*vaultURL, "https://")) return &proxiedVaultURL } func getVaultDNSSuffix(managedHSM bool, env *azure.Environment) string { if managedHSM { return env.ManagedHSMDNSSuffix } return env.KeyVaultDNSSuffix } func getVaultResourceIdentifier(managedHSM bool, env *azure.Environment) string { if managedHSM { return env.ResourceIdentifiers.ManagedHSM } return env.ResourceIdentifiers.KeyVault } func getKeyIDHash(vaultURL, keyName, keyVersion string) (string, error) { if vaultURL == "" || keyName == "" || keyVersion == "" { return "", fmt.Errorf("vault url, key name and key version cannot be empty") } baseURL, err := url.Parse(vaultURL) if err != nil { return "", fmt.Errorf("failed to parse vault url, error: %w", err) } urlPath := path.Join("keys", keyName, keyVersion) keyID := baseURL.ResolveReference( &url.URL{ Path: urlPath, }, ).String() return fmt.Sprintf("%x", sha256.Sum256([]byte(keyID))), nil } ================================================ FILE: pkg/plugin/keyvault_test.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "fmt" "strings" "testing" "github.com/Azure/kubernetes-kms/pkg/auth" "github.com/Azure/kubernetes-kms/pkg/config" ) var ( testEnvs = []string{"", "AZUREPUBLICCLOUD", "AZURECHINACLOUD", "AZUREGERMANCLOUD", "AZUREUSGOVERNMENTCLOUD"} vaultDNSSuffix = []string{"vault.azure.net", "vault.azure.net", "vault.azure.cn", "vault.microsoftazure.de", "vault.usgovcloudapi.net"} ) func TestNewKeyVaultClientError(t *testing.T) { tests := []struct { desc string config *config.AzureConfig vaultName string keyName string keyVersion string proxyMode bool proxyAddress string proxyPort int managedHSM bool }{ { desc: "vault name not provided", config: &config.AzureConfig{}, proxyMode: false, }, { desc: "key name not provided", config: &config.AzureConfig{}, vaultName: "testkv", proxyMode: false, }, { desc: "key version not provided", config: &config.AzureConfig{}, vaultName: "testkv", keyName: "k8s", proxyMode: false, }, { desc: "no credentials in config", config: &config.AzureConfig{}, vaultName: "testkv", keyName: "key1", keyVersion: "262067a9e8ba401aa8a746c5f1a7e147", }, { desc: "managed hsm not available in the azure environment", config: &config.AzureConfig{ClientID: "clientid", ClientSecret: "clientsecret", Cloud: "AzureGermanCloud"}, vaultName: "testkv", keyName: "key1", keyVersion: "262067a9e8ba401aa8a746c5f1a7e147", managedHSM: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { if _, err := NewKeyVaultClient(test.config, test.vaultName, test.keyName, test.keyVersion, test.proxyMode, test.proxyAddress, test.proxyPort, test.managedHSM); err == nil { t.Fatalf("newKeyVaultClient() expected error, got nil") } }) } } func TestNewKeyVaultClient(t *testing.T) { tests := []struct { desc string config *config.AzureConfig vaultName string keyName string keyVersion string proxyMode bool proxyAddress string proxyPort int managedHSM bool expectedVaultURL string }{ { desc: "no error", config: &config.AzureConfig{ClientID: "clientid", ClientSecret: "clientsecret"}, vaultName: "testkv", keyName: "key1", keyVersion: "262067a9e8ba401aa8a746c5f1a7e147", proxyMode: false, expectedVaultURL: "https://testkv.vault.azure.net/", }, { desc: "no error with double quotes", config: &config.AzureConfig{ClientID: "clientid", ClientSecret: "clientsecret"}, vaultName: "\"testkv\"", keyName: "\"key1\"", keyVersion: "\"262067a9e8ba401aa8a746c5f1a7e147\"", proxyMode: false, expectedVaultURL: "https://testkv.vault.azure.net/", }, { desc: "no error with proxy mode", config: &config.AzureConfig{ClientID: "clientid", ClientSecret: "clientsecret"}, vaultName: "testkv", keyName: "key1", keyVersion: "262067a9e8ba401aa8a746c5f1a7e147", proxyMode: true, proxyAddress: "localhost", proxyPort: 7788, expectedVaultURL: "http://localhost:7788/testkv.vault.azure.net/", }, { desc: "no error with managed hsm", config: &config.AzureConfig{ClientID: "clientid", ClientSecret: "clientsecret"}, vaultName: "testkv", keyName: "key1", keyVersion: "262067a9e8ba401aa8a746c5f1a7e147", managedHSM: true, proxyMode: false, expectedVaultURL: "https://testkv.managedhsm.azure.net/", }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { kvClient, err := NewKeyVaultClient(test.config, test.vaultName, test.keyName, test.keyVersion, test.proxyMode, test.proxyAddress, test.proxyPort, test.managedHSM) if err != nil { t.Fatalf("newKeyVaultClient() failed with error: %v", err) } if kvClient == nil { t.Fatalf("newKeyVaultClient() expected kv client to not be nil") } if !strings.Contains(kvClient.GetUserAgent(), "k8s-kms-keyvault") { t.Fatalf("newKeyVaultClient() expected k8s-kms-keyvault user agent") } if kvClient.GetVaultURL() != test.expectedVaultURL { t.Fatalf("expected vault URL: %v, got vault URL: %v", test.expectedVaultURL, kvClient.GetVaultURL()) } }) } } func TestGetVaultURLError(t *testing.T) { tests := []struct { desc string vaultName string managedHSM bool }{ { desc: "vault name > 24", vaultName: "longkeyvaultnamewhichisnotvalid", }, { desc: "vault name < 3", vaultName: "kv", }, { desc: "vault name contains non alpha-numeric chars", vaultName: "kv_test", }, } for _, test := range tests { for idx := range testEnvs { t.Run(fmt.Sprintf("%s/%s", test.desc, testEnvs[idx]), func(t *testing.T) { azEnv, err := auth.ParseAzureEnvironment(testEnvs[idx]) if err != nil { t.Fatalf("failed to parse azure environment from name, err: %+v", err) } if _, err = getVaultURL(test.vaultName, test.managedHSM, azEnv); err == nil { t.Fatalf("getVaultURL() expected error, got nil") } }) } } } func TestGetVaultURL(t *testing.T) { vaultName := "testkv" for idx := range testEnvs { t.Run(testEnvs[idx], func(t *testing.T) { azEnv, err := auth.ParseAzureEnvironment(testEnvs[idx]) if err != nil { t.Fatalf("failed to parse azure environment from name, err: %+v", err) } vaultURL, err := getVaultURL(vaultName, false, azEnv) if err != nil { t.Fatalf("expected no error of getting vault URL, got error: %v", err) } expectedURL := "https://" + vaultName + "." + vaultDNSSuffix[idx] + "/" if expectedURL != *vaultURL { t.Fatalf("expected vault url: %s, got: %s", expectedURL, *vaultURL) } }) } } func TestGetKeyIDHash(t *testing.T) { testCases := []struct { name string vaultURL string keyName string keyVersion string expectedHash string expectedError bool expectedErrorString string }{ { name: "valid hash", vaultURL: "https://example.vault.azure.net/", keyName: "mykey", keyVersion: "ABCD", expectedHash: "567d783db3043fe298fe0d9eeedb0029a3815cdd4fe4b059d018c91e6acffe3b", expectedError: false, }, { name: "invalid vault URL", vaultURL: ":invalid-url:", keyName: "mykey", keyVersion: "ABCD", expectedHash: "", expectedError: true, expectedErrorString: "failed to parse vault url, error: parse \":invalid-url:\": missing protocol scheme", }, { name: "empty vault name", vaultURL: "", keyName: "mykey", keyVersion: "ABCD", expectedHash: "", expectedError: true, expectedErrorString: "vault url, key name and key version cannot be empty", }, { name: "empty key name", vaultURL: "https://example.vault.azure.net/", keyName: "", keyVersion: "ABCD", expectedHash: "", expectedError: true, expectedErrorString: "vault url, key name and key version cannot be empty", }, { name: "empty key vesion", vaultURL: "https://example.vault.azure.net/", keyName: "mykey", keyVersion: "", expectedHash: "", expectedError: true, expectedErrorString: "vault url, key name and key version cannot be empty", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { hash, err := getKeyIDHash(tc.vaultURL, tc.keyName, tc.keyVersion) if tc.expectedError { if (err != nil) && (err.Error() != tc.expectedErrorString) { t.Errorf("Expected error: %v, but got: %v", tc.expectedErrorString, err.Error()) } else if err == nil { t.Errorf("Expected error: %v, but didn't get any", tc.expectedErrorString) } } if hash != tc.expectedHash { t.Errorf("Expected hash: %s, but got: %s", tc.expectedHash, hash) } }) } } ================================================ FILE: pkg/plugin/kms_v2_server.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "context" "fmt" "time" "github.com/Azure/kubernetes-kms/pkg/metrics" "github.com/Azure/kubernetes-kms/pkg/version" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" kmsv2 "k8s.io/kms/apis/v2" "monis.app/mlog" ) // KeyManagementServiceV2Server is a gRPC server. type KeyManagementServiceV2Server struct { kmsv2.UnimplementedKeyManagementServiceServer kvClient Client reporter metrics.StatsReporter encryptionAlgorithm keyvault.JSONWebKeyEncryptionAlgorithm } // NewKMSv2Server creates an instance of the KMS Service Server with v2 apis. func NewKMSv2Server(kvClient Client) (*KeyManagementServiceV2Server, error) { statsReporter, err := metrics.NewStatsReporter() if err != nil { return nil, fmt.Errorf("failed to create stats reporter: %w", err) } return &KeyManagementServiceV2Server{ kvClient: kvClient, reporter: statsReporter, encryptionAlgorithm: keyvault.RSAOAEP256, }, nil } // Status returns the health status of the KMS plugin. func (s *KeyManagementServiceV2Server) Status(ctx context.Context, _ *kmsv2.StatusRequest) (*kmsv2.StatusResponse, error) { // We perform a simple encrypt/decrypt operation to verify the plugin's connectivity with Key Vault. // The KMS invokes the Status API every minute, resulting in 120 calls per hour to the Key Vault. // This volume of calls is well within the permissible limit of Key Vault. encryptResponse, err := s.kvClient.Encrypt(ctx, []byte(healthCheckPlainText), s.encryptionAlgorithm) if err != nil { mlog.Error("failed to encrypt healthcheck call", err) return nil, err } decryptedText, err := s.kvClient.Decrypt( ctx, encryptResponse.Ciphertext, s.encryptionAlgorithm, version.KMSv2APIVersion, encryptResponse.Annotations, encryptResponse.KeyID, ) if err != nil { mlog.Error("failed to decrypt healthcheck call", err) return nil, err } if string(decryptedText) != healthCheckPlainText { err = fmt.Errorf("decrypted text does not match") mlog.Error("healthcheck failed", err) return nil, err } return &kmsv2.StatusResponse{ Version: version.KMSv2APIVersion, Healthz: "ok", KeyId: encryptResponse.KeyID, }, nil } // Encrypt message. func (s *KeyManagementServiceV2Server) Encrypt(ctx context.Context, request *kmsv2.EncryptRequest) (*kmsv2.EncryptResponse, error) { mlog.Debug("encrypt request received", "uid", request.Uid) start := time.Now() var err error defer func() { errors := "" status := metrics.SuccessStatusTypeValue if err != nil { status = metrics.ErrorStatusTypeValue errors = err.Error() } s.reporter.ReportRequest(ctx, metrics.EncryptOperationTypeValue, status, time.Since(start).Seconds(), errors) }() mlog.Info("encrypt request started", "uid", request.Uid) encryptResponse, err := s.kvClient.Encrypt(ctx, request.Plaintext, s.encryptionAlgorithm) if err != nil { mlog.Error("failed to encrypt", err, "uid", request.Uid) return &kmsv2.EncryptResponse{}, err } mlog.Info("encrypt request complete", "uid", request.Uid) return &kmsv2.EncryptResponse{ Ciphertext: encryptResponse.Ciphertext, KeyId: encryptResponse.KeyID, Annotations: encryptResponse.Annotations, }, nil } // Decrypt message. func (s *KeyManagementServiceV2Server) Decrypt(ctx context.Context, request *kmsv2.DecryptRequest) (*kmsv2.DecryptResponse, error) { mlog.Debug("decrypt request received", "uid", request.Uid) start := time.Now() var err error defer func() { errors := "" status := metrics.SuccessStatusTypeValue if err != nil { status = metrics.ErrorStatusTypeValue errors = err.Error() } s.reporter.ReportRequest(ctx, metrics.DecryptOperationTypeValue, status, time.Since(start).Seconds(), errors) }() mlog.Info("decrypt request started", "uid", request.Uid) plainText, err := s.kvClient.Decrypt( ctx, request.Ciphertext, s.encryptionAlgorithm, version.KMSv2APIVersion, request.Annotations, request.KeyId, ) if err != nil { mlog.Error("failed to decrypt", err, "uid", request.Uid) return &kmsv2.DecryptResponse{}, err } mlog.Info("decrypt request complete", "uid", request.Uid) return &kmsv2.DecryptResponse{ Plaintext: plainText, }, nil } ================================================ FILE: pkg/plugin/kms_v2_server_test.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "bytes" "context" "errors" "fmt" "testing" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "github.com/Azure/kubernetes-kms/pkg/metrics" mockkeyvault "github.com/Azure/kubernetes-kms/pkg/plugin/mock_keyvault" "github.com/Azure/kubernetes-kms/pkg/version" kmsv2 "k8s.io/kms/apis/v2" ) func TestV2Encrypt(t *testing.T) { tests := []struct { desc string input []byte output []byte err error }{ { desc: "failed to encrypt", input: []byte("foo"), output: []byte{}, err: fmt.Errorf("failed to encrypt"), }, { desc: "successfully encrypted", input: []byte("foo"), output: []byte("bar"), err: nil, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { kvClient := &mockkeyvault.KeyVaultClient{ KeyID: "mock-key-id", Algorithm: keyvault.RSA15, } kvClient.SetEncryptResponse(test.output, test.err) statsReporter, err := metrics.NewStatsReporter() if err != nil { t.Fatalf("failed to create stats reporter: %v", err) } kmsV2Server := KeyManagementServiceV2Server{ kvClient: kvClient, reporter: statsReporter, } out, err := kmsV2Server.Encrypt(context.TODO(), &kmsv2.EncryptRequest{ Plaintext: test.input, }) if !errors.Is(err, test.err) { t.Fatalf("expected err: %v, got: %v", test.err, err) } if !bytes.Equal(out.GetCiphertext(), test.output) { t.Fatalf("expected out: %v, got: %v", test.output, out) } if err == nil && (out.KeyId != kvClient.KeyID) { t.Fatalf("expected key id: %v, got: %v", kvClient.KeyID, out.KeyId) } if err == nil && (len(out.Annotations) == 0) { t.Fatalf("invalid annotations, annotations cannot be empty") } }) } } func TestV2Decrypt(t *testing.T) { tests := []struct { desc string input []byte output []byte err error annotations map[string][]byte }{ { desc: "empty annotations failed to decrypt", input: []byte("bar"), output: []byte{}, err: fmt.Errorf("invalid annotations, annotations cannot be empty"), }, { desc: "invalid keyid failed to decrypt", input: []byte("bar"), output: []byte{}, err: fmt.Errorf("key id \"invalid-key-id\" does not match expected key id \"mock-key-id\" used for encryption"), annotations: map[string][]byte{ algorithmAnnotationKey: []byte(keyvault.RSA15), versionAnnotationKey: []byte("1"), }, }, { desc: "invalid algorithm failed to decrypt", input: []byte("bar"), output: []byte{}, err: fmt.Errorf("algorithm \"insecure-algorithm\" does not match expected algorithm \"RSAOAEP256\" used for encryption"), annotations: map[string][]byte{ algorithmAnnotationKey: []byte("insecure-algorithm"), versionAnnotationKey: []byte("1"), }, }, { desc: "invalid version failed to decrypt", input: []byte("bar"), output: []byte{}, err: fmt.Errorf("version \"10\" does not match expected version \"1\" used for encryption"), annotations: map[string][]byte{ algorithmAnnotationKey: []byte(keyvault.RSA15), versionAnnotationKey: []byte("10"), }, }, { desc: "failed to decrypt", input: []byte("foo"), output: []byte{}, err: fmt.Errorf("failed to decrypt"), annotations: map[string][]byte{ algorithmAnnotationKey: []byte(keyvault.RSA15), versionAnnotationKey: []byte("1"), }, }, { desc: "successfully decrypted", input: []byte("bar"), output: []byte("foo"), err: nil, annotations: map[string][]byte{ algorithmAnnotationKey: []byte(keyvault.RSA15), versionAnnotationKey: []byte("1"), }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { kvClient := &mockkeyvault.KeyVaultClient{ KeyID: "mock-key-id", Algorithm: keyvault.RSAOAEP256, } kvClient.SetDecryptResponse(test.output, test.err) statsReporter, err := metrics.NewStatsReporter() if err != nil { t.Fatalf("failed to create stats reporter: %v", err) } kmsV2Server := KeyManagementServiceV2Server{ kvClient: kvClient, reporter: statsReporter, } out, err := kmsV2Server.Decrypt(context.TODO(), &kmsv2.DecryptRequest{ Ciphertext: test.input, Annotations: test.annotations, KeyId: "mock-key-id", }) if err != nil && (err.Error() != test.err.Error()) { t.Fatalf("expected err: %v, got: %v", test.err, err) } if !bytes.Equal(out.GetPlaintext(), test.output) { t.Fatalf("expected out: %v, got: %v", test.output, out) } }) } } func TestStatus(t *testing.T) { kmsServer := KeyManagementServiceV2Server{} mockKeyVaultClient := &mockkeyvault.KeyVaultClient{ KeyID: "mock-key-id", } mockKeyVaultClient.SetEncryptResponse([]byte(healthCheckPlainText), nil) mockKeyVaultClient.SetDecryptResponse([]byte(healthCheckPlainText), nil) kmsServer.kvClient = mockKeyVaultClient v, err := kmsServer.Status(context.TODO(), &kmsv2.StatusRequest{}) if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } if v.Version != version.KMSv2APIVersion { t.Fatalf("expected version: %s, got: %s", version.KMSv2APIVersion, v.Version) } if v.Healthz != "ok" { t.Fatalf("expected healthz response to be: %s, got: %s", "ok", v.Healthz) } if v.KeyId != "mock-key-id" { t.Fatalf("expected key id: %s, got: %s", "mock-key-id", v.KeyId) } } ================================================ FILE: pkg/plugin/mock_keyvault/keyvault_mock.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package mockkeyvault import ( "context" "fmt" "sync" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "k8s.io/kms/pkg/service" ) type KeyVaultClient struct { mutex sync.Mutex encryptOut []byte encryptErr error decryptOut []byte decryptErr error KeyID string Algorithm keyvault.JSONWebKeyEncryptionAlgorithm } func (kvc *KeyVaultClient) Encrypt(_ context.Context, _ []byte, _ keyvault.JSONWebKeyEncryptionAlgorithm) (*service.EncryptResponse, error) { kvc.mutex.Lock() defer kvc.mutex.Unlock() return &service.EncryptResponse{ Ciphertext: kvc.encryptOut, KeyID: kvc.KeyID, Annotations: map[string][]byte{ "key-id.azure.akv.io": []byte(kvc.KeyID), "algorithm.azure.akv.io": []byte(kvc.Algorithm), "version.azure.akv.io": []byte("1"), }, }, kvc.encryptErr } func (kvc *KeyVaultClient) Decrypt(_ context.Context, _ []byte, _ keyvault.JSONWebKeyEncryptionAlgorithm, _ string, _ map[string][]byte, _ string) ([]byte, error) { kvc.mutex.Lock() defer kvc.mutex.Unlock() return kvc.decryptOut, kvc.decryptErr } func (kvc *KeyVaultClient) SetEncryptResponse(encryptOut []byte, err error) { kvc.mutex.Lock() defer kvc.mutex.Unlock() kvc.encryptOut = encryptOut kvc.encryptErr = err } func (kvc *KeyVaultClient) SetDecryptResponse(decryptOut []byte, err error) { kvc.mutex.Lock() defer kvc.mutex.Unlock() kvc.decryptOut = decryptOut kvc.decryptErr = err } func (kvc *KeyVaultClient) ValidateAnnotations(annotations map[string][]byte, keyID string) error { if len(annotations) == 0 { return fmt.Errorf("invalid annotations, annotations cannot be empty") } // validate key id if keyID != kvc.KeyID { return fmt.Errorf( "key id %q does not match expected key id %q used for encryption", string(annotations["key-id.azure.akv.io"]), kvc.KeyID, ) } // validate algorithm if string(annotations["algorithm.azure.akv.io"]) != string(kvc.Algorithm) { return fmt.Errorf("algorithm %q does not match expected algorithm %q used for encryption", string(annotations["algorithm.azure.akv.io"]), kvc.Algorithm) } // validate version if string(annotations["version.azure.akv.io"]) != "1" { return fmt.Errorf( "version %q does not match expected version %q used for encryption", string(annotations["version.azure.akv.io"]), "1", ) } return nil } func (kvc *KeyVaultClient) GetUserAgent() string { return "k8s-kms-keyvault" } func (kvc *KeyVaultClient) GetVaultURL() string { return "https://test.vault.azure.net" } ================================================ FILE: pkg/plugin/server.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "context" "fmt" "time" "github.com/Azure/kubernetes-kms/pkg/metrics" "github.com/Azure/kubernetes-kms/pkg/version" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" kmsv1 "k8s.io/kms/apis/v1beta1" "monis.app/mlog" ) // KeyManagementServiceServer is a gRPC server. type KeyManagementServiceServer struct { kmsv1.UnimplementedKeyManagementServiceServer kvClient Client reporter metrics.StatsReporter encryptionAlgorithm keyvault.JSONWebKeyEncryptionAlgorithm } // Config is the configuration for the KMS plugin. type Config struct { ConfigFilePath string KeyVaultName string KeyName string KeyVersion string ManagedHSM bool ProxyMode bool ProxyAddress string ProxyPort int } // NewKMSv1Server creates an instance of the KMS Service Server. func NewKMSv1Server(kvClient Client) (*KeyManagementServiceServer, error) { statsReporter, err := metrics.NewStatsReporter() if err != nil { return nil, fmt.Errorf("failed to create stats reporter: %w", err) } return &KeyManagementServiceServer{ kvClient: kvClient, reporter: statsReporter, encryptionAlgorithm: keyvault.RSA15, }, nil } // Version of kms. func (s *KeyManagementServiceServer) Version(_ context.Context, _ *kmsv1.VersionRequest) (*kmsv1.VersionResponse, error) { return &kmsv1.VersionResponse{ Version: version.KMSv1APIVersion, RuntimeName: version.Runtime, RuntimeVersion: version.BuildVersion, }, nil } // Encrypt message. func (s *KeyManagementServiceServer) Encrypt(ctx context.Context, request *kmsv1.EncryptRequest) (*kmsv1.EncryptResponse, error) { start := time.Now() var err error defer func() { errors := "" status := metrics.SuccessStatusTypeValue if err != nil { status = metrics.ErrorStatusTypeValue errors = err.Error() } s.reporter.ReportRequest(ctx, metrics.EncryptOperationTypeValue, status, time.Since(start).Seconds(), errors) }() mlog.Info("encrypt request started") encryptResponse, err := s.kvClient.Encrypt(ctx, request.Plain, s.encryptionAlgorithm) if err != nil { mlog.Error("failed to encrypt", err) return &kmsv1.EncryptResponse{}, err } mlog.Info("encrypt request complete") return &kmsv1.EncryptResponse{ Cipher: encryptResponse.Ciphertext, }, nil } // Decrypt message. func (s *KeyManagementServiceServer) Decrypt(ctx context.Context, request *kmsv1.DecryptRequest) (*kmsv1.DecryptResponse, error) { start := time.Now() var err error defer func() { errors := "" status := metrics.SuccessStatusTypeValue if err != nil { status = metrics.ErrorStatusTypeValue errors = err.Error() } s.reporter.ReportRequest(ctx, metrics.DecryptOperationTypeValue, status, time.Since(start).Seconds(), errors) }() mlog.Info("decrypt request started") plain, err := s.kvClient.Decrypt( ctx, request.Cipher, s.encryptionAlgorithm, request.Version, nil, "", ) if err != nil { mlog.Error("failed to decrypt", err) return &kmsv1.DecryptResponse{}, err } mlog.Info("decrypt request complete") return &kmsv1.DecryptResponse{Plain: plain}, nil } ================================================ FILE: pkg/plugin/server_test.go ================================================ // Copyright (c) Microsoft and contributors. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package plugin import ( "bytes" "context" "errors" "fmt" "testing" "github.com/Azure/kubernetes-kms/pkg/metrics" mockkeyvault "github.com/Azure/kubernetes-kms/pkg/plugin/mock_keyvault" "github.com/Azure/kubernetes-kms/pkg/version" kmsv1 "k8s.io/kms/apis/v1beta1" ) func TestEncrypt(t *testing.T) { tests := []struct { desc string input []byte output []byte err error }{ { desc: "failed to encrypt", input: []byte("foo"), output: []byte{}, err: fmt.Errorf("failed to encrypt"), }, { desc: "successfully encrypted", input: []byte("foo"), output: []byte("bar"), err: nil, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { kvClient := &mockkeyvault.KeyVaultClient{} kvClient.SetEncryptResponse(test.output, test.err) statsReporter, err := metrics.NewStatsReporter() if err != nil { t.Fatalf("failed to create stats reporter: %v", err) } kmsServer := KeyManagementServiceServer{ kvClient: kvClient, reporter: statsReporter, } out, err := kmsServer.Encrypt(context.TODO(), &kmsv1.EncryptRequest{ Plain: test.input, }) if !errors.Is(err, test.err) { t.Fatalf("expected err: %v, got: %v", test.err, err) } if !bytes.Equal(out.GetCipher(), test.output) { t.Fatalf("expected out: %v, got: %v", test.output, out) } }) } } func TestDecrypt(t *testing.T) { tests := []struct { desc string input []byte output []byte err error }{ { desc: "failed to decrypt", input: []byte("foo"), output: []byte{}, err: fmt.Errorf("failed to decrypt"), }, { desc: "successfully decrypted", input: []byte("bar"), output: []byte("foo"), err: nil, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { kvClient := &mockkeyvault.KeyVaultClient{} kvClient.SetDecryptResponse(test.output, test.err) statsReporter, err := metrics.NewStatsReporter() if err != nil { t.Fatalf("failed to create stats reporter: %v", err) } kmsServer := KeyManagementServiceServer{ kvClient: kvClient, reporter: statsReporter, } out, err := kmsServer.Decrypt(context.TODO(), &kmsv1.DecryptRequest{ Cipher: test.input, }) if !errors.Is(err, test.err) { t.Fatalf("expected err: %v, got: %v", test.err, err) } if !bytes.Equal(out.GetPlain(), test.output) { t.Fatalf("expected out: %v, got: %v", test.output, out) } }) } } func TestVersion(t *testing.T) { kmsServer := KeyManagementServiceServer{} version.BuildVersion = "latest" v, err := kmsServer.Version(context.TODO(), &kmsv1.VersionRequest{}) if err != nil { t.Fatalf("expected err to be nil, got: %v", err) } if v.Version != version.KMSv1APIVersion { t.Fatalf("expected version: %s, got: %s", version.KMSv1APIVersion, v.Version) } if v.RuntimeName != version.Runtime { t.Fatalf("expected runtime: %s, got: %s", version.Runtime, v.RuntimeName) } if v.RuntimeVersion != "latest" { t.Fatalf("expected runtime version: %s, got: %s", version.BuildVersion, v.Version) } } ================================================ FILE: pkg/utils/grpc.go ================================================ package utils import ( "context" "fmt" "strings" "time" "github.com/Azure/kubernetes-kms/pkg/metrics" "google.golang.org/grpc" "monis.app/mlog" ) // ParseEndpoint returns unix socket's protocol and address. func ParseEndpoint(ep string) (string, string, error) { if strings.HasPrefix(strings.ToLower(ep), "unix://") { s := strings.SplitN(ep, "://", 2) if s[1] != "" { return s[0], s[1], nil } } return "", "", fmt.Errorf("invalid endpoint: %v", ep) } // UnaryServerInterceptor provides metrics around Unary RPCs. func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { var err error start := time.Now() reporter, err := metrics.NewStatsReporter() if err != nil { return nil, fmt.Errorf("failed to create stats reporter: %w", err) } defer func() { errors := "" status := metrics.SuccessStatusTypeValue if err != nil { status = metrics.ErrorStatusTypeValue errors = err.Error() } reporter.ReportRequest(ctx, fmt.Sprintf("%s_%s", metrics.GrpcOperationTypeValue, getGRPCMethodName(info.FullMethod)), status, time.Since(start).Seconds(), errors) }() mlog.Trace("GRPC call", "method", info.FullMethod) resp, err := handler(ctx, req) if err != nil { mlog.Error("GRPC request error", err) } return resp, err } func getGRPCMethodName(fullMethodName string) string { fullMethodName = strings.TrimPrefix(fullMethodName, "/") methodNames := strings.Split(fullMethodName, "/") if len(methodNames) >= 2 { return strings.ToLower(methodNames[1]) } return "unknown" } ================================================ FILE: pkg/utils/grpc_test.go ================================================ package utils import "testing" func TestParseEndpoint(t *testing.T) { tests := []struct { desc string endpoint string expectedProto string expectedAddr string expectedErr bool }{ { desc: "invalid endpoint", endpoint: "udp:///provider/azure.sock", expectedErr: true, }, { desc: "invalid unix endpoint", endpoint: "unix://", expectedProto: "", expectedAddr: "", expectedErr: true, }, { desc: "valid unix endpoint", endpoint: "unix:///provider/azure.sock", expectedProto: "unix", expectedAddr: "/provider/azure.sock", expectedErr: false, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { proto, addr, err := ParseEndpoint(test.endpoint) if test.expectedErr && err == nil || !test.expectedErr && err != nil { t.Fatalf("expected error: %v, got error: %v", test.expectedErr, err) } if proto != test.expectedProto { t.Fatalf("expected proto: %v, got: %v", test.expectedProto, proto) } if addr != test.expectedAddr { t.Fatalf("expected addr: %v, got: %v", test.expectedAddr, addr) } }) } } func TestGetGRPCMethodName(t *testing.T) { testCases := []struct { name string input string expectedOutput string }{ { name: "With_Correct_Method_Name", input: "/v1beta1.KeyManagementService/Encrypt", expectedOutput: "encrypt", }, { name: "With_Incorrect_Method_Name", input: "/Encrypt", expectedOutput: "unknown", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { methodName := getGRPCMethodName(testCase.input) if methodName != testCase.expectedOutput { t.Fatalf("expected output: '%s', found: '%s'", testCase.expectedOutput, methodName) } }) } } ================================================ FILE: pkg/utils/sanitize.go ================================================ package utils import "strings" // SanitizeString returns a string that does not have white spaces and double quotes. func SanitizeString(s string) string { return strings.TrimSpace(strings.Trim(strings.TrimSpace(s), "\"")) } ================================================ FILE: pkg/utils/sanitize_test.go ================================================ package utils import "testing" func TestSanitizeString(t *testing.T) { testCases := []struct { name string input string expectedOutput string }{ { name: "With_White_Spaces", input: " hello ", expectedOutput: "hello", }, { name: "With_Double_Quotes", input: "\"hello\"", expectedOutput: "hello", }, { name: "With_White_Spaces_And_Double_Quotes", input: " \"hello\" ", expectedOutput: "hello", }, { name: "With_Double_Quotes_And_White_Spaces", input: "\" hello \"", expectedOutput: "hello", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { sanitizedString := SanitizeString(testCase.input) if sanitizedString != testCase.expectedOutput { t.Fatalf("expected output: '%s', found: '%s'", testCase.expectedOutput, sanitizedString) } }) } } ================================================ FILE: pkg/version/version.go ================================================ package version import ( "encoding/json" "fmt" "runtime" ) var ( // BuildDate is the date when the binary was built. BuildDate string // GitCommit is the commit hash when the binary was built. GitCommit string // BuildVersion is the version of the KMS binary. BuildVersion string // KMSv1APIVersion is the version of the KMS V1 APIs. KMSv1APIVersion = "v1beta1" // KMSv2APIVersion is the version of the KMS V2 APIs. KMSv2APIVersion = "v2beta1" // Runtime of the plugin. Runtime = "Microsoft AzureKMS" ) // PrintVersion prints the current KMS plugin version. func PrintVersion() (err error) { pv := struct { BuildVersion string GitCommit string BuildDate string }{ BuildDate: BuildDate, BuildVersion: BuildVersion, GitCommit: GitCommit, } var res []byte if res, err = json.Marshal(pv); err != nil { return } fmt.Printf("%s\n", res) return } // GetUserAgent returns UserAgent string to append to the agent identifier. func GetUserAgent() string { return fmt.Sprintf("k8s-kms-keyvault/%s (%s/%s) %s/%s", BuildVersion, runtime.GOOS, runtime.GOARCH, GitCommit, BuildDate) } ================================================ FILE: pkg/version/version_test.go ================================================ package version import ( "bytes" "fmt" "io" "os" "runtime" "strings" "testing" ) func TestPrintVersion(t *testing.T) { BuildDate = "Now" BuildVersion = "version" GitCommit = "hash" old := os.Stdout // keep backup of the real stdout r, w, _ := os.Pipe() os.Stdout = w err := PrintVersion() outC := make(chan string) // copy the output in a separate goroutine so printing can't block indefinitely go func() { var buf bytes.Buffer _, _ = io.Copy(&buf, r) outC <- strings.TrimSpace(buf.String()) }() // back to normal state w.Close() os.Stdout = old // restoring the real stdout out := <-outC if err != nil { t.Fatalf("expected no error, got %v", err) } expected := `{"BuildVersion":"version","GitCommit":"hash","BuildDate":"Now"}` if !strings.EqualFold(out, expected) { t.Fatalf("string doesn't match, expected %s, got %s", expected, out) } } func TestGetUserAgent(t *testing.T) { BuildDate = "Now" BuildVersion = "version" GitCommit = "hash" userAgent := GetUserAgent() expectedUserAgent := fmt.Sprintf("k8s-kms-keyvault/version (%s/%s) hash/Now", runtime.GOOS, runtime.GOARCH) if !strings.EqualFold(userAgent, expectedUserAgent) { t.Fatalf("string doesn't match, expected %s, got %s", expectedUserAgent, userAgent) } } ================================================ FILE: scripts/connect-registry.sh ================================================ #!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail if [ "${KIND_NETWORK}" != "bridge" ]; then # wait for the kind network to exist for i in $(seq 1 25); do if docker network ls | grep "${KIND_NETWORK}"; then break else sleep 1 fi done containers=$(docker network inspect "${KIND_NETWORK}" -f "{{range .Containers}}{{.Name}} {{end}}") needs_connect="true" for c in $containers; do if [ "$c" = "${REGISTRY_NAME}" ]; then needs_connect="false" fi done if [ "${needs_connect}" = "true" ]; then echo "connecting ${KIND_NETWORK} network to ${REGISTRY_NAME}" docker network connect "${KIND_NETWORK}" "${REGISTRY_NAME}" || true fi fi ================================================ FILE: scripts/setup-kind-cluster.sh ================================================ #!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail export ENCRYPTION_CONFIG_FILE=encryption-config.yaml envsubst < ./tests/e2e/kind-config.yaml > ./tests/e2e/generated_manifests/kind-config.yaml # create a cluster with the local registry enabled in containerd # add encryption config and the kms static pod manifest with custom image kind create cluster --retain --image kindest/node:"${KUBERNETES_VERSION}" --name "${KIND_CLUSTER_NAME}" --wait 2m --config=./tests/e2e/generated_manifests/kind-config.yaml ================================================ FILE: scripts/setup-kmsv2-kind-cluster.sh ================================================ #!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail export ENCRYPTION_CONFIG_FILE=kmsv2-encryption-config.yaml envsubst < ./tests/e2e/kind-config.yaml > ./tests/e2e/generated_manifests/kind-config.yaml # # create a cluster with the local registry enabled in containerd # # add encryption config and the kms static pod manifest with custom image kind create cluster --retain --image kindest/node:"${KUBERNETES_VERSION}" --name "${KIND_CLUSTER_NAME}" --wait 2m --config=./tests/e2e/generated_manifests/kind-config.yaml ================================================ FILE: scripts/setup-local-registry.sh ================================================ #!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail # create registry container unless it already exists running="$(docker inspect -f '{{.State.Running}}' "${REGISTRY_NAME}" 2>/dev/null || true)" if [ "${running}" != 'true' ]; then echo "Creating local registry" docker run \ -d --restart=always -p "${REGISTRY_PORT}:5000" --name "${REGISTRY_NAME}" \ mirror.gcr.io/registry:2 fi # create hosts.toml for the local registry containerd config # the certs.d directory is mounted into the kind node at /etc/containerd/certs.d rm -rf tests/e2e/generated_manifests/certs.d mkdir -p "tests/e2e/generated_manifests/certs.d/localhost:${REGISTRY_PORT}" cat < "tests/e2e/generated_manifests/certs.d/localhost:${REGISTRY_PORT}/hosts.toml" [host."http://${REGISTRY_NAME}:5000"] EOF # Build and push kms image export REGISTRY=localhost:${REGISTRY_PORT} export IMAGE_NAME=keyvault export IMAGE_VERSION=e2e-$(git rev-parse --short HEAD) export OUTPUT_TYPE=type=docker # push build image to local registry echo "Build and push image to local registry" make docker-init-buildx docker-build docker push "${REGISTRY}/${IMAGE_NAME}:${IMAGE_VERSION}" # generate manifest for local make e2e-generate-manifests ================================================ FILE: tests/client/client_test.go ================================================ package test import ( "bytes" "context" "fmt" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "k8s.io/apimachinery/pkg/util/uuid" kmsv1 "k8s.io/kms/apis/v1beta1" kmsv2 "k8s.io/kms/apis/v2" ) const ( netProtocol = "unix" pathToUnixSocket = "/opt/azurekms.sock" version = "v1beta1" ) var ( v1Client kmsv1.KeyManagementServiceClient v2Client kmsv2.KeyManagementServiceClient connection *grpc.ClientConn t *testing.T err error ) func setupTestCase() { if t != nil { t.Log("setup test case") connection, err = newUnixSocketConnection(pathToUnixSocket) if err != nil { fmt.Printf("%s", err) } v1Client = kmsv1.NewKeyManagementServiceClient(connection) v2Client = kmsv2.NewKeyManagementServiceClient(connection) } } func teardownTestCase() { if t != nil { t.Log("teardown test case") connection.Close() } } func TestEncryptDecrypt(t *testing.T) { cases := []struct { name string want []byte expected []byte }{ {"text", []byte("secret"), []byte("secret")}, {"number", []byte("1234"), []byte("1234")}, {"special", []byte("!@#$%^&*()_"), []byte("!@#$%^&*()_")}, {"GUID", []byte("b32a58c6-48c1-4552-8ff0-47680f3416d0"), []byte("b32a58c6-48c1-4552-8ff0-47680f3416d0")}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) v1EncryptRequest := kmsv1.EncryptRequest{Version: version, Plain: tc.want} v1EncryptResponse, err := v1Client.Encrypt(ctx, &v1EncryptRequest) if err != nil { t.Fatalf("encrypt request for KMS v1 failed with error: %+v", err) } v1DecryptRequest := kmsv1.DecryptRequest{Version: version, Cipher: v1EncryptResponse.Cipher} v1DecryptResponse, err := v1Client.Decrypt(ctx, &v1DecryptRequest) if !bytes.Equal(v1DecryptResponse.Plain, tc.want) { t.Fatalf("Expected secret, but got %s - %v", string(v1DecryptResponse.Plain), err) } uid := "integration-test-" + string(uuid.NewUUID()) v2EncryptRequest := kmsv2.EncryptRequest{ Plaintext: tc.want, Uid: uid, } v2EncryptResponse, err := v2Client.Encrypt(ctx, &v2EncryptRequest) if err != nil { t.Fatalf("encrypt request for KMS v2 failed with error: %+v", err) } if v2EncryptResponse.KeyId == "" { t.Fatalf("Returned KeyId is empty") } if v2EncryptResponse.Annotations == nil { t.Fatalf("Returned Annotations is nil") } v2DecryptRequest := kmsv2.DecryptRequest{ Ciphertext: v2EncryptResponse.Ciphertext, KeyId: v2EncryptResponse.KeyId, Uid: uid, Annotations: v2EncryptResponse.Annotations, } v2DecryptResponse, err := v2Client.Decrypt(ctx, &v2DecryptRequest) if !bytes.Equal(v2DecryptResponse.Plaintext, tc.want) { t.Fatalf("Expected secret, but got %s - %v", string(v2DecryptResponse.Plaintext), err) } }) } } // Check the KMS provider API version. // Only matching version is supported now. func TestV1Version(t *testing.T) { cases := []struct { name string want string expected string }{ {"v1beta1", "v1beta1", "v1beta1"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) request := &kmsv1.VersionRequest{Version: tc.want} response, err := v1Client.Version(ctx, request) if err != nil { t.Fatalf("failed get version from remote KMS provider: %v", err) } if response.Version != tc.want { t.Fatalf("KMS provider api version %s is not supported, only %s is supported now", tc.want, version) } }) } } func TestV2Version(t *testing.T) { cases := []struct { name string want string expected string }{ {"v2beta1", "v2beta1", "v2beta1"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) request := &kmsv2.StatusRequest{} response, err := v2Client.Status(ctx, request) if err != nil { t.Fatalf("failed get status of remote KMS v2 provider: %v", err) } if response.Version != tc.want { t.Fatalf("KMS v2 provider api version %s is not supported, only %s is supported now", tc.want, version) } }) } } func TestMain(m *testing.M) { t = &testing.T{} setupTestCase() m.Run() teardownTestCase() } func newUnixSocketConnection(path string) (*grpc.ClientConn, error) { return grpc.NewClient("unix://"+path, grpc.WithTransportCredentials(insecure.NewCredentials())) } ================================================ FILE: tests/e2e/azure.json ================================================ { "cloud": "AzurePublicCloud", "tenantId": "$AZURE_TENANT_ID", "useManagedIdentityExtension": true, "userAssignedIdentityID": "$USER_ASSIGNED_IDENTITY_ID" } ================================================ FILE: tests/e2e/encryption-config.yaml ================================================ kind: EncryptionConfiguration apiVersion: apiserver.config.k8s.io/v1 resources: - resources: - secrets providers: - kms: name: azurekmsprovider endpoint: unix:///opt/azurekms.socket cachesize: 1000 - identity: {} ================================================ FILE: tests/e2e/helpers.bash ================================================ #!/bin/bash assert_success() { if [[ "$status" != 0 ]]; then echo "expected: 0" echo "actual: $status" echo "output: $output" return 1 fi } assert_equal() { if [[ "$1" != "$2" ]]; then echo "expected: $1" echo "actual: $2" return 1 fi } assert_match() { if [[ ! "$2" =~ $1 ]]; then echo "expected: $1" echo "actual: $2" return 1 fi } wait_for_process() { wait_time="$1" sleep_time="$2" cmd="$3" while [ "$wait_time" -gt 0 ]; do if eval "$cmd"; then return 0 else sleep "$sleep_time" wait_time=$((wait_time - sleep_time)) fi done return 1 } ================================================ FILE: tests/e2e/kind-config.yaml ================================================ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 containerdConfigPatches: - |- [plugins."io.containerd.grpc.v1.cri".registry] config_path = "/etc/containerd/certs.d" nodes: - role: control-plane extraMounts: - containerPath: /etc/containerd/certs.d hostPath: tests/e2e/generated_manifests/certs.d readOnly: true - containerPath: /etc/kubernetes/${ENCRYPTION_CONFIG_FILE} hostPath: tests/e2e/${ENCRYPTION_CONFIG_FILE} readOnly: true propagation: None - containerPath: /etc/kubernetes/manifests/kubernetes-kms.yaml hostPath: tests/e2e/generated_manifests/kms.yaml readOnly: true propagation: None - containerPath: /etc/kubernetes/azure.json hostPath: tests/e2e/generated_manifests/azure.json readOnly: true propagation: None kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: extraArgs: encryption-provider-config: "/etc/kubernetes/${ENCRYPTION_CONFIG_FILE}" feature-gates: "KMSv1=true" extraVolumes: - name: encryption-config hostPath: "/etc/kubernetes/${ENCRYPTION_CONFIG_FILE}" mountPath: "/etc/kubernetes/${ENCRYPTION_CONFIG_FILE}" readOnly: true pathType: File - name: sock-path hostPath: "/opt" mountPath: "/opt" ================================================ FILE: tests/e2e/kms.yaml ================================================ apiVersion: v1 kind: Pod metadata: name: azure-kms-provider namespace: kube-system labels: tier: control-plane component: azure-kms-provider spec: priorityClassName: system-node-critical hostNetwork: true containers: - name: azure-kms-provider image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_VERSION} imagePullPolicy: IfNotPresent args: - --keyvault-name=${KEYVAULT_NAME} - --key-name=${KEY_NAME} - --key-version=${KEY_VERSION} - --managed-hsm=false - -v=5 env: # setting this env var so we get debug logs in SDK from CI runs - name: AZURE_GO_SDK_LOG_LEVEL value: DEBUG securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsUser: 0 ports: - containerPort: 8787 protocol: TCP livenessProbe: httpGet: path: /healthz port: 8787 failureThreshold: 2 periodSeconds: 10 resources: requests: cpu: 100m memory: 128Mi limits: cpu: "4" memory: 2Gi volumeMounts: - name: etc-kubernetes mountPath: /etc/kubernetes - name: etc-ssl mountPath: /etc/ssl readOnly: true - name: sock mountPath: /opt volumes: - name: etc-kubernetes hostPath: path: /etc/kubernetes - name: etc-ssl hostPath: path: /etc/ssl - name: sock hostPath: path: /opt nodeSelector: kubernetes.io/os: linux ================================================ FILE: tests/e2e/kmsv2-encryption-config.yaml ================================================ kind: EncryptionConfiguration apiVersion: apiserver.config.k8s.io/v1 resources: - resources: - secrets providers: - kms: apiVersion: v2 name: azurekmsprovider endpoint: unix:///opt/azurekms.socket ================================================ FILE: tests/e2e/test.bats ================================================ #!/usr/bin/env bats load helpers WAIT_TIME=120 SLEEP_TIME=1 export ETCD_CA_CERT=/etc/kubernetes/pki/etcd/ca.crt export ETCD_CERT=/etc/kubernetes/pki/etcd/server.crt export ETCD_KEY=/etc/kubernetes/pki/etcd/server.key @test "azure keyvault kms plugin is running" { wait_for_process ${WAIT_TIME} ${SLEEP_TIME} "kubectl -n kube-system wait --for=condition=Ready --timeout=60s pod -l component=azure-kms-provider" } @test "creating secret resource" { run kubectl create secret generic secret1 -n default --from-literal=foo=bar assert_success } @test "read the secret resource test" { result=$(kubectl get secret secret1 -o jsonpath='{.data.foo}' | base64 -d) [[ "${result//$'\r'}" == "bar" ]] } @test "check if secret is encrypted in etcd" { local pod_name=$(kubectl get pod -n kube-system -l component=etcd -o jsonpath="{.items[0].metadata.name}") run kubectl exec ${pod_name} -n kube-system -- etcdctl --cacert=${ETCD_CA_CERT} --cert=${ETCD_CERT} --key=${ETCD_KEY} get /registry/secrets/default/secret1 assert_match "k8s:enc:kms:v1:azurekmsprovider" "${output}" assert_success } @test "check if metrics endpoint works" { local curl_pod_name=curl-$(openssl rand -hex 5) kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels="test=metrics_test" -- tail -f /dev/null kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name} local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath="{.items[0].status.podIP}") run kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8095/metrics assert_match "kms_request_bucket" "${output}" assert_success } @test "check healthz for kms plugin" { local curl_pod_name=curl-$(openssl rand -hex 5) kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels="test=healthz_test" -- tail -f /dev/null kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name} local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath="{.items[0].status.podIP}") result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz) [[ "${result//$'\r'}" == "ok" ]] result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz -o /dev/null -w '%{http_code}\n' -s) [[ "${result//$'\r'}" == "200" ]] } teardown_file() { # cleanup run kubectl delete secret secret1 -n default run kubectl delete pod -l test=metrics_test --force --grace-period 0 run kubectl delete pod -l test=healthz_test --force --grace-period 0 } ================================================ FILE: tests/e2e/testkmsv2.bats ================================================ #!/usr/bin/env bats load helpers WAIT_TIME=120 SLEEP_TIME=1 export ETCD_CA_CERT=/etc/kubernetes/pki/etcd/ca.crt export ETCD_CERT=/etc/kubernetes/pki/etcd/server.crt export ETCD_KEY=/etc/kubernetes/pki/etcd/server.key setup() { # get the initial number of encrypted count local metrics=$(kubectl get --raw /metrics) expected_encyption_count=$(echo "${metrics}" | grep -oP 'apiserver_envelope_encryption_key_id_hash_total\{[^\}]*transformation_type="to_storage"[^\}]*\}\s+\K\d+') } @test "azure keyvault kms plugin is running" { wait_for_process ${WAIT_TIME} ${SLEEP_TIME} "kubectl -n kube-system wait --for=condition=Ready --timeout=60s pod -l component=azure-kms-provider" } @test "creating secret resource" { run kubectl create secret generic secret1 -n default --from-literal=foo=bar let "expected_encyption_count++" assert_success } @test "read the secret resource test" { result=$(kubectl get secret secret1 -o jsonpath='{.data.foo}' | base64 -d) [[ "${result//$'\r'}" == "bar" ]] } @test "check if secret is encrypted in etcd" { local pod_name=$(kubectl get pod -n kube-system -l component=etcd -o jsonpath="{.items[0].metadata.name}") run kubectl exec ${pod_name} -n kube-system -- etcdctl --cacert=${ETCD_CA_CERT} --cert=${ETCD_CERT} --key=${ETCD_KEY} get /registry/secrets/default/secret1 assert_match "k8s:enc:kms:v2:azurekmsprovider" "${output}" assert_success } @test "check encryption count" { # The expected_encryption_count value is set in the setup(). local metrics=$(kubectl get --raw /metrics) encyption_count=$(echo "${metrics}" | grep -oP 'apiserver_envelope_encryption_key_id_hash_total\{[^\}]*transformation_type="to_storage"[^\}]*\}\s+\K\d+') [[ "${encyption_count}" == "${expected_encyption_count}" ]] } @test "check keyID hash used for encrypt/decrypt" { # expected_hash value is computed based on the key used in CI. # this needs to be updated when we rotate that key. local expected_hash="sha256:cbda52be2f8c13d323a3b17c4679118a60b91d29454305e02ee485185b6e386f" local metrics=$(kubectl get --raw /metrics) got_hash_id=$(echo "${metrics}" | grep 'apiserver_envelope_encryption_key_id_hash_last_timestamp_seconds' | sed -n 's/.*key_id_hash="\([^"]*\)".*/\1/p' | sort -u) [[ "${got_hash_id}" == "${expected_hash}" ]] } @test "check if metrics endpoint works" { local curl_pod_name=curl-$(openssl rand -hex 5) kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels="test=metrics_test" -- tail -f /dev/null kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name} local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath="{.items[0].status.podIP}") run kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8095/metrics assert_match "kms_request_bucket" "${output}" assert_success } @test "check healthz for kms plugin" { local curl_pod_name=curl-$(openssl rand -hex 5) kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels="test=healthz_test" -- tail -f /dev/null kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name} local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath="{.items[0].status.podIP}") result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz) [[ "${result//$'\r'}" == "ok" ]] result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz -o /dev/null -w '%{http_code}\n' -s) [[ "${result//$'\r'}" == "200" ]] } teardown_file() { # cleanup run kubectl delete secret secret1 -n default run kubectl delete pod -l test=metrics_test --force --grace-period 0 run kubectl delete pod -l test=healthz_test --force --grace-period 0 } ================================================ FILE: tools/go.mod ================================================ module github.com/Azure/kubernetes-kms/tools go 1.26.2 require github.com/golangci/golangci-lint/v2 v2.7.2 require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect dev.gaijin.team/go/golib v0.6.0 // indirect github.com/4meepo/tagalign v1.4.3 // indirect github.com/Abirdcfly/dupword v0.1.7 // indirect github.com/AdminBenni/iota-mixing v1.0.0 // indirect github.com/AlwxSin/noinlineerr v1.0.5 // indirect github.com/Antonboom/errname v1.1.1 // indirect github.com/Antonboom/nilnil v1.1.1 // indirect github.com/Antonboom/testifylint v1.6.4 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/MirrexOne/unqueryvet v1.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect github.com/alecthomas/chroma/v2 v2.20.0 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect github.com/ashanbrown/makezero/v2 v2.1.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect github.com/bombsimon/wsl/v5 v5.3.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect github.com/butuzov/ireturn v0.4.0 // indirect github.com/butuzov/mirror v1.3.0 // indirect github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.11 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.6 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.17 // indirect github.com/go-critic/go-critic v0.14.2 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/godoc-lint/godoc-lint v0.10.2 // indirect github.com/gofrs/flock v0.13.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect github.com/golangci/misspell v0.7.0 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/gordonklaus/ineffassign v0.2.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jgautheron/goconst v1.8.2 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect github.com/kisielk/errcheck v1.9.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/exptostd v0.4.5 // indirect github.com/ldez/gomoddirectives v0.7.1 // indirect github.com/ldez/grignotin v0.10.1 // indirect github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect github.com/manuelarte/funcorder v0.5.0 // indirect github.com/maratori/testableexamples v1.0.1 // indirect github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.13.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.21.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.8.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/quasilyte/go-ruleguard v0.4.5 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect github.com/securego/gosec/v2 v2.22.11-0.20251204091113-daccba6b93d7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.4.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.4 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.0 // indirect github.com/uudashr/iface v1.4.1 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.14.0 // indirect go-simpler.org/sloglint v0.11.1 // indirect go.augendre.info/arangolint v0.3.1 // indirect go.augendre.info/fatcontext v0.9.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/tools v0.39.0 // indirect google.golang.org/protobuf v1.36.8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect mvdan.cc/gofumpt v0.9.2 // indirect mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect ) ================================================ FILE: tools/go.sum ================================================ 4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= 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/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/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/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= codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/MirrexOne/unqueryvet v1.3.0 h1:5slWSomgqpYU4zFuZ3NNOfOUxVPlXFDBPAVasZOGlAY= github.com/MirrexOne/unqueryvet v1.3.0/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo= github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c= github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE= github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= github.com/bombsimon/wsl/v5 v5.3.0 h1:nZWREJFL6U3vgW/B1lfDOigl+tEF6qgs6dGGbFeR0UM= github.com/bombsimon/wsl/v5 v5.3.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 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/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= 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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.17 h1:sjGPErP9o7i2Ym+z3LsQzBdLCNaqbYy2iJQPxGXg04Q= github.com/ghostiam/protogetter v0.3.17/go.mod h1:AivIX1eKA/TcUmzZdzbl+Tb8tjIe8FcyG6JFyemQAH4= github.com/go-critic/go-critic v0.14.2 h1:PMvP5f+LdR8p6B29npvChUXbD1vrNlKDf60NJtgMBOo= github.com/go-critic/go-critic v0.14.2/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= 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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 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-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godoc-lint/godoc-lint v0.10.2 h1:dksNgK+zebnVlj4Fx83CRnCmPO0qRat/9xfFsir1nfg= github.com/godoc-lint/godoc-lint v0.10.2/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7pDQg4NxMYLcBBsw= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= github.com/golangci/golangci-lint/v2 v2.7.2 h1:AhBC+YeEueec4AGlIbvPym5C70Thx0JykIqXbdIXWx0= github.com/golangci/golangci-lint/v2 v2.7.2/go.mod h1:pDijleoBu7e8sejMqyZ3L5n6geqe+cVvOAz2QImqqVc= github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8= github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c= github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= 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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/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-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= github.com/ldez/gomoddirectives v0.7.1 h1:FaULkvUIG36hj6chpwa+FdCNGZBsD7/fO+p7CCsM6pE= github.com/ldez/gomoddirectives v0.7.1/go.mod h1:auDNtakWJR1rC+YX7ar+HmveqXATBAyEK1KYpsIRW/8= github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.13.0 h1:yFbEVliCVKRXY8UgwEO7EOYNopvjb1BFbmYqm9hZjBM= github.com/mgechev/revive v1.13.0/go.mod h1:efJfeBVCX2JUumNQ7dtOLDja+QKj9mYGgEZA7rt5u+0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.21.2 h1:khzWfm2/Br8ZemX8QM1pl72LwM+rMeW6VUbQ4rzh0Po= github.com/nunnatsa/ginkgolinter v0.21.2/go.mod h1:GItSI5fw7mCGLPmkvGYrr1kEetZe7B593jcyOpyabsY= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA= github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= github.com/securego/gosec/v2 v2.22.11-0.20251204091113-daccba6b93d7 h1:rZg6IGn0ySYZwCX8LHwZoYm03JhG/cVAJJ3O+u3Vclo= github.com/securego/gosec/v2 v2.22.11-0.20251204091113-daccba6b93d7/go.mod h1:9sr22NZO5Kfh7unW/xZxkGYTmj2484/fCiE54gw7UTY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= go.augendre.info/arangolint v0.3.1 h1:n2E6p8f+zfXSFLa2e2WqFPp4bfvcuRdd50y6cT65pSo= go.augendre.info/arangolint v0.3.1/go.mod h1:6ZKzEzIZuBQwoSvlKT+qpUfIbBfFCE5gbAoTg0/117g= go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= 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.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 h1:HDjDiATsGqvuqvkDvgJjD1IgPrVekcSXVVE21JwvzGE= golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= 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/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.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 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-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190422165155-953cdadca894/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-20200106162015-b016eb3dc98e/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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 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/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-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 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-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/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/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/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/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.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= 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= ================================================ FILE: tools/tools.go ================================================ //go:build tools // +build tools package tools import ( _ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" )