ipn/ipnlocal, feature/acme: move most remaining cert code into feature/acme
f5eac39ea ("feature/acme, ipn/ipnlocal: start moving ACME/cert state
into an extension") started to move the cert code into feature/acme
but was meant as a baby step.
This goes further, moving almost everything, leaving only some hooks
in ipnlocal.
When we later move "serve" support out to feature/serve, this will
look a bit different in that the hooks currently in ipnlocal will move
to feature/serve (cert support already depends on serve).
As part of this, cert-related tests move to feaure/acme too, which
means some test infra from ipnlocal now moves to shared ipnlocaltest.
(it's not big at the moment, but I imagine it growing)
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9ea89aa9754f12d54b81751b6bd830f2664241ff
This commit is contained in:
committed by
Brad Fitzpatrick
parent
825b7c479f
commit
1c77079fd7
@@ -5,144 +5,19 @@ package ipnlocal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/eventbus/eventbustest"
|
||||
"tailscale.com/util/must"
|
||||
|
||||
gcmp "github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
func TestHandleC2NTLSCertStatus(t *testing.T) {
|
||||
b := &LocalBackend{
|
||||
store: &mem.Store{},
|
||||
varRoot: t.TempDir(),
|
||||
health: health.NewTracker(eventbustest.NewBus(t)),
|
||||
}
|
||||
certDir, err := b.certDir()
|
||||
if err != nil {
|
||||
t.Fatalf("certDir error: %v", err)
|
||||
}
|
||||
if _, err := b.getCertStore(); err != nil {
|
||||
t.Fatalf("getCertStore error: %v", err)
|
||||
}
|
||||
|
||||
testRoot, err := certTestFS.ReadFile("testdata/rootCA.pem")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
if !roots.AppendCertsFromPEM(testRoot) {
|
||||
t.Fatal("Unable to add test CA to the cert pool")
|
||||
}
|
||||
testX509Roots = roots
|
||||
defer func() { testX509Roots = nil }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
domain string
|
||||
copyFile bool // copy testdata/example.com.pem to the certDir
|
||||
wantStatus int // 0 means 200
|
||||
wantError string // wanted non-JSON non-200 error
|
||||
now time.Time
|
||||
want *tailcfg.C2NTLSCertInfo
|
||||
}{
|
||||
{
|
||||
name: "no-domain",
|
||||
wantStatus: 400,
|
||||
wantError: "no 'domain'\n",
|
||||
},
|
||||
{
|
||||
name: "missing",
|
||||
domain: "example.com",
|
||||
want: &tailcfg.C2NTLSCertInfo{
|
||||
Error: "no certificate",
|
||||
Missing: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
domain: "example.com",
|
||||
now: time.Date(2023, time.February, 20, 0, 0, 0, 0, time.UTC),
|
||||
copyFile: true,
|
||||
want: &tailcfg.C2NTLSCertInfo{
|
||||
Valid: true,
|
||||
NotBefore: "2023-02-07T20:34:18Z",
|
||||
NotAfter: "2025-05-07T19:34:18Z",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expired",
|
||||
domain: "example.com",
|
||||
now: time.Date(2030, time.February, 20, 0, 0, 0, 0, time.UTC),
|
||||
copyFile: true,
|
||||
want: &tailcfg.C2NTLSCertInfo{
|
||||
Error: "cert expired",
|
||||
Expired: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.RemoveAll(certDir) // reset per test
|
||||
if tt.copyFile {
|
||||
os.MkdirAll(certDir, 0755)
|
||||
if err := os.WriteFile(filepath.Join(certDir, "example.com.crt"),
|
||||
must.Get(os.ReadFile("testdata/example.com.pem")), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(certDir, "example.com.key"),
|
||||
must.Get(os.ReadFile("testdata/example.com-key.pem")), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.clock = tstest.NewClock(tstest.ClockOpts{
|
||||
Start: tt.now,
|
||||
})
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handleC2NTLSCertStatus(b, rec, httptest.NewRequest("GET", "/tls-cert-status?domain="+url.QueryEscape(tt.domain), nil))
|
||||
res := rec.Result()
|
||||
wantStatus := cmp.Or(tt.wantStatus, 200)
|
||||
if res.StatusCode != wantStatus {
|
||||
t.Fatalf("status code = %v; want %v. Body: %s", res.Status, wantStatus, rec.Body.Bytes())
|
||||
}
|
||||
if wantStatus == 200 {
|
||||
var got tailcfg.C2NTLSCertInfo
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
||||
t.Fatalf("bad JSON: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(&got, tt.want) {
|
||||
t.Errorf("got %v; want %v", logger.AsJSON(got), logger.AsJSON(tt.want))
|
||||
}
|
||||
} else if tt.wantError != "" {
|
||||
if got := rec.Body.String(); got != tt.wantError {
|
||||
t.Errorf("body = %q; want %q", got, tt.wantError)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHandleC2NDebugNetmap(t *testing.T) {
|
||||
nm := &netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
|
||||
+102
-1319
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build js || ts_omit_acme
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterC2N("GET /tls-cert-status", handleC2NTLSCertStatusDisabled)
|
||||
}
|
||||
|
||||
var errNoCerts = errors.New("cert support not compiled in this build")
|
||||
|
||||
type TLSCertKeyPair struct {
|
||||
CertPEM, KeyPEM []byte
|
||||
}
|
||||
|
||||
func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertKeyPair, error) {
|
||||
return nil, errNoCerts
|
||||
}
|
||||
|
||||
func serveTLSNextProtos() []string {
|
||||
return []string{"h2", "http/1.1"}
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getACMETLSALPNCert(hi *tls.ClientHelloInfo) (*tls.Certificate, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getACMETLSALPNProto(hi *tls.ClientHelloInfo) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
var errCertExpired = errors.New("cert expired")
|
||||
|
||||
type certStore interface{}
|
||||
|
||||
func getCertPEMCached(cs certStore, domain string, now time.Time) (p *TLSCertKeyPair, err error) {
|
||||
return nil, errNoCerts
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getCertStore() (certStore, error) {
|
||||
return nil, errNoCerts
|
||||
}
|
||||
|
||||
func handleC2NTLSCertStatusDisabled(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
io.WriteString(w, `{"Missing":true}`) // a minimal tailcfg.C2NTLSCertInfo
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
// CertState holds the per-[LocalBackend] state owned by the
|
||||
// feature/acme extension. The struct lives in this package so that
|
||||
// cert.go can access its fields directly without method indirection,
|
||||
// while the extension in feature/acme remains the canonical owner.
|
||||
//
|
||||
// In builds without ACME support (js or ts_omit_acme), no extension
|
||||
// constructs a CertState, and [LocalBackend.certState] returns nil.
|
||||
//
|
||||
// CertState is safe for concurrent use; the individual fields document
|
||||
// their own synchronization.
|
||||
//
|
||||
// TODO(bradfitz): continue moving all this cert code into feature/acme's package.
|
||||
// This type being here was a compromise to keep the PR small during the move.
|
||||
type CertState struct {
|
||||
// acmeMu serializes ACME operations so concurrent requests for
|
||||
// certs don't slam ACME. The first goroutine through populates the
|
||||
// on-disk cache and the rest reuse it.
|
||||
acmeMu syncs.Mutex
|
||||
|
||||
// renewMu guards renewCertAt.
|
||||
// Lock order: acmeMu before renewMu.
|
||||
renewMu syncs.Mutex
|
||||
renewCertAt map[string]time.Time // lazily initialized under renewMu
|
||||
|
||||
// pendingACMETLSALPNCerts maps SNI names to short-lived ACME
|
||||
// tls-alpn-01 challenge certificates while an ACME order is
|
||||
// waiting for validation. Entries are deleted by the cleanup
|
||||
// function returned from storeACMETLSALPNCert after the challenge
|
||||
// validation path finishes, whether it succeeds or fails.
|
||||
pendingACMETLSALPNCerts syncs.Map[string, *tls.Certificate] // "foo.bar.com" => challenge cert
|
||||
|
||||
// pendingCertDomains tracks the set of domains for which an ACME
|
||||
// issuance is currently in flight with no usable cached cert. It
|
||||
// backs the tls-cert-pending health Warnable.
|
||||
// Guarded by pendingCertDomainsMu.
|
||||
pendingCertDomainsMu sync.Mutex
|
||||
pendingCertDomains set.Set[string]
|
||||
|
||||
// getCertForTest is used to retrieve TLS certificates in tests.
|
||||
// See [forTest.ConfigureCerts]. Guarded by the containing
|
||||
// [LocalBackend]'s mutex (b.mu).
|
||||
getCertForTest func(hostname string) (*TLSCertKeyPair, error)
|
||||
|
||||
// certRefreshCancel cancels the background TLS cert refresh loop
|
||||
// that periodically pokes [LocalBackend.GetCertPEM] so renewals
|
||||
// happen on idle nodes. Guarded by the containing [LocalBackend]'s
|
||||
// mutex (b.mu). Non-nil while the loop is running.
|
||||
certRefreshCancel context.CancelFunc
|
||||
}
|
||||
|
||||
// hookCertState is set by the feature/acme extension at init time
|
||||
// to a function that returns the [CertState] for backend b, or nil
|
||||
// if the cert extension is not registered (e.g. in builds with
|
||||
// ts_omit_acme or js).
|
||||
var hookCertState feature.Hook[func(*LocalBackend) *CertState]
|
||||
|
||||
// HookCertState exposes [hookCertState] to the feature/acme package
|
||||
// for installation. It must be set exactly once at init time.
|
||||
var HookCertState = &hookCertState
|
||||
|
||||
// certState returns the cert state for b, or nil if the cert
|
||||
// extension is not registered.
|
||||
func (b *LocalBackend) certState() *CertState {
|
||||
if f, ok := hookCertState.GetOk(); ok {
|
||||
return f(b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !js && !ts_omit_acme
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import "sync"
|
||||
|
||||
// In tests we can't import feature/acme (it would import this package
|
||||
// and form a cycle), so the real cert extension is never registered.
|
||||
// Install a default [hookCertState] provider here that lazily creates
|
||||
// a [CertState] per [LocalBackend].
|
||||
//
|
||||
// Tests that want different behavior can use
|
||||
// [feature.Hook.SetForTest] to override this hook for the duration
|
||||
// of the test.
|
||||
func init() {
|
||||
if hookCertState.IsSet() {
|
||||
return
|
||||
}
|
||||
var (
|
||||
mu sync.Mutex
|
||||
states = map[*LocalBackend]*CertState{}
|
||||
)
|
||||
hookCertState.Set(func(b *LocalBackend) *CertState {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if s, ok := states[b]; ok {
|
||||
return s
|
||||
}
|
||||
s := new(CertState)
|
||||
states[b] = s
|
||||
return s
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+37
-6
@@ -4,12 +4,14 @@
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"tailscale.com/control/controlclient"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnauth"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/util/testenv"
|
||||
@@ -99,16 +101,45 @@ func (f forTest) CurrentUser() (ipn.WindowsUserID, ipnauth.Actor) {
|
||||
// ConfigureCerts sets a certificate retrieval function to be used by this
|
||||
// local backend, skipping the usual ACME certificate registration.
|
||||
func (f forTest) ConfigureCerts(getCert func(hostname string) (*TLSCertKeyPair, error)) {
|
||||
b := f.b
|
||||
cs := b.certState()
|
||||
if cs == nil {
|
||||
hook, ok := HookConfigureCertsForTest.GetOk()
|
||||
if !ok {
|
||||
panic("forTest.ConfigureCerts called without cert extension registered")
|
||||
}
|
||||
b.mu.Lock()
|
||||
cs.getCertForTest = getCert
|
||||
b.mu.Unlock()
|
||||
hook(f.b, getCert)
|
||||
}
|
||||
|
||||
// GetACMETLSALPNCert returns the short-lived ACME tls-alpn-01 challenge
|
||||
// certificate for hi, if any.
|
||||
func (f forTest) GetACMETLSALPNCert(hi *tls.ClientHelloInfo) (*tls.Certificate, bool) {
|
||||
return f.b.getACMETLSALPNCert(hi)
|
||||
}
|
||||
|
||||
// SetServeConfig installs sc as the backend's current
|
||||
// [ipn.ServeConfig] without going through the validation in
|
||||
// [LocalBackend.SetServeConfig]. It is intended for tests that need a
|
||||
// specific serve config without first standing up the prerequisites
|
||||
// (netmap, prefs, etc.).
|
||||
func (f forTest) SetServeConfig(sc ipn.ServeConfigView) {
|
||||
b := f.b
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.serveConfig = sc
|
||||
}
|
||||
|
||||
// SetNetMap installs nm as the backend's current netmap without going
|
||||
// through control-plane plumbing. It is intended for tests that need a
|
||||
// specific netmap (e.g. CertDomains, capabilities).
|
||||
func (f forTest) SetNetMap(nm *netmap.NetworkMap) {
|
||||
b := f.b
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.currentNode().SetNetMap(nm)
|
||||
}
|
||||
|
||||
// SetClock replaces b's clock with c, for tests that need
|
||||
// time-dependent behavior to be deterministic.
|
||||
func (f forTest) SetClock(c tstime.Clock) { f.b.clock = c }
|
||||
|
||||
// SetPrefs replaces the current prefs with newp.
|
||||
func (f forTest) SetPrefs(newp *ipn.Prefs) {
|
||||
if newp == nil {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package ipnlocaltest provides test helpers for constructing a
|
||||
// [*ipnlocal.LocalBackend] from external test packages that cannot
|
||||
// access ipnlocal's internal test helpers.
|
||||
package ipnlocaltest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
"tailscale.com/util/eventbus/eventbustest"
|
||||
"tailscale.com/util/testenv"
|
||||
"tailscale.com/wgengine"
|
||||
)
|
||||
|
||||
// NewBackend creates a new [*ipnlocal.LocalBackend] suitable for tests,
|
||||
// using an in-memory state store, a fake userspace engine, and a static
|
||||
// network monitor. Shutdown is registered as a t.Cleanup.
|
||||
func NewBackend(t testing.TB) *ipnlocal.LocalBackend {
|
||||
testenv.AssertInTest()
|
||||
bus := eventbustest.NewBus(t)
|
||||
return NewBackendWithSys(t, tsd.NewSystemWithBus(bus))
|
||||
}
|
||||
|
||||
// NewBackendWithSys creates a new [*ipnlocal.LocalBackend] with the
|
||||
// given [*tsd.System]. Missing components in sys (state store, engine,
|
||||
// dialer) are filled in with test fakes.
|
||||
func NewBackendWithSys(t testing.TB, sys *tsd.System) *ipnlocal.LocalBackend {
|
||||
testenv.AssertInTest()
|
||||
var logf logger.Logf = logger.Discard
|
||||
if _, ok := sys.StateStore.GetOK(); !ok {
|
||||
sys.Set(new(mem.Store))
|
||||
t.Log("Added memory store for testing")
|
||||
}
|
||||
if _, ok := sys.Engine.GetOK(); !ok {
|
||||
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set, sys.HealthTracker.Get(), sys.UserMetricsRegistry(), sys.Bus.Get())
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeUserspaceEngine: %v", err)
|
||||
}
|
||||
t.Cleanup(eng.Close)
|
||||
sys.Set(eng)
|
||||
t.Log("Added fake userspace engine for testing")
|
||||
}
|
||||
if _, ok := sys.Dialer.GetOK(); !ok {
|
||||
dialer := tsdial.NewDialer(netmon.NewStatic())
|
||||
dialer.SetBus(sys.Bus.Get())
|
||||
sys.Set(dialer)
|
||||
t.Log("Added static dialer for testing")
|
||||
}
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("NewLocalBackend: %v", err)
|
||||
}
|
||||
t.Cleanup(lb.Shutdown)
|
||||
return lb
|
||||
}
|
||||
+1
-74
@@ -1221,11 +1221,6 @@ var (
|
||||
hookCheckCaptivePortalLoop feature.Hook[func(*LocalBackend, context.Context)]
|
||||
)
|
||||
|
||||
// hookCertRefreshLoop is set by the ACME-enabled cert code to a function
|
||||
// that periodically refreshes TLS certs for Serve/Funnel-configured
|
||||
// domains so renewals proceed even on otherwise-idle nodes.
|
||||
var hookCertRefreshLoop feature.Hook[func(*LocalBackend, context.Context)]
|
||||
|
||||
func (b *LocalBackend) onHealthChange(change health.Change) {
|
||||
if !buildfeatures.HasHealth {
|
||||
return
|
||||
@@ -1327,12 +1322,7 @@ func (b *LocalBackend) Shutdown() {
|
||||
b.captiveCancel()
|
||||
}
|
||||
|
||||
if buildfeatures.HasACME {
|
||||
if state := b.certState(); state != nil && state.certRefreshCancel != nil {
|
||||
state.certRefreshCancel()
|
||||
state.certRefreshCancel = nil
|
||||
}
|
||||
}
|
||||
b.shutdownCertRefreshLoopLocked()
|
||||
|
||||
b.stopReconnectTimerLocked()
|
||||
|
||||
@@ -7478,69 +7468,6 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
|
||||
// The LocalBackend's mutex is held while calling.
|
||||
var hookMaybeMutateHostinfoLocked feature.Hooks[func(*LocalBackend, *tailcfg.Hostinfo, ipn.PrefsView) bool]
|
||||
|
||||
// updateCertRefreshLoopLocked starts or stops the background TLS cert
|
||||
// refresh loop based on whether we currently have any HTTPS-serving
|
||||
// hostname whose cert we should keep fresh. The loop runs only while:
|
||||
//
|
||||
// - ACME support is compiled in,
|
||||
// - the node is in [ipn.Running], and
|
||||
// - the current [ipn.ServeConfig] has at least one HTTPS Web entry.
|
||||
//
|
||||
// We deliberately don't keep an idle timer around on hosts that have no
|
||||
// certs to maintain (e.g. mobile devices that never run Serve), so this
|
||||
// must be called whenever any of those inputs change: state transitions
|
||||
// and ServeConfig reloads.
|
||||
//
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) updateCertRefreshLoopLocked() {
|
||||
if !buildfeatures.HasACME {
|
||||
return
|
||||
}
|
||||
state := b.certState()
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
shouldRun := hookCertRefreshLoop.IsSet() &&
|
||||
b.state == ipn.Running &&
|
||||
serveConfigUsesACMECerts(b.serveConfig)
|
||||
|
||||
switch {
|
||||
case shouldRun && state.certRefreshCancel == nil:
|
||||
ctx, cancel := context.WithCancel(b.ctx)
|
||||
state.certRefreshCancel = cancel
|
||||
b.goTracker.Go(func() { hookCertRefreshLoop.Get()(b, ctx) })
|
||||
case !shouldRun && state.certRefreshCancel != nil:
|
||||
state.certRefreshCancel()
|
||||
state.certRefreshCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
// serveConfigUsesACMECerts reports whether sc has any entry that
|
||||
// causes tailscaled to obtain ACME-managed TLS certs: an HTTPS Web
|
||||
// entry (background, foreground, or service) or a TCP handler with
|
||||
// TerminateTLS set (`tailscale serve --tls-terminated-tcp`).
|
||||
func serveConfigUsesACMECerts(sc ipn.ServeConfigView) bool {
|
||||
if !sc.Valid() {
|
||||
return false
|
||||
}
|
||||
for range sc.Webs() {
|
||||
return true
|
||||
}
|
||||
for _, tcp := range sc.TCPs() {
|
||||
if tcp.TerminateTLS() != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, svc := range sc.Services().All() {
|
||||
for _, tcp := range svc.TCP().All() {
|
||||
if tcp.TerminateTLS() != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// maybeSentHostinfoIfChangedLocked updates the hostinfo.ServicesHash, hostinfo.WireIngress and
|
||||
// hostinfo.IngressEnabled fields and kicks off a Hostinfo update if the values have changed.
|
||||
//
|
||||
|
||||
@@ -1367,7 +1367,9 @@ func (b *LocalBackend) serveTLSConfig(getCert func(*tls.ClientHelloInfo) (*tls.C
|
||||
return base
|
||||
}
|
||||
|
||||
func (b *LocalBackend) hasFunnelForHostPort(host string, port uint16) bool {
|
||||
// HasFunnelForHostPort reports whether the LocalBackend's serve config
|
||||
// has Funnel enabled for host:port.
|
||||
func (b *LocalBackend) HasFunnelForHostPort(host string, port uint16) bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if !b.serveConfig.Valid() {
|
||||
|
||||
@@ -28,7 +28,7 @@ type funnelFlow = struct{}
|
||||
|
||||
func (*LocalBackend) hasIngressEnabledLocked() bool { return false }
|
||||
func (*LocalBackend) shouldWireInactiveIngressLocked() bool { return false }
|
||||
func (*LocalBackend) hasFunnelForHostPort(host string, port uint16) bool {
|
||||
func (*LocalBackend) HasFunnelForHostPort(host string, port uint16) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user