├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── deploy └── example-webhook │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── apiservice.yaml │ ├── deployment.yaml │ ├── pki.yaml │ ├── rbac.yaml │ └── service.yaml │ └── values.yaml ├── example ├── dns.go ├── example.go └── example_test.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── testdata └── my-custom-solver ├── README.md └── config.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Ignore the built binary 15 | cert-manager-webhook-example 16 | 17 | # Make artifacts 18 | _out 19 | _test 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine3.19 AS build_deps 2 | 3 | RUN apk add --no-cache git 4 | 5 | WORKDIR /workspace 6 | 7 | COPY go.mod . 8 | COPY go.sum . 9 | 10 | RUN go mod download 11 | 12 | FROM build_deps AS build 13 | 14 | COPY . . 15 | 16 | RUN CGO_ENABLED=0 go build -o webhook -ldflags '-w -extldflags "-static"' . 17 | 18 | FROM alpine:3.18 19 | 20 | RUN apk add --no-cache ca-certificates 21 | 22 | COPY --from=build /workspace/webhook /usr/local/bin/webhook 23 | 24 | ENTRYPOINT ["webhook"] 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= $(shell which go) 2 | OS ?= $(shell $(GO) env GOOS) 3 | ARCH ?= $(shell $(GO) env GOARCH) 4 | 5 | IMAGE_NAME := "webhook" 6 | IMAGE_TAG := "latest" 7 | 8 | OUT := $(shell pwd)/_out 9 | 10 | KUBEBUILDER_VERSION=1.28.0 11 | 12 | HELM_FILES := $(shell find deploy/example-webhook) 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 $(OUT) 29 | 30 | .PHONY: build 31 | build: 32 | docker build -t "$(IMAGE_NAME):$(IMAGE_TAG)" . 33 | 34 | .PHONY: rendered-manifest.yaml 35 | rendered-manifest.yaml: $(OUT)/rendered-manifest.yaml 36 | 37 | $(OUT)/rendered-manifest.yaml: $(HELM_FILES) | $(OUT) 38 | helm template \ 39 | --name example-webhook \ 40 | --set image.repository=$(IMAGE_NAME) \ 41 | --set image.tag=$(IMAGE_TAG) \ 42 | deploy/example-webhook > $@ 43 | 44 | _test $(OUT) _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH): 45 | mkdir -p $@ 46 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - munnerz 3 | - joshvanl 4 | - wallrj 5 | - jakexks 6 | - maelvls 7 | - irbekrm 8 | - inteon 9 | reviewers: 10 | - munnerz 11 | - joshvanl 12 | - wallrj 13 | - jakexks 14 | - maelvls 15 | - irbekrm 16 | - inteon 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | cert-manager project logo 3 |

