Giter Site home page Giter Site logo

bootstrap-cfn's Introduction

# flake8: noqa .. image:: https://travis-ci.org/ministryofjustice/bootstrap-cfn.svg :target: https://travis-ci.org/ministryofjustice/bootstrap-cfn

image

Ministry of Justice - Cloudformation

The objective of this repo is to enable MoJ teams to create project infrastructure in a uniform manner. Currently this includes the following AWS services:

  • EC2 Servers via Auto-Scaling Groups
  • Elastic Load Balancers (ELB)
  • Relational Database Service (RDS)
  • S3 Storage for web static content
  • VPC Configuration

Installation

git clone [email protected]:ministryofjustice/bootstrap-cfn.git
cd bootstrap-cfn
pip install -r requirements.txt

Developing and running tests

The test suite can be run via setup.py as follows

python -m unittest discover

or

python setup.py test

Example Usage

Bootstrap-cfn uses fabric, so if your $CWD is the root directory of bootstrap-cfn then you can run

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml cfn_create

If your $CWD is anywhere else, you need to pass in a path to particular fabric file

fab -f /path/to/bootstrap-cfn/fabfile.py application:courtfinder aws:prod environment:dev config:/path/to/courtfinder-dev.yaml tag:test cfn_create
  • application:courtfinder - is just a name to associate with Cloudformation stack
  • aws:dev - is a way to differentiate between AWS accounts (defined in ~/.aws/credentials.yaml)
  • environment:dev - The key name to read in the file specified to the config task
  • config:/path/to/file.yaml - The location to the project YAML file
  • tag:test - stack tag to differentiate between stacks
  • keyname:keyname - the name of the keypair you uploaded in AWS which should store your SSH public key.

Multiple Stacks

Multiple stacks feature is supported in bootstrap-cfn version greater than 1.0.0. It is similar to Blue/Green deploy. For each application and each environment of the application, we could have more than one stack independently running on AWS differentiated by tags we give. Any existing stack of same application in same environment can be switched to 'active' via operations on R53 records, which is used as actioning stack.

Here are the steps to create a new stack, we will explain them one by one later:

  • fab-env keyname:keyops tag:mytag cfn_create: create a new stack with a tag and keyname specified.
  • fab-env -u ubuntu salt.wait_for_minions: (optional) wait until instances are ready
  • fab-env -i ~/.ssh/id_your_ssh_private_key -u ubuntu update: install salt on the stack, add admins from keys.sls, to make the stack ready
  • fab-env -u [your-ssh-name] update: remove ubuntu user from the instances for security reason

Here fab-env refers to fab application:courtfinder aws:prod environment:dev config:/path/to/courtfinder-dev.yaml passwords:/path/to/courfinder-dev-secrets.yaml.

If you would like to set the stack you just created as the active stack of that environment, run the following:

  • fab-env set_active_stack:mytag to switch DNS entry to this stack

NB: If you want to have your multiple stacks under the same zone, make sure specify it in the yaml configuration

master_zone:
  my-zone.dsd.io

cfn_create

This is to create a stack based on your yaml configuration.

fab application:app aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml tag:mytag cfn_create

After running the above, stack app-dev-e21e5110 should be created, where 'e21e5110' is an auto-generated stack-id, along with two DNS records in Route 53 that looks like:

Name Type Value
stack.mytag.blah-dev.dsd.io.
TXT
"e21e5110"
elbname-e21e5110.dsd.io. A ALIAS app-dev-elbname-1ocl2znar6wtc-1854012795.eu-west-1.elb.amazonaws.com. (z32o12xqlntsw2)

Note that:

  • mytag in TXT record name is the tag for the stack. An auto-generated stack id that's saved in Value is used as the tag if it's not specified.
  • active tag is preserved for setting the main entry point, so you should not use it as a customised tag.
  • If the tag you specified already exists (may due to improper clean up in last creation), you could manually run fab tag:[tag-name] cfn_delete to remove the leftover.

NB fab task get_stack_list returns all stacks of that application in case if you forgot your tag name :)

set_active_stack(tag_name)

Active records indicate where an app's DNS entry is.

you can set whichever existing stack to be the active stack simply by specifying the tag name in set_active_stack(tag_name)

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml set_active_stack:[tag_name]

where [tag_name] would be the stack you would like to switch to. NB this process will also automatically set deployarn record accordingly.

cfn_delete

You can also delete any stack you want no more by specifying the tag, or remove active records (entry points) by putting active as the tag.

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml tag:[tag_name] cfn_delete

NB tag_name can be any existing tag. It defaults to active. When deleting an active stack, only active DNS records will be removed without harming any existing stacks. Otherwise the whole stack along with dns records are being removed.

cfn_update

Partial support for cloudformation updates is also supported on the EC2 and ELB sections of the configuration file.

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml tag:[tag_name] cfn_update

NB Running this command will show you some structured output of what changes and how. Also a unified diff is printed on output between the old and the new Launch Configuration sections. Although we have gone to great lengths with this command, it can result in destructive operations, particularly if one reduced the desired/max/min capacities of the Auto Scaling Group.

get_stack_list

This returns a list of all available stacks for specified application.

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml get_stack_list

support_old_bootstrap_cfn

After bootstrap-cfn 1.0.0, we suggest multiple stacks which add another set of R53 records to each stack. For stacks created by old bootstrap-cfn which possibly only has active records, support_old_bootstrap_cfn adds what's missing in R53 so that you are able to use other commands in bootstrap-cfn>=v1.0.0. It basically automates the manual operations of adding missing R53 records.

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml support_old_bootstrap_cfn

NB: after running this command, you will be asked to give the name of the stack you would like to operate on and also give a tag to the stack.

swap_tags

Then you can refer to this stack by its tag in the future. In this way it is easier to bring up two stacks from the same config. If you want to swap the names of the stacks you can do the following

fab application:courtfinder aws:my_project_prod environment:dev config:/path/to/courtfinder-dev.yaml swap_tags:inactive, active

others

There are also some fab tasks for example get_active_stack that returns active stack for this application and environment; get_stack_list returns any related stacks.

Example Configuration

AWS Account Configuration

This tool needs AWS credentials to create stacks and the credentials should be placed in the ~/.aws/credentials file (which is the same one used by the AWS CLI tools). You should create named profiles like this (and the section names should match up with what you specify to the fabric command with the aws:my_project_prod flag) :

[my_project_dev]
aws_access_key_id = AKIAI***********
aws_secret_access_key = *******************************************
[my_project_prod]
aws_access_key_id = AKIAI***********
aws_secret_access_key = *******************************************

If you wish to authenticate to a separate AWS account using cross account IAM roles you should create a profile called cross-account with the access keys of the user with permission to assume roles from the second account:

[cross-account]
aws_access_key_id = AKIAI***********
aws_secret_access_key = *******************************************

And when you run the tool you must set the ARN ID of the role in the separate account which you wish to assume. For example:

AWS_ROLE_ARN_ID='arn:aws:iam::123456789012:role/S3Access' fab application:courtfinder aws:prod environment:dev config:/path/to/courtfinder-dev.yaml cfn_create

Project specific YAML file

The YAML file highlights what is possible with all the bootstrap-cfn features available to date. The minimum requirement is that it must contain an ec2 block, you do not have to use RDS, S3 or ELB's.

EC2 Auto-Scaling Groups

The ec2 key configures the EC2 instances created by auto-scaling groups (ASG) and their configuration. Note that we don't currently support auto-scaling properly, so if a scaling event happens the instances that come up will be unconfigured.

auto_scaling

Configure the size of the auto scaling groups.

desired

Target number of instances

max

Maximum number of instances to scale up to

min

Minimum number of instances to maintain.

health_check_grace_period

Seconds before running the healthcheck on an instance. Default 300

health_check_type

Use EC2 or ELB healthcheck types. Default EC2

Example

dev:
  ec2:
    #
    auto_scaling:
      desired: 1
      max: 3
      min: 0
      health_check_grace_period: 360
      health_check_type: ELB
tags

A dictionary of tag name to value to apply to all instances of the ASG. Note that the environment you select via fab aws will be applied as a tag with a name of Env.

Example

dev:
  ec2:
    #
    tags:
      Role: docker
      Apps: test
      # Env: dev # This is default if we are in the `dev` environment block.
parameters

Configuration parameters to the ASG. Known keys:

KeyName

Name of an existing key-pair in the SSH account to create add to the intial ssh user on instances

InstanceType

The size of the EC2 instances to create

Example

dev:
  ec2:
    #
    parameters:
      KeyName: default
      InstanceType: t2.micro
ami

Selects which AWS AMI to use. This can be a AWS-provided AMI, a community one, or one which exists under the account in which you're building the stack. The ami- prefix is required. If not specified then a suitable default will be chosen for the os in use. If this value is present then it is recommended to specify the os too, so that other areas of the cloud formation template are correctly generated.

Example

dev:
  ec2:
    ami: ami-7943ec0a
    os: windows2012
os

Which operating system to use. This selects a default AMI and also builds relevant user_data for use by instances when spun up by the ASG. Only 2 values are recognised: windows2012 and ubuntu-1404. The default is ubuntu-1404. If you wish to specify an AMI manually then use ami in addition.

Example

dev:
  ec2:
    os: windows2012
block_devices

A list of EBS volumes to create and attach to per instance. Each list should have

DeviceName

The path of the linux device to attach the instance to

VolumeSize

Size in gigabytes of the EBS volume

VolumeType (optional)

The type of the volume to create. One of standard (default), gp2 or io1 (see AWS API reference)

Iops (Required for io1 type)

The Iops value to assign to the io1 volume type.

Example

dev:
  ec2:
    #
    block_devices:
      - DeviceName: /dev/sda1
        VolumeSize: 10
      - DeviceName: /dev/sdf
        VolumeType: gp2
        VolumeSize: 100
      - DeviceName: /dev/sdh
        VolumeType: io1
        VolumeSize: 80
        Iops: 1200
security_groups

Dictionary of security groups to create and add the EC2 instances to. The key is the name of the security group and the value is a list of ingress rules following the Cloudformation reference

Common options are

IpProtocol

tcp, udp, or icmp

FromPort

Start of the port range or ICMP type to allow

ToPort

End of the port range/ICMP type. Often the same as FromPort

CidrIp

An IP range to allow access to this port range.

SourceSecurityGroupId

Allow access from members of this security group - which must exist in the same VPC. Use Ref (see example) to refer to a security group by name. Can be another SG referenced elsewhere or the same security group.

One of CidrIp and SourceSecurityGroupId must be specified per rule (but not both).

Example

dev:
  ec2:
    #
    security_groups:
      # Don't to this - it's too wide open
      SSH-from-anywhere:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 2222
          ToPort: 2222
          CidrIp: 0.0.0.0/0
      WebServer:
        # Allow acces to port 80 from the SG 
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: { Ref: DefaultSGtestdevexternal }
      Salt:
        # Allow all other members of the Salt sg to speak to us on 4505 and 4506
        - IpProtocol: tcp
          FromPort: 4505
          ToPort: 4506
          SourceSecurityGroupId: { Ref: Salt }
cloud_config

Dictionary to be feed in via userdata to drive cloud-init to set up the initial configuration of the host upon creation. Using cloud-config you can run commands, install packages

There doesn't appear to be a definitive list of the possible config options but the examples are quite exhaustive:

hostname_pattern

A python-style string format to set the hostname of the instance upon creation.

The default is {instance_id}.{environment}.{application}. To disable this entirely set this field explicitly to null/empty

Example

dev:
  ec2:
    hostname_pattern:

For sudo to not misbehave initially (because it cannot look up its own hostname) you will likely want to set manage_etc_hosts to true in the cloud_config section so that it will regenerate /etc/hosts with the new hostname resolving to 127.0.0.1.

Setting the hostname is achived by adding a boothook into the userdata that will interpolate the instance_id correctly on the machine very soon after boottime.

The currently support interpolations are:

instance_id

The amazon instance ID

environment

The enviroment currently selected (from the fab task)

application

The application name (taken from the fab task)

stack_name

The full stack name being created

tags

A value from a tag for this autoscailing group. For example use tags[Role] to access the value of the Role tag.

For example given this incomplete config

dev:
  ec2:
    #
    hostname_pattern: "{instance_id}.{tags[Role]}.{environment}.{application}"
    tags:
      Role: docker
    cloud_config:
      manage_etc_hosts: true

an instance created with fab application:myproject … cfn_create would get a hostname something like i-f623cfb9.docker.dev.my-project.

ELBs

By default the ELBs will have a security group opening them to the world on 80 and 443. You can replace this default SG with your own (see example ELBSecGroup above).

If you set the protocol on an ELB to HTTPS you must include a key called certificate_name in the ELB block (as example above) and matching cert data in a key with the same name as the cert under ssl (see example above). The cert and key are required and the chain is optional.

It is possilbe to define a custom health check for an ELB like follows

health_check:
  HealthyThreshold: 5
  Interval: 10
  Target: HTTP:80/ping.json
  Timeout: 5
  UnhealthyThreshold: 2

ELB Certificates

ACM

This section defines certificates for the AWS Certificate Manager. For verification, these will require the setting up of SES for the ValidationDomain so that emails to admin@<validation_domain> can be received.

For example,

acm:
  mycert:
    domain: helloworld.test.dsd.io
    subject_alternative_names:
        - goodbye.test.somewhere.io
    validation_domain: dsd.io
    domain_validation_options:
      - domain_name: goodbye.test.somewhere.io
        validation_domain: somewhere.io
    tags:
        site: testsite

Manual SSL

The SSL certificate will be uploaded before the stack is created and removed after it is deleted. To update the SSL certificate on ELB listeners run the fab task below, this uploads and updates the certificate on each HTTPS listener on your ELBs, by default the old certificate is deleted.

fab load_env:<env_data> update_certs

Note that some errors appear in the log due to the time taken for AWS changes to propogate across infrastructure elements, these are handled internally and are not neccessarily a sign of failure.

ELB Policies

Policies can be defined within an ELB block, and optionally applied to a list of instance ports or load balancer ports. The below example enable proxy protocol support on instance ports 80 and 443

policies:
  - name: EnableProxyProtocol
    type: ProxyProtocolPolicyType
    attributes:
      - ProxyProtocol: True
    # We can optionally define the instance or load_balancer ports
    # to here that the policy will be applied on
    instance_ports:
      - 80
      - 443
    #load_balancer_ports:
    #  - 80
    #  - 443

Elasticache

By specifying an elasticache section, a redis-backed elasticache replication group will be created. The group name will be available as an output.

elasticache:                     # (REQUIRED) Main elasticache key, use {} for all default settings. Defaults are shown
   clusters: 3                   # (OPTIONAL) Number of one-node clusters to create
   node_type: cache.m1.small     # (OPTIONAL) The node type of the clusters nodes
   port: 6379                    # (OPTIONAL) Port number 
   seeds:                        # (OPTIONAL) List of arns to seed the database with
      s3:                        # (OPTIONAL) List of S3 bucket seeds in <bucket>/<filepath> format
         - "test-bucket-947923urhiuy8923d/redis.rdb"

S3

An s3 section can be used to create a StaticBucket, which is exposed by nginx, but default as /assets. The bucket location will be by default public, with an output available of 'StaticBucketName'. We can create the static bucket without any arguments, though this requires the use of {} as below.

