From 3e30f2cd1a3991d09c49f76b8ba5a0656cd3480f Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 4 May 2020 15:09:08 +0200 Subject: [PATCH] Create CloudWatch LogGroup and IAM TaskExecutionRole As part of the CloudFormation template, create a LogGroup and configure task with awslogs log-driver. Also create a dedicated IAM Role, with AmazonECSTaskExecutionRolePolicy. This one will later be fine-tuned to grant access to secrets/config and other AWS resources according to custom extensions close #42 Signed-off-by: Nicolas De Loof --- ecs/pkg/amazon/cloudformation.go | 26 +++++++++++++----- ecs/pkg/amazon/iam.go | 31 ++++++++++++++++++++++ ecs/pkg/amazon/sdk.go | 3 +++ ecs/pkg/convert/convert.go | 45 +++++++++++++++++++------------- 4 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 ecs/pkg/amazon/iam.go diff --git a/ecs/pkg/amazon/cloudformation.go b/ecs/pkg/amazon/cloudformation.go index e5c4cd71b..40bf8eddd 100644 --- a/ecs/pkg/amazon/cloudformation.go +++ b/ecs/pkg/amazon/cloudformation.go @@ -6,15 +6,17 @@ import ( "fmt" "strings" - "github.com/compose-spec/compose-go/types" - "github.com/sirupsen/logrus" + "github.com/awslabs/goformation/v4/cloudformation/logs" ecsapi "github.com/aws/aws-sdk-go/service/ecs" "github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation/ec2" "github.com/awslabs/goformation/v4/cloudformation/ecs" + "github.com/awslabs/goformation/v4/cloudformation/iam" + "github.com/compose-spec/compose-go/types" "github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/convert" + "github.com/sirupsen/logrus" ) func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudformation.Template, error) { @@ -50,22 +52,32 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo VpcId: vpc, } + logGroup := fmt.Sprintf("/docker-compose/%s", project.Name) + template.Resources["LogGroup"] = &logs.LogGroup{ + LogGroupName: logGroup, + } + for _, service := range project.Services { definition, err := convert.Convert(project, service) if err != nil { return nil, err } - role, err := c.GetEcsTaskExecutionRole(ctx, service) - if err != nil { - return nil, err + taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name) + template.Resources[taskExecutionRole] = &iam.Role{ + AssumeRolePolicyDocument: assumeRolePolicyDocument, + // Here we can grant access to secrets/configs using a Policy { Allow,ssm:GetParameters,secret|config ARN} + ManagedPolicyArns: []string{ + ECSTaskExecutionPolicy, + }, } - definition.TaskRoleArn = role + definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole) + // FIXME definition.TaskRoleArn = ? taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name) template.Resources[taskDefinition] = definition - template.Resources[service.Name] = &ecs.Service{ + template.Resources[fmt.Sprintf("%sService", service.Name)] = &ecs.Service{ Cluster: c.Cluster, DesiredCount: 1, LaunchType: ecsapi.LaunchTypeFargate, diff --git a/ecs/pkg/amazon/iam.go b/ecs/pkg/amazon/iam.go new file mode 100644 index 000000000..38c4d8339 --- /dev/null +++ b/ecs/pkg/amazon/iam.go @@ -0,0 +1,31 @@ +package amazon + +var assumeRolePolicyDocument = PolicyDocument{ + Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html + Statement: []PolicyStatement{ + { + Effect: "Allow", + Principal: PolicyPrincipal{ + Service: "ecs-tasks.amazonaws.com", + }, + Action: []string{"sts:AssumeRole"}, + }, + }, +} + +// could alternatively depend on https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/master/pkg/cloud/services/iam/types.go#L52 +type PolicyDocument struct { + Version string `json:",omitempty"` + Statement []PolicyStatement `json:",omitempty"` +} + +type PolicyStatement struct { + Effect string `json:",omitempty"` + Action []string `json:",omitempty"` + Principal PolicyPrincipal `json:",omitempty"` + Resource []string `json:",omitempty"` +} + +type PolicyPrincipal struct { + Service string `json:",omitempty"` +} diff --git a/ecs/pkg/amazon/sdk.go b/ecs/pkg/amazon/sdk.go index 29ca7f7bd..89d3ec319 100644 --- a/ecs/pkg/amazon/sdk.go +++ b/ecs/pkg/amazon/sdk.go @@ -180,6 +180,9 @@ func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template StackName: aws.String(name), TemplateBody: aws.String(string(json)), TimeoutInMinutes: aws.Int64(10), + Capabilities: []*string{ + aws.String(cloudformation.CapabilityCapabilityIam), + }, }) return err } diff --git a/ecs/pkg/convert/convert.go b/ecs/pkg/convert/convert.go index c7a8cab87..8b70a6488 100644 --- a/ecs/pkg/convert/convert.go +++ b/ecs/pkg/convert/convert.go @@ -5,6 +5,7 @@ import ( "time" ecsapi "github.com/aws/aws-sdk-go/service/ecs" + "github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation/ecs" "github.com/compose-spec/compose-go/types" "github.com/docker/cli/opts" @@ -21,24 +22,32 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe ContainerDefinitions: []ecs.TaskDefinition_ContainerDefinition{ // Here we can declare sidecars and init-containers using https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_dependson { - Command: service.Command, - Cpu: 256, - DisableNetworking: service.NetworkMode == "none", - DnsSearchDomains: service.DNSSearch, - DnsServers: service.DNS, - DockerLabels: nil, - DockerSecurityOptions: service.SecurityOpt, - EntryPoint: service.Entrypoint, - Environment: toKeyValuePair(service.Environment), - Essential: true, - ExtraHosts: toHostEntryPtr(service.ExtraHosts), - FirelensConfiguration: nil, - HealthCheck: toHealthCheck(service.HealthCheck), - Hostname: service.Hostname, - Image: service.Image, - Interactive: false, - Links: nil, - LinuxParameters: toLinuxParameters(service), + Command: service.Command, + Cpu: 256, + DisableNetworking: service.NetworkMode == "none", + DnsSearchDomains: service.DNSSearch, + DnsServers: service.DNS, + DockerLabels: nil, + DockerSecurityOptions: service.SecurityOpt, + EntryPoint: service.Entrypoint, + Environment: toKeyValuePair(service.Environment), + Essential: true, + ExtraHosts: toHostEntryPtr(service.ExtraHosts), + FirelensConfiguration: nil, + HealthCheck: toHealthCheck(service.HealthCheck), + Hostname: service.Hostname, + Image: service.Image, + Interactive: false, + Links: nil, + LinuxParameters: toLinuxParameters(service), + LogConfiguration: &ecs.TaskDefinition_LogConfiguration{ + LogDriver: ecsapi.LogDriverAwslogs, + Options: map[string]string{ + "awslogs-region": cloudformation.Ref("AWS::Region"), + "awslogs-group": cloudformation.Ref("LogGroup"), + "awslogs-stream-prefix": service.Name, + }, + }, Memory: toMemoryLimits(service.Deploy), MemoryReservation: toMemoryReservation(service.Deploy), MountPoints: nil,