Configure Certificate Provisioners in step-ca | Smallstep

Configuring step-ca Provisioners

Provisioners are methods of using the CA to get certificates for humans or machines. They offer different modes of authorization for the CA. In this section we'll discuss the different provisioners, their target use cases, and how to add, remove, and configure them.

Overview

Choosing a provisioner

When choosing a provisioner for your use case, you can narrow your options by starting from the types of identifiers you need on a certificate.

Identifier Type JWK OAuth X5C ACME dns-01 ACME http-01 ACME tls-alpn-01 ACME device-attest-01 Nebula SCEP K8sSA Cloud Provisioners
Hostname โœ”๏ธ ๐—ซ โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ โœ”๏ธ โœ”๏ธ ๐—ซ โœ”๏ธ
IP address โœ”๏ธ ๐—ซ โœ”๏ธ ๐—ซ โœ”๏ธ โœ”๏ธ ๐—ซ โœ”๏ธ โœ”๏ธ ๐—ซ โœ”๏ธ
Hardware ID โœ”๏ธ ๐—ซ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ โœ”๏ธ ๐—ซ ๐—ซ
Service Account โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ โœ”๏ธ โœ”๏ธ ๐—ซ
Human โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ

Besides the identifier types described above, step-ca also integrates with and supports Wire messenger application identities. The configuration for this is described in ACME for Wire messenger clients.

You can also narrow your provisioner options based on trust dependencies. Trust dependencies are third parties you can leverage to bootstrap trust within your PKI.

Provisioner What Is Trusted?
JWK Private key bearers
OAuth An OIDC authorization server (Okta, Google, Azure, etc.)
X5C Another X.509 CA
ACME dns-01 Your DNS server and network
ACME http-01 Your network, DNS, and OS privileged port protections
ACME tls-alpn-01 Your network, DNS, and OS privileged port protections
ACME device-attest-01 Security hardware manufacturer (Apple, Infineon, Yubico, etc.)
Cloud Provisioners Your cloud provider (AWS, GCP, Azure)
SCEP Shared secret bearers

Authorization scope by provisioner

Every provisioner has a slightly different scope of authorization. Below is a table detailing the authorization capabilities of each provisioner.

Provisioner Capabilities x509-sign x509-renew x509-revoke ssh-user-cert-sign ssh-host-cert-sign ssh-user-cert-renew 1 ssh-host-cert-renew ssh-revoke ssh-rekey
JWK โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ
OIDC โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ 2 ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ
X5C โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ
Nebula โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ
K8sSA โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ
ACME โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ
SCEP โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ
SSHPOP ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ ๐—ซ โœ”๏ธ โœ”๏ธ โœ”๏ธ
AWS โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ
Azure โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ
GCP โœ”๏ธ โœ”๏ธ ๐—ซ ๐—ซ โœ”๏ธ ๐—ซ ๐—ซ ๐—ซ ๐—ซ

1

By design, SSH user certificates cannot be renewed by any provisioner. You can re-use the same key to get a new certificate, however. See smallstep/discussions#1296.

2

Privileged OIDC subject names can generate Host SSH Certificates. These can be configured in the OIDC provisioner.

For an example of how to interpret this table, let's take the JWK provisioner. The JWK provisioner is capable of signing, renewing, and revoking X.509 certificates, as well signing user and host SSH certificates. A JWK provisioner cannot renew or rekey SSH certificates.

An SSHPOP provisioner can revoke and rekey SSH certificates and renew SSH host certificates. An SSHPOP provisioner cannot sign, renew, or revoke X.509 certificates, and it cannot sign SSH user and host certificates or renew SSH user certificates.

It's important to understand the capabilities and limitations when selecting a provisioner for a given workload.

Provisioner management

Use the step ca provisioner command group to add, remove, or modify provisioner configurations. Run these commands directly on your CA machine. They need to modify the $(step path)/config/ca.json configuration file.

May I edit ca.json directly?

You may edit your ca.json configuration file directly, but we strongly recommend using step ca provisioner commands instead. Fields in ca.json may be encoded differently than you expect.

Some provisioner options override global defaults for your CA. For a list of global options, see the configuration guide section for the authority configuration block.

A remote provisioner management API can be enabled in step-ca. It is disabled by default. With remote provisioner management, the CA's provisioner configuration is stored in the database instead of ca.json. (The global CA configuration remains in ca.json.)

This feature can be useful if you have multiple CA administrators, run several load-balancedstep-ca instances, or if you want to manage your provisioners remotely (eg. with Infrastructure as Code (IaC) tools; see below).

See Remote Provisioner Management for more.

Common provisioner operations

Common provisioner operations include:

Unless you are using remote provisioner management, you must send a SIGHUP signal, or restart the step-ca process, for changes to your provisioner configuration to take effect.

Add a provisioner

Use step ca provisioner add to add a provisioner:

`step ca provisioner add acme --type ACME

`

See the command reference for complete documentation and examples.

Remove a provisioner

Use step ca provisioner remove to remove provisioners:

`step ca provisioner remove acme

`

You can also edit the ca.json configuration file and remove the entire block containing the provisioner you'd like to remove.

List all provisioners

To get a list of all of your current provisioners, use step ca provisioner list or the /provisioners endpoint on your CA.

Modify a provisioner

Use step ca provisioner update to update provisioner configurations:

`step ca provisioner update acme
--x509-min-dur=20m
--x509-max-dur=72h
--x509-default-dur=36h

`

In this example we've modified the minumum, maximum, and default durations for TLS certificates generated by this provisioner.

Provisioner configuration can be used to affect X.509 and SSH certificate lifetimes, extensions, and templates. Some provisioner-specific options exist, which are covered by the documentation for each provisioner type, below.

Remote provisioner management

This feature is disabled by default.

When remote provisioner management is enabled, your provisioner configuration is stored in the database, rather than in ca.json. Provisioner configuration is managed by running step ca provisioner commands locally or remotely. These commands require you to sign in as an Admin user.

Enable remote provisioner management

On a new CA

To enable remote provisioner management on a new CA, pass --remote-management to step ca init. This will:

On an existing CA

To migrate your ca.json provisioners to the database, and to enable remote provisioner management on an existing CA:

  1. Stop step-ca if it is running.
  2. Update your "authority" block in ca.json to include the following:

`"authority": { "enableAdmin": true, "provisioners": [
...
] },

`

  1. Start step-ca from the command line.

When step-ca starts up, it will:

  1. Remove old provisioner configurations from ca.json. Once your provisioners are migrated to the database, the provisioners in ca.json are ignored and you can remove them from that file: `"authority": { "enableAdmin": true, "provisioners": []

},

`

Managing admin users

With remote provisioner management,

"Admins" can remotely manage provisioners.

"Super Admins" are Admins that can also manage the list of Admins for the CA using step ca admin.

Create an admin user

To perform CA administrative operations, you sign into that provisioner as an Admin.

As a Super Admin, lets create an Admin user linked to the Admin JWK provisioner:

`step ca admin add carl "Admin JWK"

`

Output:

`SUBJECT PROVISIONER TYPE carl Admin JWK (JWK) ADMIN

`

Authenticating as an admin

An Admin (or a Super Admin) is a combination of a username (a subject name) and a provisioner. No passwords are associated with administrators. An administrator is just a subject name and a provisioner name.

The default Super Admin username is step, and the default admin provisioner is called Admin JWK.

You can authenticate as an administrator via the authentication scheme of the provisioner. For example, for the default JWK provisioner, running administrative commands in step will prompt for the provisioner password.

Tip: single sign-on can be used for Admin users. To enable SSO CA administration, first add an OIDC provisioner linked to your identity provider. Then, add an Admin user associated with that provisioner, using your email address as the Admin's subject name.

The Admin and Super Admin privileges are not scoped to a provisioner: admins can modify any provisioner configurations. Super Admins are Admins that can also modify Admins, regardless of provisioner.

Use remote provisioner management

You're all set. ๐ŸŽ‰

You can use the step ca provisioner commands, from any client, to modify your CA's provisioner configuration.

Unattended remote provisioner management

If you want to manage provisioners remotely using a script or an Infrastructure as Code (IaC) tool, you will need an administrative certificate and key.

An administrative certificate must meet the following criteria:

To create an admin certificate, run:

`step ca certificate step admin.crt admin.key --provisioner "Admin JWK"

`

Output:

`โœ” Provisioner: Admin JWK (JWK) [kid: 5UasaZcgkI_PxQeZqssfq5mzFnGHOWL5g5kDt07Vc1k] Please enter the password to decrypt the provisioner key: โœ” CA: https://localhost โœ” Certificate: admin.crt โœ” Private Key: admin.key

`

