From d61e7b6c65f44be231ec21adc4c9560ee7bd883a Mon Sep 17 00:00:00 2001 From: Prudhvi Godithi Date: Wed, 3 Apr 2024 15:30:19 -0700 Subject: [PATCH 1/3] Add workflow to test CDK code Signed-off-by: Prudhvi Godithi --- .github/workflows/cdk-ci-test.yml | 21 +++++++++++++++++++ .../{ci-test.yml => java-ci-test.yml} | 0 infrastructure/test/vpc-stack.test.ts | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/cdk-ci-test.yml rename .github/workflows/{ci-test.yml => java-ci-test.yml} (100%) diff --git a/.github/workflows/cdk-ci-test.yml b/.github/workflows/cdk-ci-test.yml new file mode 100644 index 0000000..7b8ee8b --- /dev/null +++ b/.github/workflows/cdk-ci-test.yml @@ -0,0 +1,21 @@ +name: CDK Test + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v2.4.1 + with: + node-version: '20.8.0' + + - name: Run CDK Test + run: | + cd infrastructure + npm install + npm test diff --git a/.github/workflows/ci-test.yml b/.github/workflows/java-ci-test.yml similarity index 100% rename from .github/workflows/ci-test.yml rename to .github/workflows/java-ci-test.yml diff --git a/infrastructure/test/vpc-stack.test.ts b/infrastructure/test/vpc-stack.test.ts index b5d3933..a06edfa 100644 --- a/infrastructure/test/vpc-stack.test.ts +++ b/infrastructure/test/vpc-stack.test.ts @@ -14,4 +14,4 @@ test('VPC Stack Test', () => { const vpcStackTemplate = Template.fromStack(vpcStack); vpcStackTemplate.resourceCountIs('AWS::EC2::VPC', 1); vpcStackTemplate.resourceCountIs('AWS::EC2::Subnet', 4); -}); \ No newline at end of file +}); From 72bd700fbd83fabc23903250d3c0ef28c4ebef7a Mon Sep 17 00:00:00 2001 From: Prudhvi Godithi Date: Thu, 4 Apr 2024 13:59:26 -0700 Subject: [PATCH 2/3] Update CDK code Signed-off-by: Prudhvi Godithi --- infrastructure/lib/constructs/opensearchCognito.ts | 10 +++++----- .../lib/constructs/opensearchNginxProxyCognito.ts | 6 ++---- infrastructure/lib/stacks/opensearch.ts | 5 +++-- .../lib/stacks/opensearchNginxProxyReadonly.ts | 6 ++---- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/infrastructure/lib/constructs/opensearchCognito.ts b/infrastructure/lib/constructs/opensearchCognito.ts index 6b4cff5..721a6b6 100644 --- a/infrastructure/lib/constructs/opensearchCognito.ts +++ b/infrastructure/lib/constructs/opensearchCognito.ts @@ -4,7 +4,7 @@ import { Effect, FederatedPrincipal, ManagedPolicy, PolicyStatement, Role, Servi import * as cognito from "aws-cdk-lib/aws-cognito"; export interface OpenSearchMetricsCognitoProps { - readonly region: string; + readonly openSearchDomainArn: string; } export class OpenSearchMetricsCognito extends Construct { @@ -99,16 +99,16 @@ export class OpenSearchMetricsCognito extends Construct { this.identityPoolAuthRole.addToPolicy( new PolicyStatement({ effect: Effect.ALLOW, - actions: ['mobileanalytics:PutEvents', 'cognito-sync:*', 'cognito-identity:*', 'es:ESHttp*'], - resources: ['*'], + actions: ["es:ESHttpGet", "es:ESHttpPost"], + resources: [`${props.openSearchDomainArn}`], }), ); this.identityPoolAdminRole.addToPolicy( new PolicyStatement({ effect: Effect.ALLOW, - actions: ['mobileanalytics:PutEvents', 'cognito-sync:*', 'cognito-identity:*', 'es:ESHttp*'], - resources: ['*'], + actions: ["es:ESHttp*", ], + resources: [`${props.openSearchDomainArn}`], }), ); diff --git a/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts b/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts index 5739b54..5282030 100644 --- a/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts +++ b/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts @@ -15,7 +15,7 @@ import { SubnetType, Vpc, AmazonLinuxGeneration, - AmazonLinuxImage + AmazonLinuxImage, MachineImage } from 'aws-cdk-lib/aws-ec2'; import { Effect, ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; import {Aspects, CfnOutput, Duration, Tag, Tags} from 'aws-cdk-lib'; @@ -62,9 +62,7 @@ export class OpenSearchMetricsNginxCognito extends Construct { instanceType: InstanceType.of(InstanceClass.M5, InstanceSize.LARGE), blockDevices: [{ deviceName: '/dev/xvda', volume: BlockDeviceVolume.ebs(10) }], // GB healthCheck: HealthCheck.ec2({ grace: Duration.seconds(90) }), - machineImage: new AmazonLinuxImage({ - generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - }), + machineImage: MachineImage.latestAmazonLinux2(), // Temp added public subnet and IP, until backed up by ALB associatePublicIpAddress: true, allowAllOutbound: true, diff --git a/infrastructure/lib/stacks/opensearch.ts b/infrastructure/lib/stacks/opensearch.ts index a2c43a5..f9aee90 100644 --- a/infrastructure/lib/stacks/opensearch.ts +++ b/infrastructure/lib/stacks/opensearch.ts @@ -53,7 +53,7 @@ export class OpenSearchDomainStack extends Stack { new PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["sts:AssumeRole"], - resources: ['*'], + resources: [`arn:aws:iam::${props.account}:role/OpenSearchFullAccessRole`], conditions: { StringEquals: { 'aws:PrincipalAccount': props.account, 'aws:RequestedRegion': props.region,}, } @@ -80,6 +80,7 @@ export class OpenSearchDomainStack extends Stack { this.fullAccessRole = new Role(this, 'OpenSearchFullAccessRole', { assumedBy: new CompositePrincipal(...secureRolesList.map((role) => new iam.ArnPrincipal(role.roleArn))), description: "Master role for OpenSearch full access", + // The Name used in openSearchLambdaRole roleName: "OpenSearchFullAccessRole", inlinePolicies: { "opensearchFullAccess": new PolicyDocument({ @@ -95,7 +96,7 @@ export class OpenSearchDomainStack extends Stack { }); const metricsCognito = new OpenSearchMetricsCognito(this, "OpenSearchHealthCognito", { - region: props.region, + openSearchDomainArn: domainArn }); diff --git a/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts b/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts index b7626e3..4b0f00d 100644 --- a/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts +++ b/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts @@ -9,7 +9,7 @@ import { SubnetType, Vpc, AmazonLinuxGeneration, - AmazonLinuxImage + AmazonLinuxImage, MachineImage } from 'aws-cdk-lib/aws-ec2'; import * as iam from "aws-cdk-lib/aws-iam"; import {Aspects, Duration, Stack, Tag, Tags} from 'aws-cdk-lib'; @@ -60,9 +60,7 @@ export class OpenSearchMetricsNginxReadonly extends Stack { instanceType: InstanceType.of(InstanceClass.M5, InstanceSize.LARGE), blockDevices: [{ deviceName: '/dev/xvda', volume: BlockDeviceVolume.ebs(10) }], // GB healthCheck: HealthCheck.ec2({ grace: Duration.seconds(90) }), - machineImage: new AmazonLinuxImage({ - generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - }), + machineImage: MachineImage.latestAmazonLinux2(), associatePublicIpAddress: false, allowAllOutbound: true, desiredCapacity: 2, From cdf01099fe21b14bf940db7ffe7e9d296106795b Mon Sep 17 00:00:00 2001 From: Prudhvi Godithi Date: Mon, 8 Apr 2024 12:41:32 -0700 Subject: [PATCH 3/3] CDK update: Add endpoint for Cognito and enable WAF Signed-off-by: Prudhvi Godithi --- .../constructs/opensearchNginxProxyCognito.ts | 32 ++++++++++++++----- infrastructure/lib/enums/project.ts | 2 +- infrastructure/lib/infrastructure-stack.ts | 12 ++++++- infrastructure/lib/stacks/opensearch.ts | 16 ++++++++-- .../stacks/opensearchNginxProxyReadonly.ts | 17 ++++++---- infrastructure/lib/stacks/waf.ts | 13 +++++--- 6 files changed, 69 insertions(+), 23 deletions(-) diff --git a/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts b/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts index 5282030..47089f1 100644 --- a/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts +++ b/infrastructure/lib/constructs/opensearchNginxProxyCognito.ts @@ -49,6 +49,9 @@ export interface opensearchDashboardUrlProps { } export class OpenSearchMetricsNginxCognito extends Construct { + + static readonly COGNITO_ALB_ARN: string = 'cognitoAlbArn'; + readonly asg: AutoScalingGroup; constructor(scope: Construct, id: string, props: NginxProps) { @@ -64,28 +67,37 @@ export class OpenSearchMetricsNginxCognito extends Construct { healthCheck: HealthCheck.ec2({ grace: Duration.seconds(90) }), machineImage: MachineImage.latestAmazonLinux2(), // Temp added public subnet and IP, until backed up by ALB - associatePublicIpAddress: true, + associatePublicIpAddress: false, allowAllOutbound: true, desiredCapacity: 1, minCapacity: 1, vpc: props.vpc, vpcSubnets: { - // Temp added public subnet and IP, until backed up by ALB - // subnetType: SubnetType.PUBLIC, - subnetType: SubnetType.PUBLIC + subnetType: SubnetType.PRIVATE_WITH_EGRESS }, role: instanceRole, - // Actually update the existing instance instead of leaving it running. This will build a new ASG - // and then destroy the old one in order to maintain availability updatePolicy: UpdatePolicy.replacingUpdate() }); Tags.of(this.asg).add("Name", "OpenSearchMetricsCognito") if (props.albProps) { + + const albSecurityGroup = new SecurityGroup(this, 'ALBSecurityGroup', { + vpc, + allowAllOutbound: true, + }); + albSecurityGroup.addIngressRule(Peer.prefixList(Project.RESTRICTED_PREFIX), Port.tcp(443)); + const openSearchCognitoApplicationLoadBalancer = new ApplicationLoadBalancer(this, `OpenSearchMetricsCognito-NginxProxyAlb`, { loadBalancerName: "OpenSearchMetricsCognito", vpc: vpc, - internetFacing: true + internetFacing: true, + securityGroup: albSecurityGroup + }); + + new CfnOutput(this, 'cognitoAlbArn', { + value: openSearchCognitoApplicationLoadBalancer.loadBalancerArn, + exportName: OpenSearchMetricsNginxCognito.COGNITO_ALB_ARN, }); const listenerCertificate = ListenerCertificate.fromArn(props.albProps.certificateArn); @@ -99,13 +111,17 @@ export class OpenSearchMetricsNginxCognito extends Construct { listener.addTargets(`OpenSearchMetricsCognito-NginxProxyAlbTarget`, { port: 443, protocol: ApplicationProtocol.HTTPS, + healthCheck: { + port: '80', + path: '/', + }, targets: [this.asg] }); const aRecord = new ARecord(this, "OpenSearchMetricsCognito-DNS", { zone: props.albProps.hostedZone.zone, - recordName: Project.METRICS_HOSTED_ZONE, + recordName: Project.METRICS_COGNITO_HOSTED_ZONE, target: RecordTarget.fromAlias(new LoadBalancerTarget(openSearchCognitoApplicationLoadBalancer)), }); } diff --git a/infrastructure/lib/enums/project.ts b/infrastructure/lib/enums/project.ts index cdcd26f..27f119f 100644 --- a/infrastructure/lib/enums/project.ts +++ b/infrastructure/lib/enums/project.ts @@ -4,7 +4,7 @@ enum Project{ JENKINS_AGENT_ROLE = '', REGION = '', METRICS_HOSTED_ZONE = 'metrics.opensearch.org', - // Temp until the project is public + METRICS_COGNITO_HOSTED_ZONE = 'metrics.login.opensearch.org', RESTRICTED_PREFIX = '', LAMBDA_PACKAGE = 'opensearch-metrics-1.0.zip', } diff --git a/infrastructure/lib/infrastructure-stack.ts b/infrastructure/lib/infrastructure-stack.ts index 0c438a0..5808026 100644 --- a/infrastructure/lib/infrastructure-stack.ts +++ b/infrastructure/lib/infrastructure-stack.ts @@ -1,4 +1,4 @@ -import {App, CfnOutput, Stack, StackProps} from 'aws-cdk-lib'; +import {App, Fn, Stack, StackProps} from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { VpcStack } from "./stacks/vpc"; import {jenkinsAccess, OpenSearchDomainStack} from "./stacks/opensearch"; @@ -7,6 +7,8 @@ import {OpenSearchHealthRoute53} from "./stacks/route53"; import {OpenSearchMetricsWorkflowStack} from "./stacks/metricsWorkflow"; import {OpenSearchMetricsNginxReadonly} from "./stacks/opensearchNginxProxyReadonly"; import {ArnPrincipal, IPrincipal} from "aws-cdk-lib/aws-iam"; +import {OpenSearchWAF} from "./stacks/waf"; +import {OpenSearchMetricsNginxCognito} from "./constructs/opensearchNginxProxyCognito"; // import * as sqs from 'aws-cdk-lib/aws-sqs'; export class InfrastructureStack extends Stack { @@ -62,5 +64,13 @@ export class InfrastructureStack extends Stack { }); openSearchMetricsNginxReadonly.node.addDependency(vpcStack, openSearchDomainStack); + // Create an OpenSearch WAF stack + const openSearchWAF = new OpenSearchWAF(app, "OpenSearchWAF", { + readOnlyLoadBalancerArn: Fn.importValue(`${OpenSearchMetricsNginxReadonly.READONLY_ALB_ARN}`), + cognitoLoadBalancerArn: Fn.importValue(`${OpenSearchMetricsNginxCognito.COGNITO_ALB_ARN}`), + appName: "OpenSearchMetricsWAF" + }); + openSearchWAF.node.addDependency(openSearchDomainStack, openSearchMetricsNginxReadonly); + } } diff --git a/infrastructure/lib/stacks/opensearch.ts b/infrastructure/lib/stacks/opensearch.ts index f9aee90..0c4e52b 100644 --- a/infrastructure/lib/stacks/opensearch.ts +++ b/infrastructure/lib/stacks/opensearch.ts @@ -6,7 +6,11 @@ import * as iam from "aws-cdk-lib/aws-iam"; import { ArnPrincipal, CompositePrincipal, Effect, IRole, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { VpcStack } from "./vpc"; import {OpenSearchMetricsCognito} from "../constructs/opensearchCognito"; -import {OpenSearchMetricsNginxCognito} from "../constructs/opensearchNginxProxyCognito";; +import {OpenSearchMetricsNginxCognito} from "../constructs/opensearchNginxProxyCognito"; +import {OpenSearchHealthRoute53} from "./route53"; +import Project from "../enums/project"; + +; export interface OpenSearchStackProps { @@ -176,6 +180,10 @@ export class OpenSearchDomainStack extends Stack { this.domain.node.addDependency(serviceLinkedRole); if(props.enableNginxCognito) { + const metricsHostedZone = new OpenSearchHealthRoute53(this, "OpenSearchMetricsCognito-HostedZone", { + hostedZone: Project.METRICS_COGNITO_HOSTED_ZONE, + appName: "OpenSearchMetricsCognito" + }); new OpenSearchMetricsNginxCognito(this, "OpenSearchMetricsNginx", { region: this.props.region, vpc: props.vpcStack.vpc, @@ -183,7 +191,11 @@ export class OpenSearchDomainStack extends Stack { opensearchDashboardUrlProps: { opensearchDashboardVpcUrl: this.domain.domainEndpoint, cognitoDomain: metricsCognito.userPoolDomain.domain - } + }, + albProps: { + hostedZone: metricsHostedZone, + certificateArn: metricsHostedZone.certificateArn, + }, }); } } diff --git a/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts b/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts index 4b0f00d..e5ef306 100644 --- a/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts +++ b/infrastructure/lib/stacks/opensearchNginxProxyReadonly.ts @@ -12,7 +12,7 @@ import { AmazonLinuxImage, MachineImage } from 'aws-cdk-lib/aws-ec2'; import * as iam from "aws-cdk-lib/aws-iam"; -import {Aspects, Duration, Stack, Tag, Tags} from 'aws-cdk-lib'; +import {Aspects, CfnOutput, Duration, Stack, Tag, Tags} from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { ApplicationLoadBalancer, ApplicationProtocol, @@ -46,6 +46,9 @@ export interface opensearchDashboardUrlProps { } export class OpenSearchMetricsNginxReadonly extends Stack { + + static readonly READONLY_ALB_ARN: string = 'readOnlyAlbArn'; + readonly asg: AutoScalingGroup; constructor(scope: Construct, id: string, props: NginxProps) { @@ -54,8 +57,6 @@ export class OpenSearchMetricsNginxReadonly extends Stack { super(scope, id); const instanceRole = this.createNginxReadonlyInstanceRole(props); - - this.asg = new AutoScalingGroup(this, 'OpenSearchMetricsReadonly-MetricsProxyAsg', { instanceType: InstanceType.of(InstanceClass.M5, InstanceSize.LARGE), blockDevices: [{ deviceName: '/dev/xvda', volume: BlockDeviceVolume.ebs(10) }], // GB @@ -88,11 +89,13 @@ export class OpenSearchMetricsNginxReadonly extends Stack { securityGroup: albSecurityGroup }); - const openSearchWAF = new OpenSearchWAF(this, "OpenSearchWAF", { - loadBalancer: openSearchApplicationLoadBalancer, - appName: "OpenSearchMetricsWAF" + new CfnOutput(this, 'readOnlyAlbArn', { + value: openSearchApplicationLoadBalancer.loadBalancerArn, + exportName: OpenSearchMetricsNginxReadonly.READONLY_ALB_ARN, }); - openSearchWAF.node.addDependency(openSearchApplicationLoadBalancer) + + //const importedArnSecretBucketValue = Fn.importValue(`${CIConfigStack.CERTIFICATE_ARN_SECRET_EXPORT_VALUE}`); + const listenerCertificate = ListenerCertificate.fromArn(props.albProps.certificateArn); diff --git a/infrastructure/lib/stacks/waf.ts b/infrastructure/lib/stacks/waf.ts index 4c5d63c..4d2754f 100644 --- a/infrastructure/lib/stacks/waf.ts +++ b/infrastructure/lib/stacks/waf.ts @@ -105,16 +105,21 @@ export class WebACLAssociation extends CfnWebACLAssociation { } export interface WafProps extends StackProps{ - loadBalancer: ApplicationLoadBalancer, + readOnlyLoadBalancerArn: string, + cognitoLoadBalancerArn: string appName: string } -export class OpenSearchWAF extends Construct { +export class OpenSearchWAF extends Stack { constructor(scope: Construct, id: string, props: WafProps) { super(scope, id); const waf = new WAF(this, `${props.appName}-WAFv2`); - new WebACLAssociation(this, 'wafALBassociation', { - resourceArn: props.loadBalancer.loadBalancerArn, + new WebACLAssociation(this, 'wafReadOnlyALBassociation', { + resourceArn: props.readOnlyLoadBalancerArn, + webAclArn: waf.attrArn, + }); + new WebACLAssociation(this, 'wafCognitoALBassociation', { + resourceArn: props.cognitoLoadBalancerArn, webAclArn: waf.attrArn, }); }