caddydns01proxy

package module
v0.1.10 Latest Latest
Warning

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

Go to latest
Published: Jan 1, 2026 License: Apache-2.0 Imports: 32 Imported by: 1

README

dns01proxy package for Caddy

dns01proxy is a server for using DNS-01 challenges to get TLS/SSL certificates from Let's Encrypt, or any ACME-compatible certificate authority, without exposing your DNS credentials to every host that needs a certificate.

This repository hosts and documents the dns01proxy Caddy package. If you want to use dns01proxy as part of Caddy, then you're in the right place! Otherwise, the dns01proxy project distributes a standalone dns01proxy server as a precompiled binary, which is recommended for most users.

Installing

This dns01proxy Caddy package provides:

  • a dns01proxy command for running Caddy as a standalone dns01proxy server,
  • an http.handlers module that implements the dns01proxy API, and
  • a Caddy application module that implements the dns01proxy server.

To use this package, it needs to be compiled into your Caddy binary, along with the dns.provider.* modules for your DNS providers. You can build your custom Caddy binary by using xcaddy or by using Caddy's download page.

Using the command line

This package adds a dns01proxy command to Caddy, making it convenient to run a standalone dns01proxy server. Naturally, because dns01proxy runs on Caddy, it automatically obtains and renews its own TLS/SSL certificates using the configured DNS credentials.

To run dns01proxy, just provide a config file:

caddy dns01proxy --config dns01proxy.toml

The example below configures dns01proxy for running at https://dns01proxy.example.com with Cloudflare as a DNS provider.

hostnames = ["dns01proxy.example.com"]
listen = [":443"]

[dns.provider]
name = "cloudflare"
api_token = "{env.CF_API_TOKEN}"  # Reads from an environment variable.

# One for each user. Password is hashed using `caddy hash-password` with the
# bcrypt algorithm.
[[accounts]]
username = "AzureDiamond"
password = "$2a$14$N5bGBXf7zwAW9Ym7IQ/mxOHTGsvFNOTEAiN4/r1LnvfzYCpiWcHOa"
allow_domains = ["private.example.com"]
Full structure
# The server's hostnames. Used for obtaining TLS/SSL certificates.
hostnames = ["<hostname>"]

# The sockets on which to listen.
listen = ["<ip_addr:port>"]

# Configures the set of trusted proxies, for accurate logging of client IP
# addresses. This must be an `http.ip_sources` Caddy module. See Caddy's module
# documentation at https://caddyserver.com/docs/modules/
#
# Note that Caddy documents its modules' options in JSON. You'll need to
# configure the module in TOML. For example, to configure
# `http.ip_sources.static`:
#
#     [trusted_proxies]
#     source = "static"
#     ranges = ["10.0.0.1", "192.168.0.1"]
#
[trusted_proxies]
source = "<module_name>"
# •••  # Module-specific configuration goes here.

[dns]
# The TTL to use in DNS TXT records. Optional. Not usually needed.
ttl = "<ttl>"  # e.g., "2m"

# Custom DNS resolvers to prefer over system or built-in defaults. Set this to
# a public resolver if you are using split-horizon DNS.
resolvers = ["<resolver>"]

# The DNS provider for publishing DNS-01 responses. This must be a
# `dns.providers` Caddy module. See Caddy's module documentation at
# https://caddyserver.com/docs/modules/
#
# Note that Caddy documents its modules' options in JSON. You'll need to
# configure the module in TOML. For example, to configure
# `dns.providers.cloudflare`:
#
#     [dns.provider]
#     name = "cloudflare"
#     api_token = "{env.CF_API_TOKEN}"  # Reads from an environment variable.
#
[dns.provider]
name = "<provider_name>"
# •••  # Module-specific configuration goes here.


# Configures HTTP basic authentication and the domains for which each user can
# get TLS/SSL certificates.
[[accounts]]
user_id = "<userID>"
password = "<hashed_password>"  # To hash passwords, use `caddy hash-password`.

# These largely follow Smallstep's domain name rules:
#
#   https://smallstep.com/docs/step-ca/policies/#domain-names
#
# Due to a limitation in ACME and DNS-01, allowing a domain also allows
# wildcard certificates for that domain.
allow_domains = ["<domain>"]
deny_domains = ["<domain>"]

If you prefer JSON, you can use the same JSON structure as the configuration for the dns01proxy Caddy app.

