class Terrafying::Components::VPN

Attributes

cidr[R]
ip_address[R]
name[R]
service[R]

Public Class Methods

create_in(vpc, name, provider, options = {}) click to toggle source
# File lib/terrafying/components/vpn.rb, line 29
def self.create_in(vpc, name, provider, options = {})
  VPN.new.create_in vpc, name, provider, options
end
new() click to toggle source
Calls superclass method
# File lib/terrafying/components/vpn.rb, line 33
def initialize
  super
end

Public Instance Methods

allow_security_group_in(vpc, name: '') click to toggle source
# File lib/terrafying/components/vpn.rb, line 129
def allow_security_group_in(vpc, name: '')
  name = "allow-#{@vpc.name}-vpn".downcase if name.empty?

  ingress_rules = [
    {
      from_port: 0,
      to_port: 0,
      protocol: -1,
      security_groups: [@service.egress_security_group],
      ipv6_cidr_blocks: nil,
      prefix_list_ids: nil,
      cidr_blocks: nil,
      self: nil,
      description: nil
    }
  ]

  if @is_public
    ingress_rules << {
      from_port: 0,
      to_port: 0,
      protocol: -1,
      cidr_blocks: ["#{@ip_address}/32"],
      ipv6_cidr_blocks: nil,
      prefix_list_ids: nil,
      security_groups: nil,
      self: nil,
      description: nil
    }
  end

  resource :aws_security_group, tf_safe("#{name}-#{vpc.name}"),
           name: name,
           vpc_id: vpc.id,
           ingress: ingress_rules
end
caddy_conf(ca, has_provider) click to toggle source
# File lib/terrafying/components/vpn.rb, line 244
      def caddy_conf(ca, has_provider)
        port = has_provider ? '4180' : '8080'
        tls = ca ? "/etc/ssl/#{ca.name}/#{@fqdn}/cert /etc/ssl/#{ca.name}/#{@fqdn}/key" : 'cloud@uswitch.com'
        {
          path: '/etc/caddy/Caddyfile',
          mode: '0644',
          contents: <<~CADDYFILE
            #{@fqdn}:443
            tls #{tls}
            proxy / localhost:#{port} {
              transparent
            }
          CADDYFILE
        }
      end
caddy_service(ca) click to toggle source
# File lib/terrafying/components/vpn.rb, line 225
def caddy_service(ca)
  optional_volumes = []

  optional_volumes << "/etc/ssl/#{ca.name}:/etc/ssl/#{ca.name}:ro" if ca

  Ignition.container_unit(
    'caddy', 'abiosoft/caddy:0.10.10',
    host_networking: true,
    volumes: [
      '/etc/ssl/certs:/etc/ssl/cert:ro',
      '/etc/caddy/Caddyfile:/etc/Caddyfile',
      '/etc/caddy/certs:/etc/caddy/certs'
    ] + optional_volumes,
    environment_variables: [
      'CADDYPATH=/etc/caddy/certs'
    ]
  )
end
create_in(vpc, name, oauth2_provider, options = {}) click to toggle source
# File lib/terrafying/components/vpn.rb, line 37
def create_in(vpc, name, oauth2_provider, options = {})
  options = {
    group: 'uSwitch Developers',
    cidr: '10.8.0.0/24',
    public: true,
    subnets: vpc.subnets.fetch(:public, []),
    static: false,
    route_all_traffic: false,
    route_dns_entries: [],
    units: [],
    tags: {},
    service: {}
  }.merge(options)

  @name = name
  @vpc = vpc
  @cidr = options[:cidr]
  @fqdn = vpc.zone.qualify(name)

  unless oauth2_provider.is_a?(Hash)
    raise 'You need to give a provider hash containing a type, client_id and client_secret'
  end

  has_provider = oauth2_provider[:type] != 'none'

  if has_provider && !%i[type client_id client_secret].all? { |k| oauth2_provider.key?(k) }
    raise 'You need to set type, client_id and client_secret'
  end

  units = [
    openvpn_service,
    openvpn_authz_service(options[:route_all_traffic], options[:route_dns_entries]),
    caddy_service(options[:ca])
  ]
  files = [
    openvpn_conf,
    openvpn_env,
    openvpn_ip_delay,
    caddy_conf(options[:ca], has_provider)
  ]
  keypairs = []

  if has_provider
    vpn_hash = Digest::SHA512.hexdigest(vpc.name + name + oauth2_provider[:client_secret] + oauth2_provider[:client_id])
    oauth2_provider[:cookie_hash_key]  ||= vpn_hash.byteslice(0, 64)
    oauth2_provider[:cookie_block_key] ||= vpn_hash.byteslice(64, 32)

    units.push(oauth2_proxy_service(oauth2_provider))
  end

  if options.key?(:ca)
    keypairs.push(options[:ca].create_keypair_in(self, @fqdn))
  end

  if options[:static]
    subnet = options[:subnets].first
    instances = [{ subnet: subnet, ip_address: subnet.ip_addresses.first }]
  else
    instances = [{}]
  end

  @is_public = options[:public]
  @service = add! Service.create_in(
    vpc, name,
    {
      public: @is_public,
      ports: [22, 443, { number: 1194, type: 'udp' }],
      tags: options[:tags],
      units: units + options[:units],
      files: files,
      keypairs: keypairs,
      subnets: options[:subnets],
      instances: instances,
      iam_policy_statements: [
        {
          Effect: 'Allow',
          Action: [
            'ec2:DescribeRouteTables'
          ],
          Resource: [
            '*'
          ]
        }
      ]
    }.merge(options[:service])
  )

  @ip_address = @service.instance_set.instances[0].ip_address

  self
