diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 8e48e2be59cec..188fa661944d3 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -370,6 +370,7 @@ export class Cluster extends Resource implements ICluster { machineImageType: provider.machineImageType, // Don't enable the instance-draining lifecycle hook if managed termination protection is enabled taskDrainTime: provider.enableManagedTerminationProtection ? Duration.seconds(0) : options.taskDrainTime, + canContainersAccessInstanceRole: options.canContainersAccessInstanceRole ?? provider.canContainersAccessInstanceRole, }); this._capacityProviderNames.push(provider.capacityProviderName); @@ -1109,6 +1110,13 @@ export class AsgCapacityProvider extends CoreConstruct { */ readonly enableManagedTerminationProtection?: boolean; + /** + * Specifies whether the containers can access the container instance role. + * + * @default false + */ + readonly canContainersAccessInstanceRole?: boolean; + constructor(scope: Construct, id: string, props: AsgCapacityProviderProps) { super(scope, id); @@ -1116,6 +1124,8 @@ export class AsgCapacityProvider extends CoreConstruct { this.machineImageType = props.machineImageType ?? MachineImageType.AMAZON_LINUX_2; + this.canContainersAccessInstanceRole = props.canContainersAccessInstanceRole; + this.enableManagedTerminationProtection = props.enableManagedTerminationProtection === undefined ? true : props.enableManagedTerminationProtection; diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index d167c30989ded..45f9601728ef7 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -2306,3 +2306,145 @@ test('throws when ASG Capacity Provider with capacityProviderName starting with cluster.addAsgCapacityProvider(capacityProviderAl2); }).toThrow(/Invalid Capacity Provider Name: ecscp, If a name is specified, it cannot start with aws, ecs, or fargate./); }); + +describe('Accessing container instance role', function () { + + const addUserDataMock = jest.fn(); + const autoScalingGroup: autoscaling.AutoScalingGroup = { + addUserData: addUserDataMock, + addToRolePolicy: jest.fn(), + protectNewInstancesFromScaleIn: jest.fn(), + } as unknown as autoscaling.AutoScalingGroup; + + afterEach(() => { + addUserDataMock.mockClear(); + }); + + test('block ecs from accessing metadata service when canContainersAccessInstanceRole not set', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + }); + + cluster.addAsgCapacityProvider(capacityProvider); + + // THEN + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is set on addAsgCapacityProvider', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: true, + }); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is set on AsgCapacityProvider instantiation', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: true, + }); + + cluster.addAsgCapacityProvider(capacityProvider); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is set on constructor and method', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: true, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: true, + }); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('block ecs from accessing metadata service when canContainersAccessInstanceRole set on constructor and not set on method', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: true, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: false, + }); + + // THEN + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is not set on constructor and set on method', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: false, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: true, + }); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); +}); +