Ejemplo n.º 1
0
    def __init__(
            self,
            name,
            ami_id,
            instance_type,
            **kwargs
    ):
        if "classic_link_vpc_id" in kwargs and "classic_link_vpc_security_groups" not in kwargs:
            raise RespawnResourceError("Classic Link VPC Sercurity Groups (classic_link_vpc_security_groups) "
                                       "required with Class Link VPC ID (classic_link_vpc_id).",
                                       "classic Link VPC Id/Classic Link Vpc Security Groups")

        attributes = kwargs.get("attributes")

        properties = {
            'ImageId': ami_id,
            'InstanceType': instance_type
        }

        if 'block_devices' in kwargs:
            devices = kwargs.get('block_devices')
            block_devices = []
            for device, args in devices.items():
                if 'ebs' in args:
                    args['ebs'] = BlockDevice(**args['ebs'])
                block_devices.append(BlockDeviceMapping(device, **args))
            properties['BlockDeviceMappings'] = block_devices

        if "public_ip" in kwargs:
            properties['AssociatePublicIpAddress'] = kwargs.get("public_ip")  # default=False
        if "classic_link_vpc_id" in kwargs:
            properties['ClassicLinkVPCId'] = kwargs.get("classic_link_vpc_id")
        if "classic_link_vpc_security_groups" in kwargs:
            properties['ClassicLinkVPCSecurityGroups'] = kwargs.get("classic_link_vpc_security_groups")
        if "ebs_optimized" in kwargs:
            properties['EbsOptimized'] = kwargs.get("ebs_optimized")  # default=False
        if "iam_role" in kwargs:
            properties['IamInstanceProfile'] = kwargs.get("iam_role")
        if "instance_id" in kwargs:
            properties['InstanceId'] = kwargs.get("instance_id")
        if "monitoring" in kwargs:
            properties['InstanceMonitoring'] = kwargs.get("monitoring")  # default=True
        if "kernel_id" in kwargs:
            properties['KernelId'] = kwargs.get("kernel_id")
        if "key_pair" in kwargs:
            properties['KeyName'] = kwargs.get("key_pair")
        if "placement_tenancy" in kwargs:
            properties['PlacementTenancy'] = kwargs.get("placement_tenancy")
        if "private_ip" in kwargs:
            properties['PlacementGroupName'] = kwargs.get("private_ip")
        if "ramdisk_id" in kwargs:
            properties['RamdiskId'] = kwargs.get("ramdisk_id")
        if "security_groups" in kwargs:
            properties['SecurityGroups'] = kwargs.get("security_groups")
        if "spot_price" in kwargs:
            properties['SpotPrice'] = kwargs.get("spot_price")
        if "user_data_script" in kwargs:
            properties['UserData'] = functions.base64(kwargs.get("user_data_script"))

        super(LaunchConfiguration, self).__init__(name, 'AWS::AutoScaling::LaunchConfiguration', properties, attributes)
Ejemplo n.º 2
0
    def __init__(self, name, ami_id, instance_type, **kwargs):
        if "classic_link_vpc_id" in kwargs and "classic_link_vpc_security_groups" not in kwargs:
            raise RespawnResourceError(
                "Classic Link VPC Sercurity Groups (classic_link_vpc_security_groups) "
                "required with Class Link VPC ID (classic_link_vpc_id).",
                "classic Link VPC Id/Classic Link Vpc Security Groups")

        attributes = kwargs.get("attributes")

        properties = {'ImageId': ami_id, 'InstanceType': instance_type}

        if 'block_devices' in kwargs:
            devices = kwargs.get('block_devices')
            block_devices = []
            for device, args in devices.items():
                if 'ebs' in args:
                    args['ebs'] = BlockDevice(**args['ebs'])
                block_devices.append(BlockDeviceMapping(device, **args))
            properties['BlockDeviceMappings'] = block_devices

        if "public_ip" in kwargs:
            properties['AssociatePublicIpAddress'] = kwargs.get(
                "public_ip")  # default=False
        if "classic_link_vpc_id" in kwargs:
            properties['ClassicLinkVPCId'] = kwargs.get("classic_link_vpc_id")
        if "classic_link_vpc_security_groups" in kwargs:
            properties['ClassicLinkVPCSecurityGroups'] = kwargs.get(
                "classic_link_vpc_security_groups")
        if "ebs_optimized" in kwargs:
            properties['EbsOptimized'] = kwargs.get(
                "ebs_optimized")  # default=False
        if "iam_role" in kwargs:
            properties['IamInstanceProfile'] = kwargs.get("iam_role")
        if "instance_id" in kwargs:
            properties['InstanceId'] = kwargs.get("instance_id")
        if "monitoring" in kwargs:
            properties['InstanceMonitoring'] = kwargs.get(
                "monitoring")  # default=True
        if "kernel_id" in kwargs:
            properties['KernelId'] = kwargs.get("kernel_id")
        if "key_pair" in kwargs:
            properties['KeyName'] = kwargs.get("key_pair")
        if "placement_tenancy" in kwargs:
            properties['PlacementTenancy'] = kwargs.get("placement_tenancy")
        if "private_ip" in kwargs:
            properties['PlacementGroupName'] = kwargs.get("private_ip")
        if "ramdisk_id" in kwargs:
            properties['RamdiskId'] = kwargs.get("ramdisk_id")
        if "security_groups" in kwargs:
            properties['SecurityGroups'] = kwargs.get("security_groups")
        if "spot_price" in kwargs:
            properties['SpotPrice'] = kwargs.get("spot_price")
        if "user_data_script" in kwargs:
            properties['UserData'] = functions.base64(
                kwargs.get("user_data_script"))

        super(LaunchConfiguration,
              self).__init__(name, 'AWS::AutoScaling::LaunchConfiguration',
                             properties, attributes)
Ejemplo n.º 3
0
 def test_base64(self):
     ret = functions.base64('test')
     self.assertEqual(ret['Fn::Base64'], 'test')
