certificates icon indicating copy to clipboard operation
certificates copied to clipboard

[Bug]: can't remove or replace invalid x509 template

Open DenisMkS opened this issue 1 month ago • 8 comments

Steps to Reproduce

  1. 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"]
}
  1. try to get a new certificate with step ca certificate "[email protected]" cert.pem key.pem --san "[email protected]"
  2. 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
  1. 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-ca Version - 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).

DenisMkS avatar Oct 28 '25 17:10 DenisMkS

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"]
}

maraino avatar Oct 28 '25 19:10 maraino

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 ""

DenisMkS avatar Oct 29 '25 08:10 DenisMkS

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 avatar Oct 29 '25 08:10 DenisMkS

@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 avatar Oct 29 '25 09:10 hslatman

@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".

DenisMkS avatar Oct 29 '25 11:10 DenisMkS

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 avatar Oct 29 '25 11:10 DenisMkS

@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/.

hslatman avatar Oct 29 '25 11:10 hslatman

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 step as admin name/subject
  • select admin (JWK) ... as provisioner key
  • paste the password to decrypt the provisioner key.

After this the template has been successfully removed.

DenisMkS avatar Oct 29 '25 11:10 DenisMkS