2017-11-14
So when we started with the ‘wire up your corporate network’ to aws we were introduced to the transitive vpc.
The automagic aws solution includes CSR’s and lambda goodness, much of which was a little too much to grasp and pay for at the time.
Being a little (too) cost averse and still learning the ropes, we started building a strongswan+quagga solution. I’ll document that one at a later stage.
The one we opted for though, was a Fortigate (we are a Fortigate shop) based solution that ties in with the cloudformation bits we do for the vpcs.
Refering back to the same picture (draw.io use it!) in the multi account post.
We will focus on the cgw, vpc and Fortigate config in the bits below.
So in an attempt to take the complexity out of vpc creations we ended up with something like this
---
acls:
- { name: Inbound, rule: 100, outgoing: false, cidr: 0.0.0.0/0 }
- { name: Outbound, rule: 100, outgoing: true, cidr: 0.0.0.0/0 }
pub_sec_group:
name: PublicSecurityGroup
incoming:
- { port: 22, cidr: 1.1.1.1/32 }
- { port: 80, cidr: 0.0.0.0/0 }
- { port: 443, cidr: 0.0.0.0/0 }
outgoing:
- { port: 80, cidr: 0.0.0.0/0 }
- { port: 443, cidr: 0.0.0.0/0 }
prv_sec_group:
name: PrivateSecurityGroup
incoming:
- { port: 80, sec_group: PublicSecurityGroup } #Will be prefixed by {{vpc.name}}
- { port: 443, sec_group: PublicSecurityGroup }
- { port: -1, cidr: 10.0.0.0/8, protocol: -1 }
outgoing: []
vpcs:
aws_foo_sandbox:
foosnd:
name: "{{stack_name}}"
cidr: 10.10.1.0/24
igw: 1
subnets:
- { name: PublicSNa, az: eu-west-1a, cidr: 10.10.1.0/28, route_table: RtPub, natgw: NatGW1 }
- { name: PublicSNb, az: eu-west-1b, cidr: 10.10.1.16/28, route_table: RtPub }
- { name: PrivateSNa, az: eu-west-1a, cidr: 10.10.1.32/27, route_table: RtPriv }
- { name: PrivateSNb, az: eu-west-1b, cidr: 10.10.1.64/27, route_table: RtPriv }
acls: "{{acls}}"
pub_sec_group: "{{pub_sec_group}}"
prv_sec_group: "{{prv_sec_group}}"
route_tables:
- name: RtPub
routes:
- { name: igw, cidr: 0.0.0.0/0, igw: 1 }
- name: RtPriv
routes:
- { name: NatGW1, cidr: 0.0.0.0/0, natgw: NatGW1 }
vpnconnection: 1
vpn:
name: "{{stack_name}}TransitVPN"
gateways:
- name: "FortigateFW1"
- name: "FortigateFW2"
instances: []
aws_foo_nonprod:
foodev:
name: "{{stack_name}}"
cidr: 10.10.2.0/24
igw: 1
subnets:
- { name: PublicSNa, az: eu-west-1a, cidr: 10.10.2.0/28, route_table: RtPub, natgw: NatGW1 }
- { name: PublicSNb, az: eu-west-1b, cidr: 10.10.2.16/28, route_table: RtPub }
- { name: PrivateSNa, az: eu-west-1a, cidr: 10.10.2.32/27, route_table: RtPriv }
- { name: PrivateSNb, az: eu-west-1b, cidr: 10.10.2.64/27, route_table: RtPriv }
acls: "{{acls}}"
pub_sec_group: "{{pub_sec_group}}"
prv_sec_group: "{{prv_sec_group}}"
route_tables:
- name: RtPub
routes:
- { name: igw, cidr: 0.0.0.0/0, igw: 1 }
- name: RtPriv
routes:
- { name: NatGW1, cidr: 0.0.0.0/0, natgw: NatGW1 }
vpnconnection: 1
vpn:
name: "{{stack_name}}TransitVPN"
gateways:
- name: "FortigateFW1"
- name: "FortigateFW2"
instances: []
footst:
name: "{{stack_name}}"
cidr: 10.10.3.0/24
igw: 1
subnets:
- { name: PublicSNa, az: eu-west-1a, cidr: 10.10.3.0/28, route_table: RtPub, natgw: NatGW1 }
- { name: PublicSNb, az: eu-west-1b, cidr: 10.10.3.16/28, route_table: RtPub }
- { name: PrivateSNa, az: eu-west-1a, cidr: 10.10.3.32/27, route_table: RtPriv }
- { name: PrivateSNb, az: eu-west-1b, cidr: 10.10.3.64/27, route_table: RtPriv }
acls: "{{acls}}"
pub_sec_group: "{{pub_sec_group}}"
prv_sec_group: "{{prv_sec_group}}"
route_tables:
- name: RtPub
routes:
- { name: igw, cidr: 0.0.0.0/0, igw: 1 }
- name: RtPriv
routes:
- { name: NatGW1, cidr: 0.0.0.0/0, natgw: NatGW1 }
vpnconnection: 1
vpn:
name: "{{stack_name}}TransitVPN"
gateways:
- name: "FortigateFW1"
- name: "FortigateFW2"
instances: []
In this case depicting a sandbox and nonprod account, the latter with 2 vpc’s.
The vpcnames are short (6 chars) for a reason - Fortigate has some limitations on its config names, so the shorter the better.
Also since we don’t use vdoms in our Fortigate config, the names must be globally (across all accounts) unique.
Since the cgw is needed only once in every account we created a cgw_cf role for the cloudformation wrapper
---
- hosts: localhost
connection: local
gather_facts: False
vars:
stack_name: cgw
gateways:
- name: FortigateFW1
ip: "1.1.1.1"
asn: 64111
- name: FortigateFW2
ip: "2.2.2.2"
asn: 64111
region: eu-west-1
roles:
- { name: sts_assume_role, target_account: "{{aws_accounts[acc]}}" }
- cgw_cf
The template in the cgw_cf role looks like this
---
AWSTemplateFormatVersion: "2010-09-09"
Description: "AWS CGW template {{stack_name}}"
Resources:
{% for gw in gateways %}
{{gw.name}}CustomerGateway:
Type: "AWS::EC2::CustomerGateway"
Properties:
Type: ipsec.1
BgpAsn: {{gw.asn}}
IpAddress: {{gw.ip}}
Tags:
- { Key: Name, Value: {{gw.name}}CustomerGateway }
{% endfor %}
Outputs:
StackName:
Value: !Ref AWS::StackName
Regionname:
Value: !Ref AWS::Region
{% for gw in gateways %}
{{gw.name}}CustomerGateway:
Value: !Ref {{gw.name}}CustomerGateway
Export:
Name: {{gw.name}}
{% endfor %}
this creates cloudformation Exports that will be used to import the CGW id’s in the VPN setup.
Passing the stack_name
and acc
variables into this playbook will create the vpc and configure the tunnels inside the fortigate firewalls.
The stack_name represents the vpc variable in the group_vars.
---
- hosts: localhost
connection: local
gather_facts: False
vars:
vpc: "{{vpcs[acc][stack_name]}}"
region: eu-west-1
roles:
- name: sts_assume_role
target_account: "{{aws_accounts[acc]}}"
- name: vpc_cf
- name: get_aws_vpnconfig
vpn1: "{{output.stack_outputs[stack_name+'FortigateFW1VPNConnection']}}"
vpn2: "{{output.stack_outputs[stack_name+'FortigateFW2VPNConnection']}}"
- name: fortigate
fwName: Transit-Hub-FW1
config: "{{awsvpn1}}"
ssh_key: "~/pems/Transit.pem"
ssh_uid: "admin"
ssh_host: "1.1.1.1"
localip: 10.10.0.10
hubName: HUB-to-DC1
- name: fortigate
fwName: Transit-Hub-FW2
config: "{{awsvpn2}}"
ssh_key: "~/pems/Transit.pem"
ssh_uid: "admin"
ssh_host: "2.2.2.2"
localip: 10.10.0.100
setmetrix: yes
hubName: HUB-to-DC2
here is the jinja template contents:
---
AWSTemplateFormatVersion: "2010-09-09"
Description: "AWS VPC template {{vpc.name}}"
Resources:
{{vpc.name}}VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: {{vpc.cidr}}
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}VPC }
{% if vpc.igw is defined %}
{{vpc.name}}InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}InternetGateway }
{{vpc.name}}AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref {{vpc.name}}VPC
InternetGatewayId: !Ref {{vpc.name}}InternetGateway
{% endif %}
{% for rt in vpc.route_tables %}
{{vpc.name}}{{rt.name}}RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref {{vpc.name}}VPC
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}{{rt.name}}RouteTable }
{% endfor %}
{{vpc.name}}NetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref {{vpc.name}}VPC
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}NetworkAcl }
{% for subnet in vpc.subnets %}
{{vpc.name}}{{subnet.name}}Subnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: {{subnet.az}}
VpcId: !Ref {{vpc.name}}VPC
CidrBlock: {{subnet.cidr}}
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}{{subnet.name}} }
{{vpc.name}}{{subnet.name}}SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref {{vpc.name}}{{subnet.name}}Subnet
RouteTableId: !Ref {{vpc.name}}{{subnet.route_table}}RouteTable
{{vpc.name}}{{subnet.name}}SubnetNetworkAclAssociation:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId: !Ref {{vpc.name}}{{subnet.name}}Subnet
NetworkAclId: !Ref {{vpc.name}}NetworkAcl
{% if subnet.natgw is defined %}
{{vpc.name}}{{subnet.natgw}}IPAddress:
Type: AWS::EC2::EIP
DependsOn: {{vpc.name}}AttachGateway
Properties:
Domain: vpc
{{vpc.name}}{{subnet.natgw}}NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt {{vpc.name}}{{subnet.natgw}}IPAddress.AllocationId
SubnetId: !Ref {{vpc.name}}{{subnet.name}}Subnet
{% endif %}
{% endfor %}
{% for acl in vpc.acls %}
{{vpc.name}}{{acl.name}}NetworkAclEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref {{vpc.name}}NetworkAcl
RuleNumber: {{acl.rule}}
RuleAction: allow
Egress: {{acl.outgoing}}
CidrBlock: {{acl.cidr}}
{% if acl.from is defined %}
Protocol: {{acl.protocol|default('6')}}
{% if acl.protocol is defined and acl.protocol == 1 %}
Icmp:
Code: -1
Type: -1
{% endif %}
PortRange:
From: {{acl.from}}
To: {{acl.to}}
{% else %}
Protocol: -1
{% endif %}
{% endfor %}
{{vpc.name}}PublicSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref {{vpc.name}}VPC
GroupDescription: Enable access via ports
SecurityGroupIngress:
{% for incoming in vpc.pub_sec_group.incoming %}
- IpProtocol: {{incoming.protocol|default('tcp')}}
FromPort: {{incoming.port}}
ToPort: {{incoming.port}}
CidrIp: {{incoming.cidr}}
{% endfor %}
SecurityGroupEgress:
{% for outgoing in vpc.pub_sec_group.outgoing %}
- IpProtocol: {{outgoing.protocol|default('tcp')}}
FromPort: {{outgoing.port}}
ToPort: {{outgoing.port}}
{% if outgoing.sec_group is defined %}
SourceSecurityGroupId: !Ref {{vpc.name}}{{outgoing.sec_group}}
{% else %}
CidrIp: {{outgoing.cidr}}
{% endif %}
{% endfor %}
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}PublicSecurityGroup }
{{vpc.name}}PrivateSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref {{vpc.name}}VPC
GroupDescription: Enable access via ports
SecurityGroupIngress:
{% for incoming in vpc.prv_sec_group.incoming %}
- IpProtocol: {{incoming.protocol|default('tcp')}}
FromPort: {{incoming.port}}
ToPort: {{incoming.port}}
{% if incoming.sec_group is defined %}
SourceSecurityGroupId: !Ref {{vpc.name}}{{incoming.sec_group}}
{% else %}
CidrIp: {{incoming.cidr}}
{% endif %}
{% endfor %}
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}PrivateSecurityGroup }
{% for rt in vpc.route_tables %}
{% for route in rt.routes %}
{{vpc.name}}{{rt.name}}{{route.name}}Route:
Type: AWS::EC2::Route
{% if route.vpngateway is defined %}
DependsOn: [{{vpc.vpn.name}}VPNGateway, {{vpc.vpn.name}}VPCGatewayAttachment]
{% endif %}
Properties:
RouteTableId: !Ref {{vpc.name}}{{rt.name}}RouteTable
DestinationCidrBlock: {{route.cidr}}
{% if route.instance is defined %}
InstanceId: !Ref {{vpc.name}}{{route.instance}}EC2Instance
{% elif route.natgw is defined %}
NatGatewayId: !Ref {{vpc.name}}{{route.natgw}}NatGateway
{% elif route.igw is defined %}
GatewayId: !Ref {{vpc.name}}InternetGateway
{% elif route.vpngateway is defined %}
GatewayId: !Ref {{vpc.vpn.name}}VPNGateway
{% endif %}
{% endfor %}
{% if rt.vpnconnection is defined %}
{% for gw in vpc.vpn.gateways %}
{{vpc.name}}{{gw.name}}VPNGatewayRoutePropagation:
Type: "AWS::EC2::VPNGatewayRoutePropagation"
DependsOn: {{vpc.name}}{{gw.name}}VPNConnection
Properties:
RouteTableIds: [!Ref {{vpc.name}}{{rt.name}}RouteTable]
VpnGatewayId: !Ref {{vpc.vpn.name}}VPNGateway
{% endfor %}
{% endif %}
{% endfor %}
{% for instance in vpc.instances %}
{{vpc.name}}{{instance.name}}EC2Instance:
Type: AWS::EC2::Instance
DependsOn: {{vpc.name}}AttachGateway
Properties:
ImageId: {{instance.imageid|default(ec2_imageid)}}
InstanceType: {{instance.instancetype|default(ec2_type)}}
KeyName: {{ec2_keyname}}
SourceDestCheck: {{instance.sourcedestcheck|default('true')}}
Tags:
- { Key: Application, Value: !Ref "AWS::StackId" }
- { Key: Name, Value: {{vpc.name}}{{instance.name}}EC2Instance }
NetworkInterfaces:
- GroupSet: [ !Ref {{vpc.name}}SecurityGroup ]
DeviceIndex: 0
DeleteOnTermination: true
SubnetId: !Ref {{vpc.name}}{{instance.subnet}}Subnet
{{vpc.name}}{{instance.name}}IPAddress:
Type: AWS::EC2::EIP
DependsOn: {{vpc.name}}AttachGateway
Properties:
Domain: vpc
InstanceId: !Ref {{vpc.name}}{{instance.name}}EC2Instance
{% endfor %}
{% if vpc.vpn is defined %}
{{vpc.vpn.name}}VPNGateway:
Type: "AWS::EC2::VPNGateway"
DependsOn: {{vpc.name}}VPC
Properties:
Type: ipsec.1
Tags:
- { Key: Name, Value: {{vpc.vpn.name}}VPNGateway }
{{vpc.vpn.name}}VPCGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
DependsOn: {{vpc.name}}VPC
Properties:
VpcId: !Ref {{vpc.name}}VPC
VpnGatewayId: !Ref {{vpc.vpn.name}}VPNGateway
{% for gw in vpc.vpn.gateways %}
{{vpc.name}}{{gw.name}}VPNConnection:
Type: "AWS::EC2::VPNConnection"
Properties:
Type: ipsec.1
CustomerGatewayId: !ImportValue {{gw.name}}
StaticRoutesOnly: false
VpnGatewayId: !Ref {{vpc.vpn.name}}VPNGateway
Tags:
- { Key: Name, Value: {{vpc.name}}{{gw.name}}VPNConnection }
{% endfor %}
{% endif %}
{{vpc.name}}FlowLog:
Type: "AWS::EC2::FlowLog"
Properties:
DeliverLogsPermissionArn : arn:aws:iam::{{aws_accounts[acc]}}:role/PublishFlowLogs
LogGroupName : vpc-flow-logs
ResourceId : !Ref {{vpc.name}}VPC
ResourceType : VPC
TrafficType : ALL
Outputs:
StackName:
Value: !Ref AWS::StackName
Regionname:
Value: !Ref AWS::Region
{{vpc.name}}VPC:
Value: !Ref {{vpc.name}}VPC
Export:
Name: {{vpc.name}}VPC
{{vpc.name}}PublicSecurityGroup:
Value: !Ref {{vpc.name}}PublicSecurityGroup
Export:
Name: {{vpc.name}}PublicSecurityGroup
{{vpc.name}}PrivateSecurityGroup:
Value: !Ref {{vpc.name}}PrivateSecurityGroup
Export:
Name: {{vpc.name}}PrivateSecurityGroup
{% for subnet in vpc.subnets %}
{{vpc.name}}{{subnet.name}}Subnet:
Value: !Ref {{vpc.name}}{{subnet.name}}Subnet
Export:
Name: {{vpc.name}}{{subnet.name}}Subnet
{% endfor %}
{% for instance in vpc.instances %}
{{vpc.name}}{{instance.name}}Private:
Value: !GetAtt {{vpc.name}}{{instance.name}}EC2Instance.PrivateIp
{{vpc.name}}{{instance.name}}Public:
Value: !GetAtt {{vpc.name}}{{instance.name}}EC2Instance.PublicIp
{% endfor %}
{% if vpc.vpn is defined %}
{% for gw in vpc.vpn.gateways %}
{{vpc.name}}{{gw.name}}VPNConnection:
Value: !Ref {{vpc.name}}{{gw.name}}VPNConnection
{% endfor %}
{% endif %}
the vpc.instances list would contain instance info (like when we deployed quagga and strongswan), but isn’t important here.
Whoa, so this is probably where rolling your own module to extract the info would have been wize, but since I am half lazy, half inspired to see how much I can squeeze into 1 command line…
---
- block:
- name: get the ip password combo for "{{vpn1}}"
shell: >
AWS_ACCESS_KEY_ID="{{ assumed_role.sts_creds.access_key | default(omit) }}"
AWS_SECRET_ACCESS_KEY="{{ assumed_role.sts_creds.secret_key | default(omit) }}"
AWS_SESSION_TOKEN="{{ assumed_role.sts_creds.session_token | default(omit) }}"
aws ec2 describe-vpn-connections
| jq '.VpnConnections[] | select(.VpnConnectionId=="{{vpn1}}").CustomerGatewayConfiguration' -r
| xmlstarlet sel -t -m '/vpn_connection/ipsec_tunnel'
-v 'concat("cgw", position(), "_outside: ", ./customer_gateway/tunnel_outside_address/ip_address/text(), "!!!")'
-v 'concat("cgw", position(), "_inside: ", ./customer_gateway/tunnel_inside_address/ip_address/text(), "!!!")'
-v 'concat("cgw", position(), "_asn: ", ./customer_gateway/bgp/asn/text(), "!!!")'
-v 'concat("vgw", position(), "_outside: ", ./vpn_gateway/tunnel_outside_address/ip_address/text(), "!!!")'
-v 'concat("vgw", position(), "_inside: ", ./vpn_gateway/tunnel_inside_address/ip_address/text(), "!!!")'
-v 'concat("vgw", position(), "_asn: ", ./vpn_gateway/bgp/asn/text(), "!!!")'
-v 'concat("psk", position(), ": ", ./ike/pre_shared_key/text(),"!!!")'
register: vpnconfig
- set_fact:
awsvpn1: "{{('{ '+(vpnconfig.stdout_lines[0].split('!!!')|difference([''])|join(', '))+' }')|from_yaml}}"
- debug: var=awsvpn1
- name: get the ip password combo for "{{vpn2}}"
shell: >
AWS_ACCESS_KEY_ID="{{ assumed_role.sts_creds.access_key | default(omit) }}"
AWS_SECRET_ACCESS_KEY="{{ assumed_role.sts_creds.secret_key | default(omit) }}"
AWS_SESSION_TOKEN="{{ assumed_role.sts_creds.session_token | default(omit) }}"
aws ec2 describe-vpn-connections
| jq '.VpnConnections[] | select(.VpnConnectionId=="{{vpn2}}").CustomerGatewayConfiguration' -r
| xmlstarlet sel -t -m '/vpn_connection/ipsec_tunnel'
-v 'concat("cgw", position(), "_outside: ", ./customer_gateway/tunnel_outside_address/ip_address/text(), "!!!")'
-v 'concat("cgw", position(), "_inside: ", ./customer_gateway/tunnel_inside_address/ip_address/text(), "!!!")'
-v 'concat("cgw", position(), "_asn: ", ./customer_gateway/bgp/asn/text(), "!!!")'
-v 'concat("vgw", position(), "_outside: ", ./vpn_gateway/tunnel_outside_address/ip_address/text(), "!!!")'
-v 'concat("vgw", position(), "_inside: ", ./vpn_gateway/tunnel_inside_address/ip_address/text(), "!!!")'
-v 'concat("vgw", position(), "_asn: ", ./vpn_gateway/bgp/asn/text(), "!!!")'
-v 'concat("psk", position(), ": ", ./ike/pre_shared_key/text(),"!!!")'
register: vpnconfig
- set_fact:
awsvpn2: "{{('{ '+(vpnconfig.stdout_lines[0].split('!!!')|difference([''])|join(', '))+' }')|from_yaml}}"
- debug: var=awsvpn2
when: not absent is defined
This uses jq to find the relevant output from aws ec2 describe-vpn-connections
and then xmlstarlet to extract the aws inside,outside ips,asn,and psk for both tunnels.
Once that is done… it creates the 2 variables we will use in the fortigate config in the next role
---
- name: remove the fortigate config
block:
- name: make the policy delete script
shell: >
echo 'config firewall policy' > "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
ssh -i "{{ssh_key}}" "{{ssh_uid}}@{{ssh_host}}" show firewall policy
| grep "{{stack_name}}" -B 1
| grep 'edit '
| sed 's/.*edit /del /g' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo end >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo 'config vpn ipsec phase2-interface' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
ssh -i "{{ssh_key}}" "{{ssh_uid}}@{{ssh_host}}" show vpn ipsec phase2-interface
| grep "{{stack_name}}" -B 1
| grep 'edit '
| sed 's/.*edit /del /g' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo end >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo 'config vpn ipsec phase1-interface' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
ssh -i "{{ssh_key}}" "{{ssh_uid}}@{{ssh_host}}" show vpn ipsec phase1-interface
| grep "{{stack_name}}" -B 1
| grep 'edit '
| sed 's/.*edit /del /g' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo end >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo 'config router bgp' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo 'config neighbor ' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
ssh -i "{{ssh_key}}" "{{ssh_uid}}@{{ssh_host}}" show router bgp
| grep "{{stack_name}}" -B 2
| grep 'edit "'
| sed 's/.*edit /del /g' >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo end >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
echo end >> "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh";
- name: run the delete script
shell: cat "./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.del.ssh" | ssh -i "{{ssh_key}}" "{{ssh_uid}}@{{ssh_host}}"
register: delfortiout
- debug: var=delfortiout
when: absent is defined
- name: do fortigate config
block:
- name: get the tunnel list to see if it exists
shell: "ssh -i {{ssh_key}} {{ssh_uid}}@{{ssh_host}} get ipsec tunnel list | grep -c {{stack_name}}T1P1"
ignore_errors: true
register: tunlist
- block:
- name: stampout "{{fwName}}"
template: src=fortigate.ssh.j2 dest="./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.ssh"
- name: run the tunnel script
shell: "cat ./generated/vpc/{{stack_name}}.fortigate.{{fwName}}.ssh | ssh -i {{ssh_key}} {{ssh_uid}}@{{ssh_host}}"
register: fortiout
- debug: var=fortiout
when: tunlist.stdout_lines[0] == '0'
when: absent is not defined
The first fugly block is there to generate the delete scripts in case you are removing the vpc’s config from the Fortigate firewalls
The second block is the create, if it doesn’t exist, that makes an .ssh file and then cat it to the Fortigate firewall.
The template itself is basically a bunch of fortigate config statements provided by our resident Fortigate experts.
The Fortigates are pretty cool tools, you can put them in ‘debug’ mode and have them spit out a recording of what you do in its web interface. That can then be put into a jinja2 template and used like we do below.
config vpn ipsec phase1-interface
edit "{{stack_name}}T1P1"
set interface "port1"
set local-gw "{{localip}}"
set keylife 28800
set peertype any
set proposal aes128-sha1
set dhgrp 2
set remote-gw "{{config.vgw1_outside}}"
set psksecret "{{config.psk1}}"
set dpd-retryinterval 10
end
config vpn ipsec phase2-interface
edit "{{stack_name}}T1P2"
set phase1name "{{stack_name}}T1P1"
set proposal aes128-sha1
set dhgrp 2
set keylifeseconds 3600
end
config system interface
edit "{{stack_name}}T1P1"
set vdom "root"
set ip {{config.cgw1_inside}} 255.255.255.255
set allowaccess ping
set type tunnel
set tcp-mss 1379
set remote-ip {{config.vgw1_inside}}
set interface "port1"
end
config vpn ipsec phase1-interface
edit "{{stack_name}}T2P1"
set interface "port1"
set local-gw "{{localip}}"
set keylife 28800
set peertype any
set proposal aes128-sha1
set dhgrp 2
set remote-gw "{{config.vgw2_outside}}"
set psksecret "{{config.psk2}}"
set dpd-retryinterval 10
end
config vpn ipsec phase2-interface
edit "{{stack_name}}T2P2"
set phase1name "{{stack_name}}T2P1"
set proposal aes128-sha1
set dhgrp 2
set keylifeseconds 3600
end
config system interface
edit "{{stack_name}}T2P1"
set vdom "root"
set ip {{config.cgw2_inside}} 255.255.255.255
set allowaccess ping
set type tunnel
set tcp-mss 1379
set remote-ip {{config.vgw2_inside}}
set interface "port1"
end
config router bgp
config neighbor
edit "{{config.vgw1_inside}}"
set soft-reconfiguration enable
set description "{{stack_name}}T1"
set remote-as {{config.vgw1_asn}}
{% if setmetrix is defined %}
set route-map-out "Set-Metrix"
{% endif %}
next
edit "{{config.vgw2_inside}}"
set soft-reconfiguration enable
set description "{{stack_name}}T2"
set remote-as {{config.vgw2_asn}}
{% if setmetrix is defined %}
set route-map-out "Set-Metrix"
{% endif %}
end
end
config firewall policy
edit 0
set name "{{stack_name}}T1P1-f"
set srcintf "{{stack_name}}T1P1"
set dstintf "port1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "f-{{stack_name}}T1P1"
set srcintf "port1"
set dstintf "{{stack_name}}T1P1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set comments "Reverse of {{stack_name}}T1P1-f"
end
config firewall policy
edit 0
set name "{{stack_name}}T2P1-f"
set srcintf "{{stack_name}}T2P1"
set dstintf "port1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "f-{{stack_name}}T2P1"
set srcintf "port1"
set dstintf "{{stack_name}}T2P1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set comments "Reverse of {{stack_name}}T2P1-f"
end
config firewall policy
edit 0
set name "{{stack_name}}T1P1-s"
set srcintf "{{stack_name}}T1P1"
set dstintf "ssl.root"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "s-{{stack_name}}T1P1"
set srcintf "ssl.root"
set dstintf "{{stack_name}}T1P1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set comments "Reverse of {{stack_name}}T1P1-s"
end
config firewall policy
edit 0
set name "{{stack_name}}T2P1-s"
set srcintf "{{stack_name}}T2P1"
set dstintf "ssl.root"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "s-{{stack_name}}T2P1"
set srcintf "ssl.root"
set dstintf "{{stack_name}}T2P1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set comments "Reverse of {{stack_name}}T2P1-s"
end
config firewall policy
edit 0
set name "{{stack_name}}T1P1-h"
set srcintf "{{stack_name}}T1P1"
set dstintf "{{hubName}}"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "h-{{stack_name}}T1P1"
set srcintf "{{hubName}}"
set dstintf "{{stack_name}}T1P1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "{{stack_name}}T2P1-h"
set srcintf "{{stack_name}}T2P1"
set dstintf "{{hubName}}"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
config firewall policy
edit 0
set name "h-{{stack_name}}T2P1"
set srcintf "{{hubName}}"
set dstintf "{{stack_name}}T2P1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
end
The script configures: