Move all the FooForTest methods on LocalBackend to instead be methods on a new unexported forTest type which is then given out to callers in other packages via an exported ForTest method (panicking in non-test contexts) that returns that unexported type. This is unusual style (exported returning unexported) but declutters godoc and makes call sites both more explicit and easier to read without the "ForTest" suffix polluting the symbols. Now FooForTest() changes into ForTest().Foo(). This was motivated by a pending change moving a bunch of code out of LocalBackend into other packages that required adding more ForTest methods to LocalBackend to keep the tests (now in other packages) working. Instead, do this refactor now so the future change is prettier. Updates #12614 Updates #cleanup Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: Ib25e6d76d48dc8622ac3a955e0b1220d582e63a8
85 lines
3.1 KiB
Go
85 lines
3.1 KiB
Go
// 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
|
|
}
|