├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── workflows │ ├── merge.yaml │ ├── pull-request.yaml │ └── release.yml ├── .gitignore ├── .mailmap ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── deploy └── bundle.yaml ├── go.mod ├── go.sum ├── main.go ├── pkg ├── dns │ ├── config.go │ ├── solver.go │ ├── utils.go │ └── utils_test.go └── util │ └── version.go └── tests ├── suite_test.go └── testdata ├── config.json └── secret.yaml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Details (please complete the following information):** 20 | - Scaleway Cert Manager Webhook version: 21 | - Platform: 22 | - Kubernetes version: 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | ## Proposed Changes 4 | - 5 | -------------------------------------------------------------------------------- /.github/workflows/merge.yaml: -------------------------------------------------------------------------------- 1 | name: merge 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | jobs: 7 | merge_main: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Unshallow 13 | run: git fetch --prune --unshallow 14 | - name: Extract branch name 15 | shell: bash 16 | run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" 17 | id: extract_branch 18 | - name: Enable experimental on dockerd 19 | run: | 20 | echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json 21 | sudo service docker restart 22 | - name: Set up Docker Buildx 23 | id: buildx 24 | uses: crazy-max/ghaction-docker-buildx@v1 25 | with: 26 | version: latest 27 | - name: Docker login 28 | run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin 29 | - name: Make latest release 30 | run: make release 31 | env: 32 | DOCKER_CLI_EXPERIMENTAL: enabled 33 | IMAGE_TAG: latest 34 | - name: Make tagged release 35 | run: make release 36 | env: 37 | DOCKER_CLI_EXPERIMENTAL: enabled 38 | IMAGE_TAG: ${{ github.sha }} 39 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: pull-request 2 | 3 | on: 4 | - pull_request 5 | 6 | jobs: 7 | unit-test: 8 | strategy: 9 | matrix: 10 | go-version: [1.22.x] 11 | platform: [ubuntu-latest] 12 | runs-on: ${{ matrix.platform }} 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v1 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - name: checkout 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 1 22 | - name: Run unit tests 23 | run: make test 24 | build-test: 25 | strategy: 26 | matrix: 27 | go-version: [1.22.x] 28 | platform: [ubuntu-latest] 29 | arch: [amd64, arm, arm64] 30 | runs-on: ${{ matrix.platform }} 31 | steps: 32 | - name: Install Go 33 | uses: actions/setup-go@v1 34 | with: 35 | go-version: ${{ matrix.go-version }} 36 | - name: checkout 37 | uses: actions/checkout@v2 38 | with: 39 | fetch-depth: 1 40 | - name: Building binary 41 | run: GOARCH=${{ matrix.arch }} make compile 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Unshallow 13 | run: git fetch --prune --unshallow 14 | - name: Highest tag 15 | id: highestTag 16 | run: | 17 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 18 | echo ::set-output name=highest::$(git tag | grep -E "^v?([0-9]+\.)+[0-9]+$" | sort -r -V | head -n1) 19 | - name: Enable experimental on dockerd 20 | run: | 21 | echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json 22 | sudo service docker restart 23 | - name: Set up Docker Buildx 24 | id: buildx 25 | uses: crazy-max/ghaction-docker-buildx@v1 26 | with: 27 | version: latest 28 | - name: Docker login 29 | run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin 30 | - name: Get tag 31 | uses: little-core-labs/get-git-tag@v3.0.2 32 | id: tagName 33 | - name: Make release 34 | run: make release 35 | env: 36 | DOCKER_CLI_EXPERIMENTAL: enabled 37 | IMAGE_TAG: ${{ steps.tagName.outputs.tag }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | main 10 | cert-manager-webhook-scaleway 11 | 12 | # Test binary, build with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Kubernetes Generated files - skip generated files, except for vendored files 19 | 20 | !vendor/**/zz_generated.* 21 | 22 | # editor and IDE paraphernalia 23 | .idea 24 | *.swp 25 | *.swo 26 | *~ 27 | 28 | tests/kubebuilder/ 29 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Patrik Cyvoct 2 | Patrik Cyvoct 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.2-alpine as builder 2 | 3 | RUN apk update && apk add --no-cache git ca-certificates && update-ca-certificates 4 | 5 | WORKDIR /go/src/github.com/scaleway/cert-manager-webhook-scaleway 6 | 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | RUN go mod download 10 | 11 | COPY main.go main.go 12 | COPY pkg/ pkg/ 13 | 14 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -a -o cert-manager-webhook-scaleway main.go 15 | 16 | FROM scratch 17 | WORKDIR / 18 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 19 | COPY --from=builder /go/src/github.com/scaleway/cert-manager-webhook-scaleway/cert-manager-webhook-scaleway . 20 | ENTRYPOINT ["/cert-manager-webhook-scaleway"] 21 | -------------------------------------------------------------------------------- /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 | OS ?= $(shell go env GOOS) 2 | ARCH ?= $(shell go env GOARCH) 3 | ALL_PLATFORM = linux/amd64,linux/arm/v7,linux/arm64 4 | 5 | # Image URL to use all building/pushing image targets 6 | REGISTRY ?= scaleway 7 | IMAGE ?= cert-manager-webhook-scaleway 8 | FULL_IMAGE ?= $(REGISTRY)/$(IMAGE) 9 | 10 | IMAGE_TAG ?= $(shell git rev-parse HEAD) 11 | 12 | DOCKER_CLI_EXPERIMENTAL ?= enabled 13 | 14 | KUBEBUILDER_VERSION=2.3.1 15 | 16 | TEST_ZONE_NAME ?= example.com. 17 | 18 | # Run tests 19 | test: tests/kubebuilder 20 | TEST_ZONE_NAME=$(TEST_ZONE_NAME) go test -v ./... -coverprofile cover.out 21 | 22 | tests/kubebuilder: 23 | curl -fsSL https://github.com/kubernetes-sigs/kubebuilder/releases/download/v$(KUBEBUILDER_VERSION)/kubebuilder_$(KUBEBUILDER_VERSION)_$(OS)_$(ARCH).tar.gz -o kubebuilder-tools.tar.gz 24 | mkdir tests/kubebuilder 25 | tar -xvf kubebuilder-tools.tar.gz 26 | mv kubebuilder_$(KUBEBUILDER_VERSION)_$(OS)_$(ARCH)/bin tests/kubebuilder/ 27 | rm kubebuilder-tools.tar.gz 28 | rm -R kubebuilder_$(KUBEBUILDER_VERSION)_$(OS)_$(ARCH) 29 | 30 | clean-kubebuilder: 31 | rm -Rf tests/kubebuilder 32 | 33 | compile: 34 | go build -v -o cert-manager-webhook-scaleway main.go 35 | 36 | docker-build: 37 | @echo "Building cert-manager-webhook-scaleway for $(ARCH)" 38 | docker build . --platform=$(OS)/$(ARCH) -f Dockerfile -t $(FULL_IMAGE):$(IMAGE_TAG)-$(ARCH) 39 | 40 | docker-buildx-all: 41 | @echo "Making release for tag $(IMAGE_TAG)" 42 | docker buildx build --platform=$(ALL_PLATFORM) --push -t $(FULL_IMAGE):$(IMAGE_TAG) . 43 | 44 | release: docker-buildx-all 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cert-manager Webhook for Scaleway DNS 2 | 3 | cert-manager Webhook for Scaleway DNS is a ACME [webhook](https://cert-manager.io/docs/configuration/acme/dns01/webhook/) for [cert-manager](https://cert-manager.io/) allowing users to use [Scaleway DNS](https://www.scaleway.com/en/docs/scaleway-dns/) for DNS01 challenge. 4 | 5 | ## Getting started 6 | 7 | ### Prerequisites 8 | 9 | - A [Scaleway Access Key and a Scaleway Secret Key](https://www.scaleway.com/en/docs/generate-api-keys/) 10 | - A valid domain configured on [Scaleway DNS](https://www.scaleway.com/en/docs/scaleway-dns/) 11 | - A Kubernetes cluster (v1.29+ recommended) 12 | - [Helm 3](https://helm.sh/) [installed](https://helm.sh/docs/intro/install/) on your computer 13 | - cert-manager [deployed](https://cert-manager.io/docs/installation/) on the cluster 14 | 15 | ### Installing 16 | 17 | > Attention: starting from `0.1.0` the chart's name is now named `scaleway-certmanager-webhook`, if upgrading from an older version you might want to add `--set nameOverride=scaleway-webhook` 18 | 19 | - Add scaleway's helm chart repository: 20 | 21 | ```bash 22 | helm repo add scaleway https://helm.scw.cloud/ 23 | helm repo update 24 | ``` 25 | 26 | - Install the chart 27 | 28 | ```bash 29 | helm install scaleway-certmanager-webhook scaleway/scaleway-certmanager-webhook 30 | ``` 31 | 32 | - Alternatively, you can install the webhook with default credentials with: 33 | 34 | ```bash 35 | helm install scaleway-certmanager-webhook scaleway/scaleway-certmanager-webhook --set secret.accessKey= --set secret.secretKey= 36 | ``` 37 | 38 | The Scaleway Webhook is now installed! :tada: 39 | 40 | > Refer to the chart's [documentation](https://github.com/scaleway/helm-charts/blob/master/charts/scaleway-certmanager-webhook/README.md) for more configuration options. 41 | 42 | > Alternatively, you may use the provided bundle for a basic install in the cert-manager namespace: 43 | > `kubectl apply -f https://raw.githubusercontent.com/scaleway/cert-manager-webhook-scaleway/main/deploy/bundle.yaml` 44 | 45 | ### How to use it 46 | 47 | **Note**: It uses the [cert-manager webhook system](https://cert-manager.io/docs/configuration/acme/dns01/webhook/). Everything after the issuer is configured is just cert-manager. You can find out more in [their documentation](https://cert-manager.io/docs/usage/). 48 | 49 | Now that the webhook is installed, here is how to use it. 50 | Let's say you need a certificate for `example.com` (should be registered in Scaleway DNS). 51 | 52 | First step is to create a secret containing the Scaleway Access and Secret keys. Create the `scaleway-secret.yaml` file with the following content: 53 | (Only needed if you don't have default credentials as seen above). 54 | ```yaml 55 | apiVersion: v1 56 | stringData: 57 | SCW_ACCESS_KEY: 58 | SCW_SECRET_KEY: 59 | kind: Secret 60 | metadata: 61 | name: scaleway-secret 62 | type: Opaque 63 | ``` 64 | 65 | And run: 66 | ```bash 67 | kubectl create -f scaleway-secret.yaml 68 | ``` 69 | 70 | Next step is to create a cert-manager `Issuer`. Create a `issuer.yaml` file with the following content: 71 | ```yaml 72 | apiVersion: cert-manager.io/v1 73 | kind: Issuer 74 | metadata: 75 | name: my-scaleway-issuer 76 | spec: 77 | acme: 78 | email: my-user@example.com 79 | # this is the acme staging URL 80 | server: https://acme-staging-v02.api.letsencrypt.org/directory 81 | # for production use this URL instead 82 | # server: https://acme-v02.api.letsencrypt.org/directory 83 | privateKeySecretRef: 84 | name: my-scaleway-private-key-secret 85 | solvers: 86 | - dns01: 87 | webhook: 88 | groupName: acme.scaleway.com 89 | solverName: scaleway 90 | config: 91 | # Only needed if you don't have default credentials as seen above. 92 | accessKeySecretRef: 93 | key: SCW_ACCESS_KEY 94 | name: scaleway-secret 95 | secretKeySecretRef: 96 | key: SCW_SECRET_KEY 97 | name: scaleway-secret 98 | ``` 99 | 100 | And run: 101 | ```bash 102 | kubectl create -f issuer.yaml 103 | ``` 104 | 105 | Finally, you can now create the `Certificate` object for `example.com`. Create a `certificate.yaml` file with the following content: 106 | ```yaml 107 | apiVersion: cert-manager.io/v1 108 | kind: Certificate 109 | metadata: 110 | name: example-com 111 | spec: 112 | dnsNames: 113 | - example.com 114 | issuerRef: 115 | name: my-scaleway-issuer 116 | secretName: example-com-tls 117 | ``` 118 | 119 | And run: 120 | ```bash 121 | kubectl create -f certificate.yaml 122 | ``` 123 | 124 | After some seconds, you should see the certificate as ready: 125 | ```bash 126 | $ kubectl get certificate example-com 127 | NAME READY SECRET AGE 128 | example-com True example-com-tls 1m12s 129 | ``` 130 | 131 | Your certificate is now available in the `example-com-tls` secret! 132 | 133 | ## Integration testing 134 | 135 | Before running the test, you need: 136 | - A valid domain on Scaleway DNS (here `example.com`) 137 | - The variables `SCW_ACCESS_KEY` and `SCW_SECRET_KEY` valid and in the environment 138 | 139 | In order to run the integration tests, run: 140 | ```bash 141 | TEST_ZONE_NAME=example.com make test 142 | ``` 143 | -------------------------------------------------------------------------------- /deploy/bundle.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: scaleway-certmanager-webhook 6 | namespace: cert-manager 7 | labels: 8 | app: scaleway-certmanager-webhook 9 | --- 10 | # Grant cert-manager permission to validate using our apiserver 11 | apiVersion: rbac.authorization.k8s.io/v1 12 | kind: ClusterRole 13 | metadata: 14 | name: scaleway-certmanager-webhook:domain-solver 15 | labels: 16 | app: scaleway-certmanager-webhook 17 | rules: 18 | - apiGroups: 19 | - acme.scaleway.com 20 | resources: 21 | - '*' 22 | verbs: 23 | - 'create' 24 | --- 25 | # apiserver gets the auth-delegator role to delegate auth decisions to 26 | # the core apiserver 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | name: scaleway-certmanager-webhook:auth-delegator 31 | labels: 32 | app: scaleway-certmanager-webhook 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: system:auth-delegator 37 | subjects: 38 | - apiGroup: "" 39 | kind: ServiceAccount 40 | name: scaleway-certmanager-webhook 41 | namespace: cert-manager 42 | --- 43 | apiVersion: rbac.authorization.k8s.io/v1 44 | kind: ClusterRoleBinding 45 | metadata: 46 | name: scaleway-certmanager-webhook:domain-solver 47 | labels: 48 | app: scaleway-certmanager-webhook 49 | roleRef: 50 | apiGroup: rbac.authorization.k8s.io 51 | kind: ClusterRole 52 | name: scaleway-certmanager-webhook:domain-solver 53 | subjects: 54 | - apiGroup: "" 55 | kind: ServiceAccount 56 | name: cert-manager 57 | namespace: cert-manager 58 | --- 59 | # Grant the webhook permission to read the secrets containing the credentials 60 | apiVersion: rbac.authorization.k8s.io/v1 61 | kind: Role 62 | metadata: 63 | name: scaleway-certmanager-webhook:secrets-reader 64 | namespace: cert-manager 65 | labels: 66 | app: scaleway-certmanager-webhook 67 | rules: 68 | - apiGroups: 69 | - '' 70 | resources: 71 | - 'secrets' 72 | verbs: 73 | - 'get' 74 | --- 75 | # Grant the webhook permission to read the secrets containing the credentials 76 | apiVersion: rbac.authorization.k8s.io/v1 77 | kind: RoleBinding 78 | metadata: 79 | name: scaleway-certmanager-webhook:secrets-reader 80 | namespace: cert-manager 81 | labels: 82 | app: scaleway-certmanager-webhook 83 | roleRef: 84 | apiGroup: rbac.authorization.k8s.io 85 | kind: Role 86 | name: scaleway-certmanager-webhook:secrets-reader 87 | subjects: 88 | - apiGroup: "" 89 | kind: ServiceAccount 90 | name: scaleway-certmanager-webhook 91 | namespace: cert-manager 92 | --- 93 | # Grant the webhook permission to read the ConfigMap containing the Kubernetes 94 | # apiserver's requestheader-ca-certificate. 95 | # This ConfigMap is automatically created by the Kubernetes apiserver. 96 | apiVersion: rbac.authorization.k8s.io/v1 97 | kind: RoleBinding 98 | metadata: 99 | name: scaleway-certmanager-webhook:webhook-authentication-reader 100 | namespace: kube-system 101 | labels: 102 | app: scaleway-certmanager-webhook 103 | roleRef: 104 | apiGroup: rbac.authorization.k8s.io 105 | kind: Role 106 | name: extension-apiserver-authentication-reader 107 | subjects: 108 | - apiGroup: "" 109 | kind: ServiceAccount 110 | name: scaleway-certmanager-webhook 111 | namespace: cert-manager 112 | --- 113 | apiVersion: v1 114 | kind: Service 115 | metadata: 116 | name: scaleway-certmanager-webhook 117 | namespace: cert-manager 118 | labels: 119 | app: scaleway-certmanager-webhook 120 | spec: 121 | type: ClusterIP 122 | ports: 123 | - port: 443 124 | targetPort: https 125 | protocol: TCP 126 | name: https 127 | selector: 128 | app: scaleway-certmanager-webhook 129 | --- 130 | apiVersion: apps/v1 131 | kind: Deployment 132 | metadata: 133 | name: scaleway-certmanager-webhook 134 | namespace: cert-manager 135 | labels: 136 | app: scaleway-certmanager-webhook 137 | spec: 138 | replicas: 1 139 | selector: 140 | matchLabels: 141 | app: scaleway-certmanager-webhook 142 | template: 143 | metadata: 144 | labels: 145 | app: scaleway-certmanager-webhook 146 | spec: 147 | serviceAccountName: scaleway-certmanager-webhook 148 | containers: 149 | - name: scaleway-certmanager-webhook 150 | image: "scaleway/cert-manager-webhook-scaleway:v0.1.0" 151 | imagePullPolicy: IfNotPresent 152 | args: 153 | - --tls-cert-file=/tls/tls.crt 154 | - --tls-private-key-file=/tls/tls.key 155 | env: 156 | - name: GROUP_NAME 157 | value: "acme.scaleway.com" 158 | 159 | ports: 160 | - name: https 161 | containerPort: 443 162 | protocol: TCP 163 | livenessProbe: 164 | httpGet: 165 | scheme: HTTPS 166 | path: /healthz 167 | port: https 168 | readinessProbe: 169 | timeoutSeconds: 5 170 | httpGet: 171 | scheme: HTTPS 172 | path: /healthz 173 | port: https 174 | volumeMounts: 175 | - name: certs 176 | mountPath: /tls 177 | readOnly: true 178 | resources: 179 | {} 180 | volumes: 181 | - name: certs 182 | secret: 183 | secretName: scaleway-certmanager-webhook-webhook-tls 184 | --- 185 | apiVersion: apiregistration.k8s.io/v1 186 | kind: APIService 187 | metadata: 188 | name: v1alpha1.acme.scaleway.com 189 | labels: 190 | app: scaleway-certmanager-webhook 191 | annotations: 192 | cert-manager.io/inject-ca-from: "cert-manager/scaleway-certmanager-webhook-webhook-tls" 193 | spec: 194 | group: acme.scaleway.com 195 | groupPriorityMinimum: 1000 196 | versionPriority: 15 197 | service: 198 | name: scaleway-certmanager-webhook 199 | namespace: cert-manager 200 | version: v1alpha1 201 | --- 202 | # Generate a CA Certificate used to sign certificates for the webhook 203 | apiVersion: cert-manager.io/v1 204 | kind: Certificate 205 | metadata: 206 | name: scaleway-certmanager-webhook-ca 207 | namespace: "cert-manager" 208 | labels: 209 | app: scaleway-certmanager-webhook 210 | spec: 211 | secretName: scaleway-certmanager-webhook-ca 212 | duration: 43800h 213 | issuerRef: 214 | name: scaleway-certmanager-webhook-selfsign 215 | commonName: "ca.scaleway-webhook.cert-manager" 216 | isCA: true 217 | --- 218 | # Finally, generate a serving certificate for the webhook to use 219 | apiVersion: cert-manager.io/v1 220 | kind: Certificate 221 | metadata: 222 | name: scaleway-certmanager-webhook-webhook-tls 223 | namespace: "cert-manager" 224 | labels: 225 | app: scaleway-certmanager-webhook 226 | spec: 227 | secretName: scaleway-certmanager-webhook-webhook-tls 228 | duration: 8760h 229 | issuerRef: 230 | name: scaleway-certmanager-webhook-ca 231 | dnsNames: 232 | - scaleway-certmanager-webhook 233 | - scaleway-certmanager-webhook.cert-manager 234 | - scaleway-certmanager-webhook.cert-manager.svc 235 | --- 236 | # Create a selfsigned Issuer, in order to create a root CA certificate for 237 | # signing webhook serving certificates 238 | apiVersion: cert-manager.io/v1 239 | kind: Issuer 240 | metadata: 241 | name: scaleway-certmanager-webhook-selfsign 242 | namespace: "cert-manager" 243 | labels: 244 | app: scaleway-certmanager-webhook 245 | spec: 246 | selfSigned: {} 247 | --- 248 | # Create an Issuer that uses the above generated CA certificate to issue certs 249 | apiVersion: cert-manager.io/v1 250 | kind: Issuer 251 | metadata: 252 | name: scaleway-certmanager-webhook-ca 253 | namespace: "cert-manager" 254 | labels: 255 | app: scaleway-certmanager-webhook 256 | spec: 257 | ca: 258 | secretName: scaleway-certmanager-webhook-ca 259 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/scaleway/cert-manager-webhook-scaleway 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.2 6 | 7 | require ( 8 | github.com/cert-manager/cert-manager v1.14.5 9 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 10 | k8s.io/api v0.30.0 11 | k8s.io/apiextensions-apiserver v0.30.0 12 | k8s.io/apimachinery v0.30.0 13 | k8s.io/client-go v0.30.0 14 | ) 15 | 16 | require ( 17 | github.com/NYTimes/gziphandler v1.1.1 // indirect 18 | github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect 19 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 20 | github.com/beorn7/perks v1.0.1 // indirect 21 | github.com/blang/semver/v4 v4.0.0 // indirect 22 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 24 | github.com/coreos/go-semver v0.3.1 // indirect 25 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 27 | github.com/emicklei/go-restful/v3 v3.12.0 // indirect 28 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 29 | github.com/evanphx/json-patch/v5 v5.7.0 // indirect 30 | github.com/felixge/httpsnoop v1.0.4 // indirect 31 | github.com/fsnotify/fsnotify v1.7.0 // indirect 32 | github.com/go-logr/logr v1.4.1 // indirect 33 | github.com/go-logr/stdr v1.2.2 // indirect 34 | github.com/go-logr/zapr v1.3.0 // indirect 35 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 36 | github.com/go-openapi/jsonreference v0.21.0 // indirect 37 | github.com/go-openapi/swag v0.23.0 // indirect 38 | github.com/gogo/protobuf v1.3.2 // indirect 39 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 40 | github.com/golang/protobuf v1.5.4 // indirect 41 | github.com/google/cel-go v0.17.8 // indirect 42 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect 43 | github.com/google/go-cmp v0.6.0 // indirect 44 | github.com/google/gofuzz v1.2.0 // indirect 45 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect 46 | github.com/google/uuid v1.6.0 // indirect 47 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 48 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect 49 | github.com/imdario/mergo v0.3.16 // indirect 50 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 51 | github.com/josharian/intern v1.0.0 // indirect 52 | github.com/json-iterator/go v1.1.12 // indirect 53 | github.com/mailru/easyjson v0.7.7 // indirect 54 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 55 | github.com/miekg/dns v1.1.57 // indirect 56 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 57 | github.com/modern-go/reflect2 v1.0.2 // indirect 58 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 59 | github.com/pkg/errors v0.9.1 // indirect 60 | github.com/prometheus/client_golang v1.18.0 // indirect 61 | github.com/prometheus/client_model v0.6.1 // indirect 62 | github.com/prometheus/common v0.45.0 // indirect 63 | github.com/prometheus/procfs v0.14.0 // indirect 64 | github.com/spf13/cobra v1.8.0 // indirect 65 | github.com/spf13/pflag v1.0.5 // indirect 66 | github.com/stoewer/go-strcase v1.3.0 // indirect 67 | go.etcd.io/etcd/api/v3 v3.5.13 // indirect 68 | go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect 69 | go.etcd.io/etcd/client/v3 v3.5.13 // indirect 70 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect 71 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect 72 | go.opentelemetry.io/otel v1.26.0 // indirect 73 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect 74 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect 75 | go.opentelemetry.io/otel/metric v1.26.0 // indirect 76 | go.opentelemetry.io/otel/sdk v1.26.0 // indirect 77 | go.opentelemetry.io/otel/trace v1.26.0 // indirect 78 | go.opentelemetry.io/proto/otlp v1.2.0 // indirect 79 | go.uber.org/multierr v1.11.0 // indirect 80 | go.uber.org/zap v1.27.0 // indirect 81 | golang.org/x/crypto v0.22.0 // indirect 82 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect 83 | golang.org/x/mod v0.17.0 // indirect 84 | golang.org/x/net v0.24.0 // indirect 85 | golang.org/x/oauth2 v0.19.0 // indirect 86 | golang.org/x/sync v0.7.0 // indirect 87 | golang.org/x/sys v0.19.0 // indirect 88 | golang.org/x/term v0.19.0 // indirect 89 | golang.org/x/text v0.14.0 // indirect 90 | golang.org/x/time v0.5.0 // indirect 91 | golang.org/x/tools v0.20.0 // indirect 92 | google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect 93 | google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect 94 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect 95 | google.golang.org/grpc v1.63.2 // indirect 96 | google.golang.org/protobuf v1.33.0 // indirect 97 | gopkg.in/inf.v0 v0.9.1 // indirect 98 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 99 | gopkg.in/yaml.v2 v2.4.0 // indirect 100 | gopkg.in/yaml.v3 v3.0.1 // indirect 101 | k8s.io/apiserver v0.30.0 // indirect 102 | k8s.io/component-base v0.30.0 // indirect 103 | k8s.io/klog/v2 v2.120.1 // indirect 104 | k8s.io/kms v0.30.0 // indirect 105 | k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect 106 | k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 // indirect 107 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.2 // indirect 108 | sigs.k8s.io/controller-runtime v0.16.3 // indirect 109 | sigs.k8s.io/gateway-api v1.0.0 // indirect 110 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 111 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 112 | sigs.k8s.io/yaml v1.4.0 // indirect 113 | ) 114 | -------------------------------------------------------------------------------- /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.14.5 h1:uuM1O2g2S80nxiH3eW2cZYMGiL2zmDFVdAzg8sibWuc= 14 | github.com/cert-manager/cert-manager v1.14.5/go.mod h1:fmr/cU5jiLxWj69CroDggSOa49RljUK+dU583TaQUXM= 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.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= 33 | github.com/evanphx/json-patch/v5 v5.7.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 v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 53 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 54 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 55 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 56 | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= 57 | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 58 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 59 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 60 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 61 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 62 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 63 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 64 | github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= 65 | github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= 66 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= 67 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= 68 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 69 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 70 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 71 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 72 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 73 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 74 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 75 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 76 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 77 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 78 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 79 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 80 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 81 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 82 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 83 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 84 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 85 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 86 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= 87 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= 88 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 89 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 90 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 91 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 92 | github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= 93 | github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 94 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 95 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 96 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 97 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 98 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 99 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 100 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 101 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 102 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 103 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 104 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 105 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 106 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 107 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 108 | github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= 109 | github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= 110 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 111 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 112 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 113 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 114 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 115 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 116 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 117 | github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= 118 | github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 119 | github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= 120 | github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= 121 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 122 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 123 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 124 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 125 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 126 | github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= 127 | github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= 128 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 129 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 130 | github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 131 | github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 132 | github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= 133 | github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= 134 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 135 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 136 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 137 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 h1:F+GIVtGqCFxPxO46ujf8cEOP574MBoRm3gNbPXECbxs= 138 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= 139 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 140 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 141 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 142 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 143 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 144 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 145 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 146 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 147 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 148 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 149 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 150 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 151 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 152 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 153 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 154 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 155 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 156 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 157 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 158 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= 159 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= 160 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 161 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 162 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 163 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 164 | go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= 165 | go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= 166 | go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4= 167 | go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= 168 | go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg= 169 | go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= 170 | go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= 171 | go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= 172 | go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js= 173 | go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI= 174 | go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM= 175 | go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs= 176 | go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA= 177 | go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6jbGRc= 178 | go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg= 179 | go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo= 180 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= 181 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= 182 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= 183 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= 184 | go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= 185 | go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= 186 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= 187 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= 188 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= 189 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= 190 | go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= 191 | go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= 192 | go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= 193 | go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= 194 | go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= 195 | go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= 196 | go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= 197 | go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= 198 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 199 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 200 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 201 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 202 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 203 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 204 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 205 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 206 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 207 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 208 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 209 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= 210 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 211 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 212 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 213 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 214 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 215 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 216 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 217 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 218 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 219 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 220 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 221 | golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= 222 | golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= 223 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 224 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 225 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 226 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 227 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 228 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 229 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 230 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 231 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 232 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 233 | golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 234 | golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= 235 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 236 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 237 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 238 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 239 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 240 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 241 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 242 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 243 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 244 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 245 | golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= 246 | golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= 247 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 248 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 249 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 250 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 251 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 252 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 253 | google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= 254 | google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= 255 | google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= 256 | google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= 257 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= 258 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 259 | google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= 260 | google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= 261 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 262 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 263 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 264 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 265 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 266 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 267 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 268 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 269 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 270 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 271 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 272 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 273 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 274 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 275 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 276 | k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= 277 | k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= 278 | k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= 279 | k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= 280 | k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= 281 | k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= 282 | k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= 283 | k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= 284 | k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= 285 | k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= 286 | k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= 287 | k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= 288 | k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= 289 | k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 290 | k8s.io/kms v0.30.0 h1:ZlnD/ei5lpvUlPw6eLfVvH7d8i9qZ6HwUQgydNVks8g= 291 | k8s.io/kms v0.30.0/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4= 292 | k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY= 293 | k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 294 | k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 h1:ao5hUqGhsqdm+bYbjH/pRkCs0unBGe9UyDahzs9zQzQ= 295 | k8s.io/utils v0.0.0-20240423183400-0849a56e8f22/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 296 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.2 h1:N2wvoG4CkNqORML7GHY9xkGKxswDhpAD46poBd/hHHg= 297 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 298 | sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= 299 | sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= 300 | sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs= 301 | sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c= 302 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 303 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 304 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 305 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 306 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 307 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 308 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/cmd" 7 | 8 | "github.com/scaleway/cert-manager-webhook-scaleway/pkg/dns" 9 | ) 10 | 11 | // GroupName is the name under which the webhook will be available 12 | var GroupName = os.Getenv("GROUP_NAME") 13 | 14 | func main() { 15 | if GroupName == "" { 16 | panic("GROUP_NAME must be specified") 17 | } 18 | 19 | cmd.RunWebhookServer(GroupName, 20 | &dns.ProviderSolver{}, 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/dns/config.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "k8s.io/api/core/v1" 5 | ) 6 | 7 | // ProviderConfig represents the config used for Scaleway DNS 8 | type ProviderConfig struct { 9 | AccessKey *v1.SecretKeySelector `json:"accessKeySecretRef,omitempty"` 10 | SecretKey *v1.SecretKeySelector `json:"secretKeySecretRef,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /pkg/dns/solver.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 9 | domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" 10 | "github.com/scaleway/scaleway-sdk-go/scw" 11 | 12 | "k8s.io/client-go/kubernetes" 13 | "k8s.io/client-go/rest" 14 | ) 15 | 16 | const ( 17 | providerName = "scaleway" 18 | ) 19 | 20 | // ProviderSolver is the struct implementing the webhook.Solver interface 21 | // for Scaleway DNS 22 | type ProviderSolver struct { 23 | client kubernetes.Interface 24 | } 25 | 26 | // Name is used as the name for this DNS solver when referencing it on the ACME 27 | // Issuer resource 28 | func (p *ProviderSolver) Name() string { 29 | return providerName 30 | } 31 | 32 | // Present is responsible for actually presenting the DNS record with the 33 | // DNS provider. 34 | // This method should tolerate being called multiple times with the same value. 35 | // cert-manager itself will later perform a self check to ensure that the 36 | // solver has correctly configured the DNS provider. 37 | func (p *ProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { 38 | domainAPI, err := p.getDomainAPI(ch) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | request := &domain.UpdateDNSZoneRecordsRequest{ 44 | DNSZone: strings.TrimSuffix(ch.ResolvedZone, "."), 45 | Changes: []*domain.RecordChange{ 46 | { 47 | Set: &domain.RecordChangeSet{ 48 | IDFields: &domain.RecordIdentifier{ 49 | Name: strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), "."), 50 | Type: domain.RecordTypeTXT, 51 | Data: scw.StringPtr(strconv.Quote(ch.Key)), 52 | }, 53 | Records: []*domain.Record{ 54 | { 55 | Name: strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), "."), 56 | Data: strconv.Quote(ch.Key), 57 | Type: domain.RecordTypeTXT, 58 | TTL: 60, 59 | }, 60 | }, 61 | }, 62 | }, 63 | }, 64 | } 65 | 66 | _, err = domainAPI.UpdateDNSZoneRecords(request) 67 | if err != nil { 68 | return fmt.Errorf("failed to update DNS zone recrds: %w", err) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | // CleanUp should delete the relevant TXT record from the DNS provider console. 75 | // If multiple TXT records exist with the same record name (e.g. 76 | // _acme-challenge.example.com) then **only** the record with the same `key` 77 | // value provided on the ChallengeRequest should be cleaned up. 78 | // This is in order to facilitate multiple DNS validations for the same domain 79 | // concurrently. 80 | func (p *ProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { 81 | domainAPI, err := p.getDomainAPI(ch) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | request := &domain.UpdateDNSZoneRecordsRequest{ 87 | DNSZone: strings.TrimSuffix(ch.ResolvedZone, "."), 88 | Changes: []*domain.RecordChange{ 89 | { 90 | Delete: &domain.RecordChangeDelete{ 91 | IDFields: &domain.RecordIdentifier{ 92 | Name: strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), "."), 93 | Data: scw.StringPtr(strconv.Quote(ch.Key)), 94 | Type: domain.RecordTypeTXT, 95 | }, 96 | }, 97 | }, 98 | }, 99 | } 100 | 101 | _, err = domainAPI.UpdateDNSZoneRecords(request) 102 | if err != nil { 103 | return fmt.Errorf("failed to update DNS zone recrds: %w", err) 104 | } 105 | 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 (p *ProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error { 119 | 120 | cl, err := kubernetes.NewForConfig(kubeClientConfig) 121 | if err != nil { 122 | return fmt.Errorf("failed to get kubernetes client: %w", err) 123 | } 124 | 125 | p.client = cl 126 | 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /pkg/dns/utils.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 10 | "github.com/scaleway/cert-manager-webhook-scaleway/pkg/util" 11 | domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" 12 | "github.com/scaleway/scaleway-sdk-go/scw" 13 | extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | ) 16 | 17 | // loadConfig is a small helper function that decodes JSON configuration into 18 | // the typed config struct. 19 | func loadConfig(cfgJSON *extapi.JSON) (ProviderConfig, error) { 20 | cfg := ProviderConfig{} 21 | // handle the 'base case' where no configuration has been provided 22 | if cfgJSON == nil { 23 | return cfg, nil 24 | } 25 | if err := json.Unmarshal(cfgJSON.Raw, &cfg); err != nil { 26 | return cfg, fmt.Errorf("error decoding solver config: %v", err) 27 | } 28 | 29 | return cfg, nil 30 | } 31 | 32 | func (p *ProviderSolver) getDomainAPI(ch *v1alpha1.ChallengeRequest) (*domain.API, error) { 33 | config, err := loadConfig(ch.Config) 34 | if err != nil { 35 | return nil, fmt.Errorf("failed to load config: %w", err) 36 | } 37 | 38 | accessKey := os.Getenv(scw.ScwAccessKeyEnv) 39 | secretKey := os.Getenv(scw.ScwSecretKeyEnv) 40 | 41 | if config.AccessKey != nil && config.SecretKey != nil { 42 | accessKeySecret, err := p.client.CoreV1().Secrets(ch.ResourceNamespace).Get(context.Background(), config.AccessKey.Name, metav1.GetOptions{}) 43 | if err != nil { 44 | return nil, fmt.Errorf("could not get secret %s: %w", config.AccessKey.Name, err) 45 | } 46 | secretKeySecret, err := p.client.CoreV1().Secrets(ch.ResourceNamespace).Get(context.Background(), config.SecretKey.Name, metav1.GetOptions{}) 47 | if err != nil { 48 | return nil, fmt.Errorf("could not get secret %s: %w", config.SecretKey.Name, err) 49 | } 50 | 51 | accessKeyData, ok := accessKeySecret.Data[config.AccessKey.Key] 52 | if !ok { 53 | return nil, fmt.Errorf("could not get key %s in secret %s", config.AccessKey.Key, config.AccessKey.Name) 54 | } 55 | 56 | secretKeyData, ok := secretKeySecret.Data[config.SecretKey.Key] 57 | if !ok { 58 | return nil, fmt.Errorf("could not get key %s in secret %s", config.SecretKey.Key, config.SecretKey.Name) 59 | } 60 | 61 | accessKey = string(accessKeyData) 62 | secretKey = string(secretKeyData) 63 | } 64 | 65 | scwClient, err := scw.NewClient( 66 | scw.WithEnv(), 67 | scw.WithAuth(accessKey, secretKey), 68 | scw.WithUserAgent("cert-manager-webhook-scaleway/"+util.GetVersion().Version), 69 | ) 70 | if err != nil { 71 | return nil, fmt.Errorf("failed to initialize scaleway client: %w", err) 72 | } 73 | 74 | domainAPI := domain.NewAPI(scwClient) 75 | 76 | return domainAPI, nil 77 | } 78 | -------------------------------------------------------------------------------- /pkg/dns/utils_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" 10 | "github.com/scaleway/scaleway-sdk-go/scw" 11 | "k8s.io/api/core/v1" 12 | extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/client-go/kubernetes/fake" 15 | ) 16 | 17 | func Test_loadConfig(t *testing.T) { 18 | testCases := []struct { 19 | json string 20 | config ProviderConfig 21 | shouldErr bool 22 | }{ 23 | { 24 | json: `{ 25 | "accessKeySecretRef": { 26 | "name": "scaleway-secret", 27 | "key": "SCW_ACCESS_KEY" 28 | }, 29 | "secretKeySecretRef": { 30 | "name": "scaleway-secret", 31 | "key": "SCW_SECRET_KEY" 32 | } 33 | }`, 34 | config: ProviderConfig{ 35 | AccessKey: &v1.SecretKeySelector{ 36 | LocalObjectReference: v1.LocalObjectReference{ 37 | Name: "scaleway-secret", 38 | }, 39 | Key: "SCW_ACCESS_KEY", 40 | }, 41 | SecretKey: &v1.SecretKeySelector{ 42 | LocalObjectReference: v1.LocalObjectReference{ 43 | Name: "scaleway-secret", 44 | }, 45 | Key: "SCW_SECRET_KEY", 46 | }, 47 | }, 48 | shouldErr: false, 49 | }, 50 | { 51 | json: `{ 52 | "dummy": } 53 | }`, 54 | shouldErr: true, 55 | }, 56 | { 57 | shouldErr: false, 58 | config: ProviderConfig{}, 59 | }, 60 | } 61 | 62 | for _, test := range testCases { 63 | json := &extapi.JSON{ 64 | Raw: []byte(test.json), 65 | } 66 | if test.json == "" { 67 | json = nil 68 | } 69 | config, err := loadConfig(json) 70 | if err != nil { 71 | if !test.shouldErr { 72 | t.Errorf("got error %v where no error was expected", err) 73 | } 74 | } else if test.shouldErr { 75 | t.Errorf("didn't get an error where an error was expected") 76 | } 77 | if !reflect.DeepEqual(config, test.config) { 78 | t.Errorf("Wrong config value: wanted %v got %v", test.config, config) 79 | } 80 | } 81 | } 82 | 83 | func Test_getDomainAPI(t *testing.T) { 84 | jsonBothKey := &extapi.JSON{ 85 | Raw: []byte(`{ 86 | "accessKeySecretRef": { 87 | "name": "scaleway-secret", 88 | "key": "SCW_ACCESS_KEY" 89 | }, 90 | "secretKeySecretRef": { 91 | "name": "scaleway-secret", 92 | "key": "SCW_SECRET_KEY" 93 | } 94 | }`), 95 | } 96 | 97 | testCases := []struct { 98 | ch *v1alpha1.ChallengeRequest 99 | env map[string]string 100 | secret *v1.Secret 101 | shouldErr bool 102 | errMessage string 103 | }{ 104 | { 105 | ch: &v1alpha1.ChallengeRequest{}, 106 | shouldErr: true, 107 | errMessage: "failed to initialize scaleway client: scaleway-sdk-go: access key cannot be empty", 108 | }, 109 | { 110 | ch: &v1alpha1.ChallengeRequest{}, 111 | env: map[string]string{ 112 | scw.ScwAccessKeyEnv: "SCWXXXXXXXXXXXXXXXXX", 113 | }, 114 | shouldErr: true, 115 | errMessage: "failed to initialize scaleway client: scaleway-sdk-go: secret key cannot be empty", 116 | }, 117 | { 118 | ch: &v1alpha1.ChallengeRequest{}, 119 | env: map[string]string{ 120 | scw.ScwAccessKeyEnv: "SCWXXXXXXXXXXXXXXXXX", 121 | scw.ScwSecretKeyEnv: "66666666-7777-8888-9999-000000000000", 122 | }, 123 | shouldErr: false, 124 | }, 125 | { 126 | ch: &v1alpha1.ChallengeRequest{ 127 | Config: jsonBothKey, 128 | ResourceNamespace: "test", 129 | }, 130 | secret: &v1.Secret{ 131 | ObjectMeta: metav1.ObjectMeta{ 132 | Name: "scaleway-secret", 133 | Namespace: "test", 134 | }, 135 | Data: map[string][]byte{ 136 | scw.ScwAccessKeyEnv: []byte("SCWXXXXXXXXXXXXXXXXX"), 137 | }, 138 | }, 139 | shouldErr: true, 140 | errMessage: "could not get key SCW_SECRET_KEY in secret scaleway-secret", 141 | }, 142 | { 143 | ch: &v1alpha1.ChallengeRequest{ 144 | Config: jsonBothKey, 145 | ResourceNamespace: "test", 146 | }, 147 | secret: &v1.Secret{ 148 | ObjectMeta: metav1.ObjectMeta{ 149 | Name: "scaleway-secret", 150 | Namespace: "test", 151 | }, 152 | Data: map[string][]byte{ 153 | scw.ScwSecretKeyEnv: []byte("66666666-7777-8888-9999-000000000000"), 154 | scw.ScwAccessKeyEnv: []byte("SCWXXXXXXXXXXXXXXXXX"), 155 | }, 156 | }, 157 | shouldErr: false, 158 | }, 159 | } 160 | 161 | for _, test := range testCases { 162 | fakeKubernetesClient := fake.NewSimpleClientset() 163 | pSolver := &ProviderSolver{ 164 | client: fakeKubernetesClient, 165 | } 166 | 167 | if test.secret != nil { 168 | _, err := pSolver.client.CoreV1().Secrets(test.ch.ResourceNamespace).Create(context.Background(), test.secret, metav1.CreateOptions{}) 169 | if err != nil { 170 | t.Errorf("failed to create kubernetes secret") 171 | } 172 | } 173 | for k, v := range test.env { 174 | os.Setenv(k, v) 175 | } 176 | _, err := pSolver.getDomainAPI(test.ch) 177 | if err != nil { 178 | if !test.shouldErr { 179 | t.Errorf("got error %v where no error was expected", err) 180 | } 181 | if err.Error() != test.errMessage { 182 | t.Errorf("expected error %s, got %s", test.errMessage, err.Error()) 183 | } 184 | } else if test.shouldErr { 185 | t.Errorf("didn't get an error where an error was expected with message %s", test.errMessage) 186 | } 187 | for k := range test.env { 188 | os.Unsetenv(k) 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /pkg/util/version.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | // These are set during build time via -ldflags 9 | var ( 10 | version = "0.0.1+dev" 11 | gitCommit string 12 | buildDate string 13 | ) 14 | 15 | // VersionInfo represents the current running version 16 | type VersionInfo struct { 17 | Version string `json:"version"` 18 | GitCommit string `json:"gitCommit"` 19 | BuildDate string `json:"buildDate"` 20 | GoVersion string `json:"goVersion"` 21 | Compiler string `json:"compiler"` 22 | Platform string `json:"platform"` 23 | } 24 | 25 | // GetVersion returns the current running version 26 | func GetVersion() VersionInfo { 27 | return VersionInfo{ 28 | Version: version, 29 | GitCommit: gitCommit, 30 | BuildDate: buildDate, 31 | GoVersion: runtime.Version(), 32 | Compiler: runtime.Compiler, 33 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/suite_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package tests 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | dns "github.com/cert-manager/cert-manager/test/acme" 11 | 12 | scalewayDns "github.com/scaleway/cert-manager-webhook-scaleway/pkg/dns" 13 | ) 14 | 15 | var ( 16 | zone = os.Getenv("TEST_ZONE_NAME") 17 | ) 18 | 19 | func TestRunsSuite(t *testing.T) { 20 | // The manifest path should contain a file named config.json that is a 21 | // snippet of valid configuration that should be included on the 22 | // ChallengeRequest passed as part of the test cases. 23 | 24 | currentDir, err := os.Getwd() 25 | if err != nil { 26 | t.Fatalf("error getting current working dir: %s", err.Error()) 27 | } 28 | 29 | fixture := dns.NewFixture(&scalewayDns.ProviderSolver{}, 30 | dns.SetResolvedZone(zone), 31 | dns.SetAllowAmbientCredentials(true), 32 | dns.SetBinariesPath(currentDir+"/kubebuilder/bin"), 33 | dns.SetManifestPath(currentDir+"/testdata"), 34 | dns.SetStrict(true), 35 | ) 36 | 37 | fixture.RunConformance(t) 38 | } 39 | -------------------------------------------------------------------------------- /tests/testdata/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessKeySecretRef": { 3 | "name": "scaleway-secret", 4 | "key": "SCW_ACCESS_KEY" 5 | }, 6 | "secretKeySecretRef": { 7 | "name": "scaleway-secret", 8 | "key": "SCW_SECRET_KEY" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/testdata/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | stringData: 3 | SCW_ACCESS_KEY: SCWXXXXXXXXXXXXXXXXX 4 | SCW_SECRET_KEY: 66666666-7777-8888-9999-000000000000 5 | kind: Secret 6 | metadata: 7 | name: scaleway-secret 8 | type: Opaque 9 | --------------------------------------------------------------------------------