Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
Merge b1db6fc into f8259a7
Browse files Browse the repository at this point in the history
  • Loading branch information
niallcreech committed Feb 14, 2017
2 parents f8259a7 + b1db6fc commit 6a0f4bd
Show file tree
Hide file tree
Showing 5 changed files with 424 additions and 91 deletions.
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa
.. image:: https://travis-ci.org/ministryofjustice/bootstrap-cfn.svg
:target: https://travis-ci.org/ministryofjustice/bootstrap-cfn

Expand Down Expand Up @@ -444,6 +445,22 @@ It is possilbe to define a custom health check for an ELB like follows
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@address.com can be recieved.

.. code:: yaml
acm:
my-cert:
domain: helloworld.test.dsd.io # (required) The domain name or wildcard the certificate should cover
validation_domain: dsd.io # (optional) The domain name the verfication email should go to. The default is dsd.io
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.
Expand Down
212 changes: 121 additions & 91 deletions bootstrap_cfn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from troposphere import Base64, FindInMap, GetAZs, GetAtt, Join, Output, Ref, Tags, Template
from troposphere.autoscaling import AutoScalingGroup, BlockDeviceMapping, \
EBSBlockDevice, LaunchConfiguration, Tag
from troposphere.certificatemanager import Certificate, DomainValidationOption
from troposphere.ec2 import InternetGateway, Route, RouteTable, SecurityGroup, \
SecurityGroupIngress, Subnet, SubnetRouteTableAssociation, VPC, \
VPCGatewayAttachment
Expand Down Expand Up @@ -155,7 +156,6 @@ def process(self):
template, sort_keys=True, indent=None, separators=(',', ': '))

def base_template(self):
from bootstrap_cfn import vpc
t = Template()

# Get the OS specific data
Expand Down Expand Up @@ -430,7 +430,7 @@ def create_s3_bucket(self, bucket_config, template):
map(template.add_resource, [bucket, bucket_policy])

def ssl(self):
return self.data['ssl']
return self.data.get('ssl', {})