end
egress_security_group() click to toggle source
# File lib/terrafying/components/vpn.rb, line 333
def egress_security_group
  @service.egress_security_group
end
ingress_security_group() click to toggle source
# File lib/terrafying/components/vpn.rb, line 329
def ingress_security_group
  @service.ingress_security_group
end
oauth2_proxy_service(oauth2_provider) click to toggle source
# File lib/terrafying/components/vpn.rb, line 207
def oauth2_proxy_service(oauth2_provider)
  Ignition.container_unit(
    'authnz', 'quay.io/uswitch/authnz-http-proxy:0.1',
    host_networking: true,
    arguments: [
      '--addr=0.0.0.0:4180',
      '--backend-url=http://localhost:8080',
      "--oauth-client-id='#{oauth2_provider[:client_id]}'",
      "--oauth-client-secret='#{oauth2_provider[:client_secret]}'",
      "--cookie-hash-key='#{oauth2_provider[:cookie_hash_key]}'",
      "--cookie-block-key='#{oauth2_provider[:cookie_block_key]}'"
    ],
    volumes: [
      '/usr/share/ca-certificates:/etc/ssl/certs:ro'
    ]
  )
end
openvpn_authz_service(route_all_traffic, route_dns_entry) click to toggle source
# File lib/terrafying/components/vpn.rb, line 179
def openvpn_authz_service(route_all_traffic, route_dns_entry)
  optional_arguments = []

  optional_arguments << '--route-all' if route_all_traffic

  if route_dns_entry.count > 0
    optional_arguments += route_dns_entry.map { |entry| "--route-dns-entries #{entry}" }
  end

  Ignition.container_unit(
    'openvpn-authz', 'quay.io/uswitch/openvpn-authz:1.2',
    host_networking: true,
    volumes: [
      '/etc/ssl/openvpn:/etc/ssl/openvpn',
      '/var/openvpn-authz:/var/openvpn-authz'
    ],
    environment_variables: [
      "AWS_REGION=#{aws.region}"
    ],
    arguments: optional_arguments + [
      "--fqdn #{@fqdn}",
      '--cache /var/openvpn-authz',
      '--user-header "X-Forwarded-Email"',
      '/etc/ssl/openvpn'
    ]
  )
end
openvpn_conf() click to toggle source
# File lib/terrafying/components/vpn.rb, line 260
      def openvpn_conf
        {
          path: '/etc/openvpn/openvpn.conf',
          mode: '0644',
          contents: <<~EOF
            server #{cidr_to_split_address(@cidr)}
            verb 3

            iproute /etc/openvpn/ovpn_ip.sh

            key /etc/ssl/openvpn/server/key
            ca /etc/ssl/openvpn/ca/cert
            cert /etc/ssl/openvpn/server/cert
            dh /etc/ssl/openvpn/dh.pem
            tls-auth /etc/ssl/openvpn/ta.key

            cipher AES-256-CBC
            auth SHA512
            tls-version-min 1.2

            key-direction 0
            keepalive 10 60
            persist-key
            persist-tun

            proto udp
            # Rely on Docker to do port mapping, internally always 1194
            port 1194
            dev tun0
            status /tmp/openvpn-status.log

            user nobody
            group nogroup
          EOF
        }
      end
openvpn_env() click to toggle source
# File lib/terrafying/components/vpn.rb, line 297
      def openvpn_env
        {
          path: '/etc/openvpn/ovpn_env.sh',
          mode: '0644',
          contents: <<~EOF
            declare -x OVPN_SERVER=#{@cidr}
          EOF
        }
      end
openvpn_ip_delay() click to toggle source

OpenVPN doesn't wait long enough for the tun0 device to init github.com/kylemanna/docker-openvpn/issues/370

# File lib/terrafying/components/vpn.rb, line 309
      def openvpn_ip_delay
        {
          path: '/etc/openvpn/ovpn_ip.sh',
          mode: '0755',
          contents: <<~IP_SCRIPT
            #!/usr/bin/env bash
            sleep 0.1
            /sbin/ip $*
          IP_SCRIPT
        }
      end
openvpn_service() click to toggle source
# File lib/terrafying/components/vpn.rb, line 166
def openvpn_service
  Ignition.container_unit(
    'openvpn', 'kylemanna/openvpn',
    host_networking: true,
    privileged: true,
    volumes: [
      '/etc/ssl/openvpn:/etc/ssl/openvpn:ro',
      '/etc/openvpn:/etc/openvpn'
    ],
    required_units: ['docker.service', 'network-online.target', 'openvpn-authz.service']
  )
end
pingable_by(*services) click to toggle source
# File lib/terrafying/components/vpn.rb, line 337
def pingable_by(*services)
  @service.pingable_by(*services)
end
pingable_by_cidr(*cidrs) click to toggle source
# File lib/terrafying/components/vpn.rb, line 345
def pingable_by_cidr(*cidrs)
  @service.pingable_by_cidr(*cidrs)
end
security_group() click to toggle source
# File lib/terrafying/components/vpn.rb, line 325
def security_group
  @service.security_group
end
used_by(*services) click to toggle source
# File lib/terrafying/components/vpn.rb, line 341
def used_by(*services)
  @service.used_by(*services)
end
used_by_cidr(*cidrs) click to toggle source
# File lib/terrafying/components/vpn.rb, line 349
def used_by_cidr(*cidrs)
  @service.used_by_cidr(*cidrs)
end
with_endpoint_service(*args) click to toggle source
# File lib/terrafying/components/vpn.rb, line 321
def with_endpoint_service(*args)
  @service.with_endpoint_service(*args)
end