s3: {}   # Required if we have no keys and use all defaults

Or we can specify the name, and optionally a custom policy file if we want to to override bootstrap-cfn's default settings. For example, the sample custom policy defined in this json file can be configured as follows:

s3:
     static-bucket-name: moj-test-dev-static
     policy: tests/sample-custom-s3-policy.json

We can also supply a list of buckets to create a range of s3 buckets, these require a name. These entries can also specify their own policies or use the default, vpc limited one. Policies for these additional buckets can be provided as an individual policy document the same as for the static bucket or a list as in this example multi-part policy

s3:
   buckets:
      - name: mybucketid
        policy: some_policy
        lifecycles:
          /prefix1:
            expirationdays: 60
          /prefix2:
            expirationdays: 30
      - name: myotherbucketid
        lifecycles:
          /:
          expirationdays: 5
        policy: tests/sample-s3-additional-bucket-multi-part-custom-policy.json

The outputs of these buckets will be the bucket name postfixed by 'BucketName', ie, mybucketidBucketName. Additionally, and as shown above, one can define a list of Lifecycle rules on a per prefix basis. If a root rule is defined, the rest of the rules are ignored.

Currently, only non-versioned buckets are supported.

Includes

If you wish to include some static cloudformation json and have it merged with the template generated by bootstrap-cfn. You can do the following in your template yaml file

includes:
  - /path/to/cloudformation.json

The tool will then perform a deep merge of the includes with the generated template dictionary. Any keys or subkeys in the template dictionary that clash will have their values overwritten by the included dictionary or recursively merged if the value is itself a dictionary.

ConfigParser

If you want to include or modify cloudformation resources but need to include some logic and not a static include. You can subclass the ConfigParser and set the new class as env.cloudformation_parser in your fabfile.

Enabling RDS encryption

You can enable encryption for your DB by adding the following

rds:
   storage-encrypted: true
   instance-class: db.m3.medium

NOTE: AWS does not support RDS encryption for the db.t2.* instance classes. More details on supported instance classes are available here

SSL cipher list pindown (updated 29/06/2015)

Amazon provides default policies for cipher lists:

  • Type: SSLNegotiationPolicyType
  • Name: Reference-Security-Policy

More info:

The policy currently in use by default is: ELBSecurityPolicy-2015-05.

bootstrap-cfn's People

Contributors

aldavidson avatar andyhd avatar ashb avatar bennythejudge avatar chriswood123 avatar ciprianc avatar csutter avatar ezman avatar filipposc5 avatar kamalahb avatar koikonom avatar kwilczynski avatar kyriakosoikonomakos avatar ltsampros avatar lukaszraczylo avatar mattmb avatar mattpep avatar milosgajdos avatar pidah avatar pwyborn avatar robyoung avatar stuartornum avatar ushkarev avatar yufangzhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bootstrap-cfn's Issues

Make YAML anchors possible in the config

i.e. make this work:

dev: &DEV
  ec2:
    auto_scaling:
      desired: 2
      max: 3
      min: 1
    tags:
      Role: docker
      Apps: crimebillingonline
      Env: dev
    parameters:
      KeyName: default
      InstanceType: m3.large
    block_devices:
      - DeviceName: /dev/sda1
        VolumeSize: 10
      - DeviceName: /dev/sdg
        VolumeSize: 10
    security_groups:
      MyBaseSG:
        - 
      WebServer:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId:
            Ref: DefaultSGdev
      MySaltSG:
        - IpProtocol: tcp
          FromPort: 4505
          ToPort: 4506
          SourceSecurityGroupId:
            Ref: MyBaseSG
  elb:
    - name: dev
      hosted_zone: crimebillingonline.dsd.io.
      scheme: internet-facing
      listeners:
        - LoadBalancerPort: 80
          InstancePort: 80
          Protocol: TCP
  s3:
    static-bucket-name: cbo-dev-assets


demo:
  # Demo is like dev, but with these changes
  <<: *DEV
  ec2:
    tags:
      Env: demo
    security_groups:
      WebServer:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId:
            Ref: DefaultSGdemo
  elb:
    - name: demo
      hosted_zone: crimebillingonline.dsd.io.
      scheme: internet-facing
      listeners:
        - LoadBalancerPort: 80
          InstancePort: 80
          Protocol: TCP
  s3:
    static-bucket-name: cbo-demo-assets
  rds:
    db-name: cbo-demo

Not sure if this is easy or not but seems like it would be cleaner.

Add multiple s3 buckets

Add the ability to add a 'groups' tag to the s3 section and set an arbitrary number of custom s3 buckets for the stack.

s3:
  - groups
       my-new-bucket:
           policy: some-policy.json
      another-bucket-name: {}

Add a fab task to compare the configuration of an existing stack with a new one based on yaml

Given an existing stack and a yaml configuration (not yet used to create a stack),

  • extract the json of the existing AWS stack
  • generate the json from the yaml (for example like when running cfn_create:test=true)
  • compare the two (using an off-the-shelf module that offers the best features)

It could be used when rebuilding a stack to compare the differences before deleting the existing stack and rebuilding.

Not waiting for instances in scaling groups.

