Automatic issuance of SSL certificates. We use Kubernetes and FreeIPA

Recently I started using a new way to obtain SSL certificates. I saved a small cheat sheet for myself so that I don’t have to remember the steps and commands every time and that's all. Then I decided that such instructions might be useful to someone else.

Important! To use this method of issuing SSL certificates, you must have an extra Kubernetes cluster and FreeIPA, between which network connectivity is configured. If so, congratulations, let's fly for certificates.

Briefly about the task

First, let me remind you what we are talking about. Issuing SSL certificates for a company's web resources is a typical administrative task. It can be solved in different ways. There is always an option to buy, the price is several tens of dollars.

You can also use free methods, including those based on Automated Certificate Management Environment (ACME) protocol algorithms. Thanks to them, you can order a domain-validated SSL certificate. This is the simplest certificate that you can get for free for 90 days. Next update is required.

You can simplify working with certificates using Kubernetes. Or rather, thanks to the cert-manager plugin. Its task is precisely to automate the issuance and renewal of SSL certificates.

As it turned out, the network connectivity of Kubernetes with IdM solutions (from identity management, in case you forgot) allows you to make the process even easier. In my case it's FreeIPA.

Advantages of the method:

  • issuing an SSL certificate takes a maximum of 5 minutes (if you do everything manually, you can be stuck with the task for half a day),

  • some of the actions are performed once, which means that the issuance of the second and next certificates is even faster,

  • all updates occur automatically, there is no need to view certificates, track their statuses, etc.

Let's get down to business

1. First you need to enable ACME support in FreeIPA:

(idm)# ipa-acme-manage enable

2. In order for cert-manager to add the _acme-challenge DNS record in FreeIPA, we use the cert-manager provider (in my case RFC-2136). To do this we must create a new TSIG key on our IPA server:

tsig-keygen -a hmac-sha512 acme-update >> /etc/named/ipa-ext.conf

3. Next, you need to connect cert-manager to Kubernetes. This action is performed with the following command:

helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.14.4 \

4. Once we have all the necessary manifests, we will need to create a secret in Kubernetes for the TSIG key. To do this, take the TSIG key and use it to create a Kubernetes secret:

kubectl -n cert-manager create secret generic ipa-tsig-secret --from-literal=tsig-secret-key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

At this stage, the basic Kubernetes settings are completed. When issuing the next certificate, we start from step 5.

5. We form a series of manifestos.

Let's move on to the ClusterIssuer resource, which provides TLS certificates. In it we indicate our IdM server and the zone within which we will issue certificates:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: myservice.testzone.nubes.ru
  namespace: testzone
spec:
  acme:
    email: test@testzone.nubes.ru
    server: https://idm1.testzone.nubes.ru/acme/directory
    privateKeySecretRef:
      name: ipa-issuer-account-key
    solvers:
      - dns01:
          rfc2136:
            nameserver: idm.testzone.nubes.ru
            tsigKeyName: acme-update
            tsigAlgorithm: HMACSHA512
            tsigSecretSecretRef:
              name: ipa-tsig-secret
              key: tsig-secret-key
        selector:
          dnsZones:
            - 'testzone.nubes.ru'

7. Next, create NetworkPolicy.

NetworkPolicy is a network policy management mechanism. It is designed to define access rules for network traffic between pods in a cluster.

Using NetworkPolicy, we specify the necessary hooks in the white lists:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  annotations:
  name: myservice-acme-http-solver
  namespace: testzone
spec:
  ingress:
  - {}
  podSelector:
    matchExpressions:
    - key: acme.cert-manager.io/http-domain
      operator: Exists
    - key: acme.cert-manager.io/http-token
      operator: Exists
  policyTypes:
  - Ingress

8. In the certificate we indicate which ClusterIssuer we will contact and to which address we want to issue the certificate. We also write secretName (we will need this for the last manifest):

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myservice-cert
  namespace: testzone
spec:
  commonName: 'myservice.testzone.nubes.ru'
  dnsNames:
    - myservice.testzone.nubes.ru
  issuerRef:
    name: myservice.testzone.nubes.ru
    kind: ClusterIssuer
  privateKey:
    algorithm: RSA
    encoding: PKCS1
    size: 4096
  secretName: myservice-tls

9. And, in fact, the last of the manifestos is Ingress. Specify the annotation 'kubernetes.io/tls-acme: “true”' and our secretName:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: myservice.testzone.nubes.ru
    kubernetes.io/tls-acme: "true"
  name: myservice-ingress
  namespace: testzone
spec:
  ingressClassName: nginx
  rules:
  - host: myservice.testzone.nubes.ru
    http:
      paths:
      - backend:
          service:
            name: myservice-service
            port:
              number: 8080
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - myservice.testzone.nubes.ru
    secretName: myservice-tls

10. On the IdM side we get:

And in Kubernetes we see:

kubectl -n testzone describe certificaterequests.cert-manager.io
...
...
...
  Conditions:
    Last Transition Time:  2024-04-17T12:19:01Z
    Message:               Certificate request has been approved by cert-manager.io
    Reason:                cert-manager.io
    Status:                True
    Type:                  Approved
    Last Transition Time:  2024-04-17T12:20:03Z
    Message:               Certificate fetched from issuer successfully
    Reason:                Issued
    Status:                True
    Type:                  Ready

That's it, the certificate has been issued. In general, of course, you can get confused and do everything through Helm, and this is even more correct. But that's a completely different story…

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *