eks: make IAM role creation optional in ServiceAccount construct
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
## 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:
```typescript
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)
```typescript
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)
```typescript
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)
```typescript
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
```typescript
// 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
```typescript
// 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
```typescript
// 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)
```typescript
// Works exactly as before
const sa = new eks.ServiceAccount(this, 'App', { cluster, name: 'app' });
```
Phase 2: Opt-out for internal workloads
```typescript
// 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
```typescript
// 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
- [x] 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
Contributor guide
Research direction
Start at the EKS ServiceAccount construct and its ServiceAccountProps entry point; trace how the current IAM role and Kubernetes ServiceAccount are created. Verify the role/createRole combinations, validation, and role exposure described in the behavior matrix, then add coverage for each case and confirm the default still creates a role.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, kubernetes, typescript
- Domain
- cloud, infrastructure
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100