It seems that cloud formation reports scaling groups as 'complete' before the instances have finished running anything in their launch configuration. Ideally we don't want cloud formation to report complete until the instances have finished launching.

This or this seem to be the answer but I didn't have any luck last week.

Salt security groups

We currently create the security groups for salt using boto.

It would be better if we created them with cloudformation. This means referencing a CF created security group as the source.

ELB resource should have options for SSL certs and communication protocol

At the moment by default bootstrap-cfn creates TCP based ELBs.

There should be an option of a communication protocol. In practice we mostly use HTTP/HTTPs Load Balancers, anyways.

On top of this, you should have an option to specify a path to SSL certificates and keys if you need to create TCP(SSL)/HTTPS ELB.

Convert to Troposphere to build template

We can replace all the manual munging of JSON and referencing structures three levels deep with Troposphere.

Ash made a start by converting each of the elb, rds, etc. methods use Troposphere objects to build things but those still return dicts so we can tidy it up and remove some of the hacks – i.e. we could have each of those functions take a troposphere Template object and add the resources/outputs/mappings directly.

Acceptance

  • No JSON stacks left in the project
  • Everything else should remain backwards compatible, no config changes or interface changes
  • Internals of bootstrap-cfn use Troposphere properly and don't return plain python dict objects

Rsync requires the folder name to be <app_name>-deploy

