4

Edit

Original details below.

In the process of chasing this down, I've now narrowed it down to the fact that this security group

  DatabaseSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: WantsMail
    Properties:
      GroupDescription: Security group for RDS Databases
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref MailSecurityGroup
      VpcId:
        !Ref Vpc

is not being created, even though WantsMail is evaluating as true. I know WantsMail is evaluating as true because I changed the Outputs section:

Outputs:
...
  DatabaseSecurityGroup:
    Condition: WantsMail
    Description: Security group for RDS database access
    Value: Foo
    Export:
      Name: FooName

Not only does that get output correctly, but the MailSecurityGroup (which also is conditional on WantsMail) gets created. However, this security group does not. Nothing showing any YAML errors. No errors that I can find in the AWS CloudFormation UI. Any ideas either on what's wrong, or how to track down whatever error might be occurring?

Aside: Is this sort of edit the right thing to do here? Or should I have killed the original question and asked this more specific one, or ... ?


Original Details

I'm working on breaking my CloudFormation templates into functional stacks. Not yet to the point of doing any nesting, just doing everything self contained. For my security-groups template, I have something very simple working, but when I start to add conditions, I start getting an "Unresolved resource dependencies ... in the Outputs block of the template". I don't think there are any typos, and that along with things having to do with nesting seem to be the most common problems I've seen in other questions similar to this.

The basic, working template looks like this:

---
AWSTemplateFormatVersion: 2010-09-09

Description: 'Create Security Groups'

Metadata:

  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: AWS Parameters
      Parameters:
        - SshAccessCidr
        - Vpc
    ParameterLabels:
      SshAccessCidr:
        default: SSH Access From
      Vpc:
        default: Vpc Id

Parameters:

  SshAccessCidr:
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Description: The CIDR IP range that is permitted to SSH to bastion instance. Note - a value of 0.0.0.0/0 will allow access from ANY IP address.
    Type: String
    Default: 0.0.0.0/0
  Vpc:
    AllowedPattern: ^(vpc-)([a-z0-9]{8}|[a-z0-9]{17})$
    Description: The Vpc Id of an existing Vpc.
    Type: AWS::EC2::VPC::Id

Resources:

  BastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Bastion instances
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SshAccessCidr
      VpcId:
        !Ref Vpc

  DatabaseSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for RDS Databases
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: 10.0.0.0/20
      VpcId:
        !Ref Vpc

Outputs:
  BastionSecurityGroup:
    Value: !Ref BastionSecurityGroup
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", BastionSecurityGroup ] ]
  DatabaseSecurityGroup:
    Value: !Ref DatabaseSecurityGroup
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", DatabaseSecurityGroup ] ]

and like I said, works great!

But, when I start to add conditions, it runs amok:

  AWS::CloudFormation::Interface:
    ParameterGroups:
...
    - Label:
        default: Optional security groups to create
      Parameters:
        - CreateMailSecGroup
    ParameterLabels:
...
      CreateMailSecGroup:
        default: Create a security group for mail servers

Parameters:
...
  CreateMailSecGroup:
    AllowedValues:
      - 'True'
      - 'False'
    Default: 'False'
    Description: Create a security group for use by mail servers
    Type: String

Conditions:

  WantsMail: !Equals ['True', !Ref CreateMailSecGroup]
  WantsNothing: !Not [ !Equals ['True', !Ref CreateMailSecGroup] ]

Resources:

  DatabaseSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: WantsMail
    Properties:
      GroupDescription: Security group for RDS Databases
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref MailSecurityGroup
      VpcId:
        !Ref Vpc

  DatabaseSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: WantsNothing
    Properties:
      GroupDescription: Security group for RDS Databases
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: 10.0.0.0/20
      VpcId:
        !Ref Vpc

  MailSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: WantsMail
    Properties:
      GroupDescription: Security group for MX Servers
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref BastionSecurityGroup
        - IpProtocol: tcp
          FromPort: 25
          ToPort: 25
          CidrIp: 0.0.0.0/0
...
      VpcId:
        !Ref Vpc

Outputs:
...
  DatabaseSecurityGroup:
    Description: Security group for RDS database access
    Value: !Ref DatabaseSecurityGroup
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", DatabaseSecurityGroup ] ]
  MailSecurityGroup:
    Condition: WantsMail
    Description: Security group for MX servers
    Value: !Ref MailSecurityGroup
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", MailSecurityGroup ] ]

Now, if I make those changes, and do a update stack on the current (functioning) stack, and leave the new parameter CreateMailSecGroup at its default value of False, it does the right thing and says "nothing to change" after parsing the template. But if I change it to True, it complains with

Unresolved resource dependencies [DatabaseSecurityGroup] in the Outputs block of the template

in the change set preview.

My initial suspicion was that, now that the DatabaseSecurityGroup is conditional, it must want a matching condition in the Outputs block, so I tried

Conditions:

  WantsMail: !Equals ['True', !Ref CreateMailSecGroup]
  WantsNothing: !Not [ !Equals ['True', !Ref CreateMailSecGroup] ]
  OutputDatabase: !Or [ Condition: WantsMail, Condition: WantsNothing ]
...
Outputs:
  DatabaseSecurityGroup:
    Condition: OutputDatabase
    Description: Security group for RDS database access
    Value: !Ref DatabaseSecurityGroup
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", DatabaseSecurityGroup ] ]

No dice. Same error.

My last guess was that maybe the DatabaseSecurityGroup wasn't actually getting processed with the True condition because of some weird ordering phenomenon, so I tried adding DependsOn: MailSecurityGroup, but that didn't fix it either.

I'm really hoping this isn't a silly typo. Anyone have any ideas what I'm doing wrong? The condition stuff for the MailSecurityGroup doesn't seem to be throwing any errors, but I'm not sure if that's because there isn't an error, or because it got stopped at the outputs section of the DatabaseSecurityGroup and never got to the final one.

Thanks!

philolegein
  • 369
  • 3
  • 9

1 Answers1

2

Hrmph. According to the documentation:

The logical ID must be alphanumeric (A-Za-z0-9) and unique within the template.

Both simple, and annoying. So, can't have two different resources named "DatabaseSecurityGroup" and switch between them based on conditions. Need to give them different names (and then, in the outputs, create multiple output sections for the different names, which are also selected based on the conditions).

philolegein
  • 369
  • 3
  • 9