4 | 5 | # ACME webhook example 6 | 7 | The ACME issuer type supports an optional 'webhook' solver, which can be used 8 | to implement custom DNS01 challenge solving logic. 9 | 10 | This is useful if you need to use cert-manager with a DNS provider that is not 11 | officially supported in cert-manager core. 12 | 13 | ## Why not in core? 14 | 15 | As the project & adoption has grown, there has been an influx of DNS provider 16 | pull requests to our core codebase. As this number has grown, the test matrix 17 | has become un-maintainable and so, it's not possible for us to certify that 18 | providers work to a sufficient level. 19 | 20 | By creating this 'interface' between cert-manager and DNS providers, we allow 21 | users to quickly iterate and test out new integrations, and then packaging 22 | those up themselves as 'extensions' to cert-manager. 23 | 24 | We can also then provide a standardised 'testing framework', or set of 25 | conformance tests, which allow us to validate that a DNS provider works as 26 | expected. 27 | 28 | ## Creating your own webhook 29 | 30 | Webhook's themselves are deployed as Kubernetes API services, in order to allow 31 | administrators to restrict access to webhooks with Kubernetes RBAC. 32 | 33 | This is important, as otherwise it'd be possible for anyone with access to your 34 | webhook to complete ACME challenge validations and obtain certificates. 35 | 36 | To make the set up of these webhook's easier, we provide a template repository 37 | that can be used to get started quickly. 38 | 39 | ### Creating your own repository 40 | 41 | ### Running the test suite 42 | 43 | All DNS providers **must** run the DNS01 provider conformance testing suite, 44 | else they will have undetermined behaviour when used with cert-manager. 45 | 46 | **It is essential that you configure and run the test suite when creating a 47 | DNS01 webhook.** 48 | 49 | An example Go test file has been provided in [main_test.go](https://github.com/cert-manager/webhook-example/blob/master/main_test.go). 50 | 51 | You can run the test suite with: 52 | 53 | ```bash 54 | $ TEST_ZONE_NAME=example.com. make test 55 | ``` 56 | 57 | The example file has a number of areas you must fill in and replace with your 58 | own options in order for tests to pass. 59 | -------------------------------------------------------------------------------- /deploy/example-webhook/.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/example-webhook/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for Kubernetes 4 | name: example-webhook 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /deploy/example-webhook/templates/NOTES.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cert-manager/webhook-example/0dcb6537405096ec6415b5e81daa6568323e9833/deploy/example-webhook/templates/NOTES.txt -------------------------------------------------------------------------------- /deploy/example-webhook/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "example-webhook.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 "example-webhook.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 "example-webhook.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{- define "example-webhook.selfSignedIssuer" -}} 35 | {{ printf "%s-selfsign" (include "example-webhook.fullname" .) }} 36 | {{- end -}} 37 | 38 | {{- define "example-webhook.rootCAIssuer" -}} 39 | {{ printf "%s-ca" (include "example-webhook.fullname" .) }} 40 | {{- end -}} 41 | 42 | {{- define "example-webhook.rootCACertificate" -}} 43 | {{ printf "%s-ca" (include "example-webhook.fullname" .) }} 44 | {{- end -}} 45 | 46 | {{- define "example-webhook.servingCertificate" -}} 47 | {{ printf "%s-webhook-tls" (include "example-webhook.fullname" .) }} 48 | {{- end -}} 49 | -------------------------------------------------------------------------------- /deploy/example-webhook/templates/apiservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiregistration.k8s.io/v1 2 | kind: APIService 3 | metadata: 4 | name: v1alpha1.{{ .Values.groupName }} 5 | labels: 6 | app: {{ include "example-webhook.name" . }} 7 | chart: {{ include "example-webhook.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | annotations: 11 | cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ include "example-webhook.servingCertificate" . }}" 12 | spec: 13 | group: {{ .Values.groupName }} 14 | groupPriorityMinimum: 1000 15 | versionPriority: 15 16 | service: 17 | name: {{ include "example-webhook.fullname" . }} 18 | namespace: {{ .Release.Namespace }} 19 | version: v1alpha1 20 | -------------------------------------------------------------------------------- /deploy/example-webhook/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "example-webhook.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | app: {{ include "example-webhook.name" . }} 8 | chart: {{ include "example-webhook.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | replicas: {{ .Values.replicaCount }} 13 | selector: 14 | matchLabels: 15 | app: {{ include "example-webhook.name" . }} 16 | release: {{ .Release.Name }} 17 | template: 18 | metadata: 19 | labels: 20 | app: {{ include "example-webhook.name" . }} 21 | release: {{ .Release.Name }} 22 | spec: 23 | serviceAccountName: {{ include "example-webhook.fullname" . }} 24 | containers: 25 | - name: {{ .Chart.Name }} 26 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 27 | imagePullPolicy: {{ .Values.image.pullPolicy }} 28 | args: 29 | - --tls-cert-file=/tls/tls.crt 30 | - --tls-private-key-file=/tls/tls.key 31 | env: 32 | - name: GROUP_NAME 33 | value: {{ .Values.groupName | quote }} 34 | ports: 35 | - name: https 36 | containerPort: 443 37 | protocol: TCP 38 | livenessProbe: 39 | httpGet: 40 | scheme: HTTPS 41 | path: /healthz 42 | port: https 43 | readinessProbe: 44 | httpGet: 45 | scheme: HTTPS 46 | path: /healthz 47 | port: https 48 | volumeMounts: 49 | - name: certs 50 | mountPath: /tls 51 | readOnly: true 52 | resources: 53 | {{ toYaml .Values.resources | indent 12 }} 54 | volumes: 55 | - name: certs 56 | secret: 57 | secretName: {{ include "example-webhook.servingCertificate" . }} 58 | {{- with .Values.nodeSelector }} 59 | nodeSelector: 60 | {{ toYaml . | indent 8 }} 61 | {{- end }} 62 | {{- with .Values.affinity }} 63 | affinity: 64 | {{ toYaml . | indent 8 }} 65 | {{- end }} 66 | {{- with .Values.tolerations }} 67 | tolerations: 68 | {{ toYaml . | indent 8 }} 69 | {{- end }} 70 | -------------------------------------------------------------------------------- /deploy/example-webhook/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 "example-webhook.selfSignedIssuer" . }} 8 | namespace: {{ .Release.Namespace | quote }} 9 | labels: 10 | app: {{ include "example-webhook.name" . }} 11 | chart: {{ include "example-webhook.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 "example-webhook.rootCACertificate" . }} 24 | namespace: {{ .Release.Namespace | quote }} 25 | labels: 26 | app: {{ include "example-webhook.name" . }} 27 | chart: {{ include "example-webhook.chart" . }} 28 | release: {{ .Release.Name }} 29 | heritage: {{ .Release.Service }} 30 | spec: 31 | secretName: {{ include "example-webhook.rootCACertificate" . }} 32 | duration: 43800h # 5y 33 | issuerRef: 34 | name: {{ include "example-webhook.selfSignedIssuer" . }} 35 | commonName: "ca.example-webhook.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 "example-webhook.rootCAIssuer" . }} 45 | namespace: {{ .Release.Namespace | quote }} 46 | labels: 47 | app: {{ include "example-webhook.name" . }} 48 | chart: {{ include "example-webhook.chart" . }} 49 | release: {{ .Release.Name }} 50 | heritage: {{ .Release.Service }} 51 | spec: 52 | ca: 53 | secretName: {{ include "example-webhook.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 "example-webhook.servingCertificate" . }} 62 | namespace: {{ .Release.Namespace | quote }} 63 | labels: 64 | app: {{ include "example-webhook.name" . }} 65 | chart: {{ include "example-webhook.chart" . }} 66 | release: {{ .Release.Name }} 67 | heritage: {{ .Release.Service }} 68 | spec: 69 | secretName: {{ include "example-webhook.servingCertificate" . }} 70 | duration: 8760h # 1y 71 | issuerRef: 72 | name: {{ include "example-webhook.rootCAIssuer" . }} 73 | dnsNames: 74 | - {{ include "example-webhook.fullname" . }} 75 | - {{ include "example-webhook.fullname" . }}.{{ .Release.Namespace }} 76 | - {{ include "example-webhook.fullname" . }}.{{ .Release.Namespace }}.svc 77 | -------------------------------------------------------------------------------- /deploy/example-webhook/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "example-webhook.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | app: {{ include "example-webhook.name" . }} 8 | chart: {{ include "example-webhook.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 "example-webhook.fullname" . }}:webhook-authentication-reader 19 | namespace: kube-system 20 | labels: 21 | app: {{ include "example-webhook.name" . }} 22 | chart: {{ include "example-webhook.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 "example-webhook.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 "example-webhook.fullname" . }}:auth-delegator 41 | labels: 42 | app: {{ include "example-webhook.name" . }} 43 | chart: {{ include "example-webhook.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 "example-webhook.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 "example-webhook.fullname" . }}:domain-solver 61 | labels: 62 | app: {{ include "example-webhook.name" . }} 63 | chart: {{ include "example-webhook.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 "example-webhook.fullname" . }}:domain-solver 78 | labels: 79 | app: {{ include "example-webhook.name" . }} 80 | chart: {{ include "example-webhook.chart" . }} 81 | release: {{ .Release.Name }} 82 | heritage: {{ .Release.Service }} 83 | roleRef: 84 | apiGroup: rbac.authorization.k8s.io 85 | kind: ClusterRole 86 | name: {{ include "example-webhook.fullname" . }}:domain-solver 87 | subjects: 88 | - apiGroup: "" 89 | kind: ServiceAccount 90 | name: {{ .Values.certManager.serviceAccountName }} 91 | namespace: {{ .Values.certManager.namespace }} 92 | -------------------------------------------------------------------------------- /deploy/example-webhook/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "example-webhook.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | app: {{ include "example-webhook.name" . }} 8 | chart: {{ include "example-webhook.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 "example-webhook.name" . }} 20 | release: {{ .Release.Name }} 21 | -------------------------------------------------------------------------------- /deploy/example-webhook/values.yaml: -------------------------------------------------------------------------------- 1 | # The GroupName here is used to identify your company or business unit that 2 | # created this webhook. 3 | # For example, this may be "acme.mycompany.com". 4 | # This name will need to be referenced in each Issuer's `webhook` stanza to 5 | # inform cert-manager of where to send ChallengePayload resources in order to 6 | # solve the DNS01 challenge. 7 | # This group name should be **unique**, hence using your own company's domain 8 | # here is recommended. 9 | groupName: acme.mycompany.com 10 | 11 | certManager: 12 | namespace: cert-manager 13 | serviceAccountName: cert-manager 14 | 15 | image: 16 | repository: mycompany/webhook-image 17 | tag: latest 18 | pullPolicy: IfNotPresent 19 | 20 | nameOverride: "" 21 | fullnameOverride: "" 22 | 23 | service: 24 | type: ClusterIP 25 | port: 443 26 | 27 | resources: {} 28 | # We usually recommend not to specify default resources and to leave this as a conscious 29 | # choice for the user. This also increases chances charts run on environments with little 30 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 31 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 32 | # limits: 33 | # cpu: 100m 34 | # memory: 128Mi 35 | # requests: 36 | # cpu: 100m 37 | # memory: 128Mi 38 | 39 | nodeSelector: {} 40 | 41 | tolerations: [] 42 | 43 | affinity: {} 44 | -------------------------------------------------------------------------------- /example/dns.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | func (e *exampleSolver) handleDNSRequest(w dns.ResponseWriter, req *dns.Msg) { 10 | msg := new(dns.Msg) 11 | msg.SetReply(req) 12 | switch req.Opcode { 13 | case dns.OpcodeQuery: 14 | for _, q := range msg.Question { 15 | if err := e.addDNSAnswer(q, msg, req); err != nil { 16 | msg.SetRcode(req, dns.RcodeServerFailure) 17 | break 18 | } 19 | } 20 | } 21 | w.WriteMsg(msg) 22 | } 23 | 24 | func (e *exampleSolver) addDNSAnswer(q dns.Question, msg *dns.Msg, req *dns.Msg) error { 25 | switch q.Qtype { 26 | // Always return loopback for any A query 27 | case dns.TypeA: 28 | rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN A 127.0.0.1", q.Name)) 29 | if err != nil { 30 | return err 31 | } 32 | msg.Answer = append(msg.Answer, rr) 33 | return nil 34 | 35 | // TXT records are the only important record for ACME dns-01 challenges 36 | case dns.TypeTXT: 37 | e.RLock() 38 | record, found := e.txtRecords[q.Name] 39 | e.RUnlock() 40 | if !found { 41 | msg.SetRcode(req, dns.RcodeNameError) 42 | return nil 43 | } 44 | rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN TXT %s", q.Name, record)) 45 | if err != nil { 46 | return err 47 | } 48 | msg.Answer = append(msg.Answer, rr) 49 | return nil 50 | 51 | // NS and SOA are for authoritative lookups, return obviously invalid data 52 | case dns.TypeNS: 53 | rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN NS ns.example-acme-webook.invalid.", q.Name)) 54 | if err != nil { 55 | return err 56 | } 57 | msg.Answer = append(msg.Answer, rr) 58 | return nil 59 | case dns.TypeSOA: 60 | rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN SOA %s 20 5 5 5 5", "ns.example-acme-webook.invalid.", "ns.example-acme-webook.invalid.")) 61 | if err != nil { 62 | return err 63 | } 64 | msg.Answer = append(msg.Answer, rr) 65 | return nil 66 | default: 67 | return fmt.Errorf("unimplemented record type %v", q.Qtype) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | // package example contains a self-contained example of a webhook that passes the cert-manager 2 | // DNS conformance tests 3 | package example 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "sync" 9 | 10 | "github.com/cert-manager/cert-manager/pkg/acme/webhook" 11 | acme "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 12 | "github.com/miekg/dns" 13 | "k8s.io/client-go/rest" 14 | ) 15 | 16 | type exampleSolver struct { 17 | name string 18 | server *dns.Server 19 | txtRecords map[string]string 20 | sync.RWMutex 21 | } 22 | 23 | func (e *exampleSolver) Name() string { 24 | return e.name 25 | } 26 | 27 | func (e *exampleSolver) Present(ch *acme.ChallengeRequest) error { 28 | e.Lock() 29 | e.txtRecords[ch.ResolvedFQDN] = ch.Key 30 | e.Unlock() 31 | return nil 32 | } 33 | 34 | func (e *exampleSolver) CleanUp(ch *acme.ChallengeRequest) error { 35 | e.Lock() 36 | delete(e.txtRecords, ch.ResolvedFQDN) 37 | e.Unlock() 38 | return nil 39 | } 40 | 41 | func (e *exampleSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error { 42 | go func(done <-chan struct{}) { 43 | <-done 44 | if err := e.server.Shutdown(); err != nil { 45 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 46 | } 47 | }(stopCh) 48 | go func() { 49 | if err := e.server.ListenAndServe(); err != nil { 50 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 51 | os.Exit(1) 52 | } 53 | }() 54 | return nil 55 | } 56 | 57 | func New(port string) webhook.Solver { 58 | e := &exampleSolver{ 59 | name: "example", 60 | txtRecords: make(map[string]string), 61 | } 62 | e.server = &dns.Server{ 63 | Addr: ":" + port, 64 | Net: "udp", 65 | Handler: dns.HandlerFunc(e.handleDNSRequest), 66 | } 67 | return e 68 | } 69 | -------------------------------------------------------------------------------- /example/example_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | "testing" 7 | 8 | acme "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 9 | "github.com/miekg/dns" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestExampleSolver_Name(t *testing.T) { 14 | port, _ := rand.Int(rand.Reader, big.NewInt(50000)) 15 | port = port.Add(port, big.NewInt(15534)) 16 | solver := New(port.String()) 17 | assert.Equal(t, "example", solver.Name()) 18 | } 19 | 20 | func TestExampleSolver_Initialize(t *testing.T) { 21 | port, _ := rand.Int(rand.Reader, big.NewInt(50000)) 22 | port = port.Add(port, big.NewInt(15534)) 23 | solver := New(port.String()) 24 | done := make(chan struct{}) 25 | err := solver.Initialize(nil, done) 26 | assert.NoError(t, err, "Expected Initialize not to error") 27 | close(done) 28 | } 29 | 30 | func TestExampleSolver_Present_Cleanup(t *testing.T) { 31 | port, _ := rand.Int(rand.Reader, big.NewInt(50000)) 32 | port = port.Add(port, big.NewInt(15534)) 33 | solver := New(port.String()) 34 | done := make(chan struct{}) 35 | err := solver.Initialize(nil, done) 36 | assert.NoError(t, err, "Expected Initialize not to error") 37 | 38 | validTestData := []struct { 39 | hostname string 40 | record string 41 | }{ 42 | {"test1.example.com.", "testkey1"}, 43 | {"test2.example.com.", "testkey2"}, 44 | {"test3.example.com.", "testkey3"}, 45 | } 46 | for _, test := range validTestData { 47 | err := solver.Present(&acme.ChallengeRequest{ 48 | Action: acme.ChallengeActionPresent, 49 | Type: "dns-01", 50 | ResolvedFQDN: test.hostname, 51 | Key: test.record, 52 | }) 53 | assert.NoError(t, err, "Unexpected error while presenting %v", t) 54 | } 55 | 56 | // Resolve test data 57 | for _, test := range validTestData { 58 | msg := new(dns.Msg) 59 | msg.Id = dns.Id() 60 | msg.RecursionDesired = true 61 | msg.Question = make([]dns.Question, 1) 62 | msg.Question[0] = dns.Question{dns.Fqdn(test.hostname), dns.TypeTXT, dns.ClassINET} 63 | in, err := dns.Exchange(msg, "127.0.0.1:"+port.String()) 64 | 65 | assert.NoError(t, err, "Presented record %s not resolvable", test.hostname) 66 | assert.Len(t, in.Answer, 1, "RR response is of incorrect length") 67 | assert.Equal(t, []string{test.record}, in.Answer[0].(*dns.TXT).Txt, "TXT record returned did not match presented record") 68 | } 69 | 70 | // Cleanup test data 71 | for _, test := range validTestData { 72 | err := solver.CleanUp(&acme.ChallengeRequest{ 73 | Action: acme.ChallengeActionCleanUp, 74 | Type: "dns-01", 75 | ResolvedFQDN: test.hostname, 76 | Key: test.record, 77 | }) 78 | assert.NoError(t, err, "Unexpected error while cleaning up %v", t) 79 | } 80 | 81 | // Resolve test data 82 | for _, test := range validTestData { 83 | msg := new(dns.Msg) 84 | msg.Id = dns.Id() 85 | msg.RecursionDesired = true 86 | msg.Question = make([]dns.Question, 1) 87 | msg.Question[0] = dns.Question{dns.Fqdn(test.hostname), dns.TypeTXT, dns.ClassINET} 88 | in, err := dns.Exchange(msg, "127.0.0.1:"+port.String()) 89 | 90 | assert.NoError(t, err, "Presented record %s not resolvable", test.hostname) 91 | assert.Len(t, in.Answer, 0, "RR response is of incorrect length") 92 | assert.Equal(t, dns.RcodeNameError, in.Rcode, "Expexted NXDOMAIN") 93 | } 94 | 95 | close(done) 96 | } 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cert-manager/webhook-example 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/cert-manager/cert-manager v1.15.1 7 | github.com/miekg/dns v1.1.61 8 | github.com/stretchr/testify v1.9.0 9 | k8s.io/apiextensions-apiserver v0.30.2 10 | k8s.io/client-go v0.30.2 11 | ) 12 | 13 | require ( 14 | github.com/NYTimes/gziphandler v1.1.1 // indirect 15 | github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // 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.0 // indirect 25 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 26 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 27 | github.com/felixge/httpsnoop v1.0.4 // indirect 28 | github.com/fsnotify/fsnotify v1.7.0 // indirect 29 | github.com/go-logr/logr v1.4.1 // 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.17.8 // indirect 39 | github.com/google/gnostic-models v0.6.8 // 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/mailru/easyjson v0.7.7 // indirect 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 51 | github.com/modern-go/reflect2 v1.0.2 // indirect 52 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 53 | github.com/pkg/errors v0.9.1 // indirect 54 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 55 | github.com/prometheus/client_golang v1.18.0 // indirect 56 | github.com/prometheus/client_model v0.6.1 // indirect 57 | github.com/prometheus/common v0.46.0 // indirect 58 | github.com/prometheus/procfs v0.15.0 // indirect 59 | github.com/spf13/cobra v1.8.0 // indirect 60 | github.com/spf13/pflag v1.0.5 // indirect 61 | github.com/stoewer/go-strcase v1.3.0 // indirect 62 | go.etcd.io/etcd/api/v3 v3.5.13 // indirect 63 | go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect 64 | go.etcd.io/etcd/client/v3 v3.5.13 // indirect 65 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect 66 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect 67 | go.opentelemetry.io/otel v1.26.0 // indirect 68 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect 69 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect 70 | go.opentelemetry.io/otel/metric v1.26.0 // indirect 71 | go.opentelemetry.io/otel/sdk v1.26.0 // indirect 72 | go.opentelemetry.io/otel/trace v1.26.0 // indirect 73 | go.opentelemetry.io/proto/otlp v1.2.0 // indirect 74 | go.uber.org/multierr v1.11.0 // indirect 75 | go.uber.org/zap v1.27.0 // indirect 76 | golang.org/x/crypto v0.24.0 // indirect 77 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 78 | golang.org/x/mod v0.18.0 // indirect 79 | golang.org/x/net v0.26.0 // indirect 80 | golang.org/x/oauth2 v0.20.0 // indirect 81 | golang.org/x/sync v0.7.0 // indirect 82 | golang.org/x/sys v0.21.0 // indirect 83 | golang.org/x/term v0.21.0 // indirect 84 | golang.org/x/text v0.16.0 // indirect 85 | golang.org/x/time v0.5.0 // indirect 86 | golang.org/x/tools v0.22.0 // indirect 87 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 88 | google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291 // indirect 89 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect 90 | google.golang.org/grpc v1.64.1 // indirect 91 | google.golang.org/protobuf v1.34.1 // indirect 92 | gopkg.in/inf.v0 v0.9.1 // indirect 93 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 94 | gopkg.in/yaml.v2 v2.4.0 // indirect 95 | gopkg.in/yaml.v3 v3.0.1 // indirect 96 | k8s.io/api v0.30.2 // indirect 97 | k8s.io/apimachinery v0.30.2 // indirect 98 | k8s.io/apiserver v0.30.2 // indirect 99 | k8s.io/component-base v0.30.2 // indirect 100 | k8s.io/klog/v2 v2.120.1 // indirect 101 | k8s.io/kms v0.30.2 // indirect 102 | k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f // indirect 103 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect 104 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect 105 | sigs.k8s.io/controller-runtime v0.18.2 // indirect 106 | sigs.k8s.io/gateway-api v1.1.0 // indirect 107 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 108 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 109 | sigs.k8s.io/yaml v1.4.0 // indirect 110 | ) 111 | -------------------------------------------------------------------------------- /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/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= 4 | github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= 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.15.1 h1:HSG4k2GlJ2YgTLkZfQzrArNaQpM9+ehDDg550IxAD94= 14 | github.com/cert-manager/cert-manager v1.15.1/go.mod h1:p98JoGv3J9JhdKU9ngsj2EhWGI6/GlU7kpjWu5lf2js= 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.3/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.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= 29 | github.com/emicklei/go-restful/v3 v3.12.0/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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 39 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 40 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 41 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 42 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 43 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 44 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 45 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 46 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 47 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 48 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 49 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 50 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 51 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 52 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 53 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 54 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 55 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 56 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 57 | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= 58 | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 59 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 60 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 61 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 62 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 63 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 64 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 65 | github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= 66 | github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= 67 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 68 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 69 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 70 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 71 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 72 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 73 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 74 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 75 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= 76 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 77 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 78 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 79 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 80 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 81 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 82 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 83 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 84 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 85 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 86 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 87 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= 88 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 89 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 90 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 91 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 92 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 93 | github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= 94 | github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 95 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 96 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 97 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 98 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 99 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 100 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 101 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 102 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 103 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 104 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 105 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 106 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 107 | github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= 108 | github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= 109 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 110 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 111 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 112 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 113 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 114 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 115 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 116 | github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= 117 | github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= 118 | github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= 119 | github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 120 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 121 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 122 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 123 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 124 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 125 | github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= 126 | github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= 127 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 128 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 129 | github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= 130 | github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= 131 | github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= 132 | github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= 133 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 134 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 135 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 136 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 137 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 138 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 139 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 140 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 141 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 142 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 143 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 144 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 145 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 146 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 147 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 148 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 149 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 150 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 151 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 152 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 153 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 154 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 155 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= 156 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= 157 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 158 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 159 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 160 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 161 | go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= 162 | go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= 163 | go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4= 164 | go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= 165 | go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg= 166 | go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= 167 | go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= 168 | go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= 169 | go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js= 170 | go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI= 171 | go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM= 172 | go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs= 173 | go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA= 174 | go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6jbGRc= 175 | go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg= 176 | go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo= 177 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= 178 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= 179 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= 180 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= 181 | go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= 182 | go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= 183 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= 184 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= 185 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= 186 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= 187 | go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= 188 | go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= 189 | go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= 190 | go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= 191 | go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= 192 | go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= 193 | go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= 194 | go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= 195 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 196 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 197 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 198 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 199 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 200 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 201 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 202 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 203 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 204 | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= 205 | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 206 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= 207 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= 208 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 209 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 210 | golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= 211 | golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 212 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 213 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 214 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 215 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 216 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 217 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 218 | golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= 219 | golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 220 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 221 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 222 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 223 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 224 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 225 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 226 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 229 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 230 | golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= 231 | golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= 232 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 233 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 234 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 235 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 236 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 237 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 238 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 239 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 240 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 241 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 242 | golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= 243 | golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 244 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 245 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 246 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 247 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 248 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 249 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 250 | google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= 251 | google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= 252 | google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291 h1:4HZJ3Xv1cmrJ+0aFo304Zn79ur1HMxptAE7aCPNLSqc= 253 | google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= 254 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= 255 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= 256 | google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= 257 | google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 258 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= 259 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 260 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 261 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 262 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 263 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 264 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 265 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 266 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 267 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 268 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 269 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 270 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 271 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 272 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 273 | k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= 274 | k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= 275 | k8s.io/apiextensions-apiserver v0.30.2 h1:l7Eue2t6QiLHErfn2vwK4KgF4NeDgjQkCXtEbOocKIE= 276 | k8s.io/apiextensions-apiserver v0.30.2/go.mod h1:lsJFLYyK40iguuinsb3nt+Sj6CmodSI4ACDLep1rgjw= 277 | k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= 278 | k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= 279 | k8s.io/apiserver v0.30.2 h1:ACouHiYl1yFI2VFI3YGM+lvxgy6ir4yK2oLOsLI1/tw= 280 | k8s.io/apiserver v0.30.2/go.mod h1:BOTdFBIch9Sv0ypSEcUR6ew/NUFGocRFNl72Ra7wTm8= 281 | k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= 282 | k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= 283 | k8s.io/component-base v0.30.2 h1:pqGBczYoW1sno8q9ObExUqrYSKhtE5rW3y6gX88GZII= 284 | k8s.io/component-base v0.30.2/go.mod h1:yQLkQDrkK8J6NtP+MGJOws+/PPeEXNpwFixsUI7h/OE= 285 | k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= 286 | k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 287 | k8s.io/kms v0.30.2 h1:VSZILO/tkzrz5Tu2j+yFQZ2Dc5JerQZX2GqhFJbQrfw= 288 | k8s.io/kms v0.30.2/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4= 289 | k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGcXawNMouPECM1+F9BVxEaM= 290 | k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro= 291 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= 292 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 293 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= 294 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 295 | sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q= 296 | sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= 297 | sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= 298 | sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= 299 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 300 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 301 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 302 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 303 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 304 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 305 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 9 | "k8s.io/client-go/rest" 10 | 11 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 12 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/cmd" 13 | ) 14 | 15 | var GroupName = os.Getenv("GROUP_NAME") 16 | 17 | func main() { 18 | if GroupName == "" { 19 | panic("GROUP_NAME must be specified") 20 | } 21 | 22 | // This will register our custom DNS provider with the webhook serving 23 | // library, making it available as an API under the provided GroupName. 24 | // You can register multiple DNS provider implementations with a single 25 | // webhook, where the Name() method will be used to disambiguate between 26 | // the different implementations. 27 | cmd.RunWebhookServer(GroupName, 28 | &customDNSProviderSolver{}, 29 | ) 30 | } 31 | 32 | // customDNSProviderSolver implements the provider-specific logic needed to 33 | // 'present' an ACME challenge TXT record for your own DNS provider. 34 | // To do so, it must implement the `github.com/cert-manager/cert-manager/pkg/acme/webhook.Solver` 35 | // interface. 36 | type customDNSProviderSolver struct { 37 | // If a Kubernetes 'clientset' is needed, you must: 38 | // 1. uncomment the additional `client` field in this structure below 39 | // 2. uncomment the "k8s.io/client-go/kubernetes" import at the top of the file 40 | // 3. uncomment the relevant code in the Initialize method below 41 | // 4. ensure your webhook's service account has the required RBAC role 42 | // assigned to it for interacting with the Kubernetes APIs you need. 43 | //client kubernetes.Clientset 44 | } 45 | 46 | // customDNSProviderConfig is a structure that is used to decode into when 47 | // solving a DNS01 challenge. 48 | // This information is provided by cert-manager, and may be a reference to 49 | // additional configuration that's needed to solve the challenge for this 50 | // particular certificate or issuer. 51 | // This typically includes references to Secret resources containing DNS 52 | // provider credentials, in cases where a 'multi-tenant' DNS solver is being 53 | // created. 54 | // If you do *not* require per-issuer or per-certificate configuration to be 55 | // provided to your webhook, you can skip decoding altogether in favour of 56 | // using CLI flags or similar to provide configuration. 57 | // You should not include sensitive information here. If credentials need to 58 | // be used by your provider here, you should reference a Kubernetes Secret 59 | // resource and fetch these credentials using a Kubernetes clientset. 60 | type customDNSProviderConfig struct { 61 | // Change the two fields below according to the format of the configuration 62 | // to be decoded. 63 | // These fields will be set by users in the 64 | // `issuer.spec.acme.dns01.providers.webhook.config` field. 65 | 66 | //Email string `json:"email"` 67 | //APIKeySecretRef v1alpha1.SecretKeySelector `json:"apiKeySecretRef"` 68 | } 69 | 70 | // Name is used as the name for this DNS solver when referencing it on the ACME 71 | // Issuer resource. 72 | // This should be unique **within the group name**, i.e. you can have two 73 | // solvers configured with the same Name() **so long as they do not co-exist 74 | // within a single webhook deployment**. 75 | // For example, `cloudflare` may be used as the name of a solver. 76 | func (c *customDNSProviderSolver) Name() string { 77 | return "my-custom-solver" 78 | } 79 | 80 | // Present is responsible for actually presenting the DNS record with the 81 | // DNS provider. 82 | // This method should tolerate being called multiple times with the same value. 83 | // cert-manager itself will later perform a self check to ensure that the 84 | // solver has correctly configured the DNS provider. 85 | func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { 86 | cfg, err := loadConfig(ch.Config) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | // TODO: do something more useful with the decoded configuration 92 | fmt.Printf("Decoded configuration %v", cfg) 93 | 94 | // TODO: add code that sets a record in the DNS provider's console 95 | return nil 96 | } 97 | 98 | // CleanUp should delete the relevant TXT record from the DNS provider console. 99 | // If multiple TXT records exist with the same record name (e.g. 100 | // _acme-challenge.example.com) then **only** the record with the same `key` 101 | // value provided on the ChallengeRequest should be cleaned up. 102 | // This is in order to facilitate multiple DNS validations for the same domain 103 | // concurrently. 104 | func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { 105 | // TODO: add code that deletes a record from the DNS provider's console 106 | return nil 107 | } 108 | 109 | // Initialize will be called when the webhook first starts. 110 | // This method can be used to instantiate the webhook, i.e. initialising 111 | // connections or warming up caches. 112 | // Typically, the kubeClientConfig parameter is used to build a Kubernetes 113 | // client that can be used to fetch resources from the Kubernetes API, e.g. 114 | // Secret resources containing credentials used to authenticate with DNS 115 | // provider accounts. 116 | // The stopCh can be used to handle early termination of the webhook, in cases 117 | // where a SIGTERM or similar signal is sent to the webhook process. 118 | func (c *customDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error { 119 | ///// UNCOMMENT THE BELOW CODE TO MAKE A KUBERNETES CLIENTSET AVAILABLE TO 120 | ///// YOUR CUSTOM DNS PROVIDER 121 | 122 | //cl, err := kubernetes.NewForConfig(kubeClientConfig) 123 | //if err != nil { 124 | // return err 125 | //} 126 | // 127 | //c.client = cl 128 | 129 | ///// END OF CODE TO MAKE KUBERNETES CLIENTSET AVAILABLE 130 | return nil 131 | } 132 | 133 | // loadConfig is a small helper function that decodes JSON configuration into 134 | // the typed config struct. 135 | func loadConfig(cfgJSON *extapi.JSON) (customDNSProviderConfig, error) { 136 | cfg := customDNSProviderConfig{} 137 | // handle the 'base case' where no configuration has been provided 138 | if cfgJSON == nil { 139 | return cfg, nil 140 | } 141 | if err := json.Unmarshal(cfgJSON.Raw, &cfg); err != nil { 142 | return cfg, fmt.Errorf("error decoding solver config: %v", err) 143 | } 144 | 145 | return cfg, nil 146 | } 147 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | acmetest "github.com/cert-manager/cert-manager/test/acme" 8 | 9 | "github.com/cert-manager/webhook-example/example" 10 | ) 11 | 12 | var ( 13 | zone = os.Getenv("TEST_ZONE_NAME") 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 | 22 | // Uncomment the below fixture when implementing your custom DNS provider 23 | //fixture := acmetest.NewFixture(&customDNSProviderSolver{}, 24 | // acmetest.SetResolvedZone(zone), 25 | // acmetest.SetAllowAmbientCredentials(false), 26 | // acmetest.SetManifestPath("testdata/my-custom-solver"), 27 | // acmetest.SetBinariesPath("_test/kubebuilder/bin"), 28 | //) 29 | solver := example.New("59351") 30 | fixture := acmetest.NewFixture(solver, 31 | acmetest.SetResolvedZone("example.com."), 32 | acmetest.SetManifestPath("testdata/my-custom-solver"), 33 | acmetest.SetDNSServer("127.0.0.1:59351"), 34 | acmetest.SetUseAuthoritative(false), 35 | ) 36 | //need to uncomment and RunConformance delete runBasic and runExtended once https://github.com/cert-manager/cert-manager/pull/4835 is merged 37 | //fixture.RunConformance(t) 38 | fixture.RunBasic(t) 39 | fixture.RunExtended(t) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /testdata/my-custom-solver/README.md: -------------------------------------------------------------------------------- 1 | # Solver testdata directory 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /testdata/my-custom-solver/config.json: -------------------------------------------------------------------------------- 1 | {} 2 | --------------------------------------------------------------------------------