From 76cdc695482ac2ace7e0d733e4ee89a1c837993c Mon Sep 17 00:00:00 2001 From: Andy Boutte Date: Thu, 20 Apr 2017 15:04:57 -0700 Subject: [PATCH] CF tested and working for EC2 deployment (#431) * AWS CloudFormation #132 * IPv6 EC2 draft * CF tested and working for EC2 deployment * IPv6 Implementation, EC2, Cloudformation * Fixed ipv6 networking * adding ip6tables rule for DHCP on AWS --- roles/cloud-ec2/tasks/cloudformation.yml | 18 +++ roles/cloud-ec2/tasks/encrypt_image.yml | 8 +- roles/cloud-ec2/tasks/main.yml | 96 +----------- roles/cloud-ec2/templates/stack.yml.j2 | 192 +++++++++++++++++++++++ roles/vpn/templates/rules.v6.j2 | 2 + 5 files changed, 221 insertions(+), 95 deletions(-) create mode 100644 roles/cloud-ec2/tasks/cloudformation.yml create mode 100644 roles/cloud-ec2/templates/stack.yml.j2 diff --git a/roles/cloud-ec2/tasks/cloudformation.yml b/roles/cloud-ec2/tasks/cloudformation.yml new file mode 100644 index 0000000..1f24b00 --- /dev/null +++ b/roles/cloud-ec2/tasks/cloudformation.yml @@ -0,0 +1,18 @@ +--- + +- name: Make a cloudformation template + template: + src: stack.yml.j2 + dest: "configs/{{ aws_server_name }}.yml" + +- name: Deploy the template + cloudformation: + aws_access_key: "{{ aws_access_key | default(lookup('env','AWS_ACCESS_KEY_ID'), true)}}" + aws_secret_key: "{{ aws_secret_key | default(lookup('env','AWS_SECRET_ACCESS_KEY'), true)}}" + stack_name: "{{ stack_name }}" + state: "present" + region: "{{ region }}" + template: "configs/{{ aws_server_name }}.yml" + tags: + Environment: Algo + register: stack \ No newline at end of file diff --git a/roles/cloud-ec2/tasks/encrypt_image.yml b/roles/cloud-ec2/tasks/encrypt_image.yml index da46534..11779ea 100644 --- a/roles/cloud-ec2/tasks/encrypt_image.yml +++ b/roles/cloud-ec2/tasks/encrypt_image.yml @@ -1,7 +1,7 @@ - name: Check if the encrypted image already exist ec2_ami_find: - aws_access_key: "{{ aws_access_key | default(lookup('env','AWS_ACCESS_KEY_ID'))}}" - aws_secret_key: "{{ aws_secret_key | default(lookup('env','AWS_SECRET_ACCESS_KEY'))}}" + aws_access_key: "{{ aws_access_key | default(lookup('env','AWS_ACCESS_KEY_ID'), true)}}" + aws_secret_key: "{{ aws_secret_key | default(lookup('env','AWS_SECRET_ACCESS_KEY'), true)}}" owner: self sort: creationDate sort_order: descending @@ -18,8 +18,8 @@ - name: Copy to an encrypted image ec2_ami_copy: - aws_access_key: "{{ aws_access_key | default(lookup('env','AWS_ACCESS_KEY_ID'))}}" - aws_secret_key: "{{ aws_secret_key | default(lookup('env','AWS_SECRET_ACCESS_KEY'))}}" + aws_access_key: "{{ aws_access_key | default(lookup('env','AWS_ACCESS_KEY_ID'), true)}}" + aws_secret_key: "{{ aws_secret_key | default(lookup('env','AWS_SECRET_ACCESS_KEY'), true)}}" encrypted: yes name: algo kms_key_id: "{{ kms_key_id | default(omit) }}" diff --git a/roles/cloud-ec2/tasks/main.yml b/roles/cloud-ec2/tasks/main.yml index dfb3b1f..46d71bb 100644 --- a/roles/cloud-ec2/tasks/main.yml +++ b/roles/cloud-ec2/tasks/main.yml @@ -1,6 +1,7 @@ - set_fact: access_key: "{{ aws_access_key | default(lookup('env','AWS_ACCESS_KEY_ID'), true) }}" secret_key: "{{ aws_secret_key | default(lookup('env','AWS_SECRET_ACCESS_KEY'), true) }}" + stack_name: "{{ aws_server_name | replace('.', '-') }}" - name: Locate official Ubuntu 16.04 AMI for region ec2_ami_find: @@ -20,107 +21,20 @@ - include: encrypt_image.yml tags: [encrypted] -- name: Add ssh public key - ec2_key: - aws_access_key: "{{ access_key }}" - aws_secret_key: "{{ secret_key }}" - name: VPNKEY - region: "{{ region }}" - key_material: "{{ item }}" - with_file: "{{ SSH_keys.public }}" - register: keypair - -- name: Configure EC2 virtual private clouds - ec2_vpc: - aws_access_key: "{{ access_key }}" - aws_secret_key: "{{ secret_key }}" - state: present - resource_tags: { "Environment":"Algo" } - region: "{{ region }}" - cidr_block: "{{ ec2_vpc_nets.cidr_block }}" - internet_gateway: yes - subnets: - - cidr: "{{ ec2_vpc_nets.subnet_cidr }}" - resource_tags: { "Environment":"Algo" } - register: vpc - -- name: Set up Public Subnets Route Table - ec2_vpc_route_table: - aws_access_key: "{{ access_key }}" - aws_secret_key: "{{ secret_key }}" - vpc_id: "{{ vpc.vpc_id }}" - region: "{{ region }}" - state: present - tags: - Environment: Algo - subnets: - - "{{ ec2_vpc_nets.subnet_cidr }}" - routes: - - dest: 0.0.0.0/0 - gateway_id: "{{ vpc.igw_id }}" - register: public_rt - -- name: Configure EC2 security group - ec2_group: - aws_access_key: "{{ access_key }}" - aws_secret_key: "{{ secret_key }}" - name: vpn-secgroup - description: Security group for VPN servers - region: "{{ region }}" - vpc_id: "{{ vpc.vpc_id }}" - rules: - - proto: udp - from_port: 4500 - to_port: 4500 - cidr_ip: 0.0.0.0/0 - - proto: udp - from_port: 500 - to_port: 500 - cidr_ip: 0.0.0.0/0 - - proto: tcp - from_port: 22 - to_port: 22 - cidr_ip: 0.0.0.0/0 - rules_egress: - - proto: all - from_port: 0-65535 - to_port: 0-65535 - cidr_ip: 0.0.0.0/0 - -- name: Launch instance - ec2: - aws_access_key: "{{ access_key }}" - aws_secret_key: "{{ secret_key }}" - keypair: "VPNKEY" - vpc_subnet_id: "{{ vpc.subnets[0].id }}" - group: vpn-secgroup - instance_type: "{{ cloud_providers.ec2.size }}" - image: "{{ ami_image }}" - wait: true - region: "{{ region }}" - instance_tags: - Name: "{{ aws_server_name }}" - Environment: Algo - exact_count: 1 - count_tag: - Name: "{{ aws_server_name }}" - assign_public_ip: yes - instance_initiated_shutdown_behavior: terminate - register: ec2 +- include: cloudformation.yml - name: Add new instance to host group add_host: - hostname: "{{ item.public_ip }}" + hostname: "{{ stack.stack_outputs.PublicIP }}" groupname: vpn-host ansible_ssh_user: ubuntu ansible_python_interpreter: "/usr/bin/python2.7" ansible_ssh_private_key_file: "{{ SSH_keys.private }}" cloud_provider: ec2 - ipv6_support: no - with_items: "{{ ec2.tagged_instances }}" + ipv6_support: yes - set_fact: - cloud_instance_ip: "{{ ec2.tagged_instances[0].public_ip }}" + cloud_instance_ip: "{{ stack.stack_outputs.PublicIP }}" - name: Get EC2 instances ec2_remote_facts: diff --git a/roles/cloud-ec2/templates/stack.yml.j2 b/roles/cloud-ec2/templates/stack.yml.j2 new file mode 100644 index 0000000..1678413 --- /dev/null +++ b/roles/cloud-ec2/templates/stack.yml.j2 @@ -0,0 +1,192 @@ +--- + +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Algo VPN stack' +Resources: + + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: {{ ec2_vpc_nets.cidr_block }} + EnableDnsSupport: true + EnableDnsHostnames: true + InstanceTenancy: default + Tags: + - Key: Name + Value: Algo + - Key: Environment + Value: Algo + + VPCIPv6: + Type: AWS::EC2::VPCCidrBlock + Properties: + AmazonProvidedIpv6CidrBlock: true + VpcId: !Ref VPC + + InternetGateway: + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Environment + Value: Algo + - Key: Name + Value: Algo + + Subnet: + Type: AWS::EC2::Subnet + Properties: + CidrBlock: {{ ec2_vpc_nets.subnet_cidr }} + MapPublicIpOnLaunch: true + Tags: + - Key: Environment + Value: Algo + - Key: Name + Value: Algo + VpcId: !Ref VPC + + VPCGatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + + RouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Environment + Value: Algo + - Key: Name + Value: Algo + + Route: + Type: AWS::EC2::Route + DependsOn: + - InternetGateway + - RouteTable + - VPCGatewayAttachment + Properties: + RouteTableId: !Ref RouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + RouteIPv6: + Type: AWS::EC2::Route + DependsOn: + - InternetGateway + - RouteTable + - VPCGatewayAttachment + Properties: + RouteTableId: !Ref RouteTable + DestinationIpv6CidrBlock: "::/0" + GatewayId: !Ref InternetGateway + + SubnetIPv6: + Type: AWS::EC2::SubnetCidrBlock + DependsOn: + - RouteIPv6 + - VPC + - VPCIPv6 + Properties: + Ipv6CidrBlock: + "Fn::Join": + - "" + - - !Select [0, !Split [ "::", !Select [0, !GetAtt VPC.Ipv6CidrBlocks] ]] + - "::dead:beef/64" + SubnetId: !Ref Subnet + + RouteSubnet: + Type: "AWS::EC2::SubnetRouteTableAssociation" + DependsOn: + - RouteTable + - Subnet + - Route + Properties: + RouteTableId: !Ref RouteTable + SubnetId: !Ref Subnet + + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + DependsOn: + - Subnet + Properties: + VpcId: !Ref VPC + GroupDescription: Enable SSH and IPsec + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '22' + ToPort: '22' + CidrIp: 0.0.0.0/0 + - IpProtocol: udp + FromPort: '500' + ToPort: '500' + CidrIp: 0.0.0.0/0 + - IpProtocol: udp + FromPort: '4500' + ToPort: '4500' + CidrIp: 0.0.0.0/0 + Tags: + - Key: Name + Value: Algo + - Key: Environment + Value: Algo + + EC2Instance: + Type: AWS::EC2::Instance + DependsOn: + - SubnetIPv6 + - Subnet + - InstanceSecurityGroup + Metadata: + AWS::CloudFormation::Init: + config: + users: + ubuntu: + groups: + - "sudo" + homeDir: "/home/ubuntu/" + files: + /home/ubuntu/.ssh/authorized_keys: + content: {{ lookup('file', SSH_keys.public) }} + mode: "000644" + owner: "ubuntu" + group: "ubuntu" + Properties: + InstanceType: {{ cloud_providers.ec2.size }} + InstanceInitiatedShutdownBehavior: terminate + SecurityGroupIds: + - Ref: InstanceSecurityGroup + ImageId: {{ ami_image }} + SubnetId: !Ref Subnet + Ipv6AddressCount: 1 + UserData: + "Fn::Base64": + !Sub | + #!/bin/bash -xe + # http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-migrate-ipv6.html + # https://bugs.launchpad.net/ubuntu/+source/ifupdown/+bug/1013597 + cat < /etc/network/interfaces.d/60-default-with-ipv6.cfg + iface eth0 inet6 dhcp + up sysctl net.ipv6.conf.\$IFACE.accept_ra=2 + pre-down ip link set dev \$IFACE up + EOF + ifdown eth0; ifup eth0 + dhclient -6 + apt-get update + apt-get -y install python-setuptools + easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz + cfn-init -v --stack {{ stack_name }} --resource EC2Instance --region {{ region }} + cfn-signal -e $? --stack {{ stack_name }} --resource EC2Instance --region {{ region }} + Tags: + - Key: Name + Value: Algo + - Key: Environment + Value: Algo + +Outputs: + PublicIP: + Value: + Fn::GetAtt: + - EC2Instance + - PublicIp diff --git a/roles/vpn/templates/rules.v6.j2 b/roles/vpn/templates/rules.v6.j2 index 0eda48f..f8d9593 100644 --- a/roles/vpn/templates/rules.v6.j2 +++ b/roles/vpn/templates/rules.v6.j2 @@ -31,6 +31,8 @@ COMMIT -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT -A INPUT -p icmpv6 --icmpv6-type redirect -m hl --hl-eq 255 -j ACCEPT +# DHCP in AWS +-A INPUT -m state --state NEW -m udp -p udp --dport 546 -d fe80::/64 -j ACCEPT # TODO: # The IP of the resolver should be bound to a DUMMY interface. # DUMMY interfaces are the proper way to install IPs without assigning them any