Using FreeIPA CA as an ACME Provider for Cert-Manager

I’m using FreeIPA for authentication services in my home lab. It’s extreme overkill for my situation, as I don’t have many users (mainly just me!) but alas I like overkill. :)

I am using FreeIPA’s DNS service to host some DNS subdomains for internal services. The way I have configured these subdomains is through DNS delegations, but since my IPA servers are not accessible from the internet, it breaks both the HTTP-01 and DNS-01 verification challenges from LetsEncypt’s.

Yesterday evening, I was playing around with TrueCommand and have it hosted on one of my IPA internal domains, but as I cannot use LetsEncrypt to issue a certificate for it, I decided to use the CA built into FreeIPA since it supports ACME as well.

As all the machines that will need to use the service are enrolled into IPA already, the CA certificate for IPA is also installed on those nodes, meaning any certificate issues by FreeIPA are automatically trusted.

To get this to work, I had to first enable ACME support from within FreeIPA:

[root@ipa-server ~]# ipa-acme-manage enable

FreeIPA’s ACME service supports both HTTP-01 and DNS-01 challenges, but I generally prefer DNS-01. For cert-manager to add the _acme-challenge DNS record to FreeIPA, we can use cert-manager’s RFC-2136 provider.

To do this, we must create a new TSIG key on our IPA server:

[root@ipa-server ~]# tsig-keygen -a hmac-sha512 acme-update >> /etc/named/ipa-ext.conf
[root@ipa-server ~]# systemctl restart named-pkcs11.service

Enable dynamic updates for the IPA DNS subdomain:

[root@ipa-server ~]#  ipa dnszone-mod k8s.intahnet.co.uk --dynamic-update=True --update-policy='grant acme-update wildcard * ANY;'

Next, I had to modify my cert-manager installation slightly to include my own CA certificate bundle, which includes my IPA CA cert. To do this I had to first create the bundle, and then create a Kubernetes ConfigMap for it:

[mhamzahkhan@laptop ~]# cat /etc/ipa/ca.crt > ca-certificates.crt
[mhamzahkhan@laptop ~]# kubectl -n cert-manager create configmap ca-bundle --from-file ca-certificates.crt
Info
If the machine you are using is enrolled in the IPA domain, you could also just use /etc/pki/tls/certs/ca-bundle.crt, which is actually what I did since it contains all the other CA certificates that cert-manager may need (for example the ISRG Root X1 CA certificate, which is needed so cert-manager can properly access the LetsEncrypt ACME servers).

Next, I had to modify the cert-manager deployment to make use of the ca-bundle. As I am using the cert-manager helm chart, this was quite easy. I added the following to my cert-manager helm values file:

---
volumes:
  - name: ca-bundle
    configMap:
      name: ca-bundle

volumeMounts:
  - name: ca-bundle
    mountPath: /etc/ssl/certs/ca-certificates.crt
    subPath: ca-certificates.crt
    readOnly: false

Once this has been deployed, we can need to create a secret in Kubernetes for the TSIG key. Grab the TSIG key we generated earlier from your IPA server (/etc/named/ipa-ext.conf), and create a Kubernetes secret with it:

[mhamzahkhan@laptop ~]# kubectl -n cert-manager create secret generic ipa-tsig-secret --from-literal=tsig-secret-key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Next, add a new ClusterIssuer for IPA’s ACME service:

---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: ipa
  namespace: cert-manager
spec:
  acme:
    email: admin@ipa.intahnet.co.uk
    server: https://ipa-ca.ipa.intahnet.co.uk/acme/directory
    privateKeySecretRef:
      name: ipa-issuer-account-key
    solvers:
      - dns01:
          rfc2136:
            nameserver: 10.0.0.22
            tsigKeyName: acme-update
            tsigAlgorithm: HMACSHA512
            tsigSecretSecretRef:
              name: ipa-tsig-secret
              key: tsig-secret-key
        selector:
          dnsZones:
            - 'k8s.intahnet.co.uk'

Now you should be set to request certificates!

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: truecommand-certificate
  namespace: default
spec:
  commonName: 'truecommand.k8s.intahnet.co.uk'
  dnsNames:
    - truecommand.k8s.intahnet.co.uk
  issuerRef:
    name: ipa
    kind: ClusterIssuer
  privateKey:
    algorithm: RSA
    encoding: PKCS1
    size: 4096
  secretName: truecommand-tls

All working:

[mhamzahkhan@laptop ~]# kubectl get certificate
NAME                           READY     SECRET                 AGE
truecommand-certificate        True     truecommand-tls        23s

[mhamzahkhan@laptop ~]# kubectl get secrets
NAME                                     TYPE                                  DATA      AGE
truecommand-certificate-q8qkh            kubernetes.io/tls                     2         29s

It’s a very similar process to use ExternalDNS with FreeIPA as ExternalDNS also supports RFC2136. I have not set this up yet, but the process is described in this excellent blog post: How to set up Dynamic DNS on FreeIPA for your Kubernetes Cluster.

Related Posts

comments