feat(tsconnect): add getCert, listenTLS, setFunnel + fix TLS cert for WASM

Enable ACME TLS certificates on js/wasm by dropping the !js build tag from
cert.go and routing storage through the state store. Add getCert, listenTLS,
and setFunnel WASM bindings with a combinedTLSListener that merges Funnel
ingress and direct tailnet connections. Notify the control plane immediately
after serve config changes to accelerate Funnel DNS provisioning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 17:59:21 +00:00
co-authored by Claude
parent 34a0d35dd5
commit 1d4097bc07
5 changed files with 244 additions and 8 deletions
+19
View File
@@ -122,6 +122,25 @@ type extension struct {
// that periodically pokes [LocalBackend.GetCertPEM] so renewals
// happen on idle nodes. Non-nil while the loop is running.
certRefreshCancel context.CancelFunc
// httpClient, if non-nil, is used for all ACME HTTP requests instead of
// http.DefaultClient. Set via [SetHTTPClient] before first cert use.
httpClient *http.Client
}
// SetHTTPClient sets a custom HTTP client for ACME certificate operations on
// b. On js/wasm this can route requests through the Tailscale network stack to
// bypass browser CORS if Let's Encrypt endpoints fail preflight. A nil value
// (the default) uses http.DefaultClient.
func SetHTTPClient(b *ipnlocal.LocalBackend, c *http.Client) error {
e, err := extFor(b)
if err != nil {
return err
}
e.mu.Lock()
defer e.mu.Unlock()
e.httpClient = c
return nil
}
// lockDomain returns the mutex for domain, creating it on first use.
+8
View File
@@ -82,6 +82,10 @@ func certDir(b *ipnlocal.LocalBackend) (string, error) {
func (e *extension) getCertStore(b *ipnlocal.LocalBackend) (certStore, error) {
st := b.Sys().StateStore.Get()
if runtime.GOOS == "js" {
// No filesystem to hold a cert directory; the state store is all we have.
return certStateStore{StateStore: st}, nil
}
switch st.(type) {
case *store.FileStore:
case *mem.Store:
@@ -396,10 +400,14 @@ func (e *extension) acmeClient(cs certStore) (*xacme.Client, error) {
// Note: if we add support for additional ACME providers (other than
// LetsEncrypt), we should make sure that they support ARI extension (see
// shouldStartDomainRenewalARI).
e.mu.Lock()
httpClient := e.httpClient
e.mu.Unlock()
return &xacme.Client{
Key: key,
UserAgent: "tailscaled/" + version.Long(),
DirectoryURL: envknob.String("TS_DEBUG_ACME_DIRECTORY_URL"),
HTTPClient: httpClient,
}, nil
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !js && !ts_omit_acme
//go:build !ts_omit_acme
package condregister