From 33b7d73c146553be03f3d77de81fc246ebf78ba0 Mon Sep 17 00:00:00 2001 From: akash1810 Date: Wed, 14 Jun 2023 13:21:06 +0100 Subject: [PATCH] feat: Allow access to Postgres from the office (and VPN) Add an ingress rule to the Postgres security group, allowing connection from the Engineering subnet. This means we can connect to the database from the office network, as it is sometimes easier to run queries outside of Grafana. --- .../lib/__snapshots__/cloudquery.test.ts.snap | 110 ++++++++++-------- packages/cdk/lib/cloudquery.ts | 29 ++++- 2 files changed, 85 insertions(+), 54 deletions(-) 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',