tailscale/tailscale · error

failed to clean up cert resources: %w

Error message

failed to clean up cert resources: %w

What it means

Thrown by HAIngressReconciler.maybeCleanupProxyGroup when cleanupCertResources fails to delete the per-domain TLS Secret and its Role/RoleBinding (label-selected on ProxyGroup and domain) for an orphaned Tailscale Service. It wraps dnsNameForService failing because no ProxyGroup node metadata exists (Pods already deleted: deriving the MagicDNS domain needs a live proxy's DNS name), or DeleteAllOf on RoleBinding/Role/Secret failing (RBAC, apiserver).

Source

Thrown at cmd/k8s-operator/ingress-for-pg.go:526

		// 3. Delete the Tailscale Service from the control plane.
		tsService, err := tsClient.VIPServices().Get(ctx, tsSvcName.String())
		switch {
		case tailscale.IsNotFound(err):
			// Already gone at the control plane; continue with cluster
			// cleanup rather than aborting the sweep.
		case err != nil:
			return svcsChanged, fmt.Errorf("getting Tailscale Service %q: %w", tsSvcName, err)
		default:
			updated, err := r.cleanupTailscaleService(ctx, tsService, logger, tsClient)
			if err != nil {
				return svcsChanged, fmt.Errorf("deleting Tailscale Service %q: %w", tsSvcName, err)
			}
			svcsChanged = svcsChanged || updated
		}

		// 4. Clean up cluster cert resources.
		if err := cleanupCertResources(ctx, r.Client, r.tsNamespace, tsSvcName, pg); err != nil {
			return svcsChanged, fmt.Errorf("failed to clean up cert resources: %w", err)
		}
	}

	return svcsChanged, nil
}

// maybeCleanup ensures that any resources, such as a Tailscale Service created for this Ingress, are cleaned up when the
// Ingress is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only
// deleted if it does not contain any other owner references. If it does the cleanup only removes the owner reference
// corresponding to this Ingress.
//
// Steps are ordered so the proxy cancels its cert loop (via serve config
// removal) before the VIPService is deleted; otherwise the loop retries
// against a domain the control plane no longer recognises.
func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger, tsClient tsclient.Client, pg *tsapi.ProxyGroup) (svcChanged bool, err error) {
	logger.Debugf("Ensuring any resources for Ingress are cleaned up")
	ix := slices.Index(ing.Finalizers, FinalizerNamePG)
	if ix < 0 {

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Order teardown: delete Ingresses and wait for finalizers to clear before deleting the ProxyGroup or operator.
  2. Reapply full RBAC if 'forbidden' appears on the DeleteAllOf calls.
  3. If the ProxyGroup is already gone: verify no control-plane VIPService remains, then manually remove the operator finalizer from the stuck Ingress and delete leftover cert Secrets, Roles, and RoleBindings by label.
Defensive patterns

Strategy: validation

Validate before calling

// teardown precondition: DNS name must be derivable, which needs live proxy state
if _, err := dnsNameForService(ctx, cl, serviceName, pg, tsNamespace); err != nil {
	// postpone ProxyGroup deletion until Ingress finalizers have cleared
}

Try / catch

if err := cleanupCertResources(ctx, r.Client, r.tsNamespace, tsSvcName, pg); err != nil {
	if strings.Contains(err.Error(), "dns name") {
		logger.Error("ProxyGroup metadata is gone; remove cert Secrets, Roles, RoleBindings by label and the Ingress finalizer manually")
	}
	return svcsChanged, fmt.Errorf("failed to clean up cert resources: %w", err)
}

Prevention

When it happens

Trigger: ProxyGroup or its Pods deleted before its Ingresses finished cleanup, so the DNS name cannot be derived; operator RBAC missing delete on roles/rolebindings/secrets; apiserver errors during teardown.

Common situations: Uninstalling the operator and ProxyGroup in one shot, leaving Ingress finalizers stuck; RBAC pruned during teardown; version skew between operators.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/0cae6d6db7dc441a. Report an issue: GitHub.