kubernetes/kops · error

Invalid service account email '%s'

Error message

Invalid service account email '%s'

What it means

Thrown by listServiceAccounts when a service account resource name cannot be split into exactly two '@'-separated parts. kops derives the account ID (local part of the email) from the SA resource name via LastComponent and expects an email form like name@project.iam.gserviceaccount.com; a malformed name breaks this assumption.

Source

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

		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,
				}

				klog.V(4).Infof("found resource: %s", sa.Name)
				resourceTrackers = append(resourceTrackers, resourceTracker)
				break
			}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. List project SAs (`gcloud iam service-accounts list`) and inspect the malformed email; recreate or remove the offending SA.
  2. Treat non-matching SAs as skippable instead of aborting the whole cleanup, since only cluster-generated names (control-plane/bastion/node) matter.
  3. If it blocks cluster deletion, temporarily delete/rename the malformed SA so discovery completes.

Example fix

// before
if len(tokens) != 2 {
	return nil, fmt.Errorf("Invalid service account email '%s'", gce.LastComponent(sa.Name))
}
// after (skip non-cluster SAs instead of failing the whole listing)
if len(tokens) != 2 {
	klog.V(4).Infof("skipping service account with unexpected name %q", gce.LastComponent(sa.Name))
	continue
}
Defensive patterns

Strategy: validation

Validate before calling

email := gce.LastComponent(sa.Name)
if strings.Count(email, "@") != 1 {
	// skip or sanitize before processing
	klog.V(4).Infof("skipping malformed SA name %q", email)
}

Type guard

func isServiceAccountEmail(name string) bool {
	parts := strings.Split(name, "@")
	return len(parts) == 2 && parts[0] != "" && strings.HasSuffix(parts[1], ".iam.gserviceaccount.com")
}

Try / catch

// error only occurs mid-loop; wrap the SA processing loop
for _, sa := range sas {
	if err := processServiceAccount(sa); err != nil {
		klog.Warningf("skipping service account %q: %v", sa.Name, err)
		continue
	}
}

Prevention

When it happens

Trigger: A service account exists in the project whose resource name's last component contains zero or multiple '@' characters — e.g. programmatically created SAs with unusual emails, SAs created by other tooling with unexpected naming, or a change in IAM API resource-name format.

Common situations: Project contains service accounts created by Terraform/other automation with custom emails; default App Engine or compute SAs with edge-case names; future API format drift.

Related errors


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