ehime
ehime

Reputation: 8415

Cloudformation Template validation error?

I am receiving the follow error but do not know where it is coming from, hopefully someone can help.

Template validation error: Template format error: Any Properties member must be a JSON object.

The cloudformation script

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application's resources outside of Lambdas and Api Gateway",
  "Resources": {
    "KMSKey": {
      "Type": "AWS::KMS::Key",
      "Properties": {
        "Description": "KMS Key Dev",
        "Enabled": "True",
        "EnableKeyRotation": "True",
        "KeyPolicy": {
          "Version": "2012-10-17",
          "Id": "key-default-1",
          "Statement": {
            "Effect": "Allow",
            "Action": [
              "kms:Encrypt",
              "kms:Decrypt",
              "kms:ReEncrypt*",
              "kms:GenerateDataKey*",
              "kms:DescribeKey"
            ],
            "Resource": "*"
          }
        }
      }
    },
    "IamRoleLambda": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "*"
      }
    },
    "IamPolicyLambda": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "dev-lambda",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": "arn:aws:logs:us-east-1:*"
            },
            {
              "Effect": "Allow",
              "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
              ],
              "Resource": {
                "Ref": "KMSKey"
              }
            },
            {
              "Effect": "Allow",
              "Action": [
                "ec2:CreateNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DetachNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "elastiCache:*"
              ],
              "Resource": "*"
            },
            {
              "Effect": "Allow",
              "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
              ],
              "Resource": [
                "arn:aws:s3:::{domain.com}/",
                "arn:aws:s3:::{domain.com}/Serverless/*"
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "IamRoleLambda"
          }
        ]
      }
    },
    "RedisCluster": {
      "Type": "AWS::ElastiCache::CacheCluster",
      "Properties": {
        "AutoMinorVersionUpgrade": "False",
        "AZMode": "cross-az",
        "CacheNodeType": "cache.m3.medium",
        "VpcSecurityGroupIds": {
          "Ref": "VpcSecurityGroup"
        },
        "ClusterName": "Dev",
        "Engine": "redis",
        "EngineVersion": "2.8",
        "NumCacheNodes": "1",
        "Tags": [
          {
            "Key": "CostCenter",
            "Value": "0000000000000000"
          },
          {
            "Key": "Application",
            "Value": "Appname"
          },
          {
            "Key": "Function",
            "Value": "cache"
          },
          {
            "Key": "Environment",
            "Value": "dev"
          }
        ]
      }
    },
    "VpcSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "DEV VPC Security group form",
        "SecurityGroupEgress": {
          "Ref": "SecurityGroupEgress"
        },
        "SecurityGroupInress": {
          "Ref": "SecurityGroupIngress"
        },
        "Tags": [
          {
            "Key": "CostCenter",
            "Value": "0000000000000000"
          },
          {
            "Key": "Application",
            "Value": "Appname"
          },
          {
            "Key": "Function",
            "Value": "cache"
          },
          {
            "Key": "Environment",
            "Value": "dev"
          }
        ],
        "VpcId": "vpc-8c3113e2"
      }
    },
    "SecurityGroupEgress": {
      "Type": "AWS::EC2::SecurityGroupEgress",
      "Properties": [
        {
          "CidrIp": "0.0.0.0/0",
          "FromPort": "443",
          "ToPort": "443",
          "IpProtocol": "tcp"
        },
        {
          "CidrIp": "0.0.0.0/0",
          "FromPort": "80",
          "ToPort": "80",
          "IpProtocol": "tcp"
        },
        {
          "DestinationSecurityGroupId": {
            "Fn:GetAtt": [
              "VpcSecurityGroup"
            ]
          },
          "FromPort": "6379",
          "ToPort": "6379",
          "IpProtocol": "tcp"
        }
      ]
    },
    "SecurityGroupIngress": {
      "Type": "AWS::EC2::SecurityGroupIngress",
      "Properties": [
        {
          "CidrIp": "0.0.0.0/0",
          "FromPort": "443",
          "ToPort": "443",
          "IpProtocol": "tcp"
        },
        {
          "CidrIp": "0.0.0.0/0",
          "FromPort": "80",
          "ToPort": "80",
          "IpProtocol": "tcp"
        },
        {
          "DestinationSecurityGroupId": {
            "Fn:GetAtt": [
              "VpcSecurityGroup"
            ]
          },
          "FromPort": "6379",
          "ToPort": "6379",
          "IpProtocol": "tcp"
        }
      ]
    }
  },
  "Outputs": {
    "IamRoleArnLambda": {
      "Description": "ARN of the lambda IAM role",
      "Value": {
        "Fn::GetAtt": [
          "IamRoleLambda",
          "Arn"
        ]
      }
    }
  }
}

Upvotes: 0

Views: 2623

Answers (1)

wjordan
wjordan

Reputation: 20390

The error describes that the Properties member requires a JSON object. Looking at your code, two of your Properties members are defined as JSON arrays. The underlying issue is that AWS::EC2::SecurityGroupIngress and AWS::EC2::SecurityGroupEgress resources define a single security group rule, whereas you're attempting to define multiple rules within a single resource.

I see that you're trying to self-reference the security group for a custom port (6379), in addition to fully-open ports 80 and 443. As noted in the documentation, this is the correct use-case for using AWS::EC2::SecurityGroupEgress and AWS::EC2::SecurityGroupIngress resources:

Important

If you want to cross-reference two security groups in the ingress and egress rules of those security groups, use the AWS::EC2::SecurityGroupEgress and AWS::EC2::SecurityGroupIngress resources to define your rules. Do not use the embedded ingress and egress rules in the AWS::EC2::SecurityGroup. If you do, it causes a circular dependency, which AWS CloudFormation doesn't allow.

To accomplish this without defining several additional resources, you can leave the fully-open port rules inline with the resource (since they don't cause a circular dependency), and just create additional AWS::EC2::SecurityGroup[In|E]gress resources for the ones you want locked down to the security group:

"VpcSecurityGroup": {
  "Type": "AWS::EC2::SecurityGroup",
  "Properties": {
    "GroupDescription": "DEV VPC Security group form",
    "SecurityGroupEgress": [
      {
        "CidrIp": "0.0.0.0/0",
        "FromPort": "443",
        "ToPort": "443",
        "IpProtocol": "tcp"
      },
      {
        "CidrIp": "0.0.0.0/0",
        "FromPort": "80",
        "ToPort": "80",
        "IpProtocol": "tcp"
      }
    ],
    "SecurityGroupIngress": [
      {
        "CidrIp": "0.0.0.0/0",
        "FromPort": "443",
        "ToPort": "443",
        "IpProtocol": "tcp"
      },
      {
        "CidrIp": "0.0.0.0/0",
        "FromPort": "80",
        "ToPort": "80",
        "IpProtocol": "tcp"
      }
    ],
    "Tags": [
      {
        "Key": "CostCenter",
        "Value": "0000000000000000"
      },
      {
        "Key": "Application",
        "Value": "Appname"
      },
      {
        "Key": "Function",
        "Value": "cache"
      },
      {
        "Key": "Environment",
        "Value": "dev"
      }
    ],
    "VpcId": "vpc-8c3113e2"
  }
},
"SecurityGroupEgress": {
  "Type": "AWS::EC2::SecurityGroupEgress",
  "Properties": {
    "DestinationSecurityGroupId": {
      "Fn::GetAtt": [
        "VpcSecurityGroup",
        "GroupId"
      ]
    },
    "FromPort": "6379",
    "ToPort": "6379",
    "IpProtocol": "tcp"
  }
},
"SecurityGroupIngress": {
  "Type": "AWS::EC2::SecurityGroupIngress",
  "Properties": {
    "SourceSecurityGroupId": {
      "Fn::GetAtt": [
        "VpcSecurityGroup",
        "GroupId"
      ]
    },
    "FromPort": "6379",
    "ToPort": "6379",
    "IpProtocol": "tcp"
  }
}
[...etc...]

You'll also want to make sure to be careful with the spelling and syntax of the Fn::GetAtt function, which I've corrected above for you.

Upvotes: 1

Related Questions