Ejemplo n.º 4
0
def main():
    config, config_yaml = load_config()

    derive_config(config)

    unique_suffix = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M")

    # We discover the current git branch/commit
    # so that the deployment script can use it
    # to clone the same commit.
    commit = os.popen("""git rev-parse HEAD""").read().rstrip()
    assert commit

    assert "'" not in config['SITE_NAME']

    instance_tags = tags.load()
    # Set the `Name` as it appears on the EC2 web UI.
    instance_tags.append({'Key': 'Name',
                         'Value': "refinery-web-" + unique_suffix})

    config['tags'] = instance_tags

    config_uri = save_s3_config(config, unique_suffix)
    sys.stderr.write("Configuration saved to {}\n".format(config_uri))

    # The userdata script is executed via CloudInit.
    # It's made by concatenating a block of parameter variables,
    # with the bootstrap.sh script,
    # and the aws.sh script.
    user_data_script = functions.join(
        "",
        "#!/bin/sh\n",
        "CONFIG_YAML=", base64.b64encode(config_yaml), "\n",
        "CONFIG_JSON=", base64.b64encode(json.dumps(config)), "\n",
        "AWS_DEFAULT_REGION=", functions.ref("AWS::Region"), "\n",
        "RDS_ID=", functions.ref('RDSInstance'), "\n",
        "RDS_ENDPOINT_ADDRESS=",
        functions.get_att('RDSInstance', 'Endpoint.Address'),
        "\n",
        "RDS_ENDPOINT_PORT=",
        functions.get_att('RDSInstance', 'Endpoint.Port'),
        "\n",
        "RDS_SUPERUSER_PASSWORD="******"\n",
        "RDS_ROLE=", config['RDS_ROLE'], "\n",
        "ADMIN=", config['ADMIN'], "\n",
        "DEFAULT_FROM_EMAIL=", config['DEFAULT_FROM_EMAIL'], "\n",
        "SERVER_EMAIL=", config['SERVER_EMAIL'], "\n",
        "IAM_SMTP_USER="******"\n",
        "S3_CONFIG_URI=", config['S3_CONFIG_URI'], "\n",
        "SITE_URL=", config['SITE_URL'], "\n",
        # May contain spaces, but can't contain "'"
        "SITE_NAME='", config['SITE_NAME'], "'\n",
        "GIT_BRANCH=", commit, "\n",
        "\n",
        open('bootstrap.sh').read(),
        open('aws.sh').read())

    cft = core.CloudFormationTemplate(description="refinery platform.")

    rds_properties = {
        "AllocatedStorage": "5",
        "AvailabilityZone": config['AVAILABILITY_ZONE'],
        "BackupRetentionPeriod": "0",
        "DBInstanceClass": "db.t2.small",       # todo:?
        "DBInstanceIdentifier": config['RDS_NAME'],
        "Engine": "postgres",
        "EngineVersion": "9.3.10",
        # "KmsKeyId" ?
        "MasterUsername": "******",
        "MasterUserPassword": "******",
        "MultiAZ": False,
        "Port": "5432",
        "PubliclyAccessible": False,
        "StorageType": "gp2",
        "Tags": instance_tags,  # todo: Should be different?
    }

    if 'RDS_SNAPSHOT' in config:
        rds_properties['DBSnapshotIdentifier'] = config['RDS_SNAPSHOT']

    cft.resources.rds_instance = core.Resource(
        'RDSInstance', 'AWS::RDS::DBInstance',
        core.Properties(rds_properties),
        core.DeletionPolicy("Snapshot"),
        )

    volume_properties = {
        'AvailabilityZone': config['AVAILABILITY_ZONE'],
        'Encrypted': True,
        'Size': config['DATA_VOLUME_SIZE'],
        'Tags': tags.load(),
        'VolumeType': config['DATA_VOLUME_TYPE'],
    }

    if 'DATA_SNAPSHOT' in config:
        volume_properties['SnapshotId'] = config['DATA_SNAPSHOT']

    # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
    cft.resources.ebs = core.Resource(
        'RefineryData', 'AWS::EC2::Volume',
        core.Properties(volume_properties),
        core.DeletionPolicy("Snapshot"),
    )

    cft.resources.ec2_instance = core.Resource(
        'WebInstance', 'AWS::EC2::Instance',
        core.Properties({
            'AvailabilityZone': config['AVAILABILITY_ZONE'],
            'ImageId': 'ami-d05e75b8',
            'InstanceType': 'm3.medium',
            'UserData': functions.base64(user_data_script),
            'KeyName': config['KEY_NAME'],
            'IamInstanceProfile': functions.ref('WebInstanceProfile'),
            'Tags': instance_tags,
        }),
        core.DependsOn('RDSInstance'),
    )

    cft.resources.instance_profile = core.Resource(
        'WebInstanceProfile', 'AWS::IAM::InstanceProfile',
        core.Properties({
            'Path': '/',
            'Roles': [
              functions.ref('WebInstanceRole')
            ]
        })
    )

    cft.resources.web_role = core.Resource(
        'WebInstanceRole', 'AWS::IAM::Role',
        core.Properties({
            # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-templateexamples
            "AssumeRolePolicyDocument": {
               "Version": "2012-10-17",
               "Statement": [{
                  "Effect": "Allow",
                  "Principal": {
                     "Service": ["ec2.amazonaws.com"]
                  },
                  "Action": ["sts:AssumeRole"]
               }]
            },
            'ManagedPolicyArns': [
                'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'
            ],
            'Path': '/',
            'Policies': [
                {
                    'PolicyName': "CreateAccessKey",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "iam:CreateAccessKey"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            }
                        ]
                    }
                },
                {
                    'PolicyName': "CreateTags",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ec2:CreateTags"
                                ],
                                "Resource": "*"
                            }
                        ]
                    }
                }
            ]
        })
    )

    cft.resources.smtp_user = core.Resource(
        'RefinerySMTPUser', 'AWS::IAM::User',
        core.Properties({
            'Policies': [{
                'PolicyName': "SESSendingAccess",
                'PolicyDocument': {
                    "Version": "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": "ses:SendRawEmail",
                        "Resource": "*"
                    }]
                }
            }]
        })
    )

    cft.resources.mount = core.Resource(
        'RefineryVolume', 'AWS::EC2::VolumeAttachment',
        core.Properties({
            'Device': '/dev/xvdr',
            'InstanceId': functions.ref('WebInstance'),
            'VolumeId': functions.ref('RefineryData'),
        })
    )

    print(str(cft))
Ejemplo n.º 5
0
def make_template(config, config_yaml):
    """Make a fresh CloudFormation template object and return it"""

    stack_name = config['STACK_NAME']

    # We discover the current git branch/commit so that the deployment script
    # can use it to clone the same commit
    commit = os.popen("""git rev-parse HEAD""").read().rstrip()
    assert commit

    assert "'" not in config['SITE_NAME']

    instance_tags = load_tags()

    # Stack Name is also used for instances.
    instance_tags.append({'Key': 'Name', 'Value': stack_name})

    # This tag is variable and can be specified by
    # template Parameter.
    instance_tags.append({
        'Key': functions.ref('SnapshotSchedulerTag'),
        'Value': 'default'
    })

    config['tags'] = instance_tags

    config_uri = save_s3_config(config)
    sys.stdout.write("Configuration saved to {}\n".format(config_uri))

    tls_rewrite = "false"
    if 'TLS_CERTIFICATE' in config:
        tls_rewrite = "true"

    # The userdata script is executed via CloudInit
    # It's made by concatenating a block of parameter variables,
    # with the bootstrap.sh script, and the aws.sh script
    user_data_script = functions.join(
        "",
        "#!/bin/sh\n",
        "CONFIG_YAML=",
        base64.b64encode(config_yaml),
        "\n",
        "CONFIG_JSON=",
        base64.b64encode(json.dumps(config)),
        "\n",
        "AWS_DEFAULT_REGION=",
        functions.ref("AWS::Region"),
        "\n",
        "RDS_ENDPOINT_ADDRESS=",
        functions.get_att('RDSInstance', 'Endpoint.Address'),
        "\n",
        "RDS_ENDPOINT_PORT=",
        functions.get_att('RDSInstance', 'Endpoint.Port'),
        "\n",
        "RDS_SUPERUSER_PASSWORD="******"\n",
        "RDS_ROLE=",
        config['RDS_ROLE'],
        "\n",
        "ADMIN=",
        config['ADMIN'],
        "\n",
        "DEFAULT_FROM_EMAIL=",
        config['DEFAULT_FROM_EMAIL'],
        "\n",
        "SERVER_EMAIL=",
        config['SERVER_EMAIL'],
        "\n",
        "IAM_SMTP_USER="******"\n",
        "export FACTER_TLS_REWRITE=",
        tls_rewrite,
        "\n",
        "S3_CONFIG_URI=",
        config['S3_CONFIG_URI'],
        "\n",
        "SITE_URL=",
        config['SITE_URL'],
        "\n",
        # May contain spaces, but can't contain "'"
        "SITE_NAME='",
        config['SITE_NAME'],
        "'\n",
        "GIT_BRANCH=",
        commit,
        "\n",
        "\n",
        open('bootstrap.sh').read(),
        open('aws.sh').read())

    cft = core.CloudFormationTemplate(description="Refinery Platform main")

    # This parameter tags the EC2 instances, and is intended to be used
    # with the AWS Reference Implementation EBS Snapshot Scheduler:
    # http://docs.aws.amazon.com/solutions/latest/ebs-snapshot-scheduler/welcome.html
    cft.parameters.add(
        core.Parameter(
            'SnapshotSchedulerTag', 'String', {
                'Default':
                'scheduler:ebs-snapshot',
                'Description':
                "Tag added to EC2 Instances so that "
                "the EBS Snapshot Scheduler will recognise them.",
            }))
    cft.parameters.add(
        core.Parameter(
            'IdentityPoolName', 'String', {
                'Default': 'Refinery Platform',
                'Description': 'Name of Cognito identity pool for S3 uploads',
            }))
    cft.parameters.add(
        core.Parameter(
            'DeveloperProviderName', 'String', {
                'Default':
                'login.refinery',
                'Description':
                '"domain" by which Cognito will refer to users',
                'AllowedPattern':
                '[a-z\-\.]+',
                'ConstraintDescription':
                'must only contain lower case letters, periods, '
                'underscores, and hyphens'
            }))
    cft.parameters.add(
        core.Parameter(
            'StorageStackName', 'String', {
                'Default':
                '${AWS::StackName}Storage',
                'Description':
                'Name of the S3 storage stack for Django '
                'static and media files',
            }))

    rds_properties = {
        "AllocatedStorage": "5",
        "AutoMinorVersionUpgrade": False,
        "BackupRetentionPeriod": "15",
        "CopyTagsToSnapshot": True,
        "DBInstanceClass": "db.t2.small",  # todo:?
        "DBInstanceIdentifier": config['RDS_NAME'],
        "Engine": "postgres",
        "EngineVersion": "9.3.14",
        # "KmsKeyId" ?
        "MasterUsername": "******",
        "MasterUserPassword": config['RDS_SUPERUSER_PASSWORD'],
        "MultiAZ": False,
        "Port": "5432",
        "PubliclyAccessible": False,
        "StorageType": "gp2",
        "Tags": instance_tags,  # todo: Should be different?
        "VPCSecurityGroups":
        [functions.get_att('RDSSecurityGroup', 'GroupId')],
    }

    if 'RDS_SNAPSHOT' in config:
        rds_properties['DBSnapshotIdentifier'] = config['RDS_SNAPSHOT']

    cft.resources.rds_instance = core.Resource(
        'RDSInstance',
        'AWS::RDS::DBInstance',
        core.Properties(rds_properties),
        core.DeletionPolicy("Snapshot"),
    )

    volume_properties = {
        'Encrypted': True,
        'Size': config['DATA_VOLUME_SIZE'],
        'Tags': load_tags(),
        'AvailabilityZone': functions.get_att('WebInstance',
                                              'AvailabilityZone'),
        'VolumeType': config['DATA_VOLUME_TYPE'],
    }

    if 'DATA_SNAPSHOT' in config:
        volume_properties['SnapshotId'] = config['DATA_SNAPSHOT']

    # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
    cft.resources.ebs = core.Resource(
        'RefineryData',
        'AWS::EC2::Volume',
        core.Properties(volume_properties),
        core.DeletionPolicy("Snapshot"),
    )

    cft.resources.ec2_instance = core.Resource(
        'WebInstance',
        'AWS::EC2::Instance',
        core.Properties({
            'ImageId':
            'ami-d05e75b8',
            'InstanceType':
            'm3.medium',
            'UserData':
            functions.base64(user_data_script),
            'KeyName':
            config['KEY_NAME'],
            'IamInstanceProfile':
            functions.ref('WebInstanceProfile'),
            'SecurityGroups': [functions.ref("InstanceSecurityGroup")],
            'Tags':
            instance_tags,
        }),
        core.DependsOn(['RDSInstance']),
    )

    cft.resources.instance_profile = core.Resource(
        'WebInstanceProfile', 'AWS::IAM::InstanceProfile',
        core.Properties({
            'Path': '/',
            'Roles': [functions.ref('WebInstanceRole')]
        }))

    cft.resources.web_role = core.Resource(
        'WebInstanceRole',
        'AWS::IAM::Role',
        core.Properties({
            # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-templateexamples
            "AssumeRolePolicyDocument": {
                "Version":
                "2012-10-17",
                "Statement": [{
                    "Effect": "Allow",
                    "Principal": {
                        "Service": ["ec2.amazonaws.com"]
                    },
                    "Action": ["sts:AssumeRole"]
                }]
            },
            'ManagedPolicyArns': [
                'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonS3FullAccess'
            ],
            'Path':
            '/',
            'Policies': [{
                'PolicyName': "CreateAccessKey",
                'PolicyDocument': {
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": ["iam:CreateAccessKey"],
                        "Resource": ["*"]
                    }]
                },
            }, {
                'PolicyName': "CreateSnapshot",
                'PolicyDocument': {
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": ["ec2:CreateSnapshot"],
                        "Resource": ["*"]
                    }]
                }
            }, {
                'PolicyName': "CreateDBSnapshot",
                'PolicyDocument': {
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": ["rds:CreateDBSnapshot"],
                        "Resource": ["*"]
                    }]
                }
            }, {
                'PolicyName': "CreateTags",
                'PolicyDocument': {
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": ["ec2:CreateTags"],
                        "Resource": "*"
                    }]
                }
            }, {
                "PolicyName": "CognitoAccess",
                "PolicyDocument": {
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect":
                        "Allow",
                        "Action": [
                            "cognito-identity:ListIdentityPools",
                        ],
                        "Resource":
                        "arn:aws:cognito-identity:*"
                    }, {
                        "Effect":
                        "Allow",
                        "Action": [
                            "cognito-identity:"
                            "GetOpenIdTokenForDeveloperIdentity"
                        ],
                        "Resource": {
                            "Fn::Sub": [
                                "arn:aws:cognito-identity:"
                                "${AWS::Region}:${AWS::AccountId}:"
                                "identitypool/${Pool}", {
                                    "Pool": functions.ref('IdentityPool')
                                }
                            ]
                        }
                    }]
                }
            }]
        }))

    cft.resources.smtp_user = core.Resource(
        'RefinerySMTPUser', 'AWS::IAM::User',
        core.Properties({
            'Policies': [{
                'PolicyName': "SESSendingAccess",
                'PolicyDocument': {
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": "ses:SendRawEmail",
                        "Resource": "*"
                    }]
                }
            }]
        }))

    cft.resources.mount = core.Resource(
        'RefineryVolume', 'AWS::EC2::VolumeAttachment',
        core.Properties({
            'Device': '/dev/xvdr',
            'InstanceId': functions.ref('WebInstance'),
            'VolumeId': functions.ref('RefineryData'),
        }))

    # Security Group for Elastic Load Balancer
    # (public facing).
    cft.resources.elbsg = core.Resource(
        'ELBSecurityGroup',
        'AWS::EC2::SecurityGroup',
        core.Properties({
            'GroupDescription':
            "Refinery ELB",
            # Egress Rule defined via
            # AWS::EC2::SecurityGroupEgress resource,
            # to avoid circularity (below).
            # See http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html # noqa: E501
            'SecurityGroupIngress': [
                {
                    "IpProtocol": "tcp",
                    "FromPort": "80",
                    "ToPort": "80",
                    "CidrIp": "0.0.0.0/0",
                },
                {
                    "IpProtocol": "tcp",
                    "FromPort": "443",
                    "ToPort": "443",
                    "CidrIp": "0.0.0.0/0",
                },
            ],
        }))

    cft.resources.elbegress = core.Resource(
        'ELBEgress', 'AWS::EC2::SecurityGroupEgress',
        core.Properties({
            "GroupId":
            functions.get_att('ELBSecurityGroup', 'GroupId'),
            "IpProtocol":
            "tcp",
            "FromPort":
            "80",
            "ToPort":
            "80",
            "DestinationSecurityGroupId":
            functions.get_att('InstanceSecurityGroup', 'GroupId'),
        }))

    # Security Group for EC2- instance.
    cft.resources.instancesg = core.Resource(
        'InstanceSecurityGroup',
        'AWS::EC2::SecurityGroup',
        core.Properties({
            'GroupDescription':
            "Refinery EC2 Instance",
            'SecurityGroupEgress': [],
            '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",
                    # Only accept connections from the ELB.
                    "SourceSecurityGroupId":
                    functions.get_att('ELBSecurityGroup', 'GroupId'),
                },
            ],
        }))

    # Security Group for RDS instance.
    cft.resources.rdssg = core.Resource(
        'RDSSecurityGroup',
        'AWS::EC2::SecurityGroup',
        core.Properties({
            'GroupDescription':
            "Refinery RDS",
            'SecurityGroupEgress': [
                # We would like to remove all egress rules here,
                # but you can't do that with this version
                # of CloudFormation.
                # We decided that the hacky workarounds are
                # not worth it.
            ],
            'SecurityGroupIngress': [
                {
                    "IpProtocol":
                    "tcp",
                    "FromPort":
                    "5432",
                    "ToPort":
                    "5432",
                    # Only accept connections from the
                    # Instance Security Group.
                    "SourceSecurityGroupId":
                    functions.get_att('InstanceSecurityGroup', 'GroupId'),
                },
            ],
        }))

    # ELB per
    # http://cfn-pyplates.readthedocs.io/en/latest/examples/options/template.html

    # Insecure, Port 80, HTTP listener
    http_listener = {
        'LoadBalancerPort': '80',
        'Protocol': 'HTTP',
        'InstanceProtocol': 'HTTP',
        'InstancePort': '80',
        'PolicyNames': []
    }
    listeners = [http_listener]

    if 'TLS_CERTIFICATE' in config:
        # Secure, Port 443, HTTPS listener
        https_listener = {
            'LoadBalancerPort': '443',
            'Protocol': 'HTTPS',
            'InstanceProtocol': 'HTTP',
            'InstancePort': '80',
            'PolicyNames': [],
            'SSLCertificateId': config['TLS_CERTIFICATE']
        }
        listeners.append(https_listener)

    cft.resources.elb = core.Resource(
        'LoadBalancer',
        'AWS::ElasticLoadBalancing::LoadBalancer',
        {
            'AccessLoggingPolicy': {
                'EmitInterval': functions.ref('LogInterval'),
                'Enabled': True,
                'S3BucketName': config['S3_LOG_BUCKET'],
                # 'S3BucketPrefix' unused
            },
            'AvailabilityZones':
            [functions.get_att('WebInstance', 'AvailabilityZone')],
            'ConnectionSettings': {
                'IdleTimeout': 1800  # seconds
            },
            'HealthCheck': {
                'HealthyThreshold': '2',
                'Interval': '30',
                'Target': 'HTTP:80/',
                'Timeout': '5',
                'UnhealthyThreshold': '4'
            },
            'Instances': [functions.ref('WebInstance')],
            'LoadBalancerName':
            config['STACK_NAME'],
            'Listeners':
            listeners,
            'SecurityGroups':
            [functions.get_att('ELBSecurityGroup', 'GroupId')],
            'Tags':
            load_tags(),
        })
    cft.parameters.add(
        core.Parameter(
            'LogInterval', 'Number', {
                'Default':
                60,
                'Description':
                "How often, in minutes, the ELB emits its logs to the "
                "configured S3 bucket. The ELB log facility restricts "
                "this to be 5 or 60.",
            }))

    # Cognito Identity Pool for Developer Authenticated Identities Authflow
    # http://docs.aws.amazon.com/cognito/latest/developerguide/authentication-flow.html
    cft.resources.add(
        core.Resource(
            'IdentityPool', 'AWS::Cognito::IdentityPool',
            core.Properties({
                'IdentityPoolName':
                functions.ref('IdentityPoolName'),
                'AllowUnauthenticatedIdentities':
                False,
                'DeveloperProviderName':
                functions.ref('DeveloperProviderName'),
            })))
    cft.resources.add(
        core.Resource(
            'IdentityPoolAuthenticatedRole',
            'AWS::Cognito::IdentityPoolRoleAttachment',
            core.Properties({
                'IdentityPoolId': functions.ref('IdentityPool'),
                'Roles': {
                    'authenticated':
                    functions.get_att('CognitoS3UploadRole', 'Arn'),
                }
            })))
    upload_role_trust_policy = {
        "Version":
        "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "cognito-identity.amazonaws.com:aud":
                    functions.ref('IdentityPool')
                },
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "authenticated"
                }
            }
        }]
    }
    upload_access_policy = {
        "Version":
        "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Action": ["cognito-identity:*"],
            "Resource": "*"
        }, {
            "Action": ["s3:PutObject", "s3:AbortMultipartUpload"],
            "Effect": "Allow",
            "Resource": {
                "Fn::Sub": [
                    "arn:aws:s3:::${MediaBucket}/uploads/"
                    "${!cognito-identity.amazonaws.com:sub}/*", {
                        "MediaBucket": {
                            "Fn::ImportValue": {
                                "Fn::Sub": "${StorageStackName}Media"
                            }
                        }
                    }
                ]
            }
        }]
    }
    cft.resources.add(
        core.Resource(
            'CognitoS3UploadRole', 'AWS::IAM::Role',
            core.Properties({
                'AssumeRolePolicyDocument':
                upload_role_trust_policy,
                'Policies': [{
                    'PolicyName': 'AuthenticatedS3UploadPolicy',
                    'PolicyDocument': upload_access_policy,
                }]
            })))

    # See http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html#attach-bucket-policy # noqa: E501
    # for full list of region--principal identifiers.
    cft.mappings.region = core.Mapping(
        'Region', {'us-east-1': {
            'ELBPrincipal': '127311923021'
        }})

    cft.resources.log_policy = core.Resource(
        'LogBucketPolicy', 'AWS::S3::BucketPolicy',
        core.Properties({
            'Bucket': config['S3_LOG_BUCKET'],
            'PolicyDocument': {
                'Statement': [{
                    "Action": ["s3:PutObject"],
                    "Effect":
                    "Allow",
                    "Resource":
                    functions.join("", "arn:aws:s3:::",
                                   config['S3_LOG_BUCKET'], "/AWSLogs/",
                                   functions.ref("AWS::AccountId"), "/*"),
                    "Principal": {
                        "AWS": [
                            functions.find_in_map('Region',
                                                  functions.ref("AWS::Region"),
                                                  'ELBPrincipal'),
                        ]
                    }
                }]
            }
        }))

    return cft
