├── .dockerignore ├── deploy └── cert-manager-webhook-hetzner │ ├── templates │ ├── NOTES.txt │ ├── service.yaml │ ├── apiservice.yaml │ ├── _helpers.tpl │ ├── pki.yaml │ ├── deployment.yaml │ └── rbac.yaml │ ├── .helmignore │ ├── Chart.yaml │ └── values.yaml ├── .github ├── linters │ ├── .markdown-lint.yml │ └── ct.yaml └── workflows │ ├── sync-readme.yaml │ ├── golangci-lint.yml │ ├── superlinter.yml │ ├── release.yaml │ ├── lint-test.yaml │ └── build.yml ├── testdata └── hetzner │ ├── README.md │ ├── config.json │ └── hetzner-secret.yml ├── scripts └── fetch-test-binaries.sh ├── .gitignore ├── main_test.go ├── Dockerfile ├── Makefile ├── internal └── hetzner.go ├── .golangci.yml ├── README.md ├── go.mod ├── main.go ├── LICENSE └── go.sum /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !go.* 3 | !*.go 4 | !internal 5 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | MD013: 2 | line_length: 600 3 | -------------------------------------------------------------------------------- /testdata/hetzner/README.md: -------------------------------------------------------------------------------- 1 | # Solver testdata directory 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /testdata/hetzner/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "secretName": "hetzner-secret", 3 | "zoneName": "example.com", 4 | "apiUrl" : "https://dns.hetzner.com/api/v1" 5 | } 6 | -------------------------------------------------------------------------------- /testdata/hetzner/hetzner-secret.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: hetzner-secret 5 | data: 6 | api-key: cmVwbGFjZS13aXRoLWFwaS1rZXk= -------------------------------------------------------------------------------- /scripts/fetch-test-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | curl https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-1.15.5-linux-amd64.tar.gz -o kubebuilder-tools.tar.gz 3 | tar -zvxf kubebuilder-tools.tar.gz 4 | 5 | rm kubebuilder-tools.tar.gz 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | kubebuilder 3 | .vscode/ 4 | .idea/ 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, build with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Ignore the built binary 19 | ./cert-manager-webhook-hetzner 20 | -------------------------------------------------------------------------------- /.github/linters/ct.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/helm/chart-testing#configuration 2 | remote: origin 3 | target-branch: master 4 | chart-dirs: 5 | - deploy 6 | chart-repos: 7 | - cert-manager-webhook-hetzner=https://vadimkim.github.io/cert-manager-webhook-hetzner 8 | helm-extra-args: --timeout 600s 9 | excluded-charts: 10 | additional-commands: 11 | # - helm unittest --helm3 --strict --file unittests/*.yaml --file 'unittests/**/*.yaml' {{ .Path }} 12 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: cert-manager-webhook-hetzner 3 | version: 1.4.0 4 | appVersion: "1.4.0" 5 | kubeVersion: ">= 1.22.0-0" 6 | description: Allow cert-manager to solve DNS challenges using Hetzner DNS API 7 | home: https://github.com/vadimkim/cert-manager-webhook-hetzner 8 | icon: https://raw.githubusercontent.com/cert-manager/cert-manager/master/logo/logo-small.png 9 | keywords: 10 | - cert-manager 11 | - hetzner 12 | - kube-lego 13 | - letsencrypt 14 | - tls 15 | sources: 16 | - https://github.com/vadimkim/cert-manager-webhook-hetzner 17 | maintainers: 18 | - name: vadimkim 19 | email: vadim@ant.ee 20 | annotations: 21 | artifacthub.io/license: Apache-2.0 22 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 8 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | type: {{ .Values.service.type }} 13 | ports: 14 | - port: {{ .Values.service.port }} 15 | targetPort: https 16 | protocol: TCP 17 | name: https 18 | selector: 19 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 20 | release: {{ .Release.Name }} 21 | -------------------------------------------------------------------------------- /.github/workflows/sync-readme.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - 'master' 5 | paths: 6 | - 'README.md' 7 | 8 | jobs: 9 | build: 10 | permissions: 11 | contents: write # for git push 12 | 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: | 17 | cp -f README.md ${{ runner.temp }}/README.md 18 | - uses: actions/checkout@v4 19 | with: 20 | ref: gh-pages 21 | - run: | 22 | cp -f ${{ runner.temp }}/README.md . 23 | git config user.name "$GITHUB_ACTOR" 24 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 25 | git add README.md 26 | git commit --signoff -m "Sync README from master" 27 | git push 28 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/apiservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiregistration.k8s.io/v1 2 | kind: APIService 3 | metadata: 4 | name: v1alpha1.{{ .Values.groupName }} 5 | labels: 6 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 7 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | annotations: 11 | cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ include "cert-manager-webhook-hetzner.servingCertificate" . }}" 12 | spec: 13 | group: {{ .Values.groupName }} 14 | groupPriorityMinimum: 1000 15 | versionPriority: 15 16 | service: 17 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 18 | namespace: {{ .Release.Namespace }} 19 | version: v1alpha1 20 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "os" 6 | "testing" 7 | 8 | dns "github.com/cert-manager/cert-manager/test/acme" 9 | ) 10 | 11 | var ( 12 | zone = os.Getenv("TEST_ZONE_NAME") 13 | fqdn string 14 | ) 15 | 16 | func TestRunsSuite(t *testing.T) { 17 | // The manifest path should contain a file named config.json that is a 18 | // snippet of valid configuration that should be included on the 19 | // ChallengeRequest passed as part of the test cases. 20 | 21 | fqdn = GetRandomString(20) + "." + zone 22 | 23 | fixture := dns.NewFixture(&hetznerDNSProviderSolver{}, 24 | dns.SetResolvedZone(zone), 25 | dns.SetResolvedFQDN(fqdn), 26 | dns.SetAllowAmbientCredentials(false), 27 | dns.SetManifestPath("testdata/hetzner"), 28 | // dns.SetBinariesPath("kubebuilder/bin"), 29 | ) 30 | 31 | fixture.RunConformance(t) 32 | } 33 | 34 | func GetRandomString(n int) string { 35 | letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 36 | 37 | b := make([]rune, n) 38 | for i := range b { 39 | b[i] = letters[rand.Intn(len(letters))] 40 | } 41 | return string(b) 42 | } 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ---- Build dependencies ---- 2 | FROM golang:1.25-alpine3.22 AS build_deps 3 | ARG TARGETARCH 4 | 5 | RUN apk add --no-cache git=2.49.1-r0 6 | 7 | WORKDIR /workspace 8 | ENV GO111MODULE=on 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # ---- Build stage ---- 16 | FROM build_deps AS build 17 | 18 | COPY . . 19 | 20 | RUN CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o webhook -ldflags '-w -extldflags "-static"' . 21 | 22 | # ---- Final runtime image ---- 23 | FROM alpine:3.22 24 | LABEL maintainer="vadimkim " 25 | LABEL org.opencontainers.image.source="https://github.com/vadimkim/cert-manager-webhook-hetzner" 26 | 27 | # Install minimal runtime 28 | RUN apk add --no-cache ca-certificates \ 29 | && adduser -D -u 1000 appuser 30 | USER appuser 31 | 32 | COPY --from=build /workspace/webhook /usr/local/bin/webhook 33 | 34 | ENTRYPOINT ["webhook"] 35 | 36 | # Add healthcheck (adjust endpoint/port if needed) 37 | HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ 38 | CMD wget --no-verbose --tries=1 --spider http://localhost:8080/healthz || exit 1 39 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | paths: 8 | - "**.go" 9 | - ".golangci.yml" 10 | - "go.mod" 11 | - "go.sum" 12 | 13 | permissions: 14 | contents: read 15 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 16 | # pull-requests: read 17 | 18 | jobs: 19 | golangci: 20 | name: lint 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v6 24 | with: 25 | persist-credentials: false 26 | - uses: actions/setup-go@v6 27 | with: 28 | go-version: stable 29 | cache: false 30 | - name: golangci-lint 31 | uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 32 | with: 33 | # Require: The version of golangci-lint to use. 34 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 35 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 36 | version: latest 37 | args: --timeout=15m 38 | -------------------------------------------------------------------------------- /.github/workflows/superlinter.yml: -------------------------------------------------------------------------------- 1 | name: Lint Code Base 2 | 3 | # Documentation: 4 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - "deploy/**" 10 | - ".github/**" 11 | 12 | jobs: 13 | build: 14 | name: Lint Code Base 15 | runs-on: ubuntu-latest 16 | 17 | ############################################ 18 | # Grant status permission for MULTI_STATUS # 19 | ############################################ 20 | permissions: 21 | contents: write 22 | packages: read 23 | statuses: write 24 | 25 | steps: 26 | - name: Checkout Code 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | persist-credentials: false 31 | 32 | - name: Lint Code Base 33 | uses: docker://ghcr.io/super-linter/super-linter:slim-v8.1.0 34 | with: 35 | args: --timeout=30m 36 | env: 37 | DEFAULT_BRANCH: master 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | LINTER_RULES_PATH: .github/linters 40 | VALIDATE_ALL_CODEBASE: false 41 | VALIDATE_BASH: false 42 | VALIDATE_JSCPD: false 43 | VALIDATE_CHECKOV: false 44 | VALIDATE_KUBERNETES_KUBECONFORM: false 45 | VALIDATE_PYTHON: false 46 | VALIDATE_PYTHON_FLAKE8: false 47 | VALIDATE_PYTHON_BLACK: false 48 | VALIDATE_YAML: false 49 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/values.yaml: -------------------------------------------------------------------------------- 1 | # The kubernetes api group under which the webhook will be exposed. There is no need to 2 | # modify this value unless you are facing a collision in your api services. 3 | groupName: hetzner.cert-mananger-webhook.noshoes.xyz 4 | 5 | certManager: 6 | namespace: cert-manager 7 | serviceAccountName: cert-manager 8 | 9 | image: 10 | registry: ghcr.io 11 | repository: vadimkim/cert-manager-webhook-hetzner 12 | # Overrides the image tag whose default is {{ printf "v%s" .Chart.AppVersion }} 13 | tag: "" 14 | pullPolicy: IfNotPresent 15 | 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | service: 20 | type: ClusterIP 21 | port: 443 22 | 23 | secretName: 24 | - hetzner-secret 25 | 26 | resources: {} 27 | # We usually recommend not to specify default resources and to leave this as a conscious 28 | # choice for the user. This also increases chances charts run on environments with little 29 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 30 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 31 | # limits: 32 | # cpu: 100m 33 | # memory: 128Mi 34 | # requests: 35 | # cpu: 100m 36 | # memory: 128Mi 37 | 38 | nodeSelector: {} 39 | 40 | tolerations: [] 41 | 42 | affinity: {} 43 | 44 | # Use these variables to configure the HTTP_PROXY environment variables 45 | # http_proxy: "http://proxy:8080" 46 | # https_proxy: "https://proxy:8080" 47 | # no_proxy: 127.0.0.1,localhost 48 | 49 | securityContext: 50 | allowPrivilegeEscalation: false 51 | capabilities: 52 | drop: ["ALL"] 53 | 54 | podSecurityContext: 55 | runAsGroup: 1000 56 | runAsUser: 1000 57 | runAsNonRoot: true 58 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release-charts 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | permissions: 11 | contents: write # to push chart release and create a release (helm/chart-releaser-action) 12 | packages: write # needed for ghcr access 13 | id-token: write # needed for keyless signing 14 | 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Code 18 | uses: actions/checkout@v4 19 | 20 | - name: Fetch history 21 | run: git fetch --prune --unshallow 22 | 23 | - name: Configure Git 24 | run: | 25 | git config user.name "$GITHUB_ACTOR" 26 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 27 | 28 | - name: Set up Helm 29 | uses: azure/setup-helm@v3.5 30 | with: 31 | version: v3.12.0 32 | 33 | - name: Run chart-releaser 34 | uses: helm/chart-releaser-action@v1.6.0 35 | with: 36 | charts_dir: deploy 37 | env: 38 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 39 | CR_GENERATE_RELEASE_NOTES: true 40 | 41 | # see https://github.com/helm/chart-releaser/issues/183 42 | - name: Login to GitHub Container Registry 43 | uses: docker/login-action@v3 44 | with: 45 | registry: ghcr.io 46 | username: ${{ github.actor }} 47 | password: ${{ secrets.GITHUB_TOKEN }} 48 | 49 | - name: Push charts to GHCR 50 | run: | 51 | shopt -s nullglob 52 | for pkg in .cr-release-packages/*; do 53 | if [ -z "${pkg:-}" ]; then 54 | break 55 | fi 56 | helm push "${pkg}" "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts" 57 | done 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= $(shell which go) 2 | OS ?= $(shell $(GO) env GOOS) 3 | ARCH ?= $(shell $(GO) env GOARCH) 4 | 5 | IMAGE_NAME := "cert-manager-webhook-hetzner" 6 | IMAGE_TAG := "latest" 7 | 8 | OUT := $(shell pwd)/deploy 9 | 10 | $(shell mkdir -p "$(OUT)") 11 | 12 | KUBEBUILDER_VERSION=1.28.0 13 | 14 | test: _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/etcd _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kube-apiserver _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kubectl 15 | TEST_ASSET_ETCD=_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/etcd \ 16 | TEST_ASSET_KUBE_APISERVER=_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kube-apiserver \ 17 | TEST_ASSET_KUBECTL=_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kubectl \ 18 | $(GO) test -v . 19 | 20 | _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH).tar.gz: | _test 21 | curl -fsSL https://go.kubebuilder.io/test-tools/$(KUBEBUILDER_VERSION)/$(OS)/$(ARCH) -o $@ 22 | 23 | _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/etcd _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kube-apiserver _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kubectl: _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH).tar.gz | _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH) 24 | tar xfO $< kubebuilder/bin/$(notdir $@) > $@ && chmod +x $@ 25 | 26 | .PHONY: clean 27 | clean: 28 | rm -r _test 29 | 30 | build: 31 | docker build -t "$(IMAGE_NAME):$(IMAGE_TAG)" . 32 | 33 | .PHONY: rendered-manifest.yaml 34 | rendered-manifest.yaml: 35 | helm template \ 36 | cert-manager-webhook-hetzner \ 37 | --set image.repository=$(IMAGE_NAME) \ 38 | --set image.tag=$(IMAGE_TAG) \ 39 | --namespace cert-manager \ 40 | deploy/cert-manager-webhook-oci > "$(OUT)/rendered-manifest.yaml" 41 | 42 | _test $(OUT) _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH): 43 | mkdir -p $@ 44 | -------------------------------------------------------------------------------- /.github/workflows/lint-test.yaml: -------------------------------------------------------------------------------- 1 | name: Lint and Test Charts 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "deploy/**" 7 | - ".github/**" 8 | 9 | jobs: 10 | lint-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set up Helm 19 | uses: azure/setup-helm@v3 20 | with: 21 | version: v3.12.1 22 | 23 | - uses: actions/setup-python@v5 24 | with: 25 | python-version: "3.10" 26 | 27 | - name: Set up chart-testing 28 | uses: helm/chart-testing-action@v2.6.1 29 | 30 | - name: Run chart-testing (list-changed) 31 | id: list-changed 32 | run: | 33 | changed=$(ct list-changed --config .github/linters/ct.yaml) 34 | if [[ -n "$changed" ]]; then 35 | echo "changed=true" >> "$GITHUB_OUTPUT" 36 | fi 37 | 38 | - name: Run chart-testing (lint) 39 | if: steps.list-changed.outputs.changed == 'true' 40 | run: ct lint --config .github/linters/ct.yaml 41 | 42 | - name: Create kind cluster 43 | if: steps.list-changed.outputs.changed == 'true' 44 | uses: helm/kind-action@v1 45 | 46 | - name: Install cert-manager 47 | if: steps.list-changed.outputs.changed == 'true' 48 | run: | 49 | kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.16.2/cert-manager.yaml 50 | kubectl wait --timeout=5m --for=condition=available deployment cert-manager -n cert-manager 51 | kubectl wait --timeout=5m --for=condition=available deployment cert-manager-webhook -n cert-manager 52 | 53 | - name: Run chart-testing (install) 54 | if: steps.list-changed.outputs.changed == 'true' 55 | run: ct install --config .github/linters/ct.yaml 56 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "cert-manager-webhook-hetzner.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "cert-manager-webhook-hetzner.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "cert-manager-webhook-hetzner.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{- define "cert-manager-webhook-hetzner.selfSignedIssuer" -}} 35 | {{ printf "%s-selfsign" (include "cert-manager-webhook-hetzner.fullname" .) }} 36 | {{- end -}} 37 | 38 | {{- define "cert-manager-webhook-hetzner.rootCAIssuer" -}} 39 | {{ printf "%s-ca" (include "cert-manager-webhook-hetzner.fullname" .) }} 40 | {{- end -}} 41 | 42 | {{- define "cert-manager-webhook-hetzner.rootCACertificate" -}} 43 | {{ printf "%s-ca" (include "cert-manager-webhook-hetzner.fullname" .) }} 44 | {{- end -}} 45 | 46 | {{- define "cert-manager-webhook-hetzner.servingCertificate" -}} 47 | {{ printf "%s-webhook-tls" (include "cert-manager-webhook-hetzner.fullname" .) }} 48 | {{- end -}} 49 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | run-name: Build and Deploy 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - 'v*' 10 | pull_request: 11 | paths: 12 | - '**.go' 13 | - 'Dockerfile' 14 | - 'Makefile' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout Code 22 | uses: actions/checkout@v4 23 | 24 | - name: Docker meta 25 | id: meta 26 | uses: docker/metadata-action@v5 27 | with: 28 | images: | 29 | zmejg/cert-manager-webhook-hetzner 30 | ghcr.io/vadimkim/cert-manager-webhook-hetzner 31 | tags: | 32 | type=ref,event=branch 33 | type=ref,event=pr 34 | type=semver,pattern={{version}} 35 | type=semver,pattern={{major}}.{{minor}} 36 | 37 | - name: Set up QEMU 38 | uses: docker/setup-qemu-action@v3 39 | 40 | - name: Set up Docker Buildx 41 | id: buildx 42 | uses: docker/setup-buildx-action@v3 43 | 44 | - name: Login to Docker Hub 45 | if: github.event_name != 'pull_request' 46 | uses: docker/login-action@v3 47 | with: 48 | username: ${{ secrets.DOCKERHUB_USERNAME }} 49 | password: ${{ secrets.DOCKERHUB_TOKEN }} 50 | 51 | - name: Login to GitHub Container Registry 52 | if: github.event_name != 'pull_request' 53 | uses: docker/login-action@v3 54 | with: 55 | registry: ghcr.io 56 | username: ${{ github.actor }} 57 | password: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | - name: Build and push 60 | uses: docker/build-push-action@v5 61 | with: 62 | context: . 63 | platforms: linux/amd64,linux/arm64,linux/arm/v7 64 | push: ${{ github.event_name != 'pull_request' }} 65 | tags: ${{ steps.meta.outputs.tags }} 66 | labels: ${{ steps.meta.outputs.labels }} 67 | -------------------------------------------------------------------------------- /internal/hetzner.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type Config struct { 4 | ApiKey, ZoneName, ApiUrl string 5 | } 6 | 7 | type RecordResponse struct { 8 | Records []Record `json:"records"` 9 | Meta Meta `json:"meta"` 10 | } 11 | 12 | type ZoneResponse struct { 13 | Zones []Zone `json:"zones"` 14 | Meta Meta `json:"meta"` 15 | } 16 | 17 | type Meta struct { 18 | Pagination Pagination `json:"pagination"` 19 | } 20 | 21 | type Pagination struct { 22 | Page int `json:"page"` 23 | PerPage int `json:"per_page"` 24 | LastPage int `json:"last_page"` 25 | TotalEntries int `json:"total_entries"` 26 | } 27 | 28 | type Record struct { 29 | Type string `json:"type"` 30 | Id string `json:"id"` 31 | Created string `json:"created"` 32 | Modified string `json:"modified"` 33 | ZoneId string `json:"zone_id"` 34 | Name string `json:"name"` 35 | Value string `json:"value"` 36 | Ttl int `json:"ttl"` 37 | } 38 | 39 | type Zone struct { 40 | Id string `json:"id"` 41 | Created string `json:"created"` 42 | Modified string `json:"modified"` 43 | LegacyDnsHost string `json:"legacy_dns_host"` 44 | LegacyNs []string `json:"legacy_ns"` 45 | Name string `json:"name"` 46 | Ns []string `json:"ns"` 47 | Owner string `json:"owner"` 48 | Paused bool `json:"paused"` 49 | Permission string `json:"permission"` 50 | Project string `json:"project"` 51 | Registrar string `json:"registrar"` 52 | Status string `json:"status"` 53 | Ttl int `json:"ttl"` 54 | Verified string `json:"verified"` 55 | RecordsCount int `json:"records_count"` 56 | IsSecondaryDns bool `json:"is_secondary_dns"` 57 | TxtVerification Verification `json:"txt_verification"` 58 | } 59 | 60 | type Verification struct { 61 | Name string `json:"name"` 62 | Token string `json:"token"` 63 | } 64 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | modules-download-mode: mod 4 | linters: 5 | default: none 6 | enable: 7 | - bodyclose 8 | - copyloopvar 9 | - depguard 10 | - dogsled 11 | - dupl 12 | - errcheck 13 | - exhaustive 14 | - funlen 15 | - goconst 16 | - gocritic 17 | - gocyclo 18 | - goprintffuncname 19 | - gosec 20 | - govet 21 | - ineffassign 22 | - lll 23 | - misspell 24 | - nakedret 25 | - noctx 26 | - nolintlint 27 | - revive 28 | - rowserrcheck 29 | - staticcheck 30 | - unconvert 31 | - unparam 32 | - unused 33 | - whitespace 34 | settings: 35 | depguard: 36 | rules: 37 | main: 38 | files: 39 | - '!**/*_a _file.go' 40 | allow: 41 | - $gostd 42 | - k8s.io/apiextensions-apiserver 43 | - k8s.io/apimachinery 44 | - k8s.io/client-go/kubernetes 45 | - k8s.io/client-go/rest 46 | - k8s.io/klog/v2 47 | - github.com/cert-manager/cert-manager 48 | - github.com/vadimkim/cert-manager-webhook-hetzner/internal 49 | deny: 50 | - pkg: github.com/sirupsen/logrus 51 | desc: not allowed 52 | - pkg: github.com/pkg/errors 53 | desc: Should be replaced by standard lib errors package 54 | gocritic: 55 | disabled-checks: 56 | - whyNoLint 57 | - wrapperFunc 58 | enabled-tags: 59 | - performance 60 | - style 61 | - experimental 62 | gosec: 63 | excludes: 64 | - G404 65 | misspell: 66 | locale: US 67 | revive: 68 | rules: 69 | - name: var-naming 70 | disabled: true 71 | exclusions: 72 | generated: lax 73 | presets: 74 | - comments 75 | - common-false-positives 76 | - legacy 77 | - std-error-handling 78 | paths: 79 | - third_party$ 80 | - builtin$ 81 | - examples$ 82 | formatters: 83 | enable: 84 | - gofmt 85 | - goimports 86 | settings: 87 | goimports: 88 | local-prefixes: 89 | - github.com/golangci/golangci-lint 90 | exclusions: 91 | generated: lax 92 | paths: 93 | - third_party$ 94 | - builtin$ 95 | - examples$ 96 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/pki.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Create a selfsigned Issuer, in order to create a root CA certificate for 3 | # signing webhook serving certificates 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | name: {{ include "cert-manager-webhook-hetzner.selfSignedIssuer" . }} 8 | namespace: {{ .Release.Namespace | quote }} 9 | labels: 10 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 11 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 12 | release: {{ .Release.Name }} 13 | heritage: {{ .Release.Service }} 14 | spec: 15 | selfSigned: {} 16 | 17 | --- 18 | 19 | # Generate a CA Certificate used to sign certificates for the webhook 20 | apiVersion: cert-manager.io/v1 21 | kind: Certificate 22 | metadata: 23 | name: {{ include "cert-manager-webhook-hetzner.rootCACertificate" . }} 24 | namespace: {{ .Release.Namespace | quote }} 25 | labels: 26 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 27 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 28 | release: {{ .Release.Name }} 29 | heritage: {{ .Release.Service }} 30 | spec: 31 | secretName: {{ include "cert-manager-webhook-hetzner.rootCACertificate" . }} 32 | duration: 43800h0m0s # 5y 33 | issuerRef: 34 | name: {{ include "cert-manager-webhook-hetzner.selfSignedIssuer" . }} 35 | commonName: "ca.cert-manager-webhook-hetzner.cert-manager" 36 | isCA: true 37 | 38 | --- 39 | 40 | # Create an Issuer that uses the above generated CA certificate to issue certs 41 | apiVersion: cert-manager.io/v1 42 | kind: Issuer 43 | metadata: 44 | name: {{ include "cert-manager-webhook-hetzner.rootCAIssuer" . }} 45 | namespace: {{ .Release.Namespace | quote }} 46 | labels: 47 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 48 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 49 | release: {{ .Release.Name }} 50 | heritage: {{ .Release.Service }} 51 | spec: 52 | ca: 53 | secretName: {{ include "cert-manager-webhook-hetzner.rootCACertificate" . }} 54 | 55 | --- 56 | 57 | # Finally, generate a serving certificate for the webhook to use 58 | apiVersion: cert-manager.io/v1 59 | kind: Certificate 60 | metadata: 61 | name: {{ include "cert-manager-webhook-hetzner.servingCertificate" . }} 62 | namespace: {{ .Release.Namespace | quote }} 63 | labels: 64 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 65 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 66 | release: {{ .Release.Name }} 67 | heritage: {{ .Release.Service }} 68 | spec: 69 | secretName: {{ include "cert-manager-webhook-hetzner.servingCertificate" . }} 70 | duration: 8760h0m0s # 1y 71 | issuerRef: 72 | name: {{ include "cert-manager-webhook-hetzner.rootCAIssuer" . }} 73 | dnsNames: 74 | - {{ include "cert-manager-webhook-hetzner.fullname" . }} 75 | - {{ include "cert-manager-webhook-hetzner.fullname" . }}.{{ .Release.Namespace }} 76 | - {{ include "cert-manager-webhook-hetzner.fullname" . }}.{{ .Release.Namespace }}.svc -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 8 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | replicas: {{ .Values.replicaCount }} 13 | selector: 14 | matchLabels: 15 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 16 | release: {{ .Release.Name }} 17 | template: 18 | metadata: 19 | labels: 20 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 21 | release: {{ .Release.Name }} 22 | spec: 23 | serviceAccountName: {{ include "cert-manager-webhook-hetzner.fullname" . }} 24 | containers: 25 | - name: {{ .Chart.Name }} 26 | image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 27 | imagePullPolicy: {{ .Values.image.pullPolicy }} 28 | args: 29 | - --tls-cert-file=/tls/tls.crt 30 | - --tls-private-key-file=/tls/tls.key 31 | - --secure-port=8443 32 | env: 33 | - name: GROUP_NAME 34 | value: {{ .Values.groupName | quote }} 35 | {{- with .Values.http_proxy }} 36 | - name: HTTP_PROXY 37 | value: {{ . }} 38 | {{- end }} 39 | {{- with .Values.https_proxy }} 40 | - name: HTTPS_PROXY 41 | value: {{ . }} 42 | {{- end }} 43 | {{- with .Values.no_proxy }} 44 | - name: NO_PROXY 45 | value: {{ . }} 46 | {{- end }} 47 | ports: 48 | - name: https 49 | containerPort: 8443 50 | protocol: TCP 51 | livenessProbe: 52 | httpGet: 53 | scheme: HTTPS 54 | path: /healthz 55 | port: https 56 | readinessProbe: 57 | httpGet: 58 | scheme: HTTPS 59 | path: /healthz 60 | port: https 61 | {{- with .Values.securityContext }} 62 | securityContext: 63 | {{ toYaml . | indent 12 }} 64 | {{- end }} 65 | volumeMounts: 66 | - name: certs 67 | mountPath: /tls 68 | readOnly: true 69 | resources: 70 | {{ toYaml .Values.resources | indent 12 }} 71 | volumes: 72 | - name: certs 73 | secret: 74 | secretName: {{ include "cert-manager-webhook-hetzner.servingCertificate" . }} 75 | {{- with .Values.podSecurityContext }} 76 | securityContext: 77 | {{ toYaml . | indent 8 }} 78 | {{- end }} 79 | {{- with .Values.nodeSelector }} 80 | nodeSelector: 81 | {{ toYaml . | indent 8 }} 82 | {{- end }} 83 | {{- with .Values.affinity }} 84 | affinity: 85 | {{ toYaml . | indent 8 }} 86 | {{- end }} 87 | {{- with .Values.tolerations }} 88 | tolerations: 89 | {{ toYaml . | indent 8 }} 90 | {{- end }} 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACME webhook for Hetzner DNS API 2 | 3 | This solver can be used when you want to use cert-manager with Hetzner DNS API. API documentation 4 | is [Hetzner DNS API docs](https://dns.hetzner.com/api-docs) 5 | 6 | ## Requirements 7 | 8 | - [go](https://golang.org/) >= 1.25.0 9 | - [helm](https://helm.sh/) >= v3.0.0 10 | - [kubernetes](https://kubernetes.io/) >= v1.14.0 11 | - [cert-manager](https://cert-manager.io/) >= 0.12.0 12 | 13 | ## Installation 14 | 15 | ### cert-manager 16 | 17 | Follow the [instructions](https://cert-manager.io/docs/installation/) using the cert-manager documentation to install it 18 | within your cluster. 19 | 20 | ### Webhook 21 | 22 | #### Using public helm chart 23 | 24 | ```bash 25 | helm repo add cert-manager-webhook-hetzner https://vadimkim.github.io/cert-manager-webhook-hetzner 26 | helm install --namespace cert-manager cert-manager-webhook-hetzner cert-manager-webhook-hetzner/cert-manager-webhook-hetzner 27 | ``` 28 | 29 | #### From local checkout 30 | 31 | ```bash 32 | helm install --namespace cert-manager cert-manager-webhook-hetzner deploy/cert-manager-webhook-hetzner 33 | ``` 34 | 35 | **Note**: The kubernetes resources used to install the Webhook should be deployed within the same namespace as the 36 | cert-manager. 37 | 38 | To uninstall the webhook run 39 | 40 | ```bash 41 | helm uninstall --namespace cert-manager cert-manager-webhook-hetzner 42 | ``` 43 | 44 | ## Issuer 45 | 46 | Create a `ClusterIssuer` or `Issuer` resource as following: 47 | (Keep in Mind that the Example uses the Staging URL from Let's Encrypt. Look 48 | at [Getting Start](https://letsencrypt.org/getting-started/) for using the normal Let's Encrypt URL.) 49 | 50 | ```yaml 51 | apiVersion: cert-manager.io/v1 52 | kind: ClusterIssuer 53 | metadata: 54 | name: letsencrypt-staging 55 | spec: 56 | acme: 57 | # The ACME server URL 58 | server: https://acme-staging-v02.api.letsencrypt.org/directory 59 | 60 | # Email address used for ACME registration 61 | email: mail@example.com # REPLACE THIS WITH YOUR EMAIL!!! 62 | 63 | # Name of a secret used to store the ACME account private key 64 | privateKeySecretRef: 65 | name: letsencrypt-staging 66 | 67 | solvers: 68 | - dns01: 69 | webhook: 70 | groupName: hetzner.cert-mananger-webhook.noshoes.xyz 71 | solverName: hetzner 72 | config: 73 | secretName: hetzner-secret 74 | zoneName: example.com # (Optional): When not provided the Zone will searched in Hetzner API by recursion on full domain name 75 | apiUrl: https://dns.hetzner.com/api/v1 76 | ``` 77 | 78 | ### Credentials 79 | 80 | In order to access the Hetzner API, the webhook needs an API token. 81 | 82 | If you choose another name for the secret than `hetzner-secret`, you must install the chart with a modified `secretName` 83 | value. Policies ensure that no other secrets can be read by the webhook. Also modify the value of `secretName` in the 84 | `[Cluster]Issuer`. 85 | 86 | The secret for the example above will look like this: 87 | 88 | ```yaml 89 | apiVersion: v1 90 | kind: Secret 91 | metadata: 92 | name: hetzner-secret 93 | namespace: cert-manager 94 | type: Opaque 95 | data: 96 | api-key: your-key-base64-encoded 97 | ``` 98 | 99 | ### Create a certificate 100 | 101 | Finally you can create certificates, for example: 102 | 103 | ```yaml 104 | apiVersion: cert-manager.io/v1 105 | kind: Certificate 106 | metadata: 107 | name: example-cert 108 | namespace: cert-manager 109 | spec: 110 | commonName: example.com 111 | dnsNames: 112 | - example.com 113 | issuerRef: 114 | name: letsencrypt-staging 115 | kind: ClusterIssuer 116 | secretName: example-cert 117 | ``` 118 | 119 | ## Development 120 | 121 | ### Running the test suite 122 | 123 | All DNS providers **must** run the DNS01 provider conformance testing suite, 124 | else they will have undetermined behaviour when used with cert-manager. 125 | 126 | **It is essential that you configure and run the test suite when creating a 127 | DNS01 webhook.** 128 | 129 | First, you need to have Hetzner account with access to DNS control panel. You need to create API token and have a 130 | registered and verified DNS zone there. 131 | Then you need to replace `zoneName` parameter at `testdata/hetzner/config.json` file with actual one. 132 | You also must encode your API token into base64 and put the hash into `testdata/hetzner/hetzner-secret.yml` file. 133 | 134 | You can then run the test suite with: 135 | 136 | ```bash 137 | # first install necessary binaries (only required once) 138 | ./scripts/fetch-test-binaries.sh 139 | # then run the tests 140 | TEST_ZONE_NAME=example.com. make verify 141 | ``` 142 | 143 | ## Creating new package 144 | 145 | To build new Docker image for multiple architectures and push it to hub: 146 | 147 | ```shell 148 | docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t zmejg/cert-manager-webhook-hetzner:1.2.0 . --push 149 | ``` 150 | 151 | To compile and publish new Helm chart version: 152 | 153 | ```shell 154 | helm package deploy/cert-manager-webhook-hetzner 155 | git checkout gh-pages 156 | helm repo index . --url https://vadimkim.github.io/cert-manager-webhook-hetzner/ 157 | ``` 158 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vadimkim/cert-manager-webhook-hetzner 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/cert-manager/cert-manager v1.16.2 7 | k8s.io/apiextensions-apiserver v0.31.4 8 | k8s.io/apimachinery v0.31.4 9 | k8s.io/client-go v0.31.4 10 | k8s.io/klog/v2 v2.130.1 11 | ) 12 | 13 | require ( 14 | github.com/NYTimes/gziphandler v1.1.1 // indirect 15 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 16 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/blang/semver/v4 v4.0.0 // indirect 19 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/coreos/go-semver v0.3.1 // indirect 22 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 25 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 26 | github.com/felixge/httpsnoop v1.0.4 // indirect 27 | github.com/fsnotify/fsnotify v1.7.0 // indirect 28 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 29 | github.com/go-logr/logr v1.4.2 // indirect 30 | github.com/go-logr/stdr v1.2.2 // indirect 31 | github.com/go-logr/zapr v1.3.0 // indirect 32 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 33 | github.com/go-openapi/jsonreference v0.21.0 // indirect 34 | github.com/go-openapi/swag v0.23.0 // indirect 35 | github.com/gogo/protobuf v1.3.2 // indirect 36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 37 | github.com/golang/protobuf v1.5.4 // indirect 38 | github.com/google/cel-go v0.20.1 // indirect 39 | github.com/google/gnostic-models v0.6.9 // indirect 40 | github.com/google/go-cmp v0.6.0 // indirect 41 | github.com/google/gofuzz v1.2.0 // indirect 42 | github.com/google/uuid v1.6.0 // indirect 43 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 44 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect 45 | github.com/imdario/mergo v0.3.16 // indirect 46 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 47 | github.com/josharian/intern v1.0.0 // indirect 48 | github.com/json-iterator/go v1.1.12 // indirect 49 | github.com/klauspost/compress v1.17.9 // indirect 50 | github.com/kylelemons/godebug v1.1.0 // indirect 51 | github.com/mailru/easyjson v0.7.7 // indirect 52 | github.com/miekg/dns v1.1.62 // indirect 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 54 | github.com/modern-go/reflect2 v1.0.2 // indirect 55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 56 | github.com/pkg/errors v0.9.1 // indirect 57 | github.com/prometheus/client_golang v1.20.4 // indirect 58 | github.com/prometheus/client_model v0.6.1 // indirect 59 | github.com/prometheus/common v0.55.0 // indirect 60 | github.com/prometheus/procfs v0.15.1 // indirect 61 | github.com/spf13/cobra v1.8.1 // indirect 62 | github.com/spf13/pflag v1.0.5 // indirect 63 | github.com/stoewer/go-strcase v1.3.0 // indirect 64 | github.com/x448/float16 v0.8.4 // indirect 65 | go.etcd.io/etcd/api/v3 v3.5.14 // indirect 66 | go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect 67 | go.etcd.io/etcd/client/v3 v3.5.14 // indirect 68 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect 69 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect 70 | go.opentelemetry.io/otel v1.29.0 // indirect 71 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect 72 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect 73 | go.opentelemetry.io/otel/metric v1.29.0 // indirect 74 | go.opentelemetry.io/otel/sdk v1.28.0 // indirect 75 | go.opentelemetry.io/otel/trace v1.29.0 // indirect 76 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 77 | go.uber.org/multierr v1.11.0 // indirect 78 | go.uber.org/zap v1.27.0 // indirect 79 | golang.org/x/crypto v0.45.0 // indirect 80 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 81 | golang.org/x/mod v0.29.0 // indirect 82 | golang.org/x/net v0.47.0 // indirect 83 | golang.org/x/oauth2 v0.27.0 // indirect 84 | golang.org/x/sync v0.18.0 // indirect 85 | golang.org/x/sys v0.38.0 // indirect 86 | golang.org/x/term v0.37.0 // indirect 87 | golang.org/x/text v0.31.0 // indirect 88 | golang.org/x/time v0.6.0 // indirect 89 | golang.org/x/tools v0.38.0 // indirect 90 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 91 | google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect 92 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect 93 | google.golang.org/grpc v1.66.2 // indirect 94 | google.golang.org/protobuf v1.35.1 // indirect 95 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 96 | gopkg.in/inf.v0 v0.9.1 // indirect 97 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 98 | gopkg.in/yaml.v2 v2.4.0 // indirect 99 | gopkg.in/yaml.v3 v3.0.1 // indirect 100 | k8s.io/api v0.31.4 // indirect 101 | k8s.io/apiserver v0.31.4 // indirect 102 | k8s.io/component-base v0.31.4 // indirect 103 | k8s.io/kms v0.31.4 // indirect 104 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect 105 | k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect 106 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 // indirect 107 | sigs.k8s.io/controller-runtime v0.19.3 // indirect 108 | sigs.k8s.io/gateway-api v1.2.1 // indirect 109 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 110 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect 111 | sigs.k8s.io/yaml v1.4.0 // indirect 112 | ) 113 | -------------------------------------------------------------------------------- /deploy/cert-manager-webhook-hetzner/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 8 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | --- 12 | # Grant the webhook permission to read the ConfigMap containing the Kubernetes 13 | # apiserver's requestheader-ca-certificate. 14 | # This ConfigMap is automatically created by the Kubernetes apiserver. 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: RoleBinding 17 | metadata: 18 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:webhook-authentication-reader 19 | namespace: kube-system 20 | labels: 21 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 22 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: extension-apiserver-authentication-reader 29 | subjects: 30 | - apiGroup: "" 31 | kind: ServiceAccount 32 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 33 | namespace: {{ .Release.Namespace }} 34 | --- 35 | # apiserver gets the auth-delegator role to delegate auth decisions to 36 | # the core apiserver 37 | apiVersion: rbac.authorization.k8s.io/v1 38 | kind: ClusterRoleBinding 39 | metadata: 40 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:auth-delegator 41 | labels: 42 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 43 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 44 | release: {{ .Release.Name }} 45 | heritage: {{ .Release.Service }} 46 | roleRef: 47 | apiGroup: rbac.authorization.k8s.io 48 | kind: ClusterRole 49 | name: system:auth-delegator 50 | subjects: 51 | - apiGroup: "" 52 | kind: ServiceAccount 53 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 54 | namespace: {{ .Release.Namespace }} 55 | --- 56 | # Grant cert-manager permission to validate using our apiserver 57 | apiVersion: rbac.authorization.k8s.io/v1 58 | kind: ClusterRole 59 | metadata: 60 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:domain-solver 61 | labels: 62 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 63 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 64 | release: {{ .Release.Name }} 65 | heritage: {{ .Release.Service }} 66 | rules: 67 | - apiGroups: 68 | - {{ .Values.groupName }} 69 | resources: 70 | - '*' 71 | verbs: 72 | - 'create' 73 | --- 74 | apiVersion: rbac.authorization.k8s.io/v1 75 | kind: ClusterRoleBinding 76 | metadata: 77 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:domain-solver 78 | labels: 79 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 80 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 81 | release: {{ .Release.Name }} 82 | heritage: {{ .Release.Service }} 83 | roleRef: 84 | apiGroup: rbac.authorization.k8s.io 85 | kind: ClusterRole 86 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:domain-solver 87 | subjects: 88 | - apiGroup: "" 89 | kind: ServiceAccount 90 | name: {{ .Values.certManager.serviceAccountName }} 91 | namespace: {{ .Values.certManager.namespace }} 92 | --- 93 | apiVersion: rbac.authorization.k8s.io/v1 94 | kind: Role 95 | metadata: 96 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:secret-reader 97 | namespace: {{ .Release.Namespace }} 98 | rules: 99 | - apiGroups: 100 | - "" 101 | resources: 102 | - "secrets" 103 | {{- with .Values.secretName }} 104 | resourceNames: 105 | {{ toYaml . | indent 4 }} 106 | {{- end }} 107 | verbs: 108 | - "get" 109 | - "watch" 110 | --- 111 | apiVersion: rbac.authorization.k8s.io/v1 112 | kind: RoleBinding 113 | metadata: 114 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:secret-reader 115 | namespace: {{ .Release.Namespace }} 116 | roleRef: 117 | apiGroup: rbac.authorization.k8s.io 118 | kind: Role 119 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:secret-reader 120 | subjects: 121 | - apiGroup: "" 122 | kind: ServiceAccount 123 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 124 | namespace: {{ .Release.Namespace }} 125 | --- 126 | apiVersion: rbac.authorization.k8s.io/v1 127 | kind: ClusterRole 128 | metadata: 129 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:flowcontrol-solver 130 | labels: 131 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 132 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 133 | release: {{ .Release.Name }} 134 | heritage: {{ .Release.Service }} 135 | rules: 136 | - apiGroups: 137 | - "flowcontrol.apiserver.k8s.io" 138 | resources: 139 | - 'prioritylevelconfigurations' 140 | - 'flowschemas' 141 | verbs: 142 | - 'list' 143 | - 'watch' 144 | --- 145 | apiVersion: rbac.authorization.k8s.io/v1 146 | kind: ClusterRoleBinding 147 | metadata: 148 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:flowcontrol-solver 149 | labels: 150 | app: {{ include "cert-manager-webhook-hetzner.name" . }} 151 | chart: {{ include "cert-manager-webhook-hetzner.chart" . }} 152 | release: {{ .Release.Name }} 153 | heritage: {{ .Release.Service }} 154 | roleRef: 155 | apiGroup: rbac.authorization.k8s.io 156 | kind: ClusterRole 157 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }}:flowcontrol-solver 158 | subjects: 159 | - apiGroup: "" 160 | kind: ServiceAccount 161 | name: {{ include "cert-manager-webhook-hetzner.fullname" . }} 162 | namespace: {{ .Release.Namespace | quote }} 163 | --- 164 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "regexp" 10 | "strings" 11 | 12 | "encoding/json" 13 | "fmt" 14 | "os" 15 | 16 | extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 17 | "k8s.io/client-go/rest" 18 | 19 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 20 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/cmd" 21 | "github.com/vadimkim/cert-manager-webhook-hetzner/internal" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/client-go/kubernetes" 24 | "k8s.io/klog/v2" 25 | ) 26 | 27 | var GroupName = os.Getenv("GROUP_NAME") 28 | 29 | func main() { 30 | if GroupName == "" { 31 | panic("GROUP_NAME must be specified") 32 | } 33 | 34 | cmd.RunWebhookServer(GroupName, 35 | &hetznerDNSProviderSolver{}, 36 | ) 37 | } 38 | 39 | type hetznerDNSProviderSolver struct { 40 | client *kubernetes.Clientset 41 | } 42 | 43 | type hetznerDNSProviderConfig struct { 44 | SecretRef string `json:"secretName"` 45 | ZoneName string `json:"zoneName"` 46 | ApiUrl string `json:"apiUrl"` 47 | } 48 | 49 | func (c *hetznerDNSProviderSolver) Name() string { 50 | return "hetzner" 51 | } 52 | 53 | func (c *hetznerDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { 54 | klog.V(6).Infof("call function Present: namespace=%s, zone=%s, fqdn=%s", 55 | ch.ResourceNamespace, ch.ResolvedZone, ch.ResolvedFQDN) 56 | 57 | config, err := clientConfig(c, ch) 58 | 59 | if err != nil { 60 | return fmt.Errorf("unable to get secret `%s`; %v", ch.ResourceNamespace, err) 61 | } 62 | 63 | addTxtRecord(config, ch) 64 | 65 | klog.Infof("Presented txt record %v", ch.ResolvedFQDN) 66 | 67 | return nil 68 | } 69 | 70 | func (c *hetznerDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { 71 | config, err := clientConfig(c, ch) 72 | 73 | if err != nil { 74 | return fmt.Errorf("unable to get secret `%s`; %v", ch.ResourceNamespace, err) 75 | } 76 | 77 | zoneId, err := searchZoneId(config) 78 | 79 | if err != nil { 80 | return fmt.Errorf("unable to find id for zone name `%s`; %v", config.ZoneName, err) 81 | } 82 | 83 | var url = config.ApiUrl + "/records?zone_id=" + zoneId 84 | 85 | // Get all DNS records 86 | dnsRecords, err := callDnsApi(url, "GET", nil, config) 87 | 88 | if err != nil { 89 | return fmt.Errorf("unable to get DNS records %v", err) 90 | } 91 | 92 | // Unmarshall response 93 | records := internal.RecordResponse{} 94 | readErr := json.Unmarshal(dnsRecords, &records) 95 | 96 | if readErr != nil { 97 | return fmt.Errorf("unable to unmarshal response %v", readErr) 98 | } 99 | 100 | var recordId string 101 | name := recordName(ch.ResolvedFQDN, config.ZoneName) 102 | for i := len(records.Records) - 1; i >= 0; i-- { 103 | if strings.EqualFold(records.Records[i].Name, name) { 104 | recordId = records.Records[i].Id 105 | break 106 | } 107 | } 108 | 109 | // Delete TXT record 110 | url = config.ApiUrl + "/records/" + recordId 111 | del, err := callDnsApi(url, "DELETE", nil, config) 112 | 113 | if err != nil { 114 | klog.Error(err) 115 | } 116 | klog.Infof("Delete TXT record result: %s", string(del)) 117 | return nil 118 | } 119 | 120 | func (c *hetznerDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error { 121 | k8sClient, err := kubernetes.NewForConfig(kubeClientConfig) 122 | klog.V(6).Infof("Input variable stopCh is %d length", len(stopCh)) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | c.client = k8sClient 128 | 129 | return nil 130 | } 131 | 132 | func loadConfig(cfgJSON *extapi.JSON) (hetznerDNSProviderConfig, error) { 133 | cfg := hetznerDNSProviderConfig{} 134 | // handle the 'base case' where no configuration has been provided 135 | if cfgJSON == nil { 136 | return cfg, nil 137 | } 138 | if err := json.Unmarshal(cfgJSON.Raw, &cfg); err != nil { 139 | return cfg, fmt.Errorf("error decoding solver config: %v", err) 140 | } 141 | 142 | return cfg, nil 143 | } 144 | 145 | func stringFromSecretData(secretData map[string][]byte, key string) (string, error) { 146 | data, ok := secretData[key] 147 | if !ok { 148 | return "", fmt.Errorf("key %q not found in secret data", key) 149 | } 150 | return string(data), nil 151 | } 152 | 153 | func addTxtRecord(config internal.Config, ch *v1alpha1.ChallengeRequest) { 154 | url := config.ApiUrl + "/records" 155 | 156 | name := recordName(ch.ResolvedFQDN, config.ZoneName) 157 | zoneId, err := searchZoneId(config) 158 | 159 | if err != nil { 160 | klog.Errorf("unable to find id for zone name `%s`; %v", config.ZoneName, err) 161 | } 162 | 163 | var jsonStr = fmt.Sprintf(`{"value":%q, "ttl":120, "type":"TXT", "name":%q, "zone_id":%q}`, ch.Key, name, zoneId) 164 | 165 | add, err := callDnsApi(url, "POST", bytes.NewBuffer([]byte(jsonStr)), config) 166 | 167 | if err != nil { 168 | klog.Error(err) 169 | } 170 | klog.Infof("Added TXT record result: %s", string(add)) 171 | } 172 | 173 | func clientConfig(c *hetznerDNSProviderSolver, ch *v1alpha1.ChallengeRequest) (internal.Config, error) { 174 | var config internal.Config 175 | 176 | cfg, err := loadConfig(ch.Config) 177 | if err != nil { 178 | return config, err 179 | } 180 | config.ZoneName = cfg.ZoneName 181 | config.ApiUrl = cfg.ApiUrl 182 | 183 | secretName := cfg.SecretRef 184 | sec, err := c.client.CoreV1().Secrets(ch.ResourceNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 185 | 186 | if err != nil { 187 | return config, fmt.Errorf("unable to get secret `%s/%s`; %v", secretName, ch.ResourceNamespace, err) 188 | } 189 | 190 | apiKey, err := stringFromSecretData(sec.Data, "api-key") 191 | config.ApiKey = apiKey 192 | 193 | if err != nil { 194 | return config, fmt.Errorf("unable to get api-key from secret `%s/%s`; %v", secretName, ch.ResourceNamespace, err) 195 | } 196 | 197 | // Get ZoneName by api search if not provided by config 198 | if config.ZoneName == "" { 199 | foundZone, err := searchZoneName(config, ch.ResolvedZone) 200 | if err != nil { 201 | return config, err 202 | } 203 | config.ZoneName = foundZone 204 | } 205 | 206 | return config, nil 207 | } 208 | 209 | /* 210 | Domain name in Hetzner is divided in 2 parts: record + zone name. API works 211 | with record name that is FQDN without zone name. Subdomains is a part of 212 | record name and is separated by "." 213 | */ 214 | func recordName(fqdn, domain string) string { 215 | r := regexp.MustCompile("(.+)\\." + domain + "\\.") 216 | name := r.FindStringSubmatch(fqdn) 217 | if len(name) != 2 { 218 | klog.Errorf("splitting domain name %s failed!", fqdn) 219 | return "" 220 | } 221 | return name[1] 222 | } 223 | 224 | func callDnsApi(url, method string, body io.Reader, config internal.Config) ([]byte, error) { 225 | ctx := context.Background() 226 | req, err := http.NewRequestWithContext(ctx, method, url, body) 227 | if err != nil { 228 | return []byte{}, fmt.Errorf("unable to execute request %v", err) 229 | } 230 | req.Header.Set("Content-Type", "application/json") 231 | req.Header.Set("Auth-API-Token", config.ApiKey) 232 | 233 | client := &http.Client{} 234 | resp, err := client.Do(req) 235 | if err != nil { 236 | return nil, err 237 | } 238 | 239 | defer func() { 240 | err := resp.Body.Close() 241 | if err != nil { 242 | klog.Fatal(err) 243 | } 244 | }() 245 | 246 | respBody, _ := io.ReadAll(resp.Body) 247 | if resp.StatusCode == http.StatusOK { 248 | return respBody, nil 249 | } 250 | 251 | text := "Error calling API status:" + resp.Status + " url: " + url + " method: " + method 252 | klog.Error(text) 253 | return nil, errors.New(text) 254 | } 255 | 256 | func searchZoneId(config internal.Config) (string, error) { 257 | url := config.ApiUrl + "/zones?name=" + config.ZoneName 258 | 259 | // Get Zone configuration 260 | zoneRecords, err := callDnsApi(url, "GET", nil, config) 261 | 262 | if err != nil { 263 | return "", fmt.Errorf("unable to get zone info %v", err) 264 | } 265 | 266 | // Unmarshall response 267 | zones := internal.ZoneResponse{} 268 | readErr := json.Unmarshal(zoneRecords, &zones) 269 | 270 | if readErr != nil { 271 | return "", fmt.Errorf("unable to unmarshal response %v", readErr) 272 | } 273 | 274 | if zones.Meta.Pagination.TotalEntries != 1 { 275 | return "", fmt.Errorf("wrong number of zones in response %d must be exactly = 1", zones.Meta.Pagination.TotalEntries) 276 | } 277 | return zones.Zones[0].Id, nil 278 | } 279 | 280 | func searchZoneName(config internal.Config, searchZone string) (string, error) { 281 | parts := strings.Split(searchZone, ".") 282 | parts = parts[:len(parts)-1] 283 | for i := 0; i <= len(parts)-2; i++ { 284 | config.ZoneName = strings.Join(parts[i:], ".") 285 | zoneId, _ := searchZoneId(config) 286 | if zoneId != "" { 287 | klog.Infof("Found ID with ZoneName: %s", config.ZoneName) 288 | return config.ZoneName, nil 289 | } 290 | } 291 | return "", fmt.Errorf("unable to find hetzner dns zone with: %s", searchZone) 292 | } 293 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= 2 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 3 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 4 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 5 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 6 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 7 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 8 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 9 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 10 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 11 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 12 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 13 | github.com/cert-manager/cert-manager v1.16.2 h1:c9UU2E+8XWGruyvC/mdpc1wuLddtgmNr8foKdP7a8Jg= 14 | github.com/cert-manager/cert-manager v1.16.2/go.mod h1:MfLVTL45hFZsqmaT1O0+b2ugaNNQQZttSFV9hASHUb0= 15 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 16 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 18 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 19 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 20 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 21 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 25 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 27 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 28 | github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= 29 | github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 30 | github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= 31 | github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 32 | github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= 33 | github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= 34 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 35 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 36 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 37 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 38 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 39 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 40 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 41 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 42 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 43 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 44 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 45 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 46 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 47 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 48 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 49 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 50 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 51 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 52 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 53 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 54 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 55 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 56 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 57 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 58 | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= 59 | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 60 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 61 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 62 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 63 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 64 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 65 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 66 | github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= 67 | github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= 68 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 69 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 70 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 71 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 72 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 73 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 74 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 75 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 76 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= 77 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 78 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 79 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 80 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 81 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 82 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 83 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 84 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 85 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 86 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 87 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 88 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= 89 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 90 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 91 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 92 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 93 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 94 | github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= 95 | github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 96 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 97 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 98 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 99 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 100 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 101 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 102 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 103 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 104 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 105 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 106 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 107 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 108 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 109 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 110 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 111 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 112 | github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= 113 | github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= 114 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 115 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 116 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 117 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 118 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 119 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 120 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 121 | github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= 122 | github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= 123 | github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= 124 | github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 125 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 126 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 127 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 128 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 129 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 130 | github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= 131 | github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 132 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 133 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 134 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 135 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 136 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 137 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 138 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 139 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 140 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 141 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 142 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 143 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 144 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 145 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 146 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 147 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 148 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 149 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 150 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 151 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 152 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 153 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 154 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 155 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 156 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 157 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 158 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 159 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 160 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= 161 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= 162 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 163 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 164 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 165 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 166 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 167 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 168 | go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= 169 | go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= 170 | go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= 171 | go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= 172 | go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= 173 | go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= 174 | go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= 175 | go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= 176 | go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= 177 | go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= 178 | go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= 179 | go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= 180 | go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= 181 | go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= 182 | go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= 183 | go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= 184 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= 185 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= 186 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= 187 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= 188 | go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 189 | go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 190 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= 191 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= 192 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= 193 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= 194 | go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= 195 | go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= 196 | go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= 197 | go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= 198 | go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 199 | go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 200 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 201 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 202 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 203 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 204 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 205 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 206 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 207 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 208 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 209 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 210 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 211 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 212 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 213 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 214 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 215 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 216 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 217 | golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= 218 | golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= 219 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 220 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 221 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 222 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 223 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 224 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 225 | golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= 226 | golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 227 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 228 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 229 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 230 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 231 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 232 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 233 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 234 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 235 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 236 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 237 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 238 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 239 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 240 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 241 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 242 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 243 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 244 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 245 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 246 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 247 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 248 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 249 | golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= 250 | golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= 251 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 252 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 253 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 254 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 255 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 256 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 257 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= 258 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= 259 | google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= 260 | google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= 261 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= 262 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 263 | google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= 264 | google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 265 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 266 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 267 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 268 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 269 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 270 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 271 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 272 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 273 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 274 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 275 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 276 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 277 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 278 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 279 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 280 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 281 | k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM= 282 | k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw= 283 | k8s.io/apiextensions-apiserver v0.31.4 h1:FxbqzSvy92Ca9DIs5jqot883G0Ln/PGXfm/07t39LS0= 284 | k8s.io/apiextensions-apiserver v0.31.4/go.mod h1:hIW9YU8UsqZqIWGG99/gsdIU0Ar45Qd3A12QOe/rvpg= 285 | k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM= 286 | k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 287 | k8s.io/apiserver v0.31.4 h1:JbtnTaXVYEAYIHJil6Wd74Wif9sd8jVcBw84kwEmp7o= 288 | k8s.io/apiserver v0.31.4/go.mod h1:JJjoTjZ9PTMLdIFq7mmcJy2B9xLN3HeAUebW6xZyIP0= 289 | k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ= 290 | k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg= 291 | k8s.io/component-base v0.31.4 h1:wCquJh4ul9O8nNBSB8N/o8+gbfu3BVQkVw9jAUY/Qtw= 292 | k8s.io/component-base v0.31.4/go.mod h1:G4dgtf5BccwiDT9DdejK0qM6zTK0jwDGEKnCmb9+u/s= 293 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 294 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 295 | k8s.io/kms v0.31.4 h1:DVk9T1PHxG7IUMfWs1sDhBTbzGnM7lhMJO8lOzOzTIs= 296 | k8s.io/kms v0.31.4/go.mod h1:OZKwl1fan3n3N5FFxnW5C4V3ygrah/3YXeJWS3O6+94= 297 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= 298 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= 299 | k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= 300 | k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 301 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 h1:uOuSLOMBWkJH0TWa9X6l+mj5nZdm6Ay6Bli8HL8rNfk= 302 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 303 | sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= 304 | sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= 305 | sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM= 306 | sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= 307 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 308 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 309 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= 310 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= 311 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 312 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 313 | --------------------------------------------------------------------------------