├── .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 |
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 |
--------------------------------------------------------------------------------