github.com/projectcontour/contour@v1.28.2/site/content/guides/cert-manager.md (about) 1 --- 2 title: Deploying HTTPS services with Contour and cert-manager 3 layout: page 4 --- 5 6 This tutorial shows you how to securely deploy an HTTPS web application on a Kubernetes cluster, using: 7 8 - Kubernetes 9 - Contour, as the Ingress controller 10 - [JetStack's cert-manager][1] to provision TLS certificates from [the Let's Encrypt project][6] 11 12 ## Prerequisites 13 14 - A Kubernetes cluster deployed in either a data center or a cloud provider with a Kubernetes as a service offering. This tutorial was last tested on a GKE cluster running Kubernetes 1.22 15 - RBAC enabled on your cluster 16 - Your cluster must be able to request a public IP address from your cloud provider, using a load balancer. If you're on AWS or GKE this is automatic if you deploy a Kubernetes service object of type: LoadBalancer. If you're on your own datacenter you must set it up yourself 17 - A DNS domain that you control, where you host your web application 18 - Administrator permissions for all deployment steps 19 20 **NOTE:** To use a local cluster like `minikube` or `kind`, see the instructions in [the deployment guide][7]. 21 22 ## Summary 23 24 This tutorial walks you through deploying: 25 26 1. [Contour][0] 27 2. [Jetstack cert-manager][1] 28 3. A sample web application using HTTPProxy 29 30 **NOTE:** If you encounter failures related to permissions, make sure the user you are operating as has administrator permissions. 31 32 After you've been through the steps the first time, you don't need to repeat deploying Contour and cert-manager for subsequent application deployments. Instead, you can skip to step 3. 33 34 ## 1. Deploy Contour 35 36 Run: 37 38 ```bash 39 $ kubectl apply -f {{< param base_url >}}/quickstart/contour.yaml 40 ``` 41 42 to set up Contour as a deployment in its own namespace, `projectcontour`, and tell the cloud provider to provision an external IP that is forwarded to the Contour pods. 43 44 Check the progress of the deployment with this command: 45 46 ```bash 47 $ kubectl -n projectcontour get po 48 NAME READY STATUS RESTARTS AGE 49 contour-5475898957-jh9fm 1/1 Running 0 39s 50 contour-5475898957-qlbs2 1/1 Running 0 39s 51 contour-certgen-v1.19.0-5xthf 0/1 Completed 0 39s 52 envoy-hqbkm 2/2 Running 0 39s 53 ``` 54 55 After all the `contour` & `envoy` pods reach `Running` status and fully `Ready`, move on to the next step. 56 57 ### Access your cluster 58 59 Retrieve the external address of the load balancer assigned to Contour's Envoys by your cloud provider: 60 61 ```bash 62 $ kubectl get -n projectcontour service envoy -o wide 63 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR 64 envoy LoadBalancer 10.51.245.99 35.189.26.87 80:30111/TCP,443:30933/TCP 38d app=envoy 65 ``` 66 67 The value of `EXTERNAL-IP` varies by cloud provider. In this example GKE gives a bare IP address; AWS gives you a long DNS name. 68 69 To make it easier to work with the external load balancer, the tutorial adds a DNS record to a domain we control that points to this load balancer's IP address: 70 71 ```bash 72 $ host gke.davecheney.com 73 gke.davecheney.com has address 35.189.26.87 74 ``` 75 76 On AWS, you specify a `CNAME`, not an `A` record, and it would look something like this: 77 78 ```bash 79 $ host aws.davecheney.com 80 aws.davecheney.com is an alias for a4d1766f6ce1611e7b27f023b7e83d33–1465548734.ap-southeast-2.elb.amazonaws.com. 81 a4d1766f6ce1611e7b27f023b7e83d33–1465548734.ap-southeast-2.elb.amazonaws.com has address 52.63.20.117 82 a4d1766f6ce1611e7b27f023b7e83d33–1465548734.ap-southeast-2.elb.amazonaws.com has address 52.64.233.204 83 ``` 84 85 In your own data center, you need to arrange for traffic from a public IP address to be forwarded to the cluster IP of the Contour service. This is beyond the scope of the tutorial. 86 87 ### Testing connectivity 88 89 You must deploy at least one Ingress object before Contour can configure Envoy to serve traffic. 90 Note that as a security feature, Contour does not configure Envoy to expose a port to the internet unless there's a reason it should. 91 For this tutorial we deploy a version of Kenneth Reitz's [httpbin.org service][3]. 92 93 To deploy httpbin to your cluster, run this command: 94 95 ```bash 96 $ kubectl apply -f {{< param base_url >}}/examples/httpbin.yaml 97 ``` 98 99 Check that the pods are running: 100 101 ```bash 102 $ kubectl get po -l app=httpbin 103 NAME READY STATUS RESTARTS AGE 104 httpbin-85777b684b-8sqw5 1/1 Running 0 24s 105 httpbin-85777b684b-pb26w 1/1 Running 0 24s 106 httpbin-85777b684b-vpgwl 1/1 Running 0 24s 107 ``` 108 109 Then type the DNS name you set up in the previous step into a web browser, for example `http://gke.davecheney.com/`. You should see something like: 110 111 ![httpbin screenshot][8] 112 113 You can delete the httpbin service now, or at any time, by running: 114 115 ```bash 116 $ kubectl delete -f {{< param base_url >}}/examples/httpbin.yaml 117 ``` 118 119 ## 2. Deploy jetstack/cert-manager 120 121 **NOTE:** cert-manager is a powerful product that provides more functionality than this tutorial demonstrates. 122 There are plenty of [other ways to deploy cert-manager][4], but they are out of scope. 123 124 ### Fetch the source manager deployment manifest 125 126 To keep things simple, we skip cert-manager's Helm installation, and use the [supplied YAML manifests][5]. 127 128 ```bash 129 $ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.5.4/cert-manager.yaml 130 ``` 131 132 When cert-manager is up and running you should see something like: 133 134 ```bash 135 $ kubectl -n cert-manager get all 136 NAME READY STATUS RESTARTS AGE 137 pod/cert-manager-cainjector-74bb68d67c-8lb2f 1/1 Running 0 40s 138 pod/cert-manager-f7f8bf74d-65ld9 1/1 Running 0 40s 139 pod/cert-manager-webhook-645b8bdb7-2h5t6 1/1 Running 0 40s 140 141 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 142 service/cert-manager ClusterIP 10.48.13.252 <none> 9402/TCP 40s 143 service/cert-manager-webhook ClusterIP 10.48.7.220 <none> 443/TCP 40s 144 145 NAME READY UP-TO-DATE AVAILABLE AGE 146 deployment.apps/cert-manager 1/1 1 1 40s 147 deployment.apps/cert-manager-cainjector 1/1 1 1 40s 148 deployment.apps/cert-manager-webhook 1/1 1 1 40s 149 150 NAME DESIRED CURRENT READY AGE 151 replicaset.apps/cert-manager-cainjector-74bb68d67c 1 1 1 40s 152 replicaset.apps/cert-manager-f7f8bf74d 1 1 1 40s 153 replicaset.apps/cert-manager-webhook-645b8bdb7 1 1 1 40s 154 ``` 155 156 ### Deploy the Let's Encrypt cluster issuer 157 158 cert-manager supports two different CRDs for configuration, an `Issuer`, which is scoped to a single namespace, 159 and a `ClusterIssuer`, which is cluster-wide. 160 161 For Contour to be able to serve HTTPS traffic for an Ingress in any namespace, use `ClusterIssuer`. 162 Create a file called `letsencrypt-staging.yaml` with the following contents: 163 164 ```yaml 165 apiVersion: cert-manager.io/v1 166 kind: ClusterIssuer 167 metadata: 168 name: letsencrypt-staging 169 namespace: cert-manager 170 spec: 171 acme: 172 email: user@example.com 173 privateKeySecretRef: 174 name: letsencrypt-staging 175 server: https://acme-staging-v02.api.letsencrypt.org/directory 176 solvers: 177 - http01: 178 ingress: 179 class: contour 180 ``` 181 182 replacing `user@example.com` with your email address. 183 This is the email address that Let's Encrypt uses to communicate with you about certificates you request. 184 185 The staging Let's Encrypt server is not bound by [the API rate limits of the production server][2]. 186 This approach lets you set up and test your environment without worrying about rate limits. 187 You can then repeat this step for a production Let's Encrypt certificate issuer. 188 189 After you edit and save the file, deploy it: 190 191 ```bash 192 $ kubectl apply -f letsencrypt-staging.yaml 193 clusterissuer.cert-manager.io/letsencrypt-staging created 194 ``` 195 196 Wait for the `ClusterIssuer` to be ready: 197 198 ```bash 199 $ kubectl get clusterissuer letsencrypt-staging 200 NAME READY AGE 201 letsencrypt-staging True 54s 202 ``` 203 204 ## 3. Deploy your first HTTPS site using Ingress 205 206 For this tutorial we deploy a version of Kenneth Reitz's [httpbin.org service][3]. 207 We start with the deployment. 208 Copy the following to a file called `deployment.yaml`: 209 210 ```yaml 211 apiVersion: apps/v1 212 kind: Deployment 213 metadata: 214 labels: 215 app: httpbin 216 name: httpbin 217 spec: 218 replicas: 1 219 selector: 220 matchLabels: 221 app: httpbin 222 strategy: 223 rollingUpdate: 224 maxSurge: 1 225 maxUnavailable: 1 226 type: RollingUpdate 227 template: 228 metadata: 229 labels: 230 app: httpbin 231 spec: 232 containers: 233 - image: docker.io/kennethreitz/httpbin 234 name: httpbin 235 ports: 236 - containerPort: 8080 237 name: http 238 command: ["gunicorn"] 239 args: ["-b", "0.0.0.0:8080", "httpbin:app"] 240 dnsPolicy: ClusterFirst 241 ``` 242 243 Deploy to your cluster: 244 245 ```bash 246 $ kubectl apply -f deployment.yaml 247 deployment.apps/httpbin created 248 $ kubectl get pod -l app=httpbin 249 NAME READY STATUS RESTARTS AGE 250 httpbin-67fd96d97c-8j2rr 1/1 Running 0 56m 251 ``` 252 253 Expose the deployment to the world with a Service. Create a file called `service.yaml` with 254 the following contents: 255 256 ```yaml 257 apiVersion: v1 258 kind: Service 259 metadata: 260 name: httpbin 261 spec: 262 ports: 263 - port: 8080 264 protocol: TCP 265 targetPort: 8080 266 selector: 267 app: httpbin 268 ``` 269 270 and deploy: 271 272 ```bash 273 $ kubectl apply -f service.yaml 274 service/httpbin created 275 $ kubectl get service httpbin 276 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 277 httpbin ClusterIP 10.48.6.155 <none> 8080/TCP 57m 278 ``` 279 280 Expose the Service to the world with Contour and an Ingress object. Create a file called `ingress.yaml` with 281 the following contents: 282 283 ```yaml 284 apiVersion: networking.k8s.io/v1 285 kind: Ingress 286 metadata: 287 name: httpbin 288 spec: 289 rules: 290 - host: httpbin.davecheney.com 291 http: 292 paths: 293 - pathType: Prefix 294 path: / 295 backend: 296 service: 297 name: httpbin 298 port: 299 number: 8080 300 ``` 301 302 The host name, `httpbin.davecheney.com` is a `CNAME` to the `gke.davecheney.com` record that was created in the first section, and must be created in the same place as the `gke.davecheney.com` record was. 303 That is, in your cloud provider. 304 This lets requests to `httpbin.davecheney.com` resolve to the external IP address of the Contour service. 305 They are then forwarded to the Contour pods running in the cluster: 306 307 ```bash 308 $ host httpbin.davecheney.com 309 httpbin.davecheney.com is an alias for gke.davecheney.com. 310 gke.davecheney.com has address 35.189.26.87 311 ``` 312 313 Change the value of `spec.rules.host` to something that you control, and deploy the Ingress to your cluster: 314 315 ```bash 316 $ kubectl apply -f ingress.yaml 317 ingress.networking.k8s.io/httpbin created 318 $ kubectl get ingress httpbin 319 NAME CLASS HOSTS ADDRESS PORTS AGE 320 httpbin <none> httpbin.davecheney.com 80 12s 321 ``` 322 323 Now you can type the host name of the service into a browser, or use curl, to verify it's deployed and everything is working: 324 325 ```bash 326 $ curl http://httpbin.davecheney.com/get 327 { 328 "args": {}, 329 "headers": { 330 "Accept": "*/*", 331 "Content-Length": "0", 332 "Host": "htpbin.davecheney.com", 333 "User-Agent": "curl/7.58.0", 334 "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 335 "X-Envoy-Internal": "true" 336 }, 337 "origin": "10.152.0.2", 338 "url": "http://httpbin.davecheney.com/get" 339 } 340 ``` 341 342 Excellent, it looks like everything is up and running serving traffic over HTTP. 343 344 ### Request a TLS certificate from Let's Encrypt 345 346 Now it's time to use cert-manager to request a TLS certificate from Let's Encrypt. 347 Do this by adding some annotations and a `tls:` section to the Ingress spec. 348 349 We need to add the following annotations: 350 351 - `cert-manager.io/cluster-issuer: letsencrypt-staging`: tells cert-manager to use the `letsencrypt-staging` cluster issuer you just created. 352 - `kubernetes.io/tls-acme: "true"`: Tells cert-manager to do ACME TLS (what Let's Encrypt uses). 353 - `ingress.kubernetes.io/force-ssl-redirect: "true"`: tells Contour to redirect HTTP requests to the HTTPS site. 354 - `kubernetes.io/ingress.class: contour`: Tells Contour that it should handle this Ingress object. 355 356 Using `kubectl edit ingress httpbin`: 357 358 ```yaml 359 apiVersion: networking.k8s.io/v1 360 kind: Ingress 361 metadata: 362 name: httpbin 363 annotations: 364 cert-manager.io/cluster-issuer: letsencrypt-staging 365 ingress.kubernetes.io/force-ssl-redirect: "true" 366 kubernetes.io/ingress.class: contour 367 kubernetes.io/tls-acme: "true" 368 spec: 369 tls: 370 - secretName: httpbin 371 hosts: 372 - httpbin.davecheney.com 373 rules: 374 - host: httpbin.davecheney.com 375 http: 376 paths: 377 - pathType: Prefix 378 path: / 379 backend: 380 service: 381 name: httpbin 382 port: 383 number: 8080 384 ``` 385 386 The certificate is issued in the name of the hosts listed in the `tls:` section, `httpbin.davecheney.com` and stored in the secret `httpbin`. 387 Behind the scenes, cert-manager creates a certificate CRD to manage the lifecycle of the certificate, and then a series of other CRDs to handle the challenge process. 388 389 You can watch the progress of the certificate as it's issued: 390 391 ```bash 392 $ kubectl describe certificate httpbin | tail -n 12 393 Status: 394 Conditions: 395 Last Transition Time: 2019-11-07T00:37:55Z 396 Message: Waiting for CertificateRequest "httpbinproxy-1925286939" to complete 397 Reason: InProgress 398 Status: False 399 Type: Ready 400 Events: 401 Type Reason Age From Message 402 ---- ------ ---- ---- ------- 403 Normal GeneratedKey 26s cert-manager Generated a new private key 404 Normal Requested 26s cert-manager Created new CertificateRequest resource "httpbinproxy-1925286939" 405 ``` 406 407 Wait for the certificate to be issued: 408 409 ```bash 410 $ kubectl describe certificate httpbin | grep -C3 "Certificate is up to date" 411 Status: 412 Conditions: 413 Last Transition Time: 2019-11-06T23:47:50Z 414 Message: Certificate is up to date and has not expired 415 Reason: Ready 416 Status: True 417 Type: Ready 418 ``` 419 420 A `kubernetes.io/tls` secret is created with the `secretName` specified in the `tls:` field of the Ingress. 421 422 ```bash 423 $ kubectl get secret httpbin 424 NAME TYPE DATA AGE 425 httpbin kubernetes.io/tls 2 3m 426 ``` 427 428 cert-manager manages the contents of the secret as long as the Ingress is present in your cluster. 429 430 You can now visit your site, replacing `http://` with `https://` — and you get a huge security warning! 431 This is because the certificate was issued by the Let's Encrypt staging servers and has a fake CA. 432 This is so you can't accidentally use the staging servers to serve real certificates. 433 434 ```bash 435 $ curl https://httpbin.davecheney.com/get 436 curl: (60) SSL certificate problem: unable to get local issuer certificate 437 More details here: https://curl.haxx.se/docs/sslcerts.html 438 439 curl failed to verify the legitimacy of the server and therefore could not 440 establish a secure connection to it. To learn more about this situation and 441 how to fix it, please visit the web page mentioned above. 442 ``` 443 444 ### Switch to Let's Encrypt Production 445 446 To request a properly signed certificate from the Let's Encrypt production servers, we create a new `ClusterIssuer`, as before but with some modifications. 447 448 Create a file called `letsencrypt-prod.yaml` with the following contents: 449 450 ```yaml 451 apiVersion: cert-manager.io/v1 452 kind: ClusterIssuer 453 metadata: 454 name: letsencrypt-prod 455 namespace: cert-manager 456 spec: 457 acme: 458 email: user@example.com 459 privateKeySecretRef: 460 name: letsencrypt-prod 461 server: https://acme-v02.api.letsencrypt.org/directory 462 solvers: 463 - http01: 464 ingress: 465 class: contour 466 ``` 467 468 again replacing `user@example.com` with your email address. 469 470 Deploy: 471 472 ```bash 473 $ kubectl apply -f letsencrypt-prod.yaml 474 clusterissuer.cert-manager.io/letsencrypt-prod created 475 ``` 476 477 Now we use `kubectl edit ingress httpbin` to edit our Ingress to ask for a real certificate from `letsencrypt-prod`: 478 479 ```yaml 480 apiVersion: networking.k8s.io/v1 481 kind: Ingress 482 metadata: 483 name: httpbin 484 annotations: 485 cert-manager.io/cluster-issuer: letsencrypt-prod 486 spec: 487 ... 488 ``` 489 490 The certificate resource will transition to `Ready: False` while it's re-provisioned from the Let's Encrypt production servers, and then back to `Ready: True` once it's been provisioned: 491 492 ```bash 493 $ kubectl describe certificate httpbin 494 ... 495 Events: 496 Type Reason Age From Message 497 ---- ------ ---- ---- ------- 498 ... 499 Normal Issuing 21s cert-manager Issuing certificate as Secret was previously issued by ClusterIssuer.cert-manager.io/letsencrypt-staging 500 Normal Reused 21s cert-manager Reusing private key stored in existing Secret resource "httpbin" 501 Normal Requested 21s cert-manager Created new CertificateRequest resource "httpbin-sjqbt" 502 Normal Issuing 18s (x2 over 48s) cert-manager The certificate has been successfully issued 503 ``` 504 505 Followed by: 506 507 ```bash 508 $ kubectl get certificate httpbin -o wide 509 NAME READY SECRET ISSUER STATUS AGE 510 httpbin True httpbin letsencrypt-prod Certificate is up to date and has not expired 3m35s 511 ``` 512 513 Now revisiting our `https://httpbin.davecheney.com` site should show a valid, trusted, HTTPS certificate. 514 515 ```bash 516 $ curl https://httpbin.davecheney.com/get 517 { 518 "args": {}, 519 "headers": { 520 "Accept": "*/*", 521 "Content-Length": "0", 522 "Host": "httpbin.davecheney.com", 523 "User-Agent": "curl/7.58.0", 524 "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 525 "X-Envoy-Internal": "true" 526 }, 527 "origin": "10.152.0.2", 528 "url": "https://httpbin.davecheney.com/get" 529 } 530 ``` 531 532 ![httpbin.davecheney.com screenshot][9] 533 534 ## Making cert-manager work with HTTPProxy 535 536 cert-manager currently does not have a way to interact directly with HTTPProxy objects in order to respond to the HTTP01 challenge (See [#950][10] and [#951][11] for details). 537 cert-manager, however, can be configured to request certificates automatically using a `Certificate` object. 538 539 When cert-manager finds a `Certificate` object, it will implement the HTTP01 challenge by creating a new, temporary Ingress object that will direct requests from Let's Encrypt to temporary pods called 'solver pods'. 540 These pods know how to respond to Let's Encrypt's challenge process for verifying you control the domain you're issuing certificates for. 541 The Ingress resource as well as the solver pods are short lived and will only be available during the certificate request or renewal process. 542 543 The result of the work steps described previously is a TLS secret, which can be referenced by a HTTPProxy. 544 545 ## Details 546 547 To do this, we first need to create our HTTPProxy and Certificate objects. 548 549 This example uses the hostname `httpbinproxy.davecheney.com`, remember to create that name before starting. 550 551 Firstly, the HTTPProxy: 552 553 ```yaml 554 apiVersion: projectcontour.io/v1 555 kind: HTTPProxy 556 metadata: 557 name: httpbinproxy 558 spec: 559 virtualhost: 560 fqdn: httpbinproxy.davecheney.com 561 tls: 562 secretName: httpbinproxy 563 routes: 564 - services: 565 - name: httpbin 566 port: 8080 567 ``` 568 569 This object will be marked as Invalid by Contour, since the TLS secret doesn't exist yet. 570 Once that's done, create the Certificate object: 571 572 ```yaml 573 apiVersion: cert-manager.io/v1 574 kind: Certificate 575 metadata: 576 name: httpbinproxy 577 spec: 578 commonName: httpbinproxy.davecheney.com 579 dnsNames: 580 - httpbinproxy.davecheney.com 581 issuerRef: 582 name: letsencrypt-prod 583 kind: ClusterIssuer 584 secretName: httpbinproxy 585 ``` 586 587 Wait for the Certificate to be provisioned: 588 589 ```bash 590 $ kubectl get certificate httpbinproxy -o wide 591 NAME READY SECRET ISSUER STATUS AGE 592 httpbinproxy True httpbinproxy letsencrypt-prod Certificate is up to date and has not expired 39s 593 ``` 594 595 Once cert-manager has fulfilled the HTTP01 challenge, you will have a `httpbinproxy` secret, that will contain the keypair. 596 Contour will detect that the Secret exists and generate the HTTPProxy config. 597 598 After that, you should be able to curl the new site: 599 600 ```bash 601 $ curl https://httpbinproxy.davecheney.com/get 602 { 603 "args": {}, 604 "headers": { 605 "Accept": "*/*", 606 "Content-Length": "0", 607 "Host": "httpbinproxy.davecheney.com", 608 "User-Agent": "curl/7.54.0", 609 "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 610 "X-Envoy-External-Address": "122.106.57.183" 611 }, 612 "origin": "122.106.57.183", 613 "url": "https://httpbinproxy.davecheney.com/get" 614 } 615 ``` 616 617 ## Wrapping up 618 619 Now that you've deployed your first HTTPS site using Contour and Let's Encrypt, deploying additional TLS enabled services is much simpler. 620 Remember that for each HTTPS website you deploy, cert-manager will create a Certificate CRD that provides the domain name and the name of the target Secret. 621 The TLS functionality will be enabled when the HTTPProxy contains the `tls:` stanza, and the referenced secret contains a valid keypair. 622 623 See the [cert-manager docs][12] for more information. 624 625 ## Bonus points 626 627 For bonus points, you can use a feature of Contour to automatically upgrade any HTTP request to the corresponding HTTPS site so you are no longer serving any traffic over insecure HTTP. 628 629 To enable the automatic redirect from HTTP to HTTPS, add this annotation to your Ingress object. 630 631 ``` 632 metadata: 633 annotations: 634 ingress.kubernetes.io/force-ssl-redirect: "true" 635 ``` 636 Now any requests to the insecure HTTP version of your site get an unconditional 301 redirect to the HTTPS version: 637 638 ``` 639 $ curl -v http://httpbin.davecheney.com/get 640 * Trying 35.189.26.87… 641 * TCP_NODELAY set 642 * Connected to httpbin.davecheney.com (35.189.26.87) port 80 (#0) 643 > GET /get HTTP/1.1 644 > Host: httpbin.davecheney.com 645 > User-Agent: curl/7.58.0 646 > Accept: */* 647 > 648 < HTTP/1.1 301 Moved Permanently 649 < location: https://httpbin.davecheney.com/get 650 < date: Tue, 20 Feb 2018 04:11:46 GMT 651 < server: envoy 652 < content-length: 0 653 < 654 * Connection #0 to host httpbin.davecheney.com left intact 655 ``` 656 657 __Note:__ For HTTPProxy resources this happens automatically without the need for an annotation. 658 659 [0]: {{< param github_url >}} 660 [1]: https://github.com/jetstack/cert-manager 661 [2]: https://letsencrypt.org/docs/rate-limits/ 662 [3]: http://httpbin.org/ 663 [4]: https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html 664 [5]: https://github.com/jetstack/cert-manager/releases/download/v1.5.4/cert-manager.yaml 665 [6]: https://letsencrypt.org/getting-started/ 666 [7]: /docs/{{< param latest_version >}}/deploy-options/#get-your-hostname-or-ip-address 667 [8]: /img/cert-manager/httpbinhomepage.png 668 [9]: /img/cert-manager/httpbin.png 669 [10]: {{< param github_url >}}/issues/950 670 [11]: {{< param github_url >}}/issues/951 671 [12]: https://cert-manager.io/docs/usage/ingress/