Skip to content

Commit

Permalink
feat(ec2): lookup security group by name (aws#17246)
Browse files Browse the repository at this point in the history
Support looking up a security group by name.

Currently, looking up a security group is only possible by ID. This PR enhances the existing implementation to support lookup by security group name.

`securityGroupName` or `securityGroupId` can be passed to the new method `SecurityGroup.fromLookupAttributes`. In addition, property `vpc` provides the option to restrict the lookup method to a specific VPC.

If no or more than one security group is found, an error is thrown.

Closes aws#4241.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
jumic authored and TikiTDO committed Feb 21, 2022
1 parent c4b2367 commit 4e58235
Show file tree
Hide file tree
Showing 9 changed files with 505 additions and 21 deletions.
4 changes: 4 additions & 0 deletions allowed-breaking-changes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@ removed:@aws-cdk/aws-autoscaling.EbsDeviceVolumeType.IO2
# Remove autoTerminationPolicy from stepfunctions-tasks EmrCreateClusterProps. This value is not supported by stepfunctions at the moment and was not supported in the past.
removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateCluster.AutoTerminationPolicyProperty
removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateClusterProps.autoTerminationPolicy

# Changed property securityGroupId to optional because either securityGroupId or
# securityGroupName is required. Therefore securityGroupId is no longer mandatory.
weakened:@aws-cdk/cloud-assembly-schema.SecurityGroupContextQuery
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,30 @@ const mySecurityGroupWithoutInlineRules = new ec2.SecurityGroup(this, 'SecurityG
mySecurityGroupWithoutInlineRules.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow ssh access from the world');
```

### Importing an existing security group

If you know the ID and the configuration of the security group to import, you can use `SecurityGroup.fromSecurityGroupId`:

```ts
const sg = ec2.SecurityGroup.fromSecurityGroupId(this, 'SecurityGroupImport', 'sg-1234', {
allowAllOutbound: true,
});
```

Alternatively, use lookup methods to import security groups if you do not know the ID or the configuration details. Method `SecurityGroup.fromLookupByName` looks up a security group if the secruity group ID is unknown.

```ts fixture=with-vpc
const sg = ec2.SecurityGroup.fromLookupByName(this, 'SecurityGroupLookup', 'security-group-name', vpc);
```

If the security group ID is known and configuration details are unknown, use method `SecurityGroup.fromLookupById` instead. This method will lookup property `allowAllOutbound` from the current configuration of the security group.

```ts
const sg = ec2.SecurityGroup.fromLookupById(this, 'SecurityGroupLookup', 'sg-1234');
```

The result of `SecurityGroup.fromLookupByName` and `SecurityGroup.fromLookupById` operations will be written to a file called `cdk.context.json`. You must commit this file to source control so that the lookup values are available in non-privileged environments such as CI build steps, and to ensure your template builds are repeatable.

## Machine Images (AMIs)

AMIs control the OS that gets launched when you start your EC2 instance. The EC2
Expand Down
91 changes: 76 additions & 15 deletions packages/@aws-cdk/aws-ec2/lib/security-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,25 +325,25 @@ export interface SecurityGroupImportOptions {
export class SecurityGroup extends SecurityGroupBase {
/**
* Look up a security group by id.
*
* @deprecated Use `fromLookupById()` instead
*/
public static fromLookup(scope: Construct, id: string, securityGroupId: string) {
if (Token.isUnresolved(securityGroupId)) {
throw new Error('All arguments to look up a security group must be concrete (no Tokens)');
}
return this.fromLookupAttributes(scope, id, { securityGroupId });
}

const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, {
provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER,
props: { securityGroupId },
dummyValue: {
securityGroupId: 'sg-12345',
allowAllOutbound: true,
} as cxapi.SecurityGroupContextResponse,
}).value;
/**
* Look up a security group by id.
*/
public static fromLookupById(scope: Construct, id: string, securityGroupId: string) {
return this.fromLookupAttributes(scope, id, { securityGroupId });
}

return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, {
allowAllOutbound: attributes.allowAllOutbound,
mutable: true,
});
/**
* Look up a security group by name.
*/
public static fromLookupByName(scope: Construct, id: string, securityGroupName: string, vpc: IVpc) {
return this.fromLookupAttributes(scope, id, { securityGroupName, vpc });
}

/**
Expand Down Expand Up @@ -387,6 +387,33 @@ export class SecurityGroup extends SecurityGroupBase {
: new ImmutableImport(scope, id);
}

/**
* Look up a security group.
*/
private static fromLookupAttributes(scope: Construct, id: string, options: SecurityGroupLookupOptions) {
if (Token.isUnresolved(options.securityGroupId) || Token.isUnresolved(options.securityGroupName) || Token.isUnresolved(options.vpc?.vpcId)) {
throw new Error('All arguments to look up a security group must be concrete (no Tokens)');
}

const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, {
provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER,
props: {
securityGroupId: options.securityGroupId,
securityGroupName: options.securityGroupName,
vpcId: options.vpc?.vpcId,
},
dummyValue: {
securityGroupId: 'sg-12345',
allowAllOutbound: true,
} as cxapi.SecurityGroupContextResponse,
}).value;

return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, {
allowAllOutbound: attributes.allowAllOutbound,
mutable: true,
});
}

/**
* An attribute that represents the security group name.
*
Expand Down Expand Up @@ -696,3 +723,37 @@ function egressRulesEqual(a: CfnSecurityGroup.EgressProperty, b: CfnSecurityGrou
function isAllTrafficRule(rule: any) {
return rule.cidrIp === '0.0.0.0/0' && rule.ipProtocol === '-1';
}

/**
* Properties for looking up an existing SecurityGroup.
*
* Either `securityGroupName` or `securityGroupId` has to be specified.
*/
interface SecurityGroupLookupOptions {
/**
* The name of the security group
*
* If given, will import the SecurityGroup with this name.
*
* @default Don't filter on securityGroupName
*/
readonly securityGroupName?: string;

/**
* The ID of the security group
*
* If given, will import the SecurityGroup with this ID.
*
* @default Don't filter on securityGroupId
*/
readonly securityGroupId?: string;

/**
* The VPC of the security group
*
* If given, will filter the SecurityGroup based on the VPC.
*
* @default Don't filter on VPC
*/
readonly vpc?: IVpc,
}
123 changes: 123 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/security-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,131 @@ describe('security group', () => {
expect(securityGroup.securityGroupId).toEqual('sg-12345');
expect(securityGroup.allowAllOutbound).toEqual(true);

});

test('can look up a security group by id', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

// WHEN
const securityGroup = SecurityGroup.fromLookupById(stack, 'SG1', 'sg-12345');

// THEN
expect(securityGroup.securityGroupId).toEqual('sg-12345');
expect(securityGroup.allowAllOutbound).toEqual(true);

});

test('can look up a security group by name and vpc', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

const vpc = Vpc.fromVpcAttributes(stack, 'VPC', {
vpcId: 'vpc-1234',
availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'],
});

// WHEN
const securityGroup = SecurityGroup.fromLookupByName(stack, 'SG1', 'sg-12345', vpc);

// THEN
expect(securityGroup.securityGroupId).toEqual('sg-12345');
expect(securityGroup.allowAllOutbound).toEqual(true);

});

test('can look up a security group by id and vpc', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

const vpc = Vpc.fromVpcAttributes(stack, 'VPC', {
vpcId: 'vpc-1234',
availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'],
});

// WHEN
const securityGroup = SecurityGroup.fromLookupByName(stack, 'SG1', 'my-security-group', vpc);

// THEN
expect(securityGroup.securityGroupId).toEqual('sg-12345');
expect(securityGroup.allowAllOutbound).toEqual(true);

});

test('throws if securityGroupId is tokenized', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

// WHEN
expect(() => {
SecurityGroup.fromLookupById(stack, 'stack', Lazy.string({ produce: () => 'sg-12345' }));
}).toThrow('All arguments to look up a security group must be concrete (no Tokens)');

});

test('throws if securityGroupName is tokenized', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

// WHEN
expect(() => {
SecurityGroup.fromLookupById(stack, 'stack', Lazy.string({ produce: () => 'my-security-group' }));
}).toThrow('All arguments to look up a security group must be concrete (no Tokens)');

});

test('throws if vpc id is tokenized', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

const vpc = Vpc.fromVpcAttributes(stack, 'VPC', {
vpcId: Lazy.string({ produce: () => 'vpc-1234' }),
availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'],
});

// WHEN
expect(() => {
SecurityGroup.fromLookupByName(stack, 'stack', 'my-security-group', vpc);
}).toThrow('All arguments to look up a security group must be concrete (no Tokens)');

});

});

function testRulesAreInlined(contextDisableInlineRules: boolean | undefined | null, optionsDisableInlineRules: boolean | undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,24 @@ export interface SecurityGroupContextQuery {

/**
* Security group id
*
* @default - None
*/
readonly securityGroupId?: string;

/**
* Security group name
*
* @default - None
*/
readonly securityGroupId: string;
readonly securityGroupName?: string;

/**
* VPC ID
*
* @default - None
*/
readonly vpcId?: string;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,14 +761,21 @@
"type": "string"
},
"securityGroupId": {
"description": "Security group id",
"description": "Security group id (Default - None)",
"type": "string"
},
"securityGroupName": {
"description": "Security group name (Default - None)",
"type": "string"
},
"vpcId": {
"description": "VPC ID (Default - None)",
"type": "string"
}
},
"required": [
"account",
"region",
"securityGroupId"
"region"
]
},
"KeyContextQuery": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"14.0.0"}
{"version":"15.0.0"}
29 changes: 28 additions & 1 deletion packages/aws-cdk/lib/context-providers/security-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,45 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin
const account: string = args.account!;
const region: string = args.region!;

if (args.securityGroupId && args.securityGroupName) {
throw new Error('\'securityGroupId\' and \'securityGroupName\' can not be specified both when looking up a security group');
}

if (!args.securityGroupId && !args.securityGroupName) {
throw new Error('\'securityGroupId\' or \'securityGroupName\' must be specified to look up a security group');
}

const options = { assumeRoleArn: args.lookupRoleArn };
const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2();

const filters: AWS.EC2.FilterList = [];
if (args.vpcId) {
filters.push({
Name: 'vpc-id',
Values: [args.vpcId],
});
}
if (args.securityGroupName) {
filters.push({
Name: 'group-name',
Values: [args.securityGroupName],
});
}

const response = await ec2.describeSecurityGroups({
GroupIds: [args.securityGroupId],
GroupIds: args.securityGroupId ? [args.securityGroupId] : undefined,
Filters: filters.length > 0 ? filters : undefined,
}).promise();

const securityGroups = response.SecurityGroups ?? [];
if (securityGroups.length === 0) {
throw new Error(`No security groups found matching ${JSON.stringify(args)}`);
}

if (securityGroups.length > 1) {
throw new Error(`More than one security groups found matching ${JSON.stringify(args)}`);
}

const [securityGroup] = securityGroups;

return {
Expand Down
Loading

0 comments on commit 4e58235

Please sign in to comment.