| giolekva | eea069c | 2021-07-19 18:13:08 +0400 | [diff] [blame] | 1 | # ACME webhook for Gandi (cert-manager-webhook-gandi) |
| 2 | `cert-manager-webhook-gandi` is an ACME webhook for [cert-manager]. It provides an ACME (read: Let's Encrypt) webhook for [cert-manager], which allows to use a `DNS-01` challenge with [Gandi]. This allows to provide Let's Encrypt certificates to [Kubernetes] for service protocols other than HTTP and furthermore to request wildcard certificates. Internally it uses the [Gandi LiveDNS API] to communicate with Gandi. |
| 3 | |
| 4 | Quoting the [ACME DNS-01 challenge]: |
| 5 | |
| 6 | > This challenge asks you to prove that you control the DNS for your domain name by putting a specific value in a TXT record under that domain name. It is harder to configure than HTTP-01, but can work in scenarios that HTTP-01 can’t. It also allows you to issue wildcard certificates. After Let’s Encrypt gives your ACME client a token, your client will create a TXT record derived from that token and your account key, and put that record at _acme-challenge.<YOUR_DOMAIN>. Then Let’s Encrypt will query the DNS system for that record. If it finds a match, you can proceed to issue a certificate! |
| 7 | |
| 8 | |
| 9 | ## Building |
| 10 | Build the container image `cert-manager-webhook-gandi:latest`: |
| 11 | |
| 12 | make build |
| 13 | |
| 14 | |
| 15 | ## Image |
| 16 | Ready made images are hosted on Docker Hub ([image tags]). Use at your own risk: |
| 17 | |
| 18 | bwolf/cert-manager-webhook-gandi |
| 19 | |
| 20 | |
| 21 | ### Release History |
| 22 | Refer to the [ChangeLog](ChangeLog.md) file. |
| 23 | |
| 24 | |
| 25 | ## Compatibility |
| 26 | This webhook has been tested with [cert-manager] v0.13.1 and Kubernetes v0.17.x on `amd64`. In theory it should work on other hardware platforms as well but no steps have been taken to verify this. Please drop me a note if you had success. |
| 27 | |
| 28 | |
| 29 | ## Testing with Minikube |
| 30 | 1. Build this webhook in Minikube: |
| 31 | |
| 32 | minikube start --memory=4G --more-options |
| 33 | eval $(minikube docker-env) |
| 34 | make build |
| 35 | docker images | grep webhook |
| 36 | |
| 37 | 2. Install [cert-manager] with [Helm]: |
| 38 | |
| 39 | kubectl create namespace cert-manager |
| 40 | kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/v0.13.1/deploy/manifests/00-crds.yaml |
| 41 | |
| 42 | helm repo add jetstack https://charts.jetstack.io |
| 43 | helm install cert-manager --namespace cert-manager \ |
| 44 | --set 'extraArgs={--dns01-recursive-nameservers=8.8.8.8:53\,1.1.1.1:53}' \ |
| 45 | jetstack/cert-manager |
| 46 | |
| 47 | kubectl get pods --namespace cert-manager --watch |
| 48 | |
| 49 | **Note**: refer to Name servers in the official [documentation][setting-nameservers-for-dns01-self-check] according the `extraArgs`. |
| 50 | |
| 51 | **Note**: ensure that the custom CRDS of cert-manager match the major version of the cert-manager release by comparing the URL of the CRDS with the helm info of the charts app version: |
| 52 | |
| 53 | helm search repo jetstack |
| 54 | |
| 55 | Example output: |
| 56 | |
| 57 | NAME CHART VERSION APP VERSION DESCRIPTION |
| 58 | jetstack/cert-manager v0.13.1 v0.13.1 A Helm chart for cert-manager |
| 59 | |
| 60 | Check the state and ensure that all pods are running fine (watch out for any issues regarding the `cert-manager-webhook-` pod and its volume mounts): |
| 61 | |
| 62 | kubectl describe pods -n cert-manager | less |
| 63 | |
| 64 | |
| 65 | 3. Create the secret to keep the Gandi API key in the default namespace, where later on the Issuer and the Certificate are created: |
| 66 | |
| 67 | kubectl create secret generic gandi-credentials \ |
| 68 | --from-literal=api-token='<GANDI-API-KEY>' |
| 69 | |
| 70 | **Note**: See [RBAC Authorization]: |
| 71 | |
| 72 | > A Role can only be used to grant access to resources within a single namespace. |
| 73 | |
| 74 | *As far as I understand cert-manager, the `Secret` must reside in the same namespace as the `Issuer` and `Certificate` resource.* |
| 75 | |
| 76 | 4. Grant permission for the service-account to access the secret holding the Gandi API key: |
| 77 | |
| 78 | kubectl apply -f rbac.yaml |
| 79 | |
| 80 | 5. Deploy this locally built webhook (add `--dry-run` to try it and `--debug` to inspect the rendered manifests; Set `logLevel` to 6 for verbose logs): |
| 81 | |
| 82 | helm install cert-manager-webhook-gandi \ |
| 83 | --namespace cert-manager \ |
| 84 | --set image.repository=cert-manager-webhook-gandi \ |
| 85 | --set image.tag=latest \ |
| 86 | --set logLevel=2 \ |
| 87 | ./deploy/cert-manager-webhook-gandi |
| 88 | |
| 89 | To deploy using the image from Docker Hub (for example using the `v0.1.1` tag): |
| 90 | |
| 91 | helm install cert-manager-webhook-gandi \ |
| 92 | --namespace cert-manager \ |
| 93 | --set image.tag=v0.1.1 \ |
| 94 | --set logLevel=2 \ |
| 95 | ./deploy/cert-manager-webhook-gandi |
| 96 | |
| 97 | Check the logs |
| 98 | |
| 99 | kubectl get pods -n cert-manager --watch |
| 100 | kubectl logs -n cert-manager cert-manager-webhook-gandi-XYZ |
| 101 | |
| 102 | 6. Create a staging issuer (email addresses with the suffix `example.com` are forbidden): |
| 103 | |
| 104 | cat << EOF | sed "s/invalid@example.com/$email/" | kubectl apply -f - |
| 105 | apiVersion: cert-manager.io/v1alpha2 |
| 106 | kind: Issuer |
| 107 | metadata: |
| 108 | name: letsencrypt-staging |
| 109 | namespace: default |
| 110 | spec: |
| 111 | acme: |
| 112 | # The ACME server URL |
| 113 | server: https://acme-staging-v02.api.letsencrypt.org/directory |
| 114 | # Email address used for ACME registration |
| 115 | email: invalid@example.com |
| 116 | # Name of a secret used to store the ACME account private key |
| 117 | privateKeySecretRef: |
| 118 | name: letsencrypt-staging |
| 119 | solvers: |
| 120 | - dns01: |
| 121 | webhook: |
| 122 | groupName: acme.bwolf.me |
| 123 | solverName: gandi |
| 124 | config: |
| 125 | apiKeySecretRef: |
| 126 | key: api-token |
| 127 | name: gandi-credentials |
| 128 | EOF |
| 129 | |
| 130 | Check status of the Issuer: |
| 131 | |
| 132 | kubectl describe issuer letsencrypt-staging |
| 133 | |
| 134 | *Note*: The production Issuer is [similar][ACME documentation]. |
| 135 | |
| 136 | 7. Issue a [Certificate] for your `$DOMAIN`: |
| 137 | |
| 138 | cat << EOF | sed "s/example-com/$DOMAIN/" | kubectl apply -f - |
| 139 | apiVersion: cert-manager.io/v1alpha2 |
| 140 | kind: Certificate |
| 141 | metadata: |
| 142 | name: example-com |
| 143 | spec: |
| 144 | dnsNames: |
| 145 | - example-com |
| 146 | issuerRef: |
| 147 | name: letsencrypt-staging |
| 148 | secretName: example-com-tls |
| 149 | EOF |
| 150 | |
| 151 | Check the status of the Certificate: |
| 152 | |
| 153 | kubectl describe certificate $DOMAIN |
| 154 | |
| 155 | Display the details like the common name and subject alternative names: |
| 156 | |
| 157 | kubectl get secret $DOMAIN-tls -o yaml |
| 158 | |
| 159 | 8. Issue a wildcard Certificate for your `$DOMAIN`: |
| 160 | |
| 161 | cat << EOF | sed "s/example-com/$DOMAIN/" | kubectl apply -f - |
| 162 | apiVersion: cert-manager.io/v1alpha2 |
| 163 | kind: Certificate |
| 164 | metadata: |
| 165 | name: wildcard-example-com |
| 166 | spec: |
| 167 | dnsNames: |
| 168 | - '*.example-com' |
| 169 | issuerRef: |
| 170 | name: letsencrypt-staging |
| 171 | secretName: wildcard-example-com-tls |
| 172 | EOF |
| 173 | |
| 174 | Check the status of the Certificate: |
| 175 | |
| 176 | kubectl describe certificate $DOMAIN |
| 177 | |
| 178 | Display the details like the common name and subject alternative names: |
| 179 | |
| 180 | kubectl get secret wildcard-$DOMAIN-tls -o yaml |
| 181 | |
| 182 | 99. Uninstall this webhook: |
| 183 | |
| 184 | helm uninstall cert-manager-webhook-gandi --namespace cert-manager |
| 185 | kubectl delete -f rbac.yaml |
| 186 | kubectl delete gandi-credentials |
| 187 | |
| 188 | 100. Uninstalling cert-manager: |
| 189 | This is out of scope here. Refer to the official [documentation][cert-manager-uninstall]. |
| 190 | |
| 191 | |
| 192 | ## Development |
| 193 | **Note**: If some tool (IDE or build process) fails resolving a dependency, it may be the cause that a indirect dependency uses `bzr` for versioning. In such a case it may help to put the `bzr` binary into `$PATH` or `$GOPATH/bin`. |
| 194 | |
| 195 | |
| 196 | ## Release process |
| 197 | - Code changes result in a new image version and Git tag |
| 198 | - Helm chart changes result in a new chart version |
| 199 | - All other changes are pushed to master |
| 200 | - All versions are to be documented in [ChangeLog](ChangeLog.md) |
| 201 | |
| 202 | |
| 203 | ## Conformance test |
| 204 | Please note that the test is not a typical unit or integration test. Instead it invokes the web hook in a Kubernetes-like environment which asks the web hook to really call the DNS provider (.i.e. Gandi). It attempts to create an `TXT` entry like `cert-manager-dns01-tests.example.com`, verifies the presence of the entry via Google DNS. Finally it removes the entry by calling the cleanup method of web hook. |
| 205 | |
| 206 | **Note**: Replace the string `darwin` in the URL below with an OS matching your system (e.g. `linux`). |
| 207 | |
| 208 | As said above, the conformance test is run against the real Gandi API. Therefore you *must* have a Gandi account, a domain and an API key. |
| 209 | |
| 210 | ``` shell |
| 211 | cp testdata/gandi/api-key.yaml.sample testdata/gandi/api-key.yaml |
| 212 | echo -n $YOUR_GANDI_API_KEY | base64 | pbcopy # or xclip |
| 213 | $EDITOR testdata/gandi/api-key.yaml |
| 214 | ./scripts/fetch-test-binaries.sh |
| 215 | TEST_ZONE_NAME=example.com. go test -v . |
| 216 | ``` |
| 217 | |
| 218 | |
| 219 | [ACME DNS-01 challenge]: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge |
| 220 | [ACME documentation]: https://cert-manager.io/docs/configuration/acme/ |
| 221 | [Certificate]: https://cert-manager.io/docs/usage/certificate/ |
| 222 | [cert-manager]: https://cert-manager.io/ |
| 223 | [Gandi]: https://gandi.net/ |
| 224 | [Gandi LiveDNS API]: https://doc.livedns.gandi.net |
| 225 | [Helm]: https://helm.sh |
| 226 | [image tags]: https://hub.docker.com/r/bwolf/cert-manager-webhook-gandi |
| 227 | [Kubernetes]: https://kubernetes.io/ |
| 228 | [RBAC Authorization]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ |
| 229 | [setting-nameservers-for-dns01-self-check]: https://cert-manager.io/docs/configuration/acme/dns01/#setting-nameservers-for-dns01-self-check |
| 230 | [cert-manager-uninstall]: https://cert-manager.io/docs/installation/uninstall/kubernetes/ |