[Bug]: can't remove or replace invalid x509 template
Steps to Reproduce
- Import this template with
step ca provisioner update SSO --x509-template template.tpl
{
"subject": {{ toJson .Subject }},
"sans": {{ toJson .SANs }}
{{- if .Token.groups }}
{{- range .Token.groups }}
, {{ "urn:group:{{ . }}" | toJson }}
{{- end }}
{{- end }},
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
"keyUsage": ["keyEncipherment", "digitalSignature"],
{{- else }}
"keyUsage": ["digitalSignature"],
{{- end }}
"extKeyUsage": ["serverAuth", "clientAuth"]
}
- try to get a new certificate with
step ca certificate "[email protected]" cert.pem key.pem --san "[email protected]" - error:
✔ Provisioner: SSO (OIDC) [client: ***********]
Your default web browser has been opened to visit:
https://login.microsoftonline.com/******
✔ CA: https://ca.example.org
error applying certificate template
- try to remove it with
step ca provisioner update SSO --x509-template "", error:
❯ step ca provisioner update SSO --x509-template ""
No admin credentials found. You must login to execute admin commands.
✔ Provisioner: SSO (OIDC) [client: ******]
Your default web browser has been opened to visit:
https://login.microsoftonline.com/*******
error applying certificate template
Your Environment
- OS - Docker
step-caVersion - Smallstep CA/0.28.3 (linux/arm64) Release Date: 2025-03-18 15:56 UTC
Expected Behavior
- server complains about wrong template before saving it
- server allows to remove invalid template
Actual Behavior
See errors above
Additional Context
in server's logs I see
time="2025-10-28T17:09:13Z" level=error duration=5.391514ms duration-ns=5391514 error="error applying certificate template: invalid character ',' after object key at offset 319" fields.time="2025-10-28T17:09:13Z" method=POST name=ca ott=*******
Contributing
Vote on this issue by adding a 👍 reaction. To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).
Hi @DenisMkS, the template you're trying to run will print something like this (with two groups:
{
"subject": {"commonName":"104423550502665920102"},
"sans": [{"type":"email","value":"[email protected]"},{"type":"uri","value":"https://accounts.google.com#104423550502665920102"}]
, "urn:group:{{ . }}"
, "urn:group:{{ . }}",
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["serverAuth", "clientAuth"]
}
This has a couple of issues, the first one is clear, the groups are not encoded, to solve this we can do {{ printf "urn:group:%s" . | toJson }}, but this will render:
{
"subject": {"commonName":"104423550502665920102"},
"sans": [{"type":"email","value":"[email protected]"},{"type":"uri","value":"https://accounts.google.com#104423550502665920102"}]
, "urn:group:foo"
, "urn:group:bar",
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["serverAuth", "clientAuth"]
}
And this is not valid json, my guess is that you want to add those to URIs, a valid template would be something like this:
{
"subject": {{ toJson .Subject }},
"sans": {{ toJson .SANs }},
{{- if .Token.groups }}
"uris": [
{{- if $v := first .Token.groups }}
{{ printf "urn:group:%s" $v | toJson }}
{{- end }}
{{- range rest .Token.groups }}
,{{ printf "urn:group:%s" . | toJson }}
{{- end }}
],
{{- end }}
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
"keyUsage": ["keyEncipherment", "digitalSignature"],
{{- else }}
"keyUsage": ["digitalSignature"],
{{- end }}
"extKeyUsage": ["serverAuth", "clientAuth"]
}
To avoid the if condition, we can create the list first, and then just apply toJson to it:
{
"subject": {{ toJson .Subject }},
"sans": {{ toJson .SANs }},
{{- if .Token.groups }}
{{ $u := list }}
{{- range .Token.groups }}
{{ $u = append $u (printf "urn:group:%s" .) }}
{{- end }}
"uris": {{ $u | toJson }},
{{- end }}
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
"keyUsage": ["keyEncipherment", "digitalSignature"],
{{- else }}
"keyUsage": ["digitalSignature"],
{{- end }}
"extKeyUsage": ["serverAuth", "clientAuth"]
}
Thank you @maraino for such detailed explanation, I'll definitely try it. But my current issue is that I can't replace or remove a template because server always returns error applying certificate template despite I send a new correct template or try to remove existing with --x509-template ""
Just in case here is my full config for SSO provisioner returned via step ca provisioner list
{
"type": "OIDC",
"name": "SSO",
"clientID": "***********,
"clientSecret": "***********",
"configurationEndpoint": "https://login.microsoftonline.com/**********/v2.0/.well-known/openid-configuration",
"claims": {
"enableSSHCA": true,
"disableRenewal": false,
"allowRenewalAfterExpiry": false,
"disableSmallstepExtensions": false
},
"options": {
"x509": {
"template": "{\n \"subject\": {{ toJson .Subject }},\n \"sans\": {{ toJson .SANs }}\n{{- if .Token.groups }}\n {{- range .Token.groups }}\n , {{ \"urn:group:{{ . }}\" | toJson }}\n {{- end }}\n{{- end }},\n{{- if typeIs \"*rsa.PublicKey\" .Insecure.CR.PublicKey }}\n \"keyUsage\": [\"keyEncipherment\", \"digitalSignature\"],\n{{- else }}\n \"keyUsage\": [\"digitalSignature\"],\n{{- end }}\n \"extKeyUsage\": [\"serverAuth\", \"clientAuth\"]\n}\n"
},
"ssh": {
"template": "{\n \"type\": {{ toJson .Type }},\n \"keyId\": {{ toJson .KeyID }},\n\t\"principals\": {{ toJson ((concat .Principals .Token.groups) | uniq) }},\n \"extensions\": {{ toJson .Extensions }},\n \"criticalOptions\": {{ toJson .CriticalOptions }}\n}\n"
}
}
}
Now even step ca admin list returns error applying certificate template.
@DenisMkS a short term solution would be to remove the "template" from the x509 options by manually editing the ca.json.
A better fix would be to prevent invalid templates from being saved in the first place. It's not trivial to do so, because the template contains variables that are not known at the time it gets saved by the CA, so it can't readily check if the template would render correctly under all circumstances.
@hslatman It would be the easiest solution, but the issue is, that I don't have it in ca.json. Looks like it's stored in DB.
Server complains about template on several commands, not only during generating certificate, I guess it could be validated "upfront".
If I got it right, it should be fixed by https://github.com/smallstep/certificates/pull/1030, but looks like it's not. Anyway, next time I'll start a local ca to test template, but I need to remove invalid template from "production" first.
@DenisMkS that did add template validation, but as mentioned, it doesn't have actual values filled in for variables, which are dynamically loaded based on the provisioner type and additional integrations: https://github.com/smallstep/crypto/blob/master/internal/templates/validate.go#L10-L15.
The reason it complains on more requests is that the template is actually used to sign a certificate when you authenticate to the CA using that provisioner, so basically most operations will hit it. In general we advise testing templates on a different provisioner first to make sure that it works as expected.
I assumed you had them in ca.json, but with remote provisioner management, they're indeed stored in the database. It'll be a bit harder to get that fixed, indeed. You could recreate the CA using your existing intermediate and root, though; using a different database directory / wiping your current one. Some high level docs on that: https://smallstep.com/docs/tutorials/intermediate-ca-new-ca/.
I manged to fix it. TLDR: I used JWK provisioner to remove broken template from SSO provisioner.
Long story:
my ~/.step/config/defaults.json looks like this
{
"ca-url": "https://ca.example.org",
"fingerprint": "******",
"root": "/Users/denis/.step/certs/root_ca.crt",
"admin-subject": "[email protected]",
"admin-provisioner": "SSO",
"provisioner": "SSO",
"identity": "[email protected]"
}
to avoid typing --provisioner and --identity every time for step ssh login (see https://github.com/smallstep/cli/issues/1455) and the same for admin actions.
My fix was
- temporary remove
"admin-subject": "[email protected]",
"admin-provisioner": "SSO",
"provisioner": "SSO",
"identity": "[email protected]"
- execute
step ca provisioner update SSO --x509-template "" - type
stepasadmin name/subject - select
admin (JWK) ...asprovisioner key - paste
the password to decrypt the provisioner key.
After this the template has been successfully removed.