Tailscale serve implementation#3300
Open
B00M3000 wants to merge 7 commits into
Open
Conversation
Adds ACMEChallengeServer, a minimal authoritative DNS server for the configured base_domain that answers the _acme-challenge TXT lookups Tailscale clients use to complete ACME DNS-01 challenges (tailscale cert / tailscale serve --https). It serves only the zone apex SOA/NS and the published challenge TXT records; node A/AAAA records stay internal to the tailnet via MagicDNS. A new dns.https_certs config block (enabled, listen_addr, nameserver) gates the feature and is validated to require MagicDNS and a base_domain. https://claude.ai/code/session_01LYishR5SuV14gARRS3LjZU
mapper advertises the node's FQDN in DNSConfig.CertDomains when dns.https_certs is enabled, which is the signal the client needs to allow tailscale cert / serve --https. noise implements /machine/set-dns: a node may publish only the ACME challenge record for its own FQDN, which is then served by the authoritative DNS server. getAndValidateNode now takes a node key so both the map and set-dns handlers can share it. app starts and stops the ACMEChallengeServer with the server lifecycle. https://claude.ai/code/session_01LYishR5SuV14gARRS3LjZU
Adds a reference page and config example for per-node HTTPS certificates, marks Serve as supported in the features matrix, and records the change in the changelog. Adds an integration test asserting CertDomains is advertised to a connected client. The full ACME issuance flow is not CI-testable because it requires a publicly delegated base_domain reachable by Let's Encrypt; the DNS server and set-dns handler are covered by unit tests instead. https://claude.ai/code/session_01LYishR5SuV14gARRS3LjZU
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Tailscale Serve Support
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for per-node HTTPS/TLS certificate provisioning to enable tailscale serve --https / tailscale cert under Headscale by advertising DNSConfig.CertDomains, implementing the /machine/set-dns endpoint, and running an embedded authoritative DNS server for ACME DNS-01 challenges.
Changes:
- Introduces
dns.https_certsconfiguration and validation (requires MagicDNS +base_domain). - Implements ACME DNS-01 challenge handling:
/machine/set-dns+ embedded authoritative DNS server. - Adds docs, changelog entry, and unit/integration tests for CertDomains and DNS challenge server behavior.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| mkdocs.yml | Adds Serve HTTPS cert docs to site navigation. |
| integration/serve_test.go | Integration coverage for advertising DNSConfig.CertDomains. |
| hscontrol/types/config.go | Adds dns.https_certs config + validation rules. |
| hscontrol/noise.go | Implements /machine/set-dns and refactors node validation helper. |
| hscontrol/mapper/mapper.go | Advertises node FQDN via DNSConfig.CertDomains when enabled. |
| hscontrol/mapper/mapper_test.go | Unit tests for CertDomains generation behavior. |
| hscontrol/dns/acme.go | New embedded authoritative DNS server for ACME DNS-01 TXT + SOA/NS. |
| hscontrol/dns/acme_test.go | Unit tests for ACME DNS server correctness (TXT, SOA/NS, expiry, etc.). |
| hscontrol/app.go | Starts/stops the embedded authoritative DNS server when enabled. |
| go.mod | Adds dependency on github.com/miekg/dns. |
| docs/ref/serve.md | New documentation for HTTPS serve/cert setup and delegation. |
| docs/about/features.md | Updates feature matrix to mark Serve as supported and link docs. |
| config-example.yaml | Documents new dns.https_certs configuration stanza. |
| CHANGELOG.md | Documents new HTTPS Serve/cert support and requirements. |
| .github/workflows/test-integration.yaml | Adds new integration test to CI job list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+681
to
+706
| // Start the authoritative DNS server that answers ACME DNS-01 | ||
| // challenges for per-node HTTPS certificates (tailscale serve / | ||
| // tailscale cert). base_domain must be publicly delegated to this | ||
| // server for the challenges to validate. | ||
| if h.cfg.DNSConfig.HTTPSCerts.Enabled { | ||
| listenAddr := h.cfg.DNSConfig.HTTPSCerts.ListenAddr | ||
| if listenAddr == "" { | ||
| listenAddr = ":53" | ||
| } | ||
|
|
||
| h.acmeDNS = dns.NewACMEChallengeServer( | ||
| h.cfg.BaseDomain, | ||
| h.cfg.DNSConfig.HTTPSCerts.Nameserver, | ||
| listenAddr, | ||
| ) | ||
| if err := h.acmeDNS.Start(); err != nil { | ||
| return fmt.Errorf("starting ACME challenge DNS server: %w", err) | ||
| } | ||
| defer h.acmeDNS.Close() | ||
|
|
||
| log.Info(). | ||
| Str("listen_addr", listenAddr). | ||
| Str("zone", h.cfg.BaseDomain). | ||
| Msg("authoritative DNS server for per-node HTTPS certificates started") | ||
| } | ||
| } |
Comment on lines
+685
to
+695
| if h.cfg.DNSConfig.HTTPSCerts.Enabled { | ||
| listenAddr := h.cfg.DNSConfig.HTTPSCerts.ListenAddr | ||
| if listenAddr == "" { | ||
| listenAddr = ":53" | ||
| } | ||
|
|
||
| h.acmeDNS = dns.NewACMEChallengeServer( | ||
| h.cfg.BaseDomain, | ||
| h.cfg.DNSConfig.HTTPSCerts.Nameserver, | ||
| listenAddr, | ||
| ) |
Comment on lines
+831
to
+835
| var dnsReq tailcfg.SetDNSRequest | ||
| if err := json.NewDecoder(req.Body).Decode(&dnsReq); err != nil { | ||
| httpError(writer, NewHTTPError(http.StatusBadRequest, "invalid SetDNSRequest body", err)) | ||
| return | ||
| } |
Comment on lines
+170
to
+174
| if cfg.DNSConfig.HTTPSCerts.Enabled { | ||
| if fqdn, err := node.GetFQDN(cfg.BaseDomain); err == nil { | ||
| dnsConfig.CertDomains = []string{strings.TrimSuffix(fqdn, ".")} | ||
| } | ||
| } |
Comment on lines
+94
to
+99
| func (s *ACMEChallengeServer) SetTXT(name, value string) { | ||
| const maxTXTValuesPerName = 8 | ||
|
|
||
| name = dns.CanonicalName(name) | ||
| expires := time.Now().Add(acmeRecordTTL) | ||
| now := time.Now() |
Comment on lines
+43
to
+46
| - [ ] [Funnel](https://tailscale.com/docs/features/tailscale-funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040)) — requires Tailscale's public ingress relays, which Headscale cannot provide | ||
| - [x] [Serve](https://tailscale.com/docs/features/tailscale-serve) ([#1921](https://github.com/juanfont/headscale/issues/1921)) | ||
| - [x] HTTP serve (MagicDNS only) | ||
| - [x] [HTTPS serve / per-node TLS certificates](../ref/serve.md) (requires [`dns.https_certs`](../ref/serve.md) and a publicly delegated `base_domain`) |
Author
The vendor hash was out of date with the latest go.mod changes from the ACME DNS-01 server implementation. Re-computed the SRI hash based on the current vendor directory state. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I recognize that this is majority vibe coded and I am not sure if it works. However, I thought that I should open a pull request as a draft for others to look at and work on. I hope that this is helpful in pushing the implementation of HTTPS on serve.