# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: "2010-09-09" Description: "CloudFormation template that represents a task on Amazon ECS." Parameters: TaskName: Type: String TaskCPU: Type: String TaskMemory: Type: String LogRetention: Type: Number ContainerImage: Type: String TaskRole: Type: String Command: Type: CommaDelimitedList EntryPoint: Type: CommaDelimitedList EnvFileARN: Type: String OS: Type: String Arch: Type: String Conditions: # NOTE: Image cannot be pushed until the ECR repo is created, at which time ContainerImage would be "". HasImage: !Not [!Equals [!Ref ContainerImage, ""]] HasTaskRole: !Not [!Equals [!Ref TaskRole, ""]] HasCommand: !Not [!Equals [ !Join ["", !Ref Command], ""]] HasEntryPoint: !Not [ !Equals [ !Join [ "", !Ref EntryPoint ], "" ] ] # NOTE: Env file cannot be pushed until the S3 bucket is created, at which time the EnvFileARN would be "". HasEnvFile: !Not [!Equals [!Ref EnvFileARN, ""]] HasCustomPlatform: !Not [!Equals [!Ref OS, ""]] Resources: TaskDefinition: Metadata: 'aws:copilot:description': 'An ECS task definition to run your container on ECS' Condition: HasImage # NOTE: We only create TaskDefinition if an image is provided Type: AWS::ECS::TaskDefinition DependsOn: ECRRepo Properties: ContainerDefinitions: - Image: !Ref ContainerImage EntryPoint: !If [HasEntryPoint, !Ref EntryPoint, !Ref "AWS::NoValue"] EnvironmentFiles: - !If - HasEnvFile - Type: "s3" Value: !Ref EnvFileARN - !Ref "AWS::NoValue" Command: !If [HasCommand, !Ref Command, !Ref "AWS::NoValue"] LogConfiguration: LogDriver: awslogs Options: awslogs-region: !Ref AWS::Region awslogs-group: !Ref LogGroup awslogs-stream-prefix: copilot-task Name: !Ref TaskName{{if .EnvVars}} Environment:{{range $name, $value := .EnvVars}} - Name: {{$name}} Value: {{$value | printf "%q"}}{{end}}{{end}} {{- if or .SSMParamSecrets .SecretsManagerSecrets}} Secrets:{{range $name, $valueFrom := .SSMParamSecrets}} - Name: {{$name}} ValueFrom: {{$valueFrom | printf "%q"}}{{end}} {{- range $name, $valueFrom := .SecretsManagerSecrets}} - Name: {{$name}} ValueFrom: {{$valueFrom | printf "%q"}}{{end}} {{- end}} Family: !Join ['-', ["copilot", !Ref TaskName]] RuntimePlatform: !If [HasCustomPlatform, {OperatingSystemFamily: !Ref OS, CpuArchitecture: !Ref Arch}, !Ref "AWS::NoValue"] RequiresCompatibilities: - "FARGATE" NetworkMode: awsvpc Cpu: !Ref TaskCPU Memory: !Ref TaskMemory ExecutionRoleArn: {{- if eq .ExecutionRole "" }} !GetAtt DefaultExecutionRole.Arn {{- else }} {{.ExecutionRole}} {{- end }} TaskRoleArn: !If [HasTaskRole, !Ref TaskRole, !GetAtt DefaultTaskRole.Arn] {{- if eq .ExecutionRole "" }} DefaultExecutionRole: Metadata: 'aws:copilot:description': 'An IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for the Fargate agent to make AWS API calls on your behalf' Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy' {{- if .PermissionsBoundary}} PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/{{.PermissionsBoundary}}' {{- end}} Policies: - !If # Optional IAM permission required by ECS task def env file # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html#taskdef-envfiles-iam # Example EnvFileARN: arn:aws:s3:::stackset-demo-infrastruc-pipelinebuiltartifactbuc-11dj7ctf52wyf/manual/1638391936/env - HasEnvFile - PolicyName: !Join [ "", [ !Ref TaskName, GetEnvFilePolicy, ], ] PolicyDocument: Version: '2012-10-17' Statement: - Effect: "Allow" Action: - "s3:GetObject" Resource: - !Ref EnvFileARN - Effect: "Allow" Action: - "s3:GetBucketLocation" Resource: - !Join - "" - - "arn:" - !Ref AWS::Partition - ":s3:::" - !Select [ 0, !Split [ "/", !Select [ 5, !Split [ ":", !Ref EnvFileARN ] ], ], ] - !Ref AWS::NoValue {{- if or .SSMParamSecrets .SecretsManagerSecrets}} {{- if and .App .Env }} - PolicyName: 'PullSecrets' PolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Action: - ssm:GetParameters Condition: StringEquals: "ssm:ResourceTag/copilot-application": {{ .App }} "ssm:ResourceTag/copilot-environment": {{ .Env }} Resource: - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - Effect: 'Allow' Action: - secretsmanager:GetSecretValue Condition: StringEquals: "secretsmanager:ResourceTag/copilot-application": {{ .App }} "secretsmanager:ResourceTag/copilot-environment": {{ .Env }} Resource: - !Sub arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:* {{- else }} - PolicyName: 'PullSecrets' PolicyDocument: Version: '2012-10-17' Statement: {{- if .SSMParamSecrets }} - Effect: 'Allow' Action: - ssm:GetParameters Resource: {{range $name, $valueFrom := .SSMParamSecrets}} {{- if not (isARN $valueFrom) }} - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/{{ trimSlashPrefix $valueFrom}} {{- else }} - {{ $valueFrom }} {{- end }} {{ end }} {{- end }} {{- if .SecretsManagerSecrets }} - Effect: 'Allow' Action: - secretsmanager:GetSecretValue Resource: {{range $name, $valueFrom := .SecretsManagerSecrets}} - {{$valueFrom}} {{ end }} {{- end }} {{- end }} {{- end }} {{- end }} DefaultTaskRole: Metadata: 'aws:copilot:description': 'An IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for the task to make AWS API calls on your behalf. Policies are required by ECS Exec' Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: 'sts:AssumeRole' {{- if .PermissionsBoundary}} PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/{{.PermissionsBoundary}}' {{- end}} Policies: - PolicyName: 'ExecuteCommand' PolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Action: [ "ssmmessages:CreateControlChannel", "ssmmessages:OpenControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenDataChannel" ] Resource: "*" - Effect: 'Allow' Action: [ "logs:CreateLogStream", "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:PutLogEvents" ] Resource: "*" ECRRepo: Metadata: 'aws:copilot:description': 'An ECR repository to store your container images' Type: AWS::ECR::Repository Properties: RepositoryName: !Join ["-", ["copilot", !Ref TaskName]] RepositoryPolicyText: Version: '2012-10-17' Statement: - Sid: AllowPushPull Effect: Allow Principal: AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root Action: - ecr:GetDownloadUrlForLayer - ecr:BatchGetImage - ecr:BatchCheckLayerAvailability - ecr:PutImage - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload LifecyclePolicy: # TODO: inject the JSON string instead of hard-coding it here LifecyclePolicyText: "{\"rules\":[{\"rulePriority\":1,\"selection\":{\"tagStatus\":\"untagged\",\"countType\":\"sinceImagePushed\",\"countUnit\":\"days\",\"countNumber\":5},\"action\":{\"type\":\"expire\"}}]}" LogGroup: Metadata: 'aws:copilot:description': 'A CloudWatch log group to hold your task logs' Type: AWS::Logs::LogGroup Properties: LogGroupName: !Join ['', ["/copilot/", !Ref TaskName]] RetentionInDays: !Ref LogRetention S3Bucket: Metadata: 'aws:copilot:description': 'An S3 bucket to hold .env files' Type: AWS::S3::Bucket Properties: VersioningConfiguration: Status: Enabled BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true LifecycleConfiguration: Rules: # .env files are only needed on the initial RunTask call and are not needed after that. # This prevents them from piling up (hopefully it does not take 1 day to build the docker image). - Id: DeleteEnvFilesRule Status: Enabled Prefix: 'manual/env-files' ExpirationInDays: 1 S3BucketPolicy: Metadata: 'aws:copilot:description': 'A policy to allow file uploads to the S3 bucket' Type: AWS::S3::BucketPolicy DependsOn: S3Bucket Properties: Bucket: !Ref S3Bucket PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:* Effect: Allow Resource: - !Sub arn:${AWS::Partition}:s3:::${S3Bucket} - !Sub arn:${AWS::Partition}:s3:::${S3Bucket}/* Principal: AWS: - !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root Outputs: ECRRepo: Description: ECR Repo used to store images of task. Value: !GetAtt ECRRepo.Arn S3Bucket: Description: S3 Bucket used to store env files. Value: !Ref S3Bucket