kubernetes/kops · error

error listing ServiceAccounts %w

Error message

error listing ServiceAccounts %w

What it means

Thrown by listServiceAccounts when the IAM ServiceAccounts().List call for projects/<project> fails. kops enumerates project service accounts to find and clean up the cluster-generated control-plane/bastion/node service accounts.

Source

Thrown at pkg/resources/gce/gce.go:1018

	op, err := c.Compute().Routers().Delete(u.Project, u.Region, u.Name)
	if err != nil {
		if gce.IsNotFound(err) {
			klog.Infof("Router not found, assuming deleted: %q", o.SelfLink)
			return nil
		}
		return fmt.Errorf("error deleting router %s: %v", o.SelfLink, err)
	}

	return c.WaitForOp(op)
}

func (d *clusterDiscoveryGCE) listServiceAccounts() ([]*resources.Resource, error) {
	c := d.gceCloud
	ctx := context.Background()

	sas, err := c.IAM().ServiceAccounts().List(ctx, fmt.Sprintf("projects/%s", c.Project()))
	if err != nil {
		return nil, fmt.Errorf("error listing ServiceAccounts %w", err)
	}
	var resourceTrackers []*resources.Resource
	for _, sa := range sas {
		tokens := strings.Split(gce.LastComponent(sa.Name), "@")
		if len(tokens) != 2 {
			return nil, fmt.Errorf("Invalid service account email '%s'", gce.LastComponent(sa.Name))
		}
		accountID := tokens[0]
		names := []string{gce.ControlPlane, gce.Bastion, gce.Node}
		for _, name := range names {
			generatedName := gce.ServiceAccountName(name, d.clusterName)
			if generatedName == accountID {
				resourceTracker := &resources.Resource{
					Name:    gce.LastComponent(sa.Name),
					ID:      sa.Name,
					Type:    typeServiceAccount,
					Deleter: deleteServiceAccount,
					Obj:     sa,

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Enable the IAM API: gcloud services enable iam.googleapis.com.
  2. Grant roles/iam.serviceAccountViewer (or iam.serviceAccounts.list) to the kops credentials.
  3. Verify the project in the cluster spec matches the credentials' project (`gcloud config get-value project`).
  4. Retry on transient errors (429/5xx).

Example fix

// before
return nil, fmt.Errorf("error listing ServiceAccounts %w", err)
// after (distinguish permission errors for a clearer message)
if apiErr, ok := err.(*googleapi.Error); ok && apiErr.Code == 403 {
	return nil, fmt.Errorf("error listing ServiceAccounts in project %s (need roles/iam.serviceAccountViewer): %w", c.Project(), err)
}
return nil, fmt.Errorf("error listing ServiceAccounts: %w", err)
Defensive patterns

Strategy: validation

Validate before calling

// preflight IAM visibility with gcloud-equivalent check
_, err := iamClient.Projects.ServiceAccounts.List("projects/" + project).Do()
if err != nil {
	return fmt.Errorf("cannot list service accounts in %s; grant roles/iam.serviceAccountViewer: %w", project, err)
}

Type guard

func isIAMPermissionError(err error) bool {
	ge, ok := err.(*googleapi.Error)
	return ok && ge.Code == 403
}

Try / catch

sas, err := c.IAM().ServiceAccounts().List(ctx, "projects/"+c.Project())
if err != nil {
	if isIAMPermissionError(err) {
		klog.Warningf("cannot list service accounts (IAM permission); skipping SA cleanup")
		return nil, nil
	}
	return nil, fmt.Errorf("error listing ServiceAccounts: %w", err)
}

Prevention

When it happens

Trigger: IAM().ServiceAccounts().List returns an error: 403 (caller lacks iam.serviceAccounts.list, e.g. missing roles/iam.serviceAccountViewer or Service Account Token Creator), invalid project ID in the cluster spec, IAM API disabled, or transient API failure.

Common situations: kops runs with a credentials set that has compute rights but not IAM service-account visibility; project ID mismatch between cluster spec and credentials; iam.googleapis.com disabled in the project.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/06671b1b133dd05a. Report an issue: GitHub.