Ejemplo n.º 6
0
 def test_base64(self):
     ret = functions.base64('test')
     self.assertEqual(ret['Fn::Base64'], 'test')
Ejemplo n.º 7
0
    def __init__(self, name, ami_id, **kwargs):
        properties = {"ImageId": ami_id}

        if "block_devices" in kwargs:
            devices = kwargs.get("block_devices")
            block_devices = []
            for device, args in devices.items():
                if "ebs" in args:
                    args["ebs"] = dict(BlockDevice(**args["ebs"]))
                block_devices.append(dict(BlockDeviceMapping(device, **args)))
            properties["BlockDeviceMappings"] = block_devices

        if "network_interfaces" in kwargs:
            interfaces = kwargs.get("network_interfaces")
            network_interfaces_list = []
            for interface, args in interfaces.items():
                if "private_ips" in args:
                    private_ips = args["private_ips"]
                    for i in range(len(private_ips)):
                        private_ips[i] = dict(PrivateIpSpecification(**private_ips[i]))
                network_interfaces_list.append(dict(EmbeddedNetworkInterface(description=interface, **args)))
            properties["NetworkInterfaces"] = network_interfaces_list

        if "volumes" in kwargs:
            volumes = kwargs.get("volumes")
            for i in range(len(volumes)):
                volumes[i] = dict(MountPoint(**volumes[i]))
            properties["Volumes"] = volumes

        if "tags" in kwargs:
            t = kwargs.get("tags")
            tags = []
            for tag in t:
                tags.append(dict(Tag(**tag)))
            properties["Tags"] = tags

        if "availability_zone" in kwargs:
            properties["AvailabilityZone"] = kwargs.get("availability_zone")
        if "disable_api_termination" in kwargs:
            properties["DisableApiTermination"] = kwargs.get("disable_api_termination")  # default=False
        if "ebs_optimized" in kwargs:
            properties["EbsOptimized"] = kwargs.get("ebs_optimized")  # default=False
        if "iam_role" in kwargs:
            properties["IamInstanceProfile"] = kwargs.get("iam_role")
        if "instance_shutdown_behavior" in kwargs:
            properties["InstanceInitiatedShutdownBehavior"] = kwargs.get("instance_shutdown_behavior")
        if "instance_type" in kwargs:
            properties["InstanceType"] = kwargs.get("instance_type")
        if "kernel_id" in kwargs:
            properties["KernelId"] = kwargs.get("kernel_id")
        if "key_pair" in kwargs:
            properties["KeyName"] = kwargs.get("key_pair")
        if "monitoring" in kwargs:
            properties["Monitoring"] = kwargs.get("monitoring")  # default=False
        if "placement_group" in kwargs:
            properties["PlacementGroupName"] = kwargs.get("placement_group")
        if "private_ip" in kwargs:
            properties["PrivateIpAddress"] = kwargs.get("private_ip")
        if "ramdisk_id" in kwargs:
            properties["RamdiskId"] = kwargs.get("ramdisk_id")
        if "security_group_ids" in kwargs:
            properties["SecurityGroupIds"] = kwargs.get("security_group_ids")
        if "security_groups" in kwargs:
            properties["SecurityGroups"] = kwargs.get("security_groups")
        if "source_dest_check" in kwargs:
            properties["SourceDestCheck"] = kwargs.get("source_dest_check")  # default=True
        if "subnet" in kwargs:
            properties["SubnetId"] = kwargs.get("subnet")
        if "tenancy" in kwargs:
            properties["Tenancy"] = kwargs.get("tenancy")  # default="default"
        if "user_data_script" in kwargs:
            properties["UserData"] = functions.base64(kwargs.get("user_data_script"))

        attributes = kwargs.get("attributes")

        super(Instance, self).__init__(name, "AWS::EC2::Instance", properties, attributes)