With that certificate, you can use it as follows:

`step ca provisioner add ... --admin-cert=admin.crt --admin-key=admin.key

`

If the admin.crt represents a Super Admin, the same flags can also be used to manage Admins:

`step ca admin add ... --admin-cert=admin.crt --admin-key=admin_key

`

Provisioner types

JWK

JSON Web Key (JWK) is the default CA provisioner type, and the step ca certificate client tries to use it by default. With a JWK provisioner, a Certificate Signing Request (CSR) is sent to the CA along with a short-lived JSON Web Token (JWT) which authenticates the request.

The JWK provisioner can be useful for custom integrations. JWTs are easy to generate programmatically, without using step. We have written a few example clients for demonstration purposes.

Here's an example JWT used by step-ca:

`$ step ca token foo.internal | step crypto jwt inspect --insecure { "header": { "alg": "ES256", "kid": "kXLZ2xHQEXAMPLEq4j38N5QzQzNBmOPp0NCLzKAI0yA", "typ": "JWT" }, "payload": { "aud": "https://ca.internal/1.0/sign", "exp": 1711479459, "iat": 1711479159, "iss": "carl@tashian.com", "jti": "8211a180b6EXAMPLEed81da54132e9...", "nbf": 1711479159, "sans": [
"foo.internal"
], "sha": "c8de28e620ecEXAMPLE4a0c405d92d7350dbec351cec3e4f6a6d1fc9512387aa", "sub": "foo.internal" }, "signature": "MEcSHfmIEXAMPLElNiLoDcHz90..." }

`

The JWT subject (sub) specifies a subject Common Name for the issued certificate. The sha and sans claims are non-standard: they specify the root CA certificate fingerprint in SHA256 and any subject alternative names that are authorized for inclusion in the issued certificate.

The JWT is designed to be generated in one context, then passed into the context where it will be used (eg. a container or pod). Meanwhile, sensitive private key material can remain in the context where it is generated. Therefore, by default, the JWT is not bound to the CSR, which may not yet exist when the token is created. The JWT is instead bound to the expected SANs, subject common name, and the CA's root fingerprint. See Issue a Certificate Using A Single-Use CA Token for an example.

