[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help KMS Plugin for Key Vault improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\n\n**Steps To Reproduce**\n\n**Expected behavior**\n\n**KMS Plugin for Key Vault version**\n\n**Kubernetes version**\n\n**Additional context**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for KMS Plugin for Key Vault\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Describe the request**\n\n**Explain why KMS Plugin for Key Vault needs it**\n\n**Describe the solution you'd like**\n\n**Describe alternatives you've considered**\n\n**Additional context**\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thank you for helping KMS Plugin for Key Vault with a pull request! -->\n\n**Reason for Change**:\n<!-- What does this PR improve or fix in KMS Plugin for Key Vault? Why is it needed? -->\n\n\n**Issue Fixed**:\n<!-- If this PR fixes GitHub issue 1234, add \"Fixes #1234\" to the next line. -->\n\n**Notes for Reviewers**:\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"chore\"\n    ignore:\n      - dependency-name: \"*\"\n        update-types:\n        - \"version-update:semver-major\"\n        - \"version-update:semver-minor\"\n\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \"chore\"\n      \n  - package-ecosystem: docker\n    directory: /\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \"chore\"\n      \n  - package-ecosystem: gomod\n    directory: /tools\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \"chore\"\n"
  },
  {
    "path": ".github/semantic.yml",
    "content": "titleOnly: true\ntypes:\n  - chore\n  - ci\n  - docs\n  - feat\n  - fix\n  - perf\n  - refactor\n  - release\n  - revert\n  - security\n  - test\n"
  },
  {
    "path": ".github/workflows/codeql.yaml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n    branches:\n    - master\n  schedule:\n    - cron: \"0 15 * * 1\" # Mondays at 7:00 AM PST\n\npermissions: read-all\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1\n        with:\n          egress-policy: audit\n\n      - name: Checkout repository\n        uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@b2c19fb9a2a485599ccf4ed5d65527d94bc57226\n        with:\n          languages: go\n\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@b2c19fb9a2a485599ccf4ed5d65527d94bc57226\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@b2c19fb9a2a485599ccf4ed5d65527d94bc57226\n"
  },
  {
    "path": ".github/workflows/create-release.yml",
    "content": "name: create_release\non:\n  push:\n    tags:\n      - 'v*'\n\npermissions:\n  contents: write\n\njobs:\n  create-release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1\n        with:\n          egress-policy: audit\n\n      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2\n        with:\n          submodules: true\n          fetch-depth: 0\n\n      - name: Goreleaser\n        uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0\n        with:\n          version: \"~> v2\"\n          args: release --clean --fail-fast --timeout 60m --verbose\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/dependency-review.yml",
    "content": "# Dependency Review Action\n#\n# This Action will scan dependency manifest files that change as part of a Pull Request,\n# surfacing known-vulnerable versions of the packages declared or updated in the PR.\n# Once installed, if the workflow run is marked as required, \n# PRs introducing known-vulnerable packages will be blocked from merging.\n#\n# Source repository: https://github.com/actions/dependency-review-action\nname: 'Dependency Review'\non: [pull_request]\n\npermissions:\n  contents: read\n\njobs:\n  dependency-review:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1\n        with:\n          egress-policy: audit\n\n      - name: 'Checkout Repository'\n        uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2\n      - name: 'Dependency Review'\n        uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c # v2.5.1\n"
  },
  {
    "path": ".github/workflows/scorecards.yml",
    "content": "# This workflow uses actions that are not certified by GitHub. They are provided\n# by a third-party and are governed by separate terms of service, privacy\n# policy, and support documentation.\n\nname: Scorecard supply-chain security\non:\n  # For Branch-Protection check. Only the default branch is supported. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection\n  branch_protection_rule:\n  # To guarantee Maintained check is occasionally updated. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained\n  schedule:\n    - cron: '20 7 * * 2'\n  push:\n    branches: [\"master\"]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Needed to publish results and get a badge (see publish_results below).\n      id-token: write\n      contents: read\n      actions: read\n\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1\n        with:\n          egress-policy: audit\n\n      - name: \"Checkout code\"\n        uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) \"write\" PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecards on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.\n          # repo_token: ${{ secrets.SCORECARD_TOKEN }}\n\n          # Public repositories:\n          #   - Publish results to OpenSSF REST API for easy access by consumers\n          #   - Allows the repository to include the Scorecard badge.\n          #   - See https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories:\n          #   - `publish_results` will always be set to `false`, regardless\n          #     of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard.\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@8662eabe0e9f338a07350b7fd050732745f93848 # v2.3.1\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736\n.glide/\nsetenv.sh\nkubernetes-kms\nvendor\n*.env\n\n# Vscode files\n.vscode\n\n# OSX trash\n.DS_Store\n\n.idea/\n_output/\n\n# e2e output\ntests/e2e/generated_manifests/*\n\n# Go tools\n.tools/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  go: \"1.26\"\nlinters:\n  default: none\n  enable:\n    - errorlint\n    - goconst\n    - gocyclo\n    - gosec\n    - govet\n    - ineffassign\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unused\n    - whitespace\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    rules:\n      - linters:\n          - staticcheck\n        text: \"SA1019: .*(v1beta1|KMSv1 is deprecated)\"\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n  settings:\n    revive:\n      rules:\n        - name: var-naming\n          disabled: true\n    staticcheck:\n      checks:\n        - all\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# refer to https://goreleaser.com for more options\nversion: 2\nbuilds:\n- skip: true\nrelease:\n  prerelease: auto\n  header: |\n    ## {{.Tag}} - {{ time \"2006-01-02\" }}\nchangelog:\n  disable: false\n  groups:\n    - title: Bug Fixes 🐞\n      regexp: ^.*fix[(\\\\w)]*:+.*$\n    - title: Build 🏭\n      regexp: ^.*build[(\\\\w)]*:+.*$\n    - title: Code Refactoring 💎\n      regexp: ^.*refactor[(\\\\w)]*:+.*$\n    - title: Code Style 🎶\n      regexp: ^.*style[(\\\\w)]*:+.*$\n    - title: Continuous Integration 💜\n      regexp: ^.*ci[(\\\\w)]*:+.*$\n    - title: Documentation 📘\n      regexp: ^.*docs[(\\\\w)]*:+.*$\n    - title: Features 🌈\n      regexp: ^.*feat[(\\\\w)]*:+.*$\n    - title: Maintenance 🔧\n      regexp: ^.*chore[(\\\\w)]*:+.*$\n    - title: Performance Improvements 🚀\n      regexp: ^.*perf[(\\\\w)]*:+.*$\n    - title: Revert Change ◀️\n      regexp: ^.*revert[(\\\\w)]*:+.*$\n    - title: Security Fix 🛡️\n      regexp: ^.*security[(\\\\w)]*:+.*$\n    - title: Testing 💚\n      regexp: ^.*test[(\\\\w)]*:+.*$\n"
  },
  {
    "path": ".pipelines/nightly.yml",
    "content": "trigger: none\n\nschedules:\n  - cron: \"0 0 * * *\"\n    always: true\n    displayName: \"Nightly Build & Test\"\n    branches:\n      include:\n        - master\n\npool: staging-pool-amd64-mariner-2\n\njobs:\n  - template: templates/unit-tests-template.yml\n  - template: templates/e2e-upgrade-template.yml\n"
  },
  {
    "path": ".pipelines/pr.yml",
    "content": "trigger:\n  branches:\n    include:\n    - master\n\npr:\n  branches:\n    include:\n      - master\n  paths:\n    exclude:\n      - docs/*\n      - README.md\n      - .github/*\n\npool: staging-pool-amd64-mariner-2\n\njobs:\n  - template: templates/unit-tests-template.yml\n  - template: templates/e2e-kind-template.yml\n"
  },
  {
    "path": ".pipelines/templates/cleanup-template.yml",
    "content": "steps:\n  - script: |\n      kubectl logs -l component=azure-kms-provider -n kube-system --tail -1\n      kubectl get pods -o wide -A\n    displayName: \"Get logs\"\n    \n  - script: make e2e-delete-kind\n    displayName: \"Delete cluster\"\n"
  },
  {
    "path": ".pipelines/templates/cluster-health-template.yml",
    "content": "steps:\n  - script: |\n      kubectl wait --for=condition=ready node --all\n      kubectl wait pod -n kube-system --for=condition=Ready --all\n      kubectl get nodes -owide\n    displayName: \"Check cluster health\"\n"
  },
  {
    "path": ".pipelines/templates/e2e-kind-template.yml",
    "content": "jobs:\n  - job:\n    timeoutInMinutes: 15\n    cancelTimeoutInMinutes: 5\n    workspace:\n      clean: all\n    variables:\n    - name: REGISTRY_NAME\n      value: kind-registry\n    - name: REGISTRY_PORT\n      value: 5000\n    - name: KUBERNETES_VERSION\n      value: v1.32.3\n    - name: KIND_CLUSTER_NAME\n      value: kms\n    - name: KIND_NETWORK\n      value: kind\n    # contains the following environment variables:\n    # - AZURE_TENANT_ID\n    # - KEYVAULT_NAME\n    # - KEY_NAME\n    # - KEY_VERSION\n    # - USER_ASSIGNED_IDENTITY_ID\n    - group: kubernetes-kms\n    strategy:\n      matrix:\n        kmsv1_kind_v1_33_7:\n          KUBERNETES_VERSION: v1.33.7\n        kmsv1_kind_v1_34_3:\n          KUBERNETES_VERSION: v1.34.3\n        kmsv1_kind_v1_35_0:\n          KUBERNETES_VERSION: v1.35.0\n    steps:\n      - task: GoTool@0\n        inputs:\n          version: 1.26.2\n      - template: prepare-deps.yaml\n      - script: make e2e-install-prerequisites\n        displayName: \"Install e2e test prerequisites\"\n      - script: |\n          make e2e-setup-kind\n        displayName: \"Setup kind cluster with azure kms plugin\"\n        env:\n          REGISTRY_NAME: $(REGISTRY_NAME)\n          REGISTRY_PORT: $(REGISTRY_PORT)\n          KUBERNETES_VERSION: $(KUBERNETES_VERSION)\n          KIND_CLUSTER_NAME: $(KIND_CLUSTER_NAME)\n          KIND_NETWORK: $(KIND_NETWORK)\n      - template: cluster-health-template.yml\n      - template: kind-debug-template.yml\n      - script: make e2e-test\n        displayName: \"Run e2e tests for KMS v1\"\n      - template: cleanup-template.yml\n  - job:\n    timeoutInMinutes: 15\n    cancelTimeoutInMinutes: 5\n    workspace:\n      clean: all\n    variables:\n    - name: REGISTRY_NAME\n      value: kind-registry\n    - name: REGISTRY_PORT\n      value: 5000\n    - name: KUBERNETES_VERSION\n      value: v1.32.3\n    - name: KIND_CLUSTER_NAME\n      value: kms\n    - name: KIND_NETWORK\n      value: kind\n    # contains the following environment variables:\n    # - AZURE_TENANT_ID\n    # - KEYVAULT_NAME\n    # - KEY_NAME\n    # - KEY_VERSION\n    # - USER_ASSIGNED_IDENTITY_ID\n    - group: kubernetes-kms\n    strategy:\n      matrix:\n        kmsv2_kind_v1_33_7:\n          KUBERNETES_VERSION: v1.33.7\n        kmsv2_kind_v1_34_3:\n          KUBERNETES_VERSION: v1.34.3\n        kmsv2_kind_v1_35_0:\n          KUBERNETES_VERSION: v1.35.0\n    steps:\n      - task: GoTool@0\n        inputs:\n          version: 1.26.2\n      - template: prepare-deps.yaml\n      - script: make e2e-install-prerequisites\n        displayName: \"Install e2e test prerequisites\"\n      - script: |\n          make e2e-kmsv2-setup-kind\n        displayName: \"Setup kind cluster with azure kms plugin\"\n        env:\n          REGISTRY_NAME: $(REGISTRY_NAME)\n          REGISTRY_PORT: $(REGISTRY_PORT)\n          KUBERNETES_VERSION: $(KUBERNETES_VERSION)\n          KIND_CLUSTER_NAME: $(KIND_CLUSTER_NAME)\n          KIND_NETWORK: $(KIND_NETWORK)\n      - template: cluster-health-template.yml\n      - template: kind-debug-template.yml\n      - script: make e2e-kmsv2-test\n        displayName: \"Run e2e tests for KMS v2\"\n      - template: cleanup-template.yml\n"
  },
  {
    "path": ".pipelines/templates/e2e-upgrade-template.yml",
    "content": "jobs:\n  - job: e2e_upgrade_tests\n    timeoutInMinutes: 10\n    cancelTimeoutInMinutes: 5\n    workspace:\n      clean: all\n    variables:\n      - name: REGISTRY_NAME\n        value: kind-registry\n      - name: REGISTRY_PORT\n        value: 5000\n      - name: KUBERNETES_VERSION\n        value: v1.23.5\n      - name: KIND_CLUSTER_NAME\n        value: kms\n      - name: KIND_NETWORK\n        value: kind\n      # contains the following environment variables:\n      # - AZURE_TENANT_ID\n      # - KEYVAULT_NAME\n      # - KEY_NAME\n      # - KEY_VERSION\n      # - USER_ASSIGNED_IDENTITY_ID\n      - group: kubernetes-kms\n\n    steps:\n      - task: GoTool@0\n        inputs:\n          version: 1.26.2\n      - template: prepare-deps.yaml\n\n      - script: make e2e-install-prerequisites\n        displayName: \"Install e2e test prerequisites\"\n\n      - script: |\n          . scripts/setup-local-registry.sh\n        displayName: \"Setup local registry\"\n        env:\n          REGISTRY_NAME: $(REGISTRY_NAME)\n          REGISTRY_PORT: $(REGISTRY_PORT)\n\n      - script: |\n          version=$(git tag -l --sort=v:refname | tail -n 1)\n          echo \"##vso[task.setvariable variable=LATEST_KMS_VERSION]$version\"\n\n          echo \"Latest released kms version - $version\"\n        displayName: \"Get latest released version\"\n      \n      - template: manifest-template.yml\n        parameters:\n          registry: mcr.microsoft.com/oss/v2/azure/kms\n          imageName: keyvault\n          imageVersion: $(LATEST_KMS_VERSION)\n\n      - script: |\n          . scripts/setup-kind-cluster.sh &\n          . scripts/connect-registry.sh &\n          wait\n        displayName: \"Setup kind cluster with azure kms plugin\"\n        env:\n          REGISTRY_NAME: $(REGISTRY_NAME)\n          REGISTRY_PORT: $(REGISTRY_PORT)\n          KUBERNETES_VERSION: $(KUBERNETES_VERSION)\n          KIND_CLUSTER_NAME: $(KIND_CLUSTER_NAME)\n          KIND_NETWORK: $(KIND_NETWORK)\n\n      - template: cluster-health-template.yml\n      - template: kind-debug-template.yml\n\n      - script: make e2e-test\n        displayName: \"Run e2e tests\"\n\n      - script: |\n          echo \"##vso[task.setvariable variable=LOCAL_IMAGE_VERSION]$(git rev-parse --short HEAD)\"\n        displayName: \"Update Image version\"\n      \n      # This stage will upgrade kms plugin. The path (./tests/e2e/generated_manifests) is mounted in kind cluster.\n      # Any changes in the host will automatically be reflected in /etc/kubernetes/manifests mount path and that static pod is restarted with new changes.\n      # manifest-template updates these files with registry, imageName and version to desired upgrade values.\n      - template: manifest-template.yml\n        parameters:\n          registry: localhost:$(REGISTRY_PORT)\n          imageName: keyvault\n          imageVersion: e2e-$(LOCAL_IMAGE_VERSION)\n\n      - script: |\n          # wait for the kind network to exist\n          echo \"waiting for upgraded kms pod to be Running\"\n          for i in $(seq 1 25); do\n            image=$(kubectl get pods -n kube-system azure-kms-provider-kms-control-plane -o jsonpath=\"{.spec.containers[*].image}\")\n            phase=$(kubectl get pods -n kube-system azure-kms-provider-kms-control-plane -o jsonpath=\"{.status.phase}\")\n            echo \"image - $image phase - $phase\"\n            if [ \"${image}\" == \"${REGISTRY}/${IMAGE_NAME}:e2e-${LOCAL_IMAGE_VERSION}\" ] && [ \"${phase}\" == \"Running\" ]; then\n              break\n            else\n              sleep 5\n            fi\n          done\n          # Give additional 5s for plugin to start. Remove this once https://github.com/Azure/kubernetes-kms/issues/113 is fixed.\n          sleep 5\n        displayName: \"Wait for kms upgrade\"\n      \n      - template: cluster-health-template.yml\n      - template: kind-debug-template.yml\n\n      - script: make e2e-test\n        displayName: \"Run e2e tests\"\n\n      - template: cleanup-template.yml\n"
  },
  {
    "path": ".pipelines/templates/kind-debug-template.yml",
    "content": "steps:\n  - script: |\n      docker exec kms-control-plane bash -c \"cat /etc/kubernetes/manifests/kubernetes-kms.yaml\"\n      docker exec kms-control-plane bash -c \"cat /etc/kubernetes/manifests/kube-apiserver.yaml\"\n      docker exec kms-control-plane bash -c \"cat /etc/kubernetes/encryption-config.yaml\"\n      docker exec kms-control-plane bash -c \"journalctl -u kubelet > kubelet.log && cat kubelet.log\"\n      docker exec kms-control-plane bash -c \"cd /var/log/containers ; cat *\"\n      docker network ls\n    displayName: \"Debug logs\"\n    condition: failed()\n"
  },
  {
    "path": ".pipelines/templates/manifest-template.yml",
    "content": "parameters:\n  - name: registry\n    type: string\n  - name: imageName\n    type: string\n  - name: imageVersion\n    type: string\n\nsteps:\n  - script: |\n      export REGISTRY=${{ parameters.registry }}\n      export IMAGE_NAME=${{ parameters.imageName }}\n      export IMAGE_VERSION=${{ parameters.imageVersion }}\n\n      make e2e-generate-manifests\n\n      echo \"##vso[task.setvariable variable=REGISTRY]${{ parameters.registry }}\"\n      echo \"##vso[task.setvariable variable=IMAGE_NAME]${{ parameters.imageName }}\"\n    displayName: \"Generate Manifests\"\n"
  },
  {
    "path": ".pipelines/templates/prepare-deps.yaml",
    "content": "steps:\n- bash: |\n    for i in {1..10}; do\n      if sudo tdnf install -y kernel-headers make gcc glibc-devel binutils gettext; then\n        exit 0\n      fi\n      echo \"waiting until rpm lock is free\"\n      sleep 5\n    done\n    exit 1\n"
  },
  {
    "path": ".pipelines/templates/scan-images-template.yml",
    "content": "steps:\n  - script: |\n      export REGISTRY=\"e2e\"\n      export IMAGE_VERSION=\"test\"\n      export OUTPUT_TYPE=\"type=docker\"\n      make docker-init-buildx docker-build\n\n      wget https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION:-0.70.0}/trivy_${TRIVY_VERSION:-0.70.0}_Linux-64bit.tar.gz\n      tar zxvf trivy_${TRIVY_VERSION:-0.70.0}_Linux-64bit.tar.gz\n\n      # show all vulnerabilities in the logs\n      ./trivy image \"${REGISTRY}/keyvault:${IMAGE_VERSION}\"\n      ./trivy image --exit-code 1 --ignore-unfixed --severity MEDIUM,HIGH,CRITICAL \"${REGISTRY}/keyvault:${IMAGE_VERSION}\" || exit 1\n    displayName: \"Scan images for vulnerability\"\n"
  },
  {
    "path": ".pipelines/templates/unit-tests-template.yml",
    "content": "jobs:\n  - job: unit_tests\n    timeoutInMinutes: 10\n    cancelTimeoutInMinutes: 5\n    workspace:\n      clean: all\n    variables:\n      # contains the following environment variables:\n      # - AZURE_TENANT_ID\n      # - KEYVAULT_NAME\n      # - KEY_NAME\n      # - KEY_VERSION\n      # - USER_ASSIGNED_IDENTITY_ID\n    - group: kubernetes-kms\n\n    steps:\n      - task: GoTool@0\n        inputs:\n          version: 1.26.2\n      - template: prepare-deps.yaml\n      - script: make lint\n        displayName: Run lint\n      - script: make unit-test\n        displayName: Run unit tests\n      - script: make build\n        displayName: Build\n      - script: |\n          sudo ./_output/kubernetes-kms --version\n        displayName: Check binary version\n      - script: |\n          sudo mkdir /etc/kubernetes\n          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\n          sudo chown root:root /etc/kubernetes/azure.json && sudo chmod 600 /etc/kubernetes/azure.json\n        displayName: Setup azure.json on host\n      - script: |\n          sudo ./_output/kubernetes-kms --keyvault-name $KEYVAULT_NAME --key-name $KEY_NAME --key-version $KEY_VERSION --listen-addr \"unix:///opt/azurekms.sock\" > /dev/null &\n          echo Waiting 2 seconds for the server to start\n          sleep 2\n          sudo env \"PATH=$PATH\" make integration-test\n        displayName: Run integration tests\n      - template: scan-images-template.yml\n"
  },
  {
    "path": "AUTHORS",
    "content": "Rita Zhang <rita.z.zhang@gmail.com>\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "# Ref: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners\n\n*       @aramase @enj\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Microsoft Open Source Code of Conduct\n\nThis 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.\n\nOur open source communities strive to:\n\n- **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.\n- **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.\n- **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:\n        Violent threats or language.\n        Discriminatory or derogatory jokes and language.\n        Posting sexually explicit or violent material.\n        Posting, or threatening to post, people's personally identifying information (\"doxing\").\n        Insults, especially those using discriminatory terms or slurs.\n        Behavior that could be perceived as sexual attention.\n        Advocating for or encouraging any of the above behaviors.\n- **Understand disagreements:** Disagreements, both social and technical, are useful learning opportunities. Seek to understand the other viewpoints and resolve differences constructively.\n- 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.\n\n## Scope\n\nThis 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.\n\nNote: 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.\n\n## Reporting Code of Conduct Issues\n\nWe 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].\n\nIn your report please include:\n\n- Your contact information.\n- Names (real, usernames or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well.\n- 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.\n- Any additional information that may be helpful.\n\nAll 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.\n\nAnyone 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.\n\n*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].*\n\n[Code of Conduct FAQ]: https://opensource.microsoft.com/codeofconduct/faq/\n[template]: http://todogroup.org/opencodeofconduct\n[TODO Group]: http://todogroup.org/\n[Facebook]: https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct\n[Yahoo]: https://yahoo.github.io/codeofconduct\n[Twitter]: https://engineering.twitter.com/opensource/code-of-conduct\n[GitHub]: http://todogroup.org/opencodeofconduct/#opensource@github.com\n[Contributor Covenant version 1.4]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThis project welcomes contributions and suggestions.  Most contributions require you to agree to a\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\nthe rights to use your contribution. For details, visit https://cla.microsoft.com.\n\nWhen you submit a pull request, a CLA-bot will automatically determine whether you need to provide\na CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions\nprovided by the bot. You will only need to do this once across all repos using our CLA.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.2-bookworm@sha256:61e607875d60ae21a7a4a49110fe7098355473fbc74ab13091e3c1160cc92f18 AS builder\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the go source\nCOPY cmd/server/main.go main.go\nCOPY pkg/ pkg/\n\nARG TARGETARCH\nARG TARGETPLATFORM\nARG LDFLAGS\nRUN 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\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM --platform=${TARGETPLATFORM:-linux/amd64} mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0-nonroot.20250402@sha256:c5e349966c9a8ffe5af65970300d2b6899592da1714490b46561f5d86a0ab1e0\nWORKDIR /\nCOPY --from=builder /workspace/_output/kubernetes-kms .\n\nENTRYPOINT [ \"/kubernetes-kms\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "    MIT License\n\n    Copyright (c) Microsoft Corporation. All rights reserved.\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE\n"
  },
  {
    "path": "Makefile",
    "content": "ORG_PATH=github.com/Azure\nPROJECT_NAME := kubernetes-kms\nREPO_PATH=\"$(ORG_PATH)/$(PROJECT_NAME)\"\n\nREGISTRY_NAME ?= upstreamk8sci\nREPO_PREFIX ?= oss/azure/kms\nREGISTRY ?= $(REGISTRY_NAME).azurecr.io/$(REPO_PREFIX)\nLOCAL_REGISTRY_NAME ?= kind-registry\nLOCAL_REGISTRY_PORT ?= 5000\nIMAGE_NAME ?= keyvault\nIMAGE_VERSION ?= v0.10.0\nIMAGE_TAG := $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)\nCGO_ENABLED_FLAG := 0\n\n# build variables\nBUILD_VERSION_VAR := $(REPO_PATH)/pkg/version.BuildVersion\nBUILD_DATE_VAR := $(REPO_PATH)/pkg/version.BuildDate\nBUILD_DATE := $$(date +%Y-%m-%d-%H:%M)\nGIT_VAR := $(REPO_PATH)/pkg/version.GitCommit\nGIT_HASH := $$(git rev-parse --short HEAD)\nLDFLAGS ?= \"-X $(BUILD_DATE_VAR)=$(BUILD_DATE) -X $(BUILD_VERSION_VAR)=$(IMAGE_VERSION) -X $(GIT_VAR)=$(GIT_HASH)\"\n\nGO_FILES=$(shell go list ./... | grep -v /test/e2e)\nTOOLS_MOD_DIR := ./tools\nTOOLS_DIR := $(abspath ./.tools)\n\n# docker env var\nDOCKER_BUILDKIT = 1\nexport DOCKER_BUILDKIT\n\n# Testing var\nKIND_VERSION ?= 0.31.0\nKUBERNETES_VERSION ?= v1.35.0\nBATS_VERSION ?= 1.4.1\n\n## --------------------------------------\n## Linting\n## --------------------------------------\n\n$(TOOLS_DIR)/golangci-lint: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go\n\tcd $(TOOLS_MOD_DIR) && \\\n\tgo build -o $(TOOLS_DIR)/golangci-lint github.com/golangci/golangci-lint/v2/cmd/golangci-lint\n\n.PHONY: lint\nlint: $(TOOLS_DIR)/golangci-lint\n\t$(TOOLS_DIR)/golangci-lint run --timeout=5m -v\n\n## --------------------------------------\n## Images\n## --------------------------------------\n\nALL_LINUX_ARCH ?= amd64 arm64\n# Output type of docker buildx build\nOUTPUT_TYPE ?= type=registry\n\nBUILDX_BUILDER_NAME ?= img-builder\nQEMU_VERSION ?= 5.2.0-2\n# The architecture of the image\nARCH ?= amd64\n\n.PHONY: build\nbuild:\n\tgo build -a -ldflags $(LDFLAGS) -o _output/kubernetes-kms ./cmd/server/\n\n.PHONY: docker-init-buildx\ndocker-init-buildx:\n\t@if ! docker buildx ls | grep $(BUILDX_BUILDER_NAME); then \\\n\t\tdocker run --rm --privileged mirror.gcr.io/multiarch/qemu-user-static:$(QEMU_VERSION) --reset -p yes; \\\n\t\tdocker buildx create --name $(BUILDX_BUILDER_NAME) --use; \\\n\t\tdocker buildx inspect $(BUILDX_BUILDER_NAME) --bootstrap; \\\n\tfi\n\n.PHONY: docker-build\ndocker-build:\n\tdocker buildx build \\\n\t\t--build-arg LDFLAGS=$(LDFLAGS) \\\n\t\t--no-cache \\\n\t\t--platform=\"linux/$(ARCH)\" \\\n\t\t--output=$(OUTPUT_TYPE) \\\n\t\t-t $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$(ARCH)  . \\\n\t\t--progress=plain; \\\n\n\t@if [ \"$(ARCH)\" = \"amd64\" ] && [ \"$(OUTPUT_TYPE)\" = \"type=docker\" ]; then \\\n\t\tdocker tag $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$(ARCH) $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION); \\\n\tfi\n\n.PHONY: docker-build-all\ndocker-build-all:\n\t@for arch in $(ALL_LINUX_ARCH); do \\\n\t\t$(MAKE) ARCH=$${arch} docker-build; \\\n\tdone\n\n.PHONY: docker-push-manifest\ndocker-push-manifest:\n\tdocker manifest create --amend $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION) $(foreach arch,$(ALL_LINUX_ARCH),$(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$(arch)); \\\n\tfor arch in $(ALL_LINUX_ARCH); do \\\n\t\tdocker manifest annotate --os linux --arch $${arch} $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION) $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION)-linux-$${arch}; \\\n\tdone; \\\n\tdocker manifest push --purge $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION); \\\n\n## --------------------------------------\n## Testing\n## --------------------------------------\n\n.PHONY: integration-test\nintegration-test:\n\tgo test -v -count=1 -failfast github.com/Azure/kubernetes-kms/tests/client\n\n.PHONY: unit-test\nunit-test:\n\tgo test -race -v -count=1 -failfast `go list ./... | grep -v client`\n\n\n## --------------------------------------\n## E2E Testing\n## --------------------------------------\ne2e-install-prerequisites:\n\t# Download and install kind\n\tcurl -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/\n\t# Download and install kubectl\n\tcurl -LO https://dl.k8s.io/release/${KUBERNETES_VERSION}/bin/linux/amd64/kubectl && chmod +x ./kubectl && sudo mv kubectl /usr/local/bin/\n\t# Download and install bats\n\tcurl -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\n\ne2e-setup-kind: setup-local-registry\n\t./scripts/setup-kind-cluster.sh &\n\t./scripts/connect-registry.sh &\n\tsleep 90s\n\ne2e-kmsv2-setup-kind: setup-local-registry\n\t./scripts/setup-kmsv2-kind-cluster.sh &\n\t./scripts/connect-registry.sh &\n\tsleep 90s\n\n.PHONY: setup-local-registry\nsetup-local-registry:\n\t./scripts/setup-local-registry.sh\n\ne2e-generate-manifests:\n\t@mkdir -p tests/e2e/generated_manifests\n\tenvsubst < tests/e2e/azure.json > tests/e2e/generated_manifests/azure.json\n\tenvsubst < tests/e2e/kms.yaml > tests/e2e/generated_manifests/kms.yaml\n\ne2e-delete-kind:\n\t# delete kind e2e cluster created for tests\n\tkind delete cluster --name kms\n\ne2e-test:\n\t# Run test suite with kind cluster\n\tbats -t tests/e2e/test.bats\n\ne2e-kmsv2-test:\n\t# Run test suite with kind cluster\n\tbats -t tests/e2e/testkmsv2.bats\n"
  },
  {
    "path": "README.md",
    "content": "# KMS Plugin for Key Vault\n\n[![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)\n[![Go Report Card](https://goreportcard.com/badge/Azure/kubernetes-kms)](https://goreportcard.com/report/Azure/kubernetes-kms)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Azure/kubernetes-kms)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/Azure/kubernetes-kms)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Azure/kubernetes-kms/badge)](https://api.securityscorecards.dev/projects/github.com/Azure/kubernetes-kms)\n\nEnables encryption at rest of your Kubernetes data in etcd using Azure Key Vault.\n\nFrom the Kubernetes documentation on [Encrypting Secret Data at Rest]:\n\n> _[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.\n\n⚠️ **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.\n\n💡 **NOTE**: To integrate your application secrets from a key management system outside of Kubernetes, use [Azure Key Vault Provider for Secrets Store CSI Driver].\n\n## Features\n\n- Use a key in Key Vault for etcd encryption\n- Use a key in Key Vault protected by a Hardware Security Module (HSM)\n- Bring your own keys\n- Store secrets, keys, and certs in etcd, but manage them as part of Kubernetes\n\n## Getting Started\n\n### Prerequisites\n\n💡 Make sure you have a Kubernetes cluster version 1.10 or later, the minimum version that is supported by KMS Plugin for Key Vault.\n\n### Azure Kubernetes Service (AKS)\n\nAzure Kubernetes Service ([AKS]) creates managed, supported Kubernetes clusters on Azure.\n\nTo 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).\n\n### Setting up KMS Plugin manually\n\nRefer to [doc](docs/manual-install.md) for steps to setup the KMS Key Vault plugin on an existing cluster.\n\n## Verifying that Data is Encrypted\n\nNow 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:\n\n1. Create a new secret:\n\n   ```bash\n   kubectl create secret generic secret1 -n default --from-literal=mykey=mydata\n   ```\n\n2. Using `etcdctl`, read the secret from etcd:\n\n   ```bash\n   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\n   ```\n\n3. 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.\n\n4. Verify the secret is decrypted correctly when retrieved via the Kubernetes API:\n\n   ```bash\n   kubectl get secrets secret1 -o yaml\n   ```\n\n   The output should match `mykey: bXlkYXRh`, which is the encoded data of `mydata`.\n\n## Rotation\n\nRefer to [doc](docs/rotation.md) for steps to rotate the KMS Key on an existing cluster.\n\n## Metrics\nRefer to [doc](docs/metrics.md) for details on the metrics exposed by the KMS Key Vault plugin.\n\n## Contributing\n\nThe KMS Plugin for Key Vault project welcomes contributions and suggestions. Please see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Roadmap\nYou 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.\n\n## Release\n\nCurrently, this project releases monthly to patch security vulnerabilities, and bi-monthly for new features. We target the **first week** of the month for release.\n\n## Code of conduct\n\nThis 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.\n\n## Support\n\nKMS 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.\n\n[aks]: https://azure.microsoft.com/services/kubernetes-service/\n[encrypting secret data at rest]: https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/#providers\n[azure key vault provider for secrets store csi driver]: https://github.com/Azure/secrets-store-csi-driver-provider-azure\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->\n\n## Security\n\nMicrosoft 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/).\n\nIf 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.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).\n\nIf 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).\n\nYou 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). \n\nPlease 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:\n\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf 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.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "cmd/server/main.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/config\"\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\t\"github.com/Azure/kubernetes-kms/pkg/plugin\"\n\t\"github.com/Azure/kubernetes-kms/pkg/utils\"\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\n\t\"google.golang.org/grpc\"\n\t\"k8s.io/klog/v2\"\n\tkmsv1 \"k8s.io/kms/apis/v1beta1\"\n\tkmsv2 \"k8s.io/kms/apis/v2\"\n\t\"monis.app/mlog\"\n)\n\nvar (\n\tlistenAddr    = flag.String(\"listen-addr\", \"unix:///opt/azurekms.socket\", \"gRPC listen address\")\n\tkeyvaultName  = flag.String(\"keyvault-name\", \"\", \"Azure Key Vault name\")\n\tkeyName       = flag.String(\"key-name\", \"\", \"Azure Key Vault KMS key name\")\n\tkeyVersion    = flag.String(\"key-version\", \"\", \"Azure Key Vault KMS key version\")\n\tmanagedHSM    = 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.\")\n\tlogFormatJSON = flag.Bool(\"log-format-json\", false, \"set log formatter to json\")\n\tlogLevel      = flag.Uint(\"v\", 0, \"In order of increasing verbosity: 0=warning/error, 2=info, 4=debug, 6=trace, 10=all\")\n\t// TODO remove this flag in future release.\n\t_              = flag.String(\"configFilePath\", \"/etc/kubernetes/azure.json\", \"[DEPRECATED] Path for Azure Cloud Provider config file\")\n\tconfigFilePath = flag.String(\"config-file-path\", \"/etc/kubernetes/azure.json\", \"Path for Azure Cloud Provider config file\")\n\tversionInfo    = flag.Bool(\"version\", false, \"Prints the version information\")\n\n\thealthzPort    = flag.Uint(\"healthz-port\", 8787, \"port for health check\")\n\thealthzPath    = flag.String(\"healthz-path\", \"/healthz\", \"path for health check\")\n\thealthzTimeout = flag.Duration(\"healthz-timeout\", 20*time.Second, \"RPC timeout for health check\")\n\tmetricsBackend = flag.String(\"metrics-backend\", \"prometheus\", \"Backend used for metrics\")\n\tmetricsAddress = flag.String(\"metrics-addr\", \"8095\", \"The address the metric endpoint binds to\")\n\n\tproxyMode    = flag.Bool(\"proxy-mode\", false, \"Proxy mode\")\n\tproxyAddress = flag.String(\"proxy-address\", \"\", \"proxy address\")\n\tproxyPort    = flag.Int(\"proxy-port\", 7788, \"port for proxy\")\n)\n\nfunc main() {\n\tif err := setupKMSPlugin(); err != nil {\n\t\tmlog.Fatal(err)\n\t}\n}\n\nfunc setupKMSPlugin() error {\n\tdefer mlog.Setup()() // set up log flushing and attempt to flush on exit\n\tflag.Parse()\n\tctx := withShutdownSignal(context.Background())\n\n\tlogFormat := mlog.FormatText\n\tif *logFormatJSON {\n\t\tlogFormat = mlog.FormatJSON\n\t}\n\n\tif *logLevel > math.MaxUint8 {\n\t\treturn fmt.Errorf(\"invalid log level: %d\", *logLevel)\n\t}\n\n\tif err := mlog.ValidateAndSetKlogLevelAndFormatGlobally(ctx, klog.Level(uint8(*logLevel)), logFormat); err != nil {\n\t\treturn fmt.Errorf(\"invalid --log-level set: %w\", err)\n\t}\n\n\tif *versionInfo {\n\t\tif err := version.PrintVersion(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to print version: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// initialize metrics exporter\n\terr := metrics.InitMetricsExporter(*metricsBackend, *metricsAddress)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize metrics exporter: %w\", err)\n\t}\n\n\tmlog.Always(\"Starting KeyManagementServiceServer service\", \"version\", version.BuildVersion, \"buildDate\", version.BuildDate)\n\n\tpluginConfig := &plugin.Config{\n\t\tKeyVaultName:   *keyvaultName,\n\t\tKeyName:        *keyName,\n\t\tKeyVersion:     *keyVersion,\n\t\tManagedHSM:     *managedHSM,\n\t\tProxyMode:      *proxyMode,\n\t\tProxyAddress:   *proxyAddress,\n\t\tProxyPort:      *proxyPort,\n\t\tConfigFilePath: *configFilePath,\n\t}\n\n\tazureConfig, err := config.GetAzureConfig(pluginConfig.ConfigFilePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get azure config: %w\", err)\n\t}\n\n\tkvClient, err := plugin.NewKeyVaultClient(\n\t\tazureConfig,\n\t\tpluginConfig.KeyVaultName,\n\t\tpluginConfig.KeyName,\n\t\tpluginConfig.KeyVersion,\n\t\tpluginConfig.ProxyMode,\n\t\tpluginConfig.ProxyAddress,\n\t\tpluginConfig.ProxyPort,\n\t\tpluginConfig.ManagedHSM,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create key vault client: %w\", err)\n\t}\n\n\t// Initialize and run the GRPC server\n\tproto, addr, err := utils.ParseEndpoint(*listenAddr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse endpoint: %w\", err)\n\t}\n\tif err := os.Remove(addr); err != nil && !os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"failed to remove socket file %s: %w\", addr, err)\n\t}\n\n\tlistener, err := net.Listen(proto, addr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to listen addr: %s, proto: %s: %w\", addr, proto, err)\n\t}\n\n\topts := []grpc.ServerOption{\n\t\tgrpc.UnaryInterceptor(utils.UnaryServerInterceptor),\n\t}\n\n\ts := grpc.NewServer(opts...)\n\n\t// register kms v1 server\n\tkmsV1Server, err := plugin.NewKMSv1Server(kvClient)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create server: %w\", err)\n\t}\n\tkmsv1.RegisterKeyManagementServiceServer(s, kmsV1Server)\n\n\t// register kms v2 server\n\tkmsV2Server, err := plugin.NewKMSv2Server(kvClient)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create kms V2 server: %w\", err)\n\t}\n\tkmsv2.RegisterKeyManagementServiceServer(s, kmsV2Server)\n\n\tmlog.Always(\"Listening for connections\", \"addr\", listener.Addr().String())\n\tgo func() {\n\t\tif err := s.Serve(listener); err != nil {\n\t\t\tmlog.Fatal(fmt.Errorf(\"failed to serve kms server: %w\", err))\n\t\t}\n\t}()\n\n\t// Health check for kms v1 and v2\n\thealthz := &plugin.HealthZ{\n\t\tKMSv1Server: kmsV1Server,\n\t\tKMSv2Server: kmsV2Server,\n\t\tHealthCheckURL: &url.URL{\n\t\t\tHost: net.JoinHostPort(\"\", strconv.FormatUint(uint64(*healthzPort), 10)),\n\t\t\tPath: *healthzPath,\n\t\t},\n\t\tUnixSocketPath: listener.Addr().String(),\n\t\tRPCTimeout:     *healthzTimeout,\n\t}\n\tgo healthz.Serve()\n\n\t<-ctx.Done()\n\t// gracefully stop the grpc server\n\tmlog.Always(\"terminating the server\")\n\ts.GracefulStop()\n\n\treturn nil\n}\n\n// withShutdownSignal returns a copy of the parent context that will close if\n// the process receives termination signals.\nfunc withShutdownSignal(ctx context.Context) context.Context {\n\tsignalChan := make(chan os.Signal, 1)\n\tsignal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, os.Interrupt)\n\n\tnctx, cancel := context.WithCancel(ctx)\n\n\tgo func() {\n\t\t<-signalChan\n\t\tmlog.Always(\"received shutdown signal\")\n\t\tcancel()\n\t}()\n\treturn nctx\n}\n"
  },
  {
    "path": "developers.md",
    "content": "# Developers Guide\n\nThis guide explains how to set up your environment for developing the Azure kubernetes kms service.\n\n## Prerequisites\n\n- Go 1.9.0 or later\n- dep\n- kubectl 1.9 or later\n- An Azure account (needed for creating Azure key vault)\n- Git\n- make\n\n### Structure of the Code\n\nThe code for the kubernetes-kms project is organized as follows:\n\n- The built binary is located in root `./kubernetes-kms`\n- 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.\n\nGo dependencies are managed with [dep](https://github.com/golang/dep) and stored in the\n`vendor/` directory.\n\n\n### Git Conventions\n\nWe use Git for our version control system. The `master` branch is the\nhome of the current development candidate. Releases are tagged.\n\nWe accept changes to the code via GitHub Pull Requests (PRs). One\nworkflow for doing this is as follows:\n\n1. Use `go get` to clone this repository: `go get github.com/Azure/kubernetes-kms`\n2. Fork that repository into your GitHub account\n3. Add your repository as a remote for `$GOPATH/github.com/Azure/kubernetes-kms`\n4. Create a new working branch (`git checkout -b feat/my-feature`) and\n   do your work on that branch.\n5. When you are ready for us to review, push your branch to GitHub, and\n   then open a new pull request with us.\n\n### Build the Code\n\nWe use `make` and `Makefile` to build the binary and the Docker image. To start the build process:\n\n1. Run `make build` to build the binary `/kubernetes-kms` for your OS\n\n### Run the Code Locally\n\nTo test your code locally:\n\n1. On a linux machine, you can run `sudo ./kubernetes-kms --configFilePath <PATH TO YOUR AZURE.JSON FILE>` to create the gRPC unix domain socket running at `/opt/azurekms.socket`. This will start the gRPC server.\n2. Create an Azure resource group, a Key Vault, and update the key vault's access policy with:\n\n```bash\naz group create -n mykeyvaultrg -l eastus\naz keyvault create -n k8skv -g mykeyvaultrg\naz keyvault set-policy -n k8skv --key-permissions create decrypt encrypt get list --spn <YOUR SPN CLIENT ID>\n```\nIf 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).\n\n3. 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`. \n\n```json\n{\n    \"tenantId\": \"<YOUR TENANT ID>\",\n    \"subscriptionId\": \"<YOUR SUBSCRIPTION ID>\",\n    \"aadClientId\": \"<YOUR CLIENT ID>\",\n    \"aadClientSecret\": \"<YOUR CLIENT SECRET>\",\n    \"resourceGroup\": \"mykeyvaultrg\",\n    \"location\": \"eastus\",\n    \"providerVaultName\": \"k8skv\",\n    \"providerKeyName\": \"mykey\"\n}\n```\n4. Test with the gRPC client, run `sudo GOPATH=[YOUR GOPATH] GOCACHE=off go test tests/client/client_test.go`.\n5. 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 &`.\n\n### Build image\n1. Run `make build-image` to build the binary `/kubernetes-kms` for linux and Docker image `mcr.microsoft.com/k8s/kms/keyvault:latest`\n"
  },
  {
    "path": "docs/manual-install.md",
    "content": "# 🛠 Manual Configurations #\n\nThis guide demonstrates steps required to enable the KMS Plugin for Key Vault in an existing cluster.\n\n### 1. Create a Keyvault\n\n  If you're bringing your own keys, skip this step.\n\n  ```bash\n  KEYVAULT_NAME=k8skv\n  RG=mykubernetesrg\n  LOC=eastus\n\n  # create resource group that'll contain the keyvault instance\n  az group create -n $RG -l $LOC\n  # create keyvault\n  az keyvault create -n $KV_NAME -g $RG\n  # create key that will be used for encryption\n  az keyvault key create -n k8s --vault-name $KV_NAME --kty RSA --size 2048\n  ```\n\n### 2. Give the cluster identity permissions to access the keys in keyvault\n\n  The KMS Plugin uses the cluster service principal or managed identity to access the keyvault instance.\n\n  #### More on authentication methods\n\n  [`/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:\n\n  | Authentication method            | `/etc/kubernetes/azure.json` fields used                                                    |\n  | -------------------------------- | ------------------------------------------------------------------------------------------- |\n  | System-assigned managed identity | `useManagedIdentityExtension: true` and `userAssignedIdentityID:\"\"`                         |\n  | User-assigned managed identity   | `useManagedIdentityExtension: true` and `userAssignedIdentityID:\"<UserAssignedIdentityID>\"` |\n  | Service principal (default)      | `aadClientID: \"<AADClientID>\"` and `aadClientSecret: \"<AADClientSecret>\"`                   |\n\n  #### Obtaining the ID of the cluster managed identity/service principal\n\n  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:\n\n  | Cluster configuration              | Command                                                                                                        |\n  | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n  | AKS cluster with service principal | `az aks show -g <AKSResourceGroup> -n <AKSClusterName> --query servicePrincipalProfile.clientId -otsv`         |\n  | AKS cluster with managed identity  | `az aks show -g <AKSResourceGroup> -n <AKSClusterName> --query identityProfile.kubeletidentity.clientId -otsv` |\n\n  Assign the following permissions:\n\n  ```bash\n  az keyvault set-policy -n $KEYVAULT_NAME --key-permissions decrypt encrypt --spn <YOUR SPN CLIENT ID>\n  ```\n\n### 3. Deploy the KMS Plugin\n\n  For all Kubernetes control plane nodes, add the static pod manifest to `/etc/kubernetes/manifests`\n\n  ```yaml\n  apiVersion: v1\n  kind: Pod\n  metadata:\n    name: azure-kms-provider\n    namespace: kube-system\n    labels:\n      tier: control-plane\n      component: azure-kms-provider\n  spec:\n    priorityClassName: system-node-critical\n    hostNetwork: true\n    containers:\n      - name: azure-kms-provider\n        image: mcr.microsoft.com/oss/v2/azure/kms/keyvault:v0.10.0\n        imagePullPolicy: IfNotPresent\n        args:\n          - --listen-addr=unix:///opt/azurekms.socket             # [OPTIONAL] gRPC listen address. Default is unix:///opt/azurekms.socket\n          - --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\n          - --key-name=${KEY_NAME}                                # [REQUIRED] Name of the keyvault key used for encrypt/decrypt\n          - --key-version=${KEY_VERSION}                          # [REQUIRED] Version of the key to use\n          - --log-format-json=false                               # [OPTIONAL] Set log formatter to json. Default is false.\n          - --healthz-port=8787                                   # [OPTIONAL] port for health check. Default is 8787\n          - --healthz-path=/healthz                               # [OPTIONAL] path for health check. Default is /healthz\n          - --healthz-timeout=20s                                 # [OPTIONAL] RPC timeout for health check. Default is 20s\n          - --managed-hsm=false                                   # [OPTIONAL] Use Azure Key Vault managed HSM. Default is false.\n          - -v=1\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsUser: 0\n        ports:\n          - containerPort: 8787                                   # Must match the value defined in --healthz-port\n            protocol: TCP\n        livenessProbe:\n          httpGet:\n            path: /healthz                                        # Must match the value defined in --healthz-path\n            port: 8787                                            # Must match the value defined in --healthz-port\n          failureThreshold: 2\n          periodSeconds: 10\n        resources:\n          requests:\n            cpu: 100m\n            memory: 128Mi\n          limits:\n            cpu: 4\n            memory: 2Gi\n        volumeMounts:\n          - name: etc-kubernetes\n            mountPath: /etc/kubernetes\n          - name: etc-ssl\n            mountPath: /etc/ssl\n            readOnly: true\n          - name: sock\n            mountPath: /opt\n    volumes:\n      - name: etc-kubernetes\n        hostPath:\n          path: /etc/kubernetes\n      - name: etc-ssl\n        hostPath:\n          path: /etc/ssl\n      - name: sock\n        hostPath:\n          path: /opt\n  ```\n\n  View logs from the kms pod:\n\n  ```bash\n  kubectl logs -l component=azure-kms-provider -n kube-system\n\n  I0219 17:35:33.608840       1 main.go:60] \"Starting KeyManagementServiceServer service\" version=\"v0.0.11\" buildDate=\"2021-02-19-17:33\"\n  I0219 17:35:33.609090       1 azure_config.go:27] populating AzureConfig from /etc/kubernetes/azure.json\n  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\"\n  I0219 17:35:33.609568       1 keyvault.go:66] \"using kms key for encrypt/decrypt\" vaultName=\"k8skmskv\" keyName=\"key1\" keyVersion=\"5cdf48ea6bb9456ebf637e1130b7751a\"\n  I0219 17:35:33.609897       1 main.go:86] Listening for connections on address: /opt/azurekms.socket\n  ...\n  ```\n\n### 4. Create encryption configuration\n\n  Create a new encryption configuration file `/etc/kubernetes/manifests/encryptionconfig.yaml` using the appropriate properties for the `kms` provider:\n\n  ```yaml\n  kind: EncryptionConfiguration\n  apiVersion: apiserver.config.k8s.io/v1\n  resources:\n    - resources:                                        # List of kubernetes resources that will be encrypted in etcd using the KMS plugin\n        - secrets\n      providers:\n        - kms:\n            name: azurekmsprovider\n            endpoint: unix:///opt/azurekms.socket       # This endpoint must match the value defined in --listen-addr for the KMS plugin\n            cachesize: 1000\n        - identity: {}\n  ```\n\n  The encryption configuration file needs to be accessible by all the api servers.\n\n### 5. Modify `/etc/kubernetes/kube-apiserver.yaml`\n\n  Add the following flag:\n\n  ```yaml\n  --encryption-provider-config=/etc/kubernetes/encryptionconfig.yaml\n  ```\n\n  Mount `/opt` to access the socket:\n\n  ```yaml\n  ...\n  volumeMounts:\n  - name: \"sock\"\n    mountPath: \"/opt\"\n  ...\n  volumes:\n    - name: \"sock\"\n      hostPath:\n        path: \"/opt\"\n  ```\n\n### 6. Restart your API server\n"
  },
  {
    "path": "docs/metrics.md",
    "content": "# Metrics provided by KMS plugin for Key Vault\n\nThis 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.\n\n## List of metrics provided by the kms plugin\n\n| Metric                          | Description                                                               | Tags                                                                              |\n| ------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |\n| kms_request                   | Distribution of how long it took for an operation                                                  | `status=success OR error`<br><br>`operation=encrypt OR decrypt OR grpc_encrypt OR grpc_decrypt`<br><br>`error_message`                           |\n\n\n### Sample Metrics output\n\n```shell\n# HELP kms_request Distribution of how long it took for an operation\n# TYPE kms_request histogram\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.1\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.13492828476735633\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.18205642030260802\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.24564560522315804\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.3314454017339986\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.4472135954999578\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.6034176336545162\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.8141810630738084\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.0985605433061172\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.4822688982138947\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.9999999999999991\"} 18\nkms_request_bucket{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"+Inf\"} 18\nkms_request_sum{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 1.010053082\nkms_request_count{operation=\"decrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 18\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.1\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.13492828476735633\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.18205642030260802\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.24564560522315804\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.3314454017339986\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.4472135954999578\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.6034176336545162\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.8141810630738084\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.0985605433061172\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.4822688982138947\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.9999999999999991\"} 19\nkms_request_bucket{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"+Inf\"} 19\nkms_request_sum{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 1.021080768\nkms_request_count{operation=\"encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 19\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.1\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.13492828476735633\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.18205642030260802\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.24564560522315804\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.3314454017339986\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.4472135954999578\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.6034176336545162\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.8141810630738084\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.0985605433061172\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.4822688982138947\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.9999999999999991\"} 1\nkms_request_bucket{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"+Inf\"} 1\nkms_request_sum{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 0.053279316\nkms_request_count{operation=\"grpc_encrypt\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 1\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.1\"} 0\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.13492828476735633\"} 11\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.18205642030260802\"} 13\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.24564560522315804\"} 13\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.3314454017339986\"} 13\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.4472135954999578\"} 13\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.6034176336545162\"} 14\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.8141810630738084\"} 14\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.0985605433061172\"} 14\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.4822688982138947\"} 14\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.9999999999999991\"} 14\nkms_request_bucket{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"+Inf\"} 14\nkms_request_sum{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 2.1240865880000004\nkms_request_count{operation=\"grpc_status\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 14\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.1\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.13492828476735633\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.18205642030260802\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.24564560522315804\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.3314454017339986\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.4472135954999578\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.6034176336545162\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"0.8141810630738084\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.0985605433061172\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.4822688982138947\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"1.9999999999999991\"} 9\nkms_request_bucket{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\",le=\"+Inf\"} 9\nkms_request_sum{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 0.0007254060000000001\nkms_request_count{operation=\"grpc_version\",otel_scope_name=\"keyvaultkms\",otel_scope_version=\"\",status=\"success\"} 9\n```\n"
  },
  {
    "path": "docs/rotation.md",
    "content": "# Rotating KMS key\n\nThis guide demonstrates steps required to update your cluster to use a new KMS key for encryption.\n\n> 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.\n\n### 1. Generate a new key or rotate the existing key\n\n* 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.\n* If this is a new version of the same key that's already being used, then proceed to the next step.\n\n### 2. Deploy another instance of KMS plugin with new key\n\nTo 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.\n\nFor all Kubernetes control plane nodes, add the static pod manifest to `/etc/kubernetes/manifests`\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: azure-kms-provider-2\n  namespace: kube-system\n  labels:\n    tier: control-plane\n    component: azure-kms-provider\nspec:\n  priorityClassName: system-node-critical\n  hostNetwork: true\n  containers:\n    - name: azure-kms-provider\n      image: mcr.microsoft.com/oss/v2/azure/kms/keyvault:v0.10.0\n      imagePullPolicy: IfNotPresent\n      args:\n      - --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.\n      - --keyvault-name=${KV_NAME}                            # [REQUIRED] Name of the keyvault\n      - --key-name=${KEY_NAME}                                # [REQUIRED] Name of the keyvault key used for encrypt/decrypt\n      - --key-version=${KEY_VERSION}                          # [REQUIRED] Version of the key to use\n      - --log-format-json=false                               # [OPTIONAL] Set log formatter to json. Default is false.\n      - --healthz-port=8788                                   # The port used here should be different than the one used by the primary kms plugin pod.\n      - --healthz-path=/healthz                               # [OPTIONAL] path for health check. Default is /healthz\n      - --healthz-timeout=20s                                 # [OPTIONAL] RPC timeout for health check. Default is 20s\n      - --managed-hsm=false                                   # [OPTIONAL] Use Azure Key Vault managed HSM. Default is false.\n      - -v=5\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsUser: 0\n      ports:\n        - containerPort: 8788                                 # Must match the value defined in --healthz-port\n          protocol: TCP\n      livenessProbe:\n        httpGet:\n          path: /healthz                                      # Must match the value defined in --healthz-path\n          port: 8788                                          # Must match the value defined in --healthz-port\n        failureThreshold: 2\n        periodSeconds: 10\n      resources:\n        requests:\n          cpu: 100m\n          memory: 128Mi\n        limits:\n          cpu: \"4\"\n          memory: 2Gi\n      volumeMounts:\n        - name: etc-kubernetes\n          mountPath: /etc/kubernetes\n        - name: etc-ssl\n          mountPath: /etc/ssl\n          readOnly: true\n        - name: sock\n          mountPath: /opt\n  volumes:\n    - name: etc-kubernetes\n      hostPath:\n        path: /etc/kubernetes\n    - name: etc-ssl\n      hostPath:\n        path: /etc/ssl\n    - name: sock\n      hostPath:\n        path: /opt\n  nodeSelector:\n    kubernetes.io/os: linux\n```\n\nView logs from the kms pod:\n\n```bash\nkubectl logs -l component=azure-kms-provider -n kube-system\n\nI0219 17:35:33.608840       1 main.go:60] \"Starting KeyManagementServiceServer service\" version=\"v0.0.11\" buildDate=\"2021-02-19-17:33\"\nI0219 17:35:33.609090       1 azure_config.go:27] populating AzureConfig from /etc/kubernetes/azure.json\nI0219 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\"\nI0219 17:35:33.609568       1 keyvault.go:66] \"using kms key for encrypt/decrypt\" vaultName=\"k8skmskv\" keyName=\"key1\" keyVersion=\"5cdf48ea6bb9456ebf637e1130b7751a\"\nI0219 17:35:33.609897       1 main.go:86] Listening for connections on address: /opt/azurekms2.socket\n...\n```\n\n### 3. Add the new provider to encryption configuration in `/etc/kubernetes/manifests/encryptionconfig.yaml`\n\n```yaml\nkind: EncryptionConfiguration\napiVersion: apiserver.config.k8s.io/v1\nresources:\n  - resources:                                            # List of kubernetes resources that will be encrypted in etcd using the KMS plugin\n      - secrets\n    providers:\n      - kms:\n          name: azurekmsprovider\n          endpoint: unix:///opt/azurekms.socket           # This endpoint must match the value defined in --listen-addr for the KMS plugin using old key\n          cachesize: 1000\n      - kms:\n          name: azurekmsprovider2\n          endpoint: unix:///opt/azurekms2.socket          # This endpoint must match the value defined in --listen-addr for the KMS plugin using new key\n          cachesize: 1000\n```\n\n### 4. Restart all `kube-apiserver`\n\n* Proceed to the next step if using a single `kube-apiserver`\n* 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.\n* To validate the decryption still works, run `kubectl get secret <secret name> -o yaml` with one of the existing secrets to confirm the data is returned and is valid.\n\n### 5. Switch the order of provider in the encryption config\n\n```yaml\nkind: EncryptionConfiguration\napiVersion: apiserver.config.k8s.io/v1\nresources:\n  - resources:                                            # List of kubernetes resources that will be encrypted in etcd using the KMS plugin\n      - secrets\n    providers:\n      # kms provider with new key\n      - kms:\n          name: azurekmsprovider2\n          endpoint: unix:///opt/azurekms2.socket          # This endpoint must match the value defined in --listen-addr for the KMS plugin using new key\n          cachesize: 1000\n      # kms provider with old key\n      - kms:\n          name: azurekmsprovider\n          endpoint: unix:///opt/azurekms.socket           # This endpoint must match the value defined in --listen-addr for the KMS plugin using old key\n          cachesize: 1000\n```\n\n### 6. Restart all `kube-apiserver` again\n\nRefer to [step 4](#4-restart-all-kube-apiserver) to again restart the `kube-apiserver` for the encryption config changes to take effect.\n\n### 7. Decrypt and re-encrypt existing secrets with new key\n\nSince secrets are encrypted on write, performing an update on a secret will encrypt that content.\n\nRun `kubectl get secrets --all-namespaces -o json | kubectl replace -f -` to encrypt all existing secrets with the new key.\n\n> NOTE: For larger clusters, you may wish to subdivide the secrets by namespace or script an update.\n\n#### How does this work?\n\nThe 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.\n\n### 8. Remove the old provider from encryption configuration\n\nNow that all the secrets have been re-encrypted with the new key, we can safely remove the old kms provider from the encryption configuration.\n\n```yaml\nkind: EncryptionConfiguration\napiVersion: apiserver.config.k8s.io/v1\nresources:\n  - resources:                                            # List of kubernetes resources that will be encrypted in etcd using the KMS plugin\n      - secrets\n    providers:\n      # kms provider with new key\n      - kms:\n          name: azurekmsprovider2\n          endpoint: unix:///opt/azurekms2.socket          # This endpoint must match the value defined in --listen-addr for the KMS plugin using new key\n          cachesize: 1000\n```\n"
  },
  {
    "path": "docs/testing.md",
    "content": "# End-to-end testing for KMS Plugin for Keyvault\n\n## Prerequisites\n\nTo run tests locally, following components are required:\n\n1. [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)\n1. [bats](https://bats-core.readthedocs.io/en/latest/installation.html)\n1. [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)\n\nTo install the prerequisites, run the following command:\n\n```bash\nmake e2e-install-prerequisites\n```\n\nThe E2E test suite extracts runtime configurations through environment variables. Below is a list of environment variables to set before running the E2E test suite.\n| Variable            | Description                                                                                         |\n| ------------------- | --------------------------------------------------------------------------------------------------- |\n| AZURE_CLIENT_ID     | The client ID of your service principal that has `encrypt, decrypt` access to the keyvault key.     |\n| AZURE_CLIENT_SECRET | The client secret of your service principal that has `encrypt, decrypt` access to the keyvault key. |\n| AZURE_TENANT_ID     | The Azure tenant ID.                                                                                |\n| KEYVAULT_NAME       | The Azure Keyvault name.                                                                            |\n| KEY_NAME            | The name of Keyvault key that will be used by the kms plugin.                                       |\n| KEY_VERSION         | The version of Keyvault key that will be used by the kms plugin.                                    |\n\n## Running the tests\n\nThe 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.\n\n1. Setup cluster, registry and build image:\n\n```bash\nmake e2e-setup-kind\n```\n\n- This creates the local registry\n- Builds a kms plugin image with the latest changes and pushes to local registry\n- Creates a kind cluster with connectivity to local registry and kms plugin enabled with custom image\n\n1. Run the end-to-end tests:\n\n```bash\nmake e2e-test\n```\n\n1. To delete the kind cluster after running tests:\n\n```bash\nmake e2e-delete-kind\n```\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/Azure/kubernetes-kms\n\ngo 1.26.2\n\nrequire (\n\tgithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible\n\tgithub.com/Azure/go-autorest/autorest v0.11.28\n\tgithub.com/Azure/go-autorest/autorest/adal v0.9.23\n\tgo.opentelemetry.io/otel v1.43.0\n\tgo.opentelemetry.io/otel/exporters/prometheus v0.60.0\n\tgo.opentelemetry.io/otel/metric v1.43.0\n\tgolang.org/x/crypto v0.48.0\n\tgoogle.golang.org/grpc v1.79.3\n\tgopkg.in/yaml.v3 v3.0.1\n\tk8s.io/apimachinery v0.27.1\n\tk8s.io/klog/v2 v2.100.1\n\tk8s.io/kms v0.35.1\n\tmonis.app/mlog v0.0.4\n)\n\nrequire (\n\tgithub.com/Azure/go-autorest v14.2.0+incompatible // indirect\n\tgithub.com/Azure/go-autorest/autorest/date v0.3.0 // indirect\n\tgithub.com/Azure/go-autorest/autorest/to v0.4.0 // indirect\n\tgithub.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect\n\tgithub.com/Azure/go-autorest/logger v0.2.1 // indirect\n\tgithub.com/Azure/go-autorest/tracing v0.6.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.2.3 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.1 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.23.0\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.65.0 // indirect\n\tgithub.com/prometheus/otlptranslator v0.0.2 // indirect\n\tgithub.com/prometheus/procfs v0.17.0 // indirect\n\tgithub.com/spf13/cobra v1.6.1 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/stretchr/testify v1.11.1 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.43.0\n\tgo.opentelemetry.io/otel/trace v1.43.0 // indirect\n\tgo.uber.org/atomic v1.10.0 // indirect\n\tgo.uber.org/multierr v1.8.0 // indirect\n\tgo.uber.org/zap v1.24.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tk8s.io/component-base v0.27.1 // indirect\n\tk8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=\ngithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=\ngithub.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=\ngithub.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=\ngithub.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=\ngithub.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=\ngithub.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=\ngithub.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=\ngithub.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=\ngithub.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=\ngithub.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=\ngithub.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=\ngithub.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=\ngithub.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=\ngithub.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=\ngithub.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=\ngithub.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=\ngithub.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=\ngithub.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=\ngo.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=\ngo.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=\ngo.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=\ngo.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=\ngo.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=\ngo.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=\ngo.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=\ngo.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=\ngo.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=\ngo.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=\ngo.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=\ngo.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngo.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=\ngo.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc=\nk8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM=\nk8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM=\nk8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0=\nk8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=\nk8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/kms v0.35.1 h1:kjv2r9g1mY7uL+l1RhyAZvWVZIA/4qIfBHXyjFGLRhU=\nk8s.io/kms v0.35.1/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=\nk8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=\nk8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nmonis.app/mlog v0.0.4 h1:YEzh5sguG4ApywaRWnBU+mGP6SA4WxOqiJ36u+KtoeE=\nmonis.app/mlog v0.0.4/go.mod h1:LtOpnndFuRGqnLBwzBvpA1DaoKuud2/moLzYXIiNl1s=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=\nsigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=\nsigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=\n"
  },
  {
    "path": "pkg/auth/auth.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage auth\n\nimport (\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/config\"\n\t\"github.com/Azure/kubernetes-kms/pkg/consts\"\n\n\t\"github.com/Azure/go-autorest/autorest\"\n\t\"github.com/Azure/go-autorest/autorest/adal\"\n\t\"github.com/Azure/go-autorest/autorest/azure\"\n\t\"golang.org/x/crypto/pkcs12\"\n\t\"monis.app/mlog\"\n)\n\n// GetKeyvaultToken() returns token for Keyvault endpoint.\nfunc GetKeyvaultToken(config *config.AzureConfig, env *azure.Environment, resource string, proxyMode bool) (authorizer autorest.Authorizer, err error) {\n\tservicePrincipalToken, err := GetServicePrincipalToken(config, env.ActiveDirectoryEndpoint, resource, proxyMode)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tauthorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)\n\treturn authorizer, nil\n}\n\n// GetServicePrincipalToken creates a new service principal token based on the configuration.\nfunc GetServicePrincipalToken(config *config.AzureConfig, aadEndpoint, resource string, proxyMode bool) (adal.OAuthTokenProvider, error) {\n\toauthConfig, err := adal.NewOAuthConfig(aadEndpoint, config.TenantID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create OAuth config, error: %w\", err)\n\t}\n\n\tif config.UseManagedIdentityExtension {\n\t\tmlog.Info(\"using managed identity extension to retrieve access token\")\n\t\tmsiEndpoint, err := adal.GetMSIVMEndpoint()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get managed service identity endpoint, error: %w\", err)\n\t\t}\n\t\t// using user-assigned managed identity to access keyvault\n\t\tif len(config.UserAssignedIdentityID) > 0 {\n\t\t\tmlog.Info(\"using User-assigned managed identity to retrieve access token\", \"clientID\", redactClientCredentials(config.UserAssignedIdentityID))\n\t\t\treturn adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint,\n\t\t\t\tresource,\n\t\t\t\tconfig.UserAssignedIdentityID)\n\t\t}\n\t\tmlog.Info(\"using system-assigned managed identity to retrieve access token\")\n\t\t// using system-assigned managed identity to access keyvault\n\t\treturn adal.NewServicePrincipalTokenFromMSI(\n\t\t\tmsiEndpoint,\n\t\t\tresource)\n\t}\n\n\tif len(config.ClientSecret) > 0 && len(config.ClientID) > 0 {\n\t\tmlog.Info(\"azure: using client_id+client_secret to retrieve access token\",\n\t\t\t\"clientID\", redactClientCredentials(config.ClientID), \"clientSecret\", redactClientCredentials(config.ClientSecret))\n\n\t\tspt, err := adal.NewServicePrincipalToken(\n\t\t\t*oauthConfig,\n\t\t\tconfig.ClientID,\n\t\t\tconfig.ClientSecret,\n\t\t\tresource)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif proxyMode {\n\t\t\treturn addTargetTypeHeader(spt), nil\n\t\t}\n\t\treturn spt, nil\n\t}\n\n\tif len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {\n\t\tmlog.Info(\"using jwt client_assertion (client_cert+client_private_key) to retrieve access token\")\n\t\tcertData, err := os.ReadFile(config.AADClientCertPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read client certificate from file %s, error: %w\", config.AADClientCertPath, err)\n\t\t}\n\t\tcertificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode the client certificate, error: %w\", err)\n\t\t}\n\t\tspt, err := adal.NewServicePrincipalTokenFromCertificate(\n\t\t\t*oauthConfig,\n\t\t\tconfig.ClientID,\n\t\t\tcertificate,\n\t\t\tprivateKey,\n\t\t\tresource)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif proxyMode {\n\t\t\treturn addTargetTypeHeader(spt), nil\n\t\t}\n\t\treturn spt, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"no credentials provided for accessing keyvault\")\n}\n\n// ParseAzureEnvironment returns azure environment by name.\nfunc ParseAzureEnvironment(cloudName string) (*azure.Environment, error) {\n\tvar env azure.Environment\n\tvar err error\n\tif cloudName == \"\" {\n\t\tenv = azure.PublicCloud\n\t} else {\n\t\tenv, err = azure.EnvironmentFromName(cloudName)\n\t}\n\treturn &env, err\n}\n\n// decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and\n// the private RSA key.\nfunc decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {\n\tprivateKey, certificate, err := pkcs12.Decode(pkcs, password)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"decoding the PKCS#12 client certificate: %w\", err)\n\t}\n\trsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)\n\tif !isRsaKey {\n\t\treturn nil, nil, fmt.Errorf(\"PKCS#12 certificate must contain a RSA private key\")\n\t}\n\n\treturn certificate, rsaPrivateKey, nil\n}\n\n// redactClientCredentials applies regex to a sensitive string and return the redacted value.\nfunc redactClientCredentials(sensitiveString string) string {\n\tr := regexp.MustCompile(`^(\\S{4})(\\S|\\s)*(\\S{4})$`)\n\treturn r.ReplaceAllString(sensitiveString, \"$1##### REDACTED #####$3\")\n}\n\n// addTargetTypeHeader adds the target header if proxy mode is enabled.\nfunc addTargetTypeHeader(spt *adal.ServicePrincipalToken) *adal.ServicePrincipalToken {\n\tspt.SetSender(autorest.CreateSender(\n\t\t(func() autorest.SendDecorator {\n\t\t\treturn func(s autorest.Sender) autorest.Sender {\n\t\t\t\treturn autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {\n\t\t\t\t\tr.Header.Set(consts.RequestHeaderTargetType, consts.TargetTypeAzureActiveDirectory)\n\t\t\t\t\treturn s.Do(r)\n\t\t\t\t})\n\t\t\t}\n\t\t})()))\n\treturn spt\n}\n"
  },
  {
    "path": "pkg/auth/auth_test.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage auth\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/config\"\n\n\t\"github.com/Azure/go-autorest/autorest/adal\"\n\t\"github.com/Azure/go-autorest/autorest/azure\"\n)\n\nfunc TestParseAzureEnvironment(t *testing.T) {\n\tenvNamesArray := []string{\"AZURECHINACLOUD\", \"AZUREGERMANCLOUD\", \"AZUREPUBLICCLOUD\", \"AZUREUSGOVERNMENTCLOUD\", \"\"}\n\tfor _, envName := range envNamesArray {\n\t\tazureEnv, err := ParseAzureEnvironment(envName)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"expected no error, got %v\", err)\n\t\t}\n\t\tif strings.EqualFold(envName, \"\") && !strings.EqualFold(azureEnv.Name, \"AZUREPUBLICCLOUD\") {\n\t\t\tt.Fatalf(\"string doesn't match, expected AZUREPUBLICCLOUD, got %s\", azureEnv.Name)\n\t\t} else if !strings.EqualFold(envName, \"\") && !strings.EqualFold(envName, azureEnv.Name) {\n\t\t\tt.Fatalf(\"string doesn't match, expected %s, got %s\", envName, azureEnv.Name)\n\t\t}\n\t}\n\n\twrongEnvName := \"AZUREWRONGCLOUD\"\n\t_, err := ParseAzureEnvironment(wrongEnvName)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error for wrong azure environment name\")\n\t}\n}\n\nfunc TestRedactClientCredentials(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tclientID string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"should redact client id\",\n\t\t\tclientID: \"aabc0000-a83v-9h4m-000j-2c0a66b0c1f9\",\n\t\t\texpected: \"aabc##### REDACTED #####c1f9\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactual := redactClientCredentials(test.clientID)\n\t\t\tif actual != test.expected {\n\t\t\t\tt.Fatalf(\"expected: %s, got %s\", test.expected, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tconfig    *config.AzureConfig\n\t\tproxyMode bool // The proxy mode doesn't matter if user-assigned managed identity is used to get service principal token\n\t}{\n\t\t{\n\t\t\tname: \"using user-assigned managed identity to access keyvault\",\n\t\t\tconfig: &config.AzureConfig{\n\t\t\t\tUseManagedIdentityExtension: true,\n\t\t\t\tUserAssignedIdentityID:      \"clientID\",\n\t\t\t\tTenantID:                    \"TenantID\",\n\t\t\t\tClientID:                    \"AADClientID\",\n\t\t\t\tClientSecret:                \"AADClientSecret\",\n\t\t\t},\n\t\t\tproxyMode: false,\n\t\t},\n\t\t// The Azure service principal is ignored when\n\t\t// UseManagedIdentityExtension is set to true\n\t\t{\n\t\t\tname: \"using user-assigned managed identity over service principal if set to true\",\n\t\t\tconfig: &config.AzureConfig{\n\t\t\t\tUseManagedIdentityExtension: true,\n\t\t\t\tUserAssignedIdentityID:      \"clientID\",\n\t\t\t},\n\t\t\tproxyMode: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttoken, err := GetServicePrincipalToken(test.config, \"https://login.microsoftonline.com/\", \"https://vault.azure.net\", test.proxyMode)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tmsiEndpoint, err := adal.GetMSIVMEndpoint()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tspt, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, \"https://vault.azure.net\", \"clientID\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(token, spt) {\n\t\t\t\tt.Fatalf(\"expected: %v, got: %v\", spt, token)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetServicePrincipalTokenFromMSI(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tconfig    *config.AzureConfig\n\t\tproxyMode bool // The proxy mode doesn't matter if MSI is used to get service principal token\n\t}{\n\t\t{\n\t\t\tname: \"using system-assigned managed identity to access keyvault\",\n\t\t\tconfig: &config.AzureConfig{\n\t\t\t\tUseManagedIdentityExtension: true,\n\t\t\t},\n\t\t\tproxyMode: false,\n\t\t},\n\t\t// The Azure service principal is ignored when\n\t\t// UseManagedIdentityExtension is set to true\n\t\t{\n\t\t\tname: \"using system-assigned managed identity over service principal if set to true\",\n\t\t\tconfig: &config.AzureConfig{\n\t\t\t\tUseManagedIdentityExtension: true,\n\t\t\t\tTenantID:                    \"TenantID\",\n\t\t\t\tClientID:                    \"AADClientID\",\n\t\t\t\tClientSecret:                \"AADClientSecret\",\n\t\t\t},\n\t\t\tproxyMode: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttoken, err := GetServicePrincipalToken(test.config, \"https://login.microsoftonline.com/\", \"https://vault.azure.net\", test.proxyMode)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tmsiEndpoint, err := adal.GetMSIVMEndpoint()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tspt, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, \"https://vault.azure.net\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(token, spt) {\n\t\t\t\tt.Fatalf(\"expected: %v, got: %v\", spt, token)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetServicePrincipalToken(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tconfig *config.AzureConfig\n\t}{\n\t\t{\n\t\t\tname: \"using service-principal credentials to access keyvault\",\n\t\t\tconfig: &config.AzureConfig{\n\t\t\t\tTenantID:     \"TenantID\",\n\t\t\t\tClientID:     \"AADClientID\",\n\t\t\t\tClientSecret: \"AADClientSecret\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttoken, err := GetServicePrincipalToken(test.config, \"https://login.microsoftonline.com/\", \"https://vault.azure.net\", false)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tenv := &azure.PublicCloud\n\n\t\t\toauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, test.config.TenantID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tspt, err := adal.NewServicePrincipalToken(*oauthConfig, test.config.ClientID, test.config.ClientSecret, \"https://vault.azure.net\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(token, spt) {\n\t\t\t\tt.Fatalf(\"expected: %+v, got: %+v\", spt, token)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/config/azure_config.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"gopkg.in/yaml.v3\"\n\t\"monis.app/mlog\"\n)\n\n// AzureConfig is representing /etc/kubernetes/azure.json.\ntype AzureConfig struct {\n\tCloud                       string `json:\"cloud\" yaml:\"cloud\"`\n\tTenantID                    string `json:\"tenantId\" yaml:\"tenantId\"`\n\tClientID                    string `json:\"aadClientId\" yaml:\"aadClientId\"`\n\tClientSecret                string `json:\"aadClientSecret\" yaml:\"aadClientSecret\"`\n\tUseManagedIdentityExtension bool   `json:\"useManagedIdentityExtension,omitempty\" yaml:\"useManagedIdentityExtension,omitempty\"`\n\tUserAssignedIdentityID      string `json:\"userAssignedIdentityID,omitempty\" yaml:\"userAssignedIdentityID,omitempty\"`\n\tAADClientCertPath           string `json:\"aadClientCertPath\" yaml:\"aadClientCertPath\"`\n\tAADClientCertPassword       string `json:\"aadClientCertPassword\" yaml:\"aadClientCertPassword\"`\n}\n\n// GetAzureConfig returns configs in the azure.json cloud provider file.\nfunc GetAzureConfig(configFile string) (config *AzureConfig, err error) {\n\tcfg := AzureConfig{}\n\n\tmlog.Trace(\"populating AzureConfig from config file\", \"configFile\", configFile)\n\tbytes, err := os.ReadFile(configFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load config file %s, error: %w\", configFile, err)\n\t}\n\tif err = yaml.Unmarshal(bytes, &cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal azure.json, error: %w\", err)\n\t}\n\treturn &cfg, nil\n}\n"
  },
  {
    "path": "pkg/consts/consts.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage consts\n\nconst (\n\t// In proxy mode, the header is added into the requests from kms-plugin.\n\t// The proxy will check the header and forward the request to different destinations.\n\t// e.g. When the value of the header \"x-azure-proxy-target\" is \"KeyVault\", the request\n\t// is forwared to Azure Key Vault by the proxy.\n\tRequestHeaderTargetType        = \"x-azure-proxy-target\"\n\tTargetTypeAzureActiveDirectory = \"AzureActiveDirectory\"\n\tTargetTypeKeyVault             = \"KeyVault\"\n)\n"
  },
  {
    "path": "pkg/metrics/exporter.go",
    "content": "package metrics\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"monis.app/mlog\"\n)\n\nconst (\n\tprometheusExporter = \"prometheus\"\n)\n\n// InitMetricsExporter initializes new exporter.\nfunc InitMetricsExporter(metricsBackend, metricsAddress string) error {\n\texporter := strings.ToLower(metricsBackend)\n\tmlog.Always(\"metrics backend\", \"exporter\", exporter)\n\n\tswitch exporter {\n\t// Prometheus is the only exporter supported for now\n\tcase prometheusExporter:\n\t\treturn initPrometheusExporter(metricsAddress)\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported metrics backend %v\", metricsBackend)\n\t}\n}\n"
  },
  {
    "path": "pkg/metrics/exporter_test.go",
    "content": "package metrics\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestInitMetricsExporter(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tmetricsBackend string\n\t\tmetricsAddress string\n\t\texpectedError  bool\n\t}{\n\t\t{\n\t\t\tname:           \"With_Prometheus_Backend\",\n\t\t\tmetricsBackend: \"prometheus\",\n\t\t\tmetricsAddress: \"8095\",\n\t\t\texpectedError:  false,\n\t\t},\n\t\t{\n\t\t\tname:           \"With_Non_Prometheus_Backend\",\n\t\t\tmetricsBackend: \"nonprometheus\",\n\t\t\texpectedError:  true,\n\t\t},\n\t\t{\n\t\t\tname:           \"With_Uppercase_Backend_Name\",\n\t\t\tmetricsBackend: \"Prometheus\",\n\t\t\tmetricsAddress: \"8096\",\n\t\t\texpectedError:  false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\terr := InitMetricsExporter(testCase.metricsBackend, testCase.metricsAddress)\n\n\t\t\tif testCase.expectedError && err == nil || !testCase.expectedError && err != nil {\n\t\t\t\tt.Fatalf(\"expected error: %v, found: %v\", testCase.expectedError, err)\n\t\t\t}\n\n\t\t\t// Reset handler to test /metrics  repeatedly.\n\t\t\thttp.DefaultServeMux = new(http.ServeMux)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/metrics/prometheus_exporter.go",
    "content": "package metrics\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\tpromclient \"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/prometheus\"\n\tsdkmetric \"go.opentelemetry.io/otel/sdk/metric\"\n\t\"monis.app/mlog\"\n)\n\nconst (\n\tmetricsEndpoint = \"metrics\"\n)\n\nfunc initPrometheusExporter(metricsAddress string) error {\n\tregistry := promclient.NewRegistry()\n\texporter, err := prometheus.New(\n\t\tprometheus.WithRegisterer(registry))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmp := sdkmetric.NewMeterProvider(\n\t\tsdkmetric.WithReader(exporter),\n\t\tsdkmetric.WithView(sdkmetric.NewView(\n\t\t\tsdkmetric.Instrument{Kind: sdkmetric.InstrumentKindHistogram},\n\t\t\tsdkmetric.Stream{\n\t\t\t\tAggregation: sdkmetric.AggregationExplicitBucketHistogram{\n\t\t\t\t\tBoundaries: promclient.ExponentialBucketsRange(0.1, 2, 11),\n\t\t\t\t},\n\t\t\t},\n\t\t)),\n\t)\n\totel.SetMeterProvider(mp)\n\n\thttp.Handle(fmt.Sprintf(\"/%s\", metricsEndpoint), promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))\n\tgo func() {\n\t\tserver := &http.Server{\n\t\t\tAddr:              fmt.Sprintf(\":%s\", metricsAddress),\n\t\t\tReadHeaderTimeout: 5 * time.Second,\n\t\t}\n\t\tif err := server.ListenAndServe(); err != nil {\n\t\t\tmlog.Fatal(err, \"failed to register prometheus endpoint\", \"metricsAddress\", metricsAddress)\n\t\t}\n\t}()\n\tmlog.Always(\"Prometheus metrics server running\", \"address\", metricsAddress)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/metrics/stats_reporter.go",
    "content": "package metrics\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/metric\"\n)\n\nconst (\n\tinstrumentationName  = \"keyvaultkms\"\n\terrorMessageKey      = \"error_message\"\n\tstatusTypeKey        = \"status\"\n\toperationTypeKey     = \"operation\"\n\tkmsRequestMetricName = \"kms_request\"\n\t// ErrorStatusTypeValue sets status tag to \"error\".\n\tErrorStatusTypeValue = \"error\"\n\t// SuccessStatusTypeValue sets status tag to \"success\".\n\tSuccessStatusTypeValue = \"success\"\n\t// EncryptOperationTypeValue sets operation tag to \"encrypt\".\n\tEncryptOperationTypeValue = \"encrypt\"\n\t// DecryptOperationTypeValue sets operation tag to \"decrypt\".\n\tDecryptOperationTypeValue = \"decrypt\"\n\t// GrpcOperationTypeValue sets operation tag to \"grpc\".\n\tGrpcOperationTypeValue = \"grpc\"\n)\n\ntype reporter struct {\n\thistogram metric.Float64Histogram\n}\n\n// StatsReporter reports metrics.\ntype StatsReporter interface {\n\tReportRequest(ctx context.Context, operationType, status string, duration float64, errors ...string)\n}\n\n// NewStatsReporter instantiates otel reporter.\nfunc NewStatsReporter() (StatsReporter, error) {\n\tmeter := otel.GetMeterProvider().Meter(instrumentationName)\n\n\tmetricCounter, err := meter.Float64Histogram(\n\t\tkmsRequestMetricName,\n\t\tmetric.WithDescription(\"Distribution of how long it took for an operation\"),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &reporter{\n\t\thistogram: metricCounter,\n\t}, nil\n}\n\nfunc (r *reporter) ReportRequest(ctx context.Context, operationType, status string, duration float64, errors ...string) {\n\tlabels := []attribute.KeyValue{\n\t\tattribute.String(operationTypeKey, operationType),\n\t\tattribute.String(statusTypeKey, status),\n\t}\n\n\t// Add errors\n\tif (status == ErrorStatusTypeValue) && len(errors) > 0 {\n\t\tfor _, err := range errors {\n\t\t\tlabels = append(labels, attribute.String(errorMessageKey, err))\n\t\t}\n\t}\n\n\tr.histogram.Record(ctx, duration, metric.WithAttributes(labels...))\n}\n"
  },
  {
    "path": "pkg/plugin/healthz.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"k8s.io/apimachinery/pkg/util/uuid\"\n\tkmsv1 \"k8s.io/kms/apis/v1beta1\"\n\tkmsv2 \"k8s.io/kms/apis/v2\"\n\t\"monis.app/mlog\"\n)\n\nconst (\n\thealthCheckPlainText = \"healthcheck\"\n)\n\n// HealthZ is the health check server for the KMS plugin.\ntype HealthZ struct {\n\tKMSv1Server    *KeyManagementServiceServer\n\tKMSv2Server    *KeyManagementServiceV2Server\n\tHealthCheckURL *url.URL\n\tUnixSocketPath string\n\tRPCTimeout     time.Duration\n}\n\n// Serve creates the http handler for serving health requests.\nfunc (h *HealthZ) Serve() {\n\tserveMux := http.NewServeMux()\n\tserveMux.HandleFunc(h.HealthCheckURL.EscapedPath(), h.ServeHTTP)\n\tserver := &http.Server{\n\t\tAddr:              h.HealthCheckURL.Host,\n\t\tReadHeaderTimeout: 5 * time.Second,\n\t\tHandler:           serveMux,\n\t}\n\tif err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\tmlog.Fatal(err, \"failed to start health check server\", \"url\", h.HealthCheckURL.String())\n\t}\n}\n\nfunc (h *HealthZ) ServeHTTP(w http.ResponseWriter, _ *http.Request) {\n\tmlog.Trace(\"Started health check\")\n\tctx, cancel := context.WithTimeout(context.Background(), h.RPCTimeout)\n\tdefer cancel()\n\n\tconn, err := h.dialUnixSocket()\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusServiceUnavailable)\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\t// create the kms client for v1\n\tkmsClient := kmsv1.NewKeyManagementServiceClient(conn)\n\n\t// create the kms client for v2\n\tkmsV2Client := kmsv2.NewKeyManagementServiceClient(conn)\n\n\t// check version response against KMS-Plugin's gRPC endpoint.\n\terr = h.checkRPC(ctx, kmsClient, kmsV2Client)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusServiceUnavailable)\n\t\treturn\n\t}\n\n\t// Both encryption and decryption calls are made for each version,\n\t// resulting in a total of 4 calls to the keyvault.\n\t// Additionally, a health check is performed every 10 seconds.\n\n\t// v1 checks\n\t// check the configured keyvault, key, key version and permissions are still\n\t// valid to encrypt and decrypt with test data.\n\tenc, err := h.KMSv1Server.Encrypt(ctx, &kmsv1.EncryptRequest{Plain: []byte(healthCheckPlainText)})\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdec, err := h.KMSv1Server.Decrypt(ctx, &kmsv1.DecryptRequest{Cipher: enc.Cipher})\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tif string(dec.Plain) != healthCheckPlainText {\n\t\thttp.Error(w, \"plain text mismatch after decryption\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// v2 checks.\n\t// appending a string to UUID allows us to differentiate the UUIDs generated by us from those generated by the API server.\n\tuid := \"local-healthz-check-\" + string(uuid.NewUUID())\n\n\tv2EncryptResponse, err := h.KMSv2Server.Encrypt(\n\t\tctx,\n\t\t&kmsv2.EncryptRequest{\n\t\t\tPlaintext: []byte(healthCheckPlainText),\n\t\t\tUid:       uid,\n\t\t},\n\t)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tv2DecryptResponse, err := h.KMSv2Server.Decrypt(ctx, &kmsv2.DecryptRequest{\n\t\tCiphertext:  v2EncryptResponse.Ciphertext,\n\t\tKeyId:       v2EncryptResponse.KeyId,\n\t\tUid:         uid, // passing the same uid to track roundtrip encrypt/decrypt calls\n\t\tAnnotations: v2EncryptResponse.Annotations,\n\t})\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif string(v2DecryptResponse.Plaintext) != healthCheckPlainText {\n\t\thttp.Error(w, \"plain text mismatch after decryption with KMSv2\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n\tif _, err = w.Write([]byte(\"ok\")); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tmlog.Trace(\"Completed health check\")\n}\n\n// checkRPC initiates a grpc request to validate the socket is responding\n// sends a KMS VersionRequest and checks if the VersionResponse is valid.\nfunc (h *HealthZ) checkRPC(\n\tctx context.Context,\n\tkmsV1Client kmsv1.KeyManagementServiceClient,\n\tkmsV2Client kmsv2.KeyManagementServiceClient,\n) error {\n\tv, err := kmsV1Client.Version(ctx, &kmsv1.VersionRequest{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif v.Version != version.KMSv1APIVersion || v.RuntimeName != version.Runtime || v.RuntimeVersion != version.BuildVersion {\n\t\treturn fmt.Errorf(\"failed to get correct version response\")\n\t}\n\n\tv2Status, err := kmsV2Client.Status(ctx, &kmsv2.StatusRequest{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif v2Status.Version != version.KMSv2APIVersion {\n\t\treturn fmt.Errorf(\n\t\t\t\"failed to get correct version response for v2 expected: %s, got: %s\",\n\t\t\tversion.KMSv2APIVersion,\n\t\t\tv2Status.Version,\n\t\t)\n\t}\n\n\treturn nil\n}\n\nfunc (h *HealthZ) dialUnixSocket() (*grpc.ClientConn, error) {\n\treturn grpc.NewClient(\"unix://\"+h.UnixSocketPath,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()))\n}\n"
  },
  {
    "path": "pkg/plugin/healthz_test.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\tmockkeyvault \"github.com/Azure/kubernetes-kms/pkg/plugin/mock_keyvault\"\n\n\t\"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault\"\n\t\"google.golang.org/grpc\"\n\tkmsv1 \"k8s.io/kms/apis/v1beta1\"\n\tkmsv2 \"k8s.io/kms/apis/v2\"\n\t\"monis.app/mlog\"\n)\n\nfunc TestServe(t *testing.T) {\n\ttests := []struct {\n\t\tdesc                   string\n\t\tsetEncryptResponse     string\n\t\tsetDecryptResponse     string\n\t\tsetEncryptError        error\n\t\tsetDecryptError        error\n\t\texpectedHTTPStatusCode int\n\t}{\n\t\t{\n\t\t\tdesc:                   \"failed to encrypt in health check\",\n\t\t\tsetEncryptResponse:     \"\",\n\t\t\tsetEncryptError:        fmt.Errorf(\"failed to encrypt\"),\n\t\t\texpectedHTTPStatusCode: http.StatusServiceUnavailable,\n\t\t},\n\t\t{\n\t\t\tdesc:                   \"failed to decrypt in health check\",\n\t\t\tsetEncryptResponse:     \"\",\n\t\t\tsetEncryptError:        nil,\n\t\t\tsetDecryptResponse:     \"\",\n\t\t\tsetDecryptError:        fmt.Errorf(\"failed to decrypt\"),\n\t\t\texpectedHTTPStatusCode: http.StatusServiceUnavailable,\n\t\t},\n\t\t{\n\t\t\tdesc:                   \"encrypt-decrypt mismatch\",\n\t\t\tsetEncryptResponse:     \"bar\",\n\t\t\tsetEncryptError:        nil,\n\t\t\tsetDecryptResponse:     \"foo\",\n\t\t\tsetDecryptError:        nil,\n\t\t\texpectedHTTPStatusCode: http.StatusServiceUnavailable,\n\t\t},\n\t\t{\n\t\t\tdesc:                   \"successful health check\",\n\t\t\tsetEncryptResponse:     \"bar\",\n\t\t\tsetDecryptResponse:     \"healthcheck\",\n\t\t\texpectedHTTPStatusCode: http.StatusOK,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tsocketPath := fmt.Sprintf(\"%s/kms.sock\", getTempTestDir(t))\n\t\t\tdefer os.Remove(socketPath)\n\n\t\t\tfakeKMSServer, fakeKMSV2Server, mockKVClient, err := setupFakeKMSServer(socketPath)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create fake kms server, err: %+v\", err)\n\t\t\t}\n\n\t\t\tmockKVClient.SetEncryptResponse([]byte(test.setEncryptResponse), test.setEncryptError)\n\t\t\tmockKVClient.SetDecryptResponse([]byte(test.setDecryptResponse), test.setDecryptError)\n\n\t\t\thealthz := &HealthZ{\n\t\t\t\tKMSv1Server:    fakeKMSServer,\n\t\t\t\tKMSv2Server:    fakeKMSV2Server,\n\t\t\t\tUnixSocketPath: socketPath,\n\t\t\t\tRPCTimeout:     20 * time.Second,\n\t\t\t\tHealthCheckURL: &url.URL{\n\t\t\t\t\tScheme: \"http\",\n\t\t\t\t\tHost:   net.JoinHostPort(\"localhost\", \"8080\"),\n\t\t\t\t\tPath:   \"/healthz\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tserver := httptest.NewServer(healthz)\n\t\t\tdefer server.Close()\n\n\t\t\trespCode, body := doHealthCheck(t, server.URL)\n\t\t\tif respCode != test.expectedHTTPStatusCode {\n\t\t\t\tt.Fatalf(\"expected status code: %v, got: %v\", test.expectedHTTPStatusCode, respCode)\n\t\t\t}\n\t\t\tif test.expectedHTTPStatusCode == http.StatusOK && string(body) != \"ok\" {\n\t\t\t\tt.Fatalf(\"expected response body to be 'ok', got: %s\", string(body))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckRPC(t *testing.T) {\n\tsocketPath := fmt.Sprintf(\"%s/kms.sock\", getTempTestDir(t))\n\tdefer os.Remove(socketPath)\n\n\tfakeKMSV1Server, fakeKMSV2Server, mockKVClient, err := setupFakeKMSServer(socketPath)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create fake kms server, err: %+v\", err)\n\t}\n\thealthz := &HealthZ{\n\t\tKMSv1Server:    fakeKMSV1Server,\n\t\tKMSv2Server:    fakeKMSV2Server,\n\t\tUnixSocketPath: socketPath,\n\t}\n\tmockKVClient.SetEncryptResponse([]byte(healthCheckPlainText), nil)\n\tmockKVClient.SetDecryptResponse([]byte(healthCheckPlainText), nil)\n\n\tconn, err := healthz.dialUnixSocket()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create connection, err: %+v\", err)\n\t}\n\n\terr = healthz.checkRPC(\n\t\tcontext.TODO(),\n\t\tkmsv1.NewKeyManagementServiceClient(conn),\n\t\tkmsv2.NewKeyManagementServiceClient(conn),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"expected err to be nil, got: %+v\", err)\n\t}\n}\n\nfunc getTempTestDir(t *testing.T) string {\n\ttmpDir, err := os.MkdirTemp(\"\", \"ut\")\n\tif err != nil {\n\t\tt.Fatalf(\"expected err to be nil, got: %+v\", err)\n\t}\n\treturn tmpDir\n}\n\nfunc setupFakeKMSServer(socketPath string) (\n\t*KeyManagementServiceServer,\n\t*KeyManagementServiceV2Server,\n\t*mockkeyvault.KeyVaultClient,\n\terror,\n) {\n\tlistener, err := net.Listen(\"unix\", socketPath)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tstatsReporter, err := metrics.NewStatsReporter()\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tkvClient := &mockkeyvault.KeyVaultClient{\n\t\tKeyID:     \"mock-key-id\",\n\t\tAlgorithm: keyvault.RSA15,\n\t}\n\tfakeKMSV1Server := &KeyManagementServiceServer{\n\t\tkvClient: kvClient,\n\t\treporter: statsReporter,\n\t}\n\n\tfakeKMSV2Server := &KeyManagementServiceV2Server{\n\t\tkvClient: kvClient,\n\t\treporter: statsReporter,\n\t}\n\n\ts := grpc.NewServer()\n\tkmsv1.RegisterKeyManagementServiceServer(s, fakeKMSV1Server)\n\tkmsv2.RegisterKeyManagementServiceServer(s, fakeKMSV2Server)\n\tgo func() {\n\t\tif err := s.Serve(listener); err != nil {\n\t\t\tmlog.Fatal(err, \"failed to serve fake kms server\")\n\t\t}\n\t}()\n\n\treturn fakeKMSV1Server, fakeKMSV2Server, kvClient, nil\n}\n\nfunc doHealthCheck(t *testing.T, url string) (int, []byte) {\n\treq, err := http.NewRequest(http.MethodGet, url, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create new http request, err: %+v\", err)\n\t}\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to invoke http request, err: %+v\", err)\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read response body, err: %+v\", err)\n\t}\n\treturn resp.StatusCode, body\n}\n"
  },
  {
    "path": "pkg/plugin/keyvault.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"path\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/auth\"\n\t\"github.com/Azure/kubernetes-kms/pkg/config\"\n\t\"github.com/Azure/kubernetes-kms/pkg/consts\"\n\t\"github.com/Azure/kubernetes-kms/pkg/utils\"\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\n\tkv \"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault\"\n\t\"github.com/Azure/go-autorest/autorest\"\n\t\"github.com/Azure/go-autorest/autorest/azure\"\n\t\"k8s.io/kms/pkg/service\"\n\t\"monis.app/mlog\"\n)\n\n// encryptionResponseVersion is validated prior to decryption.\n// This is helpful in case we want to change anything about the data we send in the future.\nvar encryptionResponseVersion = \"1\"\n\nconst (\n\tdateAnnotationKey             = \"date.azure.akv.io\"\n\trequestIDAnnotationKey        = \"x-ms-request-id.azure.akv.io\"\n\tkeyvaultRegionAnnotationKey   = \"x-ms-keyvault-region.azure.akv.io\"\n\tversionAnnotationKey          = \"version.azure.akv.io\"\n\talgorithmAnnotationKey        = \"algorithm.azure.akv.io\"\n\tdateAnnotationValue           = \"Date\"\n\trequestIDAnnotationValue      = \"X-Ms-Request-Id\"\n\tkeyvaultRegionAnnotationValue = \"X-Ms-Keyvault-Region\"\n)\n\n// Client interface for interacting with Keyvault.\ntype Client interface {\n\tEncrypt(\n\t\tctx context.Context,\n\t\tplain []byte,\n\t\tencryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm,\n\t) (*service.EncryptResponse, error)\n\tDecrypt(\n\t\tctx context.Context,\n\t\tcipher []byte,\n\t\tencryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm,\n\t\tapiVersion string,\n\t\tannotations map[string][]byte,\n\t\tdecryptRequestKeyID string,\n\t) ([]byte, error)\n\tGetUserAgent() string\n\tGetVaultURL() string\n}\n\n// KeyVaultClient is a client for interacting with Keyvault.\ntype KeyVaultClient struct {\n\tbaseClient       kv.BaseClient\n\tconfig           *config.AzureConfig\n\tvaultName        string\n\tkeyName          string\n\tkeyVersion       string\n\tvaultURL         string\n\tkeyIDHash        string\n\tazureEnvironment *azure.Environment\n}\n\n// NewKeyVaultClient returns a new key vault client to use for kms operations.\nfunc NewKeyVaultClient(\n\tconfig *config.AzureConfig,\n\tvaultName, keyName, keyVersion string,\n\tproxyMode bool,\n\tproxyAddress string,\n\tproxyPort int,\n\tmanagedHSM bool,\n) (Client, error) {\n\t// Sanitize vaultName, keyName, keyVersion. (https://github.com/Azure/kubernetes-kms/issues/85)\n\tvaultName = utils.SanitizeString(vaultName)\n\tkeyName = utils.SanitizeString(keyName)\n\tkeyVersion = utils.SanitizeString(keyVersion)\n\n\t// this should be the case for bring your own key, clusters bootstrapped with\n\t// aks-engine or aks and standalone kms plugin deployments\n\tif len(vaultName) == 0 || len(keyName) == 0 || len(keyVersion) == 0 {\n\t\treturn nil, fmt.Errorf(\"key vault name, key name and key version are required\")\n\t}\n\tkvClient := kv.New()\n\terr := kvClient.AddToUserAgent(version.GetUserAgent())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to add user agent to keyvault client, error: %w\", err)\n\t}\n\tenv, err := auth.ParseAzureEnvironment(config.Cloud)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse cloud environment: %s, error: %w\", config.Cloud, err)\n\t}\n\tif proxyMode {\n\t\tenv.ActiveDirectoryEndpoint = fmt.Sprintf(\"http://%s:%d/\", proxyAddress, proxyPort)\n\t}\n\n\tvaultResourceURL := getVaultResourceIdentifier(managedHSM, env)\n\tif vaultResourceURL == azure.NotAvailable {\n\t\treturn nil, fmt.Errorf(\"keyvault resource identifier not available for cloud: %s\", env.Name)\n\t}\n\ttoken, err := auth.GetKeyvaultToken(config, env, vaultResourceURL, proxyMode)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get key vault token, error: %w\", err)\n\t}\n\tkvClient.Authorizer = token\n\n\tvaultURL, err := getVaultURL(vaultName, managedHSM, env)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get vault url, error: %w\", err)\n\t}\n\n\tkeyIDHash, err := getKeyIDHash(*vaultURL, keyName, keyVersion)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get key id hash, error: %w\", err)\n\t}\n\n\tif proxyMode {\n\t\tkvClient.RequestInspector = autorest.WithHeader(consts.RequestHeaderTargetType, consts.TargetTypeKeyVault)\n\t\tvaultURL = getProxiedVaultURL(vaultURL, proxyAddress, proxyPort)\n\t}\n\n\tmlog.Always(\"using kms key for encrypt/decrypt\", \"vaultURL\", *vaultURL, \"keyName\", keyName, \"keyVersion\", keyVersion)\n\n\tclient := &KeyVaultClient{\n\t\tbaseClient:       kvClient,\n\t\tconfig:           config,\n\t\tvaultName:        vaultName,\n\t\tkeyName:          keyName,\n\t\tkeyVersion:       keyVersion,\n\t\tvaultURL:         *vaultURL,\n\t\tazureEnvironment: env,\n\t\tkeyIDHash:        keyIDHash,\n\t}\n\treturn client, nil\n}\n\n// Encrypt encrypts the given plain text using the keyvault key.\nfunc (kvc *KeyVaultClient) Encrypt(\n\tctx context.Context,\n\tplain []byte,\n\tencryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm,\n) (*service.EncryptResponse, error) {\n\tvalue := base64.RawURLEncoding.EncodeToString(plain)\n\n\tparams := kv.KeyOperationsParameters{\n\t\tAlgorithm: encryptionAlgorithm,\n\t\tValue:     &value,\n\t}\n\tresult, err := kvc.baseClient.Encrypt(ctx, kvc.vaultURL, kvc.keyName, kvc.keyVersion, params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to encrypt, error: %w\", err)\n\t}\n\n\tif kvc.keyIDHash != fmt.Sprintf(\"%x\", sha256.Sum256([]byte(*result.Kid))) {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"key id initialized does not match with the key id from encryption result, expected: %s, got: %s\",\n\t\t\tkvc.keyIDHash,\n\t\t\t*result.Kid,\n\t\t)\n\t}\n\n\tannotations := map[string][]byte{\n\t\tdateAnnotationKey:           []byte(result.Header.Get(dateAnnotationValue)),\n\t\trequestIDAnnotationKey:      []byte(result.Header.Get(requestIDAnnotationValue)),\n\t\tkeyvaultRegionAnnotationKey: []byte(result.Header.Get(keyvaultRegionAnnotationValue)),\n\t\tversionAnnotationKey:        []byte(encryptionResponseVersion),\n\t\talgorithmAnnotationKey:      []byte(encryptionAlgorithm),\n\t}\n\n\treturn &service.EncryptResponse{\n\t\tCiphertext:  []byte(*result.Result),\n\t\tKeyID:       kvc.keyIDHash,\n\t\tAnnotations: annotations,\n\t}, nil\n}\n\n// Decrypt decrypts the given cipher text using the keyvault key.\nfunc (kvc *KeyVaultClient) Decrypt(\n\tctx context.Context,\n\tcipher []byte,\n\tencryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm,\n\tapiVersion string,\n\tannotations map[string][]byte,\n\tdecryptRequestKeyID string,\n) ([]byte, error) {\n\tif apiVersion == version.KMSv2APIVersion {\n\t\terr := kvc.validateAnnotations(annotations, decryptRequestKeyID, encryptionAlgorithm)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvalue := string(cipher)\n\tparams := kv.KeyOperationsParameters{\n\t\tAlgorithm: encryptionAlgorithm,\n\t\tValue:     &value,\n\t}\n\n\tresult, err := kvc.baseClient.Decrypt(ctx, kvc.vaultURL, kvc.keyName, kvc.keyVersion, params)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decrypt, error: %w\", err)\n\t}\n\tbytes, err := base64.RawURLEncoding.DecodeString(*result.Result)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to base64 decode result, error: %w\", err)\n\t}\n\n\treturn bytes, nil\n}\n\nfunc (kvc *KeyVaultClient) GetUserAgent() string {\n\treturn kvc.baseClient.UserAgent\n}\n\nfunc (kvc *KeyVaultClient) GetVaultURL() string {\n\treturn kvc.vaultURL\n}\n\n// ValidateAnnotations validates following annotations before decryption:\n// - Algorithm.\n// - Version.\n// It also validates keyID that the API server checks.\nfunc (kvc *KeyVaultClient) validateAnnotations(\n\tannotations map[string][]byte,\n\tkeyID string,\n\tencryptionAlgorithm kv.JSONWebKeyEncryptionAlgorithm,\n) error {\n\tif len(annotations) == 0 {\n\t\treturn fmt.Errorf(\"invalid annotations, annotations cannot be empty\")\n\t}\n\n\tif keyID != kvc.keyIDHash {\n\t\treturn fmt.Errorf(\n\t\t\t\"key id %s does not match expected key id %s used for encryption\",\n\t\t\tkeyID,\n\t\t\tkvc.keyIDHash,\n\t\t)\n\t}\n\n\talgorithm := string(annotations[algorithmAnnotationKey])\n\tif algorithm != string(encryptionAlgorithm) {\n\t\treturn fmt.Errorf(\n\t\t\t\"algorithm %s does not match expected algorithm %s used for encryption\",\n\t\t\talgorithm,\n\t\t\tencryptionAlgorithm,\n\t\t)\n\t}\n\n\tversion := string(annotations[versionAnnotationKey])\n\tif version != encryptionResponseVersion {\n\t\treturn fmt.Errorf(\n\t\t\t\"version %s does not match expected version %s used for encryption\",\n\t\t\tversion,\n\t\t\tencryptionResponseVersion,\n\t\t)\n\t}\n\n\treturn nil\n}\n\nfunc getVaultURL(vaultName string, managedHSM bool, env *azure.Environment) (vaultURL *string, err error) {\n\t// Key Vault name must be a 3-24 character string\n\tif len(vaultName) < 3 || len(vaultName) > 24 {\n\t\treturn nil, fmt.Errorf(\"invalid vault name: %q, must be between 3 and 24 chars\", vaultName)\n\t}\n\n\t// See docs for validation spec: https://docs.microsoft.com/en-us/azure/key-vault/about-keys-secrets-and-certificates#objects-identifiers-and-versioning\n\tisValid := regexp.MustCompile(`^[-A-Za-z0-9]+$`).MatchString\n\tif !isValid(vaultName) {\n\t\treturn nil, fmt.Errorf(\"invalid vault name: %q, must match [-a-zA-Z0-9]{3,24}\", vaultName)\n\t}\n\n\tvaultDNSSuffixValue := getVaultDNSSuffix(managedHSM, env)\n\tif vaultDNSSuffixValue == azure.NotAvailable {\n\t\treturn nil, fmt.Errorf(\"vault dns suffix not available for cloud: %s\", env.Name)\n\t}\n\n\tvaultURI := fmt.Sprintf(\"https://%s.%s/\", vaultName, vaultDNSSuffixValue)\n\treturn &vaultURI, nil\n}\n\nfunc getProxiedVaultURL(vaultURL *string, proxyAddress string, proxyPort int) *string {\n\tproxiedVaultURL := fmt.Sprintf(\"http://%s:%d/%s\", proxyAddress, proxyPort, strings.TrimPrefix(*vaultURL, \"https://\"))\n\treturn &proxiedVaultURL\n}\n\nfunc getVaultDNSSuffix(managedHSM bool, env *azure.Environment) string {\n\tif managedHSM {\n\t\treturn env.ManagedHSMDNSSuffix\n\t}\n\treturn env.KeyVaultDNSSuffix\n}\n\nfunc getVaultResourceIdentifier(managedHSM bool, env *azure.Environment) string {\n\tif managedHSM {\n\t\treturn env.ResourceIdentifiers.ManagedHSM\n\t}\n\treturn env.ResourceIdentifiers.KeyVault\n}\n\nfunc getKeyIDHash(vaultURL, keyName, keyVersion string) (string, error) {\n\tif vaultURL == \"\" || keyName == \"\" || keyVersion == \"\" {\n\t\treturn \"\", fmt.Errorf(\"vault url, key name and key version cannot be empty\")\n\t}\n\n\tbaseURL, err := url.Parse(vaultURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse vault url, error: %w\", err)\n\t}\n\n\turlPath := path.Join(\"keys\", keyName, keyVersion)\n\tkeyID := baseURL.ResolveReference(\n\t\t&url.URL{\n\t\t\tPath: urlPath,\n\t\t},\n\t).String()\n\n\treturn fmt.Sprintf(\"%x\", sha256.Sum256([]byte(keyID))), nil\n}\n"
  },
  {
    "path": "pkg/plugin/keyvault_test.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/auth\"\n\t\"github.com/Azure/kubernetes-kms/pkg/config\"\n)\n\nvar (\n\ttestEnvs       = []string{\"\", \"AZUREPUBLICCLOUD\", \"AZURECHINACLOUD\", \"AZUREGERMANCLOUD\", \"AZUREUSGOVERNMENTCLOUD\"}\n\tvaultDNSSuffix = []string{\"vault.azure.net\", \"vault.azure.net\", \"vault.azure.cn\", \"vault.microsoftazure.de\", \"vault.usgovcloudapi.net\"}\n)\n\nfunc TestNewKeyVaultClientError(t *testing.T) {\n\ttests := []struct {\n\t\tdesc         string\n\t\tconfig       *config.AzureConfig\n\t\tvaultName    string\n\t\tkeyName      string\n\t\tkeyVersion   string\n\t\tproxyMode    bool\n\t\tproxyAddress string\n\t\tproxyPort    int\n\t\tmanagedHSM   bool\n\t}{\n\t\t{\n\t\t\tdesc:      \"vault name not provided\",\n\t\t\tconfig:    &config.AzureConfig{},\n\t\t\tproxyMode: false,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"key name not provided\",\n\t\t\tconfig:    &config.AzureConfig{},\n\t\t\tvaultName: \"testkv\",\n\t\t\tproxyMode: false,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"key version not provided\",\n\t\t\tconfig:    &config.AzureConfig{},\n\t\t\tvaultName: \"testkv\",\n\t\t\tkeyName:   \"k8s\",\n\t\t\tproxyMode: false,\n\t\t},\n\t\t{\n\t\t\tdesc:       \"no credentials in config\",\n\t\t\tconfig:     &config.AzureConfig{},\n\t\t\tvaultName:  \"testkv\",\n\t\t\tkeyName:    \"key1\",\n\t\t\tkeyVersion: \"262067a9e8ba401aa8a746c5f1a7e147\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"managed hsm not available in the azure environment\",\n\t\t\tconfig:     &config.AzureConfig{ClientID: \"clientid\", ClientSecret: \"clientsecret\", Cloud: \"AzureGermanCloud\"},\n\t\t\tvaultName:  \"testkv\",\n\t\t\tkeyName:    \"key1\",\n\t\t\tkeyVersion: \"262067a9e8ba401aa8a746c5f1a7e147\",\n\t\t\tmanagedHSM: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tif _, err := NewKeyVaultClient(test.config, test.vaultName, test.keyName, test.keyVersion, test.proxyMode, test.proxyAddress, test.proxyPort, test.managedHSM); err == nil {\n\t\t\t\tt.Fatalf(\"newKeyVaultClient() expected error, got nil\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewKeyVaultClient(t *testing.T) {\n\ttests := []struct {\n\t\tdesc             string\n\t\tconfig           *config.AzureConfig\n\t\tvaultName        string\n\t\tkeyName          string\n\t\tkeyVersion       string\n\t\tproxyMode        bool\n\t\tproxyAddress     string\n\t\tproxyPort        int\n\t\tmanagedHSM       bool\n\t\texpectedVaultURL string\n\t}{\n\t\t{\n\t\t\tdesc:             \"no error\",\n\t\t\tconfig:           &config.AzureConfig{ClientID: \"clientid\", ClientSecret: \"clientsecret\"},\n\t\t\tvaultName:        \"testkv\",\n\t\t\tkeyName:          \"key1\",\n\t\t\tkeyVersion:       \"262067a9e8ba401aa8a746c5f1a7e147\",\n\t\t\tproxyMode:        false,\n\t\t\texpectedVaultURL: \"https://testkv.vault.azure.net/\",\n\t\t},\n\t\t{\n\t\t\tdesc:             \"no error with double quotes\",\n\t\t\tconfig:           &config.AzureConfig{ClientID: \"clientid\", ClientSecret: \"clientsecret\"},\n\t\t\tvaultName:        \"\\\"testkv\\\"\",\n\t\t\tkeyName:          \"\\\"key1\\\"\",\n\t\t\tkeyVersion:       \"\\\"262067a9e8ba401aa8a746c5f1a7e147\\\"\",\n\t\t\tproxyMode:        false,\n\t\t\texpectedVaultURL: \"https://testkv.vault.azure.net/\",\n\t\t},\n\t\t{\n\t\t\tdesc:             \"no error with proxy mode\",\n\t\t\tconfig:           &config.AzureConfig{ClientID: \"clientid\", ClientSecret: \"clientsecret\"},\n\t\t\tvaultName:        \"testkv\",\n\t\t\tkeyName:          \"key1\",\n\t\t\tkeyVersion:       \"262067a9e8ba401aa8a746c5f1a7e147\",\n\t\t\tproxyMode:        true,\n\t\t\tproxyAddress:     \"localhost\",\n\t\t\tproxyPort:        7788,\n\t\t\texpectedVaultURL: \"http://localhost:7788/testkv.vault.azure.net/\",\n\t\t},\n\t\t{\n\t\t\tdesc:             \"no error with managed hsm\",\n\t\t\tconfig:           &config.AzureConfig{ClientID: \"clientid\", ClientSecret: \"clientsecret\"},\n\t\t\tvaultName:        \"testkv\",\n\t\t\tkeyName:          \"key1\",\n\t\t\tkeyVersion:       \"262067a9e8ba401aa8a746c5f1a7e147\",\n\t\t\tmanagedHSM:       true,\n\t\t\tproxyMode:        false,\n\t\t\texpectedVaultURL: \"https://testkv.managedhsm.azure.net/\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tkvClient, err := NewKeyVaultClient(test.config, test.vaultName, test.keyName, test.keyVersion, test.proxyMode, test.proxyAddress, test.proxyPort, test.managedHSM)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"newKeyVaultClient() failed with error: %v\", err)\n\t\t\t}\n\t\t\tif kvClient == nil {\n\t\t\t\tt.Fatalf(\"newKeyVaultClient() expected kv client to not be nil\")\n\t\t\t}\n\t\t\tif !strings.Contains(kvClient.GetUserAgent(), \"k8s-kms-keyvault\") {\n\t\t\t\tt.Fatalf(\"newKeyVaultClient() expected k8s-kms-keyvault user agent\")\n\t\t\t}\n\t\t\tif kvClient.GetVaultURL() != test.expectedVaultURL {\n\t\t\t\tt.Fatalf(\"expected vault URL: %v, got vault URL: %v\", test.expectedVaultURL, kvClient.GetVaultURL())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetVaultURLError(t *testing.T) {\n\ttests := []struct {\n\t\tdesc       string\n\t\tvaultName  string\n\t\tmanagedHSM bool\n\t}{\n\t\t{\n\t\t\tdesc:      \"vault name > 24\",\n\t\t\tvaultName: \"longkeyvaultnamewhichisnotvalid\",\n\t\t},\n\t\t{\n\t\t\tdesc:      \"vault name < 3\",\n\t\t\tvaultName: \"kv\",\n\t\t},\n\t\t{\n\t\t\tdesc:      \"vault name contains non alpha-numeric chars\",\n\t\t\tvaultName: \"kv_test\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tfor idx := range testEnvs {\n\t\t\tt.Run(fmt.Sprintf(\"%s/%s\", test.desc, testEnvs[idx]), func(t *testing.T) {\n\t\t\t\tazEnv, err := auth.ParseAzureEnvironment(testEnvs[idx])\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to parse azure environment from name, err: %+v\", err)\n\t\t\t\t}\n\t\t\t\tif _, err = getVaultURL(test.vaultName, test.managedHSM, azEnv); err == nil {\n\t\t\t\t\tt.Fatalf(\"getVaultURL() expected error, got nil\")\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestGetVaultURL(t *testing.T) {\n\tvaultName := \"testkv\"\n\n\tfor idx := range testEnvs {\n\t\tt.Run(testEnvs[idx], func(t *testing.T) {\n\t\t\tazEnv, err := auth.ParseAzureEnvironment(testEnvs[idx])\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to parse azure environment from name, err: %+v\", err)\n\t\t\t}\n\t\t\tvaultURL, err := getVaultURL(vaultName, false, azEnv)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expected no error of getting vault URL, got error: %v\", err)\n\t\t\t}\n\t\t\texpectedURL := \"https://\" + vaultName + \".\" + vaultDNSSuffix[idx] + \"/\"\n\t\t\tif expectedURL != *vaultURL {\n\t\t\t\tt.Fatalf(\"expected vault url: %s, got: %s\", expectedURL, *vaultURL)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetKeyIDHash(t *testing.T) {\n\ttestCases := []struct {\n\t\tname                string\n\t\tvaultURL            string\n\t\tkeyName             string\n\t\tkeyVersion          string\n\t\texpectedHash        string\n\t\texpectedError       bool\n\t\texpectedErrorString string\n\t}{\n\t\t{\n\t\t\tname:          \"valid hash\",\n\t\t\tvaultURL:      \"https://example.vault.azure.net/\",\n\t\t\tkeyName:       \"mykey\",\n\t\t\tkeyVersion:    \"ABCD\",\n\t\t\texpectedHash:  \"567d783db3043fe298fe0d9eeedb0029a3815cdd4fe4b059d018c91e6acffe3b\",\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname:                \"invalid vault URL\",\n\t\t\tvaultURL:            \":invalid-url:\",\n\t\t\tkeyName:             \"mykey\",\n\t\t\tkeyVersion:          \"ABCD\",\n\t\t\texpectedHash:        \"\",\n\t\t\texpectedError:       true,\n\t\t\texpectedErrorString: \"failed to parse vault url, error: parse \\\":invalid-url:\\\": missing protocol scheme\",\n\t\t},\n\t\t{\n\t\t\tname:                \"empty vault name\",\n\t\t\tvaultURL:            \"\",\n\t\t\tkeyName:             \"mykey\",\n\t\t\tkeyVersion:          \"ABCD\",\n\t\t\texpectedHash:        \"\",\n\t\t\texpectedError:       true,\n\t\t\texpectedErrorString: \"vault url, key name and key version cannot be empty\",\n\t\t},\n\t\t{\n\t\t\tname:                \"empty key name\",\n\t\t\tvaultURL:            \"https://example.vault.azure.net/\",\n\t\t\tkeyName:             \"\",\n\t\t\tkeyVersion:          \"ABCD\",\n\t\t\texpectedHash:        \"\",\n\t\t\texpectedError:       true,\n\t\t\texpectedErrorString: \"vault url, key name and key version cannot be empty\",\n\t\t},\n\t\t{\n\t\t\tname:                \"empty key vesion\",\n\t\t\tvaultURL:            \"https://example.vault.azure.net/\",\n\t\t\tkeyName:             \"mykey\",\n\t\t\tkeyVersion:          \"\",\n\t\t\texpectedHash:        \"\",\n\t\t\texpectedError:       true,\n\t\t\texpectedErrorString: \"vault url, key name and key version cannot be empty\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thash, err := getKeyIDHash(tc.vaultURL, tc.keyName, tc.keyVersion)\n\n\t\t\tif tc.expectedError {\n\t\t\t\tif (err != nil) && (err.Error() != tc.expectedErrorString) {\n\t\t\t\t\tt.Errorf(\"Expected error: %v, but got: %v\", tc.expectedErrorString, err.Error())\n\t\t\t\t} else if err == nil {\n\t\t\t\t\tt.Errorf(\"Expected error: %v, but didn't get any\", tc.expectedErrorString)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif hash != tc.expectedHash {\n\t\t\t\tt.Errorf(\"Expected hash: %s, but got: %s\", tc.expectedHash, hash)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/plugin/kms_v2_server.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\n\t\"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault\"\n\tkmsv2 \"k8s.io/kms/apis/v2\"\n\t\"monis.app/mlog\"\n)\n\n// KeyManagementServiceV2Server is a gRPC server.\ntype KeyManagementServiceV2Server struct {\n\tkmsv2.UnimplementedKeyManagementServiceServer\n\tkvClient            Client\n\treporter            metrics.StatsReporter\n\tencryptionAlgorithm keyvault.JSONWebKeyEncryptionAlgorithm\n}\n\n// NewKMSv2Server creates an instance of the KMS Service Server with v2 apis.\nfunc NewKMSv2Server(kvClient Client) (*KeyManagementServiceV2Server, error) {\n\tstatsReporter, err := metrics.NewStatsReporter()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create stats reporter: %w\", err)\n\t}\n\n\treturn &KeyManagementServiceV2Server{\n\t\tkvClient:            kvClient,\n\t\treporter:            statsReporter,\n\t\tencryptionAlgorithm: keyvault.RSAOAEP256,\n\t}, nil\n}\n\n// Status returns the health status of the KMS plugin.\nfunc (s *KeyManagementServiceV2Server) Status(ctx context.Context, _ *kmsv2.StatusRequest) (*kmsv2.StatusResponse, error) {\n\t// We perform a simple encrypt/decrypt operation to verify the plugin's connectivity with Key Vault.\n\t// The KMS invokes the Status API every minute, resulting in 120 calls per hour to the Key Vault.\n\t// This volume of calls is well within the permissible limit of Key Vault.\n\tencryptResponse, err := s.kvClient.Encrypt(ctx, []byte(healthCheckPlainText), s.encryptionAlgorithm)\n\tif err != nil {\n\t\tmlog.Error(\"failed to encrypt healthcheck call\", err)\n\t\treturn nil, err\n\t}\n\n\tdecryptedText, err := s.kvClient.Decrypt(\n\t\tctx,\n\t\tencryptResponse.Ciphertext,\n\t\ts.encryptionAlgorithm,\n\t\tversion.KMSv2APIVersion,\n\t\tencryptResponse.Annotations,\n\t\tencryptResponse.KeyID,\n\t)\n\tif err != nil {\n\t\tmlog.Error(\"failed to decrypt healthcheck call\", err)\n\t\treturn nil, err\n\t}\n\n\tif string(decryptedText) != healthCheckPlainText {\n\t\terr = fmt.Errorf(\"decrypted text does not match\")\n\t\tmlog.Error(\"healthcheck failed\", err)\n\t\treturn nil, err\n\t}\n\n\treturn &kmsv2.StatusResponse{\n\t\tVersion: version.KMSv2APIVersion,\n\t\tHealthz: \"ok\",\n\t\tKeyId:   encryptResponse.KeyID,\n\t}, nil\n}\n\n// Encrypt message.\nfunc (s *KeyManagementServiceV2Server) Encrypt(ctx context.Context, request *kmsv2.EncryptRequest) (*kmsv2.EncryptResponse, error) {\n\tmlog.Debug(\"encrypt request received\", \"uid\", request.Uid)\n\tstart := time.Now()\n\n\tvar err error\n\tdefer func() {\n\t\terrors := \"\"\n\t\tstatus := metrics.SuccessStatusTypeValue\n\t\tif err != nil {\n\t\t\tstatus = metrics.ErrorStatusTypeValue\n\t\t\terrors = err.Error()\n\t\t}\n\t\ts.reporter.ReportRequest(ctx, metrics.EncryptOperationTypeValue, status, time.Since(start).Seconds(), errors)\n\t}()\n\n\tmlog.Info(\"encrypt request started\", \"uid\", request.Uid)\n\tencryptResponse, err := s.kvClient.Encrypt(ctx, request.Plaintext, s.encryptionAlgorithm)\n\tif err != nil {\n\t\tmlog.Error(\"failed to encrypt\", err, \"uid\", request.Uid)\n\t\treturn &kmsv2.EncryptResponse{}, err\n\t}\n\tmlog.Info(\"encrypt request complete\", \"uid\", request.Uid)\n\n\treturn &kmsv2.EncryptResponse{\n\t\tCiphertext:  encryptResponse.Ciphertext,\n\t\tKeyId:       encryptResponse.KeyID,\n\t\tAnnotations: encryptResponse.Annotations,\n\t}, nil\n}\n\n// Decrypt message.\nfunc (s *KeyManagementServiceV2Server) Decrypt(ctx context.Context, request *kmsv2.DecryptRequest) (*kmsv2.DecryptResponse, error) {\n\tmlog.Debug(\"decrypt request received\", \"uid\", request.Uid)\n\tstart := time.Now()\n\n\tvar err error\n\tdefer func() {\n\t\terrors := \"\"\n\t\tstatus := metrics.SuccessStatusTypeValue\n\t\tif err != nil {\n\t\t\tstatus = metrics.ErrorStatusTypeValue\n\t\t\terrors = err.Error()\n\t\t}\n\t\ts.reporter.ReportRequest(ctx, metrics.DecryptOperationTypeValue, status, time.Since(start).Seconds(), errors)\n\t}()\n\n\tmlog.Info(\"decrypt request started\", \"uid\", request.Uid)\n\n\tplainText, err := s.kvClient.Decrypt(\n\t\tctx,\n\t\trequest.Ciphertext,\n\t\ts.encryptionAlgorithm,\n\t\tversion.KMSv2APIVersion,\n\t\trequest.Annotations,\n\t\trequest.KeyId,\n\t)\n\tif err != nil {\n\t\tmlog.Error(\"failed to decrypt\", err, \"uid\", request.Uid)\n\t\treturn &kmsv2.DecryptResponse{}, err\n\t}\n\tmlog.Info(\"decrypt request complete\", \"uid\", request.Uid)\n\n\treturn &kmsv2.DecryptResponse{\n\t\tPlaintext: plainText,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/plugin/kms_v2_server_test.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault\"\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\tmockkeyvault \"github.com/Azure/kubernetes-kms/pkg/plugin/mock_keyvault\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\tkmsv2 \"k8s.io/kms/apis/v2\"\n)\n\nfunc TestV2Encrypt(t *testing.T) {\n\ttests := []struct {\n\t\tdesc   string\n\t\tinput  []byte\n\t\toutput []byte\n\t\terr    error\n\t}{\n\t\t{\n\t\t\tdesc:   \"failed to encrypt\",\n\t\t\tinput:  []byte(\"foo\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"failed to encrypt\"),\n\t\t},\n\t\t{\n\t\t\tdesc:   \"successfully encrypted\",\n\t\t\tinput:  []byte(\"foo\"),\n\t\t\toutput: []byte(\"bar\"),\n\t\t\terr:    nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tkvClient := &mockkeyvault.KeyVaultClient{\n\t\t\t\tKeyID:     \"mock-key-id\",\n\t\t\t\tAlgorithm: keyvault.RSA15,\n\t\t\t}\n\t\t\tkvClient.SetEncryptResponse(test.output, test.err)\n\n\t\t\tstatsReporter, err := metrics.NewStatsReporter()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create stats reporter: %v\", err)\n\t\t\t}\n\n\t\t\tkmsV2Server := KeyManagementServiceV2Server{\n\t\t\t\tkvClient: kvClient,\n\t\t\t\treporter: statsReporter,\n\t\t\t}\n\n\t\t\tout, err := kmsV2Server.Encrypt(context.TODO(), &kmsv2.EncryptRequest{\n\t\t\t\tPlaintext: test.input,\n\t\t\t})\n\t\t\tif !errors.Is(err, test.err) {\n\t\t\t\tt.Fatalf(\"expected err: %v, got: %v\", test.err, err)\n\t\t\t}\n\t\t\tif !bytes.Equal(out.GetCiphertext(), test.output) {\n\t\t\t\tt.Fatalf(\"expected out: %v, got: %v\", test.output, out)\n\t\t\t}\n\t\t\tif err == nil && (out.KeyId != kvClient.KeyID) {\n\t\t\t\tt.Fatalf(\"expected key id: %v, got: %v\", kvClient.KeyID, out.KeyId)\n\t\t\t}\n\t\t\tif err == nil && (len(out.Annotations) == 0) {\n\t\t\t\tt.Fatalf(\"invalid annotations, annotations cannot be empty\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestV2Decrypt(t *testing.T) {\n\ttests := []struct {\n\t\tdesc        string\n\t\tinput       []byte\n\t\toutput      []byte\n\t\terr         error\n\t\tannotations map[string][]byte\n\t}{\n\t\t{\n\t\t\tdesc:   \"empty annotations failed to decrypt\",\n\t\t\tinput:  []byte(\"bar\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"invalid annotations, annotations cannot be empty\"),\n\t\t},\n\t\t{\n\t\t\tdesc:   \"invalid keyid failed to decrypt\",\n\t\t\tinput:  []byte(\"bar\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"key id \\\"invalid-key-id\\\" does not match expected key id \\\"mock-key-id\\\" used for encryption\"),\n\t\t\tannotations: map[string][]byte{\n\t\t\t\talgorithmAnnotationKey: []byte(keyvault.RSA15),\n\t\t\t\tversionAnnotationKey:   []byte(\"1\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:   \"invalid algorithm failed to decrypt\",\n\t\t\tinput:  []byte(\"bar\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"algorithm \\\"insecure-algorithm\\\" does not match expected algorithm \\\"RSAOAEP256\\\" used for encryption\"),\n\t\t\tannotations: map[string][]byte{\n\t\t\t\talgorithmAnnotationKey: []byte(\"insecure-algorithm\"),\n\t\t\t\tversionAnnotationKey:   []byte(\"1\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:   \"invalid version failed to decrypt\",\n\t\t\tinput:  []byte(\"bar\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"version \\\"10\\\" does not match expected version \\\"1\\\" used for encryption\"),\n\t\t\tannotations: map[string][]byte{\n\t\t\t\talgorithmAnnotationKey: []byte(keyvault.RSA15),\n\t\t\t\tversionAnnotationKey:   []byte(\"10\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:   \"failed to decrypt\",\n\t\t\tinput:  []byte(\"foo\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"failed to decrypt\"),\n\t\t\tannotations: map[string][]byte{\n\t\t\t\talgorithmAnnotationKey: []byte(keyvault.RSA15),\n\t\t\t\tversionAnnotationKey:   []byte(\"1\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:   \"successfully decrypted\",\n\t\t\tinput:  []byte(\"bar\"),\n\t\t\toutput: []byte(\"foo\"),\n\t\t\terr:    nil,\n\t\t\tannotations: map[string][]byte{\n\t\t\t\talgorithmAnnotationKey: []byte(keyvault.RSA15),\n\t\t\t\tversionAnnotationKey:   []byte(\"1\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tkvClient := &mockkeyvault.KeyVaultClient{\n\t\t\t\tKeyID:     \"mock-key-id\",\n\t\t\t\tAlgorithm: keyvault.RSAOAEP256,\n\t\t\t}\n\t\t\tkvClient.SetDecryptResponse(test.output, test.err)\n\n\t\t\tstatsReporter, err := metrics.NewStatsReporter()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create stats reporter: %v\", err)\n\t\t\t}\n\n\t\t\tkmsV2Server := KeyManagementServiceV2Server{\n\t\t\t\tkvClient: kvClient,\n\t\t\t\treporter: statsReporter,\n\t\t\t}\n\n\t\t\tout, err := kmsV2Server.Decrypt(context.TODO(), &kmsv2.DecryptRequest{\n\t\t\t\tCiphertext:  test.input,\n\t\t\t\tAnnotations: test.annotations,\n\t\t\t\tKeyId:       \"mock-key-id\",\n\t\t\t})\n\t\t\tif err != nil && (err.Error() != test.err.Error()) {\n\t\t\t\tt.Fatalf(\"expected err: %v, got: %v\", test.err, err)\n\t\t\t}\n\t\t\tif !bytes.Equal(out.GetPlaintext(), test.output) {\n\t\t\t\tt.Fatalf(\"expected out: %v, got: %v\", test.output, out)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestStatus(t *testing.T) {\n\tkmsServer := KeyManagementServiceV2Server{}\n\tmockKeyVaultClient := &mockkeyvault.KeyVaultClient{\n\t\tKeyID: \"mock-key-id\",\n\t}\n\tmockKeyVaultClient.SetEncryptResponse([]byte(healthCheckPlainText), nil)\n\tmockKeyVaultClient.SetDecryptResponse([]byte(healthCheckPlainText), nil)\n\tkmsServer.kvClient = mockKeyVaultClient\n\n\tv, err := kmsServer.Status(context.TODO(), &kmsv2.StatusRequest{})\n\tif err != nil {\n\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t}\n\n\tif v.Version != version.KMSv2APIVersion {\n\t\tt.Fatalf(\"expected version: %s, got: %s\", version.KMSv2APIVersion, v.Version)\n\t}\n\n\tif v.Healthz != \"ok\" {\n\t\tt.Fatalf(\"expected healthz response to be: %s, got: %s\", \"ok\", v.Healthz)\n\t}\n\n\tif v.KeyId != \"mock-key-id\" {\n\t\tt.Fatalf(\"expected key id: %s, got: %s\", \"mock-key-id\", v.KeyId)\n\t}\n}\n"
  },
  {
    "path": "pkg/plugin/mock_keyvault/keyvault_mock.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage mockkeyvault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault\"\n\t\"k8s.io/kms/pkg/service\"\n)\n\ntype KeyVaultClient struct {\n\tmutex sync.Mutex\n\n\tencryptOut []byte\n\tencryptErr error\n\tdecryptOut []byte\n\tdecryptErr error\n\tKeyID      string\n\tAlgorithm  keyvault.JSONWebKeyEncryptionAlgorithm\n}\n\nfunc (kvc *KeyVaultClient) Encrypt(_ context.Context, _ []byte, _ keyvault.JSONWebKeyEncryptionAlgorithm) (*service.EncryptResponse, error) {\n\tkvc.mutex.Lock()\n\tdefer kvc.mutex.Unlock()\n\treturn &service.EncryptResponse{\n\t\tCiphertext: kvc.encryptOut,\n\t\tKeyID:      kvc.KeyID,\n\t\tAnnotations: map[string][]byte{\n\t\t\t\"key-id.azure.akv.io\":    []byte(kvc.KeyID),\n\t\t\t\"algorithm.azure.akv.io\": []byte(kvc.Algorithm),\n\t\t\t\"version.azure.akv.io\":   []byte(\"1\"),\n\t\t},\n\t}, kvc.encryptErr\n}\n\nfunc (kvc *KeyVaultClient) Decrypt(_ context.Context, _ []byte, _ keyvault.JSONWebKeyEncryptionAlgorithm, _ string, _ map[string][]byte, _ string) ([]byte, error) {\n\tkvc.mutex.Lock()\n\tdefer kvc.mutex.Unlock()\n\treturn kvc.decryptOut, kvc.decryptErr\n}\n\nfunc (kvc *KeyVaultClient) SetEncryptResponse(encryptOut []byte, err error) {\n\tkvc.mutex.Lock()\n\tdefer kvc.mutex.Unlock()\n\tkvc.encryptOut = encryptOut\n\tkvc.encryptErr = err\n}\n\nfunc (kvc *KeyVaultClient) SetDecryptResponse(decryptOut []byte, err error) {\n\tkvc.mutex.Lock()\n\tdefer kvc.mutex.Unlock()\n\tkvc.decryptOut = decryptOut\n\tkvc.decryptErr = err\n}\n\nfunc (kvc *KeyVaultClient) ValidateAnnotations(annotations map[string][]byte, keyID string) error {\n\tif len(annotations) == 0 {\n\t\treturn fmt.Errorf(\"invalid annotations, annotations cannot be empty\")\n\t}\n\n\t// validate key id\n\tif keyID != kvc.KeyID {\n\t\treturn fmt.Errorf(\n\t\t\t\"key id %q does not match expected key id %q used for encryption\",\n\t\t\tstring(annotations[\"key-id.azure.akv.io\"]),\n\t\t\tkvc.KeyID,\n\t\t)\n\t}\n\n\t// validate algorithm\n\tif string(annotations[\"algorithm.azure.akv.io\"]) != string(kvc.Algorithm) {\n\t\treturn fmt.Errorf(\"algorithm %q does not match expected algorithm %q used for encryption\", string(annotations[\"algorithm.azure.akv.io\"]), kvc.Algorithm)\n\t}\n\n\t// validate version\n\tif string(annotations[\"version.azure.akv.io\"]) != \"1\" {\n\t\treturn fmt.Errorf(\n\t\t\t\"version %q does not match expected version %q used for encryption\",\n\t\t\tstring(annotations[\"version.azure.akv.io\"]),\n\t\t\t\"1\",\n\t\t)\n\t}\n\n\treturn nil\n}\n\nfunc (kvc *KeyVaultClient) GetUserAgent() string {\n\treturn \"k8s-kms-keyvault\"\n}\n\nfunc (kvc *KeyVaultClient) GetVaultURL() string {\n\treturn \"https://test.vault.azure.net\"\n}\n"
  },
  {
    "path": "pkg/plugin/server.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\n\t\"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault\"\n\tkmsv1 \"k8s.io/kms/apis/v1beta1\"\n\t\"monis.app/mlog\"\n)\n\n// KeyManagementServiceServer is a gRPC server.\ntype KeyManagementServiceServer struct {\n\tkmsv1.UnimplementedKeyManagementServiceServer\n\tkvClient            Client\n\treporter            metrics.StatsReporter\n\tencryptionAlgorithm keyvault.JSONWebKeyEncryptionAlgorithm\n}\n\n// Config is the configuration for the KMS plugin.\ntype Config struct {\n\tConfigFilePath string\n\tKeyVaultName   string\n\tKeyName        string\n\tKeyVersion     string\n\tManagedHSM     bool\n\tProxyMode      bool\n\tProxyAddress   string\n\tProxyPort      int\n}\n\n// NewKMSv1Server creates an instance of the KMS Service Server.\nfunc NewKMSv1Server(kvClient Client) (*KeyManagementServiceServer, error) {\n\tstatsReporter, err := metrics.NewStatsReporter()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create stats reporter: %w\", err)\n\t}\n\n\treturn &KeyManagementServiceServer{\n\t\tkvClient:            kvClient,\n\t\treporter:            statsReporter,\n\t\tencryptionAlgorithm: keyvault.RSA15,\n\t}, nil\n}\n\n// Version of kms.\nfunc (s *KeyManagementServiceServer) Version(_ context.Context, _ *kmsv1.VersionRequest) (*kmsv1.VersionResponse, error) {\n\treturn &kmsv1.VersionResponse{\n\t\tVersion:        version.KMSv1APIVersion,\n\t\tRuntimeName:    version.Runtime,\n\t\tRuntimeVersion: version.BuildVersion,\n\t}, nil\n}\n\n// Encrypt message.\nfunc (s *KeyManagementServiceServer) Encrypt(ctx context.Context, request *kmsv1.EncryptRequest) (*kmsv1.EncryptResponse, error) {\n\tstart := time.Now()\n\n\tvar err error\n\tdefer func() {\n\t\terrors := \"\"\n\t\tstatus := metrics.SuccessStatusTypeValue\n\t\tif err != nil {\n\t\t\tstatus = metrics.ErrorStatusTypeValue\n\t\t\terrors = err.Error()\n\t\t}\n\t\ts.reporter.ReportRequest(ctx, metrics.EncryptOperationTypeValue, status, time.Since(start).Seconds(), errors)\n\t}()\n\n\tmlog.Info(\"encrypt request started\")\n\tencryptResponse, err := s.kvClient.Encrypt(ctx, request.Plain, s.encryptionAlgorithm)\n\tif err != nil {\n\t\tmlog.Error(\"failed to encrypt\", err)\n\t\treturn &kmsv1.EncryptResponse{}, err\n\t}\n\tmlog.Info(\"encrypt request complete\")\n\treturn &kmsv1.EncryptResponse{\n\t\tCipher: encryptResponse.Ciphertext,\n\t}, nil\n}\n\n// Decrypt message.\nfunc (s *KeyManagementServiceServer) Decrypt(ctx context.Context, request *kmsv1.DecryptRequest) (*kmsv1.DecryptResponse, error) {\n\tstart := time.Now()\n\n\tvar err error\n\tdefer func() {\n\t\terrors := \"\"\n\t\tstatus := metrics.SuccessStatusTypeValue\n\t\tif err != nil {\n\t\t\tstatus = metrics.ErrorStatusTypeValue\n\t\t\terrors = err.Error()\n\t\t}\n\t\ts.reporter.ReportRequest(ctx, metrics.DecryptOperationTypeValue, status, time.Since(start).Seconds(), errors)\n\t}()\n\n\tmlog.Info(\"decrypt request started\")\n\tplain, err := s.kvClient.Decrypt(\n\t\tctx,\n\t\trequest.Cipher,\n\t\ts.encryptionAlgorithm,\n\t\trequest.Version,\n\t\tnil,\n\t\t\"\",\n\t)\n\tif err != nil {\n\t\tmlog.Error(\"failed to decrypt\", err)\n\t\treturn &kmsv1.DecryptResponse{}, err\n\t}\n\tmlog.Info(\"decrypt request complete\")\n\treturn &kmsv1.DecryptResponse{Plain: plain}, nil\n}\n"
  },
  {
    "path": "pkg/plugin/server_test.go",
    "content": "// Copyright (c) Microsoft and contributors.  All rights reserved.\n//\n// This source code is licensed under the MIT license found in the\n// LICENSE file in the root directory of this source tree.\n\npackage plugin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\tmockkeyvault \"github.com/Azure/kubernetes-kms/pkg/plugin/mock_keyvault\"\n\t\"github.com/Azure/kubernetes-kms/pkg/version\"\n\n\tkmsv1 \"k8s.io/kms/apis/v1beta1\"\n)\n\nfunc TestEncrypt(t *testing.T) {\n\ttests := []struct {\n\t\tdesc   string\n\t\tinput  []byte\n\t\toutput []byte\n\t\terr    error\n\t}{\n\t\t{\n\t\t\tdesc:   \"failed to encrypt\",\n\t\t\tinput:  []byte(\"foo\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"failed to encrypt\"),\n\t\t},\n\t\t{\n\t\t\tdesc:   \"successfully encrypted\",\n\t\t\tinput:  []byte(\"foo\"),\n\t\t\toutput: []byte(\"bar\"),\n\t\t\terr:    nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tkvClient := &mockkeyvault.KeyVaultClient{}\n\t\t\tkvClient.SetEncryptResponse(test.output, test.err)\n\n\t\t\tstatsReporter, err := metrics.NewStatsReporter()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create stats reporter: %v\", err)\n\t\t\t}\n\n\t\t\tkmsServer := KeyManagementServiceServer{\n\t\t\t\tkvClient: kvClient,\n\t\t\t\treporter: statsReporter,\n\t\t\t}\n\n\t\t\tout, err := kmsServer.Encrypt(context.TODO(), &kmsv1.EncryptRequest{\n\t\t\t\tPlain: test.input,\n\t\t\t})\n\t\t\tif !errors.Is(err, test.err) {\n\t\t\t\tt.Fatalf(\"expected err: %v, got: %v\", test.err, err)\n\t\t\t}\n\t\t\tif !bytes.Equal(out.GetCipher(), test.output) {\n\t\t\t\tt.Fatalf(\"expected out: %v, got: %v\", test.output, out)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDecrypt(t *testing.T) {\n\ttests := []struct {\n\t\tdesc   string\n\t\tinput  []byte\n\t\toutput []byte\n\t\terr    error\n\t}{\n\t\t{\n\t\t\tdesc:   \"failed to decrypt\",\n\t\t\tinput:  []byte(\"foo\"),\n\t\t\toutput: []byte{},\n\t\t\terr:    fmt.Errorf(\"failed to decrypt\"),\n\t\t},\n\t\t{\n\t\t\tdesc:   \"successfully decrypted\",\n\t\t\tinput:  []byte(\"bar\"),\n\t\t\toutput: []byte(\"foo\"),\n\t\t\terr:    nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tkvClient := &mockkeyvault.KeyVaultClient{}\n\t\t\tkvClient.SetDecryptResponse(test.output, test.err)\n\n\t\t\tstatsReporter, err := metrics.NewStatsReporter()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create stats reporter: %v\", err)\n\t\t\t}\n\n\t\t\tkmsServer := KeyManagementServiceServer{\n\t\t\t\tkvClient: kvClient,\n\t\t\t\treporter: statsReporter,\n\t\t\t}\n\n\t\t\tout, err := kmsServer.Decrypt(context.TODO(), &kmsv1.DecryptRequest{\n\t\t\t\tCipher: test.input,\n\t\t\t})\n\t\t\tif !errors.Is(err, test.err) {\n\t\t\t\tt.Fatalf(\"expected err: %v, got: %v\", test.err, err)\n\t\t\t}\n\t\t\tif !bytes.Equal(out.GetPlain(), test.output) {\n\t\t\t\tt.Fatalf(\"expected out: %v, got: %v\", test.output, out)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVersion(t *testing.T) {\n\tkmsServer := KeyManagementServiceServer{}\n\n\tversion.BuildVersion = \"latest\"\n\n\tv, err := kmsServer.Version(context.TODO(), &kmsv1.VersionRequest{})\n\tif err != nil {\n\t\tt.Fatalf(\"expected err to be nil, got: %v\", err)\n\t}\n\tif v.Version != version.KMSv1APIVersion {\n\t\tt.Fatalf(\"expected version: %s, got: %s\", version.KMSv1APIVersion, v.Version)\n\t}\n\tif v.RuntimeName != version.Runtime {\n\t\tt.Fatalf(\"expected runtime: %s, got: %s\", version.Runtime, v.RuntimeName)\n\t}\n\tif v.RuntimeVersion != \"latest\" {\n\t\tt.Fatalf(\"expected runtime version: %s, got: %s\", version.BuildVersion, v.Version)\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/grpc.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Azure/kubernetes-kms/pkg/metrics\"\n\n\t\"google.golang.org/grpc\"\n\t\"monis.app/mlog\"\n)\n\n// ParseEndpoint returns unix socket's protocol and address.\nfunc ParseEndpoint(ep string) (string, string, error) {\n\tif strings.HasPrefix(strings.ToLower(ep), \"unix://\") {\n\t\ts := strings.SplitN(ep, \"://\", 2)\n\t\tif s[1] != \"\" {\n\t\t\treturn s[0], s[1], nil\n\t\t}\n\t}\n\treturn \"\", \"\", fmt.Errorf(\"invalid endpoint: %v\", ep)\n}\n\n// UnaryServerInterceptor provides metrics around Unary RPCs.\nfunc UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\tvar err error\n\tstart := time.Now()\n\treporter, err := metrics.NewStatsReporter()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create stats reporter: %w\", err)\n\t}\n\n\tdefer func() {\n\t\terrors := \"\"\n\t\tstatus := metrics.SuccessStatusTypeValue\n\t\tif err != nil {\n\t\t\tstatus = metrics.ErrorStatusTypeValue\n\t\t\terrors = err.Error()\n\t\t}\n\t\treporter.ReportRequest(ctx, fmt.Sprintf(\"%s_%s\", metrics.GrpcOperationTypeValue, getGRPCMethodName(info.FullMethod)), status, time.Since(start).Seconds(), errors)\n\t}()\n\n\tmlog.Trace(\"GRPC call\", \"method\", info.FullMethod)\n\tresp, err := handler(ctx, req)\n\tif err != nil {\n\t\tmlog.Error(\"GRPC request error\", err)\n\t}\n\treturn resp, err\n}\n\nfunc getGRPCMethodName(fullMethodName string) string {\n\tfullMethodName = strings.TrimPrefix(fullMethodName, \"/\")\n\tmethodNames := strings.Split(fullMethodName, \"/\")\n\tif len(methodNames) >= 2 {\n\t\treturn strings.ToLower(methodNames[1])\n\t}\n\n\treturn \"unknown\"\n}\n"
  },
  {
    "path": "pkg/utils/grpc_test.go",
    "content": "package utils\n\nimport \"testing\"\n\nfunc TestParseEndpoint(t *testing.T) {\n\ttests := []struct {\n\t\tdesc          string\n\t\tendpoint      string\n\t\texpectedProto string\n\t\texpectedAddr  string\n\t\texpectedErr   bool\n\t}{\n\t\t{\n\t\t\tdesc:        \"invalid endpoint\",\n\t\t\tendpoint:    \"udp:///provider/azure.sock\",\n\t\t\texpectedErr: true,\n\t\t},\n\t\t{\n\t\t\tdesc:          \"invalid unix endpoint\",\n\t\t\tendpoint:      \"unix://\",\n\t\t\texpectedProto: \"\",\n\t\t\texpectedAddr:  \"\",\n\t\t\texpectedErr:   true,\n\t\t},\n\t\t{\n\t\t\tdesc:          \"valid unix endpoint\",\n\t\t\tendpoint:      \"unix:///provider/azure.sock\",\n\t\t\texpectedProto: \"unix\",\n\t\t\texpectedAddr:  \"/provider/azure.sock\",\n\t\t\texpectedErr:   false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tproto, addr, err := ParseEndpoint(test.endpoint)\n\t\t\tif test.expectedErr && err == nil || !test.expectedErr && err != nil {\n\t\t\t\tt.Fatalf(\"expected error: %v, got error: %v\", test.expectedErr, err)\n\t\t\t}\n\t\t\tif proto != test.expectedProto {\n\t\t\t\tt.Fatalf(\"expected proto: %v, got: %v\", test.expectedProto, proto)\n\t\t\t}\n\t\t\tif addr != test.expectedAddr {\n\t\t\t\tt.Fatalf(\"expected addr: %v, got: %v\", test.expectedAddr, addr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetGRPCMethodName(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tinput          string\n\t\texpectedOutput string\n\t}{\n\t\t{\n\t\t\tname:           \"With_Correct_Method_Name\",\n\t\t\tinput:          \"/v1beta1.KeyManagementService/Encrypt\",\n\t\t\texpectedOutput: \"encrypt\",\n\t\t},\n\t\t{\n\t\t\tname:           \"With_Incorrect_Method_Name\",\n\t\t\tinput:          \"/Encrypt\",\n\t\t\texpectedOutput: \"unknown\",\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tmethodName := getGRPCMethodName(testCase.input)\n\n\t\t\tif methodName != testCase.expectedOutput {\n\t\t\t\tt.Fatalf(\"expected output: '%s', found: '%s'\", testCase.expectedOutput, methodName)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/sanitize.go",
    "content": "package utils\n\nimport \"strings\"\n\n// SanitizeString returns a string that does not have white spaces and double quotes.\nfunc SanitizeString(s string) string {\n\treturn strings.TrimSpace(strings.Trim(strings.TrimSpace(s), \"\\\"\"))\n}\n"
  },
  {
    "path": "pkg/utils/sanitize_test.go",
    "content": "package utils\n\nimport \"testing\"\n\nfunc TestSanitizeString(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tinput          string\n\t\texpectedOutput string\n\t}{\n\t\t{\n\t\t\tname:           \"With_White_Spaces\",\n\t\t\tinput:          \" hello \",\n\t\t\texpectedOutput: \"hello\",\n\t\t},\n\t\t{\n\t\t\tname:           \"With_Double_Quotes\",\n\t\t\tinput:          \"\\\"hello\\\"\",\n\t\t\texpectedOutput: \"hello\",\n\t\t},\n\t\t{\n\t\t\tname:           \"With_White_Spaces_And_Double_Quotes\",\n\t\t\tinput:          \" \\\"hello\\\" \",\n\t\t\texpectedOutput: \"hello\",\n\t\t},\n\t\t{\n\t\t\tname:           \"With_Double_Quotes_And_White_Spaces\",\n\t\t\tinput:          \"\\\" hello \\\"\",\n\t\t\texpectedOutput: \"hello\",\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tsanitizedString := SanitizeString(testCase.input)\n\n\t\t\tif sanitizedString != testCase.expectedOutput {\n\t\t\t\tt.Fatalf(\"expected output: '%s', found: '%s'\", testCase.expectedOutput, sanitizedString)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"runtime\"\n)\n\nvar (\n\t// BuildDate is the date when the binary was built.\n\tBuildDate string\n\t// GitCommit is the commit hash when the binary was built.\n\tGitCommit string\n\t// BuildVersion is the version of the KMS binary.\n\tBuildVersion string\n\t// KMSv1APIVersion is the version of the KMS V1 APIs.\n\tKMSv1APIVersion = \"v1beta1\"\n\t// KMSv2APIVersion is the version of the KMS V2 APIs.\n\tKMSv2APIVersion = \"v2beta1\"\n\t// Runtime of the plugin.\n\tRuntime = \"Microsoft AzureKMS\"\n)\n\n// PrintVersion prints the current KMS plugin version.\nfunc PrintVersion() (err error) {\n\tpv := struct {\n\t\tBuildVersion string\n\t\tGitCommit    string\n\t\tBuildDate    string\n\t}{\n\t\tBuildDate:    BuildDate,\n\t\tBuildVersion: BuildVersion,\n\t\tGitCommit:    GitCommit,\n\t}\n\n\tvar res []byte\n\tif res, err = json.Marshal(pv); err != nil {\n\t\treturn\n\t}\n\n\tfmt.Printf(\"%s\\n\", res)\n\treturn\n}\n\n// GetUserAgent returns UserAgent string to append to the agent identifier.\nfunc GetUserAgent() string {\n\treturn fmt.Sprintf(\"k8s-kms-keyvault/%s (%s/%s) %s/%s\", BuildVersion, runtime.GOOS, runtime.GOARCH, GitCommit, BuildDate)\n}\n"
  },
  {
    "path": "pkg/version/version_test.go",
    "content": "package version\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPrintVersion(t *testing.T) {\n\tBuildDate = \"Now\"\n\tBuildVersion = \"version\"\n\tGitCommit = \"hash\"\n\n\told := os.Stdout // keep backup of the real stdout\n\tr, w, _ := os.Pipe()\n\tos.Stdout = w\n\n\terr := PrintVersion()\n\n\toutC := make(chan string)\n\t// copy the output in a separate goroutine so printing can't block indefinitely\n\tgo func() {\n\t\tvar buf bytes.Buffer\n\t\t_, _ = io.Copy(&buf, r)\n\t\toutC <- strings.TrimSpace(buf.String())\n\t}()\n\n\t// back to normal state\n\tw.Close()\n\tos.Stdout = old // restoring the real stdout\n\tout := <-outC\n\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error, got %v\", err)\n\t}\n\texpected := `{\"BuildVersion\":\"version\",\"GitCommit\":\"hash\",\"BuildDate\":\"Now\"}`\n\tif !strings.EqualFold(out, expected) {\n\t\tt.Fatalf(\"string doesn't match, expected %s, got %s\", expected, out)\n\t}\n}\n\nfunc TestGetUserAgent(t *testing.T) {\n\tBuildDate = \"Now\"\n\tBuildVersion = \"version\"\n\tGitCommit = \"hash\"\n\n\tuserAgent := GetUserAgent()\n\texpectedUserAgent := fmt.Sprintf(\"k8s-kms-keyvault/version (%s/%s) hash/Now\", runtime.GOOS, runtime.GOARCH)\n\tif !strings.EqualFold(userAgent, expectedUserAgent) {\n\t\tt.Fatalf(\"string doesn't match, expected %s, got %s\", expectedUserAgent, userAgent)\n\t}\n}\n"
  },
  {
    "path": "scripts/connect-registry.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nif [ \"${KIND_NETWORK}\" != \"bridge\" ]; then\n  # wait for the kind network to exist\n  for i in $(seq 1 25); do\n    if docker network ls | grep \"${KIND_NETWORK}\"; then\n      break\n    else\n      sleep 1\n    fi\n  done\n  containers=$(docker network inspect \"${KIND_NETWORK}\" -f \"{{range .Containers}}{{.Name}} {{end}}\")\n  needs_connect=\"true\"\n  for c in $containers; do\n    if [ \"$c\" = \"${REGISTRY_NAME}\" ]; then\n      needs_connect=\"false\"\n    fi\n  done\n  if [ \"${needs_connect}\" = \"true\" ]; then\n    echo \"connecting ${KIND_NETWORK} network to ${REGISTRY_NAME}\"\n    docker network connect \"${KIND_NETWORK}\" \"${REGISTRY_NAME}\" || true\n  fi\nfi\n"
  },
  {
    "path": "scripts/setup-kind-cluster.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nexport ENCRYPTION_CONFIG_FILE=encryption-config.yaml\nenvsubst < ./tests/e2e/kind-config.yaml > ./tests/e2e/generated_manifests/kind-config.yaml\n\n# create a cluster with the local registry enabled in containerd\n# add encryption config and the kms static pod manifest with custom image\nkind create cluster --retain --image kindest/node:\"${KUBERNETES_VERSION}\" --name \"${KIND_CLUSTER_NAME}\" --wait 2m --config=./tests/e2e/generated_manifests/kind-config.yaml\n"
  },
  {
    "path": "scripts/setup-kmsv2-kind-cluster.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nexport ENCRYPTION_CONFIG_FILE=kmsv2-encryption-config.yaml\nenvsubst < ./tests/e2e/kind-config.yaml > ./tests/e2e/generated_manifests/kind-config.yaml\n\n# # create a cluster with the local registry enabled in containerd\n# # add encryption config and the kms static pod manifest with custom image\nkind create cluster --retain --image kindest/node:\"${KUBERNETES_VERSION}\" --name \"${KIND_CLUSTER_NAME}\" --wait 2m --config=./tests/e2e/generated_manifests/kind-config.yaml\n"
  },
  {
    "path": "scripts/setup-local-registry.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# create registry container unless it already exists\nrunning=\"$(docker inspect -f '{{.State.Running}}' \"${REGISTRY_NAME}\" 2>/dev/null || true)\"\nif [ \"${running}\" != 'true' ]; then\n  echo \"Creating local registry\"\n  docker run \\\n    -d --restart=always -p \"${REGISTRY_PORT}:5000\" --name \"${REGISTRY_NAME}\" \\\n    mirror.gcr.io/registry:2\nfi\n\n# create hosts.toml for the local registry containerd config\n# the certs.d directory is mounted into the kind node at /etc/containerd/certs.d\nrm -rf tests/e2e/generated_manifests/certs.d\nmkdir -p \"tests/e2e/generated_manifests/certs.d/localhost:${REGISTRY_PORT}\"\ncat <<EOF > \"tests/e2e/generated_manifests/certs.d/localhost:${REGISTRY_PORT}/hosts.toml\"\n[host.\"http://${REGISTRY_NAME}:5000\"]\nEOF\n\n# Build and push kms image\nexport REGISTRY=localhost:${REGISTRY_PORT}\nexport IMAGE_NAME=keyvault\nexport IMAGE_VERSION=e2e-$(git rev-parse --short HEAD)\nexport OUTPUT_TYPE=type=docker\n\n# push build image to local registry\necho \"Build and push image to local registry\"\nmake docker-init-buildx docker-build\ndocker push \"${REGISTRY}/${IMAGE_NAME}:${IMAGE_VERSION}\"\n\n# generate manifest for local\nmake e2e-generate-manifests\n"
  },
  {
    "path": "tests/client/client_test.go",
    "content": "package test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"k8s.io/apimachinery/pkg/util/uuid\"\n\tkmsv1 \"k8s.io/kms/apis/v1beta1\"\n\tkmsv2 \"k8s.io/kms/apis/v2\"\n)\n\nconst (\n\tnetProtocol      = \"unix\"\n\tpathToUnixSocket = \"/opt/azurekms.sock\"\n\tversion          = \"v1beta1\"\n)\n\nvar (\n\tv1Client   kmsv1.KeyManagementServiceClient\n\tv2Client   kmsv2.KeyManagementServiceClient\n\tconnection *grpc.ClientConn\n\tt          *testing.T\n\terr        error\n)\n\nfunc setupTestCase() {\n\tif t != nil {\n\t\tt.Log(\"setup test case\")\n\t\tconnection, err = newUnixSocketConnection(pathToUnixSocket)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%s\", err)\n\t\t}\n\n\t\tv1Client = kmsv1.NewKeyManagementServiceClient(connection)\n\t\tv2Client = kmsv2.NewKeyManagementServiceClient(connection)\n\t}\n}\n\nfunc teardownTestCase() {\n\tif t != nil {\n\t\tt.Log(\"teardown test case\")\n\t\tconnection.Close()\n\t}\n}\n\nfunc TestEncryptDecrypt(t *testing.T) {\n\tcases := []struct {\n\t\tname     string\n\t\twant     []byte\n\t\texpected []byte\n\t}{\n\t\t{\"text\", []byte(\"secret\"), []byte(\"secret\")},\n\t\t{\"number\", []byte(\"1234\"), []byte(\"1234\")},\n\t\t{\"special\", []byte(\"!@#$%^&*()_\"), []byte(\"!@#$%^&*()_\")},\n\t\t{\"GUID\", []byte(\"b32a58c6-48c1-4552-8ff0-47680f3416d0\"), []byte(\"b32a58c6-48c1-4552-8ff0-47680f3416d0\")},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\t\tt.Cleanup(cancel)\n\n\t\t\tv1EncryptRequest := kmsv1.EncryptRequest{Version: version, Plain: tc.want}\n\t\t\tv1EncryptResponse, err := v1Client.Encrypt(ctx, &v1EncryptRequest)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"encrypt request for KMS v1 failed with error: %+v\", err)\n\t\t\t}\n\n\t\t\tv1DecryptRequest := kmsv1.DecryptRequest{Version: version, Cipher: v1EncryptResponse.Cipher}\n\t\t\tv1DecryptResponse, err := v1Client.Decrypt(ctx, &v1DecryptRequest)\n\t\t\tif !bytes.Equal(v1DecryptResponse.Plain, tc.want) {\n\t\t\t\tt.Fatalf(\"Expected secret, but got %s - %v\", string(v1DecryptResponse.Plain), err)\n\t\t\t}\n\n\t\t\tuid := \"integration-test-\" + string(uuid.NewUUID())\n\t\t\tv2EncryptRequest := kmsv2.EncryptRequest{\n\t\t\t\tPlaintext: tc.want,\n\t\t\t\tUid:       uid,\n\t\t\t}\n\t\t\tv2EncryptResponse, err := v2Client.Encrypt(ctx, &v2EncryptRequest)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"encrypt request for KMS v2 failed with error: %+v\", err)\n\t\t\t}\n\t\t\tif v2EncryptResponse.KeyId == \"\" {\n\t\t\t\tt.Fatalf(\"Returned KeyId is empty\")\n\t\t\t}\n\n\t\t\tif v2EncryptResponse.Annotations == nil {\n\t\t\t\tt.Fatalf(\"Returned Annotations is nil\")\n\t\t\t}\n\n\t\t\tv2DecryptRequest := kmsv2.DecryptRequest{\n\t\t\t\tCiphertext:  v2EncryptResponse.Ciphertext,\n\t\t\t\tKeyId:       v2EncryptResponse.KeyId,\n\t\t\t\tUid:         uid,\n\t\t\t\tAnnotations: v2EncryptResponse.Annotations,\n\t\t\t}\n\t\t\tv2DecryptResponse, err := v2Client.Decrypt(ctx, &v2DecryptRequest)\n\t\t\tif !bytes.Equal(v2DecryptResponse.Plaintext, tc.want) {\n\t\t\t\tt.Fatalf(\"Expected secret, but got %s - %v\", string(v2DecryptResponse.Plaintext), err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Check the KMS provider API version.\n// Only matching version is supported now.\nfunc TestV1Version(t *testing.T) {\n\tcases := []struct {\n\t\tname     string\n\t\twant     string\n\t\texpected string\n\t}{\n\t\t{\"v1beta1\", \"v1beta1\", \"v1beta1\"},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\t\tt.Cleanup(cancel)\n\n\t\t\trequest := &kmsv1.VersionRequest{Version: tc.want}\n\t\t\tresponse, err := v1Client.Version(ctx, request)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed get version from remote KMS provider: %v\", err)\n\t\t\t}\n\t\t\tif response.Version != tc.want {\n\t\t\t\tt.Fatalf(\"KMS provider api version %s is not supported, only %s is supported now\", tc.want, version)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestV2Version(t *testing.T) {\n\tcases := []struct {\n\t\tname     string\n\t\twant     string\n\t\texpected string\n\t}{\n\t\t{\"v2beta1\", \"v2beta1\", \"v2beta1\"},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\t\tt.Cleanup(cancel)\n\n\t\t\trequest := &kmsv2.StatusRequest{}\n\t\t\tresponse, err := v2Client.Status(ctx, request)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed get status of remote KMS v2 provider: %v\", err)\n\t\t\t}\n\t\t\tif response.Version != tc.want {\n\t\t\t\tt.Fatalf(\"KMS v2 provider api version %s is not supported, only %s is supported now\", tc.want, version)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\tt = &testing.T{}\n\tsetupTestCase()\n\tm.Run()\n\tteardownTestCase()\n}\n\nfunc newUnixSocketConnection(path string) (*grpc.ClientConn, error) {\n\treturn grpc.NewClient(\"unix://\"+path,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()))\n}\n"
  },
  {
    "path": "tests/e2e/azure.json",
    "content": "{\n    \"cloud\": \"AzurePublicCloud\",\n    \"tenantId\": \"$AZURE_TENANT_ID\",\n    \"useManagedIdentityExtension\": true,\n    \"userAssignedIdentityID\": \"$USER_ASSIGNED_IDENTITY_ID\"\n}\n\n"
  },
  {
    "path": "tests/e2e/encryption-config.yaml",
    "content": "kind: EncryptionConfiguration\napiVersion: apiserver.config.k8s.io/v1\nresources:\n  - resources:\n      - secrets\n    providers:\n      - kms:\n          name: azurekmsprovider\n          endpoint: unix:///opt/azurekms.socket\n          cachesize: 1000\n      - identity: {}\n"
  },
  {
    "path": "tests/e2e/helpers.bash",
    "content": "#!/bin/bash\n\nassert_success() {\n  if [[ \"$status\" != 0 ]]; then\n    echo \"expected: 0\"\n    echo \"actual: $status\"\n    echo \"output: $output\"\n    return 1\n  fi\n}\n\nassert_equal() {\n  if [[ \"$1\" != \"$2\" ]]; then\n    echo \"expected: $1\"\n    echo \"actual: $2\"\n    return 1\n  fi\n}\n\nassert_match() {\n  if [[ ! \"$2\" =~ $1 ]]; then\n    echo \"expected: $1\"\n    echo \"actual: $2\"\n    return 1\n  fi\n}\n\nwait_for_process() {\n  wait_time=\"$1\"\n  sleep_time=\"$2\"\n  cmd=\"$3\"\n  while [ \"$wait_time\" -gt 0 ]; do\n    if eval \"$cmd\"; then\n      return 0\n    else\n      sleep \"$sleep_time\"\n      wait_time=$((wait_time - sleep_time))\n    fi\n  done\n  return 1\n}\n"
  },
  {
    "path": "tests/e2e/kind-config.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\ncontainerdConfigPatches:\n- |-\n  [plugins.\"io.containerd.grpc.v1.cri\".registry]\n    config_path = \"/etc/containerd/certs.d\"\nnodes:\n- role: control-plane\n  extraMounts:\n  - containerPath: /etc/containerd/certs.d\n    hostPath: tests/e2e/generated_manifests/certs.d\n    readOnly: true\n  - containerPath: /etc/kubernetes/${ENCRYPTION_CONFIG_FILE}\n    hostPath: tests/e2e/${ENCRYPTION_CONFIG_FILE}\n    readOnly: true\n    propagation: None\n  - containerPath: /etc/kubernetes/manifests/kubernetes-kms.yaml\n    hostPath: tests/e2e/generated_manifests/kms.yaml\n    readOnly: true\n    propagation: None\n  - containerPath: /etc/kubernetes/azure.json\n    hostPath: tests/e2e/generated_manifests/azure.json\n    readOnly: true\n    propagation: None\n  kubeadmConfigPatches:\n    - |\n      kind: ClusterConfiguration\n      apiServer:\n        extraArgs:\n          encryption-provider-config: \"/etc/kubernetes/${ENCRYPTION_CONFIG_FILE}\"\n          feature-gates: \"KMSv1=true\"\n        extraVolumes:\n        - name: encryption-config\n          hostPath: \"/etc/kubernetes/${ENCRYPTION_CONFIG_FILE}\"\n          mountPath: \"/etc/kubernetes/${ENCRYPTION_CONFIG_FILE}\"\n          readOnly: true\n          pathType: File\n        - name: sock-path\n          hostPath: \"/opt\"\n          mountPath: \"/opt\"\n"
  },
  {
    "path": "tests/e2e/kms.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: azure-kms-provider\n  namespace: kube-system\n  labels:\n    tier: control-plane\n    component: azure-kms-provider\nspec:\n  priorityClassName: system-node-critical\n  hostNetwork: true\n  containers:\n    - name: azure-kms-provider\n      image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_VERSION}\n      imagePullPolicy: IfNotPresent\n      args:\n        - --keyvault-name=${KEYVAULT_NAME}\n        - --key-name=${KEY_NAME}\n        - --key-version=${KEY_VERSION}\n        - --managed-hsm=false\n        - -v=5\n      env:\n      # setting this env var so we get debug logs in SDK from CI runs\n      - name: AZURE_GO_SDK_LOG_LEVEL\n        value: DEBUG\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsUser: 0\n      ports:\n        - containerPort: 8787\n          protocol: TCP\n      livenessProbe:\n        httpGet:\n          path: /healthz\n          port: 8787\n        failureThreshold: 2\n        periodSeconds: 10\n      resources:\n        requests:\n          cpu: 100m\n          memory: 128Mi\n        limits:\n          cpu: \"4\"\n          memory: 2Gi\n      volumeMounts:\n        - name: etc-kubernetes\n          mountPath: /etc/kubernetes\n        - name: etc-ssl\n          mountPath: /etc/ssl\n          readOnly: true\n        - name: sock\n          mountPath: /opt\n  volumes:\n    - name: etc-kubernetes\n      hostPath:\n        path: /etc/kubernetes\n    - name: etc-ssl\n      hostPath:\n        path: /etc/ssl\n    - name: sock\n      hostPath:\n        path: /opt\n  nodeSelector:\n    kubernetes.io/os: linux\n"
  },
  {
    "path": "tests/e2e/kmsv2-encryption-config.yaml",
    "content": "kind: EncryptionConfiguration\napiVersion: apiserver.config.k8s.io/v1\nresources:\n  - resources:\n      - secrets\n    providers:\n      - kms:\n          apiVersion: v2\n          name: azurekmsprovider\n          endpoint: unix:///opt/azurekms.socket\n"
  },
  {
    "path": "tests/e2e/test.bats",
    "content": "#!/usr/bin/env bats\n\nload helpers\n\nWAIT_TIME=120\nSLEEP_TIME=1\n\nexport ETCD_CA_CERT=/etc/kubernetes/pki/etcd/ca.crt\nexport ETCD_CERT=/etc/kubernetes/pki/etcd/server.crt\nexport ETCD_KEY=/etc/kubernetes/pki/etcd/server.key\n\n@test \"azure keyvault kms plugin is running\" {\n    wait_for_process ${WAIT_TIME} ${SLEEP_TIME} \"kubectl -n kube-system wait --for=condition=Ready --timeout=60s pod -l component=azure-kms-provider\"\n}\n\n@test \"creating secret resource\" {\n    run kubectl create secret generic secret1 -n default --from-literal=foo=bar\n    assert_success\n}\n\n@test \"read the secret resource test\" {\n    result=$(kubectl get secret secret1 -o jsonpath='{.data.foo}' | base64 -d)\n    [[ \"${result//$'\\r'}\" == \"bar\" ]]\n}\n\n@test \"check if secret is encrypted in etcd\" {\n    local pod_name=$(kubectl get pod -n kube-system -l component=etcd -o jsonpath=\"{.items[0].metadata.name}\")\n    run kubectl exec ${pod_name} -n kube-system -- etcdctl --cacert=${ETCD_CA_CERT} --cert=${ETCD_CERT} --key=${ETCD_KEY} get /registry/secrets/default/secret1\n    assert_match \"k8s:enc:kms:v1:azurekmsprovider\" \"${output}\"\n    assert_success\n}\n\n@test \"check if metrics endpoint works\" {\n    local curl_pod_name=curl-$(openssl rand -hex 5)\n    kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels=\"test=metrics_test\" -- tail -f /dev/null\n    kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name}\n\n    local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath=\"{.items[0].status.podIP}\")\n    run kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8095/metrics\n    assert_match \"kms_request_bucket\" \"${output}\"\n    assert_success\n}\n\n@test \"check healthz for kms plugin\" {\n    local curl_pod_name=curl-$(openssl rand -hex 5)\n    kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels=\"test=healthz_test\" -- tail -f /dev/null\n    kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name}\n\n    local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath=\"{.items[0].status.podIP}\")\n    result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz)\n    [[ \"${result//$'\\r'}\" == \"ok\" ]]\n\n    result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz -o /dev/null -w '%{http_code}\\n' -s)\n    [[ \"${result//$'\\r'}\" == \"200\" ]]\n}\n\nteardown_file() {\n    # cleanup\n    run kubectl delete secret secret1 -n default\n\n    run kubectl delete pod -l test=metrics_test --force --grace-period 0\n    run kubectl delete pod -l test=healthz_test --force --grace-period 0\n}\n"
  },
  {
    "path": "tests/e2e/testkmsv2.bats",
    "content": "#!/usr/bin/env bats\n\nload helpers\n\nWAIT_TIME=120\nSLEEP_TIME=1\n\nexport ETCD_CA_CERT=/etc/kubernetes/pki/etcd/ca.crt\nexport ETCD_CERT=/etc/kubernetes/pki/etcd/server.crt\nexport ETCD_KEY=/etc/kubernetes/pki/etcd/server.key\n\nsetup() {\n    # get the initial number of encrypted count\n    local metrics=$(kubectl get --raw /metrics)\n    expected_encyption_count=$(echo \"${metrics}\" | grep -oP 'apiserver_envelope_encryption_key_id_hash_total\\{[^\\}]*transformation_type=\"to_storage\"[^\\}]*\\}\\s+\\K\\d+')    \n}\n\n@test \"azure keyvault kms plugin is running\" {\n    wait_for_process ${WAIT_TIME} ${SLEEP_TIME} \"kubectl -n kube-system wait --for=condition=Ready --timeout=60s pod -l component=azure-kms-provider\"\n}\n\n@test \"creating secret resource\" {\n    run kubectl create secret generic secret1 -n default --from-literal=foo=bar\n    let \"expected_encyption_count++\"\n    assert_success\n}\n\n@test \"read the secret resource test\" {\n    result=$(kubectl get secret secret1 -o jsonpath='{.data.foo}' | base64 -d)\n    [[ \"${result//$'\\r'}\" == \"bar\" ]]\n}\n\n@test \"check if secret is encrypted in etcd\" {\n    local pod_name=$(kubectl get pod -n kube-system -l component=etcd -o jsonpath=\"{.items[0].metadata.name}\")\n    run kubectl exec ${pod_name} -n kube-system -- etcdctl --cacert=${ETCD_CA_CERT} --cert=${ETCD_CERT} --key=${ETCD_KEY} get /registry/secrets/default/secret1\n    assert_match \"k8s:enc:kms:v2:azurekmsprovider\" \"${output}\"\n    assert_success\n}\n\n@test \"check encryption count\" {\n    # The expected_encryption_count value is set in the setup().\n    local metrics=$(kubectl get --raw /metrics)\n    encyption_count=$(echo \"${metrics}\" | grep -oP 'apiserver_envelope_encryption_key_id_hash_total\\{[^\\}]*transformation_type=\"to_storage\"[^\\}]*\\}\\s+\\K\\d+')\n    [[ \"${encyption_count}\" == \"${expected_encyption_count}\" ]]\n}\n\n@test \"check keyID hash used for encrypt/decrypt\" {\n    # expected_hash value is computed based on the key used in CI.\n    # this needs to be updated when we rotate that key.\n    local expected_hash=\"sha256:cbda52be2f8c13d323a3b17c4679118a60b91d29454305e02ee485185b6e386f\"\n    local metrics=$(kubectl get --raw /metrics) \n    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)\n    [[ \"${got_hash_id}\" == \"${expected_hash}\" ]]\n}\n\n@test \"check if metrics endpoint works\" {\n    local curl_pod_name=curl-$(openssl rand -hex 5)\n    kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels=\"test=metrics_test\" -- tail -f /dev/null\n    kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name}\n\n    local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath=\"{.items[0].status.podIP}\")\n    run kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8095/metrics\n    assert_match \"kms_request_bucket\" \"${output}\"\n    assert_success\n}\n\n@test \"check healthz for kms plugin\" {\n    local curl_pod_name=curl-$(openssl rand -hex 5)\n    kubectl run ${curl_pod_name} --image=curlimages/curl:7.75.0 --labels=\"test=healthz_test\" -- tail -f /dev/null\n    kubectl wait --for=condition=Ready --timeout=60s pod ${curl_pod_name}\n\n    local pod_ip=$(kubectl get pod -n kube-system -l component=azure-kms-provider -o jsonpath=\"{.items[0].status.podIP}\")\n    result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz)\n    [[ \"${result//$'\\r'}\" == \"ok\" ]]\n\n    result=$(kubectl exec ${curl_pod_name} -- curl http://${pod_ip}:8787/healthz -o /dev/null -w '%{http_code}\\n' -s)\n    [[ \"${result//$'\\r'}\" == \"200\" ]]\n}\n\nteardown_file() {\n    # cleanup\n    run kubectl delete secret secret1 -n default\n\n    run kubectl delete pod -l test=metrics_test --force --grace-period 0\n    run kubectl delete pod -l test=healthz_test --force --grace-period 0\n}\n"
  },
  {
    "path": "tools/go.mod",
    "content": "module github.com/Azure/kubernetes-kms/tools\n\ngo 1.26.2\n\nrequire github.com/golangci/golangci-lint/v2 v2.7.2\n\nrequire (\n\t4d63.com/gocheckcompilerdirectives v1.3.0 // indirect\n\t4d63.com/gochecknoglobals v0.2.2 // indirect\n\tcodeberg.org/chavacava/garif v0.2.0 // indirect\n\tdev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect\n\tdev.gaijin.team/go/golib v0.6.0 // indirect\n\tgithub.com/4meepo/tagalign v1.4.3 // indirect\n\tgithub.com/Abirdcfly/dupword v0.1.7 // indirect\n\tgithub.com/AdminBenni/iota-mixing v1.0.0 // indirect\n\tgithub.com/AlwxSin/noinlineerr v1.0.5 // indirect\n\tgithub.com/Antonboom/errname v1.1.1 // indirect\n\tgithub.com/Antonboom/nilnil v1.1.1 // indirect\n\tgithub.com/Antonboom/testifylint v1.6.4 // indirect\n\tgithub.com/BurntSushi/toml v1.5.0 // indirect\n\tgithub.com/Djarvur/go-err113 v0.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/MirrexOne/unqueryvet v1.3.0 // indirect\n\tgithub.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.20.0 // indirect\n\tgithub.com/alecthomas/go-check-sumtype v0.3.1 // indirect\n\tgithub.com/alexkohler/nakedret/v2 v2.0.6 // indirect\n\tgithub.com/alexkohler/prealloc v1.0.0 // indirect\n\tgithub.com/alfatraining/structtag v1.0.0 // indirect\n\tgithub.com/alingse/asasalint v0.0.11 // indirect\n\tgithub.com/alingse/nilnesserr v0.2.0 // indirect\n\tgithub.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect\n\tgithub.com/ashanbrown/makezero/v2 v2.1.0 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bkielbasa/cyclop v1.2.3 // indirect\n\tgithub.com/blizzy78/varnamelen v0.8.0 // indirect\n\tgithub.com/bombsimon/wsl/v4 v4.7.0 // indirect\n\tgithub.com/bombsimon/wsl/v5 v5.3.0 // indirect\n\tgithub.com/breml/bidichk v0.3.3 // indirect\n\tgithub.com/breml/errchkjson v0.4.1 // indirect\n\tgithub.com/butuzov/ireturn v0.4.0 // indirect\n\tgithub.com/butuzov/mirror v1.3.0 // indirect\n\tgithub.com/catenacyber/perfsprint v0.10.1 // indirect\n\tgithub.com/ccojocar/zxcvbn-go v1.0.4 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/charithe/durationcheck v0.0.11 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect\n\tgithub.com/charmbracelet/lipgloss v1.1.0 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.8.0 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect\n\tgithub.com/charmbracelet/x/term v0.2.1 // indirect\n\tgithub.com/ckaznocha/intrange v0.3.1 // indirect\n\tgithub.com/curioswitch/go-reassign v0.3.0 // indirect\n\tgithub.com/daixiang0/gci v0.13.7 // indirect\n\tgithub.com/dave/dst v0.27.3 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/denis-tingaikin/go-header v0.5.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.5 // indirect\n\tgithub.com/ettle/strcase v0.2.0 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/fatih/structtag v1.2.0 // indirect\n\tgithub.com/firefart/nonamedreturns v1.0.6 // indirect\n\tgithub.com/fsnotify/fsnotify v1.5.4 // indirect\n\tgithub.com/fzipp/gocyclo v0.6.0 // indirect\n\tgithub.com/ghostiam/protogetter v0.3.17 // indirect\n\tgithub.com/go-critic/go-critic v0.14.2 // indirect\n\tgithub.com/go-toolsmith/astcast v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astcopy v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astequal v1.2.0 // indirect\n\tgithub.com/go-toolsmith/astfmt v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astp v1.1.0 // indirect\n\tgithub.com/go-toolsmith/strparse v1.1.0 // indirect\n\tgithub.com/go-toolsmith/typep v1.1.0 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.4.0 // indirect\n\tgithub.com/go-xmlfmt/xmlfmt v1.1.3 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/godoc-lint/godoc-lint v0.10.2 // indirect\n\tgithub.com/gofrs/flock v0.13.0 // indirect\n\tgithub.com/golang/protobuf v1.5.3 // indirect\n\tgithub.com/golangci/asciicheck v0.5.0 // indirect\n\tgithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect\n\tgithub.com/golangci/go-printf-func-name v0.1.1 // indirect\n\tgithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect\n\tgithub.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect\n\tgithub.com/golangci/misspell v0.7.0 // indirect\n\tgithub.com/golangci/plugin-module-register v0.1.2 // indirect\n\tgithub.com/golangci/revgrep v0.8.0 // indirect\n\tgithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect\n\tgithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/gordonklaus/ineffassign v0.2.0 // indirect\n\tgithub.com/gostaticanalysis/analysisutil v0.7.1 // indirect\n\tgithub.com/gostaticanalysis/comment v1.5.0 // indirect\n\tgithub.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect\n\tgithub.com/gostaticanalysis/nilerr v0.1.2 // indirect\n\tgithub.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/hexops/gotextdiff v1.0.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jgautheron/goconst v1.8.2 // indirect\n\tgithub.com/jingyugao/rowserrcheck v1.1.1 // indirect\n\tgithub.com/jjti/go-spancheck v0.6.5 // indirect\n\tgithub.com/julz/importas v0.2.0 // indirect\n\tgithub.com/karamaru-alpha/copyloopvar v1.2.2 // indirect\n\tgithub.com/kisielk/errcheck v1.9.0 // indirect\n\tgithub.com/kkHAIKE/contextcheck v1.1.6 // indirect\n\tgithub.com/kulti/thelper v0.7.1 // indirect\n\tgithub.com/kunwardeep/paralleltest v1.0.15 // indirect\n\tgithub.com/lasiar/canonicalheader v1.1.2 // indirect\n\tgithub.com/ldez/exptostd v0.4.5 // indirect\n\tgithub.com/ldez/gomoddirectives v0.7.1 // indirect\n\tgithub.com/ldez/grignotin v0.10.1 // indirect\n\tgithub.com/ldez/tagliatelle v0.7.2 // indirect\n\tgithub.com/ldez/usetesting v0.5.0 // indirect\n\tgithub.com/leonklingele/grouper v1.1.2 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/macabu/inamedparam v0.2.0 // indirect\n\tgithub.com/magiconair/properties v1.8.6 // indirect\n\tgithub.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect\n\tgithub.com/manuelarte/funcorder v0.5.0 // indirect\n\tgithub.com/maratori/testableexamples v1.0.1 // indirect\n\tgithub.com/maratori/testpackage v1.1.2 // indirect\n\tgithub.com/matoous/godox v1.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect\n\tgithub.com/mgechev/revive v1.13.0 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/moricho/tparallel v0.3.2 // indirect\n\tgithub.com/muesli/termenv v0.16.0 // indirect\n\tgithub.com/nakabonne/nestif v0.3.1 // indirect\n\tgithub.com/nishanths/exhaustive v0.12.0 // indirect\n\tgithub.com/nishanths/predeclared v0.2.2 // indirect\n\tgithub.com/nunnatsa/ginkgolinter v0.21.2 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/polyfloyd/go-errorlint v1.8.0 // indirect\n\tgithub.com/prometheus/client_golang v1.12.1 // indirect\n\tgithub.com/prometheus/client_model v0.2.0 // indirect\n\tgithub.com/prometheus/common v0.32.1 // indirect\n\tgithub.com/prometheus/procfs v0.7.3 // indirect\n\tgithub.com/quasilyte/go-ruleguard v0.4.5 // indirect\n\tgithub.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect\n\tgithub.com/quasilyte/gogrep v0.5.0 // indirect\n\tgithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect\n\tgithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect\n\tgithub.com/raeperd/recvcheck v0.2.0 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/rogpeppe/go-internal v1.14.1 // indirect\n\tgithub.com/ryancurrah/gomodguard v1.4.1 // indirect\n\tgithub.com/ryanrolds/sqlclosecheck v0.5.1 // indirect\n\tgithub.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect\n\tgithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect\n\tgithub.com/sashamelentyev/interfacebloat v1.1.0 // indirect\n\tgithub.com/sashamelentyev/usestdlibvars v1.29.0 // indirect\n\tgithub.com/securego/gosec/v2 v2.22.11-0.20251204091113-daccba6b93d7 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/sivchari/containedctx v1.0.3 // indirect\n\tgithub.com/sonatard/noctx v0.4.0 // indirect\n\tgithub.com/sourcegraph/go-diff v0.7.0 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/cobra v1.10.2 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/spf13/viper v1.12.0 // indirect\n\tgithub.com/ssgreg/nlreturn/v2 v2.2.1 // indirect\n\tgithub.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/stretchr/testify v1.11.1 // indirect\n\tgithub.com/subosito/gotenv v1.4.1 // indirect\n\tgithub.com/tetafro/godot v1.5.4 // indirect\n\tgithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect\n\tgithub.com/timonwong/loggercheck v0.11.0 // indirect\n\tgithub.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect\n\tgithub.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect\n\tgithub.com/ultraware/funlen v0.2.0 // indirect\n\tgithub.com/ultraware/whitespace v0.2.0 // indirect\n\tgithub.com/uudashr/gocognit v1.2.0 // indirect\n\tgithub.com/uudashr/iface v1.4.1 // indirect\n\tgithub.com/xen0n/gosmopolitan v1.3.0 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/yagipy/maintidx v1.0.0 // indirect\n\tgithub.com/yeya24/promlinter v0.3.0 // indirect\n\tgithub.com/ykadowak/zerologlint v0.1.5 // indirect\n\tgitlab.com/bosi/decorder v0.4.2 // indirect\n\tgo-simpler.org/musttag v0.14.0 // indirect\n\tgo-simpler.org/sloglint v0.11.1 // indirect\n\tgo.augendre.info/arangolint v0.3.1 // indirect\n\tgo.augendre.info/fatcontext v0.9.0 // indirect\n\tgo.uber.org/automaxprocs v1.6.0 // indirect\n\tgo.uber.org/multierr v1.10.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect\n\tgolang.org/x/mod v0.30.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/tools v0.39.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\thonnef.co/go/tools v0.6.1 // indirect\n\tmvdan.cc/gofumpt v0.9.2 // indirect\n\tmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect\n)\n"
  },
  {
    "path": "tools/go.sum",
    "content": "4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=\n4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=\n4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=\n4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncodeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=\ncodeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=\ndev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=\ndev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=\ndev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=\ndev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=\ngithub.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=\ngithub.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ=\ngithub.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4=\ngithub.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=\ngithub.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=\ngithub.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=\ngithub.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=\ngithub.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q=\ngithub.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ=\ngithub.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ=\ngithub.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=\ngithub.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=\ngithub.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=\ngithub.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=\ngithub.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/MirrexOne/unqueryvet v1.3.0 h1:5slWSomgqpYU4zFuZ3NNOfOUxVPlXFDBPAVasZOGlAY=\ngithub.com/MirrexOne/unqueryvet v1.3.0/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg=\ngithub.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=\ngithub.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=\ngithub.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=\ngithub.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=\ngithub.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=\ngithub.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=\ngithub.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=\ngithub.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=\ngithub.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=\ngithub.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=\ngithub.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=\ngithub.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=\ngithub.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=\ngithub.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=\ngithub.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=\ngithub.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=\ngithub.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo=\ngithub.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c=\ngithub.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE=\ngithub.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=\ngithub.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=\ngithub.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=\ngithub.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=\ngithub.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=\ngithub.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=\ngithub.com/bombsimon/wsl/v5 v5.3.0 h1:nZWREJFL6U3vgW/B1lfDOigl+tEF6qgs6dGGbFeR0UM=\ngithub.com/bombsimon/wsl/v5 v5.3.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I=\ngithub.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=\ngithub.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=\ngithub.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=\ngithub.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=\ngithub.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=\ngithub.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=\ngithub.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=\ngithub.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=\ngithub.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=\ngithub.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=\ngithub.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=\ngithub.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=\ngithub.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=\ngithub.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=\ngithub.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=\ngithub.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=\ngithub.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=\ngithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=\ngithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=\ngithub.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=\ngithub.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=\ngithub.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=\ngithub.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=\ngithub.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=\ngithub.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=\ngithub.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=\ngithub.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=\ngithub.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=\ngithub.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=\ngithub.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=\ngithub.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=\ngithub.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=\ngithub.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=\ngithub.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=\ngithub.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=\ngithub.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=\ngithub.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=\ngithub.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=\ngithub.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=\ngithub.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=\ngithub.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=\ngithub.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=\ngithub.com/ghostiam/protogetter v0.3.17 h1:sjGPErP9o7i2Ym+z3LsQzBdLCNaqbYy2iJQPxGXg04Q=\ngithub.com/ghostiam/protogetter v0.3.17/go.mod h1:AivIX1eKA/TcUmzZdzbl+Tb8tjIe8FcyG6JFyemQAH4=\ngithub.com/go-critic/go-critic v0.14.2 h1:PMvP5f+LdR8p6B29npvChUXbD1vrNlKDf60NJtgMBOo=\ngithub.com/go-critic/go-critic v0.14.2/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=\ngithub.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=\ngithub.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=\ngithub.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=\ngithub.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=\ngithub.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=\ngithub.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=\ngithub.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=\ngithub.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=\ngithub.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=\ngithub.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=\ngithub.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=\ngithub.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=\ngithub.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=\ngithub.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=\ngithub.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=\ngithub.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=\ngithub.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=\ngithub.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=\ngithub.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=\ngithub.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=\ngithub.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=\ngithub.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/godoc-lint/godoc-lint v0.10.2 h1:dksNgK+zebnVlj4Fx83CRnCmPO0qRat/9xfFsir1nfg=\ngithub.com/godoc-lint/godoc-lint v0.10.2/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7pDQg4NxMYLcBBsw=\ngithub.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=\ngithub.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=\ngithub.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=\ngithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=\ngithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=\ngithub.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=\ngithub.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=\ngithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=\ngithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=\ngithub.com/golangci/golangci-lint/v2 v2.7.2 h1:AhBC+YeEueec4AGlIbvPym5C70Thx0JykIqXbdIXWx0=\ngithub.com/golangci/golangci-lint/v2 v2.7.2/go.mod h1:pDijleoBu7e8sejMqyZ3L5n6geqe+cVvOAz2QImqqVc=\ngithub.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8=\ngithub.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=\ngithub.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=\ngithub.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=\ngithub.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=\ngithub.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=\ngithub.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=\ngithub.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=\ngithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=\ngithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=\ngithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=\ngithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=\ngithub.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=\ngithub.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=\ngithub.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=\ngithub.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=\ngithub.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=\ngithub.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=\ngithub.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=\ngithub.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=\ngithub.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=\ngithub.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU=\ngithub.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA=\ngithub.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=\ngithub.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=\ngithub.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=\ngithub.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=\ngithub.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=\ngithub.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=\ngithub.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=\ngithub.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=\ngithub.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=\ngithub.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=\ngithub.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=\ngithub.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0=\ngithub.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=\ngithub.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=\ngithub.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=\ngithub.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=\ngithub.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=\ngithub.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w=\ngithub.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=\ngithub.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=\ngithub.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=\ngithub.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=\ngithub.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=\ngithub.com/ldez/gomoddirectives v0.7.1 h1:FaULkvUIG36hj6chpwa+FdCNGZBsD7/fO+p7CCsM6pE=\ngithub.com/ldez/gomoddirectives v0.7.1/go.mod h1:auDNtakWJR1rC+YX7ar+HmveqXATBAyEK1KYpsIRW/8=\ngithub.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=\ngithub.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=\ngithub.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk=\ngithub.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI=\ngithub.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=\ngithub.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=\ngithub.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=\ngithub.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=\ngithub.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=\ngithub.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=\ngithub.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=\ngithub.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=\ngithub.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=\ngithub.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=\ngithub.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=\ngithub.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=\ngithub.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=\ngithub.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc=\ngithub.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=\ngithub.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=\ngithub.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=\ngithub.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mgechev/revive v1.13.0 h1:yFbEVliCVKRXY8UgwEO7EOYNopvjb1BFbmYqm9hZjBM=\ngithub.com/mgechev/revive v1.13.0/go.mod h1:efJfeBVCX2JUumNQ7dtOLDja+QKj9mYGgEZA7rt5u+0=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=\ngithub.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=\ngithub.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=\ngithub.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=\ngithub.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=\ngithub.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=\ngithub.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=\ngithub.com/nunnatsa/ginkgolinter v0.21.2 h1:khzWfm2/Br8ZemX8QM1pl72LwM+rMeW6VUbQ4rzh0Po=\ngithub.com/nunnatsa/ginkgolinter v0.21.2/go.mod h1:GItSI5fw7mCGLPmkvGYrr1kEetZe7B593jcyOpyabsY=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\ngithub.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=\ngithub.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=\ngithub.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=\ngithub.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=\ngithub.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=\ngithub.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=\ngithub.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q=\ngithub.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s=\ngithub.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=\ngithub.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=\ngithub.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA=\ngithub.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=\ngithub.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY=\ngithub.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=\ngithub.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=\ngithub.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=\ngithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=\ngithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=\ngithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=\ngithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=\ngithub.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=\ngithub.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=\ngithub.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=\ngithub.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=\ngithub.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=\ngithub.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=\ngithub.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=\ngithub.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=\ngithub.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=\ngithub.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=\ngithub.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=\ngithub.com/securego/gosec/v2 v2.22.11-0.20251204091113-daccba6b93d7 h1:rZg6IGn0ySYZwCX8LHwZoYm03JhG/cVAJJ3O+u3Vclo=\ngithub.com/securego/gosec/v2 v2.22.11-0.20251204091113-daccba6b93d7/go.mod h1:9sr22NZO5Kfh7unW/xZxkGYTmj2484/fCiE54gw7UTY=\ngithub.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=\ngithub.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=\ngithub.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=\ngithub.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=\ngithub.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=\ngithub.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=\ngithub.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=\ngithub.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=\ngithub.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=\ngithub.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=\ngithub.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=\ngithub.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=\ngithub.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=\ngithub.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=\ngithub.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=\ngithub.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=\ngithub.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=\ngithub.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg=\ngithub.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=\ngithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=\ngithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=\ngithub.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=\ngithub.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=\ngithub.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is=\ngithub.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo=\ngithub.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=\ngithub.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=\ngithub.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=\ngithub.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=\ngithub.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=\ngithub.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=\ngithub.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=\ngithub.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=\ngithub.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=\ngithub.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=\ngithub.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=\ngithub.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=\ngithub.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=\ngithub.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=\ngithub.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=\ngithub.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=\ngithub.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=\ngitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=\ngo-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=\ngo-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=\ngo-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=\ngo-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=\ngo-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=\ngo-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=\ngo.augendre.info/arangolint v0.3.1 h1:n2E6p8f+zfXSFLa2e2WqFPp4bfvcuRdd50y6cT65pSo=\ngo.augendre.info/arangolint v0.3.1/go.mod h1:6ZKzEzIZuBQwoSvlKT+qpUfIbBfFCE5gbAoTg0/117g=\ngo.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE=\ngo.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=\ngo.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=\ngolang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=\ngolang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=\ngolang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=\ngolang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 h1:HDjDiATsGqvuqvkDvgJjD1IgPrVekcSXVVE21JwvzGE=\ngolang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=\ngolang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=\ngolang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=\ngolang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=\ngolang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=\ngolang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=\ngolang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=\nhonnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=\nmvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4=\nmvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s=\nmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI=\nmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "tools/tools.go",
    "content": "//go:build tools\n// +build tools\n\npackage tools\n\nimport (\n\t_ \"github.com/golangci/golangci-lint/v2/cmd/golangci-lint\"\n)\n"
  }
]