walkthroughs/howto-mutual-tls-file-provided-by-acm/infrastructure/ecs-service.yaml (387 lines of code) (raw):
---
Parameters:
EnvironmentName:
Type: String
Description: Environment name that joins all the stacks
AppMeshMeshName:
Type: String
Description: Name of mesh
EnvoyImageName:
Type: String
Description: The image to use for the Envoy container
ColorTellerImageName:
Description: The name for the color teller image
Type: String
Metadata:
cfn-lint:
config:
ignore_checks:
- E3012
- E3002
Resources:
ColorTellerServiceDiscoveryRecord:
Type: 'AWS::ServiceDiscovery::Service'
Properties:
Name: "colorteller"
DnsConfig:
NamespaceId:
'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceDiscoveryNamespace"
DnsRecords:
- Type: A
TTL: 300
HealthCheckCustomConfig:
FailureThreshold: 1
ColorTellerTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
RequiresCompatibilities:
- 'FARGATE'
Family: 'colorteller'
NetworkMode: 'awsvpc'
Cpu: 256
Memory: 512
TaskRoleArn:
'Fn::ImportValue': !Sub "${EnvironmentName}:TaskIamRoleArn"
ExecutionRoleArn:
'Fn::ImportValue': !Sub "${EnvironmentName}:TaskExecutionIamRoleArn"
ProxyConfiguration:
Type: 'APPMESH'
ContainerName: 'envoy'
ProxyConfigurationProperties:
- Name: 'IgnoredUID'
Value: '1337'
- Name: 'ProxyIngressPort'
Value: '15000'
- Name: 'ProxyEgressPort'
Value: '15001'
- Name: 'AppPorts'
Value: '9080'
- Name: 'EgressIgnoredIPs'
Value: '169.254.170.2,169.254.169.254'
ContainerDefinitions:
- Name: 'app'
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ColorTellerImageName}'
Essential: true
DependsOn:
- ContainerName: 'envoy'
Condition: 'HEALTHY'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group:
Fn::ImportValue: !Sub "${EnvironmentName}:ECSServiceLogGroup"
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: 'colorteller'
PortMappings:
- ContainerPort: 9080
Protocol: 'http'
Environment:
- Name: 'PORT'
Value: 9080
- Name: 'COLOR'
Value: 'yellow'
- Name: envoy
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EnvoyImageName}'
Essential: true
User: '1337'
Ulimits:
- Name: "nofile"
HardLimit: 15000
SoftLimit: 15000
PortMappings:
- ContainerPort: 9901
Protocol: 'tcp'
HealthCheck:
Command:
- 'CMD-SHELL'
- 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE'
Interval: 5
Timeout: 2
Retries: 3
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group:
Fn::ImportValue: !Sub "${EnvironmentName}:ECSServiceLogGroup"
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: 'colorteller'
Environment:
- Name: 'APPMESH_RESOURCE_ARN'
Value: !Sub 'mesh/${AppMeshMeshName}/virtualNode/colorteller-vn'
- Name: AWS_REGION
Value: !Ref 'AWS::Region'
- Name: ENVOY_LOG_LEVEL
Value: warning
# Set secret environment variable for the container
Secrets:
- Name: CertSecret
ValueFrom: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}
ColorTellerService:
Type: 'AWS::ECS::Service'
Properties:
Cluster:
'Fn::ImportValue': !Sub "${EnvironmentName}:ECSCluster"
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 100
DesiredCount: 1
LaunchType: FARGATE
ServiceRegistries:
- RegistryArn:
'Fn::GetAtt': ColorTellerServiceDiscoveryRecord.Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- 'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceSecurityGroup"
Subnets:
- 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet1"
- 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet2"
TaskDefinition: { Ref: ColorTellerTaskDefinition }
GatewayServiceDiscoveryRecord:
Type: 'AWS::ServiceDiscovery::Service'
Properties:
Name: "gateway"
DnsConfig:
NamespaceId:
'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceDiscoveryNamespace"
DnsRecords:
- Type: A
TTL: 300
HealthCheckCustomConfig:
FailureThreshold: 1
GatewayTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
RequiresCompatibilities:
- 'FARGATE'
Family: 'gateway'
NetworkMode: 'awsvpc'
Cpu: 256
Memory: 512
TaskRoleArn:
'Fn::ImportValue': !Sub "${EnvironmentName}:TaskIamRoleArn"
ExecutionRoleArn:
'Fn::ImportValue': !Sub "${EnvironmentName}:TaskExecutionIamRoleArn"
ContainerDefinitions:
- Name: envoy
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EnvoyImageName}'
Essential: true
User: '1337'
Ulimits:
- Name: "nofile"
HardLimit: 15000
SoftLimit: 15000
PortMappings:
- ContainerPort: 9080
Protocol: 'tcp'
- ContainerPort: 9901
Protocol: 'tcp'
HealthCheck:
Command:
- 'CMD-SHELL'
- 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE'
Interval: 5
Timeout: 2
Retries: 3
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group:
Fn::ImportValue: !Sub "${EnvironmentName}:ECSServiceLogGroup"
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: 'gateway'
Environment:
- Name: 'APPMESH_RESOURCE_ARN'
Value: !Sub 'mesh/${AppMeshMeshName}/virtualGateway/gateway-vgw'
- Name: AWS_REGION
Value: !Ref 'AWS::Region'
- Name: ENVOY_LOG_LEVEL
Value: warning
# Set secret environment variable for the container
Secrets:
- Name: CertSecret
ValueFrom: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}
GatewayService:
Type: 'AWS::ECS::Service'
DependsOn:
- PublicLoadBalancer
Properties:
Cluster:
'Fn::ImportValue': !Sub "${EnvironmentName}:ECSCluster"
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 100
DesiredCount: 1
LaunchType: FARGATE
ServiceRegistries:
- RegistryArn:
'Fn::GetAtt': GatewayServiceDiscoveryRecord.Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- 'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceSecurityGroup"
Subnets:
- 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet1"
- 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet2"
LoadBalancers:
- ContainerName: envoy
ContainerPort: 9080
TargetGroupArn: !Ref WebTargetGroup
TaskDefinition: { Ref: GatewayTaskDefinition }
# public NLB for gateway
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
Type: network
Subnets:
- { 'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet1" }
- { 'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet2" }
WebTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn:
- PublicLoadBalancer
Properties:
Protocol: TCP
TargetType: ip
Name: !Sub "${EnvironmentName}-web2"
Port: 80
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 120
VpcId:
'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
PublicLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref WebTargetGroup
Type: 'forward'
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: TCP
CertExpirationEvent:
Type: AWS::Events::Rule
Properties:
Description: "AWS ACM Certificate Expiration Event for Color Teller and Color Gateway"
EventPattern:
source:
- aws.acm
resources:
-
'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerEndpointCertArn"
-
'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayEndpointCertArn"
detail-type:
- ACM Certificate Approaching Expiration
State: "ENABLED"
Targets:
-
Arn: !GetAtt CertRotateFunction.Arn
Id: "TargetFunctionV1"
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref CertRotateFunction
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn: !GetAtt CertExpirationEvent.Arn
SourceAccount: !Ref 'AWS::AccountId'
CertRotateRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: acm-secretsmanager-access
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- acm:ExportCertificate
- acm:RenewCertificate
Resource:
-
'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayEndpointCertArn"
-
'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerEndpointCertArn"
- Effect: Allow
Action:
- secretsmanager:GetRandomPassword
Resource: '*'
- Effect: Allow
Action: secretsmanager:PutSecretValue
Resource: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}
- Effect: Allow
Action: ecs:UpdateService
Resource:
- !Ref GatewayService
- !Ref ColorTellerService
CertRotateFunction:
Type: AWS::Lambda::Function
Properties:
Description: Initial function to populate secrets manager from ACM
Handler: index.lambda_handler
Role: !GetAtt CertRotateRole.Arn
Runtime: python3.8
Timeout: 900
Environment:
Variables:
COLOR_GATEWAY_ACM_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayEndpointCertArn"}
COLOR_TELLER_ACM_PCA_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerRootCAArn"}
COLOR_GATEWAY_ACM_PCA_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayRootCAArn"}
COLOR_TELLER_ACM_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerEndpointCertArn"}
CLUSTER: {'Fn::ImportValue': !Sub "${EnvironmentName}:ECSCluster"}
SVC_TELLER: !Ref GatewayService
SVC_GATEWAY: !Ref ColorTellerService
SECRET: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}
Code:
ZipFile: |
import json
import boto3
import base64
import time
import os
sm = boto3.client('secretsmanager')
cm = boto3.client('acm')
ecs = boto3.client('ecs')
ecs_cluster = os.environ['CLUSTER']
color_gateway_svc = os.environ['SVC_GATEWAY']
color_teller_svc = os.environ['SVC_TELLER']
gate_cm = os.environ['COLOR_GATEWAY_ACM_ARN']
teller_cm = os.environ['COLOR_TELLER_ACM_ARN']
secret = os.environ['SECRET']
def lambda_handler(event, context):
print (json.dumps(event))
cm.renew_certificate(CertificateArn=teller_cm)
cm.renew_certificate(CertificateArn=gate_cm)
time.sleep(5) # allow time for acm to renew cert from acm-pca
passphrase = sm.get_random_password(ExcludePunctuation=True)['RandomPassword']
passphrase_enc = base64.b64encode(passphrase.encode('utf-8'))
cm.export_certificate(CertificateArn=teller_cm, Passphrase=passphrase_enc)
gate_rsp = cm.export_certificate(CertificateArn=gate_cm, Passphrase=passphrase_enc)
sm_value={}
sm_value['GatewayCertificate']=gate_rsp['Certificate']
sm_value['GatewayCertificateChain']=gate_rsp['CertificateChain']
sm_value['GatewayPrivateKey']=gate_rsp['PrivateKey']
sm_value['Passphrase']=passphrase
sm.put_secret_value(SecretId=secret, SecretString=json.dumps(sm_value))
ecs.update_service(
cluster=ecs_cluster,
service=color_teller_svc,
forceNewDeployment=True)
ecs.update_service(
cluster=ecs_cluster,
service=color_gateway_svc,
forceNewDeployment=True)
Outputs:
ColorAppEndpoint:
Description: Public endpoint for Color App service
Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]