Fab task rsync uses
work_dir = os.path.join('..', '{0}-deploy'.format(env.application))
This means that the directory must be <app_name> from the cloud formation config file plus a '-deploy' extension.
Need to change this work_dir to find the actual dir
`os.dirname(env.real_fabfile)

or documentated

Upgrade to boto3 for bootstrap-cfn

Boto3, the next version of Boto, is now stable and recommended for general use. It can be used side-by-side with Boto in the same project, so it is easy to start using Boto3 in your existing projects as well as new projects. Going forward, API updates and all new feature work will be focused on Boto3.

The AWS CLI tools use boto3 and they have some code we want to use around MFA and assuming roles

Replace custom config loading with Boto's own loading of `~/.aws/credentials`

Boto has built in config loading for credentials already and support for multiple accounts – we should work out how to use this

An example of how this works:

In [10]: c = connect_to_region("eu-west-1", profile_name="intra")

In [11]: c.get_all_instances()
Out[11]:
[Reservation:r-a77edfe7,
 Reservation:r-c4650a86,
 Reservation:r-0925624b,
 Reservation:r-0a4347ef,
…
 Reservation:r-ba5770f8,
 Reservation:r-f82b89b8,
 Reservation:r-c10f6683,
 Reservation:r-b092f4f2]

My ~/.aws/credentials has this in it:

[intra]
aws_access_key_id = AKIA**xxx**
aws_secret_access_key = xxx

(As a bonus it means we don't need the super generic ~/.config.yaml file)

Deleting certificates

Looks like we introduced a problem when deleting certificates.

File "/Users/matt/virtualenvs/fr-deploy/lib/python2.7/site-packages/fabric/main.py", line 743, in main
    *args, **kwargs
  File "/Users/matt/virtualenvs/fr-deploy/lib/python2.7/site-packages/fabric/tasks.py", line 424, in execute
    results['<local-only>'] = task.run(*args, **new_kwargs)
  File "/Users/matt/virtualenvs/fr-deploy/lib/python2.7/site-packages/fabric/tasks.py", line 174, in run
    return self.wrapped(*args, **kwargs)
  File "/Users/matt/working/bootstrap-cfn/bootstrap_cfn/fab_tasks.py", line 141, in cfn_delete
    iam.delete_ssl_certificate(cfn_config.ssl(), stack_name)
  File "/Users/matt/working/bootstrap-cfn/bootstrap_cfn/iam.py", line 35, in delete_ssl_certificate
    force=True)
TypeError: delete_certificate() got an unexpected keyword argument 'force'

@niallcreech can you take a look? I'm guessing we just missed removing the force kwarg here:
https://github.com/ministryofjustice/bootstrap-cfn/blob/master/bootstrap_cfn/iam.py#L35

Bootstrap automatically on instance create

Investigate bootstrapping salt in an ASG.

At the moment we run the bootstrap from fabric. This has the disadvantage that auto scaling is not auto!

Things to think about:

  • picking a master
  • exchanging keys

Handle config file not found more gracefully

If you run fab config:i-dont-exist.yml you get this behaviour:

Traceback (most recent call last):
  File "/Users/aberlin/.virtualenvs/cbo-deploy/lib/python2.7/site-packages/fabric/main.py", line 743, in main
    *args, **kwargs
  File "/Users/aberlin/.virtualenvs/cbo-deploy/lib/python2.7/site-packages/fabric/tasks.py", line 424, in execute
    results['<local-only>'] = task.run(*args, **new_kwargs)
  File "/Users/aberlin/.virtualenvs/cbo-deploy/lib/python2.7/site-packages/fabric/tasks.py", line 174, in run
    return self.wrapped(*args, **kwargs)
  File "/Users/aberlin/.virtualenvs/cbo-deploy/lib/python2.7/site-packages/bootstrap_cfn/fab_tasks.py", line 119, in cfn_delete
    cfn_config = get_config()
  File "/Users/aberlin/.virtualenvs/cbo-deploy/lib/python2.7/site-packages/bootstrap_cfn/fab_tasks.py", line 102, in get_config
    passwords=env.stack_passwords)
  File "/Users/aberlin/.virtualenvs/cbo-deploy/lib/python2.7/site-packages/bootstrap_cfn/config.py", line 16, in __init__
    self.config = self.load_yaml(config)[environment]
TypeError: 'NoneType' object has no attribute '__getitem__'

This is far from optimal and we should check for the file not existing instead.

block_devices should not be mandatory in ec2 section

If I don't care about attached volume to an instance I'm trying to create I should not need block_devices section/parameter to be present in my configuration file. Some default value should be used:

Example (incomplete) config snippet:

dev:
  ec2:
    auto_scaling:
      desired: 1
      max: 3
      min: 0
    tags:
      Project: docker-registry
      Role: docker
      App: docker-registry
      Env: dev
    parameters:
      KeyName: default
      InstanceType: t2.medium

Error:

Traceback (most recent call last):
  File "/Users/milosgajdos/projects/bootstrap-cfn/lib/python2.7/site-packages/fabric/main.py", line 743, in main
    *args, **kwargs
  File "/Users/milosgajdos/projects/bootstrap-cfn/lib/python2.7/site-packages/fabric/tasks.py", line 424, in execute
    results['<local-only>'] = task.run(*args, **new_kwargs)
  File "/Users/milosgajdos/projects/bootstrap-cfn/lib/python2.7/site-packages/fabric/tasks.py", line 174, in run
    return self.wrapped(*args, **kwargs)
  File "/Users/milosgajdos/repos/bootstrap-cfn/fabfile.py", line 78, in cfn_create
    stack = cfn.create(stack_name, cfn_config.process())
  File "/Users/milosgajdos/repos/bootstrap-cfn/helpers/config.py", line 49, in process
    ec2 = self.ec2()
  File "/Users/milosgajdos/repos/bootstrap-cfn/helpers/config.py", line 189, in ec2
    for i in self.data['ec2']['block_devices']:
KeyError: 'block_devices'

Something like 10G seems reasonable enough to me.

Acceptance

  • Sane defaults provided for block_devices secton
  • Tests added to cover this case
  • Docs added to readme.

Allow configuring EC2 volume type

At the moment we default to EBSBlockDevice, we need to be able to allow SSD

for example:

  prod:
    master_zone: domain.io
    ec2:
      block_devices:
        - DeviceName: /dev/sda1
          VolumeSize: 10
       - DeviceName: /dev/sdf
          VolumeType: gp2
          VolumeSize: 20
        - DeviceName: /dev/sdg
          VolumeType: io1
          Iops: 1200
          VolumeSize: 20

When the s3 policy json file is missing, cfn_create dies without message

Example 1 using current bootstrap_cfn:

21:20 $ fab application:crimebillingonline aws:cbo environment:opstest1 config:./cloudformation/crimebillingonline.yaml passwords:./cloudformation/crimebillingonline-secrets.yaml cfn_create

Fatal error: Failed to create:

Aborting.
Failed to create:

Example 2: using a local copy of bootstrap_cfn modified to print the message:

21:28 $ fab application:crimebillingonline aws:cbo environment:opstest1 config:./cloudformation/crimebillingonline.yaml passwords:./cloudformation/crimebillingonline-secrets.yaml cfn_create

Fatal error: Failed to create: [Errno 2] No such file or directory: './cloudformation/s3-bucket-policy-opstest1.json'

Aborting.
Failed to create: [Errno 2] No such file or directory: './cloudformation/s3-bucket-policy-opstest1.json'

File: fab_tasks.py
Line: 345

    try:
        stack = cfn.create(stack_name, cfn_config.process())
    except Exception as e:
        abort(red("Failed to create: {error}".format(error=e.message)))

to make it print the error message:

    try:
        stack = cfn.create(stack_name, cfn_config.process())
    except Exception as e:
        abort(red("Failed to create: errno: {0} message: {1} strerror: {2}".format(e.errno, e.message, e.strerror)))

The problem is that in this case, message is empty, while errno and strerror contain values.
The above seems a reasonable workaround that covers all cases as it

Let Cloudformation generate names for S3 resources

We currently hard code the name of the ELB, RDS and S3 buckets that bootstrap-cfn creates.

This means that creating duplicate environments of the same flavour (i.e. another 'dev' env) means we have to duplicate the block in the YAML and change some identifiers.

Instead we can do this:

{
    "Outputs": {
      "staticbucket": {
        "Description": "x",
        "Value": { "Ref": "StaticBucket" }
      }
    },
    "Resources": {
        "StaticBucket": {
            "Properties": {
                "AccessControl": "BucketOwnerFullControl"
            },
            "Type": "AWS::S3::Bucket"
        }
    }
}

Note how there's no BucketName specified.

I went and created this template and cloudformation with a stack name of "Ash-s3-test" and the bucket that was created was ash-s3-test-staticbucket-1hgn7cc7bhnak – that's <stackname>-<logical-resource-name>-<random>. Given that we can make it an output this means we can find the name given in the same way we do with RDS.

We should do similar things for the ELB (where the name of ELB doesn't matter, just the DNS name) and RDS (which we don't need the name at all anyway - just the endpoint which is already an output)

Show Cloudformation events in the terminal

AWS creates events as it creates the various resources in the Cloudformation stack - having these displayed in the console too would be nice to know what's going on.

Add MFA and role-assuming ability (by using code from AWS CLI)

Depends on #130.

Using the AWS The AWS CLI tools you can specify a profile with using a cross-account role as follows:

In ~/.aws/credentials:

[development]
aws_access_key_id=foo
aws_access_key_id=bar

And then in ~/.aws/config:

[profile crossaccount]
role_arn=arn:aws:iam:...
source_profile=development
mfa_serial=arn:aws:iam::123456789012:mfa/user

When you use this "crosaccount" profile boto/aws-cli asks you for the MFA code on the terminal which is cached for as long as the temporary credeitals last (which appears to default to one hour. Unsure if we can length this or not)

The net effect of this is that we can have manage the users in one place (the central account), delegate access to sub-accounts (to come in another ticket), and protect our AWS accoutns with two-factor auth.

The code that we need to import and call is here
https://github.com/aws/aws-cli/blob/1.7.38/awscli/customizations/assumerole.py and the register_assume_role_provider function seems to be called by this chain

(but we possibly don't have to have the full hierarchy for our use so long as the credential provider in assumerole.py is added)

Acceptance

  • We can specify role_arn and source_profile in config/credentials and have bootstrap-cfn operate in the right account
  • We can specify mfa_serial and have two-factor-auth protected accounts.

Release to PyPi

Bootstrap-salt relies on this module and currently it has to be installed manually (because setup.py can't pull modules from git like pip can).

This module is public so putting it on pypi makes some sense anyway.

ELB healthcheck customisation.

At the moment we take the listeners key from the yaml:
https://github.com/ministryofjustice/bootstrap-cfn/blob/master/docs/sample-project.yaml#L38

and put it into the CF ELB listeners section. This means we can't do custom health checks like this:
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-elb.html

Suggestion we add a key called healthcheck and also dump this into the CF json here:
https://github.com/ministryofjustice/bootstrap-cfn/blob/master/bootstrap_cfn/config.py#L209

cfn_delete consistently fails

CF output:

11:54:19 UTC+0000 DELETE_FAILED AWS::EC2::VPCGatewayAttachment AttachGateway Network vpc-69a8810c has some mapped public address(es). Please unmap those public address(es) before detaching the gateway.

pip freeze tells me:

bootstrap-cfn==0.7.5

If I manually find the IGW and detach it, then I can delete the stack.

Change how we deal with DNS to make it easier to create 'alternate' stacks for an environment

We would like to be in a position where we can build a completely parallel stack that doesn't conflict with the existing (say) production stack.

One thing currently stopping this is how we create the DNS records. To use a concrete example, lets take our graphite production stack which defines these two ELBs: (it defines more, but two is clear enough) and the stack id for the current stack is graphite-prod-08e50510

elbs:
  - name: grafana.service
    hosted_zone: dsd.io.
    ...
  - name: stunnel-graphite.service
    hosted_zone: dsd.io.
    ...

This will create two ELBs and for each ELB it will create the following DNS ALIAS records:

  • grafana.service.dsd.io
  • stunnel-graphite.service.dsd.io

And this TXT record is created by the fab tasks: stack.active.graphite-prod.dsd.io with a value of "08e50510".

This means that we can't create a new production stack even though it would create alternate load balancers and RDS instances etc. because it would conflict (or overwrite) those DNS records.

So a new approach is needed. In bootstrap-cfn we already have of a tag (for instance "active" and "inactive". If you don't specify a tag it will be "active") so with a small change we can extend this concept to work for our DNS records too.

The proposal is to change our Cloudformation stack so that:

  • We use the stack id when creating the DNS records, so for this example we would create grafana.service.08e50510.dsd.io and stunnel-graphite.08e50510.dsd.io (the form of that is <elb-name>.<stack-id>.<zone>)
    • If we are creating a stack tagged 'active' and the alias grafana.service.dsd.io doesn't already exist then create one extra ALIAS record (outside of Cloudformation, but still within bootstrap-cfn) from grafana.service.dsd.io pointing to grafana.service.08e50510.dsd.io

(Maybe we should use the tag name instead of the stack id in these DNS records? It's easier to intuit about that way).

Then when we come to create a new stack, lets tag it "blue":

  • fab tag:blue cfn_create ..... Lets say this creates a stack called graphite-prod-aee3683e
  • It already creates a TXT record of stack.blue.graphite-prod.dsd.io with a value of "aee3683e"
  • grafana.service.aee3683e.dsd.io a record is also created by Cloudformation
  • We check the new stack manually, run any data migration etc,
  • Then we runrun fab promote:blue,active fab task (or something. Lets work out something sensible)
    • this will swap the grafana.service.dsd.io cname from grafana.service.08e50510.dsd.io to grafana.service.aee3683e.dsd.io
    • (and the 'active' txt record, etc. what ever else it currently does.)

EC2 instance SG not included in RDS instance's SG

When creating CF stack which has EC2 instance(s) and RDS, RDS SG does not include EC2's SG.
This has a consequence of EC2 instance not being able to communicate with RDS instance.

This is a similar issue to #13

Let Cloudformation generate names for ELB resources

We currently hard code the name of the ELB, RDS and S3 buckets that bootstrap-cfn creates.

This means that creating duplicate environments of the same flavour (i.e. another 'dev' env) means we have to duplicate the block in the YAML and change some identifiers.

Instead we can do this:

{
    "Outputs": {
      "staticbucket": {
        "Description": "x",
        "Value": { "Ref": "StaticBucket" }
      }
    },
    "Resources": {
        "StaticBucket": {
            "Properties": {
                "AccessControl": "BucketOwnerFullControl"
            },
            "Type": "AWS::S3::Bucket"
        }
    }
}

Note how there's no BucketName specified.

I went and created this template and cloudformation with a stack name of "Ash-s3-test" and the bucket that was created was ash-s3-test-staticbucket-1hgn7cc7bhnak – that's <stackname>-<logical-resource-name>-<random>. Given that we can make it an output this means we can find the name given in the same way we do with RDS.

We should do similar things for the ELB (where the name of ELB doesn't matter, just the DNS name) and RDS (which we don't need the name at all anyway - just the endpoint which is already an output)

Install minions in parallel

We should make the install minions task run in parallel somehow. Otherwise the bootstrap time increases with the size of the auto scaling group.

Let Cloudformation generate names for RDS resources

We currently hard code the name of the ELB, RDS and S3 buckets that bootstrap-cfn creates.

This means that creating duplicate environments of the same flavour (i.e. another 'dev' env) means we have to duplicate the block in the YAML and change some identifiers.

Instead we can do this:

{
    "Outputs": {
      "staticbucket": {
        "Description": "x",
        "Value": { "Ref": "StaticBucket" }
      }
    },
    "Resources": {
        "StaticBucket": {
            "Properties": {
                "AccessControl": "BucketOwnerFullControl"
            },
            "Type": "AWS::S3::Bucket"
        }
    }
}

Note how there's no BucketName specified.

I went and created this template and cloudformation with a stack name of "Ash-s3-test" and the bucket that was created was ash-s3-test-staticbucket-1hgn7cc7bhnak – that's <stackname>-<logical-resource-name>-<random>. Given that we can make it an output this means we can find the name given in the same way we do with RDS.

We should do similar things for the ELB (where the name of ELB doesn't matter, just the DNS name) and RDS (which we don't need the name at all anyway - just the endpoint which is already an output)

In fab_tasks.py, if cfn_create raises an exception, certificates are not cleaned up

If cfn_create raises an exception and fails (line 344), and certificates had been created (line 336),
the certificates are not deleted (line 346).
We need to replicate the code at line 360 upon cfn_create exception to cleanup certificates.

334     cfn = get_connection(Cloudformation)
335     # Upload any SSL certs that we may need for the stack.
336     if 'ssl' in cfn_config.data:
337         iam = get_connection(IAM)
338         iam.upload_ssl_certificate(cfn_config.ssl(), stack_name)
339     # Useful for debug
340     # print cfn_config.process()
341     # Inject security groups in stack template and create stacks.
342     try:
343         stack = cfn.create(stack_name, cfn_config.process())
344     except:
345         import traceback
346         abort(red("Failed to create: {error}".format(error=traceback.format_exc())))
347
348     print green("\nSTACK {0} CREATING...\n").format(stack_name)
349
350     if not env.blocking:
351         print 'Running in non blocking mode. Exiting.'
352         sys.exit(0)
353
354     tail(cfn, stack_name)
355     stack_evt = cfn.get_last_stack_event(stack)
356
357     if stack_evt.resource_status == 'CREATE_COMPLETE':
358         print 'Successfully built stack {0}.'.format(stack)
359     else:
360         # So delete the SSL cert that we uploaded
361         if 'ssl' in cfn_config.data:
362             iam.delete_ssl_certificate(cfn_config.ssl(), stack_name)
363         abort('Failed to create stack: {0}'.format(stack))

Delete bucket contents before stack.

Stack deletion will fail if there is content in the S3 bucket.

If the S3 bucket is just for static assets then we should delete the contents of the bucket before we delete the stack.

get_stack_name uses fallback name when route53 lookup fails

get_stack_name uses fallback name of _ when route53 lookup fails. This was done for backwards compatibility reasons. Unfortunately this has the side effect of masking any route53 stack_name lookup error, which all new stacks should have. This seems more damaging and time-consuming than defaulting to failure.

Elastic IPs

Some projects need to use a specific IP address and so we have used elastic IPs.

Because the instances are in autoscaling groups if they come and go they will get a new IP each time and we have to manually assign an elastic IP.

What would be nice is to specify a list of elastic IPs in the cloudformation template and for the instances to automatically "get" one of these IPs if it can.

I'm thinking:

  ec2:
    auto_scaling:
      desired: 1
      max: 3
      min: 0
    elastic_ips:
      - 54.1.1.1
      - 54.1.1.2
      - 54.1.1.3

And then a boto script in the userdata that somehow tries to associate each of these if they are available.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.