diff --git a/platform/src/components/aws/vpc.ts b/platform/src/components/aws/vpc.ts index bf96a30769..fadb8e3d48 100644 --- a/platform/src/components/aws/vpc.ts +++ b/platform/src/components/aws/vpc.ts @@ -345,12 +345,14 @@ export class Vpc extends Component implements Link.Linkable { private securityGroup: ec2.SecurityGroup; private natGateways: Output; private natInstances: Output; + private natSecurityGroup: Output; private elasticIps: Output; private _publicSubnets: Output; private _privateSubnets: Output; private publicRouteTables: Output; private privateRouteTables: Output; private bastionInstance: Output; + private bastionSecurityGroup: Output; private cloudmapNamespace: servicediscovery.PrivateDnsNamespace; private privateKeyValue: Output; public static v1 = VpcV1; @@ -376,8 +378,10 @@ export class Vpc extends Component implements Link.Linkable { this.privateRouteTables = output(ref.privateRouteTables); this.natGateways = output(ref.natGateways); this.natInstances = output(ref.natInstances); + this.natSecurityGroup = output(ref.natSecurityGroup); this.elasticIps = ref.elasticIps; this.bastionInstance = ref.bastionInstance; + this.bastionSecurityGroup = output(ref.bastionSecurityGroup); this.cloudmapNamespace = ref.cloudmapNamespace; this.privateKeyValue = output(ref.privateKeyValue); registerOutputs(); @@ -396,9 +400,9 @@ export class Vpc extends Component implements Link.Linkable { const { publicSubnets, publicRouteTables } = createPublicSubnets(); const elasticIps = createElasticIps(); const natGateways = createNatGateways(); - const natInstances = createNatInstances(); + const { natInstances, natSecurityGroup } = createNatInstances(); const { privateSubnets, privateRouteTables } = createPrivateSubnets(); - const bastionInstance = createBastion(); + const { bastionInstance, bastionSecurityGroup } = createBastion(); const cloudmapNamespace = createCloudmapNamespace(); this.vpc = vpc; @@ -406,12 +410,14 @@ export class Vpc extends Component implements Link.Linkable { this.securityGroup = securityGroup; this.natGateways = natGateways; this.natInstances = natInstances; + this.natSecurityGroup = natSecurityGroup; this.elasticIps = elasticIps; this._publicSubnets = publicSubnets; this._privateSubnets = privateSubnets; this.publicRouteTables = publicRouteTables; this.privateRouteTables = privateRouteTables; this.bastionInstance = output(bastionInstance); + this.bastionSecurityGroup = bastionSecurityGroup; this.cloudmapNamespace = cloudmapNamespace; this.privateKeyValue = output(privateKeyValue); registerOutputs(); @@ -466,9 +472,11 @@ export class Vpc extends Component implements Link.Linkable { ) .ids.apply((ids) => { if (!ids.length) { - throw new VisibleError( - `Security group not found in VPC ${vpcId}`, - ); + vpcId.apply(vpcId => { + throw new VisibleError( + `Security group not found in VPC ${vpcId}`, + ); + }); } return ids[0]; }), @@ -582,6 +590,23 @@ export class Vpc extends Component implements Link.Linkable { }), ), ); + const natSecurityGroup = ec2 + .getSecurityGroupsOutput( + { + filters: [ + { name: "tag:sst:is-nat-sg", values: ["true"] }, + { name: "vpc-id", values: [vpcId] }, + ], + }, + { parent: self }, + ) + .ids.apply((ids) => + ids.length + ? ec2.SecurityGroup.get(`${name}NatInstanceSecurityGroup`, ids[0], undefined, { + parent: self + }) + : undefined, + ); const bastionInstance = ec2 .getInstancesOutput( { @@ -599,6 +624,23 @@ export class Vpc extends Component implements Link.Linkable { }) : undefined, ); + const bastionSecurityGroup = ec2 + .getSecurityGroupsOutput( + { + filters: [ + { name: "tag:sst:is-bastion-sg", values: ["true"] }, + { name: "vpc-id", values: [vpcId] }, + ], + }, + { parent: self }, + ) + .ids.apply((ids) => + ids.length + ? ec2.SecurityGroup.get(`${name}BastionSecurityGroup`, ids[0], undefined, { + parent: self + }) + : undefined, + ); // Note: can also use servicediscovery.getDnsNamespaceOutput() here, ie. // ```ts @@ -657,8 +699,10 @@ export class Vpc extends Component implements Link.Linkable { privateRouteTables, natGateways, natInstances, + natSecurityGroup, elasticIps, bastionInstance, + bastionSecurityGroup, cloudmapNamespace, privateKeyValue, }; @@ -909,9 +953,13 @@ export class Vpc extends Component implements Link.Linkable { function createNatInstances() { return nat.apply((nat) => { - if (nat?.type !== "ec2") return output([]); + if (nat?.type !== "ec2") + return output({ + natSecurityGroup: undefined, + natInstances: [] as ec2.Instance[], + }); - const sg = new ec2.SecurityGroup( + const natSecurityGroup = new ec2.SecurityGroup( ...transform( args.transform?.natSecurityGroup, `${name}NatInstanceSecurityGroup`, @@ -933,6 +981,9 @@ export class Vpc extends Component implements Link.Linkable { cidrBlocks: ["0.0.0.0/0"], }, ], + tags: { + "sst:is-nat-sg": "true", + }, }, { parent: self }, ), @@ -994,36 +1045,52 @@ export class Vpc extends Component implements Link.Linkable { elasticIps, keyPair, args.bastion, - ]).apply(([zones, publicSubnets, elasticIps, keyPair, bastion]) => - zones.map((_, i) => { - const instance = new ec2.Instance( - ...transform( - args.transform?.natInstance, - `${name}NatInstance${i + 1}`, - { - instanceType: nat.ec2.instance, - ami, - subnetId: publicSubnets[i].id, - vpcSecurityGroupIds: [sg.id], - iamInstanceProfile: instanceProfile.name, - sourceDestCheck: false, - keyName: keyPair?.keyName, - tags: { - Name: `${name} NAT Instance`, - "sst:is-nat": "true", - ...(bastion && i === 0 ? { "sst:is-bastion": "true" } : {}), + natSecurityGroup, + ]).apply( + ([ + zones, + publicSubnets, + elasticIps, + keyPair, + bastion, + natSecurityGroup, + ]) => ({ + natInstances: zones.map((_, i) => { + const instance = new ec2.Instance( + ...transform( + args.transform?.natInstance, + `${name}NatInstance${i + 1}`, + { + instanceType: nat.ec2.instance, + ami, + subnetId: publicSubnets[i].id, + vpcSecurityGroupIds: [natSecurityGroup.id], + iamInstanceProfile: instanceProfile.name, + sourceDestCheck: false, + keyName: keyPair?.keyName, + tags: { + Name: `${name} NAT Instance`, + "sst:is-nat": "true", + ...(bastion && i === 0 + ? { "sst:is-bastion": "true" } + : {}), + }, }, - }, - { parent: self }, - ), - ); + { parent: self }, + ), + ); - new ec2.EipAssociation(`${name}NatInstanceEipAssociation${i + 1}`, { - instanceId: instance.id, - allocationId: elasticIps[i]?.id ?? nat.ip![i], - }); + new ec2.EipAssociation( + `${name}NatInstanceEipAssociation${i + 1}`, + { + instanceId: instance.id, + allocationId: elasticIps[i]?.id ?? nat.ip![i], + }, + ); - return instance; + return instance; + }), + natSecurityGroup, }), ); }); @@ -1152,11 +1219,19 @@ export class Vpc extends Component implements Link.Linkable { function createBastion() { return all([args.bastion, natInstances, keyPair]).apply( ([bastion, natInstances, keyPair]) => { - if (!bastion) return undefined; + if (!bastion) + return { + bastionSecurityGroup: undefined, + bastionInstance: undefined, + }; - if (natInstances.length) return natInstances[0]; + if (natInstances.length) + return { + bastionSecurityGroup: natSecurityGroup, + bastionInstance: natInstances[0], + }; - const sg = new ec2.SecurityGroup( + const bastionSecurityGroup = new ec2.SecurityGroup( ...transform( args.transform?.bastionSecurityGroup, `${name}BastionSecurityGroup`, @@ -1178,6 +1253,9 @@ export class Vpc extends Component implements Link.Linkable { cidrBlocks: ["0.0.0.0/0"], }, ], + tags: { + "sst:is-bastion-sg": "true", + } }, { parent: self }, ), @@ -1228,7 +1306,7 @@ export class Vpc extends Component implements Link.Linkable { }, { parent: self }, ); - return new ec2.Instance( + const bastionInstance = new ec2.Instance( ...transform( args.transform?.bastionInstance, `${name}BastionInstance`, @@ -1236,7 +1314,7 @@ export class Vpc extends Component implements Link.Linkable { instanceType: "t4g.nano", ami: ami.id, subnetId: publicSubnets.apply((v) => v[0].id), - vpcSecurityGroupIds: [sg.id], + vpcSecurityGroupIds: [bastionSecurityGroup.id], iamInstanceProfile: instanceProfile.name, keyName: keyPair?.keyName, tags: { @@ -1246,6 +1324,7 @@ export class Vpc extends Component implements Link.Linkable { { parent: self }, ), ); + return { bastionInstance, bastionSecurityGroup }; }, ); } @@ -1333,6 +1412,10 @@ export class Vpc extends Component implements Link.Linkable { * The Amazon EC2 NAT instances. */ natInstances: this.natInstances, + /** + * The Amazon EC2 Security Group for the NAT instances. + */ + natSecurityGroup: this.natSecurityGroup, /** * The Amazon EC2 Elastic IP. */ @@ -1357,6 +1440,10 @@ export class Vpc extends Component implements Link.Linkable { * The Amazon EC2 bastion instance. */ bastionInstance: this.bastionInstance, + /** + * The Amazon EC2 Security Group for the bastion instance. + */ + bastionSecurityGroup: this.bastionSecurityGroup, /** * The AWS Cloudmap namespace. */