For most people in the k8s world, nginx-ingress has provided a fairly reliable option as their Ingress Controller. While it provides a boat-load of great features, it also provides enough string to get tangled in. Here’s one way we solved not wanting to create certificates for each microserivce, but instead, utilize a default certificate (wildcard) applied to all services existing under our TLD.

The Problem

For each Ingress definition you define, you can use cert-manager along with nginx-ingress to automatically provision a certificate for the given fully qualified domain name your provide i.e. service.example.com. Cert-manager will intercept, utilize Lets Encrypt to generate a cert, and make that certificate available to nginx-ingress.

Here’s an example with this method employed:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: service-ingress
  annotations:
    kubernetes.io/tls-acme: "true"
    certmanager.k8s.io/issuer: letsencrypt-staging
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - service.example.com
    secretName: service-staging-letsencrypt
  rules:
  - host: service.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx
          servicePort: 80

Take note of the tls section as well as secretName which tells cert-manager and nginx-ingress what the name of the certificate to use will be.

While this is fine and dandy, lets consider our scenario where we still want name-based routing (i.e. service.example.com to route to our microservice) but instead of dynamic certificate generation, we’d like to just use a single certificate across all our services. There are many reasons for this including moving fast, test or staging environments, generating your own certs, or using wildcard certificates that blanket your TLD (i.e. example.com in our case).

The Solution

First, lets add the certificate we want as a Secret to the cluster:

kubectl create secret tls example-default-cert --key ${KEY_FILE} --cert ${CERT_FILE}

Here, we create the Secret object with our secret key and certificate files provided.

Next, we must pass an option to nginx-ingress to consume this newly created certificate:

--default-ssl-certificate=default/example-default-cert

For this value, default is our namespace and example-default-cert is the name of the certificate secret we added above.

Add this option to the args for your nginx-ingress Deployment or DaemonSet using whatever method works best for you. If using the official Helm chart, you can utilize the handy controller.extraArgs variable to pass in the default cert.

After doing that, you now just define your Ingress objects in the following way:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - test.example.com
  rules:
  - host: test.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx
          servicePort: 80

Hooray!! Now every incoming HTTPS request to your service will be encrypted utilizing the certificate you’ve informed nginx-ingress to utilize for everything! Really, the only thing we’ve done here is remove any annotations relating to cert-manager and take out the secretName line since we don’t want a dedicated certificate for this service.

One thing of note: this probably isn’t the best idea for production systems depending on your situation and architecture. Please review this configuration with your security teams and verify you’re on stable ground before applying changes like this to public facing systems. Be Safe!!

Mario Loria is a builder of diverse infrastructure with modern workloads on both bare-metal and cloud platforms. He's traversed roles in system administration, network engineering, and DevOps. You can learn more about him here.