FreeIPA Dogtag Certs

Issue, track and renew service certificates with FreeIPA's bundled Dogtag CA: sub-CAs, ipa cert-request, certmonger, ACME, renewing the IPA CA itself, and the errors that show up in practice.

If you only remember six things
  • The IPA CA is Dogtag. Treat it like a CA: plan renewals, back it up, have two CA replicas.
  • Use certmonger. Letting a cert expire because you forgot the renewal script is an unforced error certmonger prevents for free.
  • Sub-CAs (ipa ca-add) exist for scoping. Issue service certs from a sub-CA whose trust you can distribute without pushing the whole root.
  • For web-ish workloads that speak ACME, enable ACME on IPA and forget manual issuance.
  • External-CA mode makes the IPA CA a sub of your corporate root. Decide before first install; changing it later is an outage.
  • Renewing the IPA CA itself is a scheduled event, not an emergency. Know the date; test on a replica first.

The Dogtag model in IPA

When you install IPA with --setup-ca, you get:

Sub-CAs (ipa ca-add)

Create a sub-CA when you want a trust domain narrower than "everything our IPA ever signs". For example: a sub-CA for internal mTLS that you can distribute to Kubernetes without also trusting every cert IPA issues.

kinit admin

# Create the sub-CA
ipa ca-add internal-mtls \
  --subject "CN=Internal mTLS,O=EXAMPLE.INTERNAL" \
  --desc "mTLS between internal services"

# See what exists
ipa ca-find

# Fetch the sub-CA cert to ship to consumers
ipa ca-show internal-mtls --out /etc/pki/ca-trust/source/anchors/internal-mtls.pem
update-ca-trust

Issuance is now parameterised by --ca=:

ipa cert-request /tmp/app.csr \
  --principal HTTP/app1.example.internal \
  --ca internal-mtls \
  --profile caIPAserviceCert
One sub-CA per trust domain. Not per-service (too noisy), not per-datacentre (not a trust boundary). Per "blast radius of compromise".

Issuing service certs

Three steps: create the service principal, generate a CSR, request the cert.

1. Generate a CSR

# On the host that needs the cert
sudo -u app openssl req -new -newkey rsa:4096 -nodes \
  -keyout /etc/pki/tls/private/app.key \
  -out /tmp/app.csr \
  -subj "/CN=app1.example.internal" \
  -addext "subjectAltName=DNS:app1.example.internal,DNS:app.example.internal"

For modern workloads, prefer ECDSA P-256 or Ed25519 where supported:

openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout /etc/pki/tls/private/app.key -out /tmp/app.csr \
  -subj "/CN=app1.example.internal" \
  -addext "subjectAltName=DNS:app1.example.internal"

2. Ensure the service principal exists

ipa service-add HTTP/app1.example.internal
# If this host is not the app's own, authorise the requester explicitly:
ipa service-add-host HTTP/app1.example.internal --hosts=deploy01.example.internal

3. Request the cert

ipa cert-request /tmp/app.csr \
  --principal HTTP/app1.example.internal \
  --profile caIPAserviceCert \
  --ca ipa \
  --certificate-out /etc/pki/tls/certs/app.pem

The returned cert chains to the IPA CA (or whatever sub-CA you asked for). Combine with the CA bundle if the consumer needs it:

ipa ca-show ipa --out /etc/pki/tls/certs/ipa-ca.pem
cat /etc/pki/tls/certs/app.pem /etc/pki/tls/certs/ipa-ca.pem \
  > /etc/pki/tls/certs/app-fullchain.pem

Tracking with certmonger

Manual issuance is fine for a one-off. For anything that lives past a renewal cycle, track with certmonger so it renews itself.

sudo ipa-getcert request \
  -K HTTP/app1.example.internal \
  -D app1.example.internal -D app.example.internal \
  -k /etc/pki/tls/private/app.key \
  -f /etc/pki/tls/certs/app.pem \
  -C "systemctl reload nginx" \
  -r
FlagMeaning
-KKerberos principal that identifies the service
-DSubject Alternative Name (DNS). Use once per SAN.
-k, -fPrivate key and cert paths certmonger will maintain
-CCommand certmonger runs after each renewal (reload the consumer)
-rAuto-renew; the default but explicit is good
-T <profile>Pick a non-default profile
-X <ca>Pick a sub-CA

Inspect and manage

sudo getcert list
sudo getcert list -i <request-id>            # details for one request
sudo getcert resubmit -i <request-id>        # force a renewal
sudo getcert stop-tracking -i <request-id>   # stop certmonger from touching it
Logging: certmonger logs to /var/log/messages (systemd: journalctl -u certmonger). When a renewal fails silently, this is the first place to look.

