Cert-Manager Integration

Cert-Manager is a common tool to manage certificates in Kubernetes, especially when backed by an external Certificate Authority (CA) such as Let's Encrypt.

The Stackable Secret Operator does not currently support managing Cert-Manager certificates directly, but it can be configured to consume certificates generated by it.

Caveats

Cert-Manager is designed to manage relatively long-lived certificates that are stored in Kubernetes Secrets. By contrast, the Stackable Secret Operator is designed to generate temporary short-lived certificates.

This has a couple of repercussions:

  • Longer-lived certificates mean that a leaked certificate has potential to be abused for longer.

  • Application teams may have access to read Secrets in their respective applications' Namespaces.

Where possible, we recommend using the autoTls backend instead.

Configuring Cert-Manager

We recommend using the autoTls backend instead for self-signed PKIs. We use Cert-Manager’s CA issuer here to show the broader concepts.

To do this, you will first need to teach Cert-Manager how to create your certificates.

In a production setup this will likely use an external CA such as ACME or OpenBao/Vault. However, to make this guide self-contained, Cert-Manager will create a self-signed CA certificate instead.

---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: secret-operator-demonstration (1)
spec:
  ca:
    secretName: secret-operator-demonstration-ca
# Create a self-signed CA for secret-operator-demonstration to use
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: secret-operator-demonstration-ca
spec:
  secretName: secret-operator-demonstration-ca
  isCA: true
  commonName: Stackable Secret Operator/Cert-Manager Demonstration CA
  issuerRef:
    kind: Issuer
    name: secret-operator-demonstration-ca
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: secret-operator-demonstration-ca
spec:
  selfSigned: {}
1 This is the Issuer that our created certificates will reference later

Creating a SecretClass

The Stackable Secret Operator needs to know how to find the certificates created by Cert-Manager. We do this by creating a SecretClass using the k8sSearch backend, which can find arbitrary Kubernetes Secret objects that have the correct labels.

---
apiVersion: secrets.stackable.tech/v1alpha1
kind: SecretClass
metadata:
  name: tls-cert-manager (1)
spec:
  backend:
    k8sSearch:
      searchNamespace:
        pod: {} (2)
1 Both certificates and Pods will reference this name, to ensure that the correct certificates are found
2 This informs the Secret Operator that certificates will be found in the same namespace as the Pod using it

Requesting a certificate

You can now use Cert-Manager to provision your first certificate. Use labels to inform the Stackable Secret Operator about which scopes the certificate fulfills. Which scopes must be provisioned is going to depend on the design of the workload. This guide assumes the service scope.

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-app-tls (1)
spec:
  secretName: my-app-tls (2)
  secretTemplate:
    labels:
      secrets.stackable.tech/class: tls-cert-manager (3)
      secrets.stackable.tech/service: my-app (4)
  dnsNames:
    - my-app (5)
  issuerRef:
    kind: Issuer
    name: secret-operator-demonstration (6)
1 The Certificate name is irrelevant for the Stackable Secret Operator’s, but must be unique (within the Namespace)
2 The Secret name must also be unique within the Namespace
3 This tells the Stackable Secret Operator that this secret corresponds to the SecretClass created before
4 This secret fulfils the service scope for my-app
5 The list of DNS names that this certificate should apply to.
6 The Cert-Manager Issuer that should sign this certificate, as created before

Using the certificate

Finally, we can create and expose a Pod that consumes the certificate!

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - name: tls
              mountPath: /tls
            - name: config
              mountPath: /etc/nginx/conf.d
          ports:
            - name: https
              containerPort: 443
      volumes:
        - name: tls (1)
          ephemeral:
            volumeClaimTemplate:
              metadata:
                annotations:
                  secrets.stackable.tech/class: tls-cert-manager (2)
                  secrets.stackable.tech/scope: service=my-app (3)
              spec:
                storageClassName: secrets.stackable.tech
                accessModes:
                  - ReadWriteOnce
                resources:
                  requests:
                    storage: "1"
        - name: config
          configMap:
            name: my-app
--- (4)
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app
data:
  default.conf: |
    server {
        listen 443 ssl;
        ssl_certificate /tls/tls.crt;
        ssl_certificate_key /tls/tls.key;
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
    }
--- (5)
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  selector:
    app: my-app
  ports:
    - name: https
      port: 443
1 A secret volume is created, where the certificate will be exposed to the app
2 The volume references the SecretClass defined before
3 The app is designated the scope service=my-app, matching the certificate’s scope
4 nginx is configured to use the mounted certificate
5 nginx is exposed as a Kubernetes Service