Skip to content

Commit

Permalink
feat(ec2): create Peers via security group ids (#18248)
Browse files Browse the repository at this point in the history
Allows users to add ingress/egress security group rules containing a security group id using the Peer interface.

Implements #7111


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
jacobklitzke committed Jan 19, 2022
1 parent eb29e6f commit 9d1b2c7
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 0 deletions.
56 changes: 56 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/peer.ts
Expand Up @@ -75,6 +75,13 @@ export class Peer {
return new PrefixList(prefixListId);
}

/**
* A security group ID
*/
public static securityGroupId(securityGroupId: string, sourceSecurityGroupOwnerId?: string): IPeer {
return new SecurityGroupId(securityGroupId, sourceSecurityGroupOwnerId);
}

protected constructor() {
}
}
Expand Down Expand Up @@ -199,3 +206,52 @@ class PrefixList implements IPeer {
return { destinationPrefixListId: this.prefixListId };
}
}

/**
* A connection to or from a given security group ID
*
* For ingress rules, a sourceSecurityGroupOwnerId parameter can be specified if
* the security group exists in another account.
* This parameter will be ignored for egress rules.
*/
class SecurityGroupId implements IPeer {
public readonly canInlineRule = true;
public readonly connections: Connections = new Connections({ peer: this });
public readonly uniqueId: string;

constructor(private readonly securityGroupId: string, private readonly sourceSecurityGroupOwnerId?: string) {
if (!Token.isUnresolved(securityGroupId)) {
const securityGroupMatch = securityGroupId.match(/^sg-[a-z0-9]{8,17}$/);

if (!securityGroupMatch) {
throw new Error(`Invalid security group ID: "${securityGroupId}"`);
}
}

if (sourceSecurityGroupOwnerId && !Token.isUnresolved(sourceSecurityGroupOwnerId)) {
const accountNumberMatch = sourceSecurityGroupOwnerId.match(/^[0-9]{12}$/);

if (!accountNumberMatch) {
throw new Error(`Invalid security group owner ID: "${sourceSecurityGroupOwnerId}"`);
}
}
this.uniqueId = securityGroupId;
}

/**
* Produce the ingress rule JSON for the given connection
*/
public toIngressRuleConfig(): any {
return {
sourceSecurityGroupId: this.securityGroupId,
...(this.sourceSecurityGroupOwnerId && { sourceSecurityGroupOwnerId: this.sourceSecurityGroupOwnerId }),
};
}

/**
* Produce the egress rule JSON for the given connection
*/
public toEgressRuleConfig(): any {
return { destinationSecurityGroupId: this.securityGroupId };
}
}
120 changes: 120 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/security-group.test.ts
Expand Up @@ -178,6 +178,7 @@ describe('security group', () => {
Peer.anyIpv4(),
Peer.anyIpv6(),
Peer.prefixList('pl-012345'),
Peer.securityGroupId('sg-012345678'),
];

const ports = [
Expand Down Expand Up @@ -337,6 +338,125 @@ describe('security group', () => {
});
});

describe('Peer security group ID validation', () => {
test('passes with valid security group ID', () => {
//GIVEN
const securityGroupIds = ['sg-12345678', 'sg-0123456789abcdefg'];

// THEN
for (const securityGroupId of securityGroupIds) {
expect(Peer.securityGroupId(securityGroupId).uniqueId).toEqual(securityGroupId);
}
});

test('passes with valid security group ID and source owner id', () => {
//GIVEN
const securityGroupIds = ['sg-12345678', 'sg-0123456789abcdefg'];
const ownerIds = ['000000000000', '000000000001'];

// THEN
for (const securityGroupId of securityGroupIds) {
for (const ownerId of ownerIds) {
expect(Peer.securityGroupId(securityGroupId, ownerId).uniqueId).toEqual(securityGroupId);
}
}
});

test('passes with unresolved security group id token or owner id token', () => {
// GIVEN
Token.asString('securityGroupId');

const securityGroupId = Lazy.string({ produce: () => 'sg-01234567' });
const ownerId = Lazy.string({ produce: () => '000000000000' });
Peer.securityGroupId(securityGroupId);
Peer.securityGroupId(securityGroupId, ownerId);

// THEN: don't throw
});

test('throws if invalid security group ID', () => {
// THEN
expect(() => {
Peer.securityGroupId('invalid');
}).toThrow(/Invalid security group ID/);


});

test('throws if invalid source security group id', () => {
// THEN
expect(() => {
Peer.securityGroupId('sg-12345678', 'invalid');
}).toThrow(/Invalid security group owner ID/);
});
});

describe('SourceSecurityGroupOwnerId property validation', () => {
test('SourceSecurityGroupOwnerId property is not present when value is not provided to ingress rule', () => {
// GIVEN
const stack = new Stack(undefined, 'TestStack');
const vpc = new Vpc(stack, 'VPC');
const sg = new SecurityGroup(stack, 'SG', { vpc });

//WHEN
sg.addIngressRule(Peer.securityGroupId('sg-123456789'), Port.allTcp(), 'no owner id property');

//THEN
expect(stack).toHaveResource('AWS::EC2::SecurityGroup', {
SecurityGroupIngress: [{
SourceSecurityGroupId: 'sg-123456789',
Description: 'no owner id property',
FromPort: 0,
ToPort: 65535,
IpProtocol: 'tcp',
}],
});
});

test('SourceSecurityGroupOwnerId property is present when value is provided to ingress rule', () => {
// GIVEN
const stack = new Stack(undefined, 'TestStack');
const vpc = new Vpc(stack, 'VPC');
const sg = new SecurityGroup(stack, 'SG', { vpc });

//WHEN
sg.addIngressRule(Peer.securityGroupId('sg-123456789', '000000000000'), Port.allTcp(), 'contains owner id property');

//THEN
expect(stack).toHaveResource('AWS::EC2::SecurityGroup', {
SecurityGroupIngress: [{
SourceSecurityGroupId: 'sg-123456789',
SourceSecurityGroupOwnerId: '000000000000',
Description: 'contains owner id property',
FromPort: 0,
ToPort: 65535,
IpProtocol: 'tcp',
}],
});
});

test('SourceSecurityGroupOwnerId property is not present when value is provided to egress rule', () => {
// GIVEN
const stack = new Stack(undefined, 'TestStack');
const vpc = new Vpc(stack, 'VPC');
const sg = new SecurityGroup(stack, 'SG', { vpc, allowAllOutbound: false });

//WHEN
sg.addEgressRule(Peer.securityGroupId('sg-123456789', '000000000000'), Port.allTcp(), 'no owner id property');

//THEN
expect(stack).toHaveResource('AWS::EC2::SecurityGroup', {
SecurityGroupEgress: [{
DestinationSecurityGroupId: 'sg-123456789',
Description: 'no owner id property',
FromPort: 0,
ToPort: 65535,
IpProtocol: 'tcp',
}],
});
});
});

testDeprecated('can look up a security group', () => {
const app = new App();
const stack = new Stack(app, 'stack', {
Expand Down

0 comments on commit 9d1b2c7

Please sign in to comment.