Integrating into a Caddyfile

This package provides the following Caddyfile handler directive.

dns01proxy {
  # The DNS provider for publishing DNS-01 responses. Optional. If this is
  # omitted, then the global `acme_dns` and `dns` options are used as
  # fallbacks, but at least one of the three must be configured.
  dns <provider_name> [<params...>]

  # The TTL to use in DNS TXT records. Optional. Not usually needed.
  dns_ttl <ttl>

  # Custom DNS resolvers to prefer over system or built-in defaults. Set this
  # to a public resolver if you are using split-horizon DNS.
  resolvers <resolvers...>

  # Configures a single user. Can be given multiple times.
  user <userID> {
    # Configures HTTP basic authentication for the user. This is optional. If
    # this is omitted, then an authentication handler must come before this one
    # in the handler chain. To hash passwords, use `caddy hash-password` with
    # the bcrypt algorithm.
    password <hashed_password>

    # Determines the domains for which the user can get TLS/SSL certificates.
    # This largely follows Smallstep's domain name rules:
    #
    #   https://smallstep.com/docs/step-ca/policies/#domain-names
    #
    # Due to a limitation in ACME and DNS-01, allowing a domain also allows
    # wildcard certificates for that domain.
    allow_domains <domains...>
    deny_domains <domains...>
  }
}

Here is an example Caddyfile for running dns01proxy as a standalone server that automatically obtains and renews its own TLS/SSL certificate:

{
  acme_dns cloudflare {env.CF_API_TOKEN}
  cert_issuer acme {
    disable_http_challenge
    disable_tlsalpn_challenge
  }
}

dns01proxy.example.com {
  log
  @endpoints {
    path /present /cleanup
  }
  handle @endpoints {
    dns01proxy {
      user AzureDiamond {
        password $2a$14$N5bGBXf7zwAW9Ym7IQ/mxOHTGsvFNOTEAiN4/r1LnvfzYCpiWcHOa
        allow_domains private.example.com
      }
    }
  }
  respond 404
}

Configuring a dns01proxy handler in JSON

The package also provides a dns01proxy HTTP handler. This example configures a handler similar to the one in the Caddyfile example above.

{
  "dns": {
    "provider": {
      "name": "cloudflare",
      "api_token": "{env.CF_API_TOKEN}"
    }
  },
  "accounts": [
    {
      "user_id": "AzureDiamond",
      "password": "$2a$14$N5bGBXf7zwAW9Ym7IQ/mxOHTGsvFNOTEAiN4/r1LnvfzYCpiWcHOa",
      "allow_domains": ["private.example.com"],
    }
  ]
}
Full JSON structure
{
  "dns": {
    // The DNS provider for publishing DNS-01 responses.
    "provider": {
      // a dns.providers module
      "name": "<provider_name>",
      // ••• 
    },

    // The TTL to use in DNS TXT records. Optional. Not usually needed.
    "ttl": "<ttl>",  // e.g., "2m"

    // Custom DNS resolvers to prefer over system or built-in defaults. Set
    // this to a public resolver if you are using split-horizon DNS.
    "resolvers": ["<resolver>"]
  },

  // Configures HTTP basic authentication (optional) and the domains for which
  // each user can get TLS/SSL certificates.
  //
  // Passwords are optional here. If they are omitted, then an authentication
  // handler must come before this one in the handler chain. To hash passwords,
  // use `caddy hash-password` with the bcrypt algorithm.
  "accounts": [
    {
      "user_id": "<userID>",
      "password": "<hashed_password>",
      "allow_domains": ["<domain>"],
      "deny_domains": ["<domain>"]
    }
  ]
}

Configuring a dns01proxy app in JSON

Here is a sample configuration for the dns01proxy Caddy app, analogous to the TOML example for the dns01proxy command.

{
  "hostnames": ["dns01proxy.example.com"],
  "listen": [":443"],
  "dns": {
    "provider": {
      "name": "cloudflare",
      "api_token": "{env.CF_API_TOKEN}"
    }
  },
  "accounts": [
    {
      "username": "AzureDiamond",
      "password": "$2a$14$N5bGBXf7zwAW9Ym7IQ/mxOHTGsvFNOTEAiN4/r1LnvfzYCpiWcHOa",
      "allow_domains": ["private.example.com"]
    }
  ]
}
Full JSON structure
{
  // The server's hostnames. Used for obtaining TLS/SSL certificates.
  "hostnames": ["<hostname>"],

  // The sockets on which to listen.
  "listen": ["<ip_addr:port>"],

  // Configures the set of trusted proxies, for accurate logging of client IP
  // addresses.
  "trusted_proxies": {
    // an http.ip_sources module
    "source": "<module_name>",
    // •••
  },

  "dns": {
    // The DNS provider for publishing DNS-01 responses.
    "provider": {
      // A `dns.providers` module.
      "name": "<provider_name>",
      // ••• 
    },

    // The TTL to use in DNS TXT records. Optional. Not usually needed.
    "ttl": "<ttl>",  // e.g., "2m"

    // Custom DNS resolvers to prefer over system or built-in defaults. Set
    // this to a public resolver if you are using split-horizon DNS.
    "resolvers": ["<resolver>"]
  },

  // Configures HTTP basic authentication and the domains for which each user
  // can get TLS/SSL certificates.
  "accounts": [
    {
      "user_id": "<userID>",

      // To hash passwords, use `caddy hash-password`.
      "password": "<hashed_password>",

      // These largely follow Smallstep's domain name rules:
      //
      //   https://smallstep.com/docs/step-ca/policies/#domain-names
      //
      // Due to a limitation in ACME and DNS-01, allowing a domain also allows
      // wildcard certificates for that domain.
      "allow_domains": ["<domain>"],
      "deny_domains": ["<domain>"]
    }
  ]
}

Acknowledgements

dns01proxy is a reimplementation of acmeproxy, which is no longer being developed. Whereas acmeproxy was built on top of lego, dns01proxy uses libdns under the hood, which allows for better compatibility with acme.sh.

acmeproxy.pl is another reimplementation of acmeproxy, written in Perl.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Release

func Release() string

Returns the release string, including the application name, version, go-mod hash, OS, and architecture. For example, "dns01proxy v0.0.0 (h1:abcd1234=) linux/amd64".

func Version

func Version() string

Returns the version and go-mod hash. For example, "v0.0.0 (h1:abcd1234=)".

Types

type App

type App struct {
	Handler

	// The server's hostnames. Used for obtaining TLS certificates.
	Hostnames []string `json:"hostnames"`

	// The sockets on which to listen. For example, "127.0.0.1:9095" or ":443".
	Listen []string `json:"listen"`

	// Configures the set of trusted proxies, for accurate logging of client IP
	// addresses.
	TrustedProxiesRaw json.RawMessage `json:"trusted_proxies,omitempty" caddy:"namespace=http.ip_sources inline_key=source"`
	// contains filtered or unexported fields
}

A proxy server for ACME DNS-01 challenges. Designed to work with acme.sh's `acmeproxy`, lego's `httpreq`, and Caddy's `acmeproxy` DNS providers.

This is a Caddy application module.

func (App) CaddyModule

func (App) CaddyModule() caddy.ModuleInfo

func (*App) MakeTLSConfig

func (app *App) MakeTLSConfig() caddytls.TLS

Returns a TLS app configuration that uses the user-specified DNS provider for ACME challenges during TLS automation.

func (*App) Provision

func (app *App) Provision(ctx caddy.Context) error

func (*App) Start

func (app *App) Start() error

func (*App) Stop

func (app *App) Stop() error

type ClientPolicy

type ClientPolicy struct {
	// Identifies the client to which this policy applies.
	UserID string `json:"user_id"`

	// Determines the domains for which the user can get TLS certificates. This
	// largely follows Smallstep's domain name rules:
	// https://smallstep.com/docs/step-ca/policies/#domain-names
	//
	// Due to a limitation in ACME and DNS-01, allowing a domain also allows
	// wildcard certificates for that domain.
	AllowDomainsRaw []string `json:"allow_domains,omitempty"`
	DenyDomainsRaw  []string `json:"deny_domains,omitempty"`

	// The policy to be applied to the DNS domains for answering DNS-01
	// challenges.
	DomainPolicy x509policy.X509Policy `json:"-"`
}

The policy configuration for a user. Specifies the domains at which the user is allowed to answer DNS-01 challenges.

func (*ClientPolicy) Provision

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

type ClientRegistry

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

A registry of known users and their corresponding policy configuration.

func (*ClientRegistry) AuthorizeUserChallengeDomain

func (r *ClientRegistry) AuthorizeUserChallengeDomain(
	req *http.Request,
	challengeDomain string,
) (optionals.Optional[DenyReason], error)

Determines whether the current authenticated user is allowed to answer a DNS-01 challenge at the given challenge domain. Returns None on success. Otherwise, returns the reason for denial.

func (*ClientRegistry) Provision

func (c *ClientRegistry) Provision(
	ctx caddy.Context,
	accountsRaw []RawAccount,
) error

type ConfigFile

type ConfigFile = App

A dns01proxy configuration file is the same as the app configuration.

type DNSConfig

type DNSConfig struct {
	// The DNS provider for publishing DNS-01 responses.
	ProviderRaw json.RawMessage `json:"provider" caddy:"namespace=dns.providers inline_key=name"`

	Provider certmagic.DNSProvider `json:"-"`

	// The TTL to use in DNS TXT records when answering challenges. Optional. Not
	// usually needed.
	TTL *caddy.Duration `json:"ttl,omitempty"`

	// Custom DNS resolvers to prefer over system or built-in defaults. Set this
	// to a public resolver if you are using split-horizon DNS.
	Resolvers []string `json:"resolvers,omitempty"`
}

func (*DNSConfig) Provision

func (d *DNSConfig) Provision(ctx caddy.Context) error

type DenyReason

type DenyReason string
const (
	// Indicates that authorization failed because the user's ID was not found in
	// the client registry.
	DenyUnknownUser DenyReason = "unknown user"

	// Indicates that authorization failed because the user is not authorized to
	// answer challenges for the requested domain.
	DenyDomainNotAllowed DenyReason = "requested domain denied by policy"

	// Indicates that authorization failed because the user requested an invalid
	// domain.
	DenyInvalidDomain DenyReason = "requested domain not valid"

	// Indicates that an error occurred during authorization.
	DenyError DenyReason = "an error occurred"
)

type Handler

type Handler struct {
	DNS DNSConfig `json:"dns"`

	// Configures HTTP basic authentication and the domains for which each user
	// can get TLS certificates.
	//
	// (During provisioning, this is used to fill in [Authentication] and
	// [ClientRegistry].)
	AccountsRaw []RawAccount `json:"accounts"`

	// Specifies how clients should be authenticated. If absent, then clients must
	// be authenticated by an `http.handlers.authentication` instance earlier in
	// the handler chain. Derived from [AccountsRaw].
	//
	// XXX This should be an Optional[*caddyauth.Authentication], but Caddy's
	// documentation generator doesn't work with generics.
	Authentication *caddyauth.Authentication `json:"-"`

	// Identifies the domains at which each client is allowed to answer DNS-01
	// challenges. Derived from [AccountsRaw].
	ClientRegistry ClientRegistry `json:"-"`
	// contains filtered or unexported fields
}

Implements an API for proxying ACME DNS-01 challenges.

This is a Caddy `http.handlers` module.

func (Handler) CaddyModule

func (Handler) CaddyModule() caddy.ModuleInfo

func (*Handler) Provision

func (h *Handler) Provision(ctx caddy.Context) error

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(
	w http.ResponseWriter,
	req *http.Request,
	nextHandler caddyhttp.Handler,
) error

func (*Handler) UnmarshalCaddyfile

func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error

Parses a dns01proxy directive into a Handler instance.

Syntax:

dns01proxy {
	dns <provider_name> [<params...>]
	dns_ttl <ttl>
	resolvers <resolvers...>
	user <userID> {
		password <hashed_password>
		allow_domains <domains...>
		deny_domains <domains...>
	}
}

type RawAccount

type RawAccount struct {
	ClientPolicy

	// The user's password, hashed using `caddy hash-password`. Optional. If
	// omitted, then clients must be authenticated by an
	// `http.handlers.authentication` instance earlier in the handler chain.
	Password *string `json:"password,omitempty"`
}

type RequestBody

type RequestBody struct {
	// The challenge domain at which the DNS-01 response should be written.
	ChallengeFQDN string `json:"fqdn"`

	// The value of the DNS-01 response.
	Value string `json:"value"`
}

See https://github.com/libdns/acmeproxy/blob/f8e0a6620dddf349d1c9ba58b755aa7a25e5613f/provider.go#L20-L23.

func (RequestBody) IsValid

func (r RequestBody) IsValid() bool

type ResponseBody

type ResponseBody = RequestBody

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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