diff --git a/packages/cdk/lib/__snapshots__/cloudquery.test.ts.snap b/packages/cdk/lib/__snapshots__/cloudquery.test.ts.snap index 09fc48077..1d33f0e54 100644 --- a/packages/cdk/lib/__snapshots__/cloudquery.test.ts.snap +++ b/packages/cdk/lib/__snapshots__/cloudquery.test.ts.snap @@ -7,6 +7,7 @@ exports[`The CloudQuery stack matches the snapshot 1`] = ` "GuSubnetListParameter", "GuVpcParameter", "GuSecurityGroup", + "GuSecurityGroup", "GuStringParameter", "GuLoggingStreamNameParameter", ], @@ -7706,7 +7707,7 @@ spec: "VPCSecurityGroups": [ { "Fn::GetAtt": [ - "PostgresInstance1SecurityGroupFA28C3C0", + "PostgresSecurityGroupCloudquery65E31BB8", "GroupId", ], }, @@ -7769,16 +7770,12 @@ spec: }, "Type": "AWS::SecretsManager::SecretTargetAttachment", }, - "PostgresInstance1SecurityGroupFA28C3C0": { + "PostgresInstance1SubnetGroupCAC045A5": { "Properties": { - "GroupDescription": "Security group for PostgresInstance1 database", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1", - }, - ], + "DBSubnetGroupDescription": "Subnet group for PostgresInstance1 database", + "SubnetIds": { + "Ref": "PrivateSubnets", + }, "Tags": [ { "Key": "gu:cdk:version", @@ -7797,40 +7794,54 @@ spec: "Value": "TEST", }, ], - "VpcId": { - "Ref": "VpcId", - }, }, - "Type": "AWS::EC2::SecurityGroup", + "Type": "AWS::RDS::DBSubnetGroup", }, - "PostgresInstance1SecurityGroupfromCloudQueryPostgresAccessSecurityGroupCloudqueryAE627D465432AE3168F5": { + "PostgresInstanceEndpointAddress6E14162C": { "Properties": { - "Description": "from CloudQueryPostgresAccessSecurityGroupCloudqueryAE627D46:5432", - "FromPort": 5432, - "GroupId": { - "Fn::GetAtt": [ - "PostgresInstance1SecurityGroupFA28C3C0", - "GroupId", - ], + "DataType": "text", + "Name": "/TEST/deploy/cloudquery/postgres-instance-endpoint-address", + "Tags": { + "Stack": "deploy", + "Stage": "TEST", + "gu:cdk:version": "TEST", + "gu:repo": "guardian/service-catalogue", }, - "IpProtocol": "tcp", - "SourceSecurityGroupId": { + "Tier": "Standard", + "Type": "String", + "Value": { "Fn::GetAtt": [ - "PostgresAccessSecurityGroupCloudqueryE959A23F", - "GroupId", + "PostgresInstance16DE4286E", + "Endpoint.Address", ], }, - "ToPort": 5432, }, - "Type": "AWS::EC2::SecurityGroupIngress", + "Type": "AWS::SSM::Parameter", }, - "PostgresInstance1SubnetGroupCAC045A5": { + "PostgresSecurityGroupCloudquery65E31BB8": { "Properties": { - "DBSubnetGroupDescription": "Subnet group for PostgresInstance1 database", - "SubnetIds": { - "Ref": "PrivateSubnets", - }, + "GroupDescription": "CloudQuery/PostgresSecurityGroupCloudquery", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1", + }, + ], + "SecurityGroupIngress": [ + { + "CidrIp": "10.0.0.4/22", + "Description": "Allow connection to Postgres from the office network.", + "FromPort": 5432, + "IpProtocol": "tcp", + "ToPort": 5432, + }, + ], "Tags": [ + { + "Key": "App", + "Value": "cloudquery", + }, { "Key": "gu:cdk:version", "Value": "TEST", @@ -7848,29 +7859,32 @@ spec: "Value": "TEST", }, ], + "VpcId": { + "Ref": "VpcId", + }, }, - "Type": "AWS::RDS::DBSubnetGroup", + "Type": "AWS::EC2::SecurityGroup", }, - "PostgresInstanceEndpointAddress6E14162C": { + "PostgresSecurityGroupCloudqueryfromCloudQueryPostgresAccessSecurityGroupCloudqueryAE627D46543299D711F8": { "Properties": { - "DataType": "text", - "Name": "/TEST/deploy/cloudquery/postgres-instance-endpoint-address", - "Tags": { - "Stack": "deploy", - "Stage": "TEST", - "gu:cdk:version": "TEST", - "gu:repo": "guardian/service-catalogue", + "Description": "from CloudQueryPostgresAccessSecurityGroupCloudqueryAE627D46:5432", + "FromPort": 5432, + "GroupId": { + "Fn::GetAtt": [ + "PostgresSecurityGroupCloudquery65E31BB8", + "GroupId", + ], }, - "Tier": "Standard", - "Type": "String", - "Value": { + "IpProtocol": "tcp", + "SourceSecurityGroupId": { "Fn::GetAtt": [ - "PostgresInstance16DE4286E", - "Endpoint.Address", + "PostgresAccessSecurityGroupCloudqueryE959A23F", + "GroupId", ], }, + "ToPort": 5432, }, - "Type": "AWS::SSM::Parameter", + "Type": "AWS::EC2::SecurityGroupIngress", }, "cloudqueryCluster5370C11B": { "Properties": { diff --git a/packages/cdk/lib/cloudquery.ts b/packages/cdk/lib/cloudquery.ts index 055098a5c..35a6bd2fe 100644 --- a/packages/cdk/lib/cloudquery.ts +++ b/packages/cdk/lib/cloudquery.ts @@ -6,13 +6,17 @@ import { SubnetType, } from '@guardian/cdk/lib/constructs/ec2'; import { GuS3Bucket } from '@guardian/cdk/lib/constructs/s3'; -import { GuardianAwsAccounts } from '@guardian/private-infrastructure-config'; +import { + GuardianAwsAccounts, + GuardianPrivateNetworks, +} from '@guardian/private-infrastructure-config'; import type { App } from 'aws-cdk-lib'; import { ArnFormat, Duration } from 'aws-cdk-lib'; import { InstanceClass, InstanceSize, InstanceType, + Peer, Port, } from 'aws-cdk-lib/aws-ec2'; import { Secret } from 'aws-cdk-lib/aws-ecs'; @@ -69,6 +73,11 @@ export class CloudQuery extends GuStack { const port = 5432; + const dbSecurityGroup = new GuSecurityGroup(this, 'PostgresSecurityGroup', { + app, + vpc, + }); + const dbProps: DatabaseInstanceProps = { engine: DatabaseInstanceEngine.POSTGRES, port, @@ -77,6 +86,7 @@ export class CloudQuery extends GuStack { iamAuthentication: true, instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.SMALL), storageEncrypted: true, + securityGroups: [dbSecurityGroup], }; const db = new DatabaseInstance(this, 'PostgresInstance1', dbProps); @@ -87,6 +97,18 @@ export class CloudQuery extends GuStack { { app, vpc }, ); + // TODO use a bastion host here instead? https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.BastionHostLinux.html + dbSecurityGroup.addIngressRule( + Peer.ipv4(GuardianPrivateNetworks.Engineering), + Port.tcp(port), + 'Allow connection to Postgres from the office network.', + ); + + dbSecurityGroup.connections.allowFrom( + applicationToPostgresSecurityGroup, + Port.tcp(port), + ); + // Used by downstream services that read CloudQuery data, namely Grafana. new StringParameter(this, 'PostgresAccessSecurityGroupParam', { parameterName: `/${stage}/${stack}/${app}/postgres-access-security-group`, @@ -103,11 +125,6 @@ export class CloudQuery extends GuStack { dataType: ParameterDataType.TEXT, }); - db.connections.allowFrom( - applicationToPostgresSecurityGroup, - Port.tcp(port), - ); - const readonlyPolicy = readonlyAccessManagedPolicy( this, 'readonly-managed-policy',