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
- List project SAs (`gcloud iam service-accounts list`) and inspect the malformed email; recreate or remove the offending SA.
- Treat non-matching SAs as skippable instead of aborting the whole cleanup, since only cluster-generated names (control-plane/bastion/node) matter.
- 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
- Audit project SAs for unusual emails created by other tooling.
- Only attempt to match cluster-generated SA names; skip others.
- Pin/verify the cloud library version that returns SA resource names.
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
- error listing ServiceAccounts %w
- error deleting ServiceAccount %s: %v
- error listing ServiceAccount %q: %w
- found ServiceAccount but email did not match expected; got %
- ServiceAccount with email %q not found
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/2851e7d148a935fe.
Report an issue: GitHub.