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/