tailscale/tailscale · error

failed to list config Secrets: %w

Error message

failed to list config Secrets: %w

What it means

Thrown by maybeAdvertiseServices when the controller-runtime client's List call fails while fetching all ProxyGroup config Secrets (label selector built by pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeConfig)) in the operator namespace. The reconciler needs these Secrets to patch the advertise-services list in each proxy's config. The %w is the API server's List error.

Source

Thrown at cmd/k8s-operator/api-server-proxy-pg.go:346

	}); err != nil {
		return fmt.Errorf("failed to create or update Role %s: %w", role.Name, err)
	}
	rolebinding := certSecretRoleBinding(pg, r.tsNamespace, domain)
	if _, err := createOrUpdate(ctx, r.Client, r.tsNamespace, rolebinding, func(rb *rbacv1.RoleBinding) {
		rb.Labels = rolebinding.Labels
		rb.Subjects = rolebinding.Subjects
		rb.RoleRef = rolebinding.RoleRef
	}); err != nil {
		return fmt.Errorf("failed to create or update RoleBinding %s: %w", rolebinding.Name, err)
	}
	return nil
}

func (r *KubeAPIServerTSServiceReconciler) maybeAdvertiseServices(ctx context.Context, pg *tsapi.ProxyGroup, serviceName tailcfg.ServiceName, logger *zap.SugaredLogger) error {
	// Get all config Secrets for this ProxyGroup
	cfgSecrets := &corev1.SecretList{}
	if err := r.List(ctx, cfgSecrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeConfig))); err != nil {
		return fmt.Errorf("failed to list config Secrets: %w", err)
	}

	// Only advertise a Tailscale Service once the TLS certs required for
	// serving it are available.
	shouldBeAdvertised, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg)
	if err != nil {
		return fmt.Errorf("error checking TLS credentials provisioned for Tailscale Service %q: %w", serviceName, err)
	}
	var advertiseServices []string
	if shouldBeAdvertised {
		advertiseServices = []string{serviceName.String()}
	}

	for _, s := range cfgSecrets.Items {
		if len(s.Data[kubetypes.KubeAPIServerConfigFile]) == 0 {
			continue
		}

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Check the wrapped error verb/URL; test with kubectl auth can-i list secrets -n <operator-namespace> --as=system:serviceaccount:<ns>:operator.
  2. Ensure the operator's namespaced Role in its installation namespace includes verbs [list, get, watch] on secrets.
  3. Confirm the operator Pod's ACTAILSCALE_OPERATOR_NAMESPACE / install namespace matches where the ProxyGroup resources are created by the operator.
  4. Retry after API server recovery; the reconciler requeues on error automatically.
  5. Look for network policies blocking the operator Pod's egress to the Kubernetes API.

Example fix

# before: Role allows only reading named secrets
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["proxygroup-foo-0"]
  verbs: ["get"]

# after: allow listing operator-managed config secrets
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
Defensive patterns

Strategy: retry

Validate before calling

// kubectl auth can-i list secrets -n <operator-ns> \
//   --as=system:serviceaccount:<operator-ns>:operator
// expected output: yes

Type guard

func isListDenied(err error) bool {
	var status *apierrors.StatusError
	if errors.As(err, &status) {
		return status.ErrStatus.Reason == metav1.StatusReasonForbidden
	}
	return false
}

Try / catch

if err := r.List(ctx, cfgSecrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(sel)); err != nil {
	if isListDenied(err) {
		logger.Error("cannot list config Secrets — fix operator Role (verbs: list,watch on secrets)")
	}
	return ctrl.Result{}, err // transient API errors retried with backoff
}

Prevention

When it happens

Trigger: r.List(ctx, cfgSecrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(...)) fails when the operator ServiceAccount cannot list secrets with that label selector in the namespace, the namespace was deleted/renamed (operator installed with a different --namespace than its RBAC covers), or the API server errors on the watch-backed list.

Common situations: Operator deployed to a non-default namespace but its Role granting secrets list only exists in the old namespace; least-privilege RBAC tuned down to get-only (no list verb); label-selector optimizations requiring the secrets resource name in RBAC (list secrets with fieldSelector/labelSelector needs 'list' on 'secrets'); transient API server or etcd issues.

Related errors


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