Skip to content

eks: make IAM role creation optional in ServiceAccount construct #35892

@pahud

Description

@pahud

Problem Statement

The AWS CDK ServiceAccount construct currently always creates a new IAM role, even when:

  1. The ServiceAccount doesn't need AWS permissions (internal workloads)
  2. Multiple ServiceAccounts could share the same IAM role

This causes:
IAM role limit exhaustion: AWS accounts have a default limit of 1,000 IAM roles
Role sprawl: Duplicate roles with identical permissions
Violates least privilege: Creates unnecessary IAM roles for Kubernetes-only workloads

Proposed Solution (Non-Breaking)

Add two new properties to control IAM role behavior:

interface ServiceAccountProps {
  cluster: ICluster;
  name: string;
  namespace?: string;

  /**
   * Existing IAM role to associate with this ServiceAccount.
   * If provided, no new role will be created.
   *
   * @default - A new role is created
   */
  role?: IRole;

  /**
   * Whether to create an IAM role for this ServiceAccount.
   * Only applicable when `role` is not provided.
   *
   * @default true (backward compatible)
   */
  createRole?: boolean;
}

Usage Examples

1. Current Behavior (Backward Compatible - Default)

const sa = new eks.ServiceAccount(this, 'S3App', {
  cluster: cluster,
  name: 's3-app',
  namespace: 'default',
  // ✅ Creates new role (default behavior, no breaking change)
});
sa.role.addManagedPolicy(/* ... */);

2. No IAM Role (New - Opt-Out)

const sa = new eks.ServiceAccount(this, 'InternalApp', {
  cluster: cluster,
  name: 'internal-app',
  namespace: 'default',
  createRole: false, // NEW: Explicitly opt-out of role creation
});
// sa.role is undefined

3. Use Existing IAM Role (New)

const sharedRole = iam.Role.fromRoleArn(this, 'SharedRole',
  'arn:aws:iam::123456789012:role/shared-s3-reader'
);

const sa1 = new eks.ServiceAccount(this, 'App1SA', {
  cluster: cluster,
  name: 'app1',
  namespace: 'team-a',
  role: sharedRole, // NEW: Reuse existing role
});

const sa2 = new eks.ServiceAccount(this, 'App2SA', {
  cluster: cluster,
  name: 'app2',
  namespace: 'team-b',
  role: sharedRole, // Same role, different ServiceAccount
});
// When role is provided, createRole is ignored

4. Multiple ServiceAccounts Share One Role

// Create one role for multiple services
const s3ReadRole = new iam.Role(this, 'S3ReadRole', {
  assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'),
  managedPolicies: [
    ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')
  ],
});

// Reuse same role for all services
['service-a', 'service-b', 'service-c'].forEach(name => {
  new eks.ServiceAccount(this, `${name}-sa`, {
    cluster: cluster,
    name: name,
    namespace: 'production',
    role: s3ReadRole, // Shared role
  });
});

// Result: 1 IAM role instead of 3 ✅

Behavior Matrix

role provided createRole Result
No undefined (default) ✅ Create new role (current behavior)
No true ✅ Create new role
No false ✅ No role created
Yes ignored ✅ Use provided role

Validation Rules

// Invalid: Cannot specify both
new eks.ServiceAccount(this, 'Invalid', {
  cluster: cluster,
  name: 'invalid',
  role: existingRole,
  createRole: false, // ❌ Error: Cannot specify createRole when role is provided
});

Property Access

// When role is created or provided
const sa1 = new eks.ServiceAccount(this, 'WithRole', {
  cluster: cluster,
  name: 'with-role',
});
sa1.role // ✅ IRole (created)

// When role is provided
const sa2 = new eks.ServiceAccount(this, 'ExistingRole', {
  cluster: cluster,
  name: 'existing-role',
  role: myRole,
});
sa2.role // ✅ IRole (provided)

// When role creation is disabled
const sa3 = new eks.ServiceAccount(this, 'NoRole', {
  cluster: cluster,
  name: 'no-role',
  createRole: false,
});
sa3.role // ✅ undefined

Benefits

  1. 100% backward compatible: Default behavior unchanged
  2. Reduces IAM role consumption when opted-in
  3. Enables role consolidation: Multiple ServiceAccounts can share one role
  4. Supports existing roles: Integrates with roles created outside CDK
  5. Follows AWS best practices: Only create IAM roles when AWS access needed

Migration Path

Users can gradually adopt the new features:

Phase 1: Continue using current behavior (no changes needed)

// Works exactly as before
const sa = new eks.ServiceAccount(this, 'App', { cluster, name: 'app' });

Phase 2: Opt-out for internal workloads

// Add createRole: false for workloads that don't need AWS access
const sa = new eks.ServiceAccount(this, 'Internal', {
  cluster,
  name: 'internal',
  createRole: false, // NEW
});

Phase 3: Consolidate roles

// Share roles across multiple ServiceAccounts
const sharedRole = new iam.Role(/* ... */);
const sa1 = new eks.ServiceAccount(this, 'SA1', { cluster, name: 'sa1', role: sharedRole });
const sa2 = new eks.ServiceAccount(this, 'SA2', { cluster, name: 'sa2', role: sharedRole });

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

AWS CDK Library version (aws-cdk-lib)

latest

AWS CDK CLI version

latest

Environment details (OS name and version, etc.)

all

Metadata

Metadata

Assignees

No one assigned

    Labels

    @aws-cdk/aws-eksRelated to Amazon Elastic Kubernetes Service@aws-cdk/aws-iamRelated to AWS Identity and Access Managementfeature-requestA feature should be added or improved.p2

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions