From 305169cbfada326b74d74ce669684eec45253101 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Thu, 13 Oct 2022 16:33:45 -0700 Subject: [PATCH 1/9] VPC flowlog for IP Traffic --- .../pkg/deploy/cloudformation/stack/env.go | 1 + internal/pkg/manifest/env.go | 3 +- internal/pkg/manifest/env_test.go | 7 +- internal/pkg/template/env.go | 1 + .../pkg/template/templates/environment/cf.yml | 68 +++++++++++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/env.go b/internal/pkg/deploy/cloudformation/stack/env.go index c2053c19cfc..bbb36a19048 100644 --- a/internal/pkg/deploy/cloudformation/stack/env.go +++ b/internal/pkg/deploy/cloudformation/stack/env.go @@ -425,6 +425,7 @@ func (e *EnvStackConfig) vpcConfig() (template.VPCConfig, error) { Managed: e.managedVPC(), AllowVPCIngress: aws.BoolValue(e.in.Mft.HTTPConfig.Private.SecurityGroupsConfig.Ingress.VPCIngress), SecurityGroupConfig: securityGroupConfig, + Flowlogs: aws.BoolValue(e.in.Mft.Network.VPC.Flowlogs), }, nil } diff --git a/internal/pkg/manifest/env.go b/internal/pkg/manifest/env.go index eff2b5f60ff..6f2b51cecb0 100644 --- a/internal/pkg/manifest/env.go +++ b/internal/pkg/manifest/env.go @@ -112,6 +112,7 @@ type environmentVPCConfig struct { CIDR *IPNet `yaml:"cidr,omitempty"` Subnets subnetsConfiguration `yaml:"subnets,omitempty"` SecurityGroupConfig securityGroupConfig `yaml:"security_group,omitempty"` + Flowlogs *bool `yaml:"flow_logs,omitempty"` } type securityGroupConfig struct { @@ -250,7 +251,7 @@ func (cfg *EnvironmentCDNConfig) UnmarshalYAML(value *yaml.Node) error { // IsEmpty returns true if vpc is not configured. func (cfg environmentVPCConfig) IsEmpty() bool { - return cfg.ID == nil && cfg.CIDR == nil && cfg.Subnets.IsEmpty() + return cfg.ID == nil && cfg.CIDR == nil && cfg.Subnets.IsEmpty() && cfg.Flowlogs == nil } func (cfg *environmentVPCConfig) loadVPCConfig(env *config.CustomizeEnv) { diff --git a/internal/pkg/manifest/env_test.go b/internal/pkg/manifest/env_test.go index d3f74f9a9ee..a09a2ad15cd 100644 --- a/internal/pkg/manifest/env_test.go +++ b/internal/pkg/manifest/env_test.go @@ -720,11 +720,16 @@ func TestEnvironmentVPCConfig_IsEmpty(t *testing.T) { "empty": { wanted: true, }, - "not empty": { + "not empty when VPC ID is provided": { in: environmentVPCConfig{ ID: aws.String("mock-vpc-id"), }, }, + "not empty when flowlog is on": { + in: environmentVPCConfig{ + Flowlogs: aws.Bool(true), + }, + }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { diff --git a/internal/pkg/template/env.go b/internal/pkg/template/env.go index 8d8f9d516ea..efdf24f162c 100644 --- a/internal/pkg/template/env.go +++ b/internal/pkg/template/env.go @@ -162,6 +162,7 @@ type VPCConfig struct { Managed ManagedVPC AllowVPCIngress bool SecurityGroupConfig *SecurityGroupConfig + Flowlogs bool } // ImportVPC holds the fields to import VPC resources. diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index c994a31804c..551d96699dc 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -557,6 +557,62 @@ Resources: {{include "lambdas" . | indent 2}} {{include "custom-resources" . | indent 2}} {{- end}} +{{- if .VPCConfig.Flowlogs }} + Flowlog: + Metadata: + 'aws:copilot:description': "A Flowlog for the VPC to capture information about the IP traffic" + Type: AWS::EC2::FlowLog + Properties: + DeliverLogsPermissionArn: !GetAtt Flowlogrole.Arn + LogDestinationType: cloud-watch-logs + LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] + MaxAggregationInterval: 60 +{{- if .VPCConfig.Imported}} + ResourceId: {{.VPCConfig.Imported.ID}} +{{- else}} + ResourceId: !Ref VPC +{{- end}} + ResourceType: VPC + TrafficType: ALL + Flowlogrole: + Metadata: + 'aws:copilot:description': "A IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group " + Type: AWS::IAM::Role + Properties: + {{- if .PermissionsBoundary}} + PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/{{.PermissionsBoundary}}' + {{- end}} + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: vpc-flow-logs.amazonaws.com + Action: sts:AssumeRole + Condition: + StringEquals: + aws:SourceAccount: !Ref AWS::AccountId + ArnLike: + aws:SourceArn: + - Fn::Sub: + - 'arn:aws:ec2:${region}:${account}:vpc-flow-log/*' + - region: !Ref AWS::Region + account: !Ref AWS::AccountId + flowlogid: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] + Policies: + - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams + Resource: "*" +{{- end}} Outputs: VpcId: {{- if .VPCConfig.Imported}} @@ -742,3 +798,15 @@ Outputs: LastForceDeployID: Value: {{quote .ForceUpdateID}} Description: Optionally force the template to update when no immediate resource change is present. +{{- if .VPCConfig.Flowlogs }} + FlowLogID: + Value: !Ref Flowlog + Description: VPC Flow Log to capture information about the IP traffic going to and from network interfaces in your VPC. + Export: + Name: !Sub ${AWS::StackName}-FlowlogID + FlowLogRoleID: + Value: !Ref Flowlogrole + Description: An IAM Role for EC2 to publish flow logs to a CloudWatch Logs log group + Export: + Name: !Sub ${AWS::StackName}-FlowlogRoleID +{{- end}} From ee46e75eb0f14956c50ff8ea1ddeb8772c492604 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Fri, 14 Oct 2022 02:07:34 -0700 Subject: [PATCH 2/9] address feedback --- internal/pkg/template/templates/environment/cf.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index 551d96699dc..9a0e22bcd4c 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -606,11 +606,11 @@ Resources: Statement: - Effect: Allow Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - - logs:DescribeLogGroups - - logs:DescribeLogStreams + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams Resource: "*" {{- end}} Outputs: From b8c21b1023d0c6c8fe9aa22b1917183a42b2192e Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Fri, 14 Oct 2022 02:29:50 -0700 Subject: [PATCH 3/9] adding permission boundary to progress tracker --- internal/pkg/template/templates/environment/cf.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index 9a0e22bcd4c..85e10727667 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -560,7 +560,7 @@ Resources: {{- if .VPCConfig.Flowlogs }} Flowlog: Metadata: - 'aws:copilot:description': "A Flowlog for the VPC to capture information about the IP traffic" + 'aws:copilot:description': 'A Flowlog for the VPC to capture information about the IP traffic' Type: AWS::EC2::FlowLog Properties: DeliverLogsPermissionArn: !GetAtt Flowlogrole.Arn @@ -576,7 +576,7 @@ Resources: TrafficType: ALL Flowlogrole: Metadata: - 'aws:copilot:description': "A IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group " + 'aws:copilot:description': 'A IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for AWS EC2 to publish flow logs to a CloudWatch log group' Type: AWS::IAM::Role Properties: {{- if .PermissionsBoundary}} From 000ca4b7157ed395fa4a74071bff42459ee9c0f2 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 17 Oct 2022 09:11:59 -0700 Subject: [PATCH 4/9] integration test for env and address fb --- .../stack/env_integration_test.go | 69 + .../template-with-defaultvpc-flowlogs.yml | 1189 +++++++++++++++++ .../template-with-importedvpc-flowlogs.yml | 1010 ++++++++++++++ .../pkg/template/templates/environment/cf.yml | 25 +- 4 files changed, 2275 insertions(+), 18 deletions(-) create mode 100644 internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml create mode 100644 internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml diff --git a/internal/pkg/deploy/cloudformation/stack/env_integration_test.go b/internal/pkg/deploy/cloudformation/stack/env_integration_test.go index d5e378149fa..96f91b70d85 100644 --- a/internal/pkg/deploy/cloudformation/stack/env_integration_test.go +++ b/internal/pkg/deploy/cloudformation/stack/env_integration_test.go @@ -223,6 +223,75 @@ type: Environment` }(), wantedFileName: "template-with-basic-manifest.yml", }, + "generate template with flowlogs is on": { + input: func() *deploy.CreateEnvironmentInput { + rawMft := `name: test +type: Environment +network: + vpc: + flow_logs: on` + var mft manifest.Environment + err := yaml.Unmarshal([]byte(rawMft), &mft) + require.NoError(t, err) + return &deploy.CreateEnvironmentInput{ + Version: "1.x", + App: deploy.AppInformation{ + AccountPrincipalARN: "arn:aws:iam::000000000:root", + Name: "demo", + }, + Name: "test", + ArtifactBucketARN: "arn:aws:s3:::mockbucket", + ArtifactBucketKeyARN: "arn:aws:kms:us-west-2:000000000:key/1234abcd-12ab-34cd-56ef-1234567890ab", + CustomResourcesURLs: map[string]string{ + "CertificateValidationFunction": "https://mockbucket.s3-us-west-2.amazonaws.com/dns-cert-validator", + "DNSDelegationFunction": "https://mockbucket.s3-us-west-2.amazonaws.com/dns-delegation", + "CustomDomainFunction": "https://mockbucket.s3-us-west-2.amazonaws.com/custom-domain", + }, + Mft: &mft, + RawMft: []byte(rawMft), + } + }(), + wantedFileName: "template-with-defaultvpc-flowlogs.yml", + }, + + "generate template with importedVPC and flowlogs is on": { + input: func() *deploy.CreateEnvironmentInput { + rawMft := `name: test +type: Environment +network: + vpc: + id: 'vpc-12345' + subnets: + public: + - id: 'subnet-11111' + - id: 'subnet-22222' + private: + - id: 'subnet-33333' + - id: 'subnet-44444' + flow_logs: on` + var mft manifest.Environment + err := yaml.Unmarshal([]byte(rawMft), &mft) + require.NoError(t, err) + return &deploy.CreateEnvironmentInput{ + Version: "1.x", + App: deploy.AppInformation{ + AccountPrincipalARN: "arn:aws:iam::000000000:root", + Name: "demo", + }, + Name: "test", + ArtifactBucketARN: "arn:aws:s3:::mockbucket", + ArtifactBucketKeyARN: "arn:aws:kms:us-west-2:000000000:key/1234abcd-12ab-34cd-56ef-1234567890ab", + CustomResourcesURLs: map[string]string{ + "CertificateValidationFunction": "https://mockbucket.s3-us-west-2.amazonaws.com/dns-cert-validator", + "DNSDelegationFunction": "https://mockbucket.s3-us-west-2.amazonaws.com/dns-delegation", + "CustomDomainFunction": "https://mockbucket.s3-us-west-2.amazonaws.com/custom-domain", + }, + Mft: &mft, + RawMft: []byte(rawMft), + } + }(), + wantedFileName: "template-with-importedvpc-flowlogs.yml", + }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml new file mode 100644 index 00000000000..8e8b850db3d --- /dev/null +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -0,0 +1,1189 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +Description: CloudFormation environment template for infrastructure shared among Copilot workloads. +Metadata: + Manifest: | + name: test + type: Environment + network: + vpc: + flow_logs: on + +Parameters: + AppName: + Type: String + EnvironmentName: + Type: String + ALBWorkloads: + Type: String + InternalALBWorkloads: + Type: String + EFSWorkloads: + Type: String + NATWorkloads: + Type: String + ToolsAccountPrincipalARN: + Type: String + AppDNSName: + Type: String + AppDNSDelegationRole: + Type: String + Aliases: + Type: String + CreateHTTPSListener: + Type: String + AllowedValues: [true, false] + CreateInternalHTTPSListener: + Type: String + AllowedValues: [true, false] + ServiceDiscoveryEndpoint: + Type: String +Conditions: + CreateALB: + !Not [!Equals [ !Ref ALBWorkloads, "" ]] + CreateInternalALB: + !Not [!Equals [ !Ref InternalALBWorkloads, "" ]] + DelegateDNS: + !Not [!Equals [ !Ref AppDNSName, "" ]] + ExportHTTPSListener: !And + - !Condition CreateALB + - !Equals [ !Ref CreateHTTPSListener, true ] + ExportInternalHTTPSListener: !And + - !Condition CreateInternalALB + - !Equals [ !Ref CreateInternalHTTPSListener, true ] + CreateEFS: + !Not [!Equals [ !Ref EFSWorkloads, ""]] + CreateNATGateways: + !Not [!Equals [ !Ref NATWorkloads, ""]] + ManagedAliases: !And + - !Condition DelegateDNS + - !Not [!Equals [ !Ref Aliases, "" ]] +Resources: + # The CloudformationExecutionRole definition must be immediately followed with DeletionPolicy: Retain. + # See #1533. + CloudformationExecutionRole: + Metadata: + 'aws:copilot:description': 'An IAM Role for AWS CloudFormation to manage resources' + DeletionPolicy: Retain + Type: AWS::IAM::Role + Properties: + RoleName: !Sub ${AWS::StackName}-CFNExecutionRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - 'cloudformation.amazonaws.com' + - 'lambda.amazonaws.com' + Action: sts:AssumeRole + Path: / + Policies: + - PolicyName: executeCfn + # This policy is more permissive than the managed PowerUserAccess + # since it allows arbitrary role creation, which is needed for the + # ECS task role specified by the customers. + PolicyDocument: + Version: '2012-10-17' + Statement: + - + Effect: Allow + NotAction: + - 'organizations:*' + - 'account:*' + Resource: '*' + - + Effect: Allow + Action: + - 'organizations:DescribeOrganization' + - 'account:ListRegions' + Resource: '*' + + EnvironmentManagerRole: + Metadata: + 'aws:copilot:description': 'An IAM Role to describe resources in your environment' + DeletionPolicy: Retain + Type: AWS::IAM::Role + DependsOn: CloudformationExecutionRole + Properties: + RoleName: !Sub ${AWS::StackName}-EnvManagerRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Sub ${ToolsAccountPrincipalARN} + Action: sts:AssumeRole + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: CloudwatchLogs + Effect: Allow + Action: [ + "logs:GetLogRecord", + "logs:GetQueryResults", + "logs:StartQuery", + "logs:GetLogEvents", + "logs:DescribeLogStreams", + "logs:StopQuery", + "logs:TestMetricFilter", + "logs:FilterLogEvents", + "logs:GetLogGroupFields", + "logs:GetLogDelivery" + ] + Resource: "*" + - Sid: Cloudwatch + Effect: Allow + Action: [ + "cloudwatch:DescribeAlarms" + ] + Resource: "*" + - Sid: ECS + Effect: Allow + Action: [ + "ecs:ListAttributes", + "ecs:ListTasks", + "ecs:DescribeServices", + "ecs:DescribeTaskSets", + "ecs:ListContainerInstances", + "ecs:DescribeContainerInstances", + "ecs:DescribeTasks", + "ecs:DescribeClusters", + "ecs:UpdateService", + "ecs:PutAttributes", + "ecs:StartTelemetrySession", + "ecs:StartTask", + "ecs:StopTask", + "ecs:ListServices", + "ecs:ListTaskDefinitionFamilies", + "ecs:DescribeTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:ListClusters", + "ecs:RunTask" + ] + Resource: "*" + - Sid: ExecuteCommand + Effect: Allow + Action: [ + "ecs:ExecuteCommand" + ] + Resource: "*" + Condition: + StringEquals: + 'aws:ResourceTag/copilot-application': !Sub '${AppName}' + 'aws:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' + - Sid: StartStateMachine + Effect: Allow + Action: + - "states:StartExecution" + - "states:DescribeStateMachine" + Resource: + - !Sub "arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${AppName}-${EnvironmentName}-*" + - Sid: CloudFormation + Effect: Allow + Action: [ + "cloudformation:CancelUpdateStack", + "cloudformation:CreateChangeSet", + "cloudformation:CreateStack", + "cloudformation:DeleteChangeSet", + "cloudformation:DeleteStack", + "cloudformation:Describe*", + "cloudformation:DetectStackDrift", + "cloudformation:DetectStackResourceDrift", + "cloudformation:ExecuteChangeSet", + "cloudformation:GetTemplate", + "cloudformation:GetTemplateSummary", + "cloudformation:UpdateStack", + "cloudformation:UpdateTerminationProtection" + ] + Resource: "*" + - Sid: GetAndPassCopilotRoles + Effect: Allow + Action: [ + "iam:GetRole", + "iam:PassRole" + ] + Resource: "*" + Condition: + StringEquals: + 'iam:ResourceTag/copilot-application': !Sub '${AppName}' + 'iam:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' + - Sid: ECR + Effect: Allow + Action: [ + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:ListImages", + "ecr:ListTagsForResource", + "ecr:PutImage", + "ecr:UploadLayerPart", + "ecr:GetAuthorizationToken" + ] + Resource: "*" + - Sid: ResourceGroups + Effect: Allow + Action: [ + "resource-groups:GetGroup", + "resource-groups:GetGroupQuery", + "resource-groups:GetTags", + "resource-groups:ListGroupResources", + "resource-groups:ListGroups", + "resource-groups:SearchResources" + ] + Resource: "*" + - Sid: SSM + Effect: Allow + Action: [ + "ssm:DeleteParameter", + "ssm:DeleteParameters", + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:GetParametersByPath" + ] + Resource: "*" + - Sid: SSMSecret + Effect: Allow + Action: [ + "ssm:PutParameter", + "ssm:AddTagsToResource" + ] + Resource: + - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/copilot/${AppName}/${EnvironmentName}/secrets/*' + - Sid: ELBv2 + Effect: Allow + Action: [ + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeRules" + ] + Resource: "*" + - Sid: BuiltArtifactAccess + Effect: Allow + Action: [ + "s3:ListBucketByTags", + "s3:GetLifecycleConfiguration", + "s3:GetBucketTagging", + "s3:GetInventoryConfiguration", + "s3:GetObjectVersionTagging", + "s3:ListBucketVersions", + "s3:GetBucketLogging", + "s3:ListBucket", + "s3:GetAccelerateConfiguration", + "s3:GetBucketPolicy", + "s3:GetObjectVersionTorrent", + "s3:GetObjectAcl", + "s3:GetEncryptionConfiguration", + "s3:GetBucketRequestPayment", + "s3:GetObjectVersionAcl", + "s3:GetObjectTagging", + "s3:GetMetricsConfiguration", + "s3:HeadBucket", + "s3:GetBucketPublicAccessBlock", + "s3:GetBucketPolicyStatus", + "s3:ListBucketMultipartUploads", + "s3:GetBucketWebsite", + "s3:ListJobs", + "s3:GetBucketVersioning", + "s3:GetBucketAcl", + "s3:GetBucketNotification", + "s3:GetReplicationConfiguration", + "s3:ListMultipartUploadParts", + "s3:GetObject", + "s3:GetObjectTorrent", + "s3:GetAccountPublicAccessBlock", + "s3:ListAllMyBuckets", + "s3:DescribeJob", + "s3:GetBucketCORS", + "s3:GetAnalyticsConfiguration", + "s3:GetObjectVersionForReplication", + "s3:GetBucketLocation", + "s3:GetObjectVersion", + "kms:Decrypt" + ] + Resource: "*" + - Sid: PutObjectsToArtifactBucket + Effect: Allow + Action: + - s3:PutObject + - s3:PutObjectAcl + Resource: + - arn:aws:s3:::mockbucket + - arn:aws:s3:::mockbucket/* + - Sid: EncryptObjectsInArtifactBucket + Effect: Allow + Action: + - kms:GenerateDataKey + Resource: arn:aws:kms:us-west-2:000000000:key/1234abcd-12ab-34cd-56ef-1234567890ab + - Sid: EC2 + Effect: Allow + Action: [ + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeRouteTables" + ] + Resource: "*" + - Sid: AppRunner + Effect: Allow + Action: [ + "apprunner:DescribeService", + "apprunner:ListOperations", + "apprunner:ListServices", + "apprunner:PauseService", + "apprunner:ResumeService", + "apprunner:StartDeployment", + "apprunner:DescribeObservabilityConfiguration" + ] + Resource: "*" + - Sid: Tags + Effect: Allow + Action: [ + "tag:GetResources" + ] + Resource: "*" + - Sid: ApplicationAutoscaling + Effect: Allow + Action: [ + "application-autoscaling:DescribeScalingPolicies" + ] + Resource: "*" + - Sid: DeleteRoles + Effect: Allow + Action: [ + "iam:DeleteRole", + "iam:ListRolePolicies", + "iam:DeleteRolePolicy" + ] + Resource: + - !GetAtt CloudformationExecutionRole.Arn + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${AWS::StackName}-EnvManagerRole" + - Sid: DeleteEnvStack + Effect: Allow + Action: + - 'cloudformation:DescribeStacks' + - 'cloudformation:DeleteStack' + Resource: + - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' + + VPC: + Metadata: + 'aws:copilot:description': 'A Virtual Private Cloud to control networking of your AWS resources' + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsHostnames: true + EnableDnsSupport: true + InstanceTenancy: default + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}' + + PublicRouteTable: + Metadata: + 'aws:copilot:description': "A custom route table that directs network traffic for the public subnets" + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}' + + DefaultPublicRoute: + Type: AWS::EC2::Route + DependsOn: InternetGatewayAttachment + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + InternetGateway: + Metadata: + 'aws:copilot:description': 'An Internet Gateway to connect to the public internet' + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}' + + InternetGatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + InternetGatewayId: !Ref InternetGateway + VpcId: !Ref VPC + PublicSubnet1: + Metadata: + 'aws:copilot:description': 'Public subnet 1 for resources that can access the internet' + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.0.0/24 + VpcId: !Ref VPC + AvailabilityZone: !Select [ 0, !GetAZs '' ] + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-pub0' + PublicSubnet2: + Metadata: + 'aws:copilot:description': 'Public subnet 2 for resources that can access the internet' + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.1.0/24 + VpcId: !Ref VPC + AvailabilityZone: !Select [ 1, !GetAZs '' ] + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-pub1' + PrivateSubnet1: + Metadata: + 'aws:copilot:description': 'Private subnet 1 for resources with no internet access' + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.2.0/24 + VpcId: !Ref VPC + AvailabilityZone: !Select [ 0, !GetAZs '' ] + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-priv0' + PrivateSubnet2: + Metadata: + 'aws:copilot:description': 'Private subnet 2 for resources with no internet access' + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.3.0/24 + VpcId: !Ref VPC + AvailabilityZone: !Select [ 1, !GetAZs '' ] + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-priv1' + PublicSubnet1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PublicRouteTable + SubnetId: !Ref PublicSubnet1 + PublicSubnet2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PublicRouteTable + SubnetId: !Ref PublicSubnet2 + + NatGateway1Attachment: + Type: AWS::EC2::EIP + Condition: CreateNATGateways + DependsOn: InternetGatewayAttachment + Properties: + Domain: vpc + NatGateway1: + Metadata: + 'aws:copilot:description': 'NAT Gateway 1 enabling workloads placed in private subnet 1 to reach the internet' + Type: AWS::EC2::NatGateway + Condition: CreateNATGateways + Properties: + AllocationId: !GetAtt NatGateway1Attachment.AllocationId + SubnetId: !Ref PublicSubnet1 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-0' + PrivateRouteTable1: + Type: AWS::EC2::RouteTable + Condition: CreateNATGateways + Properties: + VpcId: !Ref 'VPC' + PrivateRoute1: + Type: AWS::EC2::Route + Condition: CreateNATGateways + Properties: + RouteTableId: !Ref PrivateRouteTable1 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway1 + PrivateRouteTable1Association: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateNATGateways + Properties: + RouteTableId: !Ref PrivateRouteTable1 + SubnetId: !Ref PrivateSubnet1 + NatGateway2Attachment: + Type: AWS::EC2::EIP + Condition: CreateNATGateways + DependsOn: InternetGatewayAttachment + Properties: + Domain: vpc + NatGateway2: + Metadata: + 'aws:copilot:description': 'NAT Gateway 2 enabling workloads placed in private subnet 2 to reach the internet' + Type: AWS::EC2::NatGateway + Condition: CreateNATGateways + Properties: + AllocationId: !GetAtt NatGateway2Attachment.AllocationId + SubnetId: !Ref PublicSubnet2 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-1' + PrivateRouteTable2: + Type: AWS::EC2::RouteTable + Condition: CreateNATGateways + Properties: + VpcId: !Ref 'VPC' + PrivateRoute2: + Type: AWS::EC2::Route + Condition: CreateNATGateways + Properties: + RouteTableId: !Ref PrivateRouteTable2 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway2 + PrivateRouteTable2Association: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateNATGateways + Properties: + RouteTableId: !Ref PrivateRouteTable2 + SubnetId: !Ref PrivateSubnet2 + # Creates a service discovery namespace with the form provided in the parameter. + # For new environments after 1.5.0, this is "env.app.local". For upgraded environments from + # before 1.5.0, this is app.local. + ServiceDiscoveryNamespace: + Metadata: + 'aws:copilot:description': 'A private DNS namespace for discovering services within the environment' + Type: AWS::ServiceDiscovery::PrivateDnsNamespace + Properties: + Name: !Ref ServiceDiscoveryEndpoint + Vpc: !Ref VPC + Cluster: + Metadata: + 'aws:copilot:description': 'An ECS cluster to group your services' + Type: AWS::ECS::Cluster + Properties: + CapacityProviders: ['FARGATE', 'FARGATE_SPOT'] + Configuration: + ExecuteCommandConfiguration: + Logging: DEFAULT + ClusterSettings: + - Name: containerInsights + Value: disabled + PublicHTTPLoadBalancerSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your load balancer allowing HTTP traffic' + Condition: CreateALB + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: HTTP access to the public facing load balancer + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + Description: Allow from anyone on port 80 + FromPort: 80 + IpProtocol: tcp + ToPort: 80 + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-lb-http' + PublicHTTPSLoadBalancerSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your load balancer allowing HTTPS traffic' + Condition: ExportHTTPSListener + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: HTTPS access to the public facing load balancer + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + Description: Allow from anyone on port 443 + FromPort: 443 + IpProtocol: tcp + ToPort: 443 + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-lb-https' + InternalLoadBalancerSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your internal load balancer allowing HTTP traffic from within the VPC' + Condition: CreateInternalALB + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Access to the internal load balancer + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-internal-lb' + # Only accept requests coming from the public ALB, internal ALB, or other containers in the same security group. + EnvironmentSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group to allow your containers to talk to each other' + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Join ['', [!Ref AppName, '-', !Ref EnvironmentName, EnvironmentSecurityGroup]] + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-env' + EnvironmentHTTPSecurityGroupIngressFromPublicALB: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateALB + Properties: + Description: HTTP ingress from the public ALB + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref PublicHTTPLoadBalancerSecurityGroup + EnvironmentHTTPSSecurityGroupIngressFromPublicALB: + Type: AWS::EC2::SecurityGroupIngress + Condition: ExportHTTPSListener + Properties: + Description: HTTPS ingress from the public ALB + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref PublicHTTPSLoadBalancerSecurityGroup + EnvironmentSecurityGroupIngressFromInternalALB: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateInternalALB + Properties: + Description: Ingress from the internal ALB + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref InternalLoadBalancerSecurityGroup + EnvironmentSecurityGroupIngressFromSelf: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Ingress from other containers in the same security group + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + InternalALBIngressFromEnvironmentSecurityGroup: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateInternalALB + Properties: + Description: Ingress from the env security group + GroupId: !Ref InternalLoadBalancerSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + PublicLoadBalancer: + Metadata: + 'aws:copilot:description': 'An Application Load Balancer to distribute public traffic to your services' + Condition: CreateALB + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internet-facing + SecurityGroups: + - !GetAtt PublicHTTPLoadBalancerSecurityGroup.GroupId + - !If [ExportHTTPSListener, !GetAtt PublicHTTPSLoadBalancerSecurityGroup.GroupId, !Ref "AWS::NoValue"] + Subnets: [ !Ref PublicSubnet1, !Ref PublicSubnet2, ] + Type: application + # Assign a dummy target group that with no real services as targets, so that we can create + # the listeners for the services. + DefaultHTTPTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Condition: CreateALB + Properties: + # Check if your application is healthy within 20 = 10*2 seconds, compared to 2.5 mins = 30*5 seconds. + HealthCheckIntervalSeconds: 10 # Default is 30. + HealthyThresholdCount: 2 # Default is 5. + HealthCheckTimeoutSeconds: 5 + Port: 80 + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 # Default is 300. + TargetType: ip + VpcId: !Ref VPC + HTTPListener: + Metadata: + 'aws:copilot:description': 'A load balancer listener to route HTTP traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: CreateALB + Properties: + DefaultActions: + - TargetGroupArn: !Ref DefaultHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref PublicLoadBalancer + Port: 80 + Protocol: HTTP + HTTPSListener: + Metadata: + 'aws:copilot:description': 'A load balancer listener to route HTTPS traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: ExportHTTPSListener + Properties: + Certificates: + - CertificateArn: !Ref HTTPSCert + DefaultActions: + - TargetGroupArn: !Ref DefaultHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref PublicLoadBalancer + Port: 443 + Protocol: HTTPS + InternalLoadBalancer: + Metadata: + 'aws:copilot:description': 'An internal Application Load Balancer to distribute private traffic from within the VPC to your services' + Condition: CreateInternalALB + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internal + SecurityGroups: [ !GetAtt InternalLoadBalancerSecurityGroup.GroupId ] + Subnets: [ !Ref PrivateSubnet1, !Ref PrivateSubnet2, ] + Type: application + # Assign a dummy target group that with no real services as targets, so that we can create + # the listeners for the services. + DefaultInternalHTTPTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Condition: CreateInternalALB + Properties: + # Check if your application is healthy within 20 = 10*2 seconds, compared to 2.5 mins = 30*5 seconds. + HealthCheckIntervalSeconds: 10 # Default is 30. + HealthyThresholdCount: 2 # Default is 5. + HealthCheckTimeoutSeconds: 5 + Port: 80 + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 # Default is 300. + TargetType: ip + VpcId: !Ref VPC + InternalHTTPListener: + Metadata: + 'aws:copilot:description': 'An internal load balancer listener to route HTTP traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: CreateInternalALB + Properties: + DefaultActions: + - TargetGroupArn: !Ref DefaultInternalHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref InternalLoadBalancer + Port: 80 + Protocol: HTTP + InternalHTTPSListener: + Metadata: + 'aws:copilot:description': 'An internal load balancer listener to route HTTPS traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: ExportInternalHTTPSListener + Properties: + DefaultActions: + - TargetGroupArn: !Ref DefaultInternalHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref InternalLoadBalancer + Port: 443 + Protocol: HTTPS + InternalWorkloadsHostedZone: + Metadata: + 'aws:copilot:description': 'A hosted zone named test.demo.internal for backends behind a private load balancer' + Condition: CreateInternalALB + Type: AWS::Route53::HostedZone + Properties: + Name: !Sub ${EnvironmentName}.${AppName}.internal + VPCs: + - VPCId: !Ref VPC + VPCRegion: !Ref AWS::Region + FileSystem: + Condition: CreateEFS + Type: AWS::EFS::FileSystem + Metadata: + 'aws:copilot:description': 'An EFS filesystem for persistent task storage' + Properties: + BackupPolicy: + Status: ENABLED + Encrypted: true + FileSystemPolicy: + Version: '2012-10-17' + Id: CopilotEFSPolicy + Statement: + - Sid: AllowIAMFromTaggedRoles + Effect: Allow + Principal: + AWS: '*' + Action: + - elasticfilesystem:ClientWrite + - elasticfilesystem:ClientMount + Condition: + Bool: + 'elasticfilesystem:AccessedViaMountTarget': true + StringEquals: + 'iam:ResourceTag/copilot-application': !Sub '${AppName}' + 'iam:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' + - Sid: DenyUnencryptedAccess + Effect: Deny + Principal: '*' + Action: 'elasticfilesystem:*' + Condition: + Bool: + 'aws:SecureTransport': false + LifecyclePolicies: + - TransitionToIA: AFTER_30_DAYS + PerformanceMode: generalPurpose + ThroughputMode: bursting + EFSSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group to allow your containers to talk to EFS storage' + Type: AWS::EC2::SecurityGroup + Condition: CreateEFS + Properties: + GroupDescription: !Join ['', [!Ref AppName, '-', !Ref EnvironmentName, EFSSecurityGroup]] + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-efs' + EFSSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateEFS + Properties: + Description: Ingress from containers in the Environment Security Group. + GroupId: !Ref EFSSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + MountTarget1: + Type: AWS::EFS::MountTarget + Condition: CreateEFS + Properties: + FileSystemId: !Ref FileSystem + SubnetId: !Ref PrivateSubnet1 + SecurityGroups: + - !Ref EFSSecurityGroup + MountTarget2: + Type: AWS::EFS::MountTarget + Condition: CreateEFS + Properties: + FileSystemId: !Ref FileSystem + SubnetId: !Ref PrivateSubnet2 + SecurityGroups: + - !Ref EFSSecurityGroup + + CustomResourceRole: + Metadata: + 'aws:copilot:description': 'An IAM role to manage certificates and Route53 hosted zones' + Type: AWS::IAM::Role + Condition: DelegateDNS + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Path: / + Policies: + - PolicyName: "DNSandACMAccess" + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "acm:ListCertificates" + - "acm:RequestCertificate" + - "acm:DescribeCertificate" + - "acm:GetCertificate" + - "acm:DeleteCertificate" + - "acm:AddTagsToCertificate" + - "sts:AssumeRole" + - "logs:*" + - "route53:ChangeResourceRecordSets" + - "route53:Get*" + - "route53:Describe*" + - "route53:ListResourceRecordSets" + - "route53:ListHostedZonesByName" + Resource: + - "*" + EnvironmentHostedZone: + Metadata: + 'aws:copilot:description': "A Route 53 Hosted Zone for the environment's subdomain" + Type: "AWS::Route53::HostedZone" + Condition: DelegateDNS + Properties: + HostedZoneConfig: + Comment: !Sub "HostedZone for environment ${EnvironmentName} - ${EnvironmentName}.${AppName}.${AppDNSName}" + Name: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} + CertificateValidationFunction: + Type: AWS::Lambda::Function + Condition: DelegateDNS + Properties: + Code: + S3Bucket: mockbucket + S3Key: dns-cert-validator + Handler: "index.certificateRequestHandler" + Timeout: 900 + MemorySize: 512 + Role: !GetAtt 'CustomResourceRole.Arn' + Runtime: nodejs16.x + + CustomDomainFunction: + Condition: ManagedAliases + Type: AWS::Lambda::Function + Properties: + Code: + S3Bucket: mockbucket + S3Key: custom-domain + Handler: "index.handler" + Timeout: 600 + MemorySize: 512 + Role: !GetAtt 'CustomResourceRole.Arn' + Runtime: nodejs16.x + + DNSDelegationFunction: + Type: AWS::Lambda::Function + Condition: DelegateDNS + Properties: + Code: + S3Bucket: mockbucket + S3Key: dns-delegation + Handler: "index.domainDelegationHandler" + Timeout: 600 + MemorySize: 512 + Role: !GetAtt 'CustomResourceRole.Arn' + Runtime: nodejs16.x + + DelegateDNSAction: + Metadata: + 'aws:copilot:description': 'Delegate DNS for environment subdomain' + Condition: DelegateDNS + Type: Custom::DNSDelegationFunction + DependsOn: + - DNSDelegationFunction + - EnvironmentHostedZone + Properties: + ServiceToken: !GetAtt DNSDelegationFunction.Arn + DomainName: !Sub ${AppName}.${AppDNSName} + SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} + NameServers: !GetAtt EnvironmentHostedZone.NameServers + RootDNSRole: !Ref AppDNSDelegationRole + + HTTPSCert: + Metadata: + 'aws:copilot:description': 'Request and validate an ACM certificate for your domain' + Condition: DelegateDNS + Type: Custom::CertificateValidationFunction + DependsOn: + - CertificateValidationFunction + - EnvironmentHostedZone + - DelegateDNSAction + Properties: + ServiceToken: !GetAtt CertificateValidationFunction.Arn + AppName: !Ref AppName + EnvName: !Ref EnvironmentName + DomainName: !Ref AppDNSName + Aliases: !Ref Aliases + EnvHostedZoneId: !Ref EnvironmentHostedZone + Region: !Ref AWS::Region + RootDNSRole: !Ref AppDNSDelegationRole + + CustomDomainAction: + Metadata: + 'aws:copilot:description': 'Add an A-record to the hosted zone for the domain alias' + Condition: ManagedAliases + Type: Custom::CustomDomainFunction + Properties: + ServiceToken: !GetAtt CustomDomainFunction.Arn + AppName: !Ref AppName + EnvName: !Ref EnvironmentName + Aliases: !Ref Aliases + AppDNSRole: !Ref AppDNSDelegationRole + DomainName: !Ref AppDNSName + PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName + PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID + Flowlog: + Metadata: + 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic' + Type: AWS::EC2::FlowLog + Properties: + DeliverLogsPermissionArn: !GetAtt FlowlogRole.Arn + LogDestinationType: cloud-watch-logs + LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] + MaxAggregationInterval: 60 + ResourceId: !Ref VPC + ResourceType: VPC + TrafficType: ALL +# Reference to IAM Role policy for flowlogs: https://go.aws/3euClbg + FlowlogRole: + Metadata: + 'aws:copilot:description': 'An IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group' + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: vpc-flow-logs.amazonaws.com + Action: sts:AssumeRole + Condition: + StringEquals: + aws:SourceAccount: !Ref AWS::AccountId + ArnLike: + aws:SourceArn: + - Fn::Sub: + - 'arn:${partition}:ec2:${region}:${account}:vpc-flow-log/*' + - region: !Ref AWS::Region + partition: !Ref AWS::Partition + account: !Ref AWS::AccountId + Policies: + - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams + Resource: "*" +Outputs: + VpcId: + Value: !Ref VPC + Export: + Name: !Sub ${AWS::StackName}-VpcId + PublicSubnets: + Value: !Join [ ',', [ !Ref PublicSubnet1, !Ref PublicSubnet2, ] ] + Export: + Name: !Sub ${AWS::StackName}-PublicSubnets + PrivateSubnets: + Value: !Join [ ',', [ !Ref PrivateSubnet1, !Ref PrivateSubnet2, ] ] + Export: + Name: !Sub ${AWS::StackName}-PrivateSubnets + InternetGatewayID: + Value: !Ref InternetGateway + Export: + Name: !Sub ${AWS::StackName}-InternetGatewayID + PublicRouteTableID: + Value: !Ref PublicRouteTable + Export: + Name: !Sub ${AWS::StackName}-PublicRouteTableID + PrivateRouteTableIDs: + Condition: CreateNATGateways + Value: !Join [ ',', [ !Ref PrivateRouteTable1, !Ref PrivateRouteTable2, ] ] + Export: + Name: !Sub ${AWS::StackName}-PrivateRouteTableIDs + ServiceDiscoveryNamespaceID: + Value: !GetAtt ServiceDiscoveryNamespace.Id + Export: + Name: !Sub ${AWS::StackName}-ServiceDiscoveryNamespaceID + EnvironmentSecurityGroup: + Value: !Ref EnvironmentSecurityGroup + Export: + Name: !Sub ${AWS::StackName}-EnvironmentSecurityGroup + PublicLoadBalancerDNSName: + Condition: CreateALB + Value: !GetAtt PublicLoadBalancer.DNSName + Export: + Name: !Sub ${AWS::StackName}-PublicLoadBalancerDNS + PublicLoadBalancerFullName: + Condition: CreateALB + Value: !GetAtt PublicLoadBalancer.LoadBalancerFullName + Export: + Name: !Sub ${AWS::StackName}-PublicLoadBalancerFullName + PublicLoadBalancerHostedZone: + Condition: CreateALB + Value: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID + Export: + Name: !Sub ${AWS::StackName}-CanonicalHostedZoneID + HTTPListenerArn: + Condition: CreateALB + Value: !Ref HTTPListener + Export: + Name: !Sub ${AWS::StackName}-HTTPListenerArn + HTTPSListenerArn: + Condition: ExportHTTPSListener + Value: !Ref HTTPSListener + Export: + Name: !Sub ${AWS::StackName}-HTTPSListenerArn + DefaultHTTPTargetGroupArn: + Condition: CreateALB + Value: !Ref DefaultHTTPTargetGroup + Export: + Name: !Sub ${AWS::StackName}-DefaultHTTPTargetGroup + InternalLoadBalancerDNSName: + Condition: CreateInternalALB + Value: !GetAtt InternalLoadBalancer.DNSName + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerDNS + InternalLoadBalancerFullName: + Condition: CreateInternalALB + Value: !GetAtt InternalLoadBalancer.LoadBalancerFullName + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerFullName + InternalLoadBalancerHostedZone: + Condition: CreateInternalALB + Value: !GetAtt InternalLoadBalancer.CanonicalHostedZoneID + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerCanonicalHostedZoneID + InternalWorkloadsHostedZone: + Condition: CreateInternalALB + Value: !Ref InternalWorkloadsHostedZone + Export: + Name: !Sub ${AWS::StackName}-InternalWorkloadsHostedZoneID + InternalWorkloadsHostedZoneName: + Condition: CreateInternalALB + Value: !Sub ${EnvironmentName}.${AppName}.internal + Export: + Name: !Sub ${AWS::StackName}-InternalWorkloadsHostedZoneName + InternalHTTPListenerArn: + Condition: CreateInternalALB + Value: !Ref InternalHTTPListener + Export: + Name: !Sub ${AWS::StackName}-InternalHTTPListenerArn + InternalHTTPSListenerArn: + Condition: ExportInternalHTTPSListener + Value: !Ref InternalHTTPSListener + Export: + Name: !Sub ${AWS::StackName}-InternalHTTPSListenerArn + InternalLoadBalancerSecurityGroup: + Condition: CreateInternalALB + Value: !Ref InternalLoadBalancerSecurityGroup + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerSecurityGroup + ClusterId: + Value: !Ref Cluster + Export: + Name: !Sub ${AWS::StackName}-ClusterId + EnvironmentManagerRoleARN: + Value: !GetAtt EnvironmentManagerRole.Arn + Description: The role to be assumed by the ecs-cli to manage environments. + Export: + Name: !Sub ${AWS::StackName}-EnvironmentManagerRoleARN + CFNExecutionRoleARN: + Value: !GetAtt CloudformationExecutionRole.Arn + Description: The role to be assumed by the Cloudformation service when it deploys application infrastructure. + Export: + Name: !Sub ${AWS::StackName}-CFNExecutionRoleARN + EnvironmentHostedZone: + Condition: DelegateDNS + Value: !Ref EnvironmentHostedZone + Description: The HostedZone for this environment's private DNS. + Export: + Name: !Sub ${AWS::StackName}-HostedZone + EnvironmentSubdomain: + Condition: DelegateDNS + Value: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} + Description: The domain name of this environment. + Export: + Name: !Sub ${AWS::StackName}-SubDomain + EnabledFeatures: + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. + ManagedFileSystemID: + Condition: CreateEFS + Value: !Ref FileSystem + Description: The ID of the Copilot-managed EFS filesystem. + Export: + Name: !Sub ${AWS::StackName}-FilesystemID + PublicALBAccessible: + Condition: CreateALB + Value: true + LastForceDeployID: + Value: "" + Description: Optionally force the template to update when no immediate resource change is present. diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml new file mode 100644 index 00000000000..b525ab43e7e --- /dev/null +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -0,0 +1,1010 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +Description: CloudFormation environment template for infrastructure shared among Copilot workloads. +Metadata: + Manifest: | + name: test + type: Environment + network: + vpc: + id: 'vpc-12345' + subnets: + public: + - id: 'subnet-11111' + - id: 'subnet-22222' + private: + - id: 'subnet-33333' + - id: 'subnet-44444' + flow_logs: on + +Parameters: + AppName: + Type: String + EnvironmentName: + Type: String + ALBWorkloads: + Type: String + InternalALBWorkloads: + Type: String + EFSWorkloads: + Type: String + NATWorkloads: + Type: String + ToolsAccountPrincipalARN: + Type: String + AppDNSName: + Type: String + AppDNSDelegationRole: + Type: String + Aliases: + Type: String + CreateHTTPSListener: + Type: String + AllowedValues: [true, false] + CreateInternalHTTPSListener: + Type: String + AllowedValues: [true, false] + ServiceDiscoveryEndpoint: + Type: String +Conditions: + CreateALB: + !Not [!Equals [ !Ref ALBWorkloads, "" ]] + CreateInternalALB: + !Not [!Equals [ !Ref InternalALBWorkloads, "" ]] + DelegateDNS: + !Not [!Equals [ !Ref AppDNSName, "" ]] + ExportHTTPSListener: !And + - !Condition CreateALB + - !Equals [ !Ref CreateHTTPSListener, true ] + ExportInternalHTTPSListener: !And + - !Condition CreateInternalALB + - !Equals [ !Ref CreateInternalHTTPSListener, true ] + CreateEFS: + !Not [!Equals [ !Ref EFSWorkloads, ""]] + CreateNATGateways: + !Not [!Equals [ !Ref NATWorkloads, ""]] + ManagedAliases: !And + - !Condition DelegateDNS + - !Not [!Equals [ !Ref Aliases, "" ]] +Resources: + # The CloudformationExecutionRole definition must be immediately followed with DeletionPolicy: Retain. + # See #1533. + CloudformationExecutionRole: + Metadata: + 'aws:copilot:description': 'An IAM Role for AWS CloudFormation to manage resources' + DeletionPolicy: Retain + Type: AWS::IAM::Role + Properties: + RoleName: !Sub ${AWS::StackName}-CFNExecutionRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - 'cloudformation.amazonaws.com' + - 'lambda.amazonaws.com' + Action: sts:AssumeRole + Path: / + Policies: + - PolicyName: executeCfn + # This policy is more permissive than the managed PowerUserAccess + # since it allows arbitrary role creation, which is needed for the + # ECS task role specified by the customers. + PolicyDocument: + Version: '2012-10-17' + Statement: + - + Effect: Allow + NotAction: + - 'organizations:*' + - 'account:*' + Resource: '*' + - + Effect: Allow + Action: + - 'organizations:DescribeOrganization' + - 'account:ListRegions' + Resource: '*' + + EnvironmentManagerRole: + Metadata: + 'aws:copilot:description': 'An IAM Role to describe resources in your environment' + DeletionPolicy: Retain + Type: AWS::IAM::Role + DependsOn: CloudformationExecutionRole + Properties: + RoleName: !Sub ${AWS::StackName}-EnvManagerRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Sub ${ToolsAccountPrincipalARN} + Action: sts:AssumeRole + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: CloudwatchLogs + Effect: Allow + Action: [ + "logs:GetLogRecord", + "logs:GetQueryResults", + "logs:StartQuery", + "logs:GetLogEvents", + "logs:DescribeLogStreams", + "logs:StopQuery", + "logs:TestMetricFilter", + "logs:FilterLogEvents", + "logs:GetLogGroupFields", + "logs:GetLogDelivery" + ] + Resource: "*" + - Sid: Cloudwatch + Effect: Allow + Action: [ + "cloudwatch:DescribeAlarms" + ] + Resource: "*" + - Sid: ECS + Effect: Allow + Action: [ + "ecs:ListAttributes", + "ecs:ListTasks", + "ecs:DescribeServices", + "ecs:DescribeTaskSets", + "ecs:ListContainerInstances", + "ecs:DescribeContainerInstances", + "ecs:DescribeTasks", + "ecs:DescribeClusters", + "ecs:UpdateService", + "ecs:PutAttributes", + "ecs:StartTelemetrySession", + "ecs:StartTask", + "ecs:StopTask", + "ecs:ListServices", + "ecs:ListTaskDefinitionFamilies", + "ecs:DescribeTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:ListClusters", + "ecs:RunTask" + ] + Resource: "*" + - Sid: ExecuteCommand + Effect: Allow + Action: [ + "ecs:ExecuteCommand" + ] + Resource: "*" + Condition: + StringEquals: + 'aws:ResourceTag/copilot-application': !Sub '${AppName}' + 'aws:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' + - Sid: StartStateMachine + Effect: Allow + Action: + - "states:StartExecution" + - "states:DescribeStateMachine" + Resource: + - !Sub "arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${AppName}-${EnvironmentName}-*" + - Sid: CloudFormation + Effect: Allow + Action: [ + "cloudformation:CancelUpdateStack", + "cloudformation:CreateChangeSet", + "cloudformation:CreateStack", + "cloudformation:DeleteChangeSet", + "cloudformation:DeleteStack", + "cloudformation:Describe*", + "cloudformation:DetectStackDrift", + "cloudformation:DetectStackResourceDrift", + "cloudformation:ExecuteChangeSet", + "cloudformation:GetTemplate", + "cloudformation:GetTemplateSummary", + "cloudformation:UpdateStack", + "cloudformation:UpdateTerminationProtection" + ] + Resource: "*" + - Sid: GetAndPassCopilotRoles + Effect: Allow + Action: [ + "iam:GetRole", + "iam:PassRole" + ] + Resource: "*" + Condition: + StringEquals: + 'iam:ResourceTag/copilot-application': !Sub '${AppName}' + 'iam:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' + - Sid: ECR + Effect: Allow + Action: [ + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:ListImages", + "ecr:ListTagsForResource", + "ecr:PutImage", + "ecr:UploadLayerPart", + "ecr:GetAuthorizationToken" + ] + Resource: "*" + - Sid: ResourceGroups + Effect: Allow + Action: [ + "resource-groups:GetGroup", + "resource-groups:GetGroupQuery", + "resource-groups:GetTags", + "resource-groups:ListGroupResources", + "resource-groups:ListGroups", + "resource-groups:SearchResources" + ] + Resource: "*" + - Sid: SSM + Effect: Allow + Action: [ + "ssm:DeleteParameter", + "ssm:DeleteParameters", + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:GetParametersByPath" + ] + Resource: "*" + - Sid: SSMSecret + Effect: Allow + Action: [ + "ssm:PutParameter", + "ssm:AddTagsToResource" + ] + Resource: + - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/copilot/${AppName}/${EnvironmentName}/secrets/*' + - Sid: ELBv2 + Effect: Allow + Action: [ + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeRules" + ] + Resource: "*" + - Sid: BuiltArtifactAccess + Effect: Allow + Action: [ + "s3:ListBucketByTags", + "s3:GetLifecycleConfiguration", + "s3:GetBucketTagging", + "s3:GetInventoryConfiguration", + "s3:GetObjectVersionTagging", + "s3:ListBucketVersions", + "s3:GetBucketLogging", + "s3:ListBucket", + "s3:GetAccelerateConfiguration", + "s3:GetBucketPolicy", + "s3:GetObjectVersionTorrent", + "s3:GetObjectAcl", + "s3:GetEncryptionConfiguration", + "s3:GetBucketRequestPayment", + "s3:GetObjectVersionAcl", + "s3:GetObjectTagging", + "s3:GetMetricsConfiguration", + "s3:HeadBucket", + "s3:GetBucketPublicAccessBlock", + "s3:GetBucketPolicyStatus", + "s3:ListBucketMultipartUploads", + "s3:GetBucketWebsite", + "s3:ListJobs", + "s3:GetBucketVersioning", + "s3:GetBucketAcl", + "s3:GetBucketNotification", + "s3:GetReplicationConfiguration", + "s3:ListMultipartUploadParts", + "s3:GetObject", + "s3:GetObjectTorrent", + "s3:GetAccountPublicAccessBlock", + "s3:ListAllMyBuckets", + "s3:DescribeJob", + "s3:GetBucketCORS", + "s3:GetAnalyticsConfiguration", + "s3:GetObjectVersionForReplication", + "s3:GetBucketLocation", + "s3:GetObjectVersion", + "kms:Decrypt" + ] + Resource: "*" + - Sid: PutObjectsToArtifactBucket + Effect: Allow + Action: + - s3:PutObject + - s3:PutObjectAcl + Resource: + - arn:aws:s3:::mockbucket + - arn:aws:s3:::mockbucket/* + - Sid: EncryptObjectsInArtifactBucket + Effect: Allow + Action: + - kms:GenerateDataKey + Resource: arn:aws:kms:us-west-2:000000000:key/1234abcd-12ab-34cd-56ef-1234567890ab + - Sid: EC2 + Effect: Allow + Action: [ + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeRouteTables" + ] + Resource: "*" + - Sid: AppRunner + Effect: Allow + Action: [ + "apprunner:DescribeService", + "apprunner:ListOperations", + "apprunner:ListServices", + "apprunner:PauseService", + "apprunner:ResumeService", + "apprunner:StartDeployment", + "apprunner:DescribeObservabilityConfiguration" + ] + Resource: "*" + - Sid: Tags + Effect: Allow + Action: [ + "tag:GetResources" + ] + Resource: "*" + - Sid: ApplicationAutoscaling + Effect: Allow + Action: [ + "application-autoscaling:DescribeScalingPolicies" + ] + Resource: "*" + - Sid: DeleteRoles + Effect: Allow + Action: [ + "iam:DeleteRole", + "iam:ListRolePolicies", + "iam:DeleteRolePolicy" + ] + Resource: + - !GetAtt CloudformationExecutionRole.Arn + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${AWS::StackName}-EnvManagerRole" + - Sid: DeleteEnvStack + Effect: Allow + Action: + - 'cloudformation:DescribeStacks' + - 'cloudformation:DeleteStack' + Resource: + - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' + + # Creates a service discovery namespace with the form provided in the parameter. + # For new environments after 1.5.0, this is "env.app.local". For upgraded environments from + # before 1.5.0, this is app.local. + ServiceDiscoveryNamespace: + Metadata: + 'aws:copilot:description': 'A private DNS namespace for discovering services within the environment' + Type: AWS::ServiceDiscovery::PrivateDnsNamespace + Properties: + Name: !Ref ServiceDiscoveryEndpoint + Vpc: vpc-12345 + Cluster: + Metadata: + 'aws:copilot:description': 'An ECS cluster to group your services' + Type: AWS::ECS::Cluster + Properties: + CapacityProviders: ['FARGATE', 'FARGATE_SPOT'] + Configuration: + ExecuteCommandConfiguration: + Logging: DEFAULT + ClusterSettings: + - Name: containerInsights + Value: disabled + PublicHTTPLoadBalancerSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your load balancer allowing HTTP traffic' + Condition: CreateALB + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: HTTP access to the public facing load balancer + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + Description: Allow from anyone on port 80 + FromPort: 80 + IpProtocol: tcp + ToPort: 80 + VpcId: vpc-12345 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-lb-http' + PublicHTTPSLoadBalancerSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your load balancer allowing HTTPS traffic' + Condition: ExportHTTPSListener + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: HTTPS access to the public facing load balancer + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + Description: Allow from anyone on port 443 + FromPort: 443 + IpProtocol: tcp + ToPort: 443 + VpcId: vpc-12345 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-lb-https' + InternalLoadBalancerSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for your internal load balancer allowing HTTP traffic from within the VPC' + Condition: CreateInternalALB + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Access to the internal load balancer + VpcId: vpc-12345 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-internal-lb' + # Only accept requests coming from the public ALB, internal ALB, or other containers in the same security group. + EnvironmentSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group to allow your containers to talk to each other' + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Join ['', [!Ref AppName, '-', !Ref EnvironmentName, EnvironmentSecurityGroup]] + VpcId: vpc-12345 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-env' + EnvironmentHTTPSecurityGroupIngressFromPublicALB: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateALB + Properties: + Description: HTTP ingress from the public ALB + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref PublicHTTPLoadBalancerSecurityGroup + EnvironmentHTTPSSecurityGroupIngressFromPublicALB: + Type: AWS::EC2::SecurityGroupIngress + Condition: ExportHTTPSListener + Properties: + Description: HTTPS ingress from the public ALB + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref PublicHTTPSLoadBalancerSecurityGroup + EnvironmentSecurityGroupIngressFromInternalALB: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateInternalALB + Properties: + Description: Ingress from the internal ALB + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref InternalLoadBalancerSecurityGroup + EnvironmentSecurityGroupIngressFromSelf: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Ingress from other containers in the same security group + GroupId: !Ref EnvironmentSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + InternalALBIngressFromEnvironmentSecurityGroup: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateInternalALB + Properties: + Description: Ingress from the env security group + GroupId: !Ref InternalLoadBalancerSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + PublicLoadBalancer: + Metadata: + 'aws:copilot:description': 'An Application Load Balancer to distribute public traffic to your services' + Condition: CreateALB + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internet-facing + SecurityGroups: + - !GetAtt PublicHTTPLoadBalancerSecurityGroup.GroupId + - !If [ExportHTTPSListener, !GetAtt PublicHTTPSLoadBalancerSecurityGroup.GroupId, !Ref "AWS::NoValue"] + Subnets: [ subnet-11111, subnet-22222, ] + Type: application + # Assign a dummy target group that with no real services as targets, so that we can create + # the listeners for the services. + DefaultHTTPTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Condition: CreateALB + Properties: + # Check if your application is healthy within 20 = 10*2 seconds, compared to 2.5 mins = 30*5 seconds. + HealthCheckIntervalSeconds: 10 # Default is 30. + HealthyThresholdCount: 2 # Default is 5. + HealthCheckTimeoutSeconds: 5 + Port: 80 + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 # Default is 300. + TargetType: ip + VpcId: vpc-12345 + HTTPListener: + Metadata: + 'aws:copilot:description': 'A load balancer listener to route HTTP traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: CreateALB + Properties: + DefaultActions: + - TargetGroupArn: !Ref DefaultHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref PublicLoadBalancer + Port: 80 + Protocol: HTTP + HTTPSListener: + Metadata: + 'aws:copilot:description': 'A load balancer listener to route HTTPS traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: ExportHTTPSListener + Properties: + Certificates: + - CertificateArn: !Ref HTTPSCert + DefaultActions: + - TargetGroupArn: !Ref DefaultHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref PublicLoadBalancer + Port: 443 + Protocol: HTTPS + InternalLoadBalancer: + Metadata: + 'aws:copilot:description': 'An internal Application Load Balancer to distribute private traffic from within the VPC to your services' + Condition: CreateInternalALB + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internal + SecurityGroups: [ !GetAtt InternalLoadBalancerSecurityGroup.GroupId ] + Subnets: [subnet-33333, subnet-44444] + Type: application + # Assign a dummy target group that with no real services as targets, so that we can create + # the listeners for the services. + DefaultInternalHTTPTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Condition: CreateInternalALB + Properties: + # Check if your application is healthy within 20 = 10*2 seconds, compared to 2.5 mins = 30*5 seconds. + HealthCheckIntervalSeconds: 10 # Default is 30. + HealthyThresholdCount: 2 # Default is 5. + HealthCheckTimeoutSeconds: 5 + Port: 80 + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 # Default is 300. + TargetType: ip + VpcId: vpc-12345 + InternalHTTPListener: + Metadata: + 'aws:copilot:description': 'An internal load balancer listener to route HTTP traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: CreateInternalALB + Properties: + DefaultActions: + - TargetGroupArn: !Ref DefaultInternalHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref InternalLoadBalancer + Port: 80 + Protocol: HTTP + InternalHTTPSListener: + Metadata: + 'aws:copilot:description': 'An internal load balancer listener to route HTTPS traffic' + Type: AWS::ElasticLoadBalancingV2::Listener + Condition: ExportInternalHTTPSListener + Properties: + DefaultActions: + - TargetGroupArn: !Ref DefaultInternalHTTPTargetGroup + Type: forward + LoadBalancerArn: !Ref InternalLoadBalancer + Port: 443 + Protocol: HTTPS + InternalWorkloadsHostedZone: + Metadata: + 'aws:copilot:description': 'A hosted zone named test.demo.internal for backends behind a private load balancer' + Condition: CreateInternalALB + Type: AWS::Route53::HostedZone + Properties: + Name: !Sub ${EnvironmentName}.${AppName}.internal + VPCs: + - VPCId: vpc-12345 + VPCRegion: !Ref AWS::Region + FileSystem: + Condition: CreateEFS + Type: AWS::EFS::FileSystem + Metadata: + 'aws:copilot:description': 'An EFS filesystem for persistent task storage' + Properties: + BackupPolicy: + Status: ENABLED + Encrypted: true + FileSystemPolicy: + Version: '2012-10-17' + Id: CopilotEFSPolicy + Statement: + - Sid: AllowIAMFromTaggedRoles + Effect: Allow + Principal: + AWS: '*' + Action: + - elasticfilesystem:ClientWrite + - elasticfilesystem:ClientMount + Condition: + Bool: + 'elasticfilesystem:AccessedViaMountTarget': true + StringEquals: + 'iam:ResourceTag/copilot-application': !Sub '${AppName}' + 'iam:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' + - Sid: DenyUnencryptedAccess + Effect: Deny + Principal: '*' + Action: 'elasticfilesystem:*' + Condition: + Bool: + 'aws:SecureTransport': false + LifecyclePolicies: + - TransitionToIA: AFTER_30_DAYS + PerformanceMode: generalPurpose + ThroughputMode: bursting + EFSSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group to allow your containers to talk to EFS storage' + Type: AWS::EC2::SecurityGroup + Condition: CreateEFS + Properties: + GroupDescription: !Join ['', [!Ref AppName, '-', !Ref EnvironmentName, EFSSecurityGroup]] + VpcId: vpc-12345 + Tags: + - Key: Name + Value: !Sub 'copilot-${AppName}-${EnvironmentName}-efs' + EFSSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateEFS + Properties: + Description: Ingress from containers in the Environment Security Group. + GroupId: !Ref EFSSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + MountTarget1: + Type: AWS::EFS::MountTarget + Condition: CreateEFS + Properties: + FileSystemId: !Ref FileSystem + SubnetId: subnet-33333 + SecurityGroups: + - !Ref EFSSecurityGroup + MountTarget2: + Type: AWS::EFS::MountTarget + Condition: CreateEFS + Properties: + FileSystemId: !Ref FileSystem + SubnetId: subnet-44444 + SecurityGroups: + - !Ref EFSSecurityGroup + + CustomResourceRole: + Metadata: + 'aws:copilot:description': 'An IAM role to manage certificates and Route53 hosted zones' + Type: AWS::IAM::Role + Condition: DelegateDNS + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Path: / + Policies: + - PolicyName: "DNSandACMAccess" + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "acm:ListCertificates" + - "acm:RequestCertificate" + - "acm:DescribeCertificate" + - "acm:GetCertificate" + - "acm:DeleteCertificate" + - "acm:AddTagsToCertificate" + - "sts:AssumeRole" + - "logs:*" + - "route53:ChangeResourceRecordSets" + - "route53:Get*" + - "route53:Describe*" + - "route53:ListResourceRecordSets" + - "route53:ListHostedZonesByName" + Resource: + - "*" + EnvironmentHostedZone: + Metadata: + 'aws:copilot:description': "A Route 53 Hosted Zone for the environment's subdomain" + Type: "AWS::Route53::HostedZone" + Condition: DelegateDNS + Properties: + HostedZoneConfig: + Comment: !Sub "HostedZone for environment ${EnvironmentName} - ${EnvironmentName}.${AppName}.${AppDNSName}" + Name: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} + CertificateValidationFunction: + Type: AWS::Lambda::Function + Condition: DelegateDNS + Properties: + Code: + S3Bucket: mockbucket + S3Key: dns-cert-validator + Handler: "index.certificateRequestHandler" + Timeout: 900 + MemorySize: 512 + Role: !GetAtt 'CustomResourceRole.Arn' + Runtime: nodejs16.x + + CustomDomainFunction: + Condition: ManagedAliases + Type: AWS::Lambda::Function + Properties: + Code: + S3Bucket: mockbucket + S3Key: custom-domain + Handler: "index.handler" + Timeout: 600 + MemorySize: 512 + Role: !GetAtt 'CustomResourceRole.Arn' + Runtime: nodejs16.x + + DNSDelegationFunction: + Type: AWS::Lambda::Function + Condition: DelegateDNS + Properties: + Code: + S3Bucket: mockbucket + S3Key: dns-delegation + Handler: "index.domainDelegationHandler" + Timeout: 600 + MemorySize: 512 + Role: !GetAtt 'CustomResourceRole.Arn' + Runtime: nodejs16.x + + DelegateDNSAction: + Metadata: + 'aws:copilot:description': 'Delegate DNS for environment subdomain' + Condition: DelegateDNS + Type: Custom::DNSDelegationFunction + DependsOn: + - DNSDelegationFunction + - EnvironmentHostedZone + Properties: + ServiceToken: !GetAtt DNSDelegationFunction.Arn + DomainName: !Sub ${AppName}.${AppDNSName} + SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} + NameServers: !GetAtt EnvironmentHostedZone.NameServers + RootDNSRole: !Ref AppDNSDelegationRole + + HTTPSCert: + Metadata: + 'aws:copilot:description': 'Request and validate an ACM certificate for your domain' + Condition: DelegateDNS + Type: Custom::CertificateValidationFunction + DependsOn: + - CertificateValidationFunction + - EnvironmentHostedZone + - DelegateDNSAction + Properties: + ServiceToken: !GetAtt CertificateValidationFunction.Arn + AppName: !Ref AppName + EnvName: !Ref EnvironmentName + DomainName: !Ref AppDNSName + Aliases: !Ref Aliases + EnvHostedZoneId: !Ref EnvironmentHostedZone + Region: !Ref AWS::Region + RootDNSRole: !Ref AppDNSDelegationRole + + CustomDomainAction: + Metadata: + 'aws:copilot:description': 'Add an A-record to the hosted zone for the domain alias' + Condition: ManagedAliases + Type: Custom::CustomDomainFunction + Properties: + ServiceToken: !GetAtt CustomDomainFunction.Arn + AppName: !Ref AppName + EnvName: !Ref EnvironmentName + Aliases: !Ref Aliases + AppDNSRole: !Ref AppDNSDelegationRole + DomainName: !Ref AppDNSName + PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName + PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID + Flowlog: + Metadata: + 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic' + Type: AWS::EC2::FlowLog + Properties: + DeliverLogsPermissionArn: !GetAtt FlowlogRole.Arn + LogDestinationType: cloud-watch-logs + LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] + MaxAggregationInterval: 60 + ResourceId: vpc-12345 + ResourceType: VPC + TrafficType: ALL +# Reference to IAM Role policy for flowlogs: https://go.aws/3euClbg + FlowlogRole: + Metadata: + 'aws:copilot:description': 'An IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group' + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: vpc-flow-logs.amazonaws.com + Action: sts:AssumeRole + Condition: + StringEquals: + aws:SourceAccount: !Ref AWS::AccountId + ArnLike: + aws:SourceArn: + - Fn::Sub: + - 'arn:${partition}:ec2:${region}:${account}:vpc-flow-log/*' + - region: !Ref AWS::Region + partition: !Ref AWS::Partition + account: !Ref AWS::AccountId + Policies: + - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams + Resource: "*" +Outputs: + VpcId: + Value: vpc-12345 + Export: + Name: !Sub ${AWS::StackName}-VpcId + PublicSubnets: + Value: !Join [ ',', [ subnet-11111, subnet-22222, ] ] + Export: + Name: !Sub ${AWS::StackName}-PublicSubnets + PrivateSubnets: + Value: !Join [ ',', [ subnet-33333, subnet-44444, ] ] + Export: + Name: !Sub ${AWS::StackName}-PrivateSubnets + ServiceDiscoveryNamespaceID: + Value: !GetAtt ServiceDiscoveryNamespace.Id + Export: + Name: !Sub ${AWS::StackName}-ServiceDiscoveryNamespaceID + EnvironmentSecurityGroup: + Value: !Ref EnvironmentSecurityGroup + Export: + Name: !Sub ${AWS::StackName}-EnvironmentSecurityGroup + PublicLoadBalancerDNSName: + Condition: CreateALB + Value: !GetAtt PublicLoadBalancer.DNSName + Export: + Name: !Sub ${AWS::StackName}-PublicLoadBalancerDNS + PublicLoadBalancerFullName: + Condition: CreateALB + Value: !GetAtt PublicLoadBalancer.LoadBalancerFullName + Export: + Name: !Sub ${AWS::StackName}-PublicLoadBalancerFullName + PublicLoadBalancerHostedZone: + Condition: CreateALB + Value: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID + Export: + Name: !Sub ${AWS::StackName}-CanonicalHostedZoneID + HTTPListenerArn: + Condition: CreateALB + Value: !Ref HTTPListener + Export: + Name: !Sub ${AWS::StackName}-HTTPListenerArn + HTTPSListenerArn: + Condition: ExportHTTPSListener + Value: !Ref HTTPSListener + Export: + Name: !Sub ${AWS::StackName}-HTTPSListenerArn + DefaultHTTPTargetGroupArn: + Condition: CreateALB + Value: !Ref DefaultHTTPTargetGroup + Export: + Name: !Sub ${AWS::StackName}-DefaultHTTPTargetGroup + InternalLoadBalancerDNSName: + Condition: CreateInternalALB + Value: !GetAtt InternalLoadBalancer.DNSName + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerDNS + InternalLoadBalancerFullName: + Condition: CreateInternalALB + Value: !GetAtt InternalLoadBalancer.LoadBalancerFullName + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerFullName + InternalLoadBalancerHostedZone: + Condition: CreateInternalALB + Value: !GetAtt InternalLoadBalancer.CanonicalHostedZoneID + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerCanonicalHostedZoneID + InternalWorkloadsHostedZone: + Condition: CreateInternalALB + Value: !Ref InternalWorkloadsHostedZone + Export: + Name: !Sub ${AWS::StackName}-InternalWorkloadsHostedZoneID + InternalWorkloadsHostedZoneName: + Condition: CreateInternalALB + Value: !Sub ${EnvironmentName}.${AppName}.internal + Export: + Name: !Sub ${AWS::StackName}-InternalWorkloadsHostedZoneName + InternalHTTPListenerArn: + Condition: CreateInternalALB + Value: !Ref InternalHTTPListener + Export: + Name: !Sub ${AWS::StackName}-InternalHTTPListenerArn + InternalHTTPSListenerArn: + Condition: ExportInternalHTTPSListener + Value: !Ref InternalHTTPSListener + Export: + Name: !Sub ${AWS::StackName}-InternalHTTPSListenerArn + InternalLoadBalancerSecurityGroup: + Condition: CreateInternalALB + Value: !Ref InternalLoadBalancerSecurityGroup + Export: + Name: !Sub ${AWS::StackName}-InternalLoadBalancerSecurityGroup + ClusterId: + Value: !Ref Cluster + Export: + Name: !Sub ${AWS::StackName}-ClusterId + EnvironmentManagerRoleARN: + Value: !GetAtt EnvironmentManagerRole.Arn + Description: The role to be assumed by the ecs-cli to manage environments. + Export: + Name: !Sub ${AWS::StackName}-EnvironmentManagerRoleARN + CFNExecutionRoleARN: + Value: !GetAtt CloudformationExecutionRole.Arn + Description: The role to be assumed by the Cloudformation service when it deploys application infrastructure. + Export: + Name: !Sub ${AWS::StackName}-CFNExecutionRoleARN + EnvironmentHostedZone: + Condition: DelegateDNS + Value: !Ref EnvironmentHostedZone + Description: The HostedZone for this environment's private DNS. + Export: + Name: !Sub ${AWS::StackName}-HostedZone + EnvironmentSubdomain: + Condition: DelegateDNS + Value: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} + Description: The domain name of this environment. + Export: + Name: !Sub ${AWS::StackName}-SubDomain + EnabledFeatures: + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. + ManagedFileSystemID: + Condition: CreateEFS + Value: !Ref FileSystem + Description: The ID of the Copilot-managed EFS filesystem. + Export: + Name: !Sub ${AWS::StackName}-FilesystemID + PublicALBAccessible: + Condition: CreateALB + Value: true + LastForceDeployID: + Value: "" + Description: Optionally force the template to update when no immediate resource change is present. diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index 85e10727667..2aa02e38432 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -560,10 +560,10 @@ Resources: {{- if .VPCConfig.Flowlogs }} Flowlog: Metadata: - 'aws:copilot:description': 'A Flowlog for the VPC to capture information about the IP traffic' + 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic' Type: AWS::EC2::FlowLog Properties: - DeliverLogsPermissionArn: !GetAtt Flowlogrole.Arn + DeliverLogsPermissionArn: !GetAtt FlowlogRole.Arn LogDestinationType: cloud-watch-logs LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] MaxAggregationInterval: 60 @@ -574,9 +574,10 @@ Resources: {{- end}} ResourceType: VPC TrafficType: ALL - Flowlogrole: +# Reference to IAM Role policy for Publish flow logs to CloudWatch Logs: https://go.aws/3euClbg + FlowlogRole: Metadata: - 'aws:copilot:description': 'A IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for AWS EC2 to publish flow logs to a CloudWatch log group' + 'aws:copilot:description': 'An IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for AWS EC2 to publish flow logs to a CloudWatch log group' Type: AWS::IAM::Role Properties: {{- if .PermissionsBoundary}} @@ -595,10 +596,10 @@ Resources: ArnLike: aws:SourceArn: - Fn::Sub: - - 'arn:aws:ec2:${region}:${account}:vpc-flow-log/*' + - 'arn:${partition}:ec2:${region}:${account}:vpc-flow-log/*' - region: !Ref AWS::Region + partition: !Ref AWS::Partition account: !Ref AWS::AccountId - flowlogid: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] Policies: - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' PolicyDocument: @@ -798,15 +799,3 @@ Outputs: LastForceDeployID: Value: {{quote .ForceUpdateID}} Description: Optionally force the template to update when no immediate resource change is present. -{{- if .VPCConfig.Flowlogs }} - FlowLogID: - Value: !Ref Flowlog - Description: VPC Flow Log to capture information about the IP traffic going to and from network interfaces in your VPC. - Export: - Name: !Sub ${AWS::StackName}-FlowlogID - FlowLogRoleID: - Value: !Ref Flowlogrole - Description: An IAM Role for EC2 to publish flow logs to a CloudWatch Logs log group - Export: - Name: !Sub ${AWS::StackName}-FlowlogRoleID -{{- end}} From 4d51f5cd0531e26e13d8754e96ac382d656aab60 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 17 Oct 2022 10:39:34 -0700 Subject: [PATCH 5/9] address fb --- .../pkg/deploy/cloudformation/stack/env_integration_test.go | 4 ++-- .../environments/template-with-defaultvpc-flowlogs.yml | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/env_integration_test.go b/internal/pkg/deploy/cloudformation/stack/env_integration_test.go index 96f91b70d85..bb6d8868a1a 100644 --- a/internal/pkg/deploy/cloudformation/stack/env_integration_test.go +++ b/internal/pkg/deploy/cloudformation/stack/env_integration_test.go @@ -223,7 +223,7 @@ type: Environment` }(), wantedFileName: "template-with-basic-manifest.yml", }, - "generate template with flowlogs is on": { + "generate template with default vpc and flowlogs is on": { input: func() *deploy.CreateEnvironmentInput { rawMft := `name: test type: Environment @@ -254,7 +254,7 @@ network: wantedFileName: "template-with-defaultvpc-flowlogs.yml", }, - "generate template with importedVPC and flowlogs is on": { + "generate template with imported vpc and flowlogs is on": { input: func() *deploy.CreateEnvironmentInput { rawMft := `name: test type: Environment diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index 8e8b850db3d..6ba9180d618 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -484,6 +484,8 @@ Resources: SubnetId: !Ref PublicSubnet2 NatGateway1Attachment: + Metadata: + 'aws:copilot:description':'An Elastic IP for NAT Gateway 1' Type: AWS::EC2::EIP Condition: CreateNATGateways DependsOn: InternetGatewayAttachment @@ -519,6 +521,8 @@ Resources: RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet1 NatGateway2Attachment: + Metadata: + 'aws:copilot:description':'An Elastic IP for NAT Gateway 2' Type: AWS::EC2::EIP Condition: CreateNATGateways DependsOn: InternetGatewayAttachment From 94d3ccc7216c225407d1848b63c67010f62d3135 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 17 Oct 2022 11:54:15 -0700 Subject: [PATCH 6/9] fix integration tests --- .../environments/template-with-defaultvpc-flowlogs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index 6ba9180d618..350be4fde1d 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -485,7 +485,7 @@ Resources: NatGateway1Attachment: Metadata: - 'aws:copilot:description':'An Elastic IP for NAT Gateway 1' + 'aws:copilot:description': 'An Elastic IP for NAT Gateway 1' Type: AWS::EC2::EIP Condition: CreateNATGateways DependsOn: InternetGatewayAttachment @@ -522,7 +522,7 @@ Resources: SubnetId: !Ref PrivateSubnet1 NatGateway2Attachment: Metadata: - 'aws:copilot:description':'An Elastic IP for NAT Gateway 2' + 'aws:copilot:description': 'An Elastic IP for NAT Gateway 2' Type: AWS::EC2::EIP Condition: CreateNATGateways DependsOn: InternetGatewayAttachment @@ -1010,7 +1010,7 @@ Resources: ResourceId: !Ref VPC ResourceType: VPC TrafficType: ALL -# Reference to IAM Role policy for flowlogs: https://go.aws/3euClbg +# Reference to IAM Role policy for Publish flow logs to CloudWatch Logs: https://go.aws/3euClbg FlowlogRole: Metadata: 'aws:copilot:description': 'An IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group' From badc51e45265063b5aa78508ba62853dd206cd88 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 17 Oct 2022 17:12:03 -0700 Subject: [PATCH 7/9] address dannyrandall's fb --- internal/pkg/template/templates/environment/cf.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index 2aa02e38432..db6abfc9317 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -558,12 +558,12 @@ Resources: {{include "custom-resources" . | indent 2}} {{- end}} {{- if .VPCConfig.Flowlogs }} - Flowlog: + FlowLog: Metadata: 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic' Type: AWS::EC2::FlowLog Properties: - DeliverLogsPermissionArn: !GetAtt FlowlogRole.Arn + DeliverLogsPermissionArn: !GetAtt FlowLogRole.Arn LogDestinationType: cloud-watch-logs LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] MaxAggregationInterval: 60 @@ -575,7 +575,7 @@ Resources: ResourceType: VPC TrafficType: ALL # Reference to IAM Role policy for Publish flow logs to CloudWatch Logs: https://go.aws/3euClbg - FlowlogRole: + FlowLogRole: Metadata: 'aws:copilot:description': 'An IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for AWS EC2 to publish flow logs to a CloudWatch log group' Type: AWS::IAM::Role @@ -594,12 +594,7 @@ Resources: StringEquals: aws:SourceAccount: !Ref AWS::AccountId ArnLike: - aws:SourceArn: - - Fn::Sub: - - 'arn:${partition}:ec2:${region}:${account}:vpc-flow-log/*' - - region: !Ref AWS::Region - partition: !Ref AWS::Partition - account: !Ref AWS::AccountId + aws:SourceArn: !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:vpc-flow-log/*' Policies: - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' PolicyDocument: From 4cc8243ca4f491dcb40252f825877be9e43e6395 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 17 Oct 2022 17:34:06 -0700 Subject: [PATCH 8/9] fix int tests and fb --- .../template-with-defaultvpc-flowlogs.yml | 13 ++++--------- .../template-with-importedvpc-flowlogs.yml | 13 ++++--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index 350be4fde1d..f61bc678f74 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -998,12 +998,12 @@ Resources: DomainName: !Ref AppDNSName PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID - Flowlog: + FlowLog: Metadata: 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic' Type: AWS::EC2::FlowLog Properties: - DeliverLogsPermissionArn: !GetAtt FlowlogRole.Arn + DeliverLogsPermissionArn: !GetAtt FlowLogRole.Arn LogDestinationType: cloud-watch-logs LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] MaxAggregationInterval: 60 @@ -1011,7 +1011,7 @@ Resources: ResourceType: VPC TrafficType: ALL # Reference to IAM Role policy for Publish flow logs to CloudWatch Logs: https://go.aws/3euClbg - FlowlogRole: + FlowLogRole: Metadata: 'aws:copilot:description': 'An IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group' Type: AWS::IAM::Role @@ -1027,12 +1027,7 @@ Resources: StringEquals: aws:SourceAccount: !Ref AWS::AccountId ArnLike: - aws:SourceArn: - - Fn::Sub: - - 'arn:${partition}:ec2:${region}:${account}:vpc-flow-log/*' - - region: !Ref AWS::Region - partition: !Ref AWS::Partition - account: !Ref AWS::AccountId + aws:SourceArn: !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:vpc-flow-log/*' Policies: - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' PolicyDocument: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml index b525ab43e7e..2f239575d22 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -828,12 +828,12 @@ Resources: DomainName: !Ref AppDNSName PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID - Flowlog: + FlowLog: Metadata: 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic' Type: AWS::EC2::FlowLog Properties: - DeliverLogsPermissionArn: !GetAtt FlowlogRole.Arn + DeliverLogsPermissionArn: !GetAtt FlowLogRole.Arn LogDestinationType: cloud-watch-logs LogGroupName: !Join ['-', [!Ref AppName, !Ref EnvironmentName, FlowLogGroup]] MaxAggregationInterval: 60 @@ -841,7 +841,7 @@ Resources: ResourceType: VPC TrafficType: ALL # Reference to IAM Role policy for flowlogs: https://go.aws/3euClbg - FlowlogRole: + FlowLogRole: Metadata: 'aws:copilot:description': 'An IAM Role for AWS EC2 to publish flow logs to a CloudWatch log group' Type: AWS::IAM::Role @@ -857,12 +857,7 @@ Resources: StringEquals: aws:SourceAccount: !Ref AWS::AccountId ArnLike: - aws:SourceArn: - - Fn::Sub: - - 'arn:${partition}:ec2:${region}:${account}:vpc-flow-log/*' - - region: !Ref AWS::Region - partition: !Ref AWS::Partition - account: !Ref AWS::AccountId + aws:SourceArn: !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:vpc-flow-log/*' Policies: - PolicyName: 'GrantAccesstoEC2toPublishFlowlogs' PolicyDocument: From 3d57a35c371465a3d1d1891e7978634203187423 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Tue, 18 Oct 2022 09:37:10 -0700 Subject: [PATCH 9/9] address iamhopaul123's fb --- internal/pkg/deploy/cloudformation/stack/env.go | 2 +- internal/pkg/template/env.go | 2 +- internal/pkg/template/templates/environment/cf.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/env.go b/internal/pkg/deploy/cloudformation/stack/env.go index bbb36a19048..cb192ceb2f5 100644 --- a/internal/pkg/deploy/cloudformation/stack/env.go +++ b/internal/pkg/deploy/cloudformation/stack/env.go @@ -425,7 +425,7 @@ func (e *EnvStackConfig) vpcConfig() (template.VPCConfig, error) { Managed: e.managedVPC(), AllowVPCIngress: aws.BoolValue(e.in.Mft.HTTPConfig.Private.SecurityGroupsConfig.Ingress.VPCIngress), SecurityGroupConfig: securityGroupConfig, - Flowlogs: aws.BoolValue(e.in.Mft.Network.VPC.Flowlogs), + FlowLogs: aws.BoolValue(e.in.Mft.Network.VPC.Flowlogs), }, nil } diff --git a/internal/pkg/template/env.go b/internal/pkg/template/env.go index efdf24f162c..4a649eff094 100644 --- a/internal/pkg/template/env.go +++ b/internal/pkg/template/env.go @@ -162,7 +162,7 @@ type VPCConfig struct { Managed ManagedVPC AllowVPCIngress bool SecurityGroupConfig *SecurityGroupConfig - Flowlogs bool + FlowLogs bool } // ImportVPC holds the fields to import VPC resources. diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index db6abfc9317..bedf8c79b76 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -557,7 +557,7 @@ Resources: {{include "lambdas" . | indent 2}} {{include "custom-resources" . | indent 2}} {{- end}} -{{- if .VPCConfig.Flowlogs }} +{{- if .VPCConfig.FlowLogs }} FlowLog: Metadata: 'aws:copilot:description': 'A flow log for the VPC to capture information about the IP traffic'