cmd/k8s-operator,ipn/store/kubestore,kube/kubetypes: share ACME account key per tailnet
Introduce a per-tailnet shared ACME account key so that all ingress ProxyGroup replicas on a tailnet present the same account identity to Let's Encrypt. This lets renewals claim the ARI "replaces" exemption from the 50-certs-per-week rate limit, surviving Pod restarts, ProxyGroup recreation, and cluster migrations. The operator provisions a "tailscale-acme-accounts" Secret in its namespace, guarded by a finalizer and a deletion warning event, and watched so it is recreated promptly if removed. Proxies migrate any pre-existing per-pod key into the shared Secret on first boot, adopt the shared key on subsequent boots, and restore it on cert writes if the Secret was recreated empty. Certs are stamped with the fingerprint of the issuing account so renewals skip the "replaces" claim when the account doesn't match. Opt-in per-ProxyGroup via the tailscale.com/share-acme-account annotation, or operator-wide via OPERATOR_SHARED_ACME_ACCOUNT_KEY. Updates #18251 Updates #20288 Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
committed by
Tom Meadows
parent
2900f3494a
commit
97a75c837d
+20
-7
@@ -360,17 +360,30 @@ var getCertPEM = func(ctx context.Context, e *extension, b *ipnlocal.LocalBacken
|
||||
return nil, fmt.Errorf("unexpected ACME account status %q", a.Status)
|
||||
}
|
||||
|
||||
// If we have a previous cert, include it in the order. Assuming we're
|
||||
// within the ARI renewal window this should exclude us from LE rate
|
||||
// limits.
|
||||
// Note that this order extension will fail renewals if the ACME account key has changed
|
||||
// since the last issuance, see
|
||||
// https://github.com/tailscale/tailscale/issues/18251
|
||||
// If we have a previous cert, include it in the order via the ARI
|
||||
// "replaces" extension so LE classifies the new cert as a renewal
|
||||
// and exempts it from the per-registered-domain rate limit. Stores
|
||||
// that can tell the current ACME account did not issue the previous
|
||||
// cert opt out via [ARIReplacesAllower]; see #18251.
|
||||
var opts []xacme.OrderOption
|
||||
if previous != nil && !envknob.Bool("TS_DEBUG_ACME_FORCE_RENEWAL") {
|
||||
prevCrt, err := parseCertificate(previous)
|
||||
if err == nil {
|
||||
opts = append(opts, xacme.WithOrderReplacesCert(prevCrt))
|
||||
useReplaces := true
|
||||
if a, ok := cs.(ARIReplacesAllower); ok {
|
||||
if allowed, err := a.ShouldUseARIReplacesForRenewal(domain); err != nil {
|
||||
// Fail open: an error means we couldn't check
|
||||
// eligibility, not that the account is misaligned.
|
||||
logf("acme: failed to check ARI 'replaces' eligibility for %q, defaulting to use it: %v", domain, err)
|
||||
} else {
|
||||
useReplaces = allowed
|
||||
}
|
||||
}
|
||||
if useReplaces {
|
||||
opts = append(opts, xacme.WithOrderReplacesCert(prevCrt))
|
||||
} else {
|
||||
logf("account key mismatch for previous cert; skipping ARI 'replaces' hint")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user