From ab3958ea6f4563551ca2230623e35eb54da6bd27 Mon Sep 17 00:00:00 2001 From: Prudhvi Godithi Date: Sat, 28 Sep 2024 19:46:00 -0700 Subject: [PATCH] Resources to lauch GitHub app Signed-off-by: Prudhvi Godithi --- DEVELOPER_GUIDE.md | 1 + infrastructure/lib/infrastructure-stack.ts | 19 ++- infrastructure/lib/stacks/gitHubApp.ts | 138 ++++++++++++++++++++ infrastructure/test/gitHubApp-stack.test.ts | 68 ++++++++++ 4 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 infrastructure/lib/stacks/gitHubApp.ts create mode 100644 infrastructure/test/gitHubApp-stack.test.ts diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index ef0662c..8f8a1f8 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -22,6 +22,7 @@ So you want to contribute code to this project? Excellent! We're glad you're her - `cdk deploy OpenSearchMetricsNginxReadonly`: To deploy the dashboard read only setup. - `cdk deploy OpenSearchWAF`: To deploy the AWS WAF for the project ALB's. - `cdk deploy OpenSearchMetrics-Monitoring`: To deploy the alerting stack which will monitor the step functions and URL of the project coming from [METRICS_HOSTED_ZONE](https://github.com/opensearch-project/opensearch-metrics/blob/main/infrastructure/lib/enums/project.ts) + - `cdk deploy OpenSearchMetrics-GitHubApp`: Create the resources which launches the GitHub App. Listens to GitHub events and index the data to Metrics cluster. ### Forking and Cloning diff --git a/infrastructure/lib/infrastructure-stack.ts b/infrastructure/lib/infrastructure-stack.ts index 34b2ea5..93f580c 100644 --- a/infrastructure/lib/infrastructure-stack.ts +++ b/infrastructure/lib/infrastructure-stack.ts @@ -19,6 +19,7 @@ import { OpenSearchWAF } from "./stacks/waf"; import { OpenSearchMetricsNginxCognito } from "./constructs/opensearchNginxProxyCognito"; import { OpenSearchMetricsMonitoringStack } from "./stacks/monitoringDashboard"; import { OpenSearchMetricsSecretsStack } from "./stacks/secrets"; +import {GitHubApp} from "./stacks/gitHubApp"; // import * as sqs from 'aws-cdk-lib/aws-sqs'; export class InfrastructureStack extends Stack { @@ -29,6 +30,21 @@ export class InfrastructureStack extends Stack { // Create VPC for the entire setup const vpcStack = new VpcStack(app, "OpenSearchHealth-VPC", {}); + // Create secret related to Github App + const openSearchMetricsGitHubAppSecretStack = new OpenSearchMetricsSecretsStack(app, "OpenSearchMetrics-GitHubAppSecret", { + secretName: 'github-app-creds' + }); + + // Create resources to launch the Github App + const gitHubApp = new GitHubApp(app, "OpenSearchMetrics-GitHubApp", { + vpc: vpcStack.vpc, + region: Project.REGION, + account: Project.AWS_ACCOUNT, + ami: Project.EC2_AMI_SSM.toString(), + secret: openSearchMetricsGitHubAppSecretStack.secret + }) + gitHubApp.node.addDependency(vpcStack, openSearchMetricsGitHubAppSecretStack); + // Create OpenSearch Domain, roles, permissions, cognito setup, cross account OpenSearch access for jenkins const openSearchDomainStack = new OpenSearchDomainStack(app, "OpenSearchHealth-OpenSearch", { @@ -50,8 +66,7 @@ export class InfrastructureStack extends Stack { }) openSearchMetricsWorkflowStack.node.addDependency(vpcStack, openSearchDomainStack); - // Create Secrets Manager - + // Create Secret Manager for the metrics project const openSearchMetricsSecretsStack = new OpenSearchMetricsSecretsStack(app, "OpenSearchMetrics-Secrets", { secretName: 'metrics-creds' }); diff --git a/infrastructure/lib/stacks/gitHubApp.ts b/infrastructure/lib/stacks/gitHubApp.ts new file mode 100644 index 0000000..525b16b --- /dev/null +++ b/infrastructure/lib/stacks/gitHubApp.ts @@ -0,0 +1,138 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +import {Aspects, Duration, Stack, Tag, Tags} from 'aws-cdk-lib'; +import { + AutoScalingGroup, + BlockDeviceVolume, + CfnLaunchConfiguration, + HealthCheck, + UpdatePolicy +} from 'aws-cdk-lib/aws-autoscaling'; +import { + InstanceClass, + InstanceSize, + InstanceType, + MachineImage, + SubnetType, + Vpc +} from 'aws-cdk-lib/aws-ec2'; +import { + ArnPrincipal, + CompositePrincipal, + Effect, + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; +import {OpenSearchMetricsSecretsStack} from "./secrets"; +import {Secret} from "aws-cdk-lib/aws-secretsmanager"; + + +export interface GitHubAppProps { + readonly vpc: Vpc; + readonly region: string; + readonly account: string; + readonly ami?: string + readonly secret: Secret; +} + + +export class GitHubApp extends Stack { + + readonly asg: AutoScalingGroup; + readonly githubAppRole: Role; + + constructor(scope: Construct, id: string, props: GitHubAppProps) { + super(scope, id); + + const instanceRole = this.createInstanceRole(props.secret.secretArn, props.account); + this.githubAppRole = instanceRole; + + this.asg = new AutoScalingGroup(this, 'OpenSearchMetrics-GitHubAppAsg', { + instanceType: InstanceType.of(InstanceClass.M5, InstanceSize.LARGE), + blockDevices: [{ deviceName: '/dev/xvda', volume: BlockDeviceVolume.ebs(10) }], + healthCheck: HealthCheck.ec2({ grace: Duration.seconds(90) }), + machineImage: props && props.ami ? + MachineImage.fromSsmParameter(props.ami) : + MachineImage.latestAmazonLinux2(), + associatePublicIpAddress: false, + allowAllOutbound: true, + desiredCapacity: 1, + minCapacity: 1, + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PRIVATE_WITH_EGRESS + }, + role: instanceRole, + updatePolicy: UpdatePolicy.replacingUpdate() + }); + Tags.of(this.asg).add("Name", "OpenSearchMetrics-GitHubApp") + + + const launchConfiguration = this.asg.node.findChild('LaunchConfig') as CfnLaunchConfiguration; + launchConfiguration.metadataOptions = { + httpPutResponseHopLimit: 2, + httpEndpoint: "enabled", + httpTokens: "required" + }; + + const instanceName = 'OpenSearchMetrics-GitHubAppHost'; + Aspects.of(this.asg).add(new Tag('name', instanceName, { + applyToLaunchedInstances: true, + includeResourceTypes: ['AWS::AutoScaling::AutoScalingGroup'] + }),); + this.asg.addUserData(...this.getUserData(props.secret.secretName)); + } + + private createInstanceRole(secretArn: string, account: string): Role { + const role = new Role(this, "OpenSearchMetrics-GitHubAppRole", { + assumedBy: new CompositePrincipal( + new ServicePrincipal('ec2.amazonaws.com'), + new ArnPrincipal(`arn:aws:iam::${account}:role/OpenSearchMetrics-GitHubAppRole`) + ), + roleName: "OpenSearchMetrics-GitHubAppRole", + }); + role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')); + role.addToPolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["secretsmanager:GetSecretValue"], + resources: [secretArn], + }), + ); + role.addToPolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["sts:AssumeRole"], + resources: [role.roleArn], + }), + ); + return role; + } + + + private getUserData(secretName: string): string[] { + return [ + 'sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm', + 'sudo dnf update -y', + 'sudo yum install git docker -y', + 'sudo systemctl enable docker', + 'sudo systemctl start docker', + 'sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/sbin/docker-compose', + 'sudo chmod a+x /usr/local/sbin/docker-compose', + 'git clone https://github.com/opensearch-project/automation-app.git', + `aws secretsmanager get-secret-value --secret-id ${secretName} --query SecretString --output text >> automation-app/.env`, + 'cd automation-app/docker', + 'PORT=8080 OPERATION_CONFIG=configs/operations/github-merged-pulls-monitor.yml docker-compose -p automation-app-1 up -d', + 'PORT=8081 OPERATION_CONFIG=configs/operations/github-workflow-runs-monitor.yml docker-compose -p automation-app-2 up -d' + ]; + } +} diff --git a/infrastructure/test/gitHubApp-stack.test.ts b/infrastructure/test/gitHubApp-stack.test.ts new file mode 100644 index 0000000..566002b --- /dev/null +++ b/infrastructure/test/gitHubApp-stack.test.ts @@ -0,0 +1,68 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +import { App } from "aws-cdk-lib"; +import {Match, Template} from "aws-cdk-lib/assertions"; +import Project from "../lib/enums/project"; +import { VpcStack } from "../lib/stacks/vpc"; +import {GitHubApp} from "../lib/stacks/gitHubApp"; +import {OpenSearchMetricsSecretsStack} from "../lib/stacks/secrets"; + + +test('OpenSearch GitHub App Stack test ', () => { + const app = new App(); + const vpcStack = new VpcStack(app, "Test-OpenSearchHealth-VPC", {}); + const openSearchMetricsGitHubAppSecretStack = new OpenSearchMetricsSecretsStack(app, "Test-OpenSearchMetrics-GitHubAppSecret", { + secretName: 'test-github-app-creds' + }); + const gitHubApp = new GitHubApp(app, "Test-OpenSearchMetrics-GitHubApp", { + vpc: vpcStack.vpc, + region: Project.REGION, + account: Project.AWS_ACCOUNT, + ami: Project.EC2_AMI_SSM.toString(), + secret: openSearchMetricsGitHubAppSecretStack.secret + }); + + const template = Template.fromStack(gitHubApp); + template.resourceCountIs('AWS::IAM::Role', 1); + template.hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: Match.arrayWith([ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + Service: "ec2.amazonaws.com" + } + } + ]) + } + }); + template.hasResourceProperties('AWS::EC2::SecurityGroup', { + GroupDescription: Match.stringLikeRegexp('Test-OpenSearchMetrics-GitHubApp/OpenSearchMetrics-GitHubAppAsg/InstanceSecurityGroup') + }); + template.resourceCountIs('AWS::IAM::Policy', 1); + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: "secretsmanager:GetSecretValue", + Effect: "Allow", + Resource: { + "Fn::ImportValue": Match.stringLikeRegexp('Test-OpenSearchMetrics-GitHubAppSecret:ExportsOutputRefMetricsCreds.*') + } + }) + ]) + } + }); + template.hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { + DesiredCapacity: "1", + MaxSize: "1", + MinSize: "1", + }) +});