Ejemplo n.º 8
0
def main():
    config = load_config()

    # The Availability Zone of the new instance needs to match
    # the availability zone of the existing EBS.
    derive_config(config)

    unique_suffix = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M")

    # We discover the current git branch/commit
    # so that the deployment script can use it
    # to clone the same commit.
    commit = os.popen("""git rev-parse HEAD""").read().rstrip()
    assert commit

    assert "'" not in config['SITE_NAME']

    instance_tags = tags.load()
    # Set the `Name` as it appears on the EC2 web UI.
    instance_tags.append({'Key': 'Name',
                         'Value': "refinery-web-" + unique_suffix})

    config['tags'] = instance_tags

    config_uri = save_s3_config(config, unique_suffix)
    sys.stderr.write("Configuration saved to {}\n".format(config_uri))

    # The userdata script is executed via CloudInit.
    # It's made by concatenating a block of parameter variables,
    # with the bootstrap.sh script,
    # and the aws.sh script.
    user_data_script = functions.join(
        "",
        "#!/bin/sh\n",
        "AWS_DEFAULT_REGION=", functions.ref("AWS::Region"), "\n",
        "RDS_NAME=", config['RDS_NAME'], "\n",
        "RDS_SUPERUSER_PASSWORD="******"\n",
        "RDS_ROLE=", config['RDS_ROLE'], "\n",
        "ADMIN=", config['ADMIN'], "\n",
        "DEFAULT_FROM_EMAIL=", config['DEFAULT_FROM_EMAIL'], "\n",
        "SERVER_EMAIL=", config['SERVER_EMAIL'], "\n",
        "IAM_SMTP_USER="******"\n",
        "S3_CONFIG_URI=", config['S3_CONFIG_URI'], "\n",
        "SITE_URL=", config['SITE_URL'], "\n",
        # May contain spaces, but can't contain "'"
        "SITE_NAME='", config['SITE_NAME'], "'\n",
        "GIT_BRANCH=", commit, "\n",
        "\n",
        open('bootstrap.sh').read(),
        open('aws.sh').read())

    cft = core.CloudFormationTemplate(description="refinery platform.")

    rds_properties = {
        "AllocatedStorage": "5",
        "AvailabilityZone": config['AVAILABILITY_ZONE'],
        "BackupRetentionPeriod": "0",
        "DBInstanceClass": "db.t2.small",       # todo:?
        "DBInstanceIdentifier": config['RDS_NAME'],
        "Engine": "postgres",
        "EngineVersion": "9.3.10",
        # "KmsKeyId" ?
        "MasterUsername": "******",
        "MasterUserPassword": "******",
        "MultiAZ": False,
        "Port": "5432",
        "PubliclyAccessible": False,
        "StorageType": "gp2",
        "Tags": instance_tags,  # todo: Should be different?
    }

    if 'RDS_SNAPSHOT' in config:
        rds_properties['DBSnapshotIdentifier'] = config['RDS_SNAPSHOT']

    cft.resources.rds_instance = core.Resource(
        'RDSInstance', 'AWS::RDS::DBInstance',
        core.Properties(rds_properties),
        core.DeletionPolicy("Snapshot"),
        )

    volume_properties = {
        'AvailabilityZone': config['AVAILABILITY_ZONE'],
        'Encrypted': True,
        'Size': config['DATA_VOLUME_SIZE'],
        'Tags': tags.load(),
        'VolumeType': config['DATA_VOLUME_TYPE'],
    }

    if 'DATA_SNAPSHOT' in config:
        volume_properties['SnapshotId'] = config['DATA_SNAPSHOT']

    # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
    cft.resources.ebs = core.Resource(
        'RefineryData', 'AWS::EC2::Volume',
        core.Properties(volume_properties),
        core.DeletionPolicy("Snapshot"),
    )

    cft.resources.ec2_instance = core.Resource(
        'WebInstance', 'AWS::EC2::Instance',
        core.Properties({
            'AvailabilityZone': config['AVAILABILITY_ZONE'],
            'ImageId': 'ami-d05e75b8',
            'InstanceType': 'm3.medium',
            'UserData': functions.base64(user_data_script),
            'KeyName': config['KEY_NAME'],
            'IamInstanceProfile': functions.ref('WebInstanceProfile'),
            'Tags': instance_tags,
        })
    )

    cft.resources.instance_profile = core.Resource(
        'WebInstanceProfile', 'AWS::IAM::InstanceProfile',
        core.Properties({
            'Path': '/',
            'Roles': [
              functions.ref('WebInstanceRole')
            ]
        })
    )

    cft.resources.web_role = core.Resource(
        'WebInstanceRole', 'AWS::IAM::Role',
        core.Properties({
            # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-templateexamples
            "AssumeRolePolicyDocument": {
               "Version": "2012-10-17",
               "Statement": [{
                  "Effect": "Allow",
                  "Principal": {
                     "Service": ["ec2.amazonaws.com"]
                  },
                  "Action": ["sts:AssumeRole"]
               }]
            },
            'ManagedPolicyArns': [
                'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'
            ],
            'Path': '/',
            'Policies': [
                {
                    'PolicyName': "CreateAccessKey",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "iam:CreateAccessKey"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            }
                        ]
                    }
                },
                {
                    'PolicyName': "CreateTags",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ec2:CreateTags"
                                ],
                                "Resource": "*"
                            }
                        ]
                    }
                }
            ]
        })
    )

    cft.resources.smtp_user = core.Resource(
        'RefinerySMTPUser', 'AWS::IAM::User',
        core.Properties({
            'Policies': [{
                'PolicyName': "SESSendingAccess",
                'PolicyDocument': {
                    "Version": "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": "ses:SendRawEmail",
                        "Resource": "*"
                    }]
                }
            }]
        })
    )

    cft.resources.mount = core.Resource(
        'RefineryVolume', 'AWS::EC2::VolumeAttachment',
        core.Properties({
            'Device': '/dev/xvdr',
            'InstanceId': functions.ref('WebInstance'),
            'VolumeId': functions.ref('RefineryData'),
        })
    )

    print(str(cft))