ACME on IPA

IPA has shipped an ACME endpoint for a while. If your consumer speaks ACME, this is the lowest-friction option — no keytabs, no service principals, just HTTP-01.

sudo ipa-acme-manage enable
sudo ipa-acme-manage status
# Directory URL to hand to your ACME client:
#   https://ipa1.example.internal/acme/directory

Client side, with certbot:

certbot certonly --standalone \
  --server https://ipa1.example.internal/acme/directory \
  -d app1.example.internal \
  --no-eff-email --agree-tos -m devops@example.internal

And with acme.sh:

acme.sh --issue -d app1.example.internal \
  --server https://ipa1.example.internal/acme/directory \
  --standalone

Restrictions:

Renewing the IPA CA

The CA signing cert has a lifetime. By default it is ~10 years, but early installs used shorter lifetimes; some regulated environments force shorter. You will renew eventually.

Check when

sudo ipa-certupdate                           # fetch current trust bundle
sudo getcert list | grep -E 'subject|expires' | head -40

# Or directly from Dogtag:
sudo openssl x509 -noout -dates \
  -in /etc/pki/pki-tomcat/alias/ca_signing.crt

Renew (internal-CA mode)

# Certmonger already tracks the CA cert. Force it:
sudo ipa-cacert-manage renew --self-sign
sudo ipa-certupdate     # push the new trust bundle to all clients via SSSD

Renew (external-CA mode)

# Step 1: generate a CSR for the IPA CA
sudo ipa-cacert-manage renew --external-ca

# This writes /var/lib/ipa/ca.csr. Hand it to your corporate root.
# Step 2: receive the signed cert + chain back, then:
sudo ipa-cacert-manage renew \
  --external-cert-file=/tmp/ipa-ca.new.crt \
  --external-cert-file=/tmp/corp-root.pem
sudo ipa-certupdate
Do the renewal on one replica and let it propagate. The new chain must be in LDAP before every other client picks it up. Do not renew on all replicas simultaneously.

External-CA mode

Decision at first ipa-server-install:

ipa-server-install --external-ca \
  --realm EXAMPLE.INTERNAL --domain example.internal \
  --setup-dns --auto-forwarders \
  --ds-password 'REDACTED' --admin-password 'REDACTED' \
  --unattended

# Installer pauses and emits /root/ipa.csr. Sign it with the corporate CA.
# Resume with the signed cert and the corporate chain:
ipa-server-install --external-cert-file=/root/ipa.crt \
                   --external-cert-file=/root/corp-root.pem

Trade-offs:

Profiles, short version

A profile is a Dogtag template for "what fields are allowed/required on this kind of cert". IPA ships a few:

ProfileUse for
caIPAserviceCertStandard service cert with SANs. 99% of what you'll issue.
IECUserRolesUser certs used by Dogtag internally — don't issue from this.
KDCs_PKINIT_CertsKDC certs for PKINIT.

Custom profiles are possible (ipa certprofile-import) when you need ExtKeyUsage combinations the default won't emit (e.g. TLS client + code signing). Treat them like CA change-management: version-controlled, reviewed, tested on a non-prod sub-CA first.

Common errors

ErrorCauseFix
CA is not configured on a replicaReplica wasn't installed with --setup-ca.ipa-ca-install on that replica — see promoting a replica.
DRM is not installed / KRA is not configuredYou tried to use vault / key-archival on a replica without KRA.ipa-kra-install.
could not find a suitable authorisation principalThe requester has no authority to ask IPA for this cert.ipa service-add-host to grant the issuing host permission.
invalid subject: missing nsNSCAVertificateAuthoritySubjectDNSub-CA creation races Dogtag. Retry after a few seconds.Wait, then ipa ca-add again; restart pki-tomcatd@pki-tomcat if persistent.
profile is not enabledYou're requesting against a disabled custom profile.ipa certprofile-mod <name> --store=True --enabled=True.
Cert issued but reload step in certmonger failedThe -C command's exit code was non-zero.Check journalctl -u certmonger; run the post-save command by hand to reproduce.
After CA renewal, clients distrust new certsOld trust bundle is still on clients.ipa-certupdate on clients; SSSD will pull the new bundle on next refresh.
Can't connect to the CA on requestDogtag down; firewall blocks loopback weirdly; ipa.socket issue.systemctl status pki-tomcatd@pki-tomcat; journalctl -u pki-tomcatd@pki-tomcat.

Verification

See also: FreeIPA, FreeIPA Replication, Certificates, Kerberos Keytab Rotation.