aws / aws/aws-cdk

aws-eks: IPv6 nodegroup IAM grant hardcodes the 'aws' partition, so the CNI permission is dead in GovCloud

Open
#38,552 1 comment 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-eks bug effort/small p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

When a nodegroup is created for an IPv6 cluster, `Nodegroup` grants the node role the IPv6 address-assignment permissions the VPC CNI needs. The resource ARN for that statement is a hardcoded string in the `aws` partition:

[`aws-eks/lib/managed-nodegroup.ts#L543-L552`](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts#L543-L552)

```ts
if (props.cluster.ipFamily == IpFamily.IP_V6) {
ngRole.addToPrincipalPolicy(new PolicyStatement({
// eslint-disable-next-line @cdklabs/no-literal-partition
resources: ['arn:aws:ec2:*:*:network-interface/*'],
actions: [
'ec2:AssignIpv6Addresses',
'ec2:UnassignIpv6Addresses',
],
}));
}
```

The same block exists verbatim in [`aws-eks-v2/lib/managed-nodegroup.ts#L519-L528`](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-eks-v2/lib/managed-nodegroup.ts#L519-L528).

An ARN naming the `aws` partition can never match a resource in `aws-us-gov` or `aws-cn`, so in those partitions the statement is a no-op: it grants nothing, and it does so silently — synthesis succeeds, `cdk deploy` succeeds, and the nodes come up without the permission the CNI needs to assign IPv6 addresses to pods.

Synthesizing the same app into three regions shows the ARN never changes, while CDK's own partition-aware ARNs in the same template do:

| stack region | partition | `Resource` in the IPv6 statement | a sibling managed-policy ARN in the same template |
|---|---|---|---|
| `us-east-1` | `aws` | `arn:aws:ec2:*:*:network-interface/*` | `{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/..."]]}` |
| `us-gov-west-1` | `aws-us-gov` | `arn:aws:ec2:*:*:network-interface/*` | `{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/..."]]}` |
| `cn-north-1` | `aws-cn` | `arn:aws:ec2:*:*:network-interface/*` | `{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/..."]]}` |

The `eslint-disable` for `@cdklabs/no-literal-partition` sits directly above the line, so the rule that exists to catch exactly this was suppressed rather than satisfied. [AGENTS.md § ARN Construction](https://github.com/aws/aws-cdk/blob/main/AGENTS.md) states the rule this breaks:

> Use `Stack.of(scope).formatArn()` — never hardcode ARN strings

**Scope of the impact.** AWS documents that `ipv6` cannot be specified for clusters in China Regions, so `aws-cn` is not reachable in practice today. GovCloud (US) carries no such documented restriction, and that is where this bites: an IPv6 EKS cluster in `us-gov-west-1` gets a node role whose IPv6 grant does nothing. The symptom is pods that never receive an address, with the CNI unable to call `ec2:AssignIpv6Addresses`.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Library Version

_No response_

### Expected Behavior

The grant should be scoped to the partition the stack is deployed into, the same way every other ARN CDK emits is:

```json
{
"Action": ["ec2:AssignIpv6Addresses", "ec2:UnassignIpv6Addresses"],
"Effect": "Allow",
"Resource": { "Fn::Join": ["", ["arn:", { "Ref": "AWS::Partition" }, ":ec2:*:*:network-interface/*"]] }
}
```

or, for an app with `@aws-cdk/core:enablePartitionLiterals` and a concrete region, the resolved literal for that partition — `arn:aws-us-gov:ec2:*:*:network-interface/*`.

### Current Behavior

`Resource` is the literal `arn:aws:ec2:*:*:network-interface/*` in every partition. In `aws-us-gov` and `aws-cn` the statement matches no resource, so the two `ec2:*Ipv6Addresses` actions are effectively not granted. Nothing warns at synth or deploy time.

### Reproduction Steps

```ts
import { App, Stack } from 'aws-cdk-lib';
import * as eks from 'aws-cdk-lib/aws-eks';
import * as lambda from 'aws-cdk-lib/aws-lambda';

const app = new App();
const stack = new Stack(app, 'S', { env: { account: '123456789012', region: 'us-gov-west-1' } });

const cluster = new eks.Cluster(stack, 'C', {
version: eks.KubernetesVersion.V1_32,
ipFamily: eks.IpFamily.IP_V6,
defaultCapacity: 0,
kubectlLayer: new lambda.LayerVersion(stack, 'KubectlLayer', { code: lambda.Code.fromAsset('layer') }),
});
cluster.addNodegroupCapacity('NG', {});

const tpl = app.synth().getStackByName('S').template;
for (const res of Object.values(tpl.Resources) as any[]) {
if (res.Type !== 'AWS::IAM::Policy') continue;
for (const s of res.Properties.PolicyDocument.Statement) {
if (JSON.stringify(s).includes('network-interface')) console.log(JSON.stringify(s, null, 2));
}
}
```

Output (identical for `cn-north-1`):

```json
{
"Action": ["ec2:AssignIpv6Addresses", "ec2:UnassignIpv6Addresses"],
"Effect": "Allow",
"Resource": "arn:aws:ec2:*:*:network-interface/*"
}
```

### Possible Solution

Format the ARN instead of hardcoding it, and drop the `eslint-disable`:

```ts
if (props.cluster.ipFamily == IpFamily.IP_V6) {
ngRole.addToPrincipalPolicy(new PolicyStatement({
resources: [Stack.of(this).formatArn({
service: 'ec2',
region: '*',
account: '*',
resource: 'network-interface',
resourceName: '*',
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
})],
actions: [
'ec2:AssignIpv6Addresses',
'ec2:UnassignIpv6Addresses',
],
}));
}
```

The same change applies to the `aws-eks-v2` copy. In the `aws` partition the rendered ARN is unchanged in meaning (`{"Ref":"AWS::Partition"}` resolves to `aws`), so this only widens correctness — but note it does change the literal string in the template, so existing integ snapshots covering IPv6 nodegroups will need updating.

`aws-eks/lib/alb-controller.ts` already solves the identical problem for the ALB controller policy by rewriting `arn:aws:` to `arn:${Aws.PARTITION}:`, which is a useful precedent for how the maintainers have handled this before.

### Additional Information/Context

Same class of defect as #33212 (hardcoded partition in the S3 auto-delete-objects handler) and tracked generally by #28474.

I have not verified the runtime symptom on a live GovCloud cluster — the report is from the synthesized template plus the IAM semantics of a cross-partition resource ARN. If EKS IPv6 turns out to be unavailable in every non-`aws` partition, the ARN is still wrong and still worth fixing, but the practical impact would be limited to the moment it becomes available.

### AWS CDK Library version (aws-cdk-lib)

2.264.0

### AWS CDK CLI version

N/A (synthesis-time, framework only)

### Node.js Version

v24.1.0

### OS

macOS 15 (Darwin 25.2.0)

### Language

TypeScript

### Language Version

_No response_

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Read aws-eks/lib/managed-nodegroup.ts and the matching aws-eks-v2/lib/managed-nodegroup.ts IPv6 permission blocks, then compare the partition-aware ARN handling in aws-eks/lib/alb-controller.ts. Verify synthesized templates for aws, aws-us-gov, and aws-cn, and update the existing integ snapshots covering IPv6 nodegroups so the resource is partition-aware.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.