Optionally, a confirmation claim (cnf) can be added (as defined in RFC 7800 ยง 3.4 to bind the JWT to a CSR. See smallstep/certificates#1637 for details.

Example

Add a JWK provisioner:

`step ca provisioner add you@smallstep.com --create

`

In the ca.json configuration file, a complete JWK provisioner example looks like:

`{ "type": "JWK", "name": "you@smallstep.com", "key": { "use": "sig", "kty": "EC", "kid": "NPM_9Gz_omTqchS6Xx9Yfvs-EuxkYo6VAk4sL7gyyM4", "crv": "P-256", "alg": "ES256", "x": "bBI5AkO9lwvDuWGfOr0F6ttXC-ZRzJo8kKn5wTzRJXI", "y": "rcfaqE-EEZgs34Q9SSH3f9Ua5a8dKopXNfEzDD8KRlU" }, "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiTlV6MjlEb3hKMVdOaFI3dUNjaGdYZyJ9.YN7xhz6RAbz_9bcuXoymBOj8bOg23ETAdmSCRyHpxGekkV0q3STYYg.vo1oBnZsZjgRu5Ln.Xop8AvZ74h_im2jxeaq-hYYWnaK_eF7MGr4xcZGodMUxp-hGPqS85oWkyprkQLYt1-jXTURfpejtmPeB4-sxgj7OFxMYYus84BdkG9BZgSBmMN9SqZItOv4pqg_NwQA0bv9g9A_e-N6QUFanxuYQsEPX_-IwWBDbNKyN9bXbpEQa0FKNVsTvFahGzOxQngXipi265VADkh8MJLjYerplKIbNeOJJbLd9CbS9fceLvQUNr3ACGgAejSaWmeNUVqbho1lY4882iS8QVx1VzjluTXlAMdSUUDHArHEihz008kCyF0YfvNdGebyEDLvTmF6KkhqMpsWn3zASYBidc9k._ch9BtvRRhcLD838itIQlw", "claims": { "minTLSCertDuration": "5m", "maxTLSCertDuration": "24h", "defaultTLSCertDuration": "24h", "disableRenewal": false, "minHostSSHCertDuration": "5m", "maxHostSSHCertDuration": "1680h", "minUserSSHCertDuration": "5m", "maxUserSSHCertDuration": "24h", "enableSSHCA": true }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }

`

*

Recommended

**

Optional

Decrypting the private key

We can use step crypto jwe decrypt to see the private key encrypted with the password asdf:

$ step ca provisioner list \ | jq -r '.[] | select(.name == "you@smallstep.com") | .encryptedKey' \ | step crypto jwe decrypt \ | jq Please enter the password to decrypt the content encryption key: { "use": "sig", "kty": "EC", "kid": "NPM_9Gz_omTqchS6Xx9Yfvs-EuxkYo6VAk4sL7gyyM4", "crv": "P-256", "alg": "ES256", "x": "bBI5AkO9lwvDuWGfOr0F6ttXC-ZRzJo8kKn5wTzRJXI", "y": "rcfaqE-EEZgs34Q9SSH3f9Ua5a8dKopXNfEzDD8KRlU", "d": "rsjCCM_2FQ-uk7nywBEQHl84oaPo4mTpYDgXAu63igE" }

Changing a JWK provisioner password

  1. Retrieve the current encrypted key.

Run the following, changing the provisioner name in the jq command to match your configuration: `OLD_ENCRYPTED_KEY=$(step ca provisioner list
| jq -r '.[] | select(.name == "you@smallstep.com").encryptedKey')

` 2. Update the encrypted key password.

Run: `ENCRYPTED_KEY=$(echo $OLD_ENCRYPTED_KEY |
step crypto jwe decrypt |
step crypto jwe encrypt --alg PBES2-HS256+A128KW |
step crypto jose format)

` You'll be asked for the old and new passwords.

  1. Update the provisioner.

Run the following, changing the provisioner name in the command to match your configuration: `step ca provisioner update you@smallstep.com
--private-key=<(echo -n "$ENCRYPTED_KEY")

4. Send aSIGHUPtostep-ca` to reload the configuration file.

(Skip this step if you have enabled remote provisioner administration.) `killall -i -s SIGHUP step-ca

`

Rekeying a JWK provisioner

  1. Update the provisioner.

Run the following, changing the provisioner name in the command to match your configuration: `step ca provisioner update you@smallstep.com --create

2. Send aSIGHUPtostep-ca` to reload the configuration file.

(Skip this step if you have enabled remote provisioner administration.) `killall -i -s SIGHUP step-ca

`

Removing the encrypted private key from a JWK provisioner

The encrypted private key stored in the JWK provisioner configuration and published to the public /provisioners endpoint is provided for client convenience. It is not required for step-ca to operate. To remove this key:

  1. Update the provisioner.

Run the following, changing the provisioner name in the command to match your configuration: `step ca provisioner update you@smallstep.com --private-key ""

2. Send aSIGHUPtostep-ca` to reload the configuration file.

(Skip this step if you have enabled remote provisioner administration.) `killall -i -s SIGHUP step-ca

`

OAuth/OIDC single sign-on

Sometimes it's useful to issue certificates to people. So step-ca supports single sign-on with identity providers (IdPs) like Google, Okta, Microsoft Entra ID, Keycloak, or any other provider that supports OAuth's OpenID Connect extension.

OpenID Connect is an extension to OAuth 2.0 that adds an identity layer. Providers that support OIDC can issue identity tokens ("ID tokens") to OAuth clients. These are JSON Web Tokens (JWTs) containing user identity information (eg. full name, username, email address). Like certificates, OIDC tokens have a validity period and are cryptographically signed by a trust authority (the OAuth provider).

Here's an example OIDC identity token issued by Google:

`{ "alg": "RS256", "kid": "cd49b2ab16e1e9a496c8239dac0dadd09d443012", "typ": "JWT" }.{ "iss": "https://accounts.google.com", "azp": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "aud": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "sub": "115449349109627210866", "hd": "smallstep.com", "email": "mike@smallstep.com", "email_verified": true, "at_hash": "lE6o-GdMpurFQ0WrJ9-H7g", "nonce": "5f5820880a43c3f50d55ce79af15430b14b4059bdf4efbe717da6af8bfc53122", "iat": 1621877714, "exp": 1621881314 }.[Signature]

`

The OIDC provisioner in step-ca can be configured to trust and accept an OAuth provider's ID tokens for authentication. By default, the issued certificate will use the subject (sub) claim from the identity token as its subject. The value of the token's email claim is also included as an email SAN in the certificate.

Fig. 3: diagram of how step works with individuals using a single sign-on provisioner

From the user's perspective, when requesting a certificate, step detects the OIDC provisioner and initiates the OAuth login flow automatically:

`$ step ca certificate mike@smallstep.com mike.crt mike.key

โœ” Key ID: 650445034027-jsjdrkiskeq9ke99ud2rqkst82ft8uch.apps.googleusercontent.com (Google) โœ” CA: https://ca.internal โœ” Certificate: mike.crt โœ” Private Key: mike.key ``$ step certificate inspect --short mike.crt X.509v3 TLS Certificate (ECDSA P-256) [Serial: 2581...6739] Subject: 115449349109627210866 mike@smallstep.com Issuer: Smallstep Intermediate CA Provisioner: Google [ID: 6504....com] Valid from: 2019-06-20T18:21:52Z to: 2019-06-21T18:21:52Z `

Configuring your identity provider (IdP)

When creating an OAuth app, there isn't much to configure on the IdP. Most providers will ask you to specify a Redirect URI, where the ID token will be delivered at the end of the OAuth flow. Since step starts its own local web server to receive the token, use http://127.0.0.1 as the Redirect URI.

Example: Google identity

One of the most common providers, and the one used in the following example, is Google Identity.

Add a Google provisioner:

`$ step ca provisioner add Google --type oidc
--client-id 650445034027-jsjdrkiskeq9ke99ud2rqkst82ft8uch.apps.googleusercontent.com
--client-secret 6Q7lGMua_Oox4nA92QBXYypT
--configuration-endpoint https://accounts.google.com/.well-known/openid-configuration
--domain smallstep.com --domain gmail.com

`

Example ca.json provisioner configuration for a Google provisioner:

`{ "type": "OIDC", "name": "Google", "clientID": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "clientSecret": "udTrOT3gzrO7W9fDPgZQLfYJ", "configurationEndpoint": "https://accounts.google.com/.well-known/openid-configuration", "admins": ["you@smallstep.com"], "domains": ["smallstep.com"], "scopes": ["openid", "email"], "listenAddress": ":10000", "claims": { "maxTLSCertDuration": "8h", "defaultTLSCertDuration": "2h", "disableRenewal": true }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }

`

*

Optional

Browserless console mode

For OpenID Connect, by default, step opens a web browser to run the Authorization Code flow with PKCE.

Sometimes it's helpful to use OAuth in an input-constrained environment where no web browser is available. The Device Authorization Grant flow (aka "Device Flow") is an OAuth 2.0 extension designed for this scenario. The step client supports the Device Authorization Grant flow.

To use the Device Authorization Grant flow for input-constrained devices, run:

`$ step ca certificate foo foo.crt foo.key --console

`

or

`$ STEP_CONSOLE=true step ssh certificate carl carl.crt

`

Notes

Why is the OAuth client secret unprotected?

When using the OIDC provisioner, you may notice that your OAuth client secret is visible to anyone via the CA's /provisioners API endpoint. Counterintuitively, this is a secure implementation of OAuth that conforms to the OAuth Best Current Practices for Native Apps ( RFC8252 / IETF BCP212). And it is the same approach that Google's gcloud CLI tool uses for Google Cloud Platform authentication: An OAuth client secret is hardcoded into its source code.

So, what makes it secure? The Authorization Code flow for native OAuth apps requires the redirect URI hostname be hardcoded as 127.0.0.1 (or localhost) in the client configuration. This constraint obviates the need for a client secret, because the loopback address is inherently resistant to network attacks that the client secret is designed to mitigate in other, non-native app flows.

An attacker in posession of the client secret would need local access to your device in order to compromise the flow. OAuth in general is not very resistant to local attacks, so the threat model for the native app flow with an exposed client secret is the same as with any other OAuth flow: It assumes that if you have a local attacker on your device, it's unlikely that this kind of attack is going to be your biggest threat.

The client secret is superfluous in the Authorization Code flow for native apps. In fact, BCP212 has recommended that OAuth identity providers offer a special OAuth client type that has no client secret. In practice, very few OAuth providers have implemented this "secretless" approach, so we don't yet support it. Functionally, however, it is equivalent to having a public secret.

Bottom line, the OAuth flow implemented in step and step-ca is widely vetted and considered secure.