diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 5f2292427..000000000 --- a/.drone.yml +++ /dev/null @@ -1,191 +0,0 @@ ---- -kind: pipeline -name: amd64 -type: docker - -platform: - os: linux - arch: amd64 - -steps: - - name: build - image: rancher/dapper:v0.6.0 - commands: - - dapper ci - volumes: - - name: docker - path: /var/run/docker.sock - - - name: integration-test - image: rancher/rancher:v2.8-head - pull: always - privileged: true - commands: - - zypper -n install helm - - scripts/integration-test - - - name: github_binary_release - image: plugins/github-release - settings: - api_key: - from_secret: github_token - prerelease: true - checksum: - - sha256 - checksum_file: CHECKSUMsum-amd64.txt - checksum_flatten: true - files: - - "dist/artifacts/*" - when: - instance: - - drone-publish.rancher.io - ref: - - refs/head/master - - refs/tags/* - event: - - tag - - - name: docker-publish - image: plugins/docker - settings: - dockerfile: package/Dockerfile - password: - from_secret: docker_password - repo: "rancher/rancher-webhook" - tag: "${DRONE_TAG}-amd64" - username: - from_secret: docker_username - when: - instance: - - drone-publish.rancher.io - ref: - - refs/head/master - - refs/tags/* - event: - - tag -volumes: - - name: docker - host: - path: /var/run/docker.sock - ---- -kind: pipeline -name: arm64 -type: docker - -platform: - os: linux - arch: arm64 - -steps: - - name: build - image: rancher/dapper:v0.6.0 - commands: - - dapper ci - volumes: - - name: docker - path: /var/run/docker.sock - - - name: integration-test - image: rancher/rancher:v2.8-head - pull: always - privileged: true - commands: - - zypper -n install helm - - scripts/integration-test - - - name: github_binary_release - image: plugins/github-release - settings: - api_key: - from_secret: github_token - prerelease: true - checksum: - - sha256 - checksum_file: CHECKSUMsum-arm64.txt - checksum_flatten: true - files: - - "dist/artifacts/*" - when: - instance: - - drone-publish.rancher.io - ref: - - refs/head/master - - refs/tags/* - event: - - tag - - - name: docker-publish - image: plugins/docker - settings: - dockerfile: package/Dockerfile - password: - from_secret: docker_password - repo: "rancher/rancher-webhook" - tag: "${DRONE_TAG}-arm64" - username: - from_secret: docker_username - when: - instance: - - drone-publish.rancher.io - ref: - - refs/head/master - - refs/tags/* - event: - - tag - -volumes: - - name: docker - host: - path: /var/run/docker.sock - ---- -kind: pipeline -name: manifest -type: docker - -platform: - os: linux - arch: amd64 - -steps: - - name: manifest - image: plugins/manifest:1.4.0 - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - platforms: - - linux/amd64 - - linux/arm64 - target: "rancher/rancher-webhook:${DRONE_TAG}" - template: "rancher/rancher-webhook:${DRONE_TAG}-ARCH" - when: - instance: - - drone-publish.rancher.io - ref: - - refs/head/master - - refs/tags/* - event: - - tag - -depends_on: - - amd64 - - arm64 - ---- -kind: pipeline -name: fossa -type: docker - -steps: - - name: fossa - image: rancher/drone-fossa:latest - failure: ignore - settings: - api_key: - from_secret: FOSSA_API_KEY - when: - instance: - - drone-publish.rancher.io diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..878b94e85 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,85 @@ +name: CI + +on: + push: + branches: + - release/v* + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + pull_request: + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: CI + strategy: + matrix: + archBox: + - { arch: amd64, vmArch: x64 } + - { arch: arm64, vmArch: arm64 } + runs-on: runs-on,runner=1cpu-linux-${{ matrix.archBox.vmArch }},run-id=${{ github.run_id }} + steps: + - name : Checkout repository + # https://github.com/actions/checkout/releases/tag/v4.1.1 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + # https://github.com/actions/setup-go/releases/tag/v5.0.0 + with: + go-version-file: 'go.mod' + + - name: Checkout rancher/rancher and build the chart + run: | + mkdir -p "${{ runner.temp}}" + pushd "${{ runner.temp}}" + git clone --depth 1 -b release/v2.8 https://github.com/rancher/rancher.git rancherDir-v2.8 + cd rancherDir-v2.8 + ./scripts/chart/build chart + tar cfz "${{ runner.temp }}/rancher.tgz" -C build/chart/rancher . + popd + + - name: install K3d + run: ./.github/workflows/scripts/install-k3d.sh + env: + K3D_VERSION: latest + + - name: ci + run: make ci + + - name: setup cluster + run: ./.github/workflows/scripts/setup-cluster.sh + env: + CLUSTER_NAME: webhook + K3S_VERSION: v1.28.11-k3s1 + ARCH: "${{ matrix.archBox.arch }}" + + - name: import image + run: k3d image import dist/rancher-webhook-image.tar -c webhook + + - name: start rancher + run: ./.github/workflows/scripts/start-rancher.sh + env: + CHART_PATH: "${{ runner.temp }}/rancher.tgz" + RANCHER_IMAGE_TAG: "v2.8-head" + VERSION: "2.8" + + - name: get vars + run: cat dist/image_tag >> $GITHUB_ENV + + - name: Run integration tests + run: ./.github/workflows/scripts/integration-test-ci + env: + ARCH: "${{ matrix.archBox.arch }}" + CLUSTER_NAME: webhook + IMAGE_REPO: rancher/webhook + IMAGE_TAG: "${{ env.IMAGE_TAG }}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..084ee5652 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,221 @@ +name: release + +on: + push: + tags: + - v* + workflow_dispatch: + +permissions: + contents: write + +env: + REGISTRY: docker.io + REPO: rancher + +jobs: + build: + name: build and package + runs-on: ubuntu-latest + strategy: + matrix: + arch: + - amd64 + - arm64 + steps: + + - name : Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + # https://github.com/actions/checkout/releases/tag/v4.1.1 + + - name: Setup Go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + # https://github.com/actions/setup-go/releases/tag/v5.0.0 + with: + go-version-file: 'go.mod' + + - name: Build and package + run: | + ./scripts/build + mkdir -p dist/artifacts + cp bin/webhook dist/artifacts/webhook-linux-${{ matrix.arch }} + env: + ARCH: "${{ matrix.arch}}" + GOARCH: "${{ matrix.arch}}" + + - name: Generate checksum files + run: | + ls -lR dist + cd dist/artifacts + sha256sum webhook-linux-${{ matrix.arch }} > sha256sum-${{ matrix.arch }}.txt + + - name: Upload artifacts + # https://github.com/actions/upload-artifact/commit/65462800fd760344b1a7b4382951275a0abb4808 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 + with: + name: webhook-artifacts-${{ matrix.arch }} + path: | + dist/artifacts/webhook-linux-${{ matrix.arch }} + dist/artifacts/sha256sum-${{ matrix.arch }}.txt + dist/artifacts/rancher-webhook-*.tgz + + release: + needs: build + runs-on: ubuntu-latest + steps: + + - name : Checkout repository + # https://github.com/actions/checkout/releases/tag/v4.1.1 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: package-helm + run: ./scripts/package-helm + + - name: Download the amd64 artifacts + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + # https://github.com/actions/download-artifact/releases/tag/v4.1.7 + with: + name: webhook-artifacts-amd64 + path: dist/artifacts + + - name: Download the arm64 artifacts + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + # https://github.com/actions/download-artifact/releases/tag/v4.1.7 + with: + name: webhook-artifacts-arm64 + path: dist/artifacts + + - name: Get the version + run: | + source ./scripts/version + echo "TAG=$(echo $TAG | sed 's/-amd64$//')" >> $GITHUB_ENV + + - name: Upload the files + run: | + ls -lR dist + cd dist/artifacts + gh --repo "${{ github.repository }}" release create ${{ github.ref_name }} --prerelease --verify-tag --generate-notes webhook-linux-* sha256sum-*.txt rancher-webhook*.tgz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + image: + permissions: + contents: read + id-token: write + strategy: + matrix: + arch: + - amd64 + - arm64 + name: Build and push Webhook images + runs-on: ubuntu-latest + needs: build + steps: + - name : Checkout repository + # https://github.com/actions/checkout/releases/tag/v4.1.1 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Download the artifacts + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + # https://github.com/actions/download-artifact/releases/tag/v4.1.7 + with: + name: webhook-artifacts-${{ matrix.arch }} + path: dist/artifacts + + - name: Move binary to bin/ + run: | + mkdir -p bin/ + cp -v dist/artifacts/webhook-linux-${{ matrix.arch }} bin/webhook + chmod +x bin/webhook + + - name: "Read vault secrets" + uses: rancher-eio/read-vault-secrets@main + with: + secrets: | + secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; + secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD + + - name: Set up QEMU + uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3.1.0 + # https://github.com/docker/setup-qemu-action/releases/tag/v3.1.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 + # https://github.com/docker/setup-buildx-action/releases/tag/v3.4.0 + + - name: Log in to the Container registry + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + # https://github.com/docker/login-action/releases/tag/v3.2.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_PASSWORD }} + + - name: Build and push the webhook image + id: build + uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 + # https://github.com/docker/build-push-action/releases/tag/v6.3.0 + with: + context: . + file: ./package/Dockerfile + platforms: "linux/${{ matrix.arch }}" + outputs: type=image,name=${{ env.REPO }}/rancher-webhook,push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + # https://github.com/actions/upload-artifact/releases/tag/v4.3.3 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + permissions: + id-token: write + runs-on: ubuntu-latest + needs: image + steps: + - name: Download digests + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + # https://github.com/actions/download-artifact/releases/tag/v4.1.7 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 + # https://github.com/docker/setup-buildx-action/releases/tag/v3.4.0 + + - name: "Read vault secrets" + uses: rancher-eio/read-vault-secrets@main + with: + secrets: | + secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ; + secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD + + - name: Log in to the Container registry + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + # https://github.com/docker/login-action/releases/tag/v3.2.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_PASSWORD }} + + # setup tag name + - if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: | + echo TAG_NAME=$(echo $GITHUB_REF | sed -e "s|refs/tags/||") >> $GITHUB_ENV + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ env.REPO }}/rancher-webhook:${{ env.TAG_NAME }} \ + $(printf '${{ env.REPO }}/rancher-webhook@sha256:%s ' *) diff --git a/.github/workflows/scripts/install-k3d.sh b/.github/workflows/scripts/install-k3d.sh new file mode 100755 index 000000000..6993f3312 --- /dev/null +++ b/.github/workflows/scripts/install-k3d.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eu + +REPO_URL=https://github.com/rancher/k3d +K3D_URL=https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh + +install_k3d(){ + if [ -z "${K3D_VERSION:-}" -o "${K3D_VERSION:-}" = "latest" ] ; then + K3D_VERSION=$(curl -Ls -o /dev/null -w %{url_effective} "${REPO_URL}/releases/latest" | grep -oE "[^/]+$") + fi + echo -e "Downloading k3d@${K3D_VERSION} from ${K3D_URL}" + curl --silent --fail ${K3D_URL} | TAG=${K3D_VERSION} bash +} + +install_k3d + +k3d version diff --git a/.github/workflows/scripts/integration-test-ci b/.github/workflows/scripts/integration-test-ci new file mode 100755 index 000000000..0a73b61c1 --- /dev/null +++ b/.github/workflows/scripts/integration-test-ci @@ -0,0 +1,85 @@ +#!/bin/bash +set -eu + +cd $(dirname $0)/../../.. +DIST_DIR="$PWD"/dist + +source ./scripts/version +# Source tags file to get the last built tags +source ./dist/tags + +set -o pipefail + +source ./.github/workflows/scripts/try.sh + +set +e + +# Wait for rancher to start up +try --waitmsg "Waiting for rancher to start" --failmsg "No rancher here" kubectl rollout status --watch=true --timeout=10s -n cattle-system deploy/rancher +echo "Rancher deployed" + +# Wait for the system to spawn a rancher-webhook deployment +webhook_deployment_created() { + kubectl get deployments -n cattle-system | grep rancher-webhook +} +try --max 48 --delay 5 --waitmsg "Waiting for a rancher-webhook deployment to be created" --failmsg "Deployment creation failed" webhook_deployment_created + +try --waitmsg "Waiting for rancher/webhook to be deployed" --failmsg "No rancher/webhook here" kubectl rollout status --watch=true --timeout=10s -n cattle-system deploy/rancher-webhook +echo "Rancher deployed" + +webhook_deployed() { + status=$(kubectl get apps.catalog.cattle.io -n cattle-system rancher-webhook -o jsonpath="{@.status.summary.state}") && [[ "$status" == "deployed" ]] +} + +# Wait for Rancher to deploy rancher-webhook. +try --waitmsg "Waiting for webhook to be deployed (2)" webhook_deployed +echo "Webhook deployed" + +# Shut down the core rancher part, but leave the rest of the rancher environment running +set -e +echo "Shutting down core rancher" +kubectl scale deploy rancher -n cattle-system --replicas=0 +kubectl wait pods -l app=rancher --for=delete -n cattle-system +# Make sure the webhook recreates configurations on startup +kubectl delete validatingwebhookconfiguration rancher.cattle.io +kubectl delete mutatingwebhookconfiguration rancher.cattle.io + +echo "Uploading new webhook image" + +# Install the webhook chart we just built. +upgrade_rancher_webhook() { + helm upgrade rancher-webhook ./dist/artifacts/rancher-webhook-${HELM_CHART_VERSION}.tgz -n cattle-system \ + --wait --timeout=120s --set image.repository="${IMAGE_REPO}" --set image.tag="${IMAGE_TAG}" --reuse-values --debug +} + +set +e + +try --max 3 --delay 2 --waitmsg "Upgrading Webhook" --failmsg "Failed to upgrade webhook" upgrade_rancher_webhook + +x=$(kubectl get pods -n cattle-system -l app=rancher-webhook --no-headers --field-selector='status.phase!=RUNNING' --output custom-columns=NODE:.metadata.name | head -n 1) +if [ -n "$x" ] ; then + echo "Logs for failed rancher-webhook $x ": + kubectl get pods -n cattle-system -l app=rancher-webhook + kubectl get pod "$x" -n cattle-system + kubectl logs pod/"$x" -n cattle-system + kubectl describe pod/"$x" -n cattle-system + try --max 4 --failmsg "Couldn't helm upgrade rancher-webhook" upgrade_rancher_webhook +fi + +# Done trying things, so reinstate 'set -e' +set -e + +./bin/rancher-webhook-integration.test -test.v -test.run IntegrationTest + +# Install the webhook chart with new ports. +helm upgrade rancher-webhook ./dist/artifacts/rancher-webhook-${HELM_CHART_VERSION}.tgz -n cattle-system \ + --wait --reuse-values --set port=443 + +# Test that the ports are set as expected and run a single integration test to verify the webhook is still accessible. +./bin/rancher-webhook-integration.test -test.v -test.run PortTest +./bin/rancher-webhook-integration.test -test.v -test.run IntegrationTest -testify.m TestGlobalRole + +# Scale down rancher-webhook so that we can run tests on the FailurePolicy. +kubectl scale deploy rancher-webhook -n cattle-system --replicas=0 +kubectl wait pods -l app=rancher-webhook --for=delete -n cattle-system +./bin/rancher-webhook-integration.test -test.v -test.run FailurePolicyTest diff --git a/.github/workflows/scripts/setup-cluster.sh b/.github/workflows/scripts/setup-cluster.sh new file mode 100755 index 000000000..2930e775e --- /dev/null +++ b/.github/workflows/scripts/setup-cluster.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +set -e + +source ./scripts/version +set -x + +echo $TAG + +if [ -z "$CLUSTER_NAME" ]; then + echo "CLUSTER_NAME must be specified when setting up a cluster" + exit 1 +fi + +if [ -z "$K3S_VERSION" ]; then + echo "K3S_VERSION must be specified when setting up a cluster, use $(k3d version list k3s) to find valid versions" + exit 1 +fi + +# waits until all nodes are ready +wait_for_nodes(){ + timeout=120 + start_time=$(date +%s) + echo "wait until all agents are ready" + while : + do + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + if [ $elapsed_time -ge $timeout ]; then + echo "Timeout reached, exiting..." + exit 1 + fi + + readyNodes=1 + statusList=$(kubectl get nodes --no-headers | awk '{ print $2}') + # shellcheck disable=SC2162 + while read status + do + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + if [ $elapsed_time -ge $timeout ]; then + echo "Timeout reached, exiting..." + exit 1 + fi + if [ "$status" == "NotReady" ] || [ "$status" == "" ] + then + readyNodes=0 + break + fi + done <<< "$(echo -e "$statusList")" + # all nodes are ready; exit + if [[ $readyNodes == 1 ]] + then + break + fi + sleep 1 + done +} + +k3d registry create gha -p 42765 +k3d cluster create $CLUSTER_NAME --servers 1 --agents 1 \ + --registry-use gha:42765 \ + --image "rancher/k3s:${K3S_VERSION}" --api-port 6550 + +wait_for_nodes + +echo "k3d cluster $CLUSTER_NAME is ready" + +kubectl cluster-info --context k3d-${CLUSTER_NAME} +kubectl config use-context k3d-${CLUSTER_NAME} +kubectl get nodes -o wide +kubectl get pods -A diff --git a/.github/workflows/scripts/start-rancher.sh b/.github/workflows/scripts/start-rancher.sh new file mode 100755 index 000000000..8f41b7014 --- /dev/null +++ b/.github/workflows/scripts/start-rancher.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +flip() { + echo $1 + exit 1 +} + +kubectl get ns | grep -s cattle && flip 'rancher already installed?' + +set -exu + +set +e +helm repo add cert-manager https://charts.jetstack.io +helm repo add rancher-latest https://releases.rancher.com/server-charts/latest +helm repo add jetstack https://charts.jetstack.io +set -e + +helm repo update + +helm upgrade --install cert-manager --namespace cert-manager cert-manager/cert-manager --set installCRDs=true --create-namespace --wait --timeout=10m + +# kubectl get pods --namespace cert-manager +kubectl rollout status --namespace cert-manager deploy/cert-manager --timeout 1m + +# Chart based + +helm upgrade --install rancher "$CHART_PATH" --namespace cattle-system --set replicas=1 --set hostname=localhost --wait --timeout=10m --create-namespace --version "$VERSION" --set rancherImage=rancher/rancher --set rancherImageTag="$RANCHER_IMAGE_TAG" diff --git a/.github/workflows/scripts/sync-deps.sh b/.github/workflows/scripts/sync-deps.sh new file mode 100755 index 000000000..aa581773c --- /dev/null +++ b/.github/workflows/scripts/sync-deps.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# + +set -e + +RANCHER_REPO_DIR=$1 +# File to write the changes to +CHANGES_FILE=${2:-/dev/null} + +PKG_APIS="github.com/rancher/rancher/pkg/apis" +DEPS_TO_SYNC=" + github.com/rancher/dynamiclistener + github.com/rancher/lasso + github.com/rancher/wrangler + github.com/rancher/wrangler/v2 + github.com/rancher/wrangler/v3 +" + +if [ -z "$RANCHER_REPO_DIR" ]; then + usage + exit 1 +fi + +usage() { + echo "$0 []" +} + +update_dep() { + module=$1 + old_version=$2 + new_version=$3 + + echo "Version mismatch for $module (rancher=$new_version, webhook=$old_version) detected" + go mod edit -require="$module@$new_version" + printf '**%s**\n`%s` => `%s`\n' "$module" "$old_version" "$new_version" >> "$CHANGES_FILE" +} + +rancher_deps=$(cd "$RANCHER_REPO_DIR" && go mod graph) +webhook_deps=$(go mod graph) + +rancher_ref=$(cd "$RANCHER_REPO_DIR" && git rev-parse HEAD) +if ! rancher_pkg_apis_version=$(go mod download -json "github.com/rancher/rancher/pkg/apis@$rancher_ref" | jq -r '.Version'); then + echo "Unable to get version of $PKG_APIS" + exit 1 +fi +webhook_pkg_apis_version=$(echo "$webhook_deps" | grep "^$PKG_APIS@\w*\S" | head -n 1 | cut -d' ' -f1 | cut -d@ -f2) + +if [ "$rancher_pkg_apis_version" != "$webhook_pkg_apis_version" ]; then + update_dep "$PKG_APIS" "$webhook_pkg_apis_version" "$rancher_pkg_apis_version" +fi + +for dep in $DEPS_TO_SYNC; do + if ! rancher_version=$(echo "$rancher_deps" | grep "^$dep@\w*\S"); then + continue + fi + + if ! webhook_version=$(echo "$webhook_deps" | grep "^$dep@\w*\S"); then + continue + fi + + rancher_version=$(echo "$rancher_version" | head -n 1 | cut -d' ' -f1 | cut -d@ -f2) + webhook_version=$(echo "$webhook_version" | head -n 1 | cut -d' ' -f1 | cut -d@ -f2) + if [ "$rancher_version" = "$webhook_version" ]; then + continue + fi + + update_dep "$dep" "$webhook_version" "$rancher_version" +done + +echo "Running go mod tidy" +go mod tidy diff --git a/.github/workflows/scripts/try.sh b/.github/workflows/scripts/try.sh new file mode 100755 index 000000000..1abc265e0 --- /dev/null +++ b/.github/workflows/scripts/try.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Usage: try [--max [30] --delay [2] --waitmsg 'retrying' --failmsg 'retrying' ] command... + +try() { + local max=30 + local delay=2 + local waitmsg="retrying" + local failmsg="" + while [[ $# -gt 0 ]] && [[ $1 == -* ]]; do + case "$1" in + --max) + max=$2 + shift + ;; + --delay) + delay=$2 + shift + ;; + --waitmsg) + waitmsg=$2 + shift + ;; + --failmsg) + failmsg=$2 + shift + ;; + --) + shift + break + ;; + *) + printf "Usage error: unknown flag '%s'" "$1" >&2 + return 1 + ;; + esac + shift + done + + local count=0 + while true; do + $* + status=$? + count=$(expr $count + 1) + if [[ $status -eq 0 ]]; then + break + elif [[ $count -ge $max ]]; then + if [ -n "$failmsg" ] ; then + echo $failmsg + else + echo "Failed to run <$*>" + fi + exit 1 + break + fi + echo "$waitmsg on try $count/$max" + sleep $delay + done +} diff --git a/.github/workflows/sync-deps.yaml b/.github/workflows/sync-deps.yaml new file mode 100644 index 000000000..2028df8dc --- /dev/null +++ b/.github/workflows/sync-deps.yaml @@ -0,0 +1,108 @@ +name: Sync dependencies + +on: + workflow_dispatch: + inputs: + rancher_ref: + description: "Version of rancher/rancher to compare" + required: true + default: "release/v2.8" + rancher_repository: + description: "Repository for rancher/rancher" + required: true + default: "rancher/rancher" + +env: + RANCHER_REF: "${{ github.event.inputs.rancher_ref }}" + WEBHOOK_REF: "${{ github.ref_name }}" + +permissions: + contents: read + # Needed to access to vault + id-token: write + +jobs: + sync: + name: Sync dependencies + runs-on: ubuntu-latest + steps: + - uses: rancher-eio/read-vault-secrets@main + with: + secrets: | + secret/data/github/repo/${{ github.repository }}/github/app-credentials appId | APP_ID ; + secret/data/github/repo/${{ github.repository }}/github/app-credentials privateKey | PRIVATE_KEY + + # Fetch github token just for the webhook repository + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ env.APP_ID }} + private-key: ${{ env.PRIVATE_KEY }} + repositories: | + webhook + + - name : Checkout webhook repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: "${{ env.WEBHOOK_REF }}" + path: webhook + token: ${{ steps.app-token.outputs.token }} + # Allow making git push request later on + persist-credentials: true + + - name : Checkout rancher repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: "${{ github.event.inputs.rancher_repository }}" + ref: "${{ env.RANCHER_REF }}" + path: rancher + + - name: Install dependencies + run: sudo snap install yq --channel=v4/stable + + - name: Configure the committer + run: | + cd webhook + user_id=$(gh api "/users/$APP_USER" --jq .id) + git config --global user.name "$APP_USER" + git config --global user.email "${user_id}+${APP_USER}@users.noreply.github.com" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_USER: "${{ steps.app-token.outputs.app-slug }}[bot]" + + - name: Run sync-deps script + run: | + cd webhook + BRANCH="sync-deps-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + echo "BRANCH=${BRANCH}" >> $GITHUB_ENV + git checkout -b "$BRANCH" + ./.github/workflows/scripts/sync-deps.sh ../rancher "changes.md" + if [ -f changes.md ]; then + git add go.mod go.sum + git commit -m "Sync dependencies" + git push origin "$BRANCH" + fi + + - name: Create PR + # Only create the PR if changes were detected + if: ${{ hashFiles('webhook/changes.md') != '' }} + run: | + cd webhook + changes=$(cat changes.md) + body=$(cat </out.txt 2>&1; \ fi RUN GOBIN=/usr/local/bin go install github.com/golang/mock/mockgen@v1.6.0 -ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS +ENV DAPPER_ENV REPO TAG CROSS ENV DAPPER_SOURCE /go/src/github.com/rancher/webhook/ ENV DAPPER_OUTPUT ./bin ./dist ENV DAPPER_DOCKER_SOCKET true diff --git a/README.md b/README.md index 88794767d..1bbece6fb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -Rancher Webhook -======== +# Rancher Webhook + Rancher webhook is both a validating admission webhook and a mutating admission webhook for Kubernetes. [Explanation of Webhooks in Kubernetes]( https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) ## Background + The Rancher Webhook is an instance of a Kubernetes admission controller. Admission controllers are a standard Kubernetes mechanism to intercept requests to a cluster's API server and perform validation or mutation of resources prior to their persistence in @@ -26,62 +27,63 @@ It handles TLS certificates and the management of associated Secrets for secure Documentation on each of the resources that are validated or mutated can be found in `docs.md`. It is recommended to review the [kubernetes docs on CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) as well. -Docs are added by creating a resource-specific readme in the directory of your mutator/validator (e.x. `pkg/resources/$GROUP/$GROUP_VERSION/$RESOURCE/$READABLE_RESOURCE.MD`). +Docs are added by creating a resource-specific readme in the directory of your mutator/validator (e.x. `pkg/resources/$GROUP/$GROUP_VERSION/$RESOURCE/$READABLE_RESOURCE.MD`). These files should be named with a human-readable version of the resource's name. For example, `GlobalRole.md`. Running `go generate` will then aggregate these into the user-facing docs in the `docs.md` file. ## Webhooks -Rancher-Webhook is composed of multiple [WebhookHandlers](pkg/admission/admission.go) which is used when creating [ValidatingWebhooks](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#validatingwebhook-v1-admissionregistration-k8s-io) and [MutatingWebhooks](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#mutatingwebhook-v1-admissionregistration-k8s-io). + +Rancher-Webhook is composed of multiple [WebhookHandlers](pkg/admission/admission.go) which is used when creating [ValidatingWebhooks](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#validatingwebhook-v1-admissionregistration-k8s-io) and [MutatingWebhooks](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#mutatingwebhook-v1-admissionregistration-k8s-io). ``` golang // WebhookHandler base interface for both ValidatingAdmissionHandler and MutatingAdmissionHandler. // WebhookHandler is used for creating new http.HandlerFunc for each Webhook. type WebhookHandler interface { - // GVR returns GroupVersionResource that the Webhook reviews. - // The returned GVR is used to define the route for accessing this webhook as well as creating the Webhooks Name. - // Thus the GVR returned must be unique from other WebhookHandlers of the same type e.g.(Mutating or Validating). - // If a WebhookHandler desires to monitor all resources in a group the Resource defined int he GVR should be "*". - // If a WebhookHandler desires to monitor a core type the Group can be left empty "". - GVR() schema.GroupVersionResource - - // Operations returns list of operations that this WebhookHandler supports. - // Handlers will only be sent request with operations that are contained in the provided list. - Operations() []v1.OperationType - - // Admit handles the webhook admission request sent to this webhook. - // The response returned by the WebhookHandler will be forwarded to the kube-api server. - // If the WebhookHandler can not accurately evaluate the request it should return an error. - Admit(*Request) (*admissionv1.AdmissionResponse, error) + // GVR returns GroupVersionResource that the Webhook reviews. + // The returned GVR is used to define the route for accessing this webhook as well as creating the Webhooks Name. + // Thus the GVR returned must be unique from other WebhookHandlers of the same type e.g.(Mutating or Validating). + // If a WebhookHandler desires to monitor all resources in a group the Resource defined int he GVR should be "*". + // If a WebhookHandler desires to monitor a core type the Group can be left empty "". + GVR() schema.GroupVersionResource + + // Operations returns list of operations that this WebhookHandler supports. + // Handlers will only be sent request with operations that are contained in the provided list. + Operations() []v1.OperationType + + // Admit handles the webhook admission request sent to this webhook. + // The response returned by the WebhookHandler will be forwarded to the kube-api server. + // If the WebhookHandler can not accurately evaluate the request it should return an error. + Admit(*Request) (*admissionv1.AdmissionResponse, error) } // ValidatingAdmissionHandler is a handler used for creating a ValidationAdmission Webhook. type ValidatingAdmissionHandler interface { - WebhookHandler - - // ValidatingWebhook returns a list of configurations to route to this handler. - // - // This functions allows ValidatingAdmissionHandler to perform modifications to the default configuration if needed. - // A default configuration can be made using NewDefaultValidatingWebhook(...) - // Most Webhooks implementing ValidatingWebhook will only return one configuration. - ValidatingWebhook(clientConfig v1.WebhookClientConfig) []v1.ValidatingWebhook + WebhookHandler + + // ValidatingWebhook returns a list of configurations to route to this handler. + // + // This functions allows ValidatingAdmissionHandler to perform modifications to the default configuration if needed. + // A default configuration can be made using NewDefaultValidatingWebhook(...) + // Most Webhooks implementing ValidatingWebhook will only return one configuration. + ValidatingWebhook(clientConfig v1.WebhookClientConfig) []v1.ValidatingWebhook } // MutatingAdmissionHandler is a handler used for creating a MutatingAdmission Webhook. type MutatingAdmissionHandler interface { - WebhookHandler - - // MutatingWebhook returns a list of configurations to route to this handler. - // - // MutatingWebhook functions allows MutatingAdmissionHandler to perform modifications to the default configuration if needed. - // A default configuration can be made using NewDefaultMutatingWebhook(...) - // Most Webhooks implementing MutatingWebhook will only return one configuration. - MutatingWebhook(clientConfig v1.WebhookClientConfig) []v1.MutatingWebhook + WebhookHandler + + // MutatingWebhook returns a list of configurations to route to this handler. + // + // MutatingWebhook functions allows MutatingAdmissionHandler to perform modifications to the default configuration if needed. + // A default configuration can be made using NewDefaultMutatingWebhook(...) + // Most Webhooks implementing MutatingWebhook will only return one configuration. + MutatingWebhook(clientConfig v1.WebhookClientConfig) []v1.MutatingWebhook } - - ``` + Any admission controller, as an app, consists of two main things: + 1. The configuration which describes the resources and actions for which the webhook is active. This configuration also references the Kubernetes service which directs traffic to the actual web-server (this Webhook project) that does the work. The configuration exists as [ValidatingWebhookConfiguration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#validatingwebhookconfiguration-v1-admissionregistration-k8s-io) @@ -92,8 +94,10 @@ _Note: both the ValidatingWebhookConfiguration and MutatingWebhookConfiguration webhook startup, not beforehand._ All objects with custom validation logic exist in the `pkg/resources` package. -### Validation -Both Mutating and Validating webhooks can be used for basic validation of user input. + +### Validation + +Both Mutating and Validating webhooks can be used for basic validation of user input. [A ValidatingAdmissionHandler should be used when validation is needed after all mutations are completed.](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#what-are-admission-webhooks) For example, there is a @@ -105,35 +109,39 @@ ensures that each rule of the GlobalRole has at least one verb. If it does not, changes returns a response value with `Allowed` set to false. ```go - for _, rule := range newGR.Rules { - if len(rule.Verbs) == 0 { - return &admissionv1.AdmissionResponse{ - Result: &metav1.Status{ - Status: "Failure", - Message: "GlobalRole.Rules: PolicyRules must have at least one verb", - Reason: metav1.StatusReasonBadRequest, - Code: http.StatusBadRequest, - }, - Allowed: false, - }, nil - } - } + for _, rule := range newGR.Rules { + if len(rule.Verbs) == 0 { + return &admissionv1.AdmissionResponse{ + Result: &metav1.Status{ + Status: "Failure", + Message: "GlobalRole.Rules: PolicyRules must have at least one verb", + Reason: metav1.StatusReasonBadRequest, + Code: http.StatusBadRequest, + }, + Allowed: false, + }, nil + } + } ``` This logic is the main part of object inspection and admission control. ### Mutation + A MutatingAdmissionHandler should be used when the data being updated needs to be modified. All modifications must be recorded using a [JSONpatch](https://jsonpatch.com/). This can be done easily using the `pkg/patch` library for example the [MutatingAdmissionHandler for secrets](pkg/resources/core/v1/secret/mutator.go) add the creator's username as an annotation then creates a patch that is attached to the response. -```go - newSecret.Annotations[auth.CreatorIDAnn] = request.UserInfo.Username - response := &admissionv1.AdmissionResponse{} - if err := patch.CreatePatch(request.Object.Raw, newSecret, response); err != nil { - return nil, fmt.Errorf("failed to create patch: %w", err) - } - response.Allowed = true - return response, nil + +```go + newSecret.Annotations[auth.CreatorIDAnn] = request.UserInfo.Username + response := &admissionv1.AdmissionResponse{} + if err := patch.CreatePatch(request.Object.Raw, newSecret, response); err != nil { + return nil, fmt.Errorf("failed to create patch: %w", err) + } + response.Allowed = true + return response, nil ``` + ### Creating a WebhookHandler + The `pkg/server` package is the main setup package of the Webhook server itself. The package defines the rules for resources and actions for which the Webhook will be active. These are later brought to life as cluster-wide Kubernetes resources (ValidatingWebhookConfiguration and MutatingWebhookConfiguration). @@ -155,22 +163,26 @@ make ## Development - 1. Get a new address that forwards to `https://localhost:9443` using ngrok. -```bash -ngrok http https://localhost:9443 -``` + + ```bash + ngrok http https://localhost:9443 + ``` + 2. Run the webhook with the given address and the kubeconfig for the cluster hosting Rancher. -``` bash -export KUBECONFIG= -export CATTLE_WEBHOOK_URL="https://.ngrok.io" -./bin/webhook -``` + + ``` bash + export KUBECONFIG= + export CATTLE_WEBHOOK_URL="https://.ngrok.io" + ./bin/webhook + ``` + After 15 seconds the webhook will update the `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` in the Kubernetes cluster to point at the locally running instance. > :warning: Kubernetes API server authentication will not work with ngrok. ## License + Copyright (c) 2019-2021 [Rancher Labs, Inc.](http://rancher.com) Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/charts/rancher-webhook/Chart.yaml b/charts/rancher-webhook/Chart.yaml index 13a9efba9..1c417aeb0 100644 --- a/charts/rancher-webhook/Chart.yaml +++ b/charts/rancher-webhook/Chart.yaml @@ -11,7 +11,4 @@ annotations: catalog.cattle.io/os: linux catalog.cattle.io/permits-os: linux,windows catalog.cattle.io/rancher-version: ">= 2.8.0-0 < 2.9.0-0" - catalog.cattle.io/kube-version: ">= 1.23.0-0 < 1.28.0-0" -dependencies: - - name: capi - condition: capi.enabled + catalog.cattle.io/kube-version: ">= 1.23.0-0 < 1.29.0-0" diff --git a/charts/rancher-webhook/charts/capi/Chart.yaml b/charts/rancher-webhook/charts/capi/Chart.yaml deleted file mode 100644 index 72d4b79d2..000000000 --- a/charts/rancher-webhook/charts/capi/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v2 -name: capi -version: 0.0.0 -appVersion: 0.0.0 diff --git a/charts/rancher-webhook/charts/capi/templates/service.yaml b/charts/rancher-webhook/charts/capi/templates/service.yaml deleted file mode 100644 index de7c255c4..000000000 --- a/charts/rancher-webhook/charts/capi/templates/service.yaml +++ /dev/null @@ -1,13 +0,0 @@ -kind: Service -apiVersion: v1 -metadata: - name: webhook-service - annotations: - need-a-cert.cattle.io/secret-name: rancher-webhook-tls -spec: - ports: - - name: https - port: 443 - targetPort: {{ .Values.port | default 8777 }} - selector: - app: rancher-webhook diff --git a/charts/rancher-webhook/templates/deployment.yaml b/charts/rancher-webhook/templates/deployment.yaml index a0cc77c2d..b8a7201da 100644 --- a/charts/rancher-webhook/templates/deployment.yaml +++ b/charts/rancher-webhook/templates/deployment.yaml @@ -12,15 +12,8 @@ spec: labels: app: rancher-webhook spec: - {{- if or .Values.capi.enabled $auth.clientCA }} - volumes: - {{- end }} - {{- if .Values.capi.enabled }} - - name: tls - secret: - secretName: rancher-webhook-tls - {{- end }} {{- if $auth.clientCA }} + volumes: - name: client-ca secret: secretName: client-ca @@ -40,14 +33,10 @@ spec: - env: - name: STAMP value: "{{.Values.stamp}}" - - name: ENABLE_CAPI - value: "{{.Values.capi.enabled}}" - name: ENABLE_MCM value: "{{.Values.mcm.enabled}}" - name: CATTLE_PORT value: {{.Values.port | default 9443 | quote}} - - name: CATTLE_CAPI_PORT - value: {{.Values.capi.port | default 8777 | quote}} - name: NAMESPACE valueFrom: fieldRef: @@ -62,8 +51,6 @@ spec: ports: - name: https containerPort: {{ .Values.port | default 9443 }} - - name: capi-https - containerPort: {{ .Values.capi.port | default 8777}} startupProbe: httpGet: path: "/healthz" @@ -77,15 +64,8 @@ spec: port: "https" scheme: "HTTPS" periodSeconds: 5 - {{- if or .Values.capi.enabled $auth.clientCA }} - volumeMounts: - {{- end }} - {{- if .Values.capi.enabled }} - - name: tls - mountPath: /tmp/k8s-webhook-server/serving-certs - readOnly: true - {{- end }} {{- if $auth.clientCA }} + volumeMounts: - name: client-ca mountPath: /tmp/k8s-webhook-server/client-ca readOnly: true diff --git a/charts/rancher-webhook/tests/capi-service_test.yaml b/charts/rancher-webhook/tests/capi-service_test.yaml deleted file mode 100644 index 4ee94a84a..000000000 --- a/charts/rancher-webhook/tests/capi-service_test.yaml +++ /dev/null @@ -1,20 +0,0 @@ -suite: Test Service -templates: - - charts/capi/templates/service.yaml -tests: - - it: should set webhook default port values - set: - capi.enabled: true - asserts: - - equal: - path: spec.ports[0].targetPort - value: 8777 - - - it: should set updated target port - set: - capi.port: 2319 - capi.enabled: true - asserts: - - equal: - path: spec.ports[0].targetPort - value: 2319 diff --git a/charts/rancher-webhook/tests/deployment_test.yaml b/charts/rancher-webhook/tests/deployment_test.yaml index 5f153461c..bbd6e3044 100644 --- a/charts/rancher-webhook/tests/deployment_test.yaml +++ b/charts/rancher-webhook/tests/deployment_test.yaml @@ -8,19 +8,11 @@ tests: - equal: path: spec.template.spec.containers[0].ports[0].containerPort value: 9443 - - equal: - path: spec.template.spec.containers[0].ports[1].containerPort - value: 8777 - contains: path: spec.template.spec.containers[0].env content: name: CATTLE_PORT value: "9443" - - contains: - path: spec.template.spec.containers[0].env - content: - name: CATTLE_CAPI_PORT - value: "8777" - it: should set updated webhook port set: @@ -35,19 +27,6 @@ tests: name: CATTLE_PORT value: "2319" - - it: should set updated capi port - set: - capi.port: 2319 - asserts: - - equal: - path: spec.template.spec.containers[0].ports[1].containerPort - value: 2319 - - contains: - path: spec.template.spec.containers[0].env - content: - name: CATTLE_CAPI_PORT - value: "2319" - - it: should not set capabilities by default. asserts: - isNull: diff --git a/charts/rancher-webhook/values.yaml b/charts/rancher-webhook/values.yaml index 9ad161ff9..541db5c46 100644 --- a/charts/rancher-webhook/values.yaml +++ b/charts/rancher-webhook/values.yaml @@ -8,10 +8,6 @@ global: systemDefaultRegistry: "" hostNetwork: false -capi: - enabled: false - port: 8777 - mcm: enabled: true diff --git a/docs.md b/docs.md index 14d4f6c7c..83d24b957 100644 --- a/docs.md +++ b/docs.md @@ -51,7 +51,9 @@ If yes, the webhook redacts the role, so that it only grants a deletion permissi #### Escalation Prevention -Users can only create/update ClusterRoleTemplateBindings which grant permissions to RoleTemplates with rights less than or equal to those they currently possess. This is to prevent privilege escalation. +Users can only create/update ClusterRoleTemplateBindings which grant permissions to RoleTemplates with rights less than or equal to those they currently possess. This is to prevent privilege escalation. +For external RoleTemplates (RoleTemplates with `external` set to `true`), if the `external-rules` feature flag is enabled and `ExternalRules` is specified in the roleTemplate in `RoleTemplateName`, +`ExternalRules` will be used for authorization. Otherwise (if the feature flag is off or `ExternalRules` are nil), the rules from the backing `ClusterRole` in the local cluster will be used. #### Invalid Fields - Create @@ -90,6 +92,7 @@ In addition, as in the create validation, both a user subject and a group subjec #### On update The desired value must not change on new spec unless it's equal to the `lockedValue` or `lockedValue` is nil. +Due to the security impact of the `external-rules` feature flag, only users with admin permissions (`*` verbs on `*` resources in `*` APIGroups in all namespaces) can enable or disable this feature flag. ## FleetWorkspace @@ -115,9 +118,13 @@ Note: all checks are bypassed if the GlobalRole is being deleted, or if only the #### Invalid Fields - Create and Update On create or update, the following checks take place: -- The webhook checks that each rule has at least one verb. +- The webhook validates each rule using the standard Kubernetes RBAC checks (see next section). - Each new RoleTemplate referred to in `inheritedClusterRoles` must have a context of `cluster` and not be `locked`. This validation is skipped for RoleTemplates in `inheritedClusterRoles` for the prior version of this object. +#### Rules Without Verbs, Resources, API groups + +Rules without verbs, resources, or apigroups are not permitted. The `rules` included in a GlobalRole are of the same type as the rules used by standard Kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one. + #### Escalation Prevention Users can only change GlobalRoles with rights less than or equal to those they currently possess. This is to prevent privilege escalation. This includes the rules in the RoleTemplates referred to in `inheritedClusterRoles`. @@ -203,6 +210,8 @@ Adds the authz.management.cattle.io/creator-role-bindings annotation. Users can only create/update ProjectRoleTemplateBindings with rights less than or equal to those they currently possess. This is to prevent privilege escalation. +For external RoleTemplates (RoleTemplates with `external` set to `true`), if the `external-rules` feature flag is enabled and `ExternalRules` is specified in the roleTemplate in `RoleTemplateName`, +`ExternalRules` will be used for authorization. Otherwise, if `ExternalRules` are nil when the feature flag is on, the rules from the backing `ClusterRole` in the local cluster will be used. #### Invalid Fields - Create @@ -250,13 +259,14 @@ Note: all checks are bypassed if the RoleTemplate is being deleted Circular references to a `RoleTemplate` (a inherits b, b inherits a) are not allowed. More specifically, if "roleTemplate1" is included in the `roleTemplateNames` of "roleTemplate2", then "roleTemplate2" must not be included in the `roleTemplateNames` of "roleTemplate1". This check prevents the creation of roles whose end-state cannot be resolved. -#### Rules Without Verbs +#### Rules Without Verbs, Resources, API groups -Rules without verbs are not permitted. The `rules` included in a RoleTemplate are of the same type as the rules used by standard Kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one. +Rules without verbs, resources, or apigroups are not permitted. The `rules` and `externalRules` included in a RoleTemplate are of the same type as the rules used by standard Kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one. #### Escalation Prevention Users can only change RoleTemplates with rights less than or equal to those they currently possess. This prevents privilege escalation. +Users can't create external RoleTemplates (or update existing RoleTemplates) with `ExternalRules` without having the `escalate` verb on that RoleTemplate. #### Context Validation @@ -276,3 +286,66 @@ If `roletemplates.builtin` is true then all fields are immutable except: ### Deletion check RoleTemplate can not be deleted if they are referenced by other RoleTemplates via `roletemplates.roleTemplateNames` or by GlobalRoles via `globalRoles.inheritedClusterRoles` + +## Setting + +### Validation Checks + +#### Invalid Fields - Create + +When a Setting is created, the following checks take place: + +- If set, `disable-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `delete-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `user-last-login-default` must be a date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `user-retention-cron` must be a valid standard cron expression (e.g. `0 0 * * 0`). + +#### Invalid Fields - Update + +When a Setting is updated, the following checks take place: + +- If set, `disable-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `delete-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `user-last-login-default` must be a date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `user-retention-cron` must be a valid standard cron expression (e.g. `0 0 * * 1`). + +#### Forbidden - Update + +- If `agent-tls-mode` has `default` or `value` updated from `system-store` to `strict`, then all non-local clusters must + have a status condition `AgentTlsStrictCheck` set to `True`, unless the new setting has an overriding + annotation `cattle.io/force=true`. + +## UserAttribute + +### Validation Checks + +#### Invalid Fields - Create + +When a UserAttribute is created, the following checks take place: + +- If set, `lastLogin` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `disableAfter` must be zero or a positive duration (e.g. `240h`). +- If set, `deleteAfter` must be zero or a positive duration (e.g. `240h`). + +#### Invalid Fields - Update + +When a UserAttribute is updated, the following checks take place: + +- If set, `lastLogin` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `disableAfter` must be zero or a positive duration (e.g. `240h`). +- If set, `deleteAfter` must be zero or a positive duration (e.g. `240h`). + +# provisioning.cattle.io/v1 + +## Cluster + +### Mutation Checks + +#### On Update + +##### Dynamic Schema Drop + +Check for the presence of the `provisioning.cattle.io/allow-dynamic-schema-drop` annotation. If the value is `"true"`, +perform no mutations. If the value is not present or not `"true"`, compare the value of the `dynamicSchemaSpec` field +for each `machinePool`, to its' previous value. If the values are not identical, revert the value for the +`dynamicSchemaSpec` for the specific `machinePool`, but do not reject the request. diff --git a/go.mod b/go.mod index e97766996..0cd049e07 100644 --- a/go.mod +++ b/go.mod @@ -1,149 +1,175 @@ module github.com/rancher/webhook -go 1.21 +go 1.22.0 -// on release remove this wrangler replace and use the latest tag -replace github.com/rancher/wrangler v1.1.1 => github.com/rancher/wrangler v1.1.1-0.20230831050635-df1bd5aae9df +toolchain go1.22.7 replace ( - k8s.io/api => k8s.io/api v0.27.4 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.27.4 - k8s.io/apimachinery => k8s.io/apimachinery v0.27.4 - k8s.io/apiserver => k8s.io/apiserver v0.27.4 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.27.4 - k8s.io/client-go => github.com/rancher/client-go v1.27.4-rancher1 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.27.4 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.27.4 - k8s.io/code-generator => k8s.io/code-generator v0.27.4 - k8s.io/component-base => k8s.io/component-base v0.27.4 - k8s.io/component-helpers => k8s.io/component-helpers v0.27.4 - k8s.io/controller-manager => k8s.io/controller-manager v0.27.4 - k8s.io/cri-api => k8s.io/cri-api v0.27.4 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.27.4 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.27.4 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.27.4 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.27.4 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.27.4 - k8s.io/kubectl => k8s.io/kubectl v0.27.4 - k8s.io/kubelet => k8s.io/kubelet v0.27.4 - k8s.io/kubernetes => k8s.io/kubernetes v1.27.4 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.27.4 - k8s.io/metrics => k8s.io/metrics v0.27.4 - k8s.io/mount-utils => k8s.io/mount-utils v0.27.4 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.27.4 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.27.4 + github.com/rancher/rke => github.com/rancher/rke v1.5.13 + k8s.io/api => k8s.io/api v0.28.6 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.28.6 + k8s.io/apimachinery => k8s.io/apimachinery v0.28.6 + k8s.io/apiserver => k8s.io/apiserver v0.28.6 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.28.6 + k8s.io/client-go => github.com/rancher/client-go v1.28.6-rancher1 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.28.6 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.28.6 + k8s.io/code-generator => k8s.io/code-generator v0.28.6 + k8s.io/component-base => k8s.io/component-base v0.28.6 + k8s.io/component-helpers => k8s.io/component-helpers v0.28.6 + k8s.io/controller-manager => k8s.io/controller-manager v0.28.6 + k8s.io/cri-api => k8s.io/cri-api v0.28.6 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.28.6 + k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.28.6 + k8s.io/endpointslice => k8s.io/endpointslice v0.28.6 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.28.6 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.28.6 + k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.28.6 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.28.6 + k8s.io/kubectl => k8s.io/kubectl v0.28.6 + k8s.io/kubelet => k8s.io/kubelet v0.28.6 + k8s.io/kubernetes => k8s.io/kubernetes v1.28.6 + k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.28.6 + k8s.io/metrics => k8s.io/metrics v0.28.6 + k8s.io/mount-utils => k8s.io/mount-utils v0.28.6 + k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.28.6 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.28.6 + sigs.k8s.io/structured-merge-diff/v4 => sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) require ( github.com/blang/semver v3.5.1+incompatible - github.com/evanphx/json-patch v5.6.0+incompatible + github.com/evanphx/json-patch v5.9.11+incompatible github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 - github.com/rancher/dynamiclistener v0.3.5 - github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29 - github.com/rancher/lasso/controller-runtime v0.0.0-20230830164424-d684fdeb6f29 - github.com/rancher/rancher/pkg/apis v0.0.0-20230908142231-468d4204cd8e - github.com/rancher/rke v1.5.0-rc2 - github.com/rancher/wrangler v1.1.1 + github.com/rancher/dynamiclistener v0.4.0 + github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 + github.com/rancher/rancher/pkg/apis v0.0.0-20250121135817-bfaaf7d60643 + github.com/rancher/rke v1.5.15 + github.com/rancher/wrangler/v2 v2.1.4 + github.com/robfig/cron v1.2.0 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/text v0.12.0 - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 - k8s.io/api v0.27.4 - k8s.io/apiextensions-apiserver v0.27.4 - k8s.io/apimachinery v0.27.4 - k8s.io/apiserver v0.27.4 + github.com/stretchr/testify v1.10.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/text v0.21.0 + golang.org/x/tools v0.28.0 + k8s.io/api v0.30.0 + k8s.io/apimachinery v0.30.0 + k8s.io/apiserver v0.28.9 k8s.io/client-go v12.0.0+incompatible - k8s.io/kubernetes v1.27.4 - k8s.io/pod-security-admission v0.27.4 - k8s.io/utils v0.0.0-20230726121419-3b25d923346b - sigs.k8s.io/cluster-api v1.5.0 - sigs.k8s.io/controller-runtime v0.15.0 - sigs.k8s.io/yaml v1.3.0 + k8s.io/kubernetes v1.28.9 + k8s.io/pod-security-admission v0.28.6 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 + sigs.k8s.io/controller-runtime v0.16.6 + sigs.k8s.io/yaml v1.4.0 ) -require github.com/mitchellh/mapstructure v1.5.0 // indirect - require ( - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect - github.com/gobuffalo/flect v1.0.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/cel-go v0.16.0 // indirect - github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/cel-go v0.16.1 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/huandu/xstrings v1.3.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.27.10 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect - github.com/rancher/aks-operator v1.2.0-rc2 // indirect - github.com/rancher/eks-operator v1.3.0-rc2 // indirect - github.com/rancher/fleet/pkg/apis v0.0.0-20230810121238-9d0ee7f56848 // indirect - github.com/rancher/gke-operator v1.2.0-rc1 // indirect - github.com/rancher/norman v0.0.0-20230426211126-d3552b018687 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/cast v1.5.1 // indirect + github.com/rancher/aks-operator v1.2.7 // indirect + github.com/rancher/eks-operator v1.3.7 // indirect + github.com/rancher/fleet/pkg/apis v0.9.13 // indirect + github.com/rancher/gke-operator v1.2.7 // indirect + github.com/rancher/norman v0.0.0-20240206180703-6eda4bc94b4c // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/time v0.3.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.9 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect + go.etcd.io/etcd/client/v3 v3.5.9 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1 // indirect + go.opentelemetry.io/otel v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect + go.opentelemetry.io/otel/metric v0.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.10.0 // indirect + go.opentelemetry.io/otel/trace v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.25.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/code-generator v0.27.4 // indirect - k8s.io/component-base v0.27.4 // indirect - k8s.io/component-helpers v0.27.4 // indirect + k8s.io/apiextensions-apiserver v0.28.9 // indirect + k8s.io/cloud-provider v0.0.0 // indirect + k8s.io/code-generator v0.28.9 // indirect + k8s.io/component-base v0.28.9 // indirect + k8s.io/component-helpers v0.28.6 // indirect + k8s.io/controller-manager v0.28.6 // indirect k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect k8s.io/klog v1.0.0 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-aggregator v0.27.4 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - sigs.k8s.io/cli-utils v0.27.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kms v0.28.9 // indirect + k8s.io/kube-aggregator v0.28.6 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kubelet v0.0.0 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 // indirect + sigs.k8s.io/cli-utils v0.28.0 // indirect + sigs.k8s.io/cluster-api v1.5.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect ) diff --git a/go.sum b/go.sum index dd5c7a9e4..026508850 100644 --- a/go.sum +++ b/go.sum @@ -13,27 +13,18 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -44,201 +35,111 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= -github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= -github.com/coredns/corefile-migration v1.0.20 h1:MdOkT6F3ehju/n9tgxlGct8XAajOX2vN+wG7To4BWSI= -github.com/coredns/corefile-migration v1.0.20/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= +github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= -github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -251,7 +152,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -269,24 +169,17 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A= -github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE= -github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= -github.com/google/cel-go v0.16.0 h1:DG9YQ8nFCFXAs/FDDwBxmL1tpKNrdlGUM9U3537bX/Y= -github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo= +github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -294,22 +187,17 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -317,428 +205,199 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= -github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= -github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= -github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= -github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= -github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= -github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= -github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rancher/aks-operator v1.2.0-rc2 h1:zP1VEzAc/jZ7FyeiQcQ+pkFUxDySrKoN+RdSnnj7LnE= -github.com/rancher/aks-operator v1.2.0-rc2/go.mod h1:ga4p9r8nTUuSRqkYLTBgA5OlPf19iT/i9Qa3kXH7H7Y= -github.com/rancher/client-go v1.27.4-rancher1 h1:1W1A0bV6KXYbu1z+KIezlfLsHPc1wiasAM42BICVKBM= -github.com/rancher/client-go v1.27.4-rancher1/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc= -github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= -github.com/rancher/dynamiclistener v0.3.5/go.mod h1:dW/YF6/m2+uEyJ5VtEcd9THxda599HP6N9dSXk81+k0= -github.com/rancher/eks-operator v1.3.0-rc2 h1:Poua1CXexs0h/C0dRllBsaehdc61H+L4xc4e7oxT5tY= -github.com/rancher/eks-operator v1.3.0-rc2/go.mod h1:30gJm9FbNBdzMX300ibhtlU72BgkjDuW1bX/u4FC0ns= -github.com/rancher/fleet/pkg/apis v0.0.0-20230810121238-9d0ee7f56848 h1:FNZEI+rCC+u5RVE4dyH7EX8ltlZ74zBowUOviMoGk5c= -github.com/rancher/fleet/pkg/apis v0.0.0-20230810121238-9d0ee7f56848/go.mod h1:TbpjMODeuFUJlHG8IfehZ4FBzgzpxwPwa+GwtFq2Tq0= -github.com/rancher/gke-operator v1.2.0-rc1 h1:P0ochfyPHiUQcVSLCIq5xYAxckWHtvGTpQF8Zk0bODk= -github.com/rancher/gke-operator v1.2.0-rc1/go.mod h1:kWeCkEjexmtJSTm86v/32q3xn/TbN5vosAJloviAJjk= -github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29 h1:+kige/h8/LnzWgPjB5NUIHz/pWiW/lFpqcTUkN5uulY= -github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29/go.mod h1:kgk9kJVMj9FIrrXU0iyM6u/9Je4bEjPImqswkTVaKsQ= -github.com/rancher/lasso/controller-runtime v0.0.0-20230830164424-d684fdeb6f29 h1:WNOHge0CTLP5gPPxlAjLK4yV5KhwUdQAZPPHTj3C9mE= -github.com/rancher/lasso/controller-runtime v0.0.0-20230830164424-d684fdeb6f29/go.mod h1:SdYcp1RfyvKyyqWVNQDzjXLOfN+Ne7joDgdpWpMlDTE= -github.com/rancher/norman v0.0.0-20230426211126-d3552b018687 h1:9Bf4fZBIdkidKTqHFsJXMlnzflxx3h4ZAEH/n6HMuyI= -github.com/rancher/norman v0.0.0-20230426211126-d3552b018687/go.mod h1:7MyWxfCmPl6N/UFLu4neLH6nwTFgQQF5rxtUGyZvPFE= -github.com/rancher/rancher/pkg/apis v0.0.0-20230908142231-468d4204cd8e h1:Csccak9RRpp9xW77WeXxSg1IkYQy8L1zrdVLwLwn98w= -github.com/rancher/rancher/pkg/apis v0.0.0-20230908142231-468d4204cd8e/go.mod h1:Sgu7uXo+1hieUck6aBuKXpWbUqk+iIfjURZ5cSpH4hw= -github.com/rancher/rke v1.5.0-rc2 h1:gec//2jkyEimO/fZLMMRVAJF8GpKqDf3voe+k3jrhGg= -github.com/rancher/rke v1.5.0-rc2/go.mod h1:wUwsm6dXyzzxWlVwmPPR5XMWX6ICjAdWJ+l45ZqV+P0= -github.com/rancher/wrangler v1.1.1-0.20230831050635-df1bd5aae9df h1:WJ+aaUICHPX8HeLmHE9JL/RFHhilMfcJlqmhgpc7gJU= -github.com/rancher/wrangler v1.1.1-0.20230831050635-df1bd5aae9df/go.mod h1:4T80p+rLh2OLbjCjdExIjRHKNBgK9NUAd7eIU/gRPKk= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rancher/aks-operator v1.2.7 h1:X1uUL0OZzAf3+Po3UHydCiLvYljx5Nq+K3dc8OW7Lcg= +github.com/rancher/aks-operator v1.2.7/go.mod h1:Ao92zHjV4dq09mvp724xGTCVoac/hYKrKsYI8cXt/WM= +github.com/rancher/client-go v1.28.6-rancher1 h1:nSoGKs12BuPviZtzemO7wTX8jxABaLqfYKFLRBV8MI0= +github.com/rancher/client-go v1.28.6-rancher1/go.mod h1:+nu0Yp21Oeo/cBCsprNVXB2BfJTV51lFfe5tXl2rUL8= +github.com/rancher/dynamiclistener v0.4.0 h1:1bOlH4uzD5BPQ8fo2FeiWxiMp87ppwmyc2uYxN2z1kc= +github.com/rancher/dynamiclistener v0.4.0/go.mod h1:Y+VdjQH9KQGE97uMwYEWqNN6puFQ17aBemIjVLYdlD8= +github.com/rancher/eks-operator v1.3.7 h1:26GXMB15+sa6OeDw0axZP4MxwdJLSSdsS62j8J/kvnI= +github.com/rancher/eks-operator v1.3.7/go.mod h1:WJOaU0nuWRiWryjE/UCEgT+KMXQe4AnuGW30vlFElIM= +github.com/rancher/fleet/pkg/apis v0.9.13 h1:5390pr8Xy1yeZNWo0qRjo2o9JwPPGT86yavLs4YBYq8= +github.com/rancher/fleet/pkg/apis v0.9.13/go.mod h1:jLJGCUCq4m3YfP89pKQaHQYuKH511+UhuSDqMfPa7zU= +github.com/rancher/gke-operator v1.2.7 h1:5+bbQFZM0pRSRxlgVPUUtMbAKdeZk4KwkCEfhfLVjtQ= +github.com/rancher/gke-operator v1.2.7/go.mod h1:YbGp/isjJH+BI61qo372C6zAaBc64O00K+i0JvKVSns= +github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 h1:vv1jDlYbd4KhGbPNxmjs8CYgEHUrQm2bMtmULfXJ6iw= +github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1/go.mod h1:A/y3BLQkxZXYD60MNDRwAG9WGxXfvd6Z6gWR/a8wPw8= +github.com/rancher/norman v0.0.0-20240206180703-6eda4bc94b4c h1:ayqZqJ4AYYVaZGlBztLBij81z/QRsSFbQfxs9bzA+Tg= +github.com/rancher/norman v0.0.0-20240206180703-6eda4bc94b4c/go.mod h1:WbNpu/HwChwKk54W0rWBdioxYVVZwVVz//UX84m/NvY= +github.com/rancher/rancher/pkg/apis v0.0.0-20250121135817-bfaaf7d60643 h1:8iphPYzqVInJ1rFrL8kieClyRj011NT3yD/dRywVAEg= +github.com/rancher/rancher/pkg/apis v0.0.0-20250121135817-bfaaf7d60643/go.mod h1:loJaH1qwWQRsqOXazyS0IU5re9Ml09Qbo8LsLbVCFbE= +github.com/rancher/rke v1.5.13 h1:Y7e3qI0G2HbU3Vm5k3Sqeq+UR2w114K5yY6wT9VFKcY= +github.com/rancher/rke v1.5.13/go.mod h1:/z9oyKqYpFwgRBV9rfLxqUdjydz/VMCTcjld4uUt7uM= +github.com/rancher/wrangler/v2 v2.1.4 h1:ohov0i6A9dJHHO6sjfsH4Dqv93ZTdm5lIJVJdPzVdQc= +github.com/rancher/wrangler/v2 v2.1.4/go.mod h1:af5OaGU/COgreQh1mRbKiUI64draT2NN34uk+PALFY8= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spyzhov/ajson v0.4.2/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= -go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw= -go.etcd.io/etcd/pkg/v3 v3.5.7/go.mod h1:kcOfWt3Ov9zgYdOiJ/o1Y9zFfLhQjylTgL4Lru8opRo= -go.etcd.io/etcd/raft/v3 v3.5.7/go.mod h1:TflkAb/8Uy6JFBxcRaH2Fr6Slm9mCPVdI2efzxY96yU= -go.etcd.io/etcd/server/v3 v3.5.7/go.mod h1:gxBgT84issUVBRpZ3XkW1T55NjOb4vZZRI4wVvNhf4A= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.9 h1:YZ2OLi0OvR0H75AcgSUajjd5uqKDKocQUqROTG11jIo= +go.etcd.io/etcd/client/v2 v2.305.9/go.mod h1:0NBdNx9wbxtEQLwAQtrDHwx58m02vXpDcgSYI2seohQ= +go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.etcd.io/etcd/pkg/v3 v3.5.9 h1:6R2jg/aWd/zB9+9JxmijDKStGJAPFsX3e6BeJkMi6eQ= +go.etcd.io/etcd/pkg/v3 v3.5.9/go.mod h1:BZl0SAShQFk0IpLWR78T/+pyt8AruMHhTNNX73hkNVY= +go.etcd.io/etcd/raft/v3 v3.5.9 h1:ZZ1GIHoUlHsn0QVqiRysAm3/81Xx7+i2d7nSdWxlOiI= +go.etcd.io/etcd/raft/v3 v3.5.9/go.mod h1:WnFkqzFdZua4LVlVXQEGhmooLeyS7mqzS4Pf4BCVqXg= +go.etcd.io/etcd/server/v3 v3.5.9 h1:vomEmmxeztLtS5OEH7d0hBAg4cjVIu9wXuNzUZx2ZA0= +go.etcd.io/etcd/server/v3 v3.5.9/go.mod h1:GgI1fQClQCFIzuVjlvdbMxNbnISt90gdfYyqiAIt65g= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1 h1:sxoY9kG1s1WpSYNyzm24rlwH4lnRYFXUVVBmKMBfRgw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= -go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= -go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1/go.mod h1:Kv8liBeVNFkkkbilbgWRpV+wWuu+H5xdOT6HAgd30iw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= +go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= +go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= -go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= -go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -749,8 +408,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -763,8 +422,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -773,37 +430,21 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -814,61 +455,22 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -878,38 +480,21 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -922,97 +507,37 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1022,8 +547,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1051,33 +574,14 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1096,27 +600,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1138,7 +627,6 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1148,42 +636,15 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 h1:HJMDndgxest5n2y77fnErkM62iUsptE/H8p0dC2Huo4= +google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1194,26 +655,12 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1227,39 +674,24 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1269,79 +701,60 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.27.4 h1:0pCo/AN9hONazBKlNUdhQymmnfLRbSZjd5H5H3f0bSs= -k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y= -k8s.io/apiextensions-apiserver v0.27.4 h1:ie1yZG4nY/wvFMIR2hXBeSVq+HfNzib60FjnBYtPGSs= -k8s.io/apiextensions-apiserver v0.27.4/go.mod h1:KHZaDr5H9IbGEnSskEUp/DsdXe1hMQ7uzpQcYUFt2bM= -k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs= -k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/apiserver v0.27.4 h1:ncZ0MBR9yQ/Gf34rtu1EK+HqT8In1YpfAUINu/Akvho= -k8s.io/apiserver v0.27.4/go.mod h1:GDEFRfFZ4/l+pAvwYRnoSfz0K4j3TWiN4WsG2KnRteE= -k8s.io/cli-runtime v0.27.4/go.mod h1:k9Z1xiZq2xNplQmehpDquLgc+rE+pubpO1cK4al4Mlw= -k8s.io/cluster-bootstrap v0.27.4 h1:fobmnzmWrv0RcTed71Kdek7ElUWDkQ38zG799o8M8I4= -k8s.io/cluster-bootstrap v0.27.4/go.mod h1:sLvyEcIhRmoG8HhaIDy8htZ9MuaYK6nP+BMKGqqirBs= -k8s.io/code-generator v0.27.4 h1:bw2xFEBnthhCSC7Bt6FFHhPTfWX21IJ30GXxOzywsFE= -k8s.io/code-generator v0.27.4/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= -k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c= -k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY= -k8s.io/component-helpers v0.27.4 h1:l1hn/Zx9mWXflo5xz1mo5RRW2g8b6rptWCG7My6rYoE= -k8s.io/component-helpers v0.27.4/go.mod h1:ayW5btpTdJkVv+CcxhzNRfWT+oPrV6T6qZ1Ay6NEJNI= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/api v0.28.6 h1:yy6u9CuIhmg55YvF/BavPBBXB+5QicB64njJXxVnzLo= +k8s.io/api v0.28.6/go.mod h1:AM6Ys6g9MY3dl/XNaNfg/GePI0FT7WBGu8efU/lirAo= +k8s.io/apiextensions-apiserver v0.28.6 h1:myB3iG/3v3jqCg28JDbOefu4sH2/erNEXgytRzJKBOo= +k8s.io/apiextensions-apiserver v0.28.6/go.mod h1:qlp6xRKBgyRhe5AYc81TQpLx4kLNK8/sGQUOwMkVjRk= +k8s.io/apimachinery v0.28.6 h1:RsTeR4z6S07srPg6XYrwXpTJVMXsjPXn0ODakMytSW0= +k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA= +k8s.io/apiserver v0.28.6 h1:SfS5v4I5UGvh0q/1rzvNwLFsK+r7YzcsixnUc0NwoEk= +k8s.io/apiserver v0.28.6/go.mod h1:8n0aerS3kPm9usyB8B+an6/BZ5+Fa9fNqlASFdDDVwk= +k8s.io/cloud-provider v0.28.6 h1:KfexGkg8UVsfb7Jq163V/eyMVm0/IUHrnJnZ9m/qN8A= +k8s.io/cloud-provider v0.28.6/go.mod h1:J8X7fNWmekayasdhQGSab4hm7hk0KQssDsA5WQlPi8k= +k8s.io/code-generator v0.28.6 h1:5RnSOAvdcvuhqAp7LV5I2wwE/9DgEz2vV2llA78ubV8= +k8s.io/code-generator v0.28.6/go.mod h1:IaYGMqYjgj0zE3L9mnHo7hIL9GkY08GvGyyracaIxTA= +k8s.io/component-base v0.28.6 h1:G4T8VrcQ7xZou3by/fY5NU5mfxOBlWaivS2lPrEltAo= +k8s.io/component-base v0.28.6/go.mod h1:Dg62OOG3ALu2P4nAG00UdsuHoNLQJ5VsUZKQlLDcS+E= +k8s.io/component-helpers v0.28.6 h1:0vkzt/Oesntc8TJ1LrD5g4IIKAZljHcgTd7aMHkmULg= +k8s.io/component-helpers v0.28.6/go.mod h1:E1wTMmZYIdNZEUnglIbSf314IsvI0K1elnBIsdJwTM4= +k8s.io/controller-manager v0.28.6 h1:Gw/SN1z9AVWMpO7bsl7SGYFvRFQ3v5zmiLMS/DXfDsY= +k8s.io/controller-manager v0.28.6/go.mod h1:LHG+7FwwANxLrXD3Zn0EGMTMQ6mEYrFMkQ/CmqLOveo= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kms v0.27.4/go.mod h1:0BY6tkfa+zOP85u8yE7iNNf1Yx7rEZnRQSWLEbsSk+w= -k8s.io/kube-aggregator v0.27.4 h1:WdK9iiBr32G8bWfpUEFVQl70RZO2dU19ZAktUXL5JFc= -k8s.io/kube-aggregator v0.27.4/go.mod h1:+eG83gkAyh0uilQEAOgheeQW4hr+PkyV+5O1nLGsjlM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/kubectl v0.27.4/go.mod h1:qtc1s3BouB9KixJkriZMQqTsXMc+OAni6FeKAhq7q14= -k8s.io/kubernetes v1.27.4 h1:js5bonPoe7jgVPduNcWo6IjPTUdLzlnfhRgGmC7isM0= -k8s.io/kubernetes v1.27.4/go.mod h1:MbYZxAacYS6HjZ6VJuvKaKTilbzp0B0atzW3J8TFBEo= -k8s.io/metrics v0.27.4/go.mod h1:kRvfhFC7wCQEFvu6H92uiV7v05z3Ty/vtluYT5D2Xpk= -k8s.io/pod-security-admission v0.27.4 h1:AA32ID+ECNJoUU8yuzLt4WzKPDZg7zMmP2cZ9rVsFyE= -k8s.io/pod-security-admission v0.27.4/go.mod h1:GOcnrXk8TT5cPhtCxdlkOAvBnX3QmZiMHqPw9PbZhPs= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kms v0.28.9 h1:ApCWJulBl+uFRTr2jtTpG1lffmqqMuLnOH/RUbtO4UY= +k8s.io/kms v0.28.9/go.mod h1:VgyAIRMFqZX9lHyixecU/JTI0wnPD1wCIlquvlXRJ+Y= +k8s.io/kube-aggregator v0.28.6 h1:opRVDw+inLPIyAqG9Fu3+EYWcmbTHOHJNrmuKoeuzQM= +k8s.io/kube-aggregator v0.28.6/go.mod h1:NXzqtkCuAfv/modgRbAkPdfUZF+koazCy8Qrs8L+WyE= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/kubelet v0.28.6 h1:Ofn26Lr/4MCApqNPbRHEALIx5YTnKjNxC1CfEgwbrUA= +k8s.io/kubelet v0.28.6/go.mod h1:ANu5Pa5P4beDu2BGTW588WsFPfjmkgKbikiUvatdzAo= +k8s.io/kubernetes v1.28.6 h1:Nn2fXNjaqmKyXeFq6verbdqzlj1aqUpBcyeC1OhRFa4= +k8s.io/kubernetes v1.28.6/go.mod h1:0qpyGJTR3blkbQOmZA3Z0u1VDZJNxJM8ifLUVNJN0X8= +k8s.io/pod-security-admission v0.28.6 h1:EkObQaDPU7y+b4ODafwKIZlK9M6P8Ss23D77TZ+8hqs= +k8s.io/pod-security-admission v0.28.6/go.mod h1:TYNCxTgViyOELpDYOMn81PQsa+hd9Co00HTQI9c6qXk= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0= -sigs.k8s.io/cli-utils v0.27.0 h1:BxI7lPNn0fBZa5g4UwR+ShJyL4CCxELA6tLHbr2YrpU= -sigs.k8s.io/cli-utils v0.27.0/go.mod h1:8ll2fyx+bzjbwmwUnKBQU+2LDbMDsxy44DiDZ+drALg= -sigs.k8s.io/cluster-api v1.5.0 h1:pwXvzScbAwnrB7EWHTApzW+VQfrj2OSrWAQDC9+bcbU= -sigs.k8s.io/cluster-api v1.5.0/go.mod h1:ZSEP01t8oT6104gB4ljsOwwp5uJcI8SWy8IFp2HUvrc= -sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= -sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/cli-utils v0.28.0 h1:gsvwqygoXlW2y8CmKdflQJNZp1Yhi4geATW3/Ei7oYc= +sigs.k8s.io/cli-utils v0.28.0/go.mod h1:WDVRa5/eQBKntG++uyKdyT+xU7MLdCR4XsgseqL5uX4= +sigs.k8s.io/cluster-api v1.5.5 h1:MmxTGE8bJvALrJzzuTA2YEP+qKPrpxg7u8AecU93fwc= +sigs.k8s.io/cluster-api v1.5.5/go.mod h1:T50/vFznHz409V1XS74DIiPl9JDH4r2+xHiCYpYRTmc= +sigs.k8s.io/controller-runtime v0.16.6 h1:FiXwTuFF5ZJKmozfP2Z0j7dh6kmxP4Ou1KLfxgKKC3I= +sigs.k8s.io/controller-runtime v0.16.6/go.mod h1:+dQzkZxnylD0u49e0a+7AR+vlibEBaThmPca7lTyUsI= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.2/go.mod h1:DUp325VVMFVcQSq+ZxyDisA8wtldwHxLZbr1g94UHsw= -sigs.k8s.io/kustomize/cmd/config v0.11.1/go.mod h1:z1I4ubecg4py5Jn4+04p4A/vmnqUjxjxDCEyYEjQu/0= -sigs.k8s.io/kustomize/kustomize/v5 v5.0.1/go.mod h1:Q8o+soB41Pn1y26eXzG9cniuECDpTJe2eKOA1fENCU8= -sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= -sigs.k8s.io/kustomize/kyaml v0.14.1/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/main.go b/main.go index 21ebfc82d..675e6452c 100644 --- a/main.go +++ b/main.go @@ -7,11 +7,11 @@ import ( "os" "github.com/rancher/webhook/pkg/server" - _ "github.com/rancher/wrangler/pkg/generated/controllers/admissionregistration.k8s.io" - "github.com/rancher/wrangler/pkg/k8scheck" - "github.com/rancher/wrangler/pkg/kubeconfig" - "github.com/rancher/wrangler/pkg/ratelimit" - "github.com/rancher/wrangler/pkg/signals" + _ "github.com/rancher/wrangler/v2/pkg/generated/controllers/admissionregistration.k8s.io" + "github.com/rancher/wrangler/v2/pkg/k8scheck" + "github.com/rancher/wrangler/v2/pkg/kubeconfig" + "github.com/rancher/wrangler/v2/pkg/ratelimit" + "github.com/rancher/wrangler/v2/pkg/signals" "github.com/sirupsen/logrus" ) @@ -50,7 +50,7 @@ func run() error { return err } - if err := server.ListenAndServe(ctx, cfg, os.Getenv("ENABLE_CAPI") == "true", os.Getenv("ENABLE_MCM") != "false"); err != nil { + if err := server.ListenAndServe(ctx, cfg, os.Getenv("ENABLE_MCM") != "false"); err != nil { return err } diff --git a/package/Dockerfile b/package/Dockerfile index b52dd3a86..b9bc2e6da 100644 --- a/package/Dockerfile +++ b/package/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.suse.com/bci/bci-micro:15.5 +FROM registry.suse.com/bci/bci-micro:15.6 ARG user=webhook diff --git a/pkg/auth/globalrole_test.go b/pkg/auth/globalrole_test.go index a9e167276..907bc37e9 100644 --- a/pkg/auth/globalrole_test.go +++ b/pkg/auth/globalrole_test.go @@ -7,7 +7,7 @@ import ( "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/auth" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/require" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -153,7 +153,7 @@ func TestGlobalRulesFromRole(t *testing.T) { if test.stateSetup != nil { test.stateSetup(state) } - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), nil) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil, nil), nil) rules := grResolver.GlobalRulesFromRole(test.globalRole) require.Len(t, rules, len(test.wantRules)) @@ -264,7 +264,7 @@ func TestClusterRulesFromRole(t *testing.T) { if test.stateSetup != nil { test.stateSetup(state) } - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), nil) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil, nil), nil) rules, err := grResolver.ClusterRulesFromRole(test.globalRole) if test.wantErr { require.Error(t, err) @@ -337,7 +337,7 @@ func TestGetRoleTemplatesForGlobalRole(t *testing.T) { if test.stateSetup != nil { test.stateSetup(state) } - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), nil) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil, nil), nil) roleTemplates, err := grResolver.GetRoleTemplatesForGlobalRole(test.globalRole) if test.wantErr { require.Error(t, err) diff --git a/pkg/auth/roleTemplate.go b/pkg/auth/roleTemplate.go index 87f974627..3e788d0b3 100644 --- a/pkg/auth/roleTemplate.go +++ b/pkg/auth/roleTemplate.go @@ -5,21 +5,26 @@ import ( rancherv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" - v1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1" + v1 "github.com/rancher/wrangler/v2/pkg/generated/controllers/rbac/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" ) +const ExternalRulesFeature = "external-rules" + // RoleTemplateResolver provides an interface to flatten role templates into slice of rules. type RoleTemplateResolver struct { roleTemplates v3.RoleTemplateCache clusterRoles v1.ClusterRoleCache + features v3.FeatureCache } // NewRoleTemplateResolver creates a newly allocated RoleTemplateResolver from the provided caches -func NewRoleTemplateResolver(roleTemplates v3.RoleTemplateCache, clusterRoles v1.ClusterRoleCache) *RoleTemplateResolver { +func NewRoleTemplateResolver(roleTemplates v3.RoleTemplateCache, clusterRoles v1.ClusterRoleCache, features v3.FeatureCache) *RoleTemplateResolver { return &RoleTemplateResolver{ roleTemplates: roleTemplates, clusterRoles: clusterRoles, + features: features, } } @@ -58,12 +63,29 @@ func (r *RoleTemplateResolver) RulesFromTemplate(roleTemplate *rancherv3.RoleTem func (r *RoleTemplateResolver) gatherRules(roleTemplate *rancherv3.RoleTemplate, rules []rbacv1.PolicyRule, seen map[string]bool) ([]rbacv1.PolicyRule, error) { seen[roleTemplate.Name] = true - if roleTemplate.External && roleTemplate.Context == "cluster" { - cr, err := r.clusterRoles.Get(roleTemplate.Name) + if roleTemplate.External { + externalRulesEnabled, err := r.isExternalRulesFeatureFlagEnabled() if err != nil { - return nil, fmt.Errorf("failed to get clusterRoles '%s': %w", roleTemplate.Name, err) + return nil, fmt.Errorf("failed to check externalRules feature flag: %w", err) + } + + if externalRulesEnabled { + if roleTemplate.ExternalRules != nil { + rules = append(rules, roleTemplate.ExternalRules...) + } else { + cr, err := r.clusterRoles.Get(roleTemplate.Name) + if err != nil { + return nil, fmt.Errorf("for external RoleTemplates, externalRules must be provided or a backing clusterRole must be installed to check for privilege escalations: failed to get ClusterRole %q: %w", roleTemplate.Name, err) + } + rules = append(rules, cr.Rules...) + } + } else if roleTemplate.Context == "cluster" { + cr, err := r.clusterRoles.Get(roleTemplate.Name) + if err != nil { + return nil, fmt.Errorf("failed to get ClusterRole %q: %w", roleTemplate.Name, err) + } + rules = append(rules, cr.Rules...) } - rules = append(rules, cr.Rules...) } rules = append(rules, roleTemplate.Rules...) @@ -84,3 +106,17 @@ func (r *RoleTemplateResolver) gatherRules(roleTemplate *rancherv3.RoleTemplate, } return rules, nil } + +func (r *RoleTemplateResolver) isExternalRulesFeatureFlagEnabled() (bool, error) { + f, err := r.features.Get(ExternalRulesFeature) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + if f.Spec.Value == nil { + return f.Status.Default, nil + } + return *f.Spec.Value, nil +} diff --git a/pkg/auth/roleTemplate_test.go b/pkg/auth/roleTemplate_test.go index 571b76259..b74a88469 100644 --- a/pkg/auth/roleTemplate_test.go +++ b/pkg/auth/roleTemplate_test.go @@ -10,8 +10,8 @@ import ( apisv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/auth" v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" - wranglerv1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1" - "github.com/rancher/wrangler/pkg/generic/fake" + wrbacv1 "github.com/rancher/wrangler/v2/pkg/generated/controllers/rbac/v1" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/suite" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -54,14 +54,18 @@ func (r Rules) Equal(r2 Rules) bool { type RoleTemplateResolverSuite struct { suite.Suite - adminRT *apisv3.RoleTemplate - readNodesRT *apisv3.RoleTemplate - writeNodesRT *apisv3.RoleTemplate - inheritedRT *apisv3.RoleTemplate - externalRT *apisv3.RoleTemplate - invalidInhertedRT *apisv3.RoleTemplate + adminRT *apisv3.RoleTemplate + readNodesRT *apisv3.RoleTemplate + writeNodesRT *apisv3.RoleTemplate + inheritedRT *apisv3.RoleTemplate + externalRulesClusterRT *apisv3.RoleTemplate + externalRulesProjectRT *apisv3.RoleTemplate + externalClusterRT *apisv3.RoleTemplate + externalProjectRT *apisv3.RoleTemplate + invalidInhertedRT *apisv3.RoleTemplate readServiceCR *rbacv1.ClusterRole + writeNodesCR *rbacv1.ClusterRole } func TestRoleTemplateResolver(t *testing.T) { @@ -93,6 +97,10 @@ func (r *RoleTemplateResolverSuite) SetupSuite() { ObjectMeta: metav1.ObjectMeta{Name: "read-services"}, Rules: []rbacv1.PolicyRule{ruleReadServices}, } + r.writeNodesCR = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "write-nodes"}, + Rules: []rbacv1.PolicyRule{ruleWriteNodes}, + } r.readNodesRT = &apisv3.RoleTemplate{ ObjectMeta: metav1.ObjectMeta{ @@ -121,7 +129,25 @@ func (r *RoleTemplateResolverSuite) SetupSuite() { Administrative: true, Context: "cluster", } - r.externalRT = &apisv3.RoleTemplate{ + r.externalRulesClusterRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.readServiceCR.Name, + }, + DisplayName: "External Role", + Context: "cluster", + External: true, + ExternalRules: []rbacv1.PolicyRule{ruleWriteNodes}, + } + r.externalRulesProjectRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.readServiceCR.Name, + }, + DisplayName: "External Role", + Context: "project", + External: true, + ExternalRules: []rbacv1.PolicyRule{ruleWriteNodes}, + } + r.externalClusterRT = &apisv3.RoleTemplate{ ObjectMeta: metav1.ObjectMeta{ Name: r.readServiceCR.Name, }, @@ -129,7 +155,14 @@ func (r *RoleTemplateResolverSuite) SetupSuite() { Context: "cluster", External: true, } - + r.externalProjectRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.readServiceCR.Name, + }, + DisplayName: "External Role", + Context: "project", + External: true, + } r.inheritedRT = &apisv3.RoleTemplate{ ObjectMeta: metav1.ObjectMeta{ Name: "inherited-role", @@ -153,7 +186,7 @@ func (r *RoleTemplateResolverSuite) SetupSuite() { func (r *RoleTemplateResolverSuite) TestRoleTemplateResolver() { type args struct { name string - caches func() (v3.RoleTemplateCache, wranglerv1.ClusterRoleCache) + caches func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) } tests := []struct { name string @@ -165,12 +198,13 @@ func (r *RoleTemplateResolverSuite) TestRoleTemplateResolver() { { name: "Test simple Role Template", args: args{ - caches: func() (v3.RoleTemplateCache, wranglerv1.ClusterRoleCache) { + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().Get(r.adminRT.Name).Return(r.adminRT, nil).AnyTimes() clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - return roleTemplateCache, clusterRoleCache + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + return roleTemplateCache, clusterRoleCache, featuresCache }, name: r.adminRT.Name, }, @@ -181,47 +215,319 @@ func (r *RoleTemplateResolverSuite) TestRoleTemplateResolver() { { name: "Test inherited Role Templates", args: args{ - caches: func() (v3.RoleTemplateCache, wranglerv1.ClusterRoleCache) { + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().Get(r.inheritedRT.Name).Return(r.inheritedRT, nil) roleTemplateCache.EXPECT().Get(r.readNodesRT.Name).Return(r.readNodesRT, nil) roleTemplateCache.EXPECT().Get(r.writeNodesRT.Name).Return(r.writeNodesRT, nil) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - return roleTemplateCache, clusterRoleCache + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + return roleTemplateCache, clusterRoleCache, featuresCache }, name: r.inheritedRT.Name, }, want: append(r.inheritedRT.Rules, append(r.readNodesRT.Rules, r.writeNodesRT.Rules...)...), wantErr: false, }, - // Get external role { - name: "Test external cluster role", + name: "Test externalRules (context=cluster) when feature flag is set to true", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalRulesClusterRT.Name).Return(r.externalRulesClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + Status: apisv3.FeatureStatus{ + Default: false, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalRulesClusterRT.Name, + }, + want: r.writeNodesCR.Rules, + wantErr: false, + }, + { + name: "Test externalRules (context=project) when feature flag is set to true", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalRulesProjectRT.Name).Return(r.externalRulesProjectRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + Status: apisv3.FeatureStatus{ + Default: false, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalRulesProjectRT.Name, + }, + want: r.writeNodesCR.Rules, + wantErr: false, + }, + { + name: "Test externalRules (context=project) when feature flag is set to false", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalRulesProjectRT.Name).Return(r.externalRulesProjectRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalRulesProjectRT.Name, + }, + want: nil, + wantErr: false, + }, + { + name: "Test externalRules (context=cluster) when feature flag is set to false", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalRulesClusterRT.Name).Return(r.externalRulesClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(r.readServiceCR, nil) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalRulesClusterRT.Name, + }, + want: r.readServiceCR.Rules, + wantErr: false, + }, + { + name: "Test external cluster role (context=cluster) when feature flag defaults to true", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalClusterRT.Name).Return(r.externalClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(r.readServiceCR, nil) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Status: apisv3.FeatureStatus{ + Default: true, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalClusterRT.Name, + }, + want: r.readServiceCR.Rules, + wantErr: false, + }, + { + name: "Test external cluster role (context=project) when feature flag defaults to true", args: args{ - caches: func() (v3.RoleTemplateCache, wranglerv1.ClusterRoleCache) { + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) - roleTemplateCache.EXPECT().Get(r.externalRT.Name).Return(r.externalRT, nil) + roleTemplateCache.EXPECT().Get(r.externalProjectRT.Name).Return(r.externalProjectRT, nil) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(r.readServiceCR, nil) - return roleTemplateCache, clusterRoleCache + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Status: apisv3.FeatureStatus{ + Default: true, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache }, - name: r.externalRT.Name, + name: r.externalProjectRT.Name, }, want: r.readServiceCR.Rules, wantErr: false, }, + { + name: "Test external cluster role (context=cluster) when feature flag is set to false", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalClusterRT.Name).Return(r.externalClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(r.readServiceCR, nil) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + Status: apisv3.FeatureStatus{ + Default: true, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalClusterRT.Name, + }, + want: r.readServiceCR.Rules, + wantErr: false, + }, + { + name: "Test external cluster role (context=project) when feature flag is set to false", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalProjectRT.Name).Return(r.externalProjectRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + Status: apisv3.FeatureStatus{ + Default: true, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalProjectRT.Name, + }, + want: nil, + wantErr: false, + }, + { + name: "Test non-existing external cluster role (context=cluster) when feature flag is set to true", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalClusterRT.Name).Return(r.externalClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(nil, errExpected) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + Status: apisv3.FeatureStatus{ + Default: true, + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalClusterRT.Name, + }, + wantErr: true, + }, + { + name: "Test non-existing external cluster role (context=cluster) when feature flag is set to false", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalClusterRT.Name).Return(r.externalClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(nil, errExpected) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalClusterRT.Name, + }, + wantErr: true, + }, + { + name: "Test non-existing external cluster role (context=project) when feature flag is set to true", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalProjectRT.Name).Return(r.externalProjectRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(nil, errExpected) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalProjectRT.Name, + }, + wantErr: true, + }, + { + name: "Test err getting ClusterRole when feature flag is disabled", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalClusterRT.Name).Return(r.externalClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + clusterRoleCache.EXPECT().Get(r.readServiceCR.Name).Return(nil, errExpected) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalClusterRT.Name, + }, + wantErr: true, + }, + { + name: "Test err getting feature flag", + args: args{ + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { + ctrl := gomock.NewController(r.T()) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(r.externalClusterRT.Name).Return(r.externalClusterRT, nil) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + featuresCache.EXPECT().Get(auth.ExternalRulesFeature).Return(nil, errExpected) + return roleTemplateCache, clusterRoleCache, featuresCache + }, + name: r.externalClusterRT.Name, + }, + wantErr: true, + }, // Get unknown role { name: "Test invalid template name", args: args{ - caches: func() (v3.RoleTemplateCache, wranglerv1.ClusterRoleCache) { + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().Get(invalidName).Return(nil, errExpected) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - return roleTemplateCache, clusterRoleCache + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + return roleTemplateCache, clusterRoleCache, featuresCache }, name: invalidName, }, @@ -232,13 +538,14 @@ func (r *RoleTemplateResolverSuite) TestRoleTemplateResolver() { { name: "Test invalid inherted template name", args: args{ - caches: func() (v3.RoleTemplateCache, wranglerv1.ClusterRoleCache) { + caches: func() (v3.RoleTemplateCache, wrbacv1.ClusterRoleCache, v3.FeatureCache) { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().Get(r.invalidInhertedRT.Name).Return(r.invalidInhertedRT, nil) roleTemplateCache.EXPECT().Get(invalidName).Return(nil, errExpected) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - return roleTemplateCache, clusterRoleCache + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + return roleTemplateCache, clusterRoleCache, featuresCache }, name: r.invalidInhertedRT.Name, }, @@ -269,6 +576,7 @@ func (r *RoleTemplateResolverSuite) TestGetCache() { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - resolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + resolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, featuresCache) r.Equal(resolver.RoleTemplateCache(), roleTemplateCache, "Resolver did not correctly return cache") } diff --git a/pkg/auth/rolegetter.go b/pkg/auth/rolegetter.go index e7f423d34..ea2ca3264 100644 --- a/pkg/auth/rolegetter.go +++ b/pkg/auth/rolegetter.go @@ -1,7 +1,7 @@ package auth import ( - wranglerv1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1" + wranglerv1 "github.com/rancher/wrangler/v2/pkg/generated/controllers/rbac/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/labels" ) diff --git a/pkg/capi/capi.go b/pkg/capi/capi.go deleted file mode 100644 index 7ea5446e4..000000000 --- a/pkg/capi/capi.go +++ /dev/null @@ -1,114 +0,0 @@ -// Package capi is used to run a CAPI webhook server -package capi - -import ( - "context" - "crypto/tls" - "fmt" - "os" - "path/filepath" - "strconv" - - controllerruntime "github.com/rancher/lasso/controller-runtime" - "github.com/rancher/webhook/pkg/clients" - "github.com/rancher/wrangler/pkg/schemes" - "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - clusterv1alpha3 "sigs.k8s.io/cluster-api/api/v1alpha3" - clusterv1alpha4 "sigs.k8s.io/cluster-api/api/v1alpha4" - clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" - addonsv1alpha3 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3" - addonsv1alpha4 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha4" - addonsv1beta1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" - expv1alpha3 "sigs.k8s.io/cluster-api/exp/api/v1alpha3" - expv1alpha4 "sigs.k8s.io/cluster-api/exp/api/v1alpha4" - expv1beta1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" - "sigs.k8s.io/cluster-api/webhooks" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func init() { - _ = clientgoscheme.AddToScheme(schemes.All) - _ = clusterv1alpha3.AddToScheme(schemes.All) - _ = clusterv1alpha4.AddToScheme(schemes.All) - _ = clusterv1beta1.AddToScheme(schemes.All) - _ = expv1alpha3.AddToScheme(schemes.All) - _ = expv1alpha4.AddToScheme(schemes.All) - _ = expv1beta1.AddToScheme(schemes.All) - _ = addonsv1alpha3.AddToScheme(schemes.All) - _ = addonsv1alpha4.AddToScheme(schemes.All) - _ = addonsv1beta1.AddToScheme(schemes.All) - _ = apiextensionsv1.AddToScheme(schemes.All) -} - -const ( - defaultCapiPort = 8777 - capiPortEnvKey = "CATTLE_CAPI_PORT" -) - -var tlsCert = filepath.Join(os.TempDir(), "k8s-webhook-server", "serving-certs", "tls.crt") - -// Register registers a new CAPI webhook server and returns a start function. -func Register(clients *clients.Clients, tlsOpts ...func(*tls.Config)) (func(ctx context.Context) error, error) { - capiPort := defaultCapiPort - if portStr := os.Getenv(capiPortEnvKey); portStr != "" { - var err error - capiPort, err = strconv.Atoi(portStr) - if err != nil { - return nil, fmt.Errorf("failed to decode CAPI port value '%s': %w", portStr, err) - } - } - mgr, err := ctrl.NewManager(clients.RESTConfig, ctrl.Options{ - MetricsBindAddress: "0", - NewCache: controllerruntime.NewNewCacheFunc(clients.SharedControllerFactory.SharedCacheFactory(), - clients.Dynamic), - Scheme: schemes.All, - ClientDisableCacheFor: []client.Object{ - &corev1.ConfigMap{}, - &corev1.Secret{}, - }, - WebhookServer: webhook.NewServer(webhook.Options{ - Port: capiPort, - TLSMinVersion: "1.2", - TLSOpts: tlsOpts, - }), - }) - - if err != nil { - return nil, err - } - - for _, capiWebhook := range capiWebhooks() { - if err := capiWebhook.SetupWebhookWithManager(mgr); err != nil { - return nil, err - } - } - - return func(ctx context.Context) error { - if _, err := os.Stat(tlsCert); os.IsNotExist(err) { - logrus.Errorf("Failed to file %s, not running capi webhooks", tlsCert) - return nil - } else if err != nil { - return err - } - return mgr.Start(ctx) - }, nil -} - -func capiWebhooks() []capiWebhook { - return []capiWebhook{ - &webhooks.Cluster{}, - &clusterv1beta1.Machine{}, - &clusterv1beta1.MachineHealthCheck{}, - &clusterv1beta1.MachineSet{}, - &clusterv1beta1.MachineDeployment{}, - } -} - -type capiWebhook interface { - SetupWebhookWithManager(mgr ctrl.Manager) error -} diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go index 9fe4fbc8e..ca61e4e90 100644 --- a/pkg/clients/clients.go +++ b/pkg/clients/clients.go @@ -8,8 +8,8 @@ import ( managementv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" "github.com/rancher/webhook/pkg/generated/controllers/provisioning.cattle.io" provv1 "github.com/rancher/webhook/pkg/generated/controllers/provisioning.cattle.io/v1" - "github.com/rancher/wrangler/pkg/clients" - "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/v2/pkg/clients" + "github.com/rancher/wrangler/v2/pkg/schemes" v1 "k8s.io/api/admissionregistration/v1" "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -66,7 +66,7 @@ func New(ctx context.Context, rest *rest.Config, mcmEnabled bool) (*Clients, err } if mcmEnabled { - result.RoleTemplateResolver = auth.NewRoleTemplateResolver(mgmt.Management().V3().RoleTemplate().Cache(), clients.RBAC.ClusterRole().Cache()) + result.RoleTemplateResolver = auth.NewRoleTemplateResolver(mgmt.Management().V3().RoleTemplate().Cache(), clients.RBAC.ClusterRole().Cache(), mgmt.Management().V3().Feature().Cache()) result.GlobalRoleResolver = auth.NewGlobalRoleResolver(result.RoleTemplateResolver, mgmt.Management().V3().GlobalRole().Cache()) } diff --git a/pkg/codegen/main.go b/pkg/codegen/main.go index 977c26216..a043ba17e 100644 --- a/pkg/codegen/main.go +++ b/pkg/codegen/main.go @@ -11,8 +11,8 @@ import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" v1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" - controllergen "github.com/rancher/wrangler/pkg/controller-gen" - "github.com/rancher/wrangler/pkg/controller-gen/args" + controllergen "github.com/rancher/wrangler/v2/pkg/controller-gen" + "github.com/rancher/wrangler/v2/pkg/controller-gen/args" "golang.org/x/tools/imports" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -43,6 +43,7 @@ func main() { v3.ProjectRoleTemplateBinding{}, v3.Node{}, v3.Project{}, + v3.Feature{}, }, }, "provisioning.cattle.io": { @@ -68,6 +69,7 @@ func main() { &v3.ProjectRoleTemplateBinding{}, &v3.NodeDriver{}, &v3.Project{}, + &v3.Setting{}, }, }, "provisioning.cattle.io": { diff --git a/pkg/generated/controllers/management.cattle.io/factory.go b/pkg/generated/controllers/management.cattle.io/factory.go index fe6198d6d..9a06dc6d7 100644 --- a/pkg/generated/controllers/management.cattle.io/factory.go +++ b/pkg/generated/controllers/management.cattle.io/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package management import ( "github.com/rancher/lasso/pkg/controller" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" "k8s.io/client-go/rest" ) diff --git a/pkg/generated/controllers/management.cattle.io/interface.go b/pkg/generated/controllers/management.cattle.io/interface.go index 1132a5891..8bf3b9b6c 100644 --- a/pkg/generated/controllers/management.cattle.io/interface.go +++ b/pkg/generated/controllers/management.cattle.io/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/controllers/management.cattle.io/v3/cluster.go b/pkg/generated/controllers/management.cattle.io/v3/cluster.go index c3c46bc1a..a3a2b8e8b 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/cluster.go +++ b/pkg/generated/controllers/management.cattle.io/v3/cluster.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ package v3 import ( "context" + "sync" "time" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/apply" - "github.com/rancher/wrangler/pkg/condition" - "github.com/rancher/wrangler/pkg/generic" - "github.com/rancher/wrangler/pkg/kv" + "github.com/rancher/wrangler/v2/pkg/apply" + "github.com/rancher/wrangler/v2/pkg/condition" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/kv" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,10 +49,14 @@ type ClusterCache interface { generic.NonNamespacedCacheInterface[*v3.Cluster] } +// ClusterStatusHandler is executed for every added or modified Cluster. Should return the new status to be updated type ClusterStatusHandler func(obj *v3.Cluster, status v3.ClusterStatus) (v3.ClusterStatus, error) +// ClusterGeneratingHandler is the top-level handler that is executed for every Cluster event. It extends ClusterStatusHandler by a returning a slice of child objects to be passed to apply.Apply type ClusterGeneratingHandler func(obj *v3.Cluster, status v3.ClusterStatus) ([]runtime.Object, v3.ClusterStatus, error) +// RegisterClusterStatusHandler configures a ClusterController to execute a ClusterStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterClusterStatusHandler(ctx context.Context, controller ClusterController, condition condition.Cond, name string, handler ClusterStatusHandler) { statusHandler := &clusterStatusHandler{ client: controller, @@ -61,6 +66,8 @@ func RegisterClusterStatusHandler(ctx context.Context, controller ClusterControl controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) } +// RegisterClusterGeneratingHandler configures a ClusterController to execute a ClusterGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterClusterGeneratingHandler(ctx context.Context, controller ClusterController, apply apply.Apply, condition condition.Cond, name string, handler ClusterGeneratingHandler, opts *generic.GeneratingHandlerOptions) { statusHandler := &clusterGeneratingHandler{ @@ -82,6 +89,7 @@ type clusterStatusHandler struct { handler ClusterStatusHandler } +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API func (a *clusterStatusHandler) sync(key string, obj *v3.Cluster) (*v3.Cluster, error) { if obj == nil { return obj, nil @@ -127,8 +135,10 @@ type clusterGeneratingHandler struct { opts generic.GeneratingHandlerOptions gvk schema.GroupVersionKind name string + seen sync.Map } +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied func (a *clusterGeneratingHandler) Remove(key string, obj *v3.Cluster) (*v3.Cluster, error) { if obj != nil { return obj, nil @@ -138,12 +148,17 @@ func (a *clusterGeneratingHandler) Remove(key string, obj *v3.Cluster) (*v3.Clus obj.Namespace, obj.Name = kv.RSplit(key, "/") obj.SetGroupVersionKind(a.gvk) + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects() } +// Handle executes the configured ClusterGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource func (a *clusterGeneratingHandler) Handle(obj *v3.Cluster, status v3.ClusterStatus) (v3.ClusterStatus, error) { if !obj.DeletionTimestamp.IsZero() { return status, nil @@ -153,9 +168,41 @@ func (a *clusterGeneratingHandler) Handle(obj *v3.Cluster, status v3.ClusterStat if err != nil { return newStatus, err } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } - return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *clusterGeneratingHandler) isNewResourceVersion(obj *v3.Cluster) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *clusterGeneratingHandler) storeResourceVersion(obj *v3.Cluster) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) } diff --git a/pkg/generated/controllers/management.cattle.io/v3/clusterroletemplatebinding.go b/pkg/generated/controllers/management.cattle.io/v3/clusterroletemplatebinding.go index 56d686439..4d2589354 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/clusterroletemplatebinding.go +++ b/pkg/generated/controllers/management.cattle.io/v3/clusterroletemplatebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v3 import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" ) // ClusterRoleTemplateBindingController interface for managing ClusterRoleTemplateBinding resources. diff --git a/pkg/generated/controllers/management.cattle.io/v3/feature.go b/pkg/generated/controllers/management.cattle.io/v3/feature.go new file mode 100644 index 000000000..52ee59e90 --- /dev/null +++ b/pkg/generated/controllers/management.cattle.io/v3/feature.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by codegen. DO NOT EDIT. + +package v3 + +import ( + "context" + "sync" + "time" + + v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + "github.com/rancher/wrangler/v2/pkg/apply" + "github.com/rancher/wrangler/v2/pkg/condition" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// FeatureController interface for managing Feature resources. +type FeatureController interface { + generic.NonNamespacedControllerInterface[*v3.Feature, *v3.FeatureList] +} + +// FeatureClient interface for managing Feature resources in Kubernetes. +type FeatureClient interface { + generic.NonNamespacedClientInterface[*v3.Feature, *v3.FeatureList] +} + +// FeatureCache interface for retrieving Feature resources in memory. +type FeatureCache interface { + generic.NonNamespacedCacheInterface[*v3.Feature] +} + +// FeatureStatusHandler is executed for every added or modified Feature. Should return the new status to be updated +type FeatureStatusHandler func(obj *v3.Feature, status v3.FeatureStatus) (v3.FeatureStatus, error) + +// FeatureGeneratingHandler is the top-level handler that is executed for every Feature event. It extends FeatureStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type FeatureGeneratingHandler func(obj *v3.Feature, status v3.FeatureStatus) ([]runtime.Object, v3.FeatureStatus, error) + +// RegisterFeatureStatusHandler configures a FeatureController to execute a FeatureStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterFeatureStatusHandler(ctx context.Context, controller FeatureController, condition condition.Cond, name string, handler FeatureStatusHandler) { + statusHandler := &featureStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterFeatureGeneratingHandler configures a FeatureController to execute a FeatureGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterFeatureGeneratingHandler(ctx context.Context, controller FeatureController, apply apply.Apply, + condition condition.Cond, name string, handler FeatureGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &featureGeneratingHandler{ + FeatureGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterFeatureStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type featureStatusHandler struct { + client FeatureClient + condition condition.Cond + handler FeatureStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *featureStatusHandler) sync(key string, obj *v3.Feature) (*v3.Feature, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type featureGeneratingHandler struct { + FeatureGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *featureGeneratingHandler) Remove(key string, obj *v3.Feature) (*v3.Feature, error) { + if obj != nil { + return obj, nil + } + + obj = &v3.Feature{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured FeatureGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *featureGeneratingHandler) Handle(obj *v3.Feature, status v3.FeatureStatus) (v3.FeatureStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.FeatureGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *featureGeneratingHandler) isNewResourceVersion(obj *v3.Feature) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *featureGeneratingHandler) storeResourceVersion(obj *v3.Feature) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/pkg/generated/controllers/management.cattle.io/v3/globalrole.go b/pkg/generated/controllers/management.cattle.io/v3/globalrole.go index 3468d909b..0c3db12d6 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/globalrole.go +++ b/pkg/generated/controllers/management.cattle.io/v3/globalrole.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v3 import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" ) // GlobalRoleController interface for managing GlobalRole resources. diff --git a/pkg/generated/controllers/management.cattle.io/v3/globalrolebinding.go b/pkg/generated/controllers/management.cattle.io/v3/globalrolebinding.go index c26f6d91e..277af21f0 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/globalrolebinding.go +++ b/pkg/generated/controllers/management.cattle.io/v3/globalrolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v3 import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" ) // GlobalRoleBindingController interface for managing GlobalRoleBinding resources. diff --git a/pkg/generated/controllers/management.cattle.io/v3/interface.go b/pkg/generated/controllers/management.cattle.io/v3/interface.go index 8bfe4ce12..ce06dab07 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/interface.go +++ b/pkg/generated/controllers/management.cattle.io/v3/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ package v3 import ( "github.com/rancher/lasso/pkg/controller" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" - "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/schemes" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -33,6 +33,7 @@ func init() { type Interface interface { Cluster() ClusterController ClusterRoleTemplateBinding() ClusterRoleTemplateBindingController + Feature() FeatureController GlobalRole() GlobalRoleController GlobalRoleBinding() GlobalRoleBindingController Node() NodeController @@ -60,6 +61,10 @@ func (v *version) ClusterRoleTemplateBinding() ClusterRoleTemplateBindingControl return generic.NewController[*v3.ClusterRoleTemplateBinding, *v3.ClusterRoleTemplateBindingList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "ClusterRoleTemplateBinding"}, "clusterroletemplatebindings", true, v.controllerFactory) } +func (v *version) Feature() FeatureController { + return generic.NewNonNamespacedController[*v3.Feature, *v3.FeatureList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "Feature"}, "features", v.controllerFactory) +} + func (v *version) GlobalRole() GlobalRoleController { return generic.NewNonNamespacedController[*v3.GlobalRole, *v3.GlobalRoleList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "GlobalRole"}, "globalroles", v.controllerFactory) } diff --git a/pkg/generated/controllers/management.cattle.io/v3/node.go b/pkg/generated/controllers/management.cattle.io/v3/node.go index 286b03444..98c653737 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/node.go +++ b/pkg/generated/controllers/management.cattle.io/v3/node.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ package v3 import ( "context" + "sync" "time" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/apply" - "github.com/rancher/wrangler/pkg/condition" - "github.com/rancher/wrangler/pkg/generic" - "github.com/rancher/wrangler/pkg/kv" + "github.com/rancher/wrangler/v2/pkg/apply" + "github.com/rancher/wrangler/v2/pkg/condition" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/kv" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,10 +49,14 @@ type NodeCache interface { generic.CacheInterface[*v3.Node] } +// NodeStatusHandler is executed for every added or modified Node. Should return the new status to be updated type NodeStatusHandler func(obj *v3.Node, status v3.NodeStatus) (v3.NodeStatus, error) +// NodeGeneratingHandler is the top-level handler that is executed for every Node event. It extends NodeStatusHandler by a returning a slice of child objects to be passed to apply.Apply type NodeGeneratingHandler func(obj *v3.Node, status v3.NodeStatus) ([]runtime.Object, v3.NodeStatus, error) +// RegisterNodeStatusHandler configures a NodeController to execute a NodeStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterNodeStatusHandler(ctx context.Context, controller NodeController, condition condition.Cond, name string, handler NodeStatusHandler) { statusHandler := &nodeStatusHandler{ client: controller, @@ -61,6 +66,8 @@ func RegisterNodeStatusHandler(ctx context.Context, controller NodeController, c controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) } +// RegisterNodeGeneratingHandler configures a NodeController to execute a NodeGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterNodeGeneratingHandler(ctx context.Context, controller NodeController, apply apply.Apply, condition condition.Cond, name string, handler NodeGeneratingHandler, opts *generic.GeneratingHandlerOptions) { statusHandler := &nodeGeneratingHandler{ @@ -82,6 +89,7 @@ type nodeStatusHandler struct { handler NodeStatusHandler } +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API func (a *nodeStatusHandler) sync(key string, obj *v3.Node) (*v3.Node, error) { if obj == nil { return obj, nil @@ -127,8 +135,10 @@ type nodeGeneratingHandler struct { opts generic.GeneratingHandlerOptions gvk schema.GroupVersionKind name string + seen sync.Map } +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied func (a *nodeGeneratingHandler) Remove(key string, obj *v3.Node) (*v3.Node, error) { if obj != nil { return obj, nil @@ -138,12 +148,17 @@ func (a *nodeGeneratingHandler) Remove(key string, obj *v3.Node) (*v3.Node, erro obj.Namespace, obj.Name = kv.RSplit(key, "/") obj.SetGroupVersionKind(a.gvk) + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects() } +// Handle executes the configured NodeGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource func (a *nodeGeneratingHandler) Handle(obj *v3.Node, status v3.NodeStatus) (v3.NodeStatus, error) { if !obj.DeletionTimestamp.IsZero() { return status, nil @@ -153,9 +168,41 @@ func (a *nodeGeneratingHandler) Handle(obj *v3.Node, status v3.NodeStatus) (v3.N if err != nil { return newStatus, err } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } - return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *nodeGeneratingHandler) isNewResourceVersion(obj *v3.Node) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *nodeGeneratingHandler) storeResourceVersion(obj *v3.Node) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) } diff --git a/pkg/generated/controllers/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate.go b/pkg/generated/controllers/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate.go index 65b8e7cd0..d37ac0e8d 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate.go +++ b/pkg/generated/controllers/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v3 import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" ) // PodSecurityAdmissionConfigurationTemplateController interface for managing PodSecurityAdmissionConfigurationTemplate resources. diff --git a/pkg/generated/controllers/management.cattle.io/v3/project.go b/pkg/generated/controllers/management.cattle.io/v3/project.go index 6f3e0060a..221a70c9d 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/project.go +++ b/pkg/generated/controllers/management.cattle.io/v3/project.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ package v3 import ( "context" + "sync" "time" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/apply" - "github.com/rancher/wrangler/pkg/condition" - "github.com/rancher/wrangler/pkg/generic" - "github.com/rancher/wrangler/pkg/kv" + "github.com/rancher/wrangler/v2/pkg/apply" + "github.com/rancher/wrangler/v2/pkg/condition" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/kv" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,10 +49,14 @@ type ProjectCache interface { generic.CacheInterface[*v3.Project] } +// ProjectStatusHandler is executed for every added or modified Project. Should return the new status to be updated type ProjectStatusHandler func(obj *v3.Project, status v3.ProjectStatus) (v3.ProjectStatus, error) +// ProjectGeneratingHandler is the top-level handler that is executed for every Project event. It extends ProjectStatusHandler by a returning a slice of child objects to be passed to apply.Apply type ProjectGeneratingHandler func(obj *v3.Project, status v3.ProjectStatus) ([]runtime.Object, v3.ProjectStatus, error) +// RegisterProjectStatusHandler configures a ProjectController to execute a ProjectStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterProjectStatusHandler(ctx context.Context, controller ProjectController, condition condition.Cond, name string, handler ProjectStatusHandler) { statusHandler := &projectStatusHandler{ client: controller, @@ -61,6 +66,8 @@ func RegisterProjectStatusHandler(ctx context.Context, controller ProjectControl controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) } +// RegisterProjectGeneratingHandler configures a ProjectController to execute a ProjectGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterProjectGeneratingHandler(ctx context.Context, controller ProjectController, apply apply.Apply, condition condition.Cond, name string, handler ProjectGeneratingHandler, opts *generic.GeneratingHandlerOptions) { statusHandler := &projectGeneratingHandler{ @@ -82,6 +89,7 @@ type projectStatusHandler struct { handler ProjectStatusHandler } +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API func (a *projectStatusHandler) sync(key string, obj *v3.Project) (*v3.Project, error) { if obj == nil { return obj, nil @@ -127,8 +135,10 @@ type projectGeneratingHandler struct { opts generic.GeneratingHandlerOptions gvk schema.GroupVersionKind name string + seen sync.Map } +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied func (a *projectGeneratingHandler) Remove(key string, obj *v3.Project) (*v3.Project, error) { if obj != nil { return obj, nil @@ -138,12 +148,17 @@ func (a *projectGeneratingHandler) Remove(key string, obj *v3.Project) (*v3.Proj obj.Namespace, obj.Name = kv.RSplit(key, "/") obj.SetGroupVersionKind(a.gvk) + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects() } +// Handle executes the configured ProjectGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource func (a *projectGeneratingHandler) Handle(obj *v3.Project, status v3.ProjectStatus) (v3.ProjectStatus, error) { if !obj.DeletionTimestamp.IsZero() { return status, nil @@ -153,9 +168,41 @@ func (a *projectGeneratingHandler) Handle(obj *v3.Project, status v3.ProjectStat if err != nil { return newStatus, err } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } - return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *projectGeneratingHandler) isNewResourceVersion(obj *v3.Project) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *projectGeneratingHandler) storeResourceVersion(obj *v3.Project) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) } diff --git a/pkg/generated/controllers/management.cattle.io/v3/projectroletemplatebinding.go b/pkg/generated/controllers/management.cattle.io/v3/projectroletemplatebinding.go index ca783178b..984da0f5b 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/projectroletemplatebinding.go +++ b/pkg/generated/controllers/management.cattle.io/v3/projectroletemplatebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v3 import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" ) // ProjectRoleTemplateBindingController interface for managing ProjectRoleTemplateBinding resources. diff --git a/pkg/generated/controllers/management.cattle.io/v3/roletemplate.go b/pkg/generated/controllers/management.cattle.io/v3/roletemplate.go index e0331017b..8958365a8 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/roletemplate.go +++ b/pkg/generated/controllers/management.cattle.io/v3/roletemplate.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v3 import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" ) // RoleTemplateController interface for managing RoleTemplate resources. diff --git a/pkg/generated/controllers/provisioning.cattle.io/factory.go b/pkg/generated/controllers/provisioning.cattle.io/factory.go index cb3cf9f56..4acd705c7 100644 --- a/pkg/generated/controllers/provisioning.cattle.io/factory.go +++ b/pkg/generated/controllers/provisioning.cattle.io/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package provisioning import ( "github.com/rancher/lasso/pkg/controller" - "github.com/rancher/wrangler/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/generic" "k8s.io/client-go/rest" ) diff --git a/pkg/generated/controllers/provisioning.cattle.io/interface.go b/pkg/generated/controllers/provisioning.cattle.io/interface.go index f63f0357f..39e0b180e 100644 --- a/pkg/generated/controllers/provisioning.cattle.io/interface.go +++ b/pkg/generated/controllers/provisioning.cattle.io/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/controllers/provisioning.cattle.io/v1/cluster.go b/pkg/generated/controllers/provisioning.cattle.io/v1/cluster.go index 04bb13e26..6d4da4d3c 100644 --- a/pkg/generated/controllers/provisioning.cattle.io/v1/cluster.go +++ b/pkg/generated/controllers/provisioning.cattle.io/v1/cluster.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ package v1 import ( "context" + "sync" "time" v1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" - "github.com/rancher/wrangler/pkg/apply" - "github.com/rancher/wrangler/pkg/condition" - "github.com/rancher/wrangler/pkg/generic" - "github.com/rancher/wrangler/pkg/kv" + "github.com/rancher/wrangler/v2/pkg/apply" + "github.com/rancher/wrangler/v2/pkg/condition" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/kv" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,10 +49,14 @@ type ClusterCache interface { generic.CacheInterface[*v1.Cluster] } +// ClusterStatusHandler is executed for every added or modified Cluster. Should return the new status to be updated type ClusterStatusHandler func(obj *v1.Cluster, status v1.ClusterStatus) (v1.ClusterStatus, error) +// ClusterGeneratingHandler is the top-level handler that is executed for every Cluster event. It extends ClusterStatusHandler by a returning a slice of child objects to be passed to apply.Apply type ClusterGeneratingHandler func(obj *v1.Cluster, status v1.ClusterStatus) ([]runtime.Object, v1.ClusterStatus, error) +// RegisterClusterStatusHandler configures a ClusterController to execute a ClusterStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterClusterStatusHandler(ctx context.Context, controller ClusterController, condition condition.Cond, name string, handler ClusterStatusHandler) { statusHandler := &clusterStatusHandler{ client: controller, @@ -61,6 +66,8 @@ func RegisterClusterStatusHandler(ctx context.Context, controller ClusterControl controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) } +// RegisterClusterGeneratingHandler configures a ClusterController to execute a ClusterGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution func RegisterClusterGeneratingHandler(ctx context.Context, controller ClusterController, apply apply.Apply, condition condition.Cond, name string, handler ClusterGeneratingHandler, opts *generic.GeneratingHandlerOptions) { statusHandler := &clusterGeneratingHandler{ @@ -82,6 +89,7 @@ type clusterStatusHandler struct { handler ClusterStatusHandler } +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API func (a *clusterStatusHandler) sync(key string, obj *v1.Cluster) (*v1.Cluster, error) { if obj == nil { return obj, nil @@ -127,8 +135,10 @@ type clusterGeneratingHandler struct { opts generic.GeneratingHandlerOptions gvk schema.GroupVersionKind name string + seen sync.Map } +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied func (a *clusterGeneratingHandler) Remove(key string, obj *v1.Cluster) (*v1.Cluster, error) { if obj != nil { return obj, nil @@ -138,12 +148,17 @@ func (a *clusterGeneratingHandler) Remove(key string, obj *v1.Cluster) (*v1.Clus obj.Namespace, obj.Name = kv.RSplit(key, "/") obj.SetGroupVersionKind(a.gvk) + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects() } +// Handle executes the configured ClusterGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource func (a *clusterGeneratingHandler) Handle(obj *v1.Cluster, status v1.ClusterStatus) (v1.ClusterStatus, error) { if !obj.DeletionTimestamp.IsZero() { return status, nil @@ -153,9 +168,41 @@ func (a *clusterGeneratingHandler) Handle(obj *v1.Cluster, status v1.ClusterStat if err != nil { return newStatus, err } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } - return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). WithOwner(obj). WithSetID(a.name). ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *clusterGeneratingHandler) isNewResourceVersion(obj *v1.Cluster) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *clusterGeneratingHandler) storeResourceVersion(obj *v1.Cluster) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) } diff --git a/pkg/generated/controllers/provisioning.cattle.io/v1/interface.go b/pkg/generated/controllers/provisioning.cattle.io/v1/interface.go index 9e9c3fc2c..057c94967 100644 --- a/pkg/generated/controllers/provisioning.cattle.io/v1/interface.go +++ b/pkg/generated/controllers/provisioning.cattle.io/v1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2023 Rancher Labs, Inc. +Copyright 2025 Rancher Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ package v1 import ( "github.com/rancher/lasso/pkg/controller" v1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" - "github.com/rancher/wrangler/pkg/generic" - "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/v2/pkg/generic" + "github.com/rancher/wrangler/v2/pkg/schemes" "k8s.io/apimachinery/pkg/runtime/schema" ) diff --git a/pkg/generated/objects/management.cattle.io/v3/objects.go b/pkg/generated/objects/management.cattle.io/v3/objects.go index fd83995b6..7cb9592f3 100644 --- a/pkg/generated/objects/management.cattle.io/v3/objects.go +++ b/pkg/generated/objects/management.cattle.io/v3/objects.go @@ -590,3 +590,56 @@ func ProjectFromRequest(request *admissionv1.AdmissionRequest) (*v3.Project, err return object, nil } + +// SettingOldAndNewFromRequest gets the old and new Setting objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for Setting. +// Similarly, if the request is a Create operation, then the old object is the zero value for Setting. +func SettingOldAndNewFromRequest(request *admissionv1.AdmissionRequest) (*v3.Setting, *v3.Setting, error) { + if request == nil { + return nil, nil, fmt.Errorf("nil request") + } + + object := &v3.Setting{} + oldObject := &v3.Setting{} + + if request.Operation != admissionv1.Delete { + err := json.Unmarshal(request.Object.Raw, object) + if err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal request object: %w", err) + } + } + + if request.Operation == admissionv1.Create { + return oldObject, object, nil + } + + err := json.Unmarshal(request.OldObject.Raw, oldObject) + if err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal request oldObject: %w", err) + } + + return oldObject, object, nil +} + +// SettingFromRequest returns a Setting object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func SettingFromRequest(request *admissionv1.AdmissionRequest) (*v3.Setting, error) { + if request == nil { + return nil, fmt.Errorf("nil request") + } + + object := &v3.Setting{} + raw := request.Object.Raw + + if request.Operation == admissionv1.Delete { + raw = request.OldObject.Raw + } + + err := json.Unmarshal(raw, object) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal request object: %w", err) + } + + return object, nil +} diff --git a/pkg/resolvers/mockAuthRuleResolver_test.go b/pkg/mocks/authRuleResolver.go similarity index 99% rename from pkg/resolvers/mockAuthRuleResolver_test.go rename to pkg/mocks/authRuleResolver.go index cbb8599d2..ff32d82ef 100644 --- a/pkg/resolvers/mockAuthRuleResolver_test.go +++ b/pkg/mocks/authRuleResolver.go @@ -2,7 +2,7 @@ // Source: k8s.io/kubernetes/pkg/registry/rbac/validation (interfaces: AuthorizationRuleResolver) // Package resolvers is a generated GoMock package. -package resolvers +package mocks import ( fmt "fmt" diff --git a/pkg/resolvers/aggregateResolver_test.go b/pkg/resolvers/aggregateResolver_test.go index b498848ed..3d5cb6bba 100644 --- a/pkg/resolvers/aggregateResolver_test.go +++ b/pkg/resolvers/aggregateResolver_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/rancher/webhook/pkg/mocks" "github.com/stretchr/testify/suite" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apiserver/pkg/authentication/user" @@ -55,9 +56,9 @@ func (a *AggregateResolverSuite) TestAggregateRuleResolverGetRules() { namespace: testNameSpace, resolvers: func(t *testing.T) ([]validation.AuthorizationRuleResolver, Rules) { expectedRules := []rbacv1.PolicyRule{a.ruleAdmin} - resolver := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver.EXPECT().VisitRulesFor(testUser, testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { for _, rule := range expectedRules { visitor(nil, &rule, nil) } @@ -74,15 +75,15 @@ func (a *AggregateResolverSuite) TestAggregateRuleResolverGetRules() { wantErr: true, resolvers: func(t *testing.T) ([]validation.AuthorizationRuleResolver, Rules) { expectedRules := []rbacv1.PolicyRule{} - resolver := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver.EXPECT().VisitRulesFor(gomock.Any(), testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { visitor(nil, nil, errNotFound) return true }) - resolver2 := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver2 := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver2.EXPECT().VisitRulesFor(gomock.Any(), testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { visitor(nil, nil, errNotFound) return true }) @@ -95,15 +96,15 @@ func (a *AggregateResolverSuite) TestAggregateRuleResolverGetRules() { namespace: testNameSpace, resolvers: func(t *testing.T) ([]validation.AuthorizationRuleResolver, Rules) { expectedRules := []rbacv1.PolicyRule{a.ruleReadPods} - resolver := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver.EXPECT().VisitRulesFor(testUser, testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, _ func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { return true }) resolver.EXPECT().GetRoleReferenceRules(gomock.Any(), gomock.Any()).Return(expectedRules, nil) - resolver2 := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver2 := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver2.EXPECT().VisitRulesFor(testUser, testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { for _, rule := range expectedRules { visitor(nil, &rule, nil) } @@ -120,18 +121,18 @@ func (a *AggregateResolverSuite) TestAggregateRuleResolverGetRules() { resolvers: func(t *testing.T) ([]validation.AuthorizationRuleResolver, Rules) { expectedRules1 := []rbacv1.PolicyRule{a.ruleAdmin} expectedRules2 := []rbacv1.PolicyRule{a.ruleReadPods} - resolver := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver.EXPECT().VisitRulesFor(testUser, testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { for _, rule := range expectedRules1 { visitor(nil, &rule, nil) } return true }) resolver.EXPECT().GetRoleReferenceRules(gomock.Any(), gomock.Any()).Return(expectedRules1, nil) - resolver2 := NewMockAuthorizationRuleResolver(gomock.NewController(t)) + resolver2 := mocks.NewMockAuthorizationRuleResolver(gomock.NewController(t)) resolver2.EXPECT().VisitRulesFor(testUser, testNameSpace, gomock.Any()). - DoAndReturn(func(userInfo user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { + DoAndReturn(func(_ user.Info, _ string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) bool { for _, rule := range expectedRules2 { visitor(nil, &rule, nil) } diff --git a/pkg/resolvers/crtbResolver_test.go b/pkg/resolvers/crtbResolver_test.go index 8f1648004..8b128f576 100644 --- a/pkg/resolvers/crtbResolver_test.go +++ b/pkg/resolvers/crtbResolver_test.go @@ -7,7 +7,7 @@ import ( apisv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/auth" v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/suite" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -278,7 +278,7 @@ func (c *CRTBResolverSuite) NewTestCRTBResolver() *CRTBRuleResolver { roleTemplateCache.EXPECT().Get(c.readRT.Name).Return(c.readRT, nil).AnyTimes() roleTemplateCache.EXPECT().Get(c.writeRT.Name).Return(c.writeRT, nil).AnyTimes() roleTemplateCache.EXPECT().Get(invalidName).Return(nil, errNotFound).AnyTimes() - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, nil) return NewCRTBRuleResolver(crtbCache, roleResolver) } @@ -294,7 +294,7 @@ func NewCRTBCache(ctrl *gomock.Controller, bindings []*apisv3.ClusterRoleTemplat return nil, errNotFound }).AnyTimes() clusterCache.EXPECT().AddIndexer(crtbSubjectIndex, gomock.Any()).AnyTimes() - clusterCache.EXPECT().GetByIndex(crtbSubjectIndex, gomock.Any()).DoAndReturn(func(index string, subject string) ([]*apisv3.ClusterRoleTemplateBinding, error) { + clusterCache.EXPECT().GetByIndex(crtbSubjectIndex, gomock.Any()).DoAndReturn(func(_ string, subject string) ([]*apisv3.ClusterRoleTemplateBinding, error) { retList := []*apisv3.ClusterRoleTemplateBinding{} // for each binding create a lists of subject keys from the binding // if the provided subject matches any of those keys at the binding to the returned list diff --git a/pkg/resolvers/grbClusterResolver_test.go b/pkg/resolvers/grbClusterResolver_test.go index b8ac4c61d..7fc39f252 100644 --- a/pkg/resolvers/grbClusterResolver_test.go +++ b/pkg/resolvers/grbClusterResolver_test.go @@ -7,7 +7,7 @@ import ( "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/auth" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/suite" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -324,7 +324,7 @@ func (g *GRBClusterRuleResolverSuite) TestGRBClusterRuleResolver() { test.setup(state) } - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCache, nil), state.grCache) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCache, nil, nil), state.grCache) grbResolver := NewGRBClusterRuleResolver(state.grbCache, grResolver) rules, err := grbResolver.RulesFor(g.userInfo, test.namespace) diff --git a/pkg/resolvers/prtbResolver_test.go b/pkg/resolvers/prtbResolver_test.go index 3bf4e3cbd..0d6454525 100644 --- a/pkg/resolvers/prtbResolver_test.go +++ b/pkg/resolvers/prtbResolver_test.go @@ -7,7 +7,7 @@ import ( apisv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/auth" v3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/suite" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -281,7 +281,7 @@ func (p *PRTBResolverSuite) NewTestPRTBResolver() *PRTBRuleResolver { roleTemplateCache.EXPECT().Get(p.readRT.Name).Return(p.readRT, nil).AnyTimes() roleTemplateCache.EXPECT().Get(p.writeRT.Name).Return(p.writeRT, nil).AnyTimes() roleTemplateCache.EXPECT().Get(invalidName).Return(nil, errNotFound).AnyTimes() - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, nil) return NewPRTBRuleResolver(PRTBCache, roleResolver) } @@ -290,7 +290,7 @@ func NewPRTBCache(ctrl *gomock.Controller, bindings []*apisv3.ProjectRoleTemplat projectCache.EXPECT().AddIndexer(prtbSubjectIndex, gomock.Any()).AnyTimes() - projectCache.EXPECT().GetByIndex(prtbSubjectIndex, gomock.Any()).DoAndReturn(func(index string, subject string) ([]*apisv3.ProjectRoleTemplateBinding, error) { + projectCache.EXPECT().GetByIndex(prtbSubjectIndex, gomock.Any()).DoAndReturn(func(_ string, subject string) ([]*apisv3.ProjectRoleTemplateBinding, error) { retList := []*apisv3.ProjectRoleTemplateBinding{} // for each binding create a lists of subject keys from the binding diff --git a/pkg/resources/common/validation.go b/pkg/resources/common/validation.go index 47ec6606a..9c3e53df6 100644 --- a/pkg/resources/common/validation.go +++ b/pkg/resources/common/validation.go @@ -1,7 +1,7 @@ package common import ( - "fmt" + "errors" "net/http" "github.com/rancher/webhook/pkg/admission" @@ -9,6 +9,9 @@ import ( admissionv1 "k8s.io/api/admission/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/rbac" + rbacValidation "k8s.io/kubernetes/pkg/apis/rbac/validation" ) func CheckCreatorID(request *admission.Request, oldObj, newObj metav1.Object) *metav1.Status { @@ -44,13 +47,14 @@ func CheckCreatorID(request *admission.Request, oldObj, newObj metav1.Object) *m return nil } -// CheckForVerbs checks that all the rules in the given list have a verb set -func CheckForVerbs(rules []rbacv1.PolicyRule) error { - for i := range rules { - rule := rules[i] - if len(rule.Verbs) == 0 { - return fmt.Errorf("policyRules must have at least one verb: %s", rule.String()) - } +// ValidateRules calls on standard kubernetes RBAC functionality for the validation of policy rules +// to validate Rancher rules. This is currently used in the validation of globalroles and roletemplates. +func ValidateRules(rules []rbacv1.PolicyRule, isNamespaced bool, fldPath *field.Path) error { + var returnErr error + for index, r := range rules { + fieldErrs := rbacValidation.ValidatePolicyRule(rbac.PolicyRule(r), isNamespaced, + fldPath.Index(index)) + returnErr = errors.Join(returnErr, fieldErrs.ToAggregate()) } - return nil + return returnErr } diff --git a/pkg/resources/common/validation_test.go b/pkg/resources/common/validation_test.go new file mode 100644 index 000000000..8b64c4e0d --- /dev/null +++ b/pkg/resources/common/validation_test.go @@ -0,0 +1,85 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" + v1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestValidateRules(t *testing.T) { + t.Parallel() + + gResource := "something-global" + nsResource := "something-namespaced" + + gField := field.NewPath(gResource) + nsField := field.NewPath(nsResource) + + tests := []struct { + name string // label for testcase + data rbacv1.PolicyRule // policy rule to be validated + haserr bool // error expected ? + }{ + { + name: "ok", + data: rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{""}, + Resources: []string{"*"}, + }, + }, + { + name: "no-verbs", + data: rbacv1.PolicyRule{ + Verbs: []string{}, + APIGroups: []string{""}, + Resources: []string{"*"}, + }, + haserr: true, + }, + { + name: "no-api-groups", + data: rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{}, + Resources: []string{"*"}, + }, + haserr: true, + }, + { + name: "no-resources", + data: rbacv1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{""}, + Resources: []string{}, + }, + haserr: true, + }, + } + + for _, testcase := range tests { + t.Run("global/"+testcase.name, func(t *testing.T) { + err := ValidateRules([]v1.PolicyRule{ + testcase.data, + }, false, gField) + if testcase.haserr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + t.Run("namespaced/"+testcase.name, func(t *testing.T) { + err := ValidateRules([]v1.PolicyRule{ + testcase.data, + }, true, nsField) + if testcase.haserr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/resources/core/v1/secret/mutator.go b/pkg/resources/core/v1/secret/mutator.go index 0ff7524b4..3b96d009c 100644 --- a/pkg/resources/core/v1/secret/mutator.go +++ b/pkg/resources/core/v1/secret/mutator.go @@ -7,7 +7,7 @@ import ( "github.com/rancher/webhook/pkg/auth" objectsv1 "github.com/rancher/webhook/pkg/generated/objects/core/v1" "github.com/rancher/webhook/pkg/patch" - v1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1" + v1 "github.com/rancher/wrangler/v2/pkg/generated/controllers/rbac/v1" "github.com/sirupsen/logrus" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" diff --git a/pkg/resources/core/v1/secret/mutator_test.go b/pkg/resources/core/v1/secret/mutator_test.go index 20630248f..a04ce97fd 100644 --- a/pkg/resources/core/v1/secret/mutator_test.go +++ b/pkg/resources/core/v1/secret/mutator_test.go @@ -7,7 +7,7 @@ import ( "github.com/golang/mock/gomock" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" authenicationv1 "k8s.io/api/authentication/v1" diff --git a/pkg/resources/core/v1/secret/validator.go b/pkg/resources/core/v1/secret/validator.go index a3b515230..286ee7b70 100644 --- a/pkg/resources/core/v1/secret/validator.go +++ b/pkg/resources/core/v1/secret/validator.go @@ -6,7 +6,7 @@ import ( "github.com/rancher/webhook/pkg/admission" objectsv1 "github.com/rancher/webhook/pkg/generated/objects/core/v1" - v1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1" + v1 "github.com/rancher/wrangler/v2/pkg/generated/controllers/rbac/v1" "github.com/sirupsen/logrus" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" diff --git a/pkg/resources/core/v1/secret/validator_test.go b/pkg/resources/core/v1/secret/validator_test.go index b9b6c279c..c5d619e2e 100644 --- a/pkg/resources/core/v1/secret/validator_test.go +++ b/pkg/resources/core/v1/secret/validator_test.go @@ -7,7 +7,7 @@ import ( "github.com/golang/mock/gomock" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" v1authentication "k8s.io/api/authentication/v1" diff --git a/pkg/resources/management.cattle.io/v3/cluster/mutator.go b/pkg/resources/management.cattle.io/v3/cluster/mutator.go index d93fc985f..ad219fae6 100644 --- a/pkg/resources/management.cattle.io/v3/cluster/mutator.go +++ b/pkg/resources/management.cattle.io/v3/cluster/mutator.go @@ -1,6 +1,7 @@ package cluster import ( + "encoding/json" "fmt" "reflect" @@ -59,6 +60,10 @@ func (m *ManagementClusterMutator) Admit(request *admission.Request) (*admission if err != nil { return nil, fmt.Errorf("failed to get old and new clusters from request: %w", err) } + newClusterRaw, err := json.Marshal(newCluster) + if err != nil { + return nil, fmt.Errorf("unable to re-marshal new cluster: %w", err) + } // no need to mutate the local cluster, or imported cluster which represents a KEv2 cluster (GKE/EKS/AKS) or v1 Provisioning Cluster if newCluster.Name == "local" || newCluster.Spec.RancherKubernetesEngineConfig == nil { return admission.ResponseAllowed(), nil @@ -94,7 +99,9 @@ func (m *ManagementClusterMutator) Admit(request *admission.Request) (*admission } response := &admissionv1.AdmissionResponse{} - if err := patch.CreatePatch(request.Object.Raw, newCluster, response); err != nil { + // we use the re-marshalled new cluster to make sure that the patch doesn't drop "unknown" fields which were + // in the json, but not in the cluster struct. This can occur due to out of date RKE versions + if err := patch.CreatePatch(newClusterRaw, newCluster, response); err != nil { return response, fmt.Errorf("failed to create patch: %w", err) } response.Allowed = true diff --git a/pkg/resources/management.cattle.io/v3/cluster/mutator_test.go b/pkg/resources/management.cattle.io/v3/cluster/mutator_test.go new file mode 100644 index 000000000..a5bc3734b --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/cluster/mutator_test.go @@ -0,0 +1,46 @@ +package cluster + +import ( + "encoding/json" + "testing" + + v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + "github.com/rancher/webhook/pkg/admission" + data2 "github.com/rancher/wrangler/v2/pkg/data" + "github.com/stretchr/testify/assert" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestAdmitPreserveUnknownFields(t *testing.T) { + cluster := &v3.Cluster{} + data, err := data2.Convert(cluster) + assert.Nil(t, err) + + data.SetNested("test", "spec", "rancherKubernetesEngineConfig", "network", "aciNetworkProvider", "apicUserKeyTest") + raw, err := json.Marshal(data) + assert.Nil(t, err) + + request := &admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: runtime.RawExtension{ + Raw: raw, + }, + OldObject: runtime.RawExtension{ + Raw: raw, + }, + }, + } + + m := ManagementClusterMutator{} + + request.Operation = admissionv1.Create + response, err := m.Admit(request) + assert.Nil(t, err) + assert.Nil(t, response.Patch) + + request.Operation = admissionv1.Update + response, err = m.Admit(request) + assert.Nil(t, err) + assert.Nil(t, response.Patch) +} diff --git a/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/ClusterRoleTemplateBinding.md b/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/ClusterRoleTemplateBinding.md index 591b70290..039406637 100644 --- a/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/ClusterRoleTemplateBinding.md +++ b/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/ClusterRoleTemplateBinding.md @@ -2,7 +2,9 @@ ### Escalation Prevention -Users can only create/update ClusterRoleTemplateBindings which grant permissions to RoleTemplates with rights less than or equal to those they currently possess. This is to prevent privilege escalation. +Users can only create/update ClusterRoleTemplateBindings which grant permissions to RoleTemplates with rights less than or equal to those they currently possess. This is to prevent privilege escalation. +For external RoleTemplates (RoleTemplates with `external` set to `true`), if the `external-rules` feature flag is enabled and `ExternalRules` is specified in the roleTemplate in `RoleTemplateName`, +`ExternalRules` will be used for authorization. Otherwise (if the feature flag is off or `ExternalRules` are nil), the rules from the backing `ClusterRole` in the local cluster will be used. ### Invalid Fields - Create diff --git a/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/validator_test.go b/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/validator_test.go index 9198c55a3..74d16c489 100644 --- a/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding/validator_test.go @@ -14,7 +14,7 @@ import ( "github.com/rancher/webhook/pkg/auth" "github.com/rancher/webhook/pkg/resolvers" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/clusterroletemplatebinding" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" v1 "k8s.io/api/admission/v1" @@ -34,13 +34,16 @@ const ( type ClusterRoleTemplateBindingSuite struct { suite.Suite - adminRT *apisv3.RoleTemplate - readNodesRT *apisv3.RoleTemplate - lockedRT *apisv3.RoleTemplate - projectRT *apisv3.RoleTemplate - adminCR *rbacv1.ClusterRole - writeNodeCR *rbacv1.ClusterRole - readServiceRole *rbacv1.Role + adminRT *apisv3.RoleTemplate + readNodesRT *apisv3.RoleTemplate + lockedRT *apisv3.RoleTemplate + projectRT *apisv3.RoleTemplate + externalRulesWriteNodesRT *apisv3.RoleTemplate + externalClusterRoleRT *v3.RoleTemplate + adminCR *rbacv1.ClusterRole + writeNodeCR *rbacv1.ClusterRole + readPodsCR *rbacv1.ClusterRole + readServiceRole *rbacv1.Role } func TestClusterRoleTemplateBindings(t *testing.T) { @@ -87,6 +90,27 @@ func (c *ClusterRoleTemplateBindingSuite) SetupSuite() { Administrative: true, Context: "cluster", } + c.externalRulesWriteNodesRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "external-rule-write-nodes", + }, + DisplayName: "External Role", + ExternalRules: []rbacv1.PolicyRule{ruleWriteNodes}, + External: true, + Builtin: true, + Administrative: true, + Context: "cluster", + } + c.externalClusterRoleRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "read-pods-role", + }, + DisplayName: "External Role", + External: true, + Builtin: true, + Administrative: true, + Context: "cluster", + } c.lockedRT = &apisv3.RoleTemplate{ ObjectMeta: metav1.ObjectMeta{ Name: "locked-role", @@ -118,6 +142,10 @@ func (c *ClusterRoleTemplateBindingSuite) SetupSuite() { ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "read-service"}, Rules: []rbacv1.PolicyRule{ruleReadServices}, } + c.readPodsCR = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "read-pods-role"}, + Rules: []rbacv1.PolicyRule{ruleReadPods}, + } } func (c *ClusterRoleTemplateBindingSuite) Test_PrivilegeEscalation() { @@ -151,7 +179,8 @@ func (c *ClusterRoleTemplateBindingSuite) Test_PrivilegeEscalation() { roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().Get(c.adminRT.Name).Return(c.adminRT, nil).AnyTimes() clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, featuresCache) crtbCache := fake.NewMockCacheInterface[*apisv3.ClusterRoleTemplateBinding](ctrl) crtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) crtbCache.EXPECT().GetByIndex(gomock.Any(), resolvers.GetUserKey(crtbUser, newDefaultCRTB().ClusterName)).Return([]*apisv3.ClusterRoleTemplateBinding{ @@ -301,7 +330,8 @@ func (c *ClusterRoleTemplateBindingSuite) Test_UpdateValidation() { roleTemplateCache.EXPECT().Get(c.adminRT.Name).Return(c.adminRT, nil).AnyTimes() roleTemplateCache.EXPECT().List(gomock.Any()).Return([]*apisv3.RoleTemplate{c.adminRT}, nil).AnyTimes() clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + featuresCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, featuresCache) crtbCache := fake.NewMockCacheInterface[*apisv3.ClusterRoleTemplateBinding](ctrl) crtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) crtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() @@ -623,12 +653,19 @@ func (c *ClusterRoleTemplateBindingSuite) Test_UpdateValidation() { } func (c *ClusterRoleTemplateBindingSuite) Test_Create() { + type testState struct { + featureCacheMock *fake.MockNonNamespacedCacheInterface[*apisv3.Feature] + clusterRoleCacheMock *fake.MockNonNamespacedCacheInterface[*rbacv1.ClusterRole] + } + ctrl := gomock.NewController(c.T()) const adminUser = "admin-userid" + const writeNodeUser = "write-node-userid" + const readPodUser = "read-pod-userid" const badRoleTemplateName = "bad-roletemplate" const missingCluster = "missing-cluster" const errorCluster = "error-cluster" const nilCluster = "nil-cluster" - clusterRoles := []*rbacv1.ClusterRole{c.adminCR} + clusterRoles := []*rbacv1.ClusterRole{c.adminCR, c.writeNodeCR, c.readPodsCR} clusterRoleBindings := []*rbacv1.ClusterRoleBinding{ { Subjects: []rbacv1.Subject{ @@ -636,6 +673,18 @@ func (c *ClusterRoleTemplateBindingSuite) Test_Create() { }, RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: c.adminCR.Name}, }, + { + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: writeNodeUser}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: c.writeNodeCR.Name}, + }, + { + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: readPodUser}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: c.readPodsCR.Name}, + }, } validGRB := v3.GlobalRoleBinding{ @@ -654,57 +703,59 @@ func (c *ClusterRoleTemplateBindingSuite) Test_Create() { GlobalRoleName: "some-gr", } - resolver, _ := validation.NewTestRuleResolver(nil, nil, clusterRoles, clusterRoleBindings) - - ctrl := gomock.NewController(c.T()) - roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](ctrl) - roleTemplateCache.EXPECT().Get(c.adminRT.Name).Return(c.adminRT, nil).AnyTimes() - roleTemplateCache.EXPECT().Get(c.lockedRT.Name).Return(c.lockedRT, nil).AnyTimes() - roleTemplateCache.EXPECT().Get(c.projectRT.Name).Return(c.projectRT, nil).AnyTimes() - expectedError := apierrors.NewNotFound(schema.GroupResource{}, "") - roleTemplateCache.EXPECT().Get(badRoleTemplateName).Return(nil, expectedError).AnyTimes() - roleTemplateCache.EXPECT().Get("").Return(nil, expectedError).AnyTimes() - clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) - crtbCache := fake.NewMockCacheInterface[*apisv3.ClusterRoleTemplateBinding](ctrl) - crtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) - crtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() - grbCache := fake.NewMockNonNamespacedCacheInterface[*v3.GlobalRoleBinding](ctrl) - notFoundError := apierrors.NewNotFound(schema.GroupResource{ - Group: "management.cattle.io", - Resource: "globalrolebindings", - }, "not-found") - grbCache.EXPECT().Get(validGRB.Name).Return(&validGRB, nil).AnyTimes() - grbCache.EXPECT().Get(deletingGRB.Name).Return(&deletingGRB, nil).AnyTimes() - grbCache.EXPECT().Get("error").Return(nil, fmt.Errorf("server not available")).AnyTimes() - grbCache.EXPECT().Get("not-found").Return(nil, notFoundError).AnyTimes() - grbCache.EXPECT().Get("nil-grb").Return(nil, nil).AnyTimes() + validatorWithMocks := func(state testState) *clusterroletemplatebinding.Validator { + resolver, _ := validation.NewTestRuleResolver(nil, nil, clusterRoles, clusterRoleBindings) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(c.adminRT.Name).Return(c.adminRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(c.externalRulesWriteNodesRT.Name).Return(c.externalRulesWriteNodesRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(c.externalClusterRoleRT.Name).Return(c.externalClusterRoleRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(c.lockedRT.Name).Return(c.lockedRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(c.projectRT.Name).Return(c.projectRT, nil).AnyTimes() + expectedError := apierrors.NewNotFound(schema.GroupResource{}, "") + roleTemplateCache.EXPECT().Get(badRoleTemplateName).Return(nil, expectedError).AnyTimes() + roleTemplateCache.EXPECT().Get("").Return(nil, expectedError).AnyTimes() + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, state.clusterRoleCacheMock, state.featureCacheMock) + crtbCache := fake.NewMockCacheInterface[*apisv3.ClusterRoleTemplateBinding](ctrl) + crtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) + crtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + grbCache := fake.NewMockNonNamespacedCacheInterface[*v3.GlobalRoleBinding](ctrl) + notFoundError := apierrors.NewNotFound(schema.GroupResource{ + Group: "management.cattle.io", + Resource: "globalrolebindings", + }, "not-found") + grbCache.EXPECT().Get(validGRB.Name).Return(&validGRB, nil).AnyTimes() + grbCache.EXPECT().Get(deletingGRB.Name).Return(&deletingGRB, nil).AnyTimes() + grbCache.EXPECT().Get("error").Return(nil, fmt.Errorf("server not available")).AnyTimes() + grbCache.EXPECT().Get("not-found").Return(nil, notFoundError).AnyTimes() + grbCache.EXPECT().Get("nil-grb").Return(nil, nil).AnyTimes() - clusterCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Cluster](ctrl) - clusterCache.EXPECT().Get(defaultClusterID).Return(&apisv3.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: defaultClusterID, - }, - }, nil).AnyTimes() - clusterCache.EXPECT().Get(errorCluster).Return(nil, fmt.Errorf("server not available")).AnyTimes() - clusterCache.EXPECT().Get(missingCluster).Return(nil, apierrors.NewNotFound(schema.GroupResource{ - Group: "management.cattle.io", - Resource: "clusters", - }, missingCluster)).AnyTimes() - clusterCache.EXPECT().Get(nilCluster).Return(nil, nil).AnyTimes() + clusterCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Cluster](ctrl) + clusterCache.EXPECT().Get(defaultClusterID).Return(&apisv3.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultClusterID, + }, + }, nil).AnyTimes() + clusterCache.EXPECT().Get(errorCluster).Return(nil, fmt.Errorf("server not available")).AnyTimes() + clusterCache.EXPECT().Get(missingCluster).Return(nil, apierrors.NewNotFound(schema.GroupResource{ + Group: "management.cattle.io", + Resource: "clusters", + }, missingCluster)).AnyTimes() + clusterCache.EXPECT().Get(nilCluster).Return(nil, nil).AnyTimes() - crtbResolver := resolvers.NewCRTBRuleResolver(crtbCache, roleResolver) - validator := clusterroletemplatebinding.NewValidator(crtbResolver, resolver, roleResolver, grbCache, clusterCache) + crtbResolver := resolvers.NewCRTBRuleResolver(crtbCache, roleResolver) + return clusterroletemplatebinding.NewValidator(crtbResolver, resolver, roleResolver, grbCache, clusterCache) + } type args struct { oldCRTB func() *apisv3.ClusterRoleTemplateBinding newCRTB func() *apisv3.ClusterRoleTemplateBinding username string } tests := []struct { - name string - args args - wantErr bool - allowed bool + name string + args args + wantErr bool + allowed bool + stateSetup func(state testState) }{ { name: "base test valid CRTB", @@ -970,6 +1021,104 @@ func (c *ClusterRoleTemplateBindingSuite) Test_Create() { }, allowed: false, }, + { + name: "external RT with externalRules valid CRTB creation when feature flag is on", + args: args{ + username: writeNodeUser, + oldCRTB: func() *apisv3.ClusterRoleTemplateBinding { + return nil + }, + newCRTB: func() *apisv3.ClusterRoleTemplateBinding { + baseCRTB := newDefaultCRTB() + baseCRTB.RoleTemplateName = "external-rule-write-nodes" + + return baseCRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: true, + }, + { + name: "external RT with externalRules rejected when feature flag is on and there are not enough permissions", + args: args{ + username: readPodUser, + oldCRTB: func() *apisv3.ClusterRoleTemplateBinding { + return nil + }, + newCRTB: func() *apisv3.ClusterRoleTemplateBinding { + baseCRTB := newDefaultCRTB() + baseCRTB.RoleTemplateName = "external-rule-write-nodes" + + return baseCRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: false, + }, + { + name: "external RT valid CRTB creation when feature flag is off", + args: args{ + username: adminUser, + oldCRTB: func() *apisv3.ClusterRoleTemplateBinding { + return nil + }, + newCRTB: func() *apisv3.ClusterRoleTemplateBinding { + baseCRTB := newDefaultCRTB() + baseCRTB.RoleTemplateName = "read-pods-role" + + return baseCRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + state.clusterRoleCacheMock.EXPECT().Get(c.readPodsCR.Name).Return(c.readPodsCR, nil) + }, + allowed: true, + }, + { + name: "external RT CRTB is rejected when there are not enough permissions when feature flag is off", + args: args{ + username: writeNodeUser, + oldCRTB: func() *apisv3.ClusterRoleTemplateBinding { + return nil + }, + newCRTB: func() *apisv3.ClusterRoleTemplateBinding { + basePRTB := newDefaultCRTB() + basePRTB.RoleTemplateName = "read-pods-role" + + return basePRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + state.clusterRoleCacheMock.EXPECT().Get(c.readPodsCR.Name).Return(c.readPodsCR, nil) + }, + allowed: false, + }, } for i := range tests { @@ -977,6 +1126,16 @@ func (c *ClusterRoleTemplateBindingSuite) Test_Create() { c.Run(test.name, func() { c.T().Parallel() req := createCRTBRequest(c.T(), test.args.oldCRTB(), test.args.newCRTB(), test.args.username) + featureCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + state := testState{ + clusterRoleCacheMock: clusterRoleCache, + featureCacheMock: featureCache, + } + if test.stateSetup != nil { + test.stateSetup(state) + } + validator := validatorWithMocks(state) admitters := validator.Admitters() assert.Len(c.T(), admitters, 1) resp, err := admitters[0].Admit(req) diff --git a/pkg/resources/management.cattle.io/v3/feature/Feature.md b/pkg/resources/management.cattle.io/v3/feature/Feature.md index 076e155b0..702e69df4 100644 --- a/pkg/resources/management.cattle.io/v3/feature/Feature.md +++ b/pkg/resources/management.cattle.io/v3/feature/Feature.md @@ -3,3 +3,4 @@ ### On update The desired value must not change on new spec unless it's equal to the `lockedValue` or `lockedValue` is nil. +Due to the security impact of the `external-rules` feature flag, only users with admin permissions (`*` verbs on `*` resources in `*` APIGroups in all namespaces) can enable or disable this feature flag. diff --git a/pkg/resources/management.cattle.io/v3/feature/validator.go b/pkg/resources/management.cattle.io/v3/feature/validator.go index d455cb83d..b25a88fdd 100644 --- a/pkg/resources/management.cattle.io/v3/feature/validator.go +++ b/pkg/resources/management.cattle.io/v3/feature/validator.go @@ -6,11 +6,14 @@ import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/webhook/pkg/auth" objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/registry/rbac/validation" "k8s.io/utils/trace" ) @@ -26,9 +29,11 @@ type Validator struct { } // NewValidator returns a new validator for features. -func NewValidator() *Validator { +func NewValidator(ruleResolver validation.AuthorizationRuleResolver) *Validator { return &Validator{ - admitter: admitter{}, + admitter: admitter{ + ruleResolver: ruleResolver, + }, } } @@ -54,7 +59,9 @@ func (v *Validator) Admitters() []admission.Admitter { return []admission.Admitter{&v.admitter} } -type admitter struct{} +type admitter struct { + ruleResolver validation.AuthorizationRuleResolver +} // Admit handles the webhook admission request sent to this webhook. func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { @@ -78,11 +85,62 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp }, nil } + if newFeature.Name == auth.ExternalRulesFeature { + var rules []rbacv1.PolicyRule + oldFeatureValue := getEffectiveValue(oldFeature) + newFeatureValue := getEffectiveValue(newFeature) + + // if the feature value isn't changing, allow it + if oldFeatureValue == newFeatureValue { + return &admissionv1.AdmissionResponse{ + Allowed: true, + }, nil + } + + if !oldFeatureValue && newFeatureValue { + // enabling the feature requires the "security-enable" verb + rules = []rbacv1.PolicyRule{ + { + Verbs: []string{"security-enable"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + ResourceNames: []string{"external-rules"}, + }, + } + } else { + // disabling the feature requires administrator permissions + rules = []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + } + } + err := auth.ConfirmNoEscalation(request, rules, "", a.ruleResolver) + if err != nil { + return admission.ResponseFailedEscalation(fmt.Sprintf("updating the 'external-rules' feature requires admin permissions: %s ", err.Error())), nil + } + } + return &admissionv1.AdmissionResponse{ Allowed: true, }, nil } +// getEffectiveValue considers a feature's default, value, and locked value to determine +// its effective value. +func getEffectiveValue(obj *v3.Feature) bool { + val := obj.Status.Default + if obj.Spec.Value != nil { + val = *obj.Spec.Value + } + if obj.Status.LockedValue != nil { + val = *obj.Status.LockedValue + } + return val +} + // isUpdateAllowed checks that the new value does not change on spec unless it's equal to the lockedValue, // or lockedValue is nil. func isUpdateAllowed(old, new *v3.Feature) bool { diff --git a/pkg/resources/management.cattle.io/v3/feature/validator_test.go b/pkg/resources/management.cattle.io/v3/feature/validator_test.go index 5d01fbcbe..71b66a963 100644 --- a/pkg/resources/management.cattle.io/v3/feature/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/feature/validator_test.go @@ -4,12 +4,16 @@ import ( "encoding/json" "testing" + "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/webhook/pkg/auth" + "github.com/rancher/webhook/pkg/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" authenicationv1 "k8s.io/api/authentication/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -20,6 +24,11 @@ var ( ) func TestFeatureValueValid(t *testing.T) { + type testState struct { + authorizationRuleResolverMock *mocks.MockAuthorizationRuleResolver + } + ctrl := gomock.NewController(t) + t.Parallel() tests := []struct { name string @@ -27,6 +36,7 @@ func TestFeatureValueValid(t *testing.T) { oldFeature v3.Feature wantError bool wantAdmit bool + stateSetup func(state testState) }{ { name: "new feature locked with spec value changed", @@ -95,13 +105,319 @@ func TestFeatureValueValid(t *testing.T) { }, wantAdmit: true, }, + { + name: "external rules feature can be changed by admins", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(false), + }, + }, + stateSetup: func(state testState) { + adminRules := []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + } + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return(adminRules, nil) + }, + wantAdmit: true, + }, + { + name: "external rules feature can't be enabled by users with update permissions", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(false), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + stateSetup: func(state testState) { + adminRules := []rbacv1.PolicyRule{ + { + Verbs: []string{"update"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + }, + } + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return(adminRules, nil) + }, + wantAdmit: false, + }, + { + name: "external rules feature can't be disabled by users with update permissions", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(false), + }, + }, + stateSetup: func(state testState) { + adminRules := []rbacv1.PolicyRule{ + { + Verbs: []string{"update"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + }, + } + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return(adminRules, nil) + }, + wantAdmit: false, + }, + { + name: "external rules feature can't be set to nil by users with update permissions", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: nil, + }, + }, + stateSetup: func(state testState) { + adminRules := []rbacv1.PolicyRule{ + { + Verbs: []string{"update"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + }, + } + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return(adminRules, nil) + }, + wantAdmit: false, + }, + { + name: "external rules feature can be enabled for users with security-enable (RA)", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(false), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + stateSetup: func(state testState) { + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return([]rbacv1.PolicyRule{ + { + Verbs: []string{"security-enable"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + ResourceNames: []string{"external-rules"}, + }, + }, nil) + }, + wantAdmit: true, + }, + { + name: "external rules feature can't be disabled for users with security-enable (RA)", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(false), + }, + }, + stateSetup: func(state testState) { + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return([]rbacv1.PolicyRule{ + { + Verbs: []string{"security-enable", "update"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + ResourceNames: []string{"external-rules"}, + }, + }, nil) + }, + wantAdmit: false, + }, + { + name: "external rules feature can be enabled with default value for users with security-enable (RA)", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: nil, + }, + Status: v3.FeatureStatus{ + Default: false, + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + stateSetup: func(state testState) { + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return([]rbacv1.PolicyRule{ + { + Verbs: []string{"security-enable"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + ResourceNames: []string{"external-rules"}, + }, + }, nil) + }, + wantAdmit: true, + }, + { + name: "external rules feature can't be disabled for users with security-enable (RA)", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: nil, + }, + Status: v3.FeatureStatus{ + Default: true, + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(false), + }, + }, + stateSetup: func(state testState) { + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return([]rbacv1.PolicyRule{ + { + Verbs: []string{"security-enable", "update"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + ResourceNames: []string{"external-rules"}, + }, + }, nil) + }, + wantAdmit: false, + }, + { + name: "external rules feature can't be set to nil for users with security-enable (RA)", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: nil, + }, + }, + stateSetup: func(state testState) { + state.authorizationRuleResolverMock.EXPECT().RulesFor(gomock.Any(), gomock.Any()).Return([]rbacv1.PolicyRule{ + { + Verbs: []string{"security-enable", "update"}, + APIGroups: []string{"management.cattle.io"}, + Resources: []string{"features"}, + ResourceNames: []string{"external-rules"}, + }, + }, nil) + }, + wantAdmit: false, + }, + { + name: "external rules feature can be modified if the value doesn't change", + oldFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + newFeature: v3.Feature{ + ObjectMeta: metav1.ObjectMeta{ + Name: auth.ExternalRulesFeature, + Annotations: map[string]string{ + "test-annotation": "test-value", + }, + }, + Spec: v3.FeatureSpec{ + Value: admission.Ptr(true), + }, + }, + wantAdmit: true, + }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - admitters := NewValidator().Admitters() + authorizationRuleResolverMock := mocks.NewMockAuthorizationRuleResolver(ctrl) + state := testState{ + authorizationRuleResolverMock: authorizationRuleResolverMock, + } + if test.stateSetup != nil { + test.stateSetup(state) + } + admitters := NewValidator(state.authorizationRuleResolverMock).Admitters() assert.Len(t, admitters, 1) req := admission.Request{ @@ -137,7 +453,7 @@ func TestFeatureValueValid(t *testing.T) { func TestRejectsBadRequest(t *testing.T) { t.Parallel() - admitters := NewValidator().Admitters() + admitters := NewValidator(nil).Admitters() assert.Len(t, admitters, 1) req := admission.Request{ diff --git a/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator.go b/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator.go index f0dfced31..ff9292473 100644 --- a/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator.go +++ b/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator.go @@ -9,8 +9,8 @@ import ( "github.com/rancher/webhook/pkg/auth" "github.com/rancher/webhook/pkg/clients" objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" - corev1controller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" - rbacvacontroller "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1" + corev1controller "github.com/rancher/wrangler/v2/pkg/generated/controllers/core/v1" + rbacvacontroller "github.com/rancher/wrangler/v2/pkg/generated/controllers/rbac/v1" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" v1 "k8s.io/api/core/v1" diff --git a/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator_test.go b/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator_test.go index 2de455b9e..f10c7c0ff 100644 --- a/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/fleetworkspace/mutator_test.go @@ -9,7 +9,7 @@ import ( "github.com/rancher/rancher/pkg/apis/management.cattle.io" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" diff --git a/pkg/resources/management.cattle.io/v3/globalrole/GlobalRole.md b/pkg/resources/management.cattle.io/v3/globalrole/GlobalRole.md index da579cacd..a8bb3c148 100644 --- a/pkg/resources/management.cattle.io/v3/globalrole/GlobalRole.md +++ b/pkg/resources/management.cattle.io/v3/globalrole/GlobalRole.md @@ -5,9 +5,13 @@ Note: all checks are bypassed if the GlobalRole is being deleted, or if only the ### Invalid Fields - Create and Update On create or update, the following checks take place: -- The webhook checks that each rule has at least one verb. +- The webhook validates each rule using the standard Kubernetes RBAC checks (see next section). - Each new RoleTemplate referred to in `inheritedClusterRoles` must have a context of `cluster` and not be `locked`. This validation is skipped for RoleTemplates in `inheritedClusterRoles` for the prior version of this object. +### Rules Without Verbs, Resources, API groups + +Rules without verbs, resources, or apigroups are not permitted. The `rules` included in a GlobalRole are of the same type as the rules used by standard Kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one. + ### Escalation Prevention Users can only change GlobalRoles with rights less than or equal to those they currently possess. This is to prevent privilege escalation. This includes the rules in the RoleTemplates referred to in `inheritedClusterRoles`. diff --git a/pkg/resources/management.cattle.io/v3/globalrole/setup_test.go b/pkg/resources/management.cattle.io/v3/globalrole/setup_test.go index a07fa449c..2dbe46229 100644 --- a/pkg/resources/management.cattle.io/v3/globalrole/setup_test.go +++ b/pkg/resources/management.cattle.io/v3/globalrole/setup_test.go @@ -11,7 +11,7 @@ import ( "github.com/rancher/webhook/pkg/admission" "github.com/rancher/webhook/pkg/auth" "github.com/rancher/webhook/pkg/resolvers" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" v1authentication "k8s.io/api/authentication/v1" @@ -279,6 +279,6 @@ func newDefaultState(t *testing.T) testState { } func (m *testState) createBaseGRBResolver() *resolvers.GRBClusterRuleResolver { - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(m.rtCacheMock, nil), m.grCacheMock) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(m.rtCacheMock, nil, nil), m.grCacheMock) return resolvers.NewGRBClusterRuleResolver(m.grbCacheMock, grResolver) } diff --git a/pkg/resources/management.cattle.io/v3/globalrole/validator.go b/pkg/resources/management.cattle.io/v3/globalrole/validator.go index d6f30700e..70a44c22d 100644 --- a/pkg/resources/management.cattle.io/v3/globalrole/validator.go +++ b/pkg/resources/management.cattle.io/v3/globalrole/validator.go @@ -113,7 +113,7 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp return nil, fmt.Errorf("%s operation %v: %w", gvr.Resource, request.Operation, admission.ErrUnsupportedOperation) } - err = a.validateFields(oldGR, newGR, fldPath) + err = a.validateInheritedClusterRoles(oldGR, newGR, fldPath.Child("inheritedClusterRoles")) if err != nil { if errors.As(err, admission.Ptr(new(field.Error))) { return admission.ResponseBadRequest(err.Error()), nil @@ -121,6 +121,13 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp return nil, err } + // Validate the global rules of the new GR + globalRules := a.grbResolver.GlobalRoleResolver.GlobalRulesFromRole(newGR) + returnError := common.ValidateRules(globalRules, false, fldPath.Child("rules")) + if returnError != nil { + return admission.ResponseBadRequest(returnError.Error()), nil + } + // check for escalation separately between cluster permissions and global permissions to prevent crossover clusterRules, err := a.grbResolver.GlobalRoleResolver.ClusterRulesFromRole(newGR) if err != nil { @@ -182,14 +189,6 @@ func validateCreateFields(oldRole *v3.GlobalRole, fldPath *field.Path) *field.Er return nil } -// validateFields validates fields validates that the defined rules all have verbs and check the inheritedClusterRoles. -func (a *admitter) validateFields(oldRole, newRole *v3.GlobalRole, fldPath *field.Path) error { - if err := common.CheckForVerbs(newRole.Rules); err != nil { - return field.Required(fldPath.Child("rules"), err.Error()) - } - return a.validateInheritedClusterRoles(oldRole, newRole, fldPath.Child("inheritedClusterRoles")) -} - // validateInheritedClusterRoles validates that new RoleTemplates specified by InheritedClusterRoles have a context of // cluster and are not locked. Does NOT check for user privilege escalation. May return a field.Error indicating the // source of the error. diff --git a/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go b/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go index 58b4081a4..dd51fd4ec 100644 --- a/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go @@ -8,7 +8,7 @@ import ( "github.com/golang/mock/gomock" apisv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/globalrolebinding" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/resources/management.cattle.io/v3/globalrolebinding/setup_test.go b/pkg/resources/management.cattle.io/v3/globalrolebinding/setup_test.go index 11970c8cf..69322dd48 100644 --- a/pkg/resources/management.cattle.io/v3/globalrolebinding/setup_test.go +++ b/pkg/resources/management.cattle.io/v3/globalrolebinding/setup_test.go @@ -10,7 +10,7 @@ import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" "github.com/rancher/webhook/pkg/resolvers" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" v1 "k8s.io/api/admission/v1" v1authentication "k8s.io/api/authentication/v1" diff --git a/pkg/resources/management.cattle.io/v3/globalrolebinding/validator_test.go b/pkg/resources/management.cattle.io/v3/globalrolebinding/validator_test.go index c2259cedc..e09420270 100644 --- a/pkg/resources/management.cattle.io/v3/globalrolebinding/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/globalrolebinding/validator_test.go @@ -658,7 +658,7 @@ func TestAdmit(t *testing.T) { if test.args.stateSetup != nil { test.args.stateSetup(state) } - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), state.grCacheMock) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil, nil), state.grCacheMock) grbResolver := resolvers.NewGRBClusterRuleResolver(state.grbCacheMock, grResolver) admitters := globalrolebinding.NewValidator(state.resolver, grbResolver, state.sarMock).Admitters() require.Len(t, admitters, 1) @@ -679,7 +679,7 @@ func TestAdmit(t *testing.T) { func Test_UnexpectedErrors(t *testing.T) { t.Parallel() state := newDefaultState(t) - grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil), state.grCacheMock) + grResolver := auth.NewGlobalRoleResolver(auth.NewRoleTemplateResolver(state.rtCacheMock, nil, nil), state.grCacheMock) grbResolver := resolvers.NewGRBClusterRuleResolver(state.grbCacheMock, grResolver) validator := globalrolebinding.NewValidator(state.resolver, grbResolver, state.sarMock) admitters := validator.Admitters() diff --git a/pkg/resources/management.cattle.io/v3/nodedriver/validator_test.go b/pkg/resources/management.cattle.io/v3/nodedriver/validator_test.go index 6329dffdd..547abe34c 100644 --- a/pkg/resources/management.cattle.io/v3/nodedriver/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/nodedriver/validator_test.go @@ -8,7 +8,7 @@ import ( "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/suite" admissionv1 "k8s.io/api/admission/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/resources/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate/validator_test.go b/pkg/resources/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate/validator_test.go index ea557feda..ccf7f258f 100644 --- a/pkg/resources/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/podsecurityadmissionconfigurationtemplate/validator_test.go @@ -9,7 +9,7 @@ import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" provv1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/pod-security-admission/api" diff --git a/pkg/resources/management.cattle.io/v3/project/mutator_test.go b/pkg/resources/management.cattle.io/v3/project/mutator_test.go index e8be26e59..6f4a19bc8 100644 --- a/pkg/resources/management.cattle.io/v3/project/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/project/mutator_test.go @@ -7,7 +7,7 @@ import ( "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/resources/management.cattle.io/v3/project/quota_validate.go b/pkg/resources/management.cattle.io/v3/project/quota_validate.go index 4de11ef68..fdb914978 100644 --- a/pkg/resources/management.cattle.io/v3/project/quota_validate.go +++ b/pkg/resources/management.cattle.io/v3/project/quota_validate.go @@ -2,7 +2,7 @@ package project import ( mgmtv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/data/convert" + "github.com/rancher/wrangler/v2/pkg/data/convert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" quotav1 "k8s.io/apiserver/pkg/quota/v1" diff --git a/pkg/resources/management.cattle.io/v3/project/validator.go b/pkg/resources/management.cattle.io/v3/project/validator.go index a1d4d53fb..e0ce19ec4 100644 --- a/pkg/resources/management.cattle.io/v3/project/validator.go +++ b/pkg/resources/management.cattle.io/v3/project/validator.go @@ -9,7 +9,7 @@ import ( "github.com/rancher/webhook/pkg/admission" controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" - "github.com/rancher/wrangler/pkg/data/convert" + "github.com/rancher/wrangler/v2/pkg/data/convert" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" v1 "k8s.io/api/core/v1" diff --git a/pkg/resources/management.cattle.io/v3/project/validator_test.go b/pkg/resources/management.cattle.io/v3/project/validator_test.go index 66995b567..daca5cdb6 100644 --- a/pkg/resources/management.cattle.io/v3/project/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/project/validator_test.go @@ -9,7 +9,7 @@ import ( "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" v1 "k8s.io/api/admission/v1" diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md index b2b4b6156..dc57d547e 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md @@ -4,6 +4,8 @@ Users can only create/update ProjectRoleTemplateBindings with rights less than or equal to those they currently possess. This is to prevent privilege escalation. +For external RoleTemplates (RoleTemplates with `external` set to `true`), if the `external-rules` feature flag is enabled and `ExternalRules` is specified in the roleTemplate in `RoleTemplateName`, +`ExternalRules` will be used for authorization. Otherwise, if `ExternalRules` are nil when the feature flag is on, the rules from the backing `ClusterRole` in the local cluster will be used. ### Invalid Fields - Create diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go index a1bbe3110..bcf08d0d6 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go @@ -13,7 +13,7 @@ import ( "github.com/rancher/webhook/pkg/auth" "github.com/rancher/webhook/pkg/resolvers" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/projectroletemplatebinding" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" v1 "k8s.io/api/admission/v1" @@ -35,13 +35,16 @@ const ( type ProjectRoleTemplateBindingSuite struct { suite.Suite - adminRT *apisv3.RoleTemplate - readNodesRT *apisv3.RoleTemplate - lockedRT *apisv3.RoleTemplate - clusterContextRT *apisv3.RoleTemplate - adminCR *rbacv1.ClusterRole - writeNodeCR *rbacv1.ClusterRole - readServiceRole *rbacv1.Role + adminRT *apisv3.RoleTemplate + readNodesRT *apisv3.RoleTemplate + lockedRT *apisv3.RoleTemplate + clusterContextRT *apisv3.RoleTemplate + externalRulesWriteNodesRT *apisv3.RoleTemplate + externalClusterRoleRT *apisv3.RoleTemplate + adminCR *rbacv1.ClusterRole + writeNodeCR *rbacv1.ClusterRole + readPodsCR *rbacv1.ClusterRole + readServiceRole *rbacv1.Role } func TestProjectRoleTemplateBindings(t *testing.T) { @@ -88,6 +91,27 @@ func (p *ProjectRoleTemplateBindingSuite) SetupSuite() { Administrative: true, Context: "project", } + p.externalRulesWriteNodesRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "external-rule-write-nodes", + }, + DisplayName: "External Role", + ExternalRules: []rbacv1.PolicyRule{ruleWriteNodes}, + External: true, + Builtin: true, + Administrative: true, + Context: "project", + } + p.externalClusterRoleRT = &apisv3.RoleTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "read-pods-role", + }, + DisplayName: "External Role", + External: true, + Builtin: true, + Administrative: true, + Context: "project", + } p.lockedRT = &apisv3.RoleTemplate{ ObjectMeta: metav1.ObjectMeta{ Name: "locked-role", @@ -119,6 +143,10 @@ func (p *ProjectRoleTemplateBindingSuite) SetupSuite() { ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "read-service"}, Rules: []rbacv1.PolicyRule{ruleReadServices}, } + p.readPodsCR = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "read-pods-role"}, + Rules: []rbacv1.PolicyRule{ruleReadPods}, + } } func (p *ProjectRoleTemplateBindingSuite) TestPrivilegeEscalation() { @@ -153,7 +181,7 @@ func (p *ProjectRoleTemplateBindingSuite) TestPrivilegeEscalation() { roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().Get(p.adminRT.Name).Return(p.adminRT, nil).AnyTimes() clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, nil) prtbCache := fake.NewMockCacheInterface[*apisv3.ProjectRoleTemplateBinding](ctrl) prtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) prtbCache.EXPECT().GetByIndex(gomock.Any(), resolvers.GetUserKey(prtbUser, projectID)).Return([]*apisv3.ProjectRoleTemplateBinding{{ @@ -340,7 +368,7 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnUpdate() { roleTemplateCache.EXPECT().Get(p.adminRT.Name).Return(p.adminRT, nil).AnyTimes() roleTemplateCache.EXPECT().List(gomock.Any()).Return([]*apisv3.RoleTemplate{p.adminRT}, nil).AnyTimes() clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, nil) prtbCache := fake.NewMockCacheInterface[*apisv3.ProjectRoleTemplateBinding](ctrl) prtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) prtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() @@ -689,7 +717,14 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnUpdate() { } func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { + type testState struct { + featureCacheMock *fake.MockNonNamespacedCacheInterface[*apisv3.Feature] + clusterRoleCacheMock *fake.MockNonNamespacedCacheInterface[*rbacv1.ClusterRole] + } + ctrl := gomock.NewController(p.T()) const adminUser = "admin-userid" + const writeNodeUser = "write-node-userid" + const readPodUser = "read-pod-userid" const badRoleTemplateName = "bad-roletemplate" const missingCluster = "missing-cluster" const nilCluster = "nil-cluster" @@ -698,7 +733,7 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { const nilProject = "nil-project" const errProject = "error-project" const badSpecProject = "bad-spec" - clusterRoles := []*rbacv1.ClusterRole{p.adminCR} + clusterRoles := []*rbacv1.ClusterRole{p.adminCR, p.writeNodeCR, p.readPodsCR} clusterRoleBindings := []*rbacv1.ClusterRoleBinding{ { Subjects: []rbacv1.Subject{ @@ -706,78 +741,94 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { }, RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: p.adminCR.Name}, }, + { + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: writeNodeUser}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: p.writeNodeCR.Name}, + }, + { + Subjects: []rbacv1.Subject{ + {Kind: rbacv1.UserKind, Name: readPodUser}, + }, + RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: p.readPodsCR.Name}, + }, } - resolver, _ := validation.NewTestRuleResolver(nil, nil, clusterRoles, clusterRoleBindings) - ctrl := gomock.NewController(p.T()) - roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) - roleTemplateCache.EXPECT().Get(p.adminRT.Name).Return(p.adminRT, nil).AnyTimes() - roleTemplateCache.EXPECT().Get(p.lockedRT.Name).Return(p.lockedRT, nil).AnyTimes() - roleTemplateCache.EXPECT().Get(p.clusterContextRT.Name).Return(p.clusterContextRT, nil).AnyTimes() - roleTemplateCache.EXPECT().Get(badRoleTemplateName).Return(nil, errExpected).AnyTimes() - roleTemplateCache.EXPECT().Get("").Return(nil, errExpected).AnyTimes() - clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) - prtbCache := fake.NewMockCacheInterface[*apisv3.ProjectRoleTemplateBinding](ctrl) - prtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) - prtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() - crtbCache := fake.NewMockCacheInterface[*apisv3.ClusterRoleTemplateBinding](ctrl) - crtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) - crtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() - crtbResolver := resolvers.NewCRTBRuleResolver(crtbCache, roleResolver) - prtbResolver := resolvers.NewPRTBRuleResolver(prtbCache, roleResolver) + validatorWithMocks := func(state testState) *projectroletemplatebinding.Validator { + resolver, _ := validation.NewTestRuleResolver(nil, nil, clusterRoles, clusterRoleBindings) + roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.RoleTemplate](ctrl) + roleTemplateCache.EXPECT().Get(p.adminRT.Name).Return(p.adminRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(p.externalRulesWriteNodesRT.Name).Return(p.externalRulesWriteNodesRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(p.externalClusterRoleRT.Name).Return(p.externalClusterRoleRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(p.lockedRT.Name).Return(p.lockedRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(p.clusterContextRT.Name).Return(p.clusterContextRT, nil).AnyTimes() + roleTemplateCache.EXPECT().Get(badRoleTemplateName).Return(nil, errExpected).AnyTimes() + roleTemplateCache.EXPECT().Get("").Return(nil, errExpected).AnyTimes() + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, state.clusterRoleCacheMock, state.featureCacheMock) + prtbCache := fake.NewMockCacheInterface[*apisv3.ProjectRoleTemplateBinding](ctrl) + prtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) + prtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + crtbCache := fake.NewMockCacheInterface[*apisv3.ClusterRoleTemplateBinding](ctrl) + crtbCache.EXPECT().AddIndexer(gomock.Any(), gomock.Any()) + crtbCache.EXPECT().GetByIndex(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + crtbResolver := resolvers.NewCRTBRuleResolver(crtbCache, roleResolver) + prtbResolver := resolvers.NewPRTBRuleResolver(prtbCache, roleResolver) - clusterCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Cluster](ctrl) - clusterCache.EXPECT().Get(clusterID).Return(&apisv3.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterID, - }, - }, nil).AnyTimes() - clusterCache.EXPECT().Get(nilCluster).Return(nil, nil).AnyTimes() - clusterCache.EXPECT().Get(errCluster).Return(nil, fmt.Errorf("server not available")).AnyTimes() - clusterCache.EXPECT().Get(missingCluster).Return(nil, apierrors.NewNotFound(schema.GroupResource{ - Group: "management.cattle.io", - Resource: "clusters", - }, missingCluster)).AnyTimes() + clusterCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Cluster](ctrl) + clusterCache.EXPECT().Get(clusterID).Return(&apisv3.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterID, + }, + }, nil).AnyTimes() + clusterCache.EXPECT().Get(nilCluster).Return(nil, nil).AnyTimes() + clusterCache.EXPECT().Get(errCluster).Return(nil, fmt.Errorf("server not available")).AnyTimes() + clusterCache.EXPECT().Get(missingCluster).Return(nil, apierrors.NewNotFound(schema.GroupResource{ + Group: "management.cattle.io", + Resource: "clusters", + }, missingCluster)).AnyTimes() - projectCache := fake.NewMockCacheInterface[*apisv3.Project](ctrl) - projectCache.EXPECT().Get(clusterID, projectID).Return(&apisv3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: clusterID, - Name: projectID, - }, - Spec: apisv3.ProjectSpec{ - ClusterName: clusterID, - }, - }, nil).AnyTimes() - projectCache.EXPECT().Get(clusterID, nilProject).Return(nil, nil).AnyTimes() - projectCache.EXPECT().Get(clusterID, errProject).Return(nil, fmt.Errorf("server not available")).AnyTimes() - projectCache.EXPECT().Get(clusterID, missingProject).Return(nil, apierrors.NewNotFound(schema.GroupResource{ - Group: "management.cattle.io", - Resource: "projects", - }, missingProject)).AnyTimes() + projectCache := fake.NewMockCacheInterface[*apisv3.Project](ctrl) + projectCache.EXPECT().Get(clusterID, projectID).Return(&apisv3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: clusterID, + Name: projectID, + }, + Spec: apisv3.ProjectSpec{ + ClusterName: clusterID, + }, + }, nil).AnyTimes() + projectCache.EXPECT().Get(clusterID, nilProject).Return(nil, nil).AnyTimes() + projectCache.EXPECT().Get(clusterID, errProject).Return(nil, fmt.Errorf("server not available")).AnyTimes() + projectCache.EXPECT().Get(clusterID, missingProject).Return(nil, apierrors.NewNotFound(schema.GroupResource{ + Group: "management.cattle.io", + Resource: "projects", + }, missingProject)).AnyTimes() - projectCache.EXPECT().Get(clusterID, badSpecProject).Return(&apisv3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: clusterID, - Name: projectID, - }, - Spec: apisv3.ProjectSpec{ - ClusterName: missingCluster, - }, - }, nil).AnyTimes() + projectCache.EXPECT().Get(clusterID, badSpecProject).Return(&apisv3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: clusterID, + Name: projectID, + }, + Spec: apisv3.ProjectSpec{ + ClusterName: missingCluster, + }, + }, nil).AnyTimes() + + return projectroletemplatebinding.NewValidator(prtbResolver, crtbResolver, resolver, roleResolver, clusterCache, projectCache) + } - validator := projectroletemplatebinding.NewValidator(prtbResolver, crtbResolver, resolver, roleResolver, clusterCache, projectCache) type args struct { oldPRTB func() *apisv3.ProjectRoleTemplateBinding newPRTB func() *apisv3.ProjectRoleTemplateBinding username string } tests := []struct { - name string - args args - wantErr bool - allowed bool + name string + args args + wantErr bool + allowed bool + stateSetup func(state testState) }{ { name: "base test valid PRTB creation", @@ -1121,6 +1172,102 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { }, allowed: false, }, + { + name: "external RT with externalRules valid PRTB creation when feature flag is on", + args: args{ + username: writeNodeUser, + oldPRTB: func() *apisv3.ProjectRoleTemplateBinding { + return nil + }, + newPRTB: func() *apisv3.ProjectRoleTemplateBinding { + basePRTB := newBasePRTB() + basePRTB.RoleTemplateName = "external-rule-write-nodes" + + return basePRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: true, + }, + { + name: "external RT with externalRules not created when feature flag is on and there are not enough permissions", + args: args{ + username: readPodUser, + oldPRTB: func() *apisv3.ProjectRoleTemplateBinding { + return nil + }, + newPRTB: func() *apisv3.ProjectRoleTemplateBinding { + basePRTB := newBasePRTB() + basePRTB.RoleTemplateName = "external-rule-write-nodes" + + return basePRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: false, + }, + { + name: "external RT valid PRTB creation when feature flag is off", + args: args{ + username: adminUser, + oldPRTB: func() *apisv3.ProjectRoleTemplateBinding { + return nil + }, + newPRTB: func() *apisv3.ProjectRoleTemplateBinding { + basePRTB := newBasePRTB() + basePRTB.RoleTemplateName = "read-pods-role" + + return basePRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + }, + allowed: true, + }, + { + name: "external RT is created even when there are not enough permissions PRTB creation when feature flag is off", + args: args{ + username: writeNodeUser, + oldPRTB: func() *apisv3.ProjectRoleTemplateBinding { + return nil + }, + newPRTB: func() *apisv3.ProjectRoleTemplateBinding { + basePRTB := newBasePRTB() + basePRTB.RoleTemplateName = "read-pods-role" + + return basePRTB + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&apisv3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: apisv3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + }, + allowed: true, + }, } for i := range tests { @@ -1128,6 +1275,16 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { p.Run(test.name, func() { p.T().Parallel() req := createPRTBRequest(p.T(), test.args.oldPRTB(), test.args.newPRTB(), test.args.username) + featureCache := fake.NewMockNonNamespacedCacheInterface[*apisv3.Feature](ctrl) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + state := testState{ + clusterRoleCacheMock: clusterRoleCache, + featureCacheMock: featureCache, + } + if test.stateSetup != nil { + test.stateSetup(state) + } + validator := validatorWithMocks(state) admitters := validator.Admitters() p.Len(admitters, 1) resp, err := admitters[0].Admit(req) diff --git a/pkg/resources/management.cattle.io/v3/roletemplate/RoleTemplate.md b/pkg/resources/management.cattle.io/v3/roletemplate/RoleTemplate.md index f1aedf0d3..120d44cbc 100644 --- a/pkg/resources/management.cattle.io/v3/roletemplate/RoleTemplate.md +++ b/pkg/resources/management.cattle.io/v3/roletemplate/RoleTemplate.md @@ -6,13 +6,14 @@ Note: all checks are bypassed if the RoleTemplate is being deleted Circular references to a `RoleTemplate` (a inherits b, b inherits a) are not allowed. More specifically, if "roleTemplate1" is included in the `roleTemplateNames` of "roleTemplate2", then "roleTemplate2" must not be included in the `roleTemplateNames` of "roleTemplate1". This check prevents the creation of roles whose end-state cannot be resolved. -### Rules Without Verbs +### Rules Without Verbs, Resources, API groups -Rules without verbs are not permitted. The `rules` included in a RoleTemplate are of the same type as the rules used by standard Kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one. +Rules without verbs, resources, or apigroups are not permitted. The `rules` and `externalRules` included in a RoleTemplate are of the same type as the rules used by standard Kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one. ### Escalation Prevention Users can only change RoleTemplates with rights less than or equal to those they currently possess. This prevents privilege escalation. +Users can't create external RoleTemplates (or update existing RoleTemplates) with `ExternalRules` without having the `escalate` verb on that RoleTemplate. ### Context Validation diff --git a/pkg/resources/management.cattle.io/v3/roletemplate/main_test.go b/pkg/resources/management.cattle.io/v3/roletemplate/main_test.go index 5c457f527..a7f664fa9 100644 --- a/pkg/resources/management.cattle.io/v3/roletemplate/main_test.go +++ b/pkg/resources/management.cattle.io/v3/roletemplate/main_test.go @@ -8,6 +8,7 @@ import ( v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/wrangler/v2/pkg/generic/fake" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" v1 "k8s.io/api/admission/v1" @@ -21,12 +22,18 @@ import ( var errTest = errors.New("bad error") +type testState struct { + featureCacheMock *fake.MockNonNamespacedCacheInterface[*v3.Feature] + clusterRoleCacheMock *fake.MockNonNamespacedCacheInterface[*rbacv1.ClusterRole] +} + type tableTest struct { - wantRT func() *v3.RoleTemplate - name string - args args - wantError bool - allowed bool + wantRT func() *v3.RoleTemplate + name string + args args + stateSetup func(state testState) + wantError bool + allowed bool } type args struct { @@ -65,6 +72,10 @@ func (c *RoleTemplateSuite) SetupSuite() { APIGroups: []string{"*"}, Resources: []string{"*"}, } + ruleAdminNonResource := rbacv1.PolicyRule{ + Verbs: []string{"*"}, + NonResourceURLs: []string{"*"}, + } c.ruleEmptyVerbs = rbacv1.PolicyRule{ Verbs: nil, APIGroups: []string{"v1"}, @@ -83,7 +94,7 @@ func (c *RoleTemplateSuite) SetupSuite() { Name: "admin-role", }, DisplayName: "Admin Role", - Rules: []rbacv1.PolicyRule{ruleAdmin}, + Rules: []rbacv1.PolicyRule{ruleAdmin, ruleAdminNonResource}, Builtin: true, Administrative: true, Context: "cluster", @@ -101,7 +112,7 @@ func (c *RoleTemplateSuite) SetupSuite() { ObjectMeta: metav1.ObjectMeta{ Name: "admin-role", }, - Rules: []rbacv1.PolicyRule{ruleAdmin}, + Rules: []rbacv1.PolicyRule{ruleAdmin, ruleAdminNonResource}, } c.manageNodeRole = &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: "manage-nodes"}, diff --git a/pkg/resources/management.cattle.io/v3/roletemplate/validator.go b/pkg/resources/management.cattle.io/v3/roletemplate/validator.go index 1c8d6aba1..187b2ac1b 100644 --- a/pkg/resources/management.cattle.io/v3/roletemplate/validator.go +++ b/pkg/resources/management.cattle.io/v3/roletemplate/validator.go @@ -127,13 +127,25 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp return admission.ResponseBadRequest(fmt.Sprintf("Circular Reference: RoleTemplate %s already inherits RoleTemplate %s", circularTemplate.Name, newRT.Name)), nil } + if newRT.ExternalRules != nil { + if !newRT.External { + return admission.ResponseBadRequest("ExternalRules can't be set in RoleTemplates with external=false"), nil + } + // verify external rules as per kubernetes rbac rules. + if err := common.ValidateRules(newRT.ExternalRules, false, fldPath.Child("externalRules")); err != nil { + return admission.ResponseBadRequest(fmt.Sprintf("Invalid externalRules: %v", err.Error())), nil + } + } + rules, err := a.roleTemplateResolver.RulesFromTemplate(newRT) if err != nil { return nil, fmt.Errorf("failed to get all rules for '%s': %w", newRT.Name, err) } - // verify inherited rules have verbs - if err := common.CheckForVerbs(rules); err != nil { + // Verify template rules as per kubernetes rbac rules. Note that we're + // validating according to the non-namespaced rules to allow .rules + // including nonResourceURLs. + if err := common.ValidateRules(rules, false, fldPath.Child("rules")); err != nil { return admission.ResponseBadRequest(err.Error()), nil } @@ -144,6 +156,11 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp return admission.ResponseAllowed(), nil } + if newRT.External && newRT.ExternalRules != nil { + // ExternalRules needs 'escalate' permissions. Request would have already been accepted if this user had 'escalate' permissions. + return admission.ResponseFailedEscalation("External RoleTemplates with ExternalRules can only be created for users with 'escalate' permissions"), nil + } + err = auth.ConfirmNoEscalation(request, rules, "", a.resolver) if err != nil { return admission.ResponseFailedEscalation(err.Error()), nil diff --git a/pkg/resources/management.cattle.io/v3/roletemplate/validator_test.go b/pkg/resources/management.cattle.io/v3/roletemplate/validator_test.go index ec93e4fdb..efe735caa 100644 --- a/pkg/resources/management.cattle.io/v3/roletemplate/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/roletemplate/validator_test.go @@ -10,7 +10,7 @@ import ( "github.com/rancher/webhook/pkg/auth" controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/roletemplate" - "github.com/rancher/wrangler/pkg/generic/fake" + "github.com/rancher/wrangler/v2/pkg/generic/fake" v1 "k8s.io/api/admission/v1" authorizationv1 "k8s.io/api/authorization/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -54,15 +54,13 @@ func (r *RoleTemplateSuite) Test_PrivilegeEscalation() { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](ctrl) - roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()) + roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()).AnyTimes() roleTemplateCache.EXPECT().Get(r.adminRT.Name).Return(r.adminRT, nil).AnyTimes() roleTemplateCache.EXPECT().Get(r.readNodesRT.Name).Return(r.readNodesRT, nil).AnyTimes() roleTemplateCache.EXPECT().Get(notFoundRoleTemplateName).Return(nil, newNotFound(notFoundRoleTemplateName)).AnyTimes() roleTemplateCache.EXPECT().List(gomock.Any()).Return([]*v3.RoleTemplate{r.adminRT, r.readNodesRT}, nil).AnyTimes() - clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) grCache := fake.NewMockNonNamespacedCacheInterface[*v3.GlobalRole](ctrl) - grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()) + grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()).AnyTimes() k8Fake := &k8testing.Fake{} fakeSAR := &k8fake.FakeSubjectAccessReviews{Fake: &k8fake.FakeAuthorizationV1{Fake: k8Fake}} @@ -78,9 +76,6 @@ func (r *RoleTemplateSuite) Test_PrivilegeEscalation() { spec.ResourceAttributes.Verb == "escalate" return true, review, nil }) - validator := roletemplate.NewValidator(resolver, roleResolver, fakeSAR, grCache) - admitters := validator.Admitters() - r.Len(admitters, 1, "wanted only one admitter") tests := []tableTest{ { @@ -153,12 +148,156 @@ func (r *RoleTemplateSuite) Test_PrivilegeEscalation() { }, allowed: false, }, + { + name: "user with escalate permissions can create external RoleTemplates with externalRules", + args: args{ + username: testUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + baseRT.External = true + baseRT.ExternalRules = r.manageNodeRole.Rules + + return baseRT + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: true, + }, + { + name: "user without escalate permissions can't create external RoleTemplates with externalRules", + args: args{ + username: noPrivUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + baseRT.External = true + baseRT.ExternalRules = r.manageNodeRole.Rules + + return baseRT + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: false, + }, + { + name: "user without escalate permissions can't create external RoleTemplates with externalRules when the feature flag is off", + args: args{ + username: noPrivUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + baseRT.External = true + baseRT.ExternalRules = r.manageNodeRole.Rules + + return baseRT + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + state.clusterRoleCacheMock.EXPECT().Get(newDefaultRT().Name).Return(&rbacv1.ClusterRole{}, nil) + }, + allowed: false, + }, + { + name: "user without escalate permissions can't update external RoleTemplates with externalRules", + args: args{ + username: noPrivUser, + oldRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + + return baseRT + }, + newRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + baseRT.External = true + baseRT.ExternalRules = r.manageNodeRole.Rules + + return baseRT + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: false, + }, + { + name: "user with escalate permissions ca update external RoleTemplates with externalRules", + args: args{ + username: testUser, + oldRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + + return baseRT + }, + newRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + baseRT.External = true + baseRT.ExternalRules = r.manageNodeRole.Rules + + return baseRT + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v3.FeatureSpec{ + Value: &[]bool{true}[0], + }, + }, nil) + }, + allowed: true, + }, } for i := range tests { test := tests[i] r.Run(test.name, func() { r.T().Parallel() + featureCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + + state := testState{ + clusterRoleCacheMock: clusterRoleCache, + featureCacheMock: featureCache, + } + if test.stateSetup != nil { + test.stateSetup(state) + } + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, state.featureCacheMock) + validator := roletemplate.NewValidator(resolver, roleResolver, fakeSAR, grCache) + admitters := validator.Admitters() + r.Len(admitters, 1, "wanted only one admitter") req := createRTRequest(r.T(), test.args.oldRT(), test.args.newRT(), test.args.username) resp, err := admitters[0].Admit(req) if r.NoError(err, "Admit failed") { @@ -185,7 +324,8 @@ func (r *RoleTemplateSuite) Test_UpdateValidation() { roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()) roleTemplateCache.EXPECT().Get(r.adminRT.Name).Return(r.adminRT, nil).AnyTimes() clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + featureCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, featureCache) grCache := fake.NewMockNonNamespacedCacheInterface[*v3.GlobalRole](ctrl) grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()) @@ -487,20 +627,14 @@ func (r *RoleTemplateSuite) Test_Create() { ctrl := gomock.NewController(r.T()) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](ctrl) - roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()) + roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()).AnyTimes() roleTemplateCache.EXPECT().Get(r.adminRT.Name).Return(r.adminRT, nil).AnyTimes() - clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) grCache := fake.NewMockNonNamespacedCacheInterface[*v3.GlobalRole](ctrl) - grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()) + grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()).AnyTimes() k8Fake := &k8testing.Fake{} fakeSAR := &k8fake.FakeSubjectAccessReviews{Fake: &k8fake.FakeAuthorizationV1{Fake: k8Fake}} - validator := roletemplate.NewValidator(resolver, roleResolver, fakeSAR, grCache) - admitters := validator.Admitters() - r.Len(admitters, 1, "wanted only one admitter") - tests := []tableTest{ { name: "base test valid RT", @@ -580,14 +714,131 @@ func (r *RoleTemplateSuite) Test_Create() { }, allowed: false, }, + { + name: "create new external RoleTemplate when feature flag is off", + args: args{ + username: adminUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + rt := newDefaultRT() + rt.External = true + return rt + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + Spec: v3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + state.clusterRoleCacheMock.EXPECT().Get("rt-new").Return(&rbacv1.ClusterRole{}, nil) + }, + allowed: true, + }, + { + name: "create new external RoleTemplate when feature flag is off, context is project and backing ClusterRole doesn't exist", + args: args{ + username: adminUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + rt := newDefaultRT() + rt.External = true + rt.Context = "project" + return rt + }, + }, + stateSetup: func(state testState) { + state.featureCacheMock.EXPECT().Get(auth.ExternalRulesFeature).Return(&v3.Feature{ + Spec: v3.FeatureSpec{ + Value: &[]bool{false}[0], + }, + }, nil) + }, + allowed: true, + }, + { + name: "invalid external rules", + args: args{ + username: adminUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + rt := newDefaultRT() + rt.External = true + rt.ExternalRules = []rbacv1.PolicyRule{r.ruleEmptyVerbs} + return rt + }, + }, + allowed: false, + wantError: true, + }, + { + name: "ExternalRules can't be set in RoleTemplates with external=false", + args: args{ + username: adminUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + rt := newDefaultRT() + rt.External = false + rt.ExternalRules = r.manageNodeRole.Rules + return rt + }, + }, + allowed: false, + wantError: true, + }, + { + // Ensure we're not blocking the creation of RTs with + // NonResourceURLs included such as cluster-owner. + name: "ensure accept non resource urls", + args: args{ + username: adminUser, + oldRT: func() *v3.RoleTemplate { + return nil + }, + newRT: func() *v3.RoleTemplate { + baseRT := newDefaultRT() + baseRT.Rules = []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + NonResourceURLs: []string{"*"}, + }, + } + return baseRT + }, + }, + allowed: true, + }, } for i := range tests { test := tests[i] r.Run(test.name, func() { r.T().Parallel() + featureCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) + state := testState{ + clusterRoleCacheMock: clusterRoleCache, + featureCacheMock: featureCache, + } + if test.stateSetup != nil { + test.stateSetup(state) + } + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, state.featureCacheMock) + validator := roletemplate.NewValidator(resolver, roleResolver, fakeSAR, grCache) + admitters := validator.Admitters() + r.Len(admitters, 1, "wanted only one admitter") + req := createRTRequest(r.T(), test.args.oldRT(), test.args.newRT(), test.args.username) resp, err := admitters[0].Admit(req) + r.NoError(err, "Admit failed") r.Equalf(test.allowed, resp.Allowed, "Response was incorrectly validated wanted response.Allowed = '%v' got '%v' message=%+v", test.allowed, resp.Allowed, resp.Result) }) @@ -650,7 +901,7 @@ func (r *RoleTemplateSuite) Test_Delete() { grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()) grCache.EXPECT().GetByIndex(expectedGlobalRefIndex, gomock.Any()).Return([]*v3.GlobalRole{}, nil).AnyTimes() return testMocks{ - rtResolver: auth.NewRoleTemplateResolver(roleTemplateCache, nil), + rtResolver: auth.NewRoleTemplateResolver(roleTemplateCache, nil, nil), grCache: grCache, } }, @@ -702,7 +953,7 @@ func (r *RoleTemplateSuite) Test_Delete() { grCache.EXPECT().GetByIndex(expectedGlobalRefIndex, gomock.Any()).Return([]*v3.GlobalRole{}, nil).AnyTimes() return testMocks{ - rtResolver: auth.NewRoleTemplateResolver(roleTemplateCache, nil), + rtResolver: auth.NewRoleTemplateResolver(roleTemplateCache, nil, nil), grCache: grCache, } }, @@ -729,7 +980,7 @@ func (r *RoleTemplateSuite) Test_Delete() { grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()) grCache.EXPECT().GetByIndex(expectedGlobalRefIndex, gomock.Any()).Return([]*v3.GlobalRole{}, nil).AnyTimes() return testMocks{ - rtResolver: auth.NewRoleTemplateResolver(roleTemplateCache, nil), + rtResolver: auth.NewRoleTemplateResolver(roleTemplateCache, nil, nil), grCache: grCache, } }, @@ -765,7 +1016,8 @@ func (r *RoleTemplateSuite) Test_ErrorHandling() { roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](ctrl) roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + featureCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, featureCache) grCache := fake.NewMockNonNamespacedCacheInterface[*v3.GlobalRole](ctrl) grCache.EXPECT().AddIndexer(expectedGlobalRefIndex, gomock.Any()) @@ -879,7 +1131,8 @@ func (r *RoleTemplateSuite) Test_CheckCircularRef() { req := createRTRequest(r.T(), nil, newRT, adminUser) clusterRoleCache := fake.NewMockNonNamespacedCacheInterface[*rbacv1.ClusterRole](ctrl) - roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache) + featureCache := fake.NewMockNonNamespacedCacheInterface[*v3.Feature](ctrl) + roleResolver := auth.NewRoleTemplateResolver(roleTemplateCache, clusterRoleCache, featureCache) validator := roletemplate.NewValidator(resolver, roleResolver, fakeSAR, grCache) admitters := validator.Admitters() diff --git a/pkg/resources/management.cattle.io/v3/setting/Setting.md b/pkg/resources/management.cattle.io/v3/setting/Setting.md new file mode 100644 index 000000000..4df21041a --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/setting/Setting.md @@ -0,0 +1,25 @@ +## Validation Checks + +### Invalid Fields - Create + +When a Setting is created, the following checks take place: + +- If set, `disable-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `delete-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `user-last-login-default` must be a date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `user-retention-cron` must be a valid standard cron expression (e.g. `0 0 * * 0`). + +### Invalid Fields - Update + +When a Setting is updated, the following checks take place: + +- If set, `disable-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `delete-inactive-user-after` must be zero or a positive duration (e.g. `240h`). +- If set, `user-last-login-default` must be a date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `user-retention-cron` must be a valid standard cron expression (e.g. `0 0 * * 1`). + +### Forbidden - Update + +- If `agent-tls-mode` has `default` or `value` updated from `system-store` to `strict`, then all non-local clusters must + have a status condition `AgentTlsStrictCheck` set to `True`, unless the new setting has an overriding + annotation `cattle.io/force=true`. diff --git a/pkg/resources/management.cattle.io/v3/setting/validator.go b/pkg/resources/management.cattle.io/v3/setting/validator.go new file mode 100644 index 000000000..9a2641900 --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/setting/validator.go @@ -0,0 +1,187 @@ +package setting + +import ( + "errors" + "fmt" + "time" + + v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + "github.com/robfig/cron" + admissionv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/trace" + + "github.com/rancher/webhook/pkg/admission" + controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" + objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" +) + +// MinDeleteInactiveUserAfter is the minimum duration for delete-inactive-user-after setting. +// This is introduced to minimize the risk of deleting users accidentally by setting a relatively low value. +// The admin can still set a lower value if needed by bypassing the webhook. +const MinDeleteInactiveUserAfter = 24 * 14 * time.Hour // 14 days. + +var gvr = schema.GroupVersionResource{ + Group: "management.cattle.io", + Version: "v3", + Resource: "settings", +} + +// Validator validates settings. +type Validator struct { + admitter admitter +} + +// NewValidator returns a new Validator instance. +func NewValidator(clusterCache controllerv3.ClusterCache) *Validator { + return &Validator{ + admitter: admitter{ + clusterCache: clusterCache, + }, + } +} + +// GVR returns the GroupVersionResource. +func (v *Validator) GVR() schema.GroupVersionResource { + return gvr +} + +// Operations returns list of operations handled by the validator. +func (v *Validator) Operations() []admissionregistrationv1.OperationType { + return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create} +} + +// ValidatingWebhook returns the ValidatingWebhook. +func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { + valWebhook := admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()) + valWebhook.FailurePolicy = admission.Ptr(admissionregistrationv1.Ignore) + return []admissionregistrationv1.ValidatingWebhook{*valWebhook} +} + +// Admitters returns the admitter objects. +func (v *Validator) Admitters() []admission.Admitter { + return []admission.Admitter{&v.admitter} +} + +type admitter struct { + clusterCache controllerv3.ClusterCache +} + +// Admit handles the webhook admission requests. +func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { + listTrace := trace.New("userAttributeValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) + defer listTrace.LogIfLong(admission.SlowTraceDuration) + + oldSetting, newSetting, err := objectsv3.SettingOldAndNewFromRequest(&request.AdmissionRequest) + if err != nil { + return nil, fmt.Errorf("failed to get Setting from request: %w", err) + } + switch request.Operation { + case admissionv1.Create: + if err := a.validateUserRetentionSettings(newSetting); err != nil { + return admission.ResponseBadRequest(err.Error()), nil + } + case admissionv1.Update: + if err := a.validateUserRetentionSettings(newSetting); err != nil { + return admission.ResponseBadRequest(err.Error()), nil + } + if err := a.validateAgentTLSMode(*oldSetting, *newSetting); err != nil { + return admission.ResponseBadRequest(err.Error()), nil + } + } + return admission.ResponseAllowed(), nil +} + +func (a *admitter) validateUserRetentionSettings(s *v3.Setting) error { + var err error + + switch s.Name { + case "disable-inactive-user-after": + if s.Value != "" { + _, err = validateDuration(s.Value) + } + case "delete-inactive-user-after": + if s.Value != "" { + var dur time.Duration + dur, err = validateDuration(s.Value) + if err == nil && dur < MinDeleteInactiveUserAfter { + err = fmt.Errorf("must be at least %s", MinDeleteInactiveUserAfter) + } + } + case "user-last-login-default": + if s.Value != "" { + _, err = time.Parse(time.RFC3339, s.Value) + } + case "user-retention-cron": + if s.Value != "" { + _, err = cron.ParseStandard(s.Value) + } + default: + } + + if err != nil { + return field.TypeInvalid(field.NewPath("value"), s.Value, err.Error()) + } + + return nil +} + +func (a *admitter) validateAgentTLSMode(oldSetting, newSetting v3.Setting) error { + if oldSetting.Name != "agent-tls-mode" || newSetting.Name != "agent-tls-mode" { + return nil + } + if effectiveValue(oldSetting) == "system-store" && effectiveValue(newSetting) == "strict" { + if force := newSetting.Annotations["cattle.io/force"]; force == "true" { + return nil + } + clusters, err := a.clusterCache.List(labels.NewSelector()) + if err != nil { + return fmt.Errorf("failed to list clusters: %w", err) + } + for _, cluster := range clusters { + if cluster.Name == "local" { + continue + } + if !clusterConditionMatches(cluster, "AgentTlsStrictCheck", "True") { + return field.Forbidden(field.NewPath("value", "default"), + fmt.Sprintf("AgentTlsStrictCheck condition of cluster %s isn't 'True'", cluster.Name)) + } + } + } + return nil +} + +func validateDuration(value string) (time.Duration, error) { + dur, err := time.ParseDuration(value) + if err != nil { + return 0, err + } + + if dur < 0 { + return 0, errors.New("negative duration") + } + + return dur, err +} + +func clusterConditionMatches(cluster *v3.Cluster, t v3.ClusterConditionType, status v1.ConditionStatus) bool { + for _, cond := range cluster.Status.Conditions { + if cond.Type == t && cond.Status == status { + return true + } + } + return false +} + +func effectiveValue(s v3.Setting) string { + if s.Value != "" { + return s.Value + } else if s.Default != "" { + return s.Default + } + return "" +} diff --git a/pkg/resources/management.cattle.io/v3/setting/validator_test.go b/pkg/resources/management.cattle.io/v3/setting/validator_test.go new file mode 100644 index 000000000..c27963333 --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/setting/validator_test.go @@ -0,0 +1,650 @@ +package setting_test + +import ( + "context" + "encoding/json" + "errors" + "testing" + "time" + + "github.com/golang/mock/gomock" + v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + "github.com/rancher/wrangler/v2/pkg/generic/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/admission/v1" + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/setting" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" +) + +type SettingSuite struct { + suite.Suite +} + +func TestRetentionFieldsValidation(t *testing.T) { + t.Parallel() + suite.Run(t, new(SettingSuite)) +} + +var ( + gvk = metav1.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "Setting"} + gvr = metav1.GroupVersionResource{Group: "management.cattle.io", Version: "v3", Resource: "settings"} +) + +type retentionTest struct { + setting string + value string + allowed bool +} + +func (t *retentionTest) name() string { + return t.setting + "_" + t.value +} + +func (t *retentionTest) toSetting() ([]byte, error) { + return json.Marshal(v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.setting, + }, + Value: t.value, + }) +} +func (t *retentionTest) toOldSetting() ([]byte, error) { + return json.Marshal(v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.setting, + }, + }) +} + +var retentionTests = []retentionTest{ + { + setting: "disable-inactive-user-after", + value: "", + allowed: true, + }, + { + setting: "delete-inactive-user-after", + value: "", + allowed: true, + }, + { + setting: "user-last-login-default", + value: "", + allowed: true, + }, + { + setting: "user-retention-cron", + value: "", + allowed: true, + }, + { + setting: "disable-inactive-user-after", + value: "2h30m", + allowed: true, + }, + { + setting: "delete-inactive-user-after", + value: setting.MinDeleteInactiveUserAfter.String(), + allowed: true, + }, + { + setting: "user-last-login-default", + value: "2024-01-08T00:00:00Z", + allowed: true, + }, + { + setting: "user-retention-cron", + value: "* * * * *", + allowed: true, + }, + { + setting: "disable-inactive-user-after", + value: "1w", + }, + { + setting: "delete-inactive-user-after", + value: "2d", + }, + { + setting: "user-last-login-default", + value: "foo", + }, + { + setting: "user-retention-cron", + value: "* * * * * *", + }, + { + setting: "disable-inactive-user-after", + value: "-1h", + }, + { + setting: "delete-inactive-user-after", + value: "-1h", + }, + { + setting: "delete-inactive-user-after", + value: (setting.MinDeleteInactiveUserAfter - time.Second).String(), + }, +} + +func (s *SettingSuite) TestValidateRetentionSettingsOnUpdate() { + s.validate(v1.Update) +} + +func (s *SettingSuite) TestValidateRetentionSettingsOnCreate() { + s.validate(v1.Create) +} + +func (s *SettingSuite) validate(op v1.Operation) { + admitter := s.setup() + + for _, test := range retentionTests { + test := test + s.Run(test.name(), func() { + t := s.T() + t.Parallel() + + oldObjRaw, err := test.toOldSetting() + assert.NoError(t, err, "failed to marshal old Setting") + + objRaw, err := test.toSetting() + assert.NoError(t, err, "failed to marshal Setting") + + resp, err := admitter.Admit(newRequest(op, objRaw, oldObjRaw)) + if assert.NoError(t, err, "Admit failed") { + assert.Equalf(t, test.allowed, resp.Allowed, "expected allowed %v got %v message=%v", test.allowed, resp.Allowed, resp.Result) + } + }) + } +} + +func (s *SettingSuite) TestValidatingWebhookFailurePolicy() { + t := s.T() + validator := setting.NewValidator(nil) + + webhook := validator.ValidatingWebhook(admissionregistrationv1.WebhookClientConfig{}) + require.Len(t, webhook, 1) + ignorePolicy := admissionregistrationv1.Ignore + require.Equal(t, &ignorePolicy, webhook[0].FailurePolicy) +} + +func (s *SettingSuite) setup() admission.Admitter { + validator := setting.NewValidator(nil) + s.Len(validator.Admitters(), 1, "expected 1 admitter") + + return validator.Admitters()[0] +} + +func newRequest(op v1.Operation, obj, oldObj []byte) *admission.Request { + return &admission.Request{ + AdmissionRequest: v1.AdmissionRequest{ + UID: "1", + Kind: gvk, + Resource: gvr, + RequestKind: &gvk, + RequestResource: &gvr, + Operation: op, + UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, + Object: runtime.RawExtension{Raw: obj}, + OldObject: runtime.RawExtension{Raw: oldObj}, + }, + Context: context.Background(), + } +} + +func TestValidateAgentTLSMode(t *testing.T) { + t.Parallel() + tests := map[string]struct { + oldSetting v3.Setting + newSetting v3.Setting + operation v1.Operation + clusters []*v3.Cluster + clusterListerFails bool + allowed bool + }{ + "create allowed for system store": { + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + }, + operation: v1.Create, + allowed: true, + }, + "create allowed for strict": { + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "strict", + }, + operation: v1.Create, + allowed: true, + }, + "update forbidden due to missing status": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Value: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Value: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-2", + }, + }, + }, + operation: v1.Update, + allowed: false, + }, + "update allowed without cluster status but with force annotation": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + Annotations: map[string]string{ + "cattle.io/force": "true", + }, + }, + Default: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-2", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "Foo", + Status: "True", + }, + }, + }, + }, + }, + operation: v1.Update, + allowed: true, + }, + "update forbidden without cluster status and non-true force annotation": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + Annotations: map[string]string{ + "cattle.io/force": "false", + }, + }, + Default: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "False", + }, + }, + }, + }, + }, + operation: v1.Update, + allowed: false, + }, + "update allowed with cluster status and force annotation": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Value: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + Annotations: map[string]string{ + "cattle.io/force": "true", + }, + }, + Value: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-2", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + }, + operation: v1.Update, + allowed: true, + }, + "update allowed from strict to system store": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "strict", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + }, + operation: v1.Update, + allowed: true, + }, + "update allowed from system store to strict": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + Value: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "strict", + Value: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "local", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "False", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-2", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + }, + operation: v1.Update, + allowed: true, + }, + "update allowed with value changing from system store to strict": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + Value: "", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + Value: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "local", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-2", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + }, + operation: v1.Update, + allowed: true, + }, + "update forbidden from system store to strict due to incorrect value on target status": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + Value: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "strict", + Value: "strict", + }, + clusters: []*v3.Cluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "local", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "True", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-2", + }, + Status: v3.ClusterStatus{ + Conditions: []v3.ClusterCondition{ + { + Type: "AgentTlsStrictCheck", + Status: "False", + }, + }, + }, + }, + }, + operation: v1.Update, + allowed: false, + }, + "update forbidden on error to list clusters": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "system-store", + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + Default: "strict", + }, + operation: v1.Update, + clusterListerFails: true, + allowed: false, + }, + "ineffectual update allowed": { + oldSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + }, + newSetting: v3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-tls-mode", + }, + }, + operation: v1.Update, + allowed: true, + }, + } + for name, tc := range tests { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + clusterCache := fake.NewMockNonNamespacedCacheInterface[*v3.Cluster](ctrl) + force := tc.newSetting.Annotations["cattle.io/force"] + if tc.operation == v1.Update && force != "true" && len(tc.clusters) > 0 { + clusterCache.EXPECT().List(gomock.Any()).Return(tc.clusters, nil) + } + if tc.clusterListerFails { + clusterCache.EXPECT().List(gomock.Any()).Return(tc.clusters, errors.New("some error")) + } + v := setting.NewValidator(clusterCache) + admitters := v.Admitters() + require.Len(t, admitters, 1) + + oldSetting, err := json.Marshal(tc.oldSetting) + require.NoError(t, err) + newSetting, err := json.Marshal(tc.newSetting) + require.NoError(t, err) + + res, err := admitters[0].Admit(&admission.Request{ + AdmissionRequest: v1.AdmissionRequest{ + Object: runtime.RawExtension{ + Raw: newSetting, + }, + OldObject: runtime.RawExtension{ + Raw: oldSetting, + }, + Operation: tc.operation, + }, + }) + require.NoError(t, err) + assert.Equal(t, tc.allowed, res.Allowed) + }) + } +} diff --git a/pkg/resources/management.cattle.io/v3/userattribute/UserAttribute.md b/pkg/resources/management.cattle.io/v3/userattribute/UserAttribute.md new file mode 100644 index 000000000..f19d28d31 --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/userattribute/UserAttribute.md @@ -0,0 +1,17 @@ +## Validation Checks + +### Invalid Fields - Create + +When a UserAttribute is created, the following checks take place: + +- If set, `lastLogin` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `disableAfter` must be zero or a positive duration (e.g. `240h`). +- If set, `deleteAfter` must be zero or a positive duration (e.g. `240h`). + +### Invalid Fields - Update + +When a UserAttribute is updated, the following checks take place: + +- If set, `lastLogin` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). +- If set, `disableAfter` must be zero or a positive duration (e.g. `240h`). +- If set, `deleteAfter` must be zero or a positive duration (e.g. `240h`). diff --git a/pkg/resources/management.cattle.io/v3/userattribute/validator.go b/pkg/resources/management.cattle.io/v3/userattribute/validator.go new file mode 100644 index 000000000..05cbb9b5a --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/userattribute/validator.go @@ -0,0 +1,116 @@ +package userattribute + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/rancher/webhook/pkg/admission" + admissionv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/trace" +) + +var gvr = schema.GroupVersionResource{ + Group: "management.cattle.io", + Version: "v3", + Resource: "userattributes", +} + +// Validator validates userattributes. +type Validator struct { + admitter admitter +} + +// NewValidator returns a new Validator instance. +func NewValidator() *Validator { + return &Validator{ + admitter: admitter{}, + } +} + +// GVR returns the GroupVersionResource. +func (v *Validator) GVR() schema.GroupVersionResource { + return gvr +} + +// Operations returns list of operations handled by the validator. +func (v *Validator) Operations() []admissionregistrationv1.OperationType { + return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create} +} + +// ValidatingWebhook returns the ValidatingWebhook. +func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { + return []admissionregistrationv1.ValidatingWebhook{ + *admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()), + } +} + +// Admitters returns the admitter objects. +func (v *Validator) Admitters() []admission.Admitter { + return []admission.Admitter{&v.admitter} +} + +type admitter struct{} + +// Admit handles the webhook admission requests. +func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { + listTrace := trace.New("userAttributeValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) + defer listTrace.LogIfLong(admission.SlowTraceDuration) + + if request.Operation == admissionv1.Create || request.Operation == admissionv1.Update { + err := a.validateRetentionFields(request) + if err != nil { + return admission.ResponseBadRequest(err.Error()), nil + } + } + + return admission.ResponseAllowed(), nil +} + +// PartialUserAttribute represents raw values of UserAttribute retention fields. +type PartialUserAttribute struct { + LastLogin *string `json:"lastLogin"` + DisableAfter *string `json:"disableAfter"` + DeleteAfter *string `json:"deleteAfter"` +} + +func (a *admitter) validateRetentionFields(request *admission.Request) error { + var ( + attr PartialUserAttribute + dur time.Duration + ) + + err := json.Unmarshal(request.Object.Raw, &attr) + if err != nil { + return fmt.Errorf("failed to get PartialUserAttribute from request: %w", err) + } + + if attr.LastLogin != nil { + if _, err = time.Parse(time.RFC3339, *attr.LastLogin); err != nil { + return field.TypeInvalid(field.NewPath("lastLogin"), attr.LastLogin, err.Error()) + } + } + + if attr.DisableAfter != nil { + if dur, err = time.ParseDuration(*attr.DisableAfter); err != nil { + return field.TypeInvalid(field.NewPath("disableAfter"), *attr.DisableAfter, err.Error()) + } + if dur < 0 { + return field.Invalid(field.NewPath("disableAfter"), *attr.DisableAfter, "negative duration") + } + } + + if attr.DeleteAfter != nil { + if dur, err = time.ParseDuration(*attr.DeleteAfter); err != nil { + return field.TypeInvalid(field.NewPath("deleteAfter"), *attr.DeleteAfter, err.Error()) + } + if dur < 0 { + return field.Invalid(field.NewPath("deleteAfter"), *attr.DeleteAfter, "negative duration") + } + } + + return nil +} diff --git a/pkg/resources/management.cattle.io/v3/userattribute/validator_test.go b/pkg/resources/management.cattle.io/v3/userattribute/validator_test.go new file mode 100644 index 000000000..9378ab8aa --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/userattribute/validator_test.go @@ -0,0 +1,172 @@ +package userattribute_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/userattribute" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/admission/v1" + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" +) + +type RetentionFieldsSuite struct { + suite.Suite +} + +func TestRetentionFieldsValidation(t *testing.T) { + t.Parallel() + suite.Run(t, new(RetentionFieldsSuite)) +} + +var ( + gvk = metav1.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "UserAttribute"} + gvr = metav1.GroupVersionResource{Group: "management.cattle.io", Version: "v3", Resource: "userattributes"} +) + +type retentionFieldsTest struct { + lastLogin *string + disableAfter *string + deleteAfter *string + allowed bool +} + +func (t *retentionFieldsTest) name() string { + return pointer.StringDeref(t.lastLogin, "nil") + "_" + + pointer.StringDeref(t.disableAfter, "nil") + "_" + + pointer.StringDeref(t.deleteAfter, "nil") +} + +func (t *retentionFieldsTest) toUserAttribute() ([]byte, error) { + return json.Marshal(userattribute.PartialUserAttribute{ + LastLogin: t.lastLogin, + DisableAfter: t.disableAfter, + DeleteAfter: t.deleteAfter, + }) +} + +var retentionFieldsTests = []retentionFieldsTest{ + { + allowed: true, + }, + { + disableAfter: pointer.String("0"), + allowed: true, + }, + { + deleteAfter: pointer.String("0"), + allowed: true, + }, + { + disableAfter: pointer.String("1h2m3s"), + allowed: true, + }, + { + deleteAfter: pointer.String("1h2m3s"), + allowed: true, + }, + { + lastLogin: pointer.String(time.Now().Format(time.RFC3339)), + allowed: true, + }, + { + disableAfter: pointer.String("1w"), + }, + { + deleteAfter: pointer.String("1w"), + }, + { + disableAfter: pointer.String("1d"), + }, + { + deleteAfter: pointer.String("1d"), + }, + { + disableAfter: pointer.String(""), + }, + { + deleteAfter: pointer.String(""), + }, + { + disableAfter: pointer.String("-1h"), + }, + { + deleteAfter: pointer.String("-1h"), + }, + { + lastLogin: pointer.String("2024-03-25T21:2:45Z"), // Not a valid RFC3339 time. + }, + { + lastLogin: pointer.String(""), + }, +} + +func (s *RetentionFieldsSuite) TestValidateOnUpdate() { + s.validate(v1.Update) +} + +func (s *RetentionFieldsSuite) TestValidateOnCreate() { + s.validate(v1.Create) +} + +func (s *RetentionFieldsSuite) TestDontValidateOnDelete() { + // Make sure that UserAttribute can be deleted without enforcing validation of user retention fields. + alwaysAllow := true + s.validate(v1.Delete, alwaysAllow) +} + +func (s *RetentionFieldsSuite) validate(op v1.Operation, allowed ...bool) { + admitter := s.setup() + + for _, test := range retentionFieldsTests { + test := test + s.Run(test.name(), func() { + t := s.T() + t.Parallel() + + objRaw, err := test.toUserAttribute() + assert.NoError(t, err, "failed to marshal PartialUserAttribute") + + resp, err := admitter.Admit(newRequest(op, objRaw)) + if assert.NoError(t, err, "Admit failed") { + wantAllowed := test.allowed + if len(allowed) > 0 { + wantAllowed = allowed[0] // Apply the override. + } + + assert.Equalf(t, wantAllowed, resp.Allowed, "expected allowed %v got %v message=%v", test.allowed, resp.Allowed, resp.Result) + } + }) + } +} + +func (s *RetentionFieldsSuite) setup() admission.Admitter { + validator := userattribute.NewValidator() + s.Len(validator.Admitters(), 1, "expected 1 admitter") + + return validator.Admitters()[0] +} + +func newRequest(op v1.Operation, obj []byte) *admission.Request { + return &admission.Request{ + AdmissionRequest: v1.AdmissionRequest{ + UID: "1", + Kind: gvk, + Resource: gvr, + RequestKind: &gvk, + RequestResource: &gvr, + Operation: op, + UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, + Object: runtime.RawExtension{Raw: obj}, + OldObject: runtime.RawExtension{Raw: []byte("{}")}, + }, + Context: context.Background(), + } +} diff --git a/pkg/resources/provisioning.cattle.io/v1/cluster/Cluster.md b/pkg/resources/provisioning.cattle.io/v1/cluster/Cluster.md new file mode 100644 index 000000000..429cbd312 --- /dev/null +++ b/pkg/resources/provisioning.cattle.io/v1/cluster/Cluster.md @@ -0,0 +1,10 @@ +## Mutation Checks + +### On Update + +#### Dynamic Schema Drop + +Check for the presence of the `provisioning.cattle.io/allow-dynamic-schema-drop` annotation. If the value is `"true"`, +perform no mutations. If the value is not present or not `"true"`, compare the value of the `dynamicSchemaSpec` field +for each `machinePool`, to its' previous value. If the values are not identical, revert the value for the +`dynamicSchemaSpec` for the specific `machinePool`, but do not reject the request. diff --git a/pkg/resources/provisioning.cattle.io/v1/cluster/mutator.go b/pkg/resources/provisioning.cattle.io/v1/cluster/mutator.go index 7be80b831..dd9d914e9 100644 --- a/pkg/resources/provisioning.cattle.io/v1/cluster/mutator.go +++ b/pkg/resources/provisioning.cattle.io/v1/cluster/mutator.go @@ -19,8 +19,8 @@ import ( objectsv1 "github.com/rancher/webhook/pkg/generated/objects/provisioning.cattle.io/v1" "github.com/rancher/webhook/pkg/patch" psa "github.com/rancher/webhook/pkg/podsecurityadmission" - "github.com/rancher/wrangler/pkg/data/convert" - corecontroller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/v2/pkg/data/convert" + corecontroller "github.com/rancher/wrangler/v2/pkg/generated/controllers/core/v1" "github.com/sirupsen/logrus" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" @@ -42,11 +42,12 @@ const ( // MountPath is where the admission control configuration file will be mounted in the control plane nodes mountPath = "/etc/rancher/%s/config/rancher-psact.yaml" - controlPlaneRoleLabel = "rke.cattle.io/control-plane-role" - secretAnnotation = "rke.cattle.io/object-authorized-for-clusters" - runtimeK3S = "k3s" - runtimeRKE2 = "rke2" - runtimeRKE = "rke" + controlPlaneRoleLabel = "rke.cattle.io/control-plane-role" + secretAnnotation = "rke.cattle.io/object-authorized-for-clusters" + allowDynamicSchemaDropAnnotation = "provisioning.cattle.io/allow-dynamic-schema-drop" + runtimeK3S = "k3s" + runtimeRKE2 = "rke2" + runtimeRKE = "rke" ) var ( @@ -102,7 +103,7 @@ func (m *ProvisioningClusterMutator) Admit(request *admission.Request) (*admissi listTrace := trace.New("provisioningCluster Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) defer listTrace.LogIfLong(admission.SlowTraceDuration) - cluster, err := objectsv1.ClusterFromRequest(&request.AdmissionRequest) + oldCluster, cluster, err := objectsv1.ClusterOldAndNewFromRequest(&request.AdmissionRequest) if err != nil { return nil, err } @@ -133,6 +134,13 @@ func (m *ProvisioningClusterMutator) Admit(request *admission.Request) (*admissi return response, nil } + if request.Operation == admissionv1.Update { + response = m.handleDynamicSchemaDrop(request, oldCluster, cluster) + if response.Result != nil { + return response, nil + } + } + response.Allowed = true if err = patch.CreatePatch(clusterJSON, cluster, response); err != nil { return nil, fmt.Errorf("failed to create patch: %w", err) @@ -140,6 +148,37 @@ func (m *ProvisioningClusterMutator) Admit(request *admission.Request) (*admissi return response, nil } +// handleDynamicSchemaDrop watches for provisioning cluster updates, and reinserts the previous value of the +// dynamicSchemaSpec field for a machine pool if the "provisioning.cattle.io/allow-dynamic-schema-drop" annotation is +// not present and true on the cluster. If the value of the annotation is true, no mutation is performed. +func (m *ProvisioningClusterMutator) handleDynamicSchemaDrop(request *admission.Request, oldCluster, cluster *v1.Cluster) *admissionv1.AdmissionResponse { + if cluster.Name == "local" || cluster.Spec.RKEConfig == nil { + return admission.ResponseAllowed() + } + + if cluster.Annotations[allowDynamicSchemaDropAnnotation] == "true" { + return admission.ResponseAllowed() + } + + oldClusterPools := map[string]*v1.RKEMachinePool{} + for _, mp := range oldCluster.Spec.RKEConfig.MachinePools { + oldClusterPools[mp.Name] = &mp + } + + for i, newPool := range cluster.Spec.RKEConfig.MachinePools { + oldPool, ok := oldClusterPools[newPool.Name] + if !ok { + logrus.Debugf("[%s] new machine pool: %s, skipping validation of dynamic schema spec", request.UID, newPool.Name) + continue + } + if oldPool.DynamicSchemaSpec != "" && newPool.DynamicSchemaSpec == "" { + logrus.Debugf("provisioning cluster %s/%s machine pool %s dynamic schema spec mutated without supplying annotation %s, reverting", cluster.Namespace, cluster.Name, newPool.Name, allowDynamicSchemaDropAnnotation) + cluster.Spec.RKEConfig.MachinePools[i].DynamicSchemaSpec = oldPool.DynamicSchemaSpec + } + } + return admission.ResponseAllowed() +} + // handlePSACT updates the cluster and an underlying secret to support PSACT. // If a PSACT is set in the cluster, handlePSACT generates an admission configuration file, mounts the file into a secret, // updates the cluster's spec to mount the secret to the control plane nodes, and configures kube-apisever to use the admission configuration file; diff --git a/pkg/resources/provisioning.cattle.io/v1/cluster/mutator_test.go b/pkg/resources/provisioning.cattle.io/v1/cluster/mutator_test.go index 9a572b97d..38ccaed7c 100644 --- a/pkg/resources/provisioning.cattle.io/v1/cluster/mutator_test.go +++ b/pkg/resources/provisioning.cattle.io/v1/cluster/mutator_test.go @@ -2,12 +2,13 @@ package cluster import ( "encoding/json" + "reflect" "testing" v1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" rkev1 "github.com/rancher/rancher/pkg/apis/rke.cattle.io/v1" "github.com/rancher/webhook/pkg/admission" - data2 "github.com/rancher/wrangler/pkg/data" + data2 "github.com/rancher/wrangler/v2/pkg/data" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -601,6 +602,9 @@ func TestAdmitPreserveUnknownFields(t *testing.T) { Object: runtime.RawExtension{ Raw: raw, }, + OldObject: runtime.RawExtension{ + Raw: raw, + }, }, } @@ -617,3 +621,274 @@ func TestAdmitPreserveUnknownFields(t *testing.T) { assert.Nil(t, response.Patch) } + +func TestDynamicSchemaDrop(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + request *admission.Request + cluster *v1.Cluster + oldCluster *v1.Cluster + expected []v1.RKEMachinePool + }{ + { + name: "not v2prov cluster", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{}, + }, + { + name: "no schema present on old or new cluster", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + { + name: "matching schema present on old and new cluster", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + { + name: "schema present on old cluster but not new cluster without annotation", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + { + name: "schema present on old cluster but not new cluster with false annotation", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"provisioning.cattle.io/allow-dynamic-schema-drop": "false"}}, + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + { + name: "schema present on old cluster and new cluster with true annotation", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"provisioning.cattle.io/allow-dynamic-schema-drop": "true"}}, + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + { + name: "schema present on old cluster but not new cluster with true annotation", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"provisioning.cattle.io/allow-dynamic-schema-drop": "true"}}, + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + }, + }, + }, + { + name: "new machine pool without schema", + request: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update}}, + cluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + { + Name: "b", + }, + }, + }, + }, + }, + oldCluster: &v1.Cluster{ + Spec: v1.ClusterSpec{ + RKEConfig: &v1.RKEConfig{ + MachinePools: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + }, + }, + }, + }, + expected: []v1.RKEMachinePool{ + { + Name: "a", + DynamicSchemaSpec: "a", + }, + { + Name: "b", + }, + }, + }, + } + + m := ProvisioningClusterMutator{} + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + resp := m.handleDynamicSchemaDrop(tt.request, tt.oldCluster, tt.cluster) + assert.True(t, resp.Allowed) + if tt.expected != nil { + assert.True(t, reflect.DeepEqual(tt.expected, tt.cluster.Spec.RKEConfig.MachinePools)) + } + }) + } +} diff --git a/pkg/resources/provisioning.cattle.io/v1/cluster/validator.go b/pkg/resources/provisioning.cattle.io/v1/cluster/validator.go index d77118875..5b821fe66 100644 --- a/pkg/resources/provisioning.cattle.io/v1/cluster/validator.go +++ b/pkg/resources/provisioning.cattle.io/v1/cluster/validator.go @@ -14,8 +14,8 @@ import ( objectsv1 "github.com/rancher/webhook/pkg/generated/objects/provisioning.cattle.io/v1" psa "github.com/rancher/webhook/pkg/podsecurityadmission" "github.com/rancher/webhook/pkg/resources/common" - corev1controller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" - "github.com/rancher/wrangler/pkg/kv" + corev1controller "github.com/rancher/wrangler/v2/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/v2/pkg/kv" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" authv1 "k8s.io/api/authorization/v1" diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index 8f91f624e..86f0546ff 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -17,6 +17,8 @@ import ( "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/project" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/projectroletemplatebinding" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/roletemplate" + "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/setting" + "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/userattribute" provisioningCluster "github.com/rancher/webhook/pkg/resources/provisioning.cattle.io/v1/cluster" "github.com/rancher/webhook/pkg/resources/rke-machine-config.cattle.io/v1/machineconfig" ) @@ -24,7 +26,7 @@ import ( // Validation returns a list of all ValidatingAdmissionHandlers used by the webhook. func Validation(clients *clients.Clients) ([]admission.ValidatingAdmissionHandler, error) { handlers := []admission.ValidatingAdmissionHandler{ - feature.NewValidator(), + feature.NewValidator(clients.DefaultResolver), managementCluster.NewValidator(clients.K8s.AuthorizationV1().SubjectAccessReviews(), clients.Management.PodSecurityAdmissionConfigurationTemplate().Cache()), provisioningCluster.NewProvisioningClusterValidator(clients), machineconfig.NewValidator(), @@ -44,8 +46,9 @@ func Validation(clients *clients.Clients) ([]admission.ValidatingAdmissionHandle secrets := secret.NewValidator(clients.RBAC.Role().Cache(), clients.RBAC.RoleBinding().Cache()) nodeDriver := nodedriver.NewValidator(clients.Management.Node().Cache(), clients.Dynamic) projects := project.NewValidator(clients.Management.Cluster().Cache()) - - handlers = append(handlers, psact, globalRoles, globalRoleBindings, prtbs, crtbs, roleTemplates, secrets, nodeDriver, projects) + userAttribute := userattribute.NewValidator() + setting := setting.NewValidator(clients.Management.Cluster().Cache()) + handlers = append(handlers, psact, globalRoles, globalRoleBindings, prtbs, crtbs, roleTemplates, secrets, nodeDriver, projects, userAttribute, setting) } return handlers, nil } diff --git a/pkg/server/server.go b/pkg/server/server.go index 5b26d21c6..1859bf82f 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -18,10 +18,9 @@ import ( "github.com/rancher/dynamiclistener" "github.com/rancher/dynamiclistener/server" "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/webhook/pkg/capi" "github.com/rancher/webhook/pkg/clients" "github.com/rancher/webhook/pkg/health" - admissionregistration "github.com/rancher/wrangler/pkg/generated/controllers/admissionregistration.k8s.io/v1" + admissionregistration "github.com/rancher/wrangler/v2/pkg/generated/controllers/admissionregistration.k8s.io/v1" "github.com/sirupsen/logrus" v1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" @@ -63,7 +62,7 @@ var tlsOpt = func(config *tls.Config) { } // ListenAndServe starts the webhook server. -func ListenAndServe(ctx context.Context, cfg *rest.Config, capiEnabled, mcmEnabled bool) error { +func ListenAndServe(ctx context.Context, cfg *rest.Config, mcmEnabled bool) error { clients, err := clients.New(ctx, cfg, mcmEnabled) if err != nil { return fmt.Errorf("failed to create a new client: %w", err) @@ -86,15 +85,6 @@ func ListenAndServe(ctx context.Context, cfg *rest.Config, capiEnabled, mcmEnabl return err } - var capiStart func(context.Context) error - - if capiEnabled { - capiStart, err = capi.Register(clients, tlsOpt) - if err != nil { - return fmt.Errorf("failed to register capi: %w", err) - } - } - if err = listenAndServe(ctx, clients, validators, mutators); err != nil { return err } @@ -103,12 +93,6 @@ func ListenAndServe(ctx context.Context, cfg *rest.Config, capiEnabled, mcmEnabl return fmt.Errorf("failed to start client: %w", err) } - if capiStart != nil { - if err = capiStart(ctx); err != nil { - return fmt.Errorf("failed to start capi: %w", err) - } - } - return nil } @@ -278,9 +262,9 @@ func (s *secretHandler) ensureWebhookConfiguration(validatingConfig *v1.Validati } } - currMutation, err := s.mutatingController.Get(validatingConfig.Name, metav1.GetOptions{}) + currMutation, err := s.mutatingController.Get(mutatingConfig.Name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { - _, err = s.mutatingController.Create(currMutation) + _, err = s.mutatingController.Create(mutatingConfig) if err != nil { return fmt.Errorf("failed to create mutating configuration: %w", err) } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go new file mode 100644 index 000000000..d6a5dc970 --- /dev/null +++ b/pkg/server/server_test.go @@ -0,0 +1,75 @@ +package server + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/rancher/wrangler/v2/pkg/generic/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestSecretHandlerEnsureWebhookConfigurationCreate(t *testing.T) { + configName := "rancher.cattle.io" + + var ( + storedValidatingConfig *v1.ValidatingWebhookConfiguration + storedMutatingConfig *v1.MutatingWebhookConfiguration + ) + + ctrl := gomock.NewController(t) + validatingController := fake.NewMockNonNamespacedClientInterface[*v1.ValidatingWebhookConfiguration, *v1.ValidatingWebhookConfigurationList](ctrl) + validatingController.EXPECT().Get(configName, gomock.Any()).Return(nil, errors.NewNotFound(schema.GroupResource{Group: v1.GroupName, Resource: "validatingwebhookconfiguration"}, configName)).Times(1) + validatingController.EXPECT().Create(gomock.Any()).DoAndReturn(func(obj *v1.ValidatingWebhookConfiguration) (*v1.ValidatingWebhookConfiguration, error) { + storedValidatingConfig = obj.DeepCopy() + return obj, nil + }).Times(1) + + mutatingController := fake.NewMockNonNamespacedClientInterface[*v1.MutatingWebhookConfiguration, *v1.MutatingWebhookConfigurationList](ctrl) + mutatingController.EXPECT().Get(configName, gomock.Any()).Return(nil, errors.NewNotFound(schema.GroupResource{Group: v1.GroupName, Resource: "mutatingwebhookconfiguration"}, configName)).Times(1) + mutatingController.EXPECT().Create(gomock.Any()).DoAndReturn(func(obj *v1.MutatingWebhookConfiguration) (*v1.MutatingWebhookConfiguration, error) { + storedMutatingConfig = obj.DeepCopy() + return obj, nil + }).Times(1) + + handler := &secretHandler{ + validatingController: validatingController, + mutatingController: mutatingController, + } + + validatingConfig := &v1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: configName, + }, + Webhooks: []v1.ValidatingWebhook{ + { + Name: "rancher.cattle.io.features.management.cattle.io", + }, + }, + } + mutatingConfig := &v1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: configName, + }, + Webhooks: []v1.MutatingWebhook{ + { + Name: "rancher.cattle.io.clusters.provisioning.cattle.io", + }, + }, + } + + err := handler.ensureWebhookConfiguration(validatingConfig, mutatingConfig) + require.NoError(t, err) + + require.NotNil(t, storedValidatingConfig) + require.Len(t, storedValidatingConfig.Webhooks, 1) + assert.Equal(t, validatingConfig.Webhooks[0].Name, storedValidatingConfig.Webhooks[0].Name) + + require.NotNil(t, storedMutatingConfig) + require.Len(t, storedMutatingConfig.Webhooks, 1) + assert.Equal(t, mutatingConfig.Webhooks[0].Name, storedMutatingConfig.Webhooks[0].Name) +} diff --git a/scripts/build b/scripts/build index 0e2b0cd27..fd6df0ce2 100755 --- a/scripts/build +++ b/scripts/build @@ -14,5 +14,5 @@ LINKFLAGS="-X main.GitCommit=$COMMIT $LINKFLAGS" CGO_ENABLED=0 go build -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" -o bin/webhook if [ "$CROSS" = "true" ] && [ "$ARCH" = "amd64" ]; then GOOS=darwin go build -ldflags "$LINKFLAGS" -o bin/webhook-darwin - GOOS=windows go build -ldflags "$LINKFLAGS" -o bin/webhook-windows + GOOS=windows go build -ldflags "$LINKFLAGS" -o bin/webhook-windows-amd64.exe fi diff --git a/scripts/ci b/scripts/ci index 51fe8e966..fad2e9e83 100755 --- a/scripts/ci +++ b/scripts/ci @@ -8,4 +8,5 @@ cd $(dirname $0) ./validate ./validate-ci ./package +./package-helm ./test-helm diff --git a/scripts/integration-test b/scripts/integration-test index 43da3e32c..45871f0ec 100755 --- a/scripts/integration-test +++ b/scripts/integration-test @@ -1,89 +1,50 @@ #!/bin/bash -set -e -export KUBECONFIG= -export CATTLE_DEV_MODE=yes -export CATTLE_SERVER_URL="https://$(ip route get 8.8.8.8 | awk '{print $7}'):443" -export CATTLE_BOOTSTRAP_PASSWORD="admin" -export CATTLE_FEATURES="harvester=false" +set -exu cd $(dirname $0)/../ -echo "Starting Rancher Server" -entrypoint.sh >./rancher.log 2>&1 & -RANCHER_PID=$! +source ./.github/workflows/scripts/try.sh -echo "Waiting for Rancher health check..." -while ! curl -sf http://localhost:80/healthz >/dev/null 2>&1; do - echo "Waiting for Rancher's /healthz endpoint to become available" - sleep 2 -done +# Wait for rancher to start up +try --delay 2 --max 30 --waitmsg "Waiting for rancher to start" --failmsg "No rancher here" kubectl rollout status --watch=true --timeout=10s -n cattle-system deploy/rancher +echo "Rancher deployed" -# Tail the rancher logs if rancher fails to deploy the webhook after 5 minutes. -bash -c "sleep 300 && echo 'Rancher has not deployed webhook after 5m tailing logs' && tail -f ./rancher.log" & -# Get PID of the tail command so we can kill it if needed -TAIL_PID=$! +# Wait for the rancher webhook to start up +try --delay 2 --max 30 --waitmsg "Waiting for rancher/webhook to be deployed" --failmsg "No webhook here" kubectl rollout status --watch=true --timeout=10s -n cattle-system deploy/rancher-webhook +echo "Webhook deployed" + +webhook_deployed() { + status=$(kubectl get apps.catalog.cattle.io -n cattle-system rancher-webhook -o jsonpath="{@.status.summary.state}") && [[ "$status" == "deployed" ]] +} # Wait for Rancher to deploy rancher-webhook. -while ! kubectl rollout status -w -n cattle-system deploy/rancher-webhook >/dev/null 2>&1; do - echo "Waiting for rancher to deploy rancher-webhook..." - sleep 2 -done +try --delay 2 --max 30 --waitmsg "Waiting for webhook to be deployed (2)" webhook_deployed echo "Webhook deployed" -# After rancher deploys webhook kill the bash command running tail. -kill ${TAIL_PID} - -# Wait for helm operation to complete and save rancher-webhook release info before we kill rancher and the cluster. -while - status=$(kubectl get apps.catalog.cattle.io -n cattle-system rancher-webhook -o jsonpath="{@.status.summary.state}") - [[ "$status" != "deployed" ]] -do - echo "Waiting for helm operation to finish, current status $status" - sleep 2 -done - -# Kill Rancher since we only need the CRDs and the initial webhook values. -# We do not want Rancher to reconcile an older version of the webhook during test. -kill ${RANCHER_PID} - -echo "Rancher has been stopped starting K3s." -# Start Cluster without Rancher. -k3s server --cluster-init --disable=traefik,servicelb,metrics-server,local-storage --node-name=local-node --log=./k3s.log >/dev/null 2>&1 & -KUBECONFIG=/etc/rancher/k3s/k3s.yaml - -# Wait for cluster to start. -while ! kubectl version >/dev/null 2>&1; do - echo "Waiting for cluster to start" - sleep 5 -done +# Shut down the core rancher part, but leave the rest of the rancher environment running -echo "Uploading new webhook image" +kubectl scale deploy rancher -n cattle-system --replicas=0 --timeout=10m +kubectl wait pods -l app=rancher-webhook --for=delete --namespace cattle-system --timeout=10m -###### Upload the newly created webhook image to containerd, then install the webhook chart using the new image -IMAGE_FILE=./dist/rancher-webhook-image.tar -# import image to containerd and get the image name -WEBHOOK_REPO=$(ctr image import ${IMAGE_FILE} | cut -d ' ' -f 2 | cut -d ':' -f 1) +echo "Rancher has been stopped." + +echo "Uploading new webhook image" # Source tags file to get the last built tags source ./dist/tags # Install the webhook chart we just built. -# This command can fail since it is so close to the cluster start so we will give it 3 retires. -RETRIES=0 -while ! helm upgrade rancher-webhook ./dist/artifacts/rancher-webhook-${HELM_VERSION}.tgz -n cattle-system \ - --wait --set image.repository=${WEBHOOK_REPO} --set image.tag=${TAG} --reuse-values; do - if [ "$RETRIES" -ge 3 ]; then - exit 1 - fi - RETRIES=$((RETRIES + 1)) - sleep 2 -done +upgrade_rancher_webhook() { + helm upgrade rancher-webhook ./dist/artifacts/rancher-webhook-${HELM_VERSION}.tgz -n cattle-system \ + --wait --set image.repository="${IMAGE_REPO}" --set image.tag="${IMAGE_TAG}" --reuse-values --debug +} +try --delay 2 --max 4 --failmsg "Couldn't helm upgrade rancher-webhook" upgrade_rancher_webhook ./bin/rancher-webhook-integration.test -test.v -test.run IntegrationTest # Install the webhook chart with new ports. helm upgrade rancher-webhook ./dist/artifacts/rancher-webhook-${HELM_VERSION}.tgz -n cattle-system \ - --wait --reuse-values --set port=443 --set capi.port=2319 + --wait --reuse-values --set port=443 # Test that the ports are set as expected and run a single integration test to verify the webhook is still accessible. ./bin/rancher-webhook-integration.test -test.v -test.run PortTest @@ -92,4 +53,5 @@ helm upgrade rancher-webhook ./dist/artifacts/rancher-webhook-${HELM_VERSION}.tg # Scale down rancher-webhook so that we can run tests on the FailurePolicy. kubectl scale deploy rancher-webhook -n cattle-system --replicas=0 kubectl wait pods -l app=rancher-webhook --for=delete -n cattle-system + ./bin/rancher-webhook-integration.test -test.v -test.run FailurePolicyTest diff --git a/scripts/package b/scripts/package index 4f2ca314c..6ba0f840c 100755 --- a/scripts/package +++ b/scripts/package @@ -1,40 +1,37 @@ #!/bin/bash -set -e +set -eu source $(dirname $0)/version cd $(dirname $0)/.. -function build-image() { - IMAGE=${REPO}/${1}:${TAG} - DOCKERFILE=package/Dockerfile${2} - if [ -e ${DOCKERFILE}.${ARCH} ]; then - DOCKERFILE=${DOCKERFILE}.${ARCH} - fi - - docker build -f ${DOCKERFILE} -t ${IMAGE} . - echo Built ${IMAGE} - - docker save -o dist/rancher-webhook-image.tar ${IMAGE} - - if [ "${PUSH}" = "true" ]; then - docker push ${IMAGE} - fi - -} +echo Running package mkdir -p dist/artifacts cp bin/webhook dist/artifacts/webhook-linux${SUFFIX} for i in bin/webhook-*; do if [ -e "$i" ]; then - if [ "$i" = webhook-windows-amd64 ]; then - cp $i dist/artifacts/webhook-windows-amd64.exe - else - cp $i dist/artifacts - fi + cp $i dist/artifacts fi done +REPO=rancher + +IMAGE=${REPO}/webhook:${TAG} +DOCKERFILE=./package/Dockerfile +if [ -e ${DOCKERFILE}.${ARCH} ]; then + DOCKERFILE=${DOCKERFILE}.${ARCH} +fi + +if [[ ${USE_DOCKER_BUILDX:-0} -eq 1 ]]; then + docker buildx build --platform linux/amd64 -f ${DOCKERFILE} . -t ${IMAGE} +else + docker build -f ${DOCKERFILE} -t ${IMAGE} . +fi +echo Built ${IMAGE} -build-image rancher-webhook +docker save -o dist/rancher-webhook-image.tar ${IMAGE} +echo IMAGE_TAG="${TAG}" > dist/image_tag -./scripts/package-helm +if [ "${PUSH:-}" = "true" ]; then + docker push ${IMAGE} +fi diff --git a/scripts/package-helm b/scripts/package-helm index 45d5c4e2f..3f3a94b90 100755 --- a/scripts/package-helm +++ b/scripts/package-helm @@ -1,27 +1,25 @@ #!/bin/bash -set -e - -if ! hash helm 2>/dev/null; then - exit 0 -fi +set -eu cd $(dirname $0)/.. . ./scripts/version +echo Running package-helm + rm -rf build/charts mkdir -p build dist/artifacts cp -rf charts build/ # must use sed -i''` for GNU and OSX compatibility sed -i'.bkp' \ - -e 's/^version:.*/version: '${HELM_VERSION}'/' \ - -e 's/appVersion:.*/appVersion: '${HELM_VERSION}'/' \ + -e 's/^version:.*/version: '${HELM_CHART_VERSION}'/' \ + -e 's/appVersion:.*/appVersion: '${HELM_CHART_VERSION}'/' \ build/charts/rancher-webhook/Chart.yaml sed -i'.bkb' \ -e 's/tag: latest/tag: '${HELM_TAG}'/' \ build/charts/rancher-webhook/values.yaml -rm build/charts/rancher-webhook/Chart.yaml.bkp build/charts/rancher-webhook/values.yaml.bkb +rm -f build/charts/rancher-webhook/Chart.yaml.bkp build/charts/rancher-webhook/values.yaml.bkb helm package -d ./dist/artifacts ./build/charts/rancher-webhook diff --git a/scripts/test-helm b/scripts/test-helm index a1e95d1e6..d5d61b696 100755 --- a/scripts/test-helm +++ b/scripts/test-helm @@ -2,7 +2,6 @@ set -e cd $(dirname $0)/.. -./scripts/package-helm echo Running helm lint helm lint ./charts/rancher-webhook # Check for unittest plugin diff --git a/scripts/version b/scripts/version index 1c84b8da7..3d25739f4 100755 --- a/scripts/version +++ b/scripts/version @@ -1,11 +1,12 @@ #!/bin/bash +DIRTY= if [ -n "$(git status --porcelain --untracked-files=no)" ]; then DIRTY="-dirty" fi -COMMIT=$(git rev-parse --short HEAD) -GIT_TAG=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)} +COMMIT=${COMMIT:-$(git rev-parse --short HEAD)} +GIT_TAG=${GIT_TAG:-$(git tag -l --contains HEAD | head -n 1)} if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then VERSION=$GIT_TAG @@ -13,23 +14,23 @@ else VERSION="0.0.0-${COMMIT}${DIRTY}" fi -if [ -z "$ARCH" ]; then +if [ -z "${ARCH:-}" ] ; then ARCH=$(go env GOHOSTARCH) fi SUFFIX="-${ARCH}" -HELM_TAG=${TAG:-${VERSION}} -HELM_VERSION=${HELM_TAG/v/} -TAG=${TAG:-${VERSION}${SUFFIX}} -REPO=${REPO:-rancher} +HELM_TAG="${HELM_TAG:-${TAG:-${VERSION}}}" +HELM_CHART_VERSION="${HELM_CHART_VERSION:-${HELM_TAG/v/}}" +TAG="${TAG:-${VERSION}${SUFFIX}}" +REPO="${REPO:-rancher}" if echo $TAG | grep -q dirty; then TAG=dev HELM_TAG=dev - HELM_VERSION=0.0.0-dev + HELM_CHART_VERSION=0.0.0-dev fi -DIST_DIR="$(dirname $0)/../dist/" +DIST_DIR="${DIST_DIR:-$(dirname $0)/../dist/}" mkdir -p ${DIST_DIR} -echo "export TAG=${TAG}; export HELM_TAG=${HELM_TAG}; export HELM_VERSION=${HELM_VERSION};" >${DIST_DIR}/tags +echo "export TAG=${TAG}; export HELM_TAG=${HELM_TAG}; export HELM_CHART_VERSION=${HELM_CHART_VERSION};" >${DIST_DIR}/tags diff --git a/tests/integration/failPolicy_test.go b/tests/integration/failPolicy_test.go index 75121c6f8..4f8ed2ae4 100644 --- a/tests/integration/failPolicy_test.go +++ b/tests/integration/failPolicy_test.go @@ -8,10 +8,11 @@ import ( "github.com/rancher/lasso/pkg/client" v3 "github.com/rancher/rancher/pkg/apis/cluster.cattle.io/v3" + mgmtv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" provisioningv1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" - "github.com/rancher/wrangler/pkg/gvk" - "github.com/rancher/wrangler/pkg/kubeconfig" - "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/v2/pkg/gvk" + "github.com/rancher/wrangler/v2/pkg/kubeconfig" + "github.com/rancher/wrangler/v2/pkg/schemes" "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" corev1 "k8s.io/api/core/v1" @@ -43,21 +44,11 @@ func (m *FailurePolicySuite) SetupSuite() { schemes.Register(provisioningv1.AddToScheme) schemes.Register(corev1.AddToScheme) } +func (m *FailurePolicySuite) SetupTest() { + m.checkWebhookIsDown() +} -func (m *FailurePolicySuite) TestNamespaceFail() { - const testNamespace = "test-namespace" - validCreateObj := &corev1.Namespace{ - ObjectMeta: v1.ObjectMeta{ - Name: testNamespace, - }, - } - - objGVK, err := gvk.Get(validCreateObj) - m.Require().NoError(err, "failed to get GVK") - - client, err := m.clientFactory.ForKind(objGVK) - m.Require().NoError(err, "Failed to create client") - +func (m *FailurePolicySuite) checkWebhookIsDown() { podGVK := schema.GroupVersionKind{ Group: "", Version: "v1", @@ -72,14 +63,29 @@ func (m *FailurePolicySuite) TestNamespaceFail() { pods := corev1.PodList{} podClient.List(context.Background(), "cattle-system", &pods, listOpts) m.Require().Equal(0, len(pods.Items), "Test can not run while rancher-webhook pods are still running") +} + +func (m *FailurePolicySuite) TestNamespaceFail() { + const name = "test-namespace" + validCreateObj := &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + }, + } + + objGVK, err := gvk.Get(validCreateObj) + m.Require().NoError(err, "failed to get GVK") + + client, err := m.clientFactory.ForKind(objGVK) + m.Require().NoError(err, "Failed to create client") ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel() err = client.Create(ctx, "", validCreateObj, nil, v1.CreateOptions{}) m.Require().True(errors.IsInternalError(err), "Webhook should fail with service unavailable when the webhook is down instead we received :%v", err) - // attempt to clean up namespace if the create went through - defer client.Delete(ctx, "", testNamespace, v1.DeleteOptions{}) + // Attempt to clean up the namespace if the create went through. + defer client.Delete(ctx, "", name, v1.DeleteOptions{}) validCreateObj.Name = "default" err = client.Update(ctx, "", validCreateObj, nil, v1.UpdateOptions{}) @@ -92,3 +98,33 @@ func (m *FailurePolicySuite) TestNamespaceFail() { err = client.Update(ctx, "", validCreateObj, nil, v1.UpdateOptions{}) m.Require().True(errors.IsInternalError(err), "Webhook should fail to update kube-system namespace with service unavailable when the webhook is down instead we received :%v", err) } + +func (m *FailurePolicySuite) TestSettingSucceed() { + const name = "new-setting" + createObj := &mgmtv3.Setting{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + }, + } + objGVK, err := gvk.Get(createObj) + m.Require().NoError(err, "failed to get GVK") + + client, err := m.clientFactory.ForKind(objGVK) + m.Require().NoError(err, "Failed to create client") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + err = client.Create(ctx, "", createObj, nil, v1.CreateOptions{}) + m.Require().NoError(err) + + // Attempt to clean up the setting if the create went through. + defer client.Delete(ctx, "", name, v1.DeleteOptions{}) + + updateObj := &mgmtv3.Setting{} + err = client.Get(ctx, "", name, updateObj, v1.GetOptions{}) + m.Require().NoError(err) + updateObj.Value = "new-value" + + err = client.Update(ctx, "", updateObj, updateObj, v1.UpdateOptions{}) + m.Require().NoError(err) +} diff --git a/tests/integration/globalRole_test.go b/tests/integration/globalRole_test.go index 04698b7e1..73dc8bfb7 100644 --- a/tests/integration/globalRole_test.go +++ b/tests/integration/globalRole_test.go @@ -52,3 +52,69 @@ func (m *IntegrationSuite) TestGlobalRole() { } validateEndpoints(m.T(), endPoints, m.clientFactory) } + +func (m *IntegrationSuite) TestGlobalRoleNoResources() { + newObj := func() *v3.GlobalRole { return &v3.GlobalRole{} } + validCreateObj := &v3.GlobalRole{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-globalrole-no-resources", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"GET", "WATCH"}, + APIGroups: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + } + invalidCreate := func() *v3.GlobalRole { + invalidCreate := validCreateObj.DeepCopy() + if len(invalidCreate.Rules) != 0 { + invalidCreate.Rules[0].Resources = nil + } + return invalidCreate + } + validDelete := func() *v3.GlobalRole { + return validCreateObj + } + endPoints := &endPointObjs[*v3.GlobalRole]{ + invalidCreate: invalidCreate, + newObj: newObj, + validCreateObj: validCreateObj, + validDelete: validDelete, + } + validateEndpoints(m.T(), endPoints, m.clientFactory) +} + +func (m *IntegrationSuite) TestGlobalRoleNoAPIGroups() { + newObj := func() *v3.GlobalRole { return &v3.GlobalRole{} } + validCreateObj := &v3.GlobalRole{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-globalrole-no-apigroups", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"GET", "WATCH"}, + APIGroups: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + } + invalidCreate := func() *v3.GlobalRole { + invalidCreate := validCreateObj.DeepCopy() + if len(invalidCreate.Rules) != 0 { + invalidCreate.Rules[0].APIGroups = nil + } + return invalidCreate + } + validDelete := func() *v3.GlobalRole { + return validCreateObj + } + endPoints := &endPointObjs[*v3.GlobalRole]{ + invalidCreate: invalidCreate, + newObj: newObj, + validCreateObj: validCreateObj, + validDelete: validDelete, + } + validateEndpoints(m.T(), endPoints, m.clientFactory) +} diff --git a/tests/integration/main_test.go b/tests/integration/main_test.go index a31703ae0..494637166 100644 --- a/tests/integration/main_test.go +++ b/tests/integration/main_test.go @@ -12,9 +12,9 @@ import ( "github.com/rancher/lasso/pkg/client" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" provisioningv1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" - "github.com/rancher/wrangler/pkg/gvk" - "github.com/rancher/wrangler/pkg/kubeconfig" - "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/v2/pkg/gvk" + "github.com/rancher/wrangler/v2/pkg/kubeconfig" + "github.com/rancher/wrangler/v2/pkg/schemes" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/tests/integration/port_test.go b/tests/integration/port_test.go index c012907de..c69e3556e 100644 --- a/tests/integration/port_test.go +++ b/tests/integration/port_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/rancher/lasso/pkg/client" - "github.com/rancher/wrangler/pkg/kubeconfig" - "github.com/rancher/wrangler/pkg/schemes" + "github.com/rancher/wrangler/v2/pkg/kubeconfig" + "github.com/rancher/wrangler/v2/pkg/schemes" "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" corev1 "k8s.io/api/core/v1" @@ -17,7 +17,6 @@ import ( const ( testWebhookPort = 443 - testCapiPort = 2319 ) type PortSuite struct { @@ -69,13 +68,9 @@ func (m *PortSuite) TestWebhookPortChanged() { } m.Require().Equal(corev1.PodRunning, webhookPod.Status.Phase, "Rancher-webhook pod is not running Phase=%s", webhookPod.Status.Phase) m.Require().Len(webhookPod.Spec.Containers, 1, "Rancher-webhook pod has the incorrect number of containers") - m.Require().Len(webhookPod.Spec.Containers[0].Ports, 2, "Rancher-webhook container has the incorrect number of ports") - havePort1 := webhookPod.Spec.Containers[0].Ports[0].ContainerPort - havePort2 := webhookPod.Spec.Containers[0].Ports[1].ContainerPort - if havePort1 != testWebhookPort && havePort2 != testWebhookPort { - m.Require().FailNowf("expected webhook port not found", "wanted '%d' was not found instead have '%d' and '%d'", testWebhookPort, havePort1, havePort2) - } - if havePort1 != testCapiPort && havePort2 != testCapiPort { - m.Require().FailNowf("expected capi port not found", "wanted '%d' was not found instead have '%d' and '%d'", testCapiPort, havePort1, havePort2) + m.Require().Len(webhookPod.Spec.Containers[0].Ports, 1, "Rancher-webhook container has the incorrect number of ports") + havePort := webhookPod.Spec.Containers[0].Ports[0].ContainerPort + if havePort != testWebhookPort { + m.Require().FailNowf("expected webhook port not found", "wanted '%d' was not found instead have '%d'", testWebhookPort, havePort) } } diff --git a/tests/integration/rkeMachineConfig_test.go b/tests/integration/rkeMachineConfig_test.go index c3a2feb5a..47a6a4921 100644 --- a/tests/integration/rkeMachineConfig_test.go +++ b/tests/integration/rkeMachineConfig_test.go @@ -1,12 +1,20 @@ package integration_test import ( + "os" + "runtime" + "github.com/rancher/webhook/pkg/auth" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) func (m *IntegrationSuite) TestRKEMachineConfig() { + if runtime.GOARCH == "arm64" && os.Getenv("CI") != "" { + // Temporarily workaround https://github.com/rancher/rancher/issues/45837 : + // Not all CRDs are built in GHA/arm64 + m.T().Skip("Skipping the RKE Machine-Config test on arm64 in CI -- machine info not available") + } objGVK := schema.GroupVersionKind{ Group: "rke-machine-config.cattle.io", Version: "v1", @@ -17,7 +25,7 @@ func (m *IntegrationSuite) TestRKEMachineConfig() { validCreateObj.SetName("test-rke.machine") validCreateObj.SetNamespace(testNamespace) validCreateObj.SetGroupVersionKind(objGVK) - invalidUpdate := func(created *unstructured.Unstructured) *unstructured.Unstructured { + invalidUpdate := func(_ *unstructured.Unstructured) *unstructured.Unstructured { invalidUpdateObj := validCreateObj.DeepCopy() invalidUpdateObj.SetAnnotations(map[string]string{auth.CreatorIDAnn: "foobar"}) return invalidUpdateObj diff --git a/tests/integration/roleTemplate_test.go b/tests/integration/roleTemplate_test.go index e4897848a..753cd6459 100644 --- a/tests/integration/roleTemplate_test.go +++ b/tests/integration/roleTemplate_test.go @@ -52,3 +52,69 @@ func (m *IntegrationSuite) TestRoleTemplate() { } validateEndpoints(m.T(), endPoints, m.clientFactory) } + +func (m *IntegrationSuite) TestRoleTemplateNoResources() { + newObj := func() *v3.RoleTemplate { return &v3.RoleTemplate{} } + validCreateObj := &v3.RoleTemplate{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-roletemplate-no-resources", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"GET", "WATCH"}, + APIGroups: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + } + invalidCreate := func() *v3.RoleTemplate { + invalidCreate := validCreateObj.DeepCopy() + if len(invalidCreate.Rules) != 0 { + invalidCreate.Rules[0].Resources = nil + } + return invalidCreate + } + validDelete := func() *v3.RoleTemplate { + return validCreateObj + } + endPoints := &endPointObjs[*v3.RoleTemplate]{ + invalidCreate: invalidCreate, + newObj: newObj, + validCreateObj: validCreateObj, + validDelete: validDelete, + } + validateEndpoints(m.T(), endPoints, m.clientFactory) +} + +func (m *IntegrationSuite) TestRoleTemplateNoAPIGroups() { + newObj := func() *v3.RoleTemplate { return &v3.RoleTemplate{} } + validCreateObj := &v3.RoleTemplate{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-roletemplate-no-apigroups", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"GET", "WATCH"}, + APIGroups: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + } + invalidCreate := func() *v3.RoleTemplate { + invalidCreate := validCreateObj.DeepCopy() + if len(invalidCreate.Rules) != 0 { + invalidCreate.Rules[0].APIGroups = nil + } + return invalidCreate + } + validDelete := func() *v3.RoleTemplate { + return validCreateObj + } + endPoints := &endPointObjs[*v3.RoleTemplate]{ + invalidCreate: invalidCreate, + newObj: newObj, + validCreateObj: validCreateObj, + validDelete: validDelete, + } + validateEndpoints(m.T(), endPoints, m.clientFactory) +}