'AWS Cloudformation Default keypair for ec2

I have a cloudformation template to create an ec2-instance. That template also starts an httpd along with some content that is served.

I'm using the Parameter section to allow a key to be specified or selected - see snippet below:

Parameters:
  paramKeyPair:
    Description: KeyPairName
    Type: AWS::EC2::KeyPair::KeyName

I'm calling the ec2-instance through the AWS CLI like this :

aws cloudformation create-stack --stack-name stack-ec2instance --template-body file://demo-ec2instance --parameters ParameterKey=paramKeyPair,ParameterValue=peterKeyPair

So the instance can be created and the keypair can be passed through as an argument - BUT - frankly I don't actually care that much if the instance can be access. It's just a web server that can be spun up or down. SSH access is nice but no big deal.

In fact, if I removed the keypair Parameter from the cloudformation template - and removed the associated reference in the AWS CLI call - Cloudformation will happily spin up the instance without a keypair. Great !

What I would really like is for cloudformation to deal with the keypair being present or not. I thought the best way to do this would be to update the code so that the parameter has a default value of "None" (for example) and then the ec2-instance could be run from the AWS CLI and if the keypair parameter is not specified then AWS would know not to bother with the keypair at all.

The problem is that by specifying the Type as AWS::EC2::KeyPair::KeyName, the AWS CLI expects an actual value.

I'm out of ideas - if anyone else has figured this out - I would really appreciate it. Thankyou Peter.



Solution 1:[1]

If I understand you correctly you want to be able to keep the parameter in your Cloudformation template, but only "allocate" a key pair to an instance if you specify a value, otherwise don't allocate a key pair to the ec2 instance resource. You can do this with AWS::NoValue pseudo parameter.

Here is a sample template:


Description: My EC2 instance

Parameters:
  SSHKeyName:
    Type: String

Conditions:

  Has-EC2-Key:
    !Not [ !Equals [ !Ref SSHKeyName, '' ] ]

Resources:

  Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: <InstanceImageID>
      InstanceType: t2.micro
      KeyName: !Ref SSHKeyName
      KeyName:
        Fn::If:
        - Has-EC2-Key
        - Ref: SSHKeyName
        - Ref: AWS::NoValue
      <other properties as required

So what this does is the condition checks if a SSHKeyName value is blank, if it's blank then the KeyName property will be ignored, if it isn't blank then it will use the value of SSHKeyName.

Solution 2:[2]

Thankyou WarrenG, your solution worked with one small exception which was to change the parameter type from AWS::EC2::KeyPair::KeyName to String. Without your help I am certain I would have burned many more hours on this.

So in conclusion, the fix was 1: Change the Parameter type to String.

Parameters:
  SSHKeyName:
    Type: String

2: Add a function that determines if the key is present.

Conditions:
  Has-EC2-Key:
    !Not [ !Equals [ !Ref SSHKeyName, '' ] ]
  1. Use the function within the resources section.

KeyName:
  Fn::If:
  - Has-EC2-Key
  - Ref: SSHKeyName
  - Ref: AWS::NoValue

Within my question I kept the code snippets to a minimum for readability but now I have marked this as solved I'm adding two blocks of code just for documentation and incase this helps anyone else.

  1. One example of calling the template through the AWS CLI.

aws cloudformation create-stack --stack-name stack-ec2instance --template-body file://demo-ec2instance --parameters ParameterKey=paramSubnetId,ParameterValue=$SubnetId ParameterKey=paramKeyPair,ParameterValue=peterKeyPair ParameterKey=paramSecurityGroupIds,ParameterValue=$SecurityGroupId
  1. The template to create a EC2 instance.

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SSHKeyName:
    Description: EC2 KeyPair for SSH access.
    Type: String

Conditions:
  Has-EC2-Key:
    !Not [ !Equals [ !Ref SSHKeyName, '' ] ]

Mappings:
  RegionMap:
    eu-west-1:
      AMI: ami-3bfab942
    eu-west-2:
      AMI: ami-098828924dc89ea4a
      
Resources:
  EC2Instance:
    Type: AWS::EC2::Instance
    Metadata: 
      AWS::CloudFormation::Init:
        config: 
          packages: 
            yum:
              httpd: []
              php: []
          files: 
            /var/www/html/index.php:
              content: !Sub |
                <?php print "Hello Peter !"; ?>
          services: 
            sysvinit:
              httpd:
                enabled: true
                ensureRunning: true
    Properties:
      InstanceType: t2.micro
      ImageId:
        Fn::FindInMap:
        - RegionMap
        - !Ref AWS::Region
        - AMI
      SecurityGroupIds:
        - !Ref MySecurityGroup
      KeyName:
        Fn::If:
        - Has-EC2-Key
        - Ref: SSHKeyName
        - Ref: AWS::NoValue
      UserData:
        'Fn::Base64': 
          !Sub |
            #!/bin/bash -xe            
            # Ensure AWS CFN Bootstrap is the latest
            yum install -y aws-cfn-bootstrap
            # Install the files and packages from the metadata
            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance  --region ${AWS::Region}
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Open Ports 22 and 80
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0

Outputs:
  Website:
    Description: The Public DNS for the EC2 Instance
    Value: !Sub 'http://${EC2Instance.PublicDnsName}'

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Peter Rhodes