Ejemplo n.º 9
0
 def test_base64(self):
     ret = functions.base64("test")
     self.assertEqual(ret["Fn::Base64"], "test")
Ejemplo n.º 10
0
    def __init__(self, name, ami_id, **kwargs):
        properties = {
            'ImageId': ami_id,
        }

        if 'block_devices' in kwargs:
            devices = kwargs.get('block_devices')
            block_devices = []
            for device, args in devices.items():
                if 'ebs' in args:
                    args['ebs'] = dict(BlockDevice(**args['ebs']))
                block_devices.append(dict(BlockDeviceMapping(device, **args)))
            properties['BlockDeviceMappings'] = block_devices

        if 'network_interfaces' in kwargs:
            interfaces = kwargs.get('network_interfaces')
            network_interfaces_list = []
            for interface, args in interfaces.items():
                if 'private_ips' in args:
                    private_ips = args['private_ips']
                    for i in range(len(private_ips)):
                        private_ips[i] = dict(
                            PrivateIpSpecification(**private_ips[i]))
                network_interfaces_list.append(
                    dict(
                        EmbeddedNetworkInterface(description=interface,
                                                 **args)))
            properties['NetworkInterfaces'] = network_interfaces_list

        if 'volumes' in kwargs:
            volumes = kwargs.get('volumes')
            for i in range(len(volumes)):
                volumes[i] = dict(MountPoint(**volumes[i]))
            properties['Volumes'] = volumes

        if 'tags' in kwargs:
            t = kwargs.get('tags')
            tags = []
            for tag in t:
                tags.append(dict(Tag(**tag)))
            properties['Tags'] = tags

        if "availability_zone" in kwargs:
            properties['AvailabilityZone'] = kwargs.get("availability_zone")
        if "disable_api_termination" in kwargs:
            properties['DisableApiTermination'] = kwargs.get(
                "disable_api_termination")  # default=False
        if "ebs_optimized" in kwargs:
            properties['EbsOptimized'] = kwargs.get(
                "ebs_optimized")  # default=False
        if "iam_role" in kwargs:
            properties['IamInstanceProfile'] = kwargs.get("iam_role")
        if "instance_shutdown_behavior" in kwargs:
            properties['InstanceInitiatedShutdownBehavior'] = kwargs.get(
                "instance_shutdown_behavior")
        if "instance_type" in kwargs:
            properties['InstanceType'] = kwargs.get("instance_type")
        if "kernel_id" in kwargs:
            properties['KernelId'] = kwargs.get("kernel_id")
        if "key_pair" in kwargs:
            properties['KeyName'] = kwargs.get("key_pair")
        if "monitoring" in kwargs:
            properties['Monitoring'] = kwargs.get(
                "monitoring")  # default=False
        if "placement_group" in kwargs:
            properties['PlacementGroupName'] = kwargs.get("placement_group")
        if "private_ip" in kwargs:
            properties['PrivateIpAddress'] = kwargs.get("private_ip")
        if "ramdisk_id" in kwargs:
            properties['RamdiskId'] = kwargs.get("ramdisk_id")
        if "security_group_ids" in kwargs:
            properties['SecurityGroupIds'] = kwargs.get("security_group_ids")
        if "security_groups" in kwargs:
            properties['SecurityGroups'] = kwargs.get("security_groups")
        if "source_dest_check" in kwargs:
            properties['SourceDestCheck'] = kwargs.get(
                "source_dest_check")  # default=True
        if "subnet" in kwargs:
            properties['SubnetId'] = kwargs.get("subnet")
        if "tenancy" in kwargs:
            properties['Tenancy'] = kwargs.get("tenancy")  # default="default"
        if "user_data_script" in kwargs:
            properties['UserData'] = functions.base64(
                kwargs.get("user_data_script"))

        attributes = kwargs.get("attributes")

        super(Instance, self).__init__(name, 'AWS::EC2::Instance', properties,
                                       attributes)
