cloudflareoriginca

package module
v0.0.0-...-9be337d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 19, 2025 License: Apache-2.0 Imports: 16 Imported by: 0

README

Caddy support for Cloudflare Origin CA

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.

Known issues

Renewal at runtime currently does not work due to Cloudflare overriding the CommonName in the returned certificate, see upstream discussion: https://github.com/caddyserver/certmagic/issues/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.

Installation

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: the caddy image tag to use as base during the build stage
  • RUNTIME_IMAGE_VARIANT: the caddy image 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.

Usage

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).

Single domain

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 your 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}
"Custom Hostnames"

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_sni global 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_off server 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}"
}

Automatic cleanup (opt-in)

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
}

Credits

This work has been graciously funded by JobMaps.

License

Apache 2.0. See LICENSE for the full license text.

Documentation

Index

Constants

View Source
const (
	DefaultCertificateAPIBaseURL = "https://api.cloudflare.com/client/v4/certificates"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client handles communication with the Cloudflare Origin CA API

func NewClient

func NewClient(serviceKey, accountAPIToken string, options ...ClientOption) *Client

NewClient creates a new Cloudflare Origin CA API client

func (*Client) RequestCertificate

func (c *Client) RequestCertificate(ctx context.Context, csr string, hostnames []string, requestType string, requestedValidity int) (string, string, error)

RequestCertificate requests a new certificate from Cloudflare Origin CA

func (*Client) RevokeCertificate

func (c *Client) RevokeCertificate(ctx context.Context, certID string) (*RevokeResult, error)

RevokeCertificate revokes a certificate by its ID

type ClientOption

type ClientOption func(*Client)

ClientOption allows customization of the Client

func WithBaseURL

func WithBaseURL(url string) ClientOption

WithBaseURL sets a custom base URL for the API

type CloudflareOriginCA

type CloudflareOriginCA struct {
	// Cloudflare Origin CA service key, a type of token
	// only allowed to manage certificates for a given zone
	ServiceKey string `json:"service_key,omitempty"`

	// Cloudflare account-scoped API token
	AccountAPIToken string `json:"account_api_token,omitempty"`

	// RequestedValidity is the duration for certificate validity (optional, max 15 years)
	// If not specified, lets Cloudflare pick a default (currently 15 years)
	RequestedValidity int `json:"requested_validity,omitempty"`

	// RevokeOnExit controls whether certificates issued by this module are
	// automatically revoked when the Caddy process exits gracefully.
	// Disabled by default.
	RevokeOnExit bool `json:"revoke_on_exit,omitempty"`

	// BaseURL allows overriding the API endpoint (optional, for testing)
	BaseURL string `json:"base_url,omitempty"`
	// contains filtered or unexported fields
}

func (CloudflareOriginCA) CaddyModule

func (CloudflareOriginCA) CaddyModule() caddy.ModuleInfo

func (*CloudflareOriginCA) Issue

func (*CloudflareOriginCA) IssuerKey

func (c *CloudflareOriginCA) IssuerKey() string

func (*CloudflareOriginCA) PreCheck

func (c *CloudflareOriginCA) PreCheck(_ context.Context, names []string, _ bool) error

PreCheck checks to reject names that are not supported by Cloudflare note: it's likely wildcard names like these are a side effect of an incorrect Caddy config

func (*CloudflareOriginCA) Provision

func (c *CloudflareOriginCA) Provision(ctx caddy.Context) error

func (*CloudflareOriginCA) Revoke

func (*CloudflareOriginCA) UnmarshalCaddyfile

func (c *CloudflareOriginCA) UnmarshalCaddyfile(d *caddyfile.Dispenser) error

type RevokeResult

type RevokeResult struct {
	AlreadyRevoked bool
	NotFound       bool
	RevokedAt      string
}

RevokeResult contains information about a revoked certificate

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL