Writing new DNS provider modules for Caddy

Caddy 2 uses a new set of interfaces for its DNS provider APIs. While there is a shim to use all the existing old ones for now, the new APIs offer better performance, leaner builds, and more capabilities. We want all providers to be ported over to the new APIs as soon as possible.

Since I do not have accounts with all DNS providers in the world, it’s up to the community to migrate to the new APIs and to maintain their implementations. This post will show you how! :mortar_board:

For what it’s worth, I was able to implement the Cloudflare package using only the Go standard library in just about a day. It shouldn’t take you too long! :smiley:

libdns

The new interfaces/APIs are called libdns. You should at least skim over its README.

Writing a new dns.providers module

Writing a new DNS provider module for Caddy has two main steps:

  1. Implement the DNS provider using the libdns APIs. There are instructions and tips in that repository’s wiki. This is the code that actually manipulates DNS records with your DNS provider, but this code does not know about (import) Caddy or any ACME libraries whatsoever.

  2. Create a caddy-dns package that turns the libdns package into a Caddy module. This consists of a very simple Go module that has 20 lines (but for full support including Caddyfile, it’s a bit longer). For example, the cloudflare package can be used as a template:

package cloudflare

import (
	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
	"github.com/libdns/cloudflare"
)

// Provider wraps the provider implementation as a Caddy module.
type Provider struct{ *cloudflare.Provider }

func init() {
	caddy.RegisterModule(Provider{})
}

// CaddyModule returns the Caddy module information.
func (Provider) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "dns.providers.cloudflare",
		New: func() caddy.Module { return &Provider{new(cloudflare.Provider)} },
	}
}

// UnmarshalCaddyfile sets up the DNS provider from Caddyfile tokens. Syntax:
//
// cloudflare [<api_token>] {
//     api_token <api_token>
// }
//
func (p *Provider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
	repl := caddy.NewReplacer()
	for d.Next() {
		if d.NextArg() {
			p.Provider.APIToken = repl.ReplaceAll(d.Val(), "")
		}
		if d.NextArg() {
			return d.ArgErr()
		}
		for nesting := d.Nesting(); d.NextBlock(nesting); {
			switch d.Val() {
			case "api_token":
				if p.Provider.APIToken != "" {
					return d.Err("API token already set")
				}
				p.Provider.APIToken = repl.ReplaceAll(d.Val(), "")
				if d.NextArg() {
					return d.ArgErr()
				}
			default:
				return d.Errf("unrecognized subdirective '%s'", d.Val())
			}
		}
	}
	if p.Provider.APIToken == "" {
		return d.Err("missing API token")
	}
	return nil
}

// Interface guard
var _ caddyfile.Unmarshaler = (*Provider)(nil)

Simply tweak that template to work with your provider and then test it. Once you confirm it works with Caddy, let’s get it added to the caddy-dns organization so others can use it.

The libdns APIs are ideal for Caddy because they result in better performance, significantly less bloat, more capacity for growth, and more flexibility (they can be used for more than just solving ACME challenges; for example, a dynamic DNS app can use them to update A/AAAA records).

Thanks for contributing to the project!

2 Likes

What if the DNS provider is not a full-fledged DNS provider like Route53? We’re using acme-dns and the API only allows one thing: to add/update the _acme-challenge record. IMO it doesn’t make much sense to implement a libdns provider for this, a caddy-dns package would be enough, right?

Looking through the code I saw that libdns.RecordAppender and libdns.RecordDeleter are the only interfaces required to implement this.

Those are the only two methods used by the DNS challenge solver currently; so I would recommend to just implement those methods the best you can, and return an error if your implementation does not support something the caller is trying to do.

So, it looks like I have to know how to write something in Go to create a libdns provider. Is that true?

Yes, it has to be written in Go. :slight_smile:

How hard would it be for someone who’s never used Go?

Probably not too hard. Go is a relatively easy language to learn. Start here! https://tour.golang.org/welcome/1 - the Go tour is the quickest way to pick up the basics of Go. Then you can use existing provider implementations as a template.

1 Like

Thanks :slightly_smiling_face:

My code:https://github.com/lscgzwd/caddy-dnspod
I want to push to : caddy-dns/dnspod