Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ec2): restrictDefaultSecurityGroup fails when default rules are not present #27039

Merged
merged 9 commits into from
Sep 20, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,20 @@ async function onUpdate(event: AWSLambda.CloudFormationCustomResourceUpdateEvent
* Revoke both ingress and egress rules
*/
async function revokeRules(groupId: string, account: string): Promise<void> {
await ec2.revokeSecurityGroupEgress(egressRuleParams(groupId));
await ec2.revokeSecurityGroupIngress(ingressRuleParams(groupId, account));
try {
await ec2.revokeSecurityGroupEgress(egressRuleParams(groupId));
} catch (e: any) {
if (!(e instanceof Error) || (e instanceof Error && e.name !== 'InvalidPermission.NotFound')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just rolled back using typed exceptions in sdk v3 because there are some known issues with it. We should just use e.name so we are in line with all our other custom resource code.

We should just be able to consolidate like so?

try {
  await ec2.revokeSecurityGroupEgress(egressRuleParams(groupId));
  await ec2.revokeSecurityGroupIngress(ingressRuleParams(groupId, account));
} catch (e: any) {
  if (e.name === 'InvalidPermission.NotFound') {
    return;
  }
  throw e;
}

Most typed exceptions in sdkV3 have a type for each different error.name value with that field hardcoded in. However Ec2 just has ServiceException with a bunch of different "error codes" which are also used as the error.name field. I just ran the commands against a non-existing security group to ensure that these name fields are as expected since we can't verify them in the sdk code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your explanation. I removed error type checks.
Just found the error code InvalidPermission.NotFound is listed in this doc.

We should just be able to consolidate like so?

I think these two API calls should be put into separate try-catch blocks, because even if the default egress rule is not found, we still want to execute revokeSecurityGroupIngress.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh yes, makes sense.

throw (e);
}
}
try {
await ec2.revokeSecurityGroupIngress(ingressRuleParams(groupId, account));
} catch (e: any) {
if (!(e instanceof Error) || (e instanceof Error && e.name !== 'InvalidPermission.NotFound')) {
throw (e);
}
}
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,61 @@ test('update event with security group change', async () => {
});
});

test('invoking when rules are not found should not throw error', async () => {
// GIVEN
const event: Partial<AWSLambda.CloudFormationCustomResourceCreateEvent> = {
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'Foo',
DefaultSecurityGroupId: 'sg-abc123',
Account: '12345678912',
},
};
mockEc2Client.on(RevokeSecurityGroupEgressCommand).rejects({ name: 'InvalidPermission.NotFound' });
mockEc2Client.on(RevokeSecurityGroupIngressCommand).rejects({ name: 'InvalidPermission.NotFound' });

// THEN
await expect(invokeHandler(event)).resolves.not.toThrow();
expect(mockEc2Client).toHaveReceivedCommandTimes(RevokeSecurityGroupEgressCommand, 1);
expect(mockEc2Client).toHaveReceivedCommandTimes(RevokeSecurityGroupIngressCommand, 1);
expect(mockEc2Client).toHaveReceivedCommandWith(RevokeSecurityGroupEgressCommand, {
GroupId: 'sg-abc123',
IpPermissions: [{
IpRanges: [{
CidrIp: '0.0.0.0/0',
}],
IpProtocol: '-1',
}],
});
expect(mockEc2Client).toHaveReceivedCommandWith(RevokeSecurityGroupIngressCommand, {
GroupId: 'sg-abc123',
IpPermissions: [{
UserIdGroupPairs: [{
UserId: '12345678912',
GroupId: 'sg-abc123',
}],
IpProtocol: '-1',
}],
});
});

test('other errors should be thrown', async () => {
// GIVEN
const event: Partial<AWSLambda.CloudFormationCustomResourceCreateEvent> = {
RequestType: 'Create',
ResourceProperties: {
ServiceToken: 'Foo',
DefaultSecurityGroupId: 'sg-abc123',
Account: '12345678912',
},
};
mockEc2Client.on(RevokeSecurityGroupEgressCommand).rejects({ name: 'Some.Other.Errors' });
mockEc2Client.on(RevokeSecurityGroupIngressCommand).rejects({ name: 'Some.Other.Errors' });

// THEN
await expect(invokeHandler(event)).rejects.toThrow();
});

// helper function to get around TypeScript expecting a complete event object,
// even though our tests only need some of the fields
async function invokeHandler(event: Partial<AWSLambda.CloudFormationCustomResourceEvent>) {
Expand Down
Loading