def rds(self, template):
"""
Expand Down Expand Up @@ -730,46 +730,14 @@ def elb(self, template):

for listener in load_balancer.Listeners:
if listener['Protocol'] == 'HTTPS':
try:
cert_name = elb['certificate_name']
except KeyError:
raise errors.CfnConfigError(
"HTTPS listener but no certificate_name specified")
try:
self.ssl()[cert_name]['cert']
self.ssl()[cert_name]['key']
except KeyError:
raise errors.CfnConfigError(
"Couldn't find ssl cert {0} in config file".format(cert_name))

listener["SSLCertificateId"] = Join("", [
"arn:aws:iam::",
Ref("AWS::AccountId"),
":server-certificate/",
"{0}-{1}".format(cert_name, self.stack_name)]
)
listener["SSLCertificateId"] = self._get_ssl_certificate(template, elb.get('certificate_name', None))
# if not present, add the default cipher policy
if 'PolicyNames' not in listener:
logging.debug(
"ELB Listener for port 443 has no SSL Policy. " +
"Using default ELBSecurityPolicy-2015-05")
listener['PolicyNames'] = ['PinDownSSLNegotiationPolicy201505']
"""
# Get all the listeners policy names and setup the policies they refer to
for policy_name in listener.get('PolicyNames', []):
matched_policies = [custom_policy for custom_policy in elb_policies
if custom_policy.PolicyName == policy_name]
assert(len(matched_policies) == 1)
matched_policy = matched_policies[0]
# Get the current ports defined in the troposphere policies config and append
# the listers ports
updated_instance_ports = matched_policy.properties.get('InstancePorts', [])
updated_instance_ports.append("{}".format(listener['InstancePort']))
matched_policy.properties['InstancePorts'] = updated_instance_ports
updated_instance_ports = matched_policy.properties.get('LoadBalancerPorts', [])
updated_instance_ports.append("{}".format(listener['LoadBalancerPort']))
matched_policy.properties['LoadBalancerPorts'] = updated_instance_ports
"""

elb_list.append(load_balancer)

dns_record = RecordSetGroup(
Expand Down Expand Up @@ -1109,9 +1077,71 @@ def ec2(self):

return resources

def _get_ssl_certificate(self, template, certificate_name):
# Create a certificate if required, first try to get an ACM certificate,
# else look for a manual SSL entry.
if not certificate_name:
raise errors.CfnConfigError("Certificate name {} is invalid.".format(certificate_name))

acm_certificate = self._get_acm_certificate(certificate_name)
if acm_certificate:
logging.info("config::_get_ssl_certificate: Found ACM certificate.")
template.add_resource(acm_certificate)
return Ref(acm_certificate)

ssl_certificate = self._get_manual_ssl_certificate(certificate_name)
if ssl_certificate:
logging.info("config::_get_ssl_certificate: Found manual SSL certificate.")
return ssl_certificate

raise errors.CfnConfigError("Couldn't find ACM or manual SSL certificate configuration "
"{0} in config file".format(certificate_name))

def _get_acm_certificate(self, certificate_name):
acm_data = self.data.get('acm', {}).get(certificate_name, None)
if not acm_data:
logging.error("config::_get_acm_certificate: Could not find ACM configuration for {}"
.format(certificate_name))
return None
logging.info("config::_get_acm_certificate: Creating certificate {} for domain {}"
.format(certificate_name, acm_data.get('domain')))
certificate = Certificate(
certificate_name,
DomainName=acm_data.get('domain'),
DomainValidationOptions=[
DomainValidationOption(
DomainName=acm_data.get('domain'),
ValidationDomain=acm_data.get('validation_domain', 'dsd.io'),
),
],
Tags=[{'Key': key, 'Value': value} for key, value in acm_data.get('tags', {}).iteritems()]
)
return certificate

def _get_manual_ssl_certificate(self, certificate_name):
if self.ssl().get(certificate_name, {}).get('cert', None) is None:
logging.error("config::_get_manual_ssl_certificate: No cert information found for {}"
.format(certificate_name))
return None
if self.ssl().get(certificate_name, {}).get('key', None) is None:
logging.error("config::_get_manual_ssl_certificate: No key information found for {}"
.format(certificate_name))
return None

certificate = Join(
"",
[
"arn:aws:iam::",
Ref("AWS::AccountId"),
":server-certificate/",
"{0}-{1}".format(certificate_name, self.stack_name)
]
)
return certificate

@classmethod
def _find_resources(cls, template, resource_type):
f = lambda x: x.resource_type == resource_type
def f(x): return (x.resource_type == resource_type)
return filter(f, template.resources.values())

@classmethod
Expand Down Expand Up @@ -1153,64 +1183,64 @@ def _get_os_data(self):
OSTypeNotFoundError: Raised when the OS in the config file is not
recognised
"""
region=env.aws_region
region = env.aws_region
os_default = 'ubuntu-1404'
if region == 'eu-west-2':
available_types = {
'ubuntu-1604': {
'name': 'ubuntu-1604',
'ami': 'ami-57eae033',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20161214'
},
'ubuntu-1404': {
'name': 'ubuntu-1404',
'ami': 'ami-45eae021',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20161213'
},
'windows2012': {
'name': 'windows2012',
'ami': 'ami-bb353fdf',
'region': region,
'distribution': 'windows',
'type': 'windows',
'release': '2016.11.23'
}
}
available_types = {
'ubuntu-1604': {
'name': 'ubuntu-1604',
'ami': 'ami-57eae033',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20161214'
},
'ubuntu-1404': {
'name': 'ubuntu-1404',
'ami': 'ami-45eae021',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20161213'
},
'windows2012': {
'name': 'windows2012',
'ami': 'ami-bb353fdf',
'region': region,
'distribution': 'windows',
'type': 'windows',
'release': '2016.11.23'
}
}
elif env.aws_region == 'eu-west-1':
available_types = {
'ubuntu-1604': {
'name': 'ubuntu-1604',
'ami': 'ami-6f587e1c',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20161214'
},
'ubuntu-1404': {
'name': 'ubuntu-1404',
'ami': 'ami-f95ef58a',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20160509.1'
},
'windows2012': {
'name': 'windows2012',
'ami': 'ami-8519a9f6',
'region': region,
'distribution': 'windows',
'type': 'windows',
'release': '2015.12.31'
}
}
available_types = {
'ubuntu-1604': {
'name': 'ubuntu-1604',
'ami': 'ami-6f587e1c',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20161214'
},
'ubuntu-1404': {
'name': 'ubuntu-1404',
'ami': 'ami-f95ef58a',
'region': region,
'distribution': 'ubuntu',
'type': 'linux',
'release': '20160509.1'
},
'windows2012': {
'name': 'windows2012',
'ami': 'ami-8519a9f6',
'region': region,
'distribution': 'windows',
'type': 'windows',
'release': '2015.12.31'
}
}
else:
raise errors.CfnConfigError('Region {} is not supported'.format(region))
raise errors.CfnConfigError('Region {} is not supported'.format(region))

os_choice = self.data['ec2'].get('os', os_default)
if not available_types.get(os_choice, False):
Expand Down

0 comments on commit 6a0f4bd

Please sign in to comment.