cmd/derper: add opt-in support for LetsEncrypt IP address certificates
LetsEncrypt made certificates for bare IP addresses generally available in January 2026. They require the short-lived ACME certificate profile and are valid for about six days. Add a new --acme-ip-certs flag. When set (with the default --certmode=letsencrypt), connections that arrive by IP address (no TLS SNI, or an IP address SNI matching the connection's destination address) get a LetsEncrypt cert for that IP, obtained on demand using the "shortlived" profile and the HTTP-01 challenge served on derper's plaintext HTTP port. Because the certificate is requested for whatever address the connection actually arrived on, it works for both IPv4 and IPv6 with no per-address configuration, and a client can never make us request a certificate for an address that isn't ours. Connections with a DNS name in the SNI keep using the regular autocert manager for --hostname. autocert can't do any of this itself, as it neither orders IP address identifiers nor serves connections without SNI, so this adds a small dedicated cert manager using tailscale.com/tempfork/acme instead. Clients can then connect to https://<IP> without the DERPMap CertName pinning that self-signed certs from --certmode=manual require. Updates tailscale/corp#45167 Updates #11776 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: I8e2d5b0a7c4f9e1b3d6a8c2f5e0b9d4a7c1f3e6d
This commit is contained in:
committed by
Brad Fitzpatrick
parent
fdd81c68b3
commit
5384d23690
+18
-1
@@ -44,12 +44,26 @@ type certProvider interface {
|
||||
HTTPHandler(fallback http.Handler) http.Handler
|
||||
}
|
||||
|
||||
func certProviderByCertMode(mode, dir, hostname, eabKID, eabKey, email string) (certProvider, error) {
|
||||
func certProviderByCertMode(mode, dir, hostname string, ipCerts bool, eabKID, eabKey, email string) (certProvider, error) {
|
||||
if dir == "" {
|
||||
return nil, errors.New("missing required --certdir flag")
|
||||
}
|
||||
if ipCerts && mode != "letsencrypt" {
|
||||
return nil, errors.New("--acme-ip-certs requires --certmode=letsencrypt")
|
||||
}
|
||||
switch mode {
|
||||
case "letsencrypt", "gcp":
|
||||
if net.ParseIP(hostname) != nil {
|
||||
if mode == "gcp" {
|
||||
return nil, errors.New("--certmode=gcp requires --hostname to be a DNS name, not an IP address")
|
||||
}
|
||||
if !ipCerts {
|
||||
return nil, errors.New("--hostname is an IP address; use --certmode=manual for a self-signed cert, or set --acme-ip-certs to get LetsEncrypt IP address certs")
|
||||
}
|
||||
// IP-only server: certs are issued on demand per
|
||||
// connection, so there is no hostname cert provider.
|
||||
return newIPCertManager(dir, email, "", nil)
|
||||
}
|
||||
certManager := &autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(hostname),
|
||||
@@ -82,6 +96,9 @@ func certProviderByCertMode(mode, dir, hostname, eabKID, eabKey, email string) (
|
||||
} else if hostname == "derp.tailscale.com" {
|
||||
certManager.Email = "security@tailscale.com"
|
||||
}
|
||||
if ipCerts {
|
||||
return newIPCertManager(dir, email, "", certManager)
|
||||
}
|
||||
return certManager, nil
|
||||
case "manual":
|
||||
return NewManualCertManager(dir, hostname)
|
||||
|
||||
Reference in New Issue
Block a user