Search code examples
amazon-web-servicesamazon-s3cloudaws-cloudformationaws-security-group

How do I attribute values to parameters created within stacks in a template?


I'm getting the following error when deploying my CloudFormation template.

✖ Failed to create the stack.
An error occurred (ValidationError) when calling the CreateStack operation: Parameters: [ClusterArn, TaskRoleArn, Env, Subnets, ExecRoleArn, ClusterLogGroup, ContainerSG] must have values

I don't know how to attribute values to the parameters specified because they are being created within the stack. For instance, the ContainerSG and LogGroup :

Parameters:

  ContainerSG:
    Description: The container security group
    Type: String

  ClusterLogGroup:
    Description: The cluster log group
    Type: String

Resources

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join [ '/', ['/aws', 'ecs', !Ref Env]]

  ContainerSecurityGroup:
    Type :  AWS::EC2::SecurityGroup
    Properties : 
       GroupDescription :  "ECS Containers Security Group"
       VpcId :  '{{resolve:ssm:/ca/config/network/vpc_id:1}}'
       GroupName: security-group-cmr
       SecurityGroupIngress :
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.49.63.0/24
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.93.0.0/16
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.97.0.0/16
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.50.128.0/21
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.50.144.0/24
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  172.25.0.0/16

The bottom-line question is, how do I attribute values to parameters that are being created along with stacks inside a CloudFormation Template?

Would appreciate someone's help on this. Thank you.

EDIT : This is the complete cloudformation file, some of the parameters (memory, buildnumber, etc) are specified within the pipeline so that's the reason they don't need any values attributed to them within the template :

Description: "Generic Research Template for Application ECS Cluster with an ECS Service"
Parameters:
  Env:
    Description: the runtime environment
    Type: String
    AllowedValues:
      - dev
      - uat
      - prod
  ServiceName:
    Description: The service name
    Type: String
  BuildNumber:
    Description: The bitbucket build number
    Type: String
  AppCode:
    Description: The application's app code
    Type: String
  Subnets:
    Description: Choose which db subnets for the instance
    Type: List<AWS::EC2::Subnet::Id>
  ContainerPort:
    Description: The container port
    Type: String
    Default: 8080
  DesiredCount:
    Description: The number of desired tasks for this service
    Type: Number
    Default: 1
  ClusterLogGroup:
    Description: The cluster log group
    Type: String
  Cpu:
    Description: allocated cpu credits
    Type: Number
    Default: 1024
  Memory:
    Description: hard cap for memory
    Type: String
    Default: 2048
  ClusterArn:
    Description: The ECS Cluster ARN
    Type: String
  ExecRoleArn:
    Description: The ARN of the cluster execution role
    Type: String
  TaskRoleArn:
    Description: The ARN of the cluster task role
    Type: String
  ContainerSG:
    Description: The container security group
    Type: String
  ExcludeTaskDef:
    Description: Flag indiciating that the task definition resource should not be created as part of this stack
    Type: Number
    AllowedValues:
      - 0
      - 1
    Default: 0
Conditions:
  IncludeTaskDef: !Equals [!Ref ExcludeTaskDef, 0]
Resources:  
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: !Sub ${Env}-${AppCode}-${ServiceName}-service
      Cluster: !Ref ClusterArn
      TaskDefinition: !Ref TaskDefinition
      DeploymentConfiguration:
        MinimumHealthyPercent: 0
        MaximumPercent: 100
      DesiredCount: !Ref DesiredCount
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          Subnets:
            - !Select [0, !Ref Subnets ]
            - !Select [1, !Ref Subnets ]
          SecurityGroups:
            - !Ref ContainerSG
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Condition: IncludeTaskDef
    Properties:
      Family: !Sub ${Env}-${AppCode}-${ServiceName}
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: !Ref Cpu
      Memory: !Ref Memory
      ExecutionRoleArn: !Ref ExecRoleArn
      TaskRoleArn: !Ref TaskRoleArn
      ContainerDefinitions:
        - Name: !Sub ${Env}-${AppCode}-${ServiceName}
          Image: !Sub 558043318296.dkr.ecr.us-east-1.amazonaws.com/com.cambridge.gir/${AppCode}-${ServiceName}:${BuildNumber}
          Essential: True
          PortMappings:
            - ContainerPort: !Ref ContainerPort
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ClusterLogGroup
              awslogs-region: us-east-1
              awslogs-stream-prefix: !Ref ServiceName
          Environment:
            - Name: Environment
              Value: !Ref Env
            - Name: Timezone
              Value: "-Duser.timezone=America/New_York"
  ClusterKmsKey:
    Type: AWS::KMS::Key
    Properties:
      Description: "Encrypts secrets for apps in the cluster"
      KeyPolicy:
        Version: '2012-10-17'
        Id: !Sub ${Env}-${AppCode}-kms-key
        Statement:
          - Sid: 'Enable IAM User Permission'
            Effect: 'Allow'
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action:
              - kms:*
            Resource: '*'
          - Sid: 'Allow ECS Tasks to use the key'
            Effect: 'Allow'
            Principal:
              Service: 'ecs-tasks.amazonaws.com'
            Action:
              - 'kms:GenerateDataKey'
              - 'kms:Encrypt'
              - 'kms:Decrypt'
            Resource: '*'
          - Sid: 'Allow SNS to use the key'
            Effect: 'Allow'
            Principal:
              Service: 'sns.amazonaws.com'
            Action:
              - 'kms:GenerateDataKey'
              - 'kms:Encrypt'
              - 'kms:Decrypt'
            Resource: '*'
          - Sid: 'Allow SQS to use the key'
            Effect: 'Allow'
            Principal:
              Service: 'sqs.amazonaws.com'
            Action:
              - 'kms:GenerateDataKey'
              - 'kms:Encrypt'
              - 'kms:Decrypt'
            Resource: '*'
          - Sid: 'Allow ES to use the key'
            Effect: 'Allow'
            Principal:
              Service: 'es.amazonaws.com'
            Action:
              - 'kms:GenerateDataKey'
              - 'kms:Encrypt'
              - 'kms:Decrypt'
            Resource: '*'
  EcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${Env}-${AppCode}-ecs-cluster
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join [ '/', ['/aws', 'ecs', !Ref Env, !Ref AppCode]]
  ContainerSecurityGroup:
    Type :  AWS::EC2::SecurityGroup
    Properties : 
       GroupDescription :  "ECS Containers Security Group"
       VpcId :  '{{resolve:ssm:/ca/config/network/vpc_id:1}}'
       GroupName :  !Sub   ${Env}-${ServiceName}-sg
       SecurityGroupIngress :
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.49.63.0/24
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.93.0.0/16
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.97.0.0/16
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.50.128.0/21
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  10.50.144.0/24
        -  IpProtocol :  tcp
           FromPort :  8080
           ToPort :  8080
           CidrIp :  172.25.0.0/16
  ClusterExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${Env}-${AppCode}-execution-role
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
        - 'arn:aws:iam::aws:policy/AmazonSSMFullAccess'
        - 'arn:aws:iam::aws:policy/SecretsManagerReadWrite'
      Policies:
        - PolicyName: !Sub ${Env}-${AppCode}-execution-role-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: AllowSecretsAccessForContainers
                Effect: Allow
                Action:
                  - 'kms:Decrypt'
                  - 'ssm:GetParameters'
                Resource:
                  - !Sub "arn:aws:ssm:*:*:parameter/${Env}/${AppCode}/*"
                  - !GetAtt ClusterKmsKey.Arn
              - Sid: AllowECRPull
                Effect: Allow
                Action:
                  - "ecr:GetAuthorizationToken"
                  - "ecr:BatchCheckLayerAvailability"
                  - "ecr:GetDownloadUrlForLayer"
                  - "ecr:BatchGetImage"
                Resource:
                  - "*"
  FargateTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${Env}-${AppCode}-fargate-task-role
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'

Solution

  • If it is all in one file, you don't need any parameters.

    You can always go to the return section of all the resources in the CloudFormation documentation. In this case we can see that a LogGroup returns it's name when you use the Ref function. Sometimes you can use the Ref function, sometimes you need to use the Fn:GetAtt function if you want to get some more exotic properties of resources.

    This cheat sheet is also really helpful to see when to use Ref or when to use GetAtt.

    In your case you can for example just use !Ref LogGroup to get the name of your log group or !Ref ContainerSecurityGroup to get the name of the security group.