Best Practices

Set Up Let’s Encrypt with Contour Using VMware Tanzu Community Edition

Trusted certificates are necessary for any web application today. Verifying that communication is encrypted and secure is table stakes. Let’s Encrypt is one of the most popular Certificate Authorities providing TLS certificates to millions of users. Using cert-manager with VMware Tanzu Community Edition makes securing applications a lot easier.

Contour is a Layer 7 application load balancer that comes as a package with Tanzu Community Edition. Getting started is easy, but let’s take the documentation a bit further and use Let’s Encrypt as a cluster issuer with cert-manager.

First, get Contour deployed (make sure to check with the documentation for the latest version). This is a single line experience and once it’s complete you will be presented with an IP Address (or DNS address with AWS ELB) tied to the envoy service. 

$ tanzu package install contour 
  --package-name contour.community.tanzu.vmware.com 
  --version 1.18.1

The next step is to install and configure cert-manager. Once again, we can follow along with the cert-manager documentation for Tanzu Community Edition to get the initial components stood up. 

$ tanzu package install cert-manager --package-name cert-manager.community.tanzu.vmware.com --version 1.5.3

Cert-manager requires a ClusterIssuer to create and supply the TLS certificates. We are going to create two ClusterIssuers: one for Let’s Encypt staging and another for production. Staging has no rate limit enforcements, while production is for using full TLS-encrypted communication. Much of this is documented in Contour’s open source documentation. Be sure that the emails referenced can be contacted.

Staging:

cat <<EOF | kubectl apply --filename -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
  namespace: cert-manager
spec:
  acme:
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    solvers:
    - http01:         
     ingress:           
      class: contour 
EOF

Production:

kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: cert-manager
spec:
  acme:
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: contour
EOF

Now, let’s deploy a simple NGINX container:

$ kubectl create deployment my-nginx --image=nginx

At this point, the NGINX server is running, but there is no way to access it since it hasn’t been exposed externally. The next stage is to create a generic Kubernetes service but without specifying a type (such as LoadBalancer). This will create a service that we can tie to the ingress controller:

$ kubectl create deployment nginx --image nginx
$ cat <<EOF | kubectl apply --filename -
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: my-nginx
EOF

To do some initial testing, let's use the Let’s Encrypt staging server on an Ingress rule. Notice the annotations section:

$ cat <<EOF | kubectl apply --filename -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
    ingress.kubernetes.io/force-ssl-redirect: "true"
    kubernetes.io/ingress.class: contour
    kubernetes.io/tls-acme: "true"
spec:
  tls:
  - secretName: my-nginx-tls
    hosts:
    - my-nginx.mydomain.com
  rules:
  - host: my-nginx.mydomain.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: my-nginx
            port:
              number: 80
EOF

We’re almost there. To access this by DNS, a record needs to be created in your DNS server that points to the address of the Envoy proxy from the first step. For AWS, you will need to create a CNAME record that points to the ELB resource. vSphere, Azure, and other clouds will have an A record that points to the IP. Anything internal can be done at the DNS server or modified with /etc/hosts on your local machine. 

After the DNS record propagation takes effect, we can access our application at https://my-nginx.mydomain.com and we should be presented with a certificate error, which is expected. 

After accepting the certificate, we can see “Not Secure” in the browser url bar. This is the expected behavior of using the staging certificate.

When you’re ready to go into production and make the certificate error disappear, edit the Ingress rule and set the cluster-issuer to letsencrypt-prod. This will reissue the certificates using the new issuer. Close the tab and reopen to see the error warning disappear.

If you want to learn more about Tanzu Community Edition, check out the latest technical overview video and get hands-on at the Tanzu Developer Center.