diff --git a/cicd/1-setup/cicd-dependencies.template.yml b/cicd/1-setup/cicd-dependencies.template.yml index 5744157..69420b9 100644 --- a/cicd/1-setup/cicd-dependencies.template.yml +++ b/cicd/1-setup/cicd-dependencies.template.yml @@ -27,7 +27,6 @@ Resources: - codepipeline.amazonaws.com Version: '2012-10-17' Path: /service-role/ - PermissionsBoundary: !ImportValue IAM-DevPermissions Policies: - PolicyName: AiProxyPassRole PolicyDocument: @@ -41,9 +40,10 @@ Resources: - Effect: Allow Action: - "cloudformation:DescribeStacks" + - "cloudformation:DescribeStackEvents" - "cloudformation:CreateStack" - "cloudformation:UpdateStack" - Resource: "*" + Resource: "*" # TODO scope to specific stacks - PolicyName: CodeBuildResourcesAccess PolicyDocument: Statement: @@ -68,11 +68,10 @@ Resources: - s3:GetObjectVersion Resource: - !Sub ${ArtifactStore.Arn}/* - # TODO: Scope to specific ECR Repos? - Effect: Allow Action: - ecr:GetAuthorizationToken - Resource: '*' + Resource: '*' # TODO scope to specific ECR repos - Effect: Allow Action: codestar-connections:UseConnection Resource: @@ -103,7 +102,6 @@ Resources: - codebuild.amazonaws.com Version: '2012-10-17' Path: /service-role/ - PermissionsBoundary: !ImportValue IAM-DevPermissions Policies: - PolicyName: PublicCodeBuildSecretsAccess PolicyDocument: diff --git a/cicd/1-setup/deploy-cicd-dependencies.sh b/cicd/1-setup/deploy-cicd-dependencies.sh index 735e57f..d02e358 100755 --- a/cicd/1-setup/deploy-cicd-dependencies.sh +++ b/cicd/1-setup/deploy-cicd-dependencies.sh @@ -11,7 +11,7 @@ TEMPLATE_FILE=cicd/1-setup/cicd-dependencies.template.yml echo Validating cloudformation template... aws cloudformation validate-template \ --template-body file://${TEMPLATE_FILE} \ - | cat + > /dev/null ACCOUNT=$(aws sts get-caller-identity --query "Account" --output text) diff --git a/cicd/2-cicd/cicd.template.yml b/cicd/2-cicd/cicd.template.yml index 3262960..eb800d9 100644 --- a/cicd/2-cicd/cicd.template.yml +++ b/cicd/2-cicd/cicd.template.yml @@ -40,8 +40,11 @@ Conditions: Resources: - # The Elastic Container Registry Repository will store our built docker - # images. + #------------------------------------- + # Elastic Container Registry (ECR) + # - Store built docker images + #------------------------------------- + EcrRepository: Type: AWS::ECR::Repository Properties: @@ -113,6 +116,10 @@ Resources: - 'kms:GenerateDataKeyWithoutPlaintext' Resource: '*' + #------------------------------------- + # CodeBuild Projects + #------------------------------------- + # The CodeBuild Project is triggered by pull requests targeting $GitHubBranch # It will perform any steps defined in the pr-buildspec.yml file. PullRequestBuildProject: @@ -191,32 +198,9 @@ Resources: Artifacts: Type: CODEPIPELINE - # Grant the AiProxy CodeBuild Role additional permissions for resources in - # this template. This allows us to avoid granting permission to * resources. - AiProxyRolePolicy: - Type: 'AWS::IAM::Policy' - Properties: - PolicyName: !Sub "${AWS::StackName}-codebuild-policy" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - codebuild:* - Resource: - - !GetAtt AppBuildProject.Arn - - !GetAtt IntegrationTestBuildProject.Arn - - Effect: Allow - Action: - - codebuild:CreateReportGroup - - codebuild:CreateReport - - codebuild:UpdateReport - - codebuild:BatchPutTestCases - - codebuild:BatchPutCodeCoverage - Resource: - - !Sub arn:aws:codebuild:us-east-1:165336972514:report-group/${AWS::StackName}-${GitHubBranch}-pr-build - Roles: - - !ImportValue AiProxyCodeBuildRoleName + #------------------------------------- + # Pipeline + #------------------------------------- Pipeline: Type: AWS::CodePipeline::Pipeline @@ -278,11 +262,25 @@ Resources: ActionMode: CREATE_UPDATE TemplatePath: appBuildResults::packaged-app-template.yml TemplateConfiguration: appBuildResults::cicd/3-app/aiproxy/dev.config.json + # ParameterOverrides must be a JSON string, not an object ParameterOverrides: !Join - - '' - - - '{ "SubdomainName": "' - - !Sub "aiproxy-dev-${GitHubBranch}" - - '" }' + - '' + - - '{' + - !Sub '"SubdomainName": "aiproxy-dev-${GitHubBranch}",' + # - !Sub '"ECRRepositoryArn": "${EcrRepository.Arn}",' + - !Sub + - '"VPC": "${VPCValue}",' + - { VPCValue: !ImportValue VPC } + - !Sub + - '"SecurityGroup": "${SecurityGroupValue}",' + - { SecurityGroupValue: !ImportValue VPC-ELBSecurityGroup } + - !Sub + - '"PublicSubnets": "${PublicSubnetsValue}",' + - { PublicSubnetsValue: !Join [",", [!ImportValue VPC-PublicSubnetB, !ImportValue VPC-PublicSubnetC, !ImportValue VPC-PublicSubnetD, !ImportValue VPC-PublicSubnetE]] } + - !Sub + - '"PrivateSubnets": "${PrivateSubnetsValue}"' + - { PrivateSubnetsValue: !Join [",", [!ImportValue VPC-SubnetB, !ImportValue VPC-SubnetC, !ImportValue VPC-SubnetD, !ImportValue VPC-SubnetE]] } + - '}' Capabilities: CAPABILITY_AUTO_EXPAND,CAPABILITY_IAM RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/admin/CloudFormationService - !Ref AWS::NoValue @@ -304,11 +302,27 @@ Resources: ActionMode: CREATE_UPDATE TemplatePath: appBuildResults::packaged-app-template.yml TemplateConfiguration: appBuildResults::cicd/3-app/aiproxy/test.config.json + # ParameterOverrides must be a JSON string, not an object ParameterOverrides: !Join - '' - - - '{ "SubdomainName": "' - - !If [ TargetsMainBranch, 'aiproxy-test', !Sub 'aiproxy-test-${GitHubBranch}' ] - - '" }' + - - '{' + - !Sub + - '"SubdomainName": "${SubdomainName}",' + - { SubdomainName: !If [ TargetsMainBranch, 'aiproxy-test', !Sub 'aiproxy-test-${GitHubBranch}' ] } + - !Sub '"ECRRepositoryArn": "${EcrRepository.Arn}",' + - !Sub + - '"VPC": "${VPCValue}",' + - { VPCValue: !ImportValue VPC } + - !Sub + - '"SecurityGroup": "${SecurityGroupValue}",' + - { SecurityGroupValue: !ImportValue VPC-ELBSecurityGroup } + - !Sub + - '"PublicSubnets": "[${PublicSubnetsValue}]",' + - { PublicSubnetsValue: !Join [",", [!ImportValue VPC-PublicSubnetB, !ImportValue VPC-PublicSubnetC, !ImportValue VPC-PublicSubnetD, !ImportValue VPC-PublicSubnetE]] } + - !Sub + - '"PrivateSubnets": "[${PrivateSubnetsValue}]"' + - { PrivateSubnetsValue: !Join [",", [!ImportValue VPC-SubnetB, !ImportValue VPC-SubnetC, !ImportValue VPC-SubnetD, !ImportValue VPC-SubnetE]] } + - '}' Capabilities: CAPABILITY_AUTO_EXPAND,CAPABILITY_IAM RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/admin/CloudFormationService - !Ref AWS::NoValue @@ -353,11 +367,27 @@ Resources: ActionMode: CREATE_UPDATE TemplatePath: appBuildResults::packaged-app-template.yml TemplateConfiguration: appBuildResults::cicd/3-app/aiproxy/production.config.json + # ParameterOverrides must be a JSON string, not an object ParameterOverrides: !Join - '' - - - '{ "SubdomainName": "' - - !If [ TargetsMainBranch, 'aiproxy', !Sub 'aiproxy-${GitHubBranch}' ] - - '" }' + - - '{' + - !Sub + - '"SubdomainName": "${SubdomainName}",' + - { SubdomainName: !If [ TargetsMainBranch, 'aiproxy', !Sub 'aiproxy-${GitHubBranch}' ] } + - !Sub '"ECRRepositoryArn": "${EcrRepository.Arn}",' + - !Sub + - '"VPC": "${VPCValue}",' + - { VPCValue: !ImportValue VPC } + - !Sub + - '"SecurityGroup": "${SecurityGroupValue}",' + - { SecurityGroupValue: !ImportValue VPC-ELBSecurityGroup } + - !Sub + - '"PublicSubnets": "[${PublicSubnetsValue}]",' + - { PublicSubnetsValue: !Join [",", [!ImportValue VPC-PublicSubnetB, !ImportValue VPC-PublicSubnetC, !ImportValue VPC-PublicSubnetD, !ImportValue VPC-PublicSubnetE]] } + - !Sub + - '"PrivateSubnets": "[${PrivateSubnetsValue}]"' + - { PrivateSubnetsValue: !Join [",", [!ImportValue VPC-SubnetB, !ImportValue VPC-SubnetC, !ImportValue VPC-SubnetD, !ImportValue VPC-SubnetE]] } + - '}' Capabilities: CAPABILITY_AUTO_EXPAND,CAPABILITY_IAM RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/admin/CloudFormationService - !Ref AWS::NoValue @@ -384,6 +414,42 @@ Resources: - Name: smokeTestResults - !Ref AWS::NoValue + + #------------------------------------- + # IAM Roles & Policies + #------------------------------------- + + # Grant the AiProxy CodeBuild Role additional permissions for resources in + # this template. This allows us to avoid granting permission to * resources. + AiProxyRolePolicy: + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: !Sub "${AWS::StackName}-codebuild-policy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - codebuild:* + Resource: + - !GetAtt AppBuildProject.Arn + - !GetAtt IntegrationTestBuildProject.Arn + - Effect: Allow + Action: + - codebuild:CreateReportGroup + - codebuild:CreateReport + - codebuild:UpdateReport + - codebuild:BatchPutTestCases + - codebuild:BatchPutCodeCoverage + Resource: + - !Sub arn:aws:codebuild:us-east-1:165336972514:report-group/${AWS::StackName}-${GitHubBranch}-pr-build + Roles: + - !ImportValue AiProxyCodeBuildRoleName + + #------------------------------------- + # Metrics & Notifications + #------------------------------------- + # Send pipeline events to an SNS topic. # Note: # Integration with Slack via AWS ChatBot is configured manually via AWS diff --git a/cicd/2-cicd/deploy-cicd.sh b/cicd/2-cicd/deploy-cicd.sh index 357dca8..31e87c7 100755 --- a/cicd/2-cicd/deploy-cicd.sh +++ b/cicd/2-cicd/deploy-cicd.sh @@ -39,7 +39,7 @@ TEMPLATE_FILE=cicd/2-cicd/cicd.template.yml echo Validating cloudformation template... aws cloudformation validate-template \ --template-body file://${TEMPLATE_FILE} \ - | cat + > /dev/null ACCOUNT=$(aws sts get-caller-identity --query "Account" --output text) diff --git a/cicd/3-app/aiproxy/template.yml b/cicd/3-app/aiproxy/template.yml index 5b87233..81fc1ab 100644 --- a/cicd/3-app/aiproxy/template.yml +++ b/cicd/3-app/aiproxy/template.yml @@ -2,8 +2,6 @@ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Provision an instance of the AI Proxy service. -# Dependencies: This template has dependencies, look for !ImportValue in the Resources section. - Parameters: BaseDomainName: Type: String @@ -14,9 +12,20 @@ Parameters: SubdomainName: Type: String Description: Subdomain name for aiproxy service (e.g. 'aiproxy' in 'aiproxy.code.org'). + # ECRRepositoryArn: + # Type: String + # Description: ARN of the ECR repository for this service. AppImageUri: Type: String Description: URI of the Docker image in ECR. + VPC: + Type: AWS::EC2::VPC::Id + SecurityGroup: + Type: AWS::EC2::SecurityGroup::Id + PublicSubnets: + Type: List + PrivateSubnets: + Type: List # Conditions: # IsDevCondition: !Equals [!Ref BaseDomainName, "dev-code.org"] @@ -63,16 +72,11 @@ Resources: LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds Value: 180 - SecurityGroups: - - !ImportValue VPC-ELBSecurityGroup - Subnets: - # Place load balancer in public subnets, so it's accessible from the internet. - # We may want to move this to the private subnets, so only internal resources - # can access it, but this is very convenient for local development. - - !ImportValue VPC-PublicSubnetB - - !ImportValue VPC-PublicSubnetC - - !ImportValue VPC-PublicSubnetD - - !ImportValue VPC-PublicSubnetE + SecurityGroups: [!Ref SecurityGroup] + # Place load balancer in public subnets, so it's accessible from the internet. + # We may want to move this to the private subnets, so only internal resources + # can access it, but this is very convenient for local development. + Subnets: !Ref PublicSubnets HttpListener: Type: AWS::ElasticLoadBalancingV2::Listener @@ -114,7 +118,7 @@ Resources: TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: - VpcId: !ImportValue VPC + VpcId: !Ref VPC Port: 80 TargetType: ip Protocol: HTTP @@ -147,12 +151,8 @@ Resources: AssignPublicIp: DISABLED SecurityGroups: - !Ref ECSSecurityGroup - Subnets: # Place ECS Service in private subnets, but traffic should use the LoadBalancer. - - !ImportValue VPC-SubnetB - - !ImportValue VPC-SubnetC - - !ImportValue VPC-SubnetD - - !ImportValue VPC-SubnetE + Subnets: !Ref PrivateSubnets LoadBalancers: - ContainerName: aiproxy ContainerPort: 80 @@ -163,7 +163,7 @@ Resources: Properties: GroupDescription: Security Group for ECS Service # TODO: This copies geocoder, but we should probably have a separate VPC for this service. - VpcId: !ImportValue VPC + VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 @@ -195,12 +195,51 @@ Resources: awslogs-region: !Ref AWS::Region awslogs-stream-prefix: ecs + # ECSTaskExecutionRole: + # Type: AWS::IAM::Role + # Properties: + # AssumeRolePolicyDocument: + # Version: "2012-10-17" + # Statement: + # - Effect: Allow + # Principal: + # Service: + # - ecs-tasks.amazonaws.com + # Action: + # - sts:AssumeRole + # Policies: + # - PolicyName: ECRPolicy + # PolicyDocument: + # Version: "2012-10-17" + # Statement: + # - Effect: Allow + # Action: + # - ecr:GetAuthorizationToken + # - ecr:BatchCheckLayerAvailability + # - ecr:GetDownloadUrlForLayer + # - ecr:BatchGetImage + # Resource: !Ref ECRRepositoryArn + # - PolicyName: LogsPolicy + # PolicyDocument: + # Version: "2012-10-17" + # Statement: + # - Effect: Allow + # Action: + # - logs:CreateLogGroup + # - logs:CreateLogStream + # - logs:PutLogEvents + # Resource: + # - !GetAtt LogGroup.Arn + # - !Sub "${LogGroup.Arn}:*" + # ------------------ # Logging & Alerts # ------------------ LogGroup: Type: AWS::Logs::LogGroup + DeletionPolicy: Retain + UpdateReplacePolicy: Retain Properties: LogGroupName: !Sub "${AWS::StackName}"