Ejemplo n.º 11
0
def main():
    config, config_yaml = load_config()

    derive_config(config)

    unique_suffix = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M")

    # We discover the current git branch/commit
    # so that the deployment script can use it
    # to clone the same commit.
    commit = os.popen("""git rev-parse HEAD""").read().rstrip()
    assert commit

    assert "'" not in config['SITE_NAME']

    instance_tags = tags.load()
    # Set the `Name` as it appears on the EC2 web UI.
    instance_tags.append({'Key': 'Name',
                         'Value': "refinery-web-" + unique_suffix})

    config['tags'] = instance_tags

    config_uri = save_s3_config(config, unique_suffix)
    sys.stderr.write("Configuration saved to {}\n".format(config_uri))

    tls_rewrite = "false"
    if 'TLS_CERTIFICATE' in config:
        tls_rewrite = "true"

    # The userdata script is executed via CloudInit.
    # It's made by concatenating a block of parameter variables,
    # with the bootstrap.sh script,
    # and the aws.sh script.
    user_data_script = functions.join(
        "",
        "#!/bin/sh\n",
        "CONFIG_YAML=", base64.b64encode(config_yaml), "\n",
        "CONFIG_JSON=", base64.b64encode(json.dumps(config)), "\n",
        "AWS_DEFAULT_REGION=", functions.ref("AWS::Region"), "\n",
        "RDS_ID=", functions.ref('RDSInstance'), "\n",
        "RDS_ENDPOINT_ADDRESS=",
        functions.get_att('RDSInstance', 'Endpoint.Address'),
        "\n",
        "RDS_ENDPOINT_PORT=",
        functions.get_att('RDSInstance', 'Endpoint.Port'),
        "\n",
        "RDS_SUPERUSER_PASSWORD="******"\n",
        "RDS_ROLE=", config['RDS_ROLE'], "\n",
        "ADMIN=", config['ADMIN'], "\n",
        "DEFAULT_FROM_EMAIL=", config['DEFAULT_FROM_EMAIL'], "\n",
        "SERVER_EMAIL=", config['SERVER_EMAIL'], "\n",
        "IAM_SMTP_USER="******"\n",
        "export FACTER_TLS_REWRITE=", tls_rewrite, "\n",
        "S3_CONFIG_URI=", config['S3_CONFIG_URI'], "\n",
        "SITE_URL=", config['SITE_URL'], "\n",
        # May contain spaces, but can't contain "'"
        "SITE_NAME='", config['SITE_NAME'], "'\n",
        "GIT_BRANCH=", commit, "\n",
        "\n",
        open('bootstrap.sh').read(),
        open('aws.sh').read())

    cft = core.CloudFormationTemplate(description="refinery platform.")

    rds_properties = {
        "AllocatedStorage": "5",
        "AutoMinorVersionUpgrade": False,
        "AvailabilityZone": config['AVAILABILITY_ZONE'],
        "BackupRetentionPeriod": "0",
        "DBInstanceClass": "db.t2.small",       # todo:?
        "DBInstanceIdentifier": config['RDS_NAME'],
        "Engine": "postgres",
        "EngineVersion": "9.3.14",
        # "KmsKeyId" ?
        "MasterUsername": "******",
        "MasterUserPassword": "******",
        "MultiAZ": False,
        "Port": "5432",
        "PubliclyAccessible": False,
        "StorageType": "gp2",
        "Tags": instance_tags,  # todo: Should be different?
    }

    if 'RDS_SNAPSHOT' in config:
        rds_properties['DBSnapshotIdentifier'] = config['RDS_SNAPSHOT']

    cft.resources.rds_instance = core.Resource(
        'RDSInstance', 'AWS::RDS::DBInstance',
        core.Properties(rds_properties),
        core.DeletionPolicy("Snapshot"),
        )

    volume_properties = {
        'AvailabilityZone': config['AVAILABILITY_ZONE'],
        'Encrypted': True,
        'Size': config['DATA_VOLUME_SIZE'],
        'Tags': tags.load(),
        'VolumeType': config['DATA_VOLUME_TYPE'],
    }

    if 'DATA_SNAPSHOT' in config:
        volume_properties['SnapshotId'] = config['DATA_SNAPSHOT']

    # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
    cft.resources.ebs = core.Resource(
        'RefineryData', 'AWS::EC2::Volume',
        core.Properties(volume_properties),
        core.DeletionPolicy("Snapshot"),
    )

    cft.resources.ec2_instance = core.Resource(
        'WebInstance', 'AWS::EC2::Instance',
        core.Properties({
            'AvailabilityZone': config['AVAILABILITY_ZONE'],
            'ImageId': 'ami-d05e75b8',
            'InstanceType': 'm3.medium',
            'UserData': functions.base64(user_data_script),
            'KeyName': config['KEY_NAME'],
            'IamInstanceProfile': functions.ref('WebInstanceProfile'),
            'Tags': instance_tags,
        }),
        core.DependsOn('RDSInstance'),
    )

    cft.resources.instance_profile = core.Resource(
        'WebInstanceProfile', 'AWS::IAM::InstanceProfile',
        core.Properties({
            'Path': '/',
            'Roles': [
              functions.ref('WebInstanceRole')
            ]
        })
    )

    cft.resources.web_role = core.Resource(
        'WebInstanceRole', 'AWS::IAM::Role',
        core.Properties({
            # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-templateexamples
            "AssumeRolePolicyDocument": {
               "Version": "2012-10-17",
               "Statement": [{
                  "Effect": "Allow",
                  "Principal": {
                     "Service": ["ec2.amazonaws.com"]
                  },
                  "Action": ["sts:AssumeRole"]
               }]
            },
            'ManagedPolicyArns': [
                'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess',
                'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'
            ],
            'Path': '/',
            'Policies': [
                {
                    'PolicyName': "CreateAccessKey",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "iam:CreateAccessKey"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            }
                        ]
                    },
                },
                {
                    'PolicyName': "CreateSnapshot",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ec2:CreateSnapshot"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            }
                        ]
                    }
                },
                {
                    'PolicyName': "CreateDBSnapshot",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "rds:CreateDBSnapshot"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            }
                        ]
                    }
                },
                {
                    'PolicyName': "CreateTags",
                    'PolicyDocument': {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ec2:CreateTags"
                                ],
                                "Resource": "*"
                            }
                        ]
                    }
                }
            ]
        })
    )

    cft.resources.smtp_user = core.Resource(
        'RefinerySMTPUser', 'AWS::IAM::User',
        core.Properties({
            'Policies': [{
                'PolicyName': "SESSendingAccess",
                'PolicyDocument': {
                    "Version": "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Action": "ses:SendRawEmail",
                        "Resource": "*"
                    }]
                }
            }]
        })
    )

    cft.resources.mount = core.Resource(
        'RefineryVolume', 'AWS::EC2::VolumeAttachment',
        core.Properties({
            'Device': '/dev/xvdr',
            'InstanceId': functions.ref('WebInstance'),
            'VolumeId': functions.ref('RefineryData'),
        })
    )

    cft.resources.elbsg = core.Resource(
        'ELBSecurityGroup', 'AWS::EC2::SecurityGroup',
        core.Properties({
            'GroupDescription': "Refinery ELB",
            'SecurityGroupEgress':  [],
            'SecurityGroupIngress': [
                {
                    "IpProtocol": "tcp",
                    "FromPort": "80",
                    "ToPort": "80",
                    "CidrIp": "0.0.0.0/0",
                },
                {
                    "IpProtocol": "tcp",
                    "FromPort": "443",
                    "ToPort": "443",
                    "CidrIp": "0.0.0.0/0",
                },
            ],
        })
    )

    # ELB per
    # http://cfn-pyplates.readthedocs.io/en/latest/examples/options/template.html

    # Insecure, Port 80, HTTP listener
    http_listener = {
        'LoadBalancerPort': '80',
        'Protocol': 'HTTP',
        'InstanceProtocol': 'HTTP',
        'InstancePort': '80',
        'PolicyNames': []
    }
    listeners = [http_listener]

    if 'TLS_CERTIFICATE' in config:
        # Secure, Port 443, HTTPS listener
        https_listener = {
            'LoadBalancerPort': '443',
            'Protocol': 'HTTPS',
            'InstanceProtocol': 'HTTP',
            'InstancePort': '80',
            'PolicyNames': [],
            'SSLCertificateId': config['TLS_CERTIFICATE']
        }
        listeners.append(https_listener)

    cft.resources.elb = core.Resource(
        'LoadBalancer', 'AWS::ElasticLoadBalancing::LoadBalancer',
        {
            'AvailabilityZones': [config['AVAILABILITY_ZONE']],
            'HealthCheck': {
                'HealthyThreshold': '2',
                'Interval': '30',
                'Target': 'HTTP:80/',
                'Timeout': '5',
                'UnhealthyThreshold': '4'
            },
            'Instances': [functions.ref('WebInstance')],

            'Listeners': listeners,
            'SecurityGroups': [
                functions.get_att('ELBSecurityGroup', 'GroupId')],
            "Tags": instance_tags,  # todo: Should be different?
        })

    sys.stdout.write(str(cft))