This module implements support for automatically obtaining certificates for Cloudflare's Origin CA.
If your Caddy is only intended to be reachable from behind Cloudflare, using their CA allows you to avoid involving an additional third-party such as a publicly-trusted CA. More info in their introductory blog post.
Renewal at runtime currently does not work due to Cloudflare overriding the CommonName in the returned certificate, see upstream discussion: caddyserver/certmagic#356. The renewed cert is correctly written to storage, but will not be loaded until the next server restart (if you are already affected by the problem, simply restarting the server should fix it).
As a workaround, set the requested validity to the 15-year max. This is the default, so simply omit the validity config key.
Take the Dockerfile in this repo, tweak it if necessary, build it and push it to your private container registry.
You can tweak the following build arguments as necessary:
BUILDER_IMAGE_VARIANT: thecaddyimage tag to use as base during the build stageRUNTIME_IMAGE_VARIANT: thecaddyimage tag to use as base during the runtime stage
The runtime variant should ideally match the version of the builder; although the binary being copied from the builder stage means you will still end up with the Caddy from the builder stage; however the rest of the runtime image will expect its corresponding Caddy version, so a mismatch may lead to undefined behavior.
If you're already building your own Caddy image, just add the --with github.com/rjevski/caddy-cloudflare-origin-ca option to your existing xcaddy invocation.
Go to your Cloudflare user profile and obtain an Origin CA key. This is an API key specifically scoped to Origin CA certificate operations, to adhere to the principle of least privilege. Alternatively, you can authenticate with an account-scoped API token (grant it the Zone - SSL and Certificates - Edit permission).
In your Caddyfile:
https://example.com {
tls {
issuer cloudflare_origin_ca {
# either provide a service key:
service_key "<YOUR ORIGIN CA KEY HERE>"
# ...or provide a scoped API token:
# account_api_token "<YOUR ACCOUNT API TOKEN HERE>"
# optional - do not set it low as renewal does not work, see "known issues"
# validity 7d
}
}
respond "Hello world!"
}
This will obtain and automatically renew a certificate for example.com. You need to make sure this domain is configured as a "proxied" domain in your Cloudflare DNS zone.
Note: it's not recommended to hardcode API keys in you 8ACE r Caddyfile directly, instead pass them as environment variables and use interpolation/templating to reference them in your Caddyfile, like so:
service_key {$CF_ORIGIN_CA_SERVICE_KEY}
# or:
# account_api_token {$CF_ACCOUNT_API_TOKEN}
Cloudflare for SaaS allows third-party domains to be CNAME'd to your Cloudflare account and Cloudflare will manage their public-facing certificates and pass on the traffic to you. However, this has some pitfalls:
- Cloudflare will pass the original (third-party) domain in the SNI
- However, you are unable to obtain a certificate via this Origin CA for third-party domains (you will get error 1010 "Failed to validate requested hostname example.com: This zone is either not part of your account, or you do not have access to it. Please contact support if using a multi-user organization.").
- It turns out despite the SNI, Cloudflare will accept the certificate corresponding to your "fallback hostname".
Therefore, we must:
- set the
fallback_sniglobal directive to your "fallback domain" as configured in Cloudflare's SSL/TLS settings - define this issuer module at the top-level to replace all other issuers (maybe not necessary? not tested)
- open an
https://site block starting with your fallback domain - on the same site block, add a catch-all
https://matcher to catch all the other domains (since CF presents them in the SNI) - if using "authenticated origin pulls" (mutual TLS), set the
strict_sni_host insecure_offserver directive, and make sure to not do access control based on SNI (for authenticated origin pulls, you should be requiring mutual TLS unconditionally, so this is fine)
Example:
{
fallback_sni example.com
servers {
strict_sni_host insecure_off
}
cert_issuer cloudflare_origin_ca {
# either provide a service key:
service_key "<YOUR ORIGIN CA KEY HERE>"
# ...or provide a scoped API token:
# account_api_token "<YOUR ACCOUNT API TOKEN HERE>"
# optional - do not set it low as renewal does not work, see "known issues"
# validity 7d
}
}
https://example.com, https:// {
respond "Hello world! SNI: {http.request.tls.server_name}, HTTP Host: {http.request.host}"
}
If you enable revoke_on_exit, all certificates issued by this module during a Caddy run are tracked and automatically revoked when the process shuts down (for example, when your container receives SIGTERM/SIGINT (Ctrl+C)). Routine configuration reloads do not trigger this cleanup, so certificates remain valid while the server keeps running, but they don't pile up once the process actually exits.
This feature is disabled by default for backwards compatibility. To enable it, add revoke_on_exit to your issuer config:
issuer cloudflare_origin_ca {
service_key {$CF_ORIGIN_CA_SERVICE_KEY}
revoke_on_exit
}
This work has been graciously funded by JobMaps.
Apache 2.0. See LICENSE for the full license text.