Enroll with ACME
ACME (RFC 8555) is the protocol behind Let's Encrypt, and it is built into most modern TLS-terminating software: certbot, acme.sh, Caddy, Traefik, cert-manager. A Strongbox TLS CA can serve ACME, so such software enrolls and renews against your own CA with the client it already has, and with no shared secret in the certificate request path.
This is the server side of ACME. The platform is also an ACME client when it obtains publicly trusted certificates from an external service; that is described in ACME certificate provisioning.
For the background on device enrollment, see Device certificate enrollment.
How accounts are authorized
Public ACME services prove control of a name with a challenge: the CA connects to the name over HTTP, or looks up a DNS record. Neither works for equipment behind a firewall that the CA cannot reach, or on a network without public DNS. Strongbox therefore uses the other mechanism RFC 8555 provides, External Account Binding (EAB): the client presents a key identifier and an HMAC over its account key, computed with a shared key you configure on the CA. An account bound this way is pre-authorized for every name the CA's enrollment policy permits, so an order needs no per-name challenge and can be finalized at once.
This is the same trust model as the SCEP challenge password and the EST initial-enroll secret: the EAB key vouches for the enrolling party, and the enrollment policy bounds what it can obtain. Use a separate key per group of devices that is rolled out or retired together, so that one key can be disabled without affecting the others.
Enable ACME on a CA
Create or update a TLS CA with an acme section:
supctl create strongbox tls ca <<EOF
name: devices
acme:
enabled: true
allowed-domains:
- example.com
allow-subdomains: true
ttl: 30d
EOF
Then generate an EAB key for each group of clients. The action adds
the key to the CA's eab-keys and returns it; like the other
enrollment secrets it stays readable on the CA for its owner:
supctl do strongbox tls ca devices generate-eab-key site-stockholm
hmac-key: 7Vb2...
A key can also be configured directly, for example from an existing
provisioning system, as an eab-keys entry with kid and a base64url
encoded hmac-key of 16 characters or more.
ACME shares its identity policy (allowed-domains, allow-subdomains,
allow-bare-domains, allow-ip-sans, ttl) with SCEP and EST, so a
certificate carries the same guarantees whichever protocol was used.
See Enroll devices with SCEP for what each setting does.
ACME is served on the enrollment port, 4665, next to EST. The CA
publishes the directory URLs:
supctl show strongbox tls ca devices --fields acme
acme:
enabled: true
eab-keys:
- kid: site-stockholm
allowed-domains:
- example.com
ttl: 30d
directory-url:
- https://192.168.1.10:4665/acme/<tenant-uuid>/devices/directory
- https://control-tower.example.com:4665/acme/<tenant-uuid>/devices/directory
A client needs only the directory URL; every other endpoint is discovered through it. Address-based URLs come first, for devices without DNS. As for EST, the URLs are local to whichever API answered: read the CA through a site's API and they carry that site's addresses. Devices at a site should enroll against their local site, so that enrollment and renewal keep working when the site's uplink is down.
The enrollment port presents the platform's API certificate. Give the client the CA bundle that signs it, or, on a closed network, tell the client not to verify the server certificate; the EAB key is what authenticates the exchange.
Enroll with certbot
certbot certonly --non-interactive --agree-tos \
--register-unsafely-without-email \
--server https://192.168.1.10:4665/acme/<tenant-uuid>/devices/directory \
--eab-kid site-stockholm --eab-hmac-key=<base64url encoded key> \
--standalone --key-type ecdsa -d sensor-7.example.com
certbot registers an account bound to the EAB key on the first run and
stores it, then renews with certbot renew on its own schedule, which
re-orders on the stored account. The chain it saves next to the
certificate carries every live root of the CA, so a client that
validates against it keeps working across a CA rollover.
To disable an EAB key, set disabled: true on it. New registrations
with that key are refused; accounts already bound to it keep working.
Enroll with Caddy
Caddy manages certificates for its sites through its built-in ACME client. In the global options:
{
acme_ca https://192.168.1.10:4665/acme/<tenant-uuid>/devices/directory
acme_ca_root /etc/caddy/api-ca.pem
acme_eab {
key_id site-stockholm
mac_key <base64url encoded key>
}
}
sensor-7.example.com {
reverse_proxy localhost:8080
}
Caddy obtains a certificate for each site name when it starts and
renews it before expiry. Since every name must be permitted by the CA's
enrollment policy, a site block with a name outside allowed-domains
fails to get a certificate and Caddy logs the rejectedIdentifier
error from the CA.
Keep the key in a vault secret
When the ACME client runs as an application on the platform, let the
platform hand it the EAB key instead of writing the key into the
application specification. Give generate-eab-key a vault, and the key
identifier and the key are also written to a secret there, under the
keys kid and hmac-key:
supctl do strongbox tls ca devices generate-eab-key site-stockholm \
--vault acme-eab --secret caddy
The application reads them as variables and passes them to Caddy, which expands environment variables in its configuration:
services:
- name: proxy
variables:
- name: EAB_KID
value-from-vault-secret:
vault: acme-eab
secret: caddy
key: kid
- name: EAB_HMAC
value-from-vault-secret:
vault: acme-eab
secret: caddy
key: hmac-key
containers:
- name: caddy
image: caddy
env:
EAB_KID: ${EAB_KID}
EAB_HMAC: ${EAB_HMAC}
acme_eab {
key_id {env.EAB_KID}
mac_key {env.EAB_HMAC}
}
To rotate, generate a new key into the same secret:
supctl do strongbox tls ca devices generate-eab-key site-stockholm-2 \
--vault acme-eab --secret caddy
The variables are mutable, so the running containers are restarted with
the new key (see on-mutable-variable-change). The key is only used when
an account is registered: a Caddy that already has an account keeps
renewing with it, so the old key can be disabled once no new
installations need it. The vault must be distributed to the sites the
application runs on.
What the server does and does not do
- Orders start
ready: their authorizations are valid on creation and carry no challenges. A client that insists on solving a challenge will find none to solve; all mainstream clients skip authorizations that are already valid. - Only
dnsidentifiers are accepted, at most 100 per order, and the certificate request is checked against the order and the enrollment policy atfinalize. - Wildcard identifiers (
*.example.com) are refused. Without a challenge, a wildcard would let any holder of an EAB key obtain a certificate for every name under the domain. - An account can be deactivated by the client (for example
certbot unregister); it is then removed, and the EAB key can register a new one. - A certificate can be revoked through ACME (
revokeCert) with a request signed by the certificate's own key, for examplecertbot revoke --cert-path ... --key-path ...; no account is needed, so a device can revoke its certificate on decommissioning or key compromise. Revocation by account key and account key rotation (keyChange) are not offered. A revocation made at a site reaches the Control Tower and appears in the CA's CRL like any other. - Certificates issued through ACME are covered by the CA's
expiry-reminderson the same terms as SCEP and EST (theenrolledsetting): a renewal supersedes the record for the same subject, so a reminder alert means a client stopped renewing. - Account and order state is kept per CA with bounded size: idle accounts are evicted after 30 days, and each EAB key gets a fair share of the account quota, so a leaked key can only exhaust its own share. This state is not part of the CA's replicated configuration: a site that resynchronizes its state drops the accounts, and a client whose account has disappeared must register again with its EAB key. Certificates are unaffected.
Troubleshooting
externalAccountRequired. The client did not present an EAB. Every
account must bind to one of the CA's eab-keys.
unauthorized on registration. The EAB signature did not verify:
check the key identifier and the HMAC key, and that the key is not
disabled.
rejectedIdentifier. A name in the order is not permitted by
allowed-domains, allow-subdomains and allow-bare-domains, is a
wildcard, or (at finalize) the certificate request names something the
order did not.
badNonce. The client reused or held a nonce too long; clients
retry this on their own.
accountDoesNotExist. The account is gone, typically after a site
resync. Remove the client's stored account and let it register again.
404 Not Found on the directory. ACME is not enabled on that CA,
or the tenant or CA name in the URL is wrong. Use the URLs published
under the CA's acme section.