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

feat(ec2): lookup security group by name #17246

Merged
merged 8 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
}
jumic marked this conversation as resolved.
Show resolved Hide resolved

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