1

So, I've got an interesting one - Cloudformation allows the use of Mustache templates (via Pystache) to build configuration files via AWS::CloudFormation::Init (They bury this a few paragraphs down, but it's there).

This is useful to me, as I need to write out some of the network details to create a config file for an OpenVPN server. So far, so good.

But here's where it gets tricky - AWS likes CIDR notation (and I need to use the same parameter for AWS resources and for this). But OpenVPN likes to use the older IP Range and Netmask format. I'm currently trying to find a good way to convert this. I can either use CloudFormation functions or try to find a way to do the transformation in Mustache.

I can get the IP Range using a combination of Fn::Select and Fn::Split to pull the first half of the CIDR, but deriving the netmask currently has me stumped.

Example so far

          "/etc/openvpn/server/configname.conf" : {
            "source" : {"Fn::Sub" : 
               [ "https://${ConfigBucket}.s3.amazonaws.com/Path/To/configname.conf.mustache"
               , { "ConfigBucket" : { "Fn::ImportValue" : "ConfigBucket-Export-Name" }} ]
            },
            "context" : {
              "VpnCIDR" : { "Ref" : "VpnCIDRRange"},
              "VpnIPRange" : { "Fn::Select" : [ "0", {"Fn::Split" : ["/", { "Ref" : "VpnCIDRRange"}]}]},
              "AwsCIDR" : { "Fn::ImportValue" : { "Fn::Sub" : "${VPCName}-VPC-CIDR" } },
              "AwsIPRange" : { "Fn::Select" : [ "0", {"Fn::Split" : ["/", { "Fn::ImportValue" : { "Fn::Sub" : "${VPCName}-VPC-CIDR" }}]}]}
            }
          } 
Adam Luchjenbroers
  • 4,917
  • 2
  • 30
  • 35

1 Answers1

1

Ok, so I wound up solving this using a simple CloudFormation Macro that takes a CIDR range and returns a JSON object containing the CIDR, subnet and netmask - for example, given 192.168.1.0/24, it would return the following JSON fragment for including in a CloudFormation template

{
  'CIDR' : '192.168.1.0/24',
  'subnet' : '192.168.1.0',
  'netmask' : '255.255.255.0'
}

The code in question is posted to this gist: https://gist.github.com/AdamLuchjenbroers/3165ab18bb0ee9da95ad6bf514f415e0

It also can take the name of a stack export instead (this was required as apparently Fn::ImportValue gets evaluated AFTER Fn::Transform) to enable cross-talk between stacks.

This can then be passed via the context key in the CloudFormation::Init files section like so:

"/etc/openvpn/server/openvpn.conf" : {
    "source": {
      "Fn::Sub": [
        "https://${BucketName}.s3.${Region}.amazonaws.com/Path/To/openvpn.conf.mustache",
        {
          "BucketName": "BucketNameGoesHere",
          "Region": { "Ref": "AWS::Region" }
        }
      ]
    },
    "context": {
      "Vpn": {
        "Fn::Transform": {
          "Name": "NetworkInfo",
          "Parameters": {
            "CIDR": { "Ref": "VpnCIDRRange"    }
          }
        }
      },
      "Aws": {
        "Fn::Transform": {
          "Name": "NetworkInfo",
          "Parameters": {
            "CIDR-export": { "Fn::Sub": "${VPCName}-VPC-CIDR" }
          }
        }
      }
    }
  },

And then referenced in the actual mustache template like so:

push "route {{Aws.subnet}} {{Aws.netmask}}"
Adam Luchjenbroers
  • 4,917
  • 2
  • 30
  • 35