-
Notifications
You must be signed in to change notification settings - Fork 502
Description
I am attempting to use step-ca as an external, offline root CA to sign a subordinate CA certificate for a new FreeIPA installation. The entire workflow is automated in a GitLab CI/CD pipeline.
The workflow is as follows:
- An Ansible playbook running on a GitLab runner configures a new FreeIPA server using the
freeipa.ansible_freeipa.ipaserver
role. As part of this process, theipa-server-install
command generates a Certificate Signing Request (CSR) for its own CA (/tmp/ipa.csr
). - A script in the GitLab CI job then needs to get this CSR signed by the
step-ca
instance.
The Problem
The FreeIPA installation process is strict and requires that the final signed certificate has a subject that exactly matches the subject from the CSR.
- CSR Subject:
CN=Certificate Authority,O=LOCAL.DOMAIN
- Required Certificate Subject:
CN=Certificate Authority,O=LOCAL.DOMAIN
However, the certificate being issued by the step-ca
server consistently has the subject of CN=Certificate Authority
, stripping the O=LOCAL.DOMAIN
part. This causes the second stage of the FreeIPA installation to fail.
step-ca
Server Configuration
I have configured a dedicated JWK provisioner on the step-ca
server specifically for this purpose. I am using a custom template that is intended to preserve the full subject from the CSR.
Here is the output of the step ca provisioner list
command for the relevant provisioner:
{
"type": "JWK",
"name": "idm@local.domain",
"key": {...},
"encryptedKey": "...",
"claims": {
"minTLSCertDuration": "5m0s",
"maxTLSCertDuration": "87600h0m0s",
"defaultTLSCertDuration": "24h0m0s",
"enableSSHCA": true
},
"options": {
"x509": {
"template": "{\n \"subject\": {{ toJson .Insecure.CR.RawSubject }},\n \"sans\": {{ toJson .SANs }},\n \"keyUsage\": [\"certSign\", \"crlSign\"],\n \"basicConstraints\": {\n \"isCA\": true,\n \"maxPathLen\": 1\n }\n}\n"
},
"ssh": {}
}
}
Client-side Steps (GitLab CI)
The CI script performs the following steps:
- Extracts the Common Name from the CSR to satisfy the server's authorization policy (
token.subject == csr.common_name
).
CN=$(openssl req -in /tmp/ipa.csr -noout -subject | sed -n 's/.*CN= *//p')
- Generates a token using the extracted CN as the subject.
TOKEN=$(step ca token "${CN}" --provisioner "idm@domain.local" --key "key.jwk" --password-file "password.txt")
- Attempts to sign the CSR.
step ca sign "/tmp/ipa.csr" "/tmp/ipa.crt" --token "$TOKEN"
Expected vs. Actual Behavior
- Expected: I expect the
step-ca server
to apply the x509.template defined in the provisioner. The template's{{ toJson .Insecure.CR.RawSubject }}
directive should force the new certificate's subject to be an exact copy of the subject in the CSR. - Actual: The step ca sign command succeeds, but a subsequent inspection of the generated
/tmp/ipa.crt
reveals its subject is onlyCN=Certificate Authority
. The template appears to be ignored or is not functioning as expected.
Question
Is this a bug, or is there a misconfiguration in the provisioner or template that would cause the .Insecure.CR.RawSubject
variable to be ignored in favor of the token's subject? What is the correct way to configure a JWK provisioner to sign a subordinate CA CSR while preserving the full, original subject from the CSR?
System Config
- OS: Ubuntu 24.04.3
- Architecture: aarch64
- Step-CA Version: Smallstep CA/0.28.4 (linux/arm64)
- Build Date: 2025-09-14 18:54 UTC