#!/usr/bin/ruby require 'yaml' require 'ipaddr' THISHOST = `hostname`.chomp RIGHTNOW = "" #RIGHTNOW = `822-date`.chomp THISPROGRAM = $0 QUAGGA_DAEMONS = "# This file was automatically created by #{THISPROGRAM} on #{THISHOST} at #{RIGHTNOW}"+' # This file tells the quagga package which daemons to start. # # Entries are in the format: =(yes|no|priority) # 0, "no" = disabled # 1, "yes" = highest priority # 2 .. 10 = lower priorities # Read /usr/share/doc/quagga/README.Debian for details. # # Sample configurations for these daemons can be found in # /usr/share/doc/quagga/examples/. # # ATTENTION: # # When activation a daemon at the first time, a config file, even if it is # empty, has to be present *and* be owned by the user and group "quagga", else # the daemon will not be started by /etc/init.d/quagga. The permissions should # be u=rw,g=r,o=. # When using "vtysh" such a config file is also needed. It should be owned by # group "quaggavty" and set to ug=rw,o= though. Check /etc/pam.d/quagga, too. # zebra=yes bgpd=yes ospfd=no ospf6d=no ripd=no ripngd=no isisd=no ' QUAGGA_ZEBRA = "! This file was automatically created by #{THISPROGRAM} on #{THISHOST} at #{RIGHTNOW}"+' ! -*- zebra -*- ! hostname HOSTNAME password zebra enable password L5ar2fa ! ! Interface\'s description. ! !interface lo ! description test of desc. ! !interface sit0 ! multicast ! ! Static default route sample. ! !ip route 0.0.0.0/0 203.181.89.241 ! log syslog ' QUAGGA_BGPD_HEAD = "! This file was automatically created by #{THISPROGRAM} on #{THISHOST} at #{RIGHTNOW}"+' ! -*- bgpd -*- ! hostname HOSTNAME password zebra enable password L5ar2fa ! log syslog !debug bgp !debug bgp events !debug bgp filters !debug bgp fsm !!debug bgp keepalives !debug bgp updates ! ' DEFAULT_OPENSSLCNF = ' HOME = CA oid_section = new_oids [ new_oids ] [ ca ] default_ca = CA_default [ CA_default ] dir = CA/keys # Where everything is kept certs = $dir # Where the issued certs are kept crl_dir = $dir # Where the issued crl are kept database = $dir/index.txt # database index file. new_certs_dir = $dir # default place for new certs. certificate = $dir/ca.crt # The CA certificate serial = $dir/serial # The current serial number crl = $dir/crl.pem # The current CRL private_key = $dir/ca.key # The private key RANDFILE = $dir/.rand # private random number file x509_extensions = usr_cert # The extentions to add to the cert default_days = 3650 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = md5 # which md to use. preserve = no # keep passed DN ordering policy = policy_match [ policy_match ] organizationName = supplied commonName = supplied [ req ] default_bits = 2048 distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extentions to add to the self signed cert string_mask = nombstr [ req_distinguished_name ] 0.organizationName = Organization Name 0.organizationName_default= %s commonName = Common Name commonName_default = $ENV::CN commonName_max = 64 [ req_attributes ] [ usr_cert ] basicConstraints = CA:FALSE nsComment = "Created using Peter Palfrader\'s MakeVPN and OpenSSL" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] basicConstraints = CA:true subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always [ crl_ext ] authorityKeyIdentifier = keyid:always,issuer:always ' UPSCRIPT='#!/bin/sh TUN_DEV=$1 TUN_MTU=$2 LINK_MTU=$3 LOCAL_IP=$4 REMOTE_IP=$5 ACTION=$6 echo 0 > /proc/sys/net/ipv4/conf/$TUN_DEV/rp_filter #gw=$(ip route show 0.0.0.0/0 | awk \'{print $3; exit }\'); #ip r add 141.70.64.0/20 via "$gw" #ip r add 10.0.0.0/8 via "$gw" #ip r add 0.0.0.0/1 dev "$TUN_DEV" #ip r add 128.0.0.0/1 dev "$TUN_DEV" ' ###################################################################################### ###################################################################################### $config = YAML::load( File.open( 'Hosts' ) ) $NAMESPACE=$config['namespace'] def sys(command) system(command) or throw "Command '#{command}' failed" end def check_ca return true if FileTest.directory?("CA") Dir.mkdir("CA") Dir.mkdir("CA/keys") opensslcnf = File.new('CA/openssl.cnf', "w") opensslcnf.puts DEFAULT_OPENSSLCNF%[$config['cadndomain']] opensslcnf.close ENV['CN'] = 'ca'+'.'+$config['cadndomain'] sys("openssl req -batch -days 3650 -nodes -new -newkey rsa:4096 -x509 -keyout CA/keys/ca.key -out CA/keys/ca.crt -config CA/openssl.cnf ") File.chmod(0600, "CA/keys/ca.key") == 1 or throw "Cannot chmod CA/keys/ca.key" index=File.new('CA/keys/index.txt', "w") index.close serial=File.new('CA/keys/serial', "w") serial.puts "01" serial.close end def check_dh return true if FileTest.exists?("CA/keys/dh2048.pem") if FileTest.exists?("dh2048.pem") sys("cp dh2048.pem CA/keys/") else sys("openssl dhparam -out CA/keys/dh2048.pem 2048") end end def build_key(cn) return true if FileTest.exists?("CA/keys/#{cn}.p12") ENV['CN'] = cn sys("openssl req -batch -days 3650 -nodes -new -newkey rsa:3072 -keyout CA/keys/#{cn}.key -out CA/keys/#{cn}.csr -config CA/openssl.cnf ") sys("openssl ca -batch -days 3650 -out CA/keys/#{cn}.crt -in CA/keys/#{cn}.csr -config CA/openssl.cnf ") File.chmod(0600, "CA/keys/#{cn}.key") == 1 or throw "Cannot chmod CA/keys/#{cn}.key" sys("openssl pkcs12 -passout pass: -export -in CA/keys/#{cn}.crt -inkey CA/keys/#{cn}.key -certfile CA/keys/ca.crt -out CA/keys/#{cn}.p12") File.chmod(0600, "CA/keys/#{cn}.p12") == 1 or throw "Cannot chmod CA/keys/#{cn}.p12" end def check_keys(host) check_ca check_dh cn = host['name'] +'.'+ $config['cadndomain'] build_key(cn) end def makelist(s) s.delete(' ').split(',') end def included_in_list(haystack, needle) makelist(haystack).include?(needle) end def can_connect(client, server) if server['accepts'] == nil return false elsif server['accepts'] == '*' return true elsif included_in_list(server['accepts'], client['name']) return true else return false end end def want_connection(h1, h2) if h1['limit_peers'] == nil and h2['limit_peers'] == nil return true end if h1['limit_peers'] != nil and !included_in_list(h1['limit_peers'], h2['name']) return false end if h2['limit_peers'] != nil and !included_in_list(h2['limit_peers'], h1['name']) return false end return true end def makelistname(peer, inout) if inout == 'import' return 'VPNn2'+peer+'In' elsif inout == 'export' return 'VPNn2'+peer+'Out' else throw "Unknown inout #{inout}" end end hostlist = $config['hosts'].values.sort{ |a,b| a['host_no'] <=> b['host_no'] } $config['hosts'].each_pair{ |name, host| throw "Host #{name} doesn't have any groups" unless host['groups'] host['groups'] = makelist(host['groups']).sort{|a,b| a<=>b} host['groups'].each{ |g| throw "Group #{g} in #{name} is not defined" unless $config['groups'][g] } host['name'] = name host['vpn_address'] = $config['prefix']+'.'+host['host_no'].to_s host['vpn_address6'] = $config['prefix6']+':'+host['host_no'].to_s host['asn'] = $config['baseasn']+host['host_no'] host['ifacename'] = "tun-n2-#{host['name']}"[0..14] host['networks'] = {} unless host['networks'] host['networks'][host['vpn_address']+"/32"] = host['groups'].join(',') host['pkcs12'] = true unless host.has_key?('pkcs12') host['ipv6'] = false unless host.has_key?('ipv6') host['avoid_udp6'] = false unless host.has_key?('avoid_udp6') host['networks6'] = {} unless host['networks6'] if host['ipv6'] host['networks6'][host['vpn_address6']+"/128"] = host['groups'].join(',') end host['inet_port'] = {} } throw "Duplicate iface names" unless hostlist.collect{ |host| host['ifacename'] }.uniq.size == $config['hosts'].size # Setup peers and link local addresses hostlist.each{ |host| host['peers'] = hostlist.find_all{ |peer| host['name'] != peer['name'] and want_connection(host, peer) and (can_connect(host,peer) or can_connect(peer,host)) } host['link-local'] = {} host['peers'].each{ |peer| lower = host['host_no'] < peer['host_no'] ? host['host_no'] : peer['host_no'] higher = host['host_no'] > peer['host_no'] ? host['host_no'] : peer['host_no'] ipv6_link_local_me = "fe80:0:#{lower}:#{higher}:0:0:0:#{host['host_no']}" ipv6_link_local_peer = "fe80:0:#{lower}:#{higher}:0:0:0:#{peer['host_no']}" host['link-local'][ peer['name'] ] = { "me" => ipv6_link_local_me, "peer" => ipv6_link_local_peer } } } # Setup ports for openvpn # ======================= # First import ports from manual configuration hostlist.each{ |host| next unless host['inet_port_override'] host['inet_port_override'].each_key{ |peername| hostname = host['name'] peer = $config['hosts'][ peername ] throw "Peer #{peername} for host #{hostname} not found" unless peer [peername, hostname].each{ |item| throw "host->#{hostname}->inet_port_override->#{peername} does not have a key #{item}" unless host['inet_port_override'][peername][item] } host['inet_port'][peername] = {} host['inet_port'][peername]['local'] = host['inet_port_override'][peername][hostname] host['inet_port'][peername]['remote'] = host['inet_port_override'][peername][peername] unless peer['inet_port'][hostname] peer['inet_port'][hostname] = {} [peername, hostname].each{ |item| if peer['inet_port_override'] and peer['inet_port_override'][hostname] and peer['inet_port_override'][hostname][item] and peer['inet_port_override'][hostname][item] != host['inet_port_override'][peername][item] throw("host->#{hostname}->inet_port_override->#{peername}->#{item} and "+ "host->#{peername}->inet_port_override->#{hostname}->#{item} both exist but are different") end } peer['inet_port'][hostname]['remote'] = host['inet_port'][peername]['local'] peer['inet_port'][hostname]['local'] = host['inet_port'][peername]['remote'] end } } # Then set the default values if nothing is set yet hostlist.each{ |host| host['peers'].each{ |peer| host['inet_port'][ peer['name'] ] = { "remote" => $config['baseport']+host['host_no'], "local" => $config['baseport']+peer['host_no'] } unless host['inet_port'][ peer['name'] ] } throw "Duplicate local ports on host #{host['name']}" unless host['inet_port'].values.collect{ |peer| peer['local'] }.uniq.size == host['inet_port'].size } ##################################################################### # Create the zonefile zonefile = File.new("#{$NAMESPACE}.zone", "w") zonefile.puts "; zonefile snipped for #{$NAMESPACE} VPN" zonefile.puts "; Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}." hostlist.each{ |host| zonefile.puts "#{host['name']} IN A #{host['vpn_address']}" zonefile.puts "#{host['name']} IN AAAA #{host['vpn_address6']}" if host['ipv6'] } zonefile.puts zonefile.puts "; vim:set syn=dns:" zonefile.close ##################################################################### hostlist.each{ |host| check_keys host cn = host['name'] +'.'+ $config['cadndomain'] dir = "#{$NAMESPACE}-#{host['name']}" Dir.mkdir(dir) unless FileTest.directory?(dir) sys("rm -f #{dir}/#{$NAMESPACE}-#{cn}.p12") sys("rm -f #{dir}/#{$NAMESPACE}-#{cn}.crt") sys("rm -f #{dir}/#{$NAMESPACE}-#{cn}.key") sys("rm -f #{dir}/#{$NAMESPACE}-CA.crt") if host['pkcs12'] sys("ln CA/keys/#{cn}.p12 #{dir}/#{$NAMESPACE}-#{cn}.p12") else sys("ln CA/keys/#{cn}.crt #{dir}/#{$NAMESPACE}-#{cn}.crt") sys("ln CA/keys/#{cn}.key #{dir}/#{$NAMESPACE}-#{cn}.key") sys("ln CA/keys/ca.crt #{dir}/#{$NAMESPACE}-CA.crt") end sys("rm -f #{dir}/#{$NAMESPACE}.dh2048.pem && ln CA/keys/dh2048.pem #{dir}/#{$NAMESPACE}.dh2048.pem") iptables = File.new("#{dir}/#{$NAMESPACE}.iptables.sh", "w") ip6tables = File.new("#{dir}/#{$NAMESPACE}.ip6tables.sh", "w") ipferm = File.new("#{dir}/#{$NAMESPACE}.iptables.ferm", "w") ip6ferm = File.new("#{dir}/#{$NAMESPACE}.ip6tables.ferm", "w") iptables.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}." iptables.puts "PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin" iptables.puts "echo 'Doing #{$NAMESPACE} VPN rules.'" iptables.puts "iptables --new-chain vpn-#{$NAMESPACE}" iptables.puts "iptables --flush vpn-#{$NAMESPACE}" ip6tables.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}." ip6tables.puts "PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin" ip6tables.puts "echo 'Doing #{$NAMESPACE} VPN rules.'" ip6tables.puts "ip6tables --new-chain vpn-#{$NAMESPACE}" ip6tables.puts "ip6tables --flush vpn-#{$NAMESPACE}" ip6ferm.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}." ip6ferm.puts "def &vpn_#{$NAMESPACE}6() = {" ipferm.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}." ipferm.puts "def &vpn_#{$NAMESPACE}() = {" daemonsfilename = "#{dir}/#{$NAMESPACE}.quagga.daemons" daemons = File.new(daemonsfilename, "w") daemons.puts QUAGGA_DAEMONS.gsub('HOSTNAME', host['name']) daemons.close zebrafilename = "#{dir}/#{$NAMESPACE}.quagga.zebra" zebra = File.new(zebrafilename, "w") zebra.puts QUAGGA_ZEBRA.gsub('HOSTNAME', host['name']) zebra.close File.chmod(0600, zebrafilename) == 1 or throw "Cannot chmod #{zebrafilename}" # BGP config bgpdfilename = "#{dir}/#{$NAMESPACE}.quagga.bgpd" bgpd = File.new(bgpdfilename, "w") bgpd.puts QUAGGA_BGPD_HEAD.gsub('HOSTNAME', host['name']) count = 10 makelist($config['default_networks']).each{ |net| bgpd.puts "ip prefix-list VPNn2 seq #{count} permit #{net} #{net =~ /\/32/ ? '' : "le 32"}" count = count + 1 } bgpd.puts "!" %w(import export).each{ |inout| if host.has_key?('extra_'+inout) host['extra_'+inout].each_pair{ |peer, extras| listname = makelistname(peer, inout) count = 10 unless host['no_defaults_'+inout] makelist($config['default_networks']).each{ |net| bgpd.puts "ip prefix-list #{listname} seq #{count} permit #{net} #{net =~ /\/32/ ? '' : "le 32"}" count = count + 1 } end makelist(extras).each{ |net| bgpd.puts "ip prefix-list #{listname} seq #{count} permit #{net} #{net =~ /\/32/ ? '' : "le 32"}" count = count + 1 } } bgpd.puts "!" end } bgpd.puts "!" bgpd.puts "!" bgpd.puts "! ** route-map for incoming **" bgpd.puts "! filter out routes that we do not care about - we should never get sent" bgpd.puts "! routes like these from our peers, but hey:" bgpd.puts "! make a community-list filter that matches all the communities" bgpd.puts "! that we do not care about. So start by denying those that we" bgpd.puts "! do care about, then accept the rest." # quagga doc: ip community-list standard (permit|deny) # When community is empty it matches to any routes. host['groups'].each{ |group| bgpd.puts "! %10s: %s"%[$config['groups'][group], group] bgpd.puts "ip community-list standard BoringCommunities deny #{ $config['groups'][group] }" } bgpd.puts "ip community-list standard BoringCommunities permit" bgpd.puts "!" inmaps = [] outmaps = [] outmaps << "! ** route-map for outgoing **" outmaps << "! We only announce prefixes to peers if they are in the proper group" outmaps << "!" outmaps << "! Each prefix is in one or more groups, set by the originating node" outmaps << "! When sending we make sure that we only announce prefixes if the" outmaps << "! peer is in the proper group to learn about them." outmaps << "! We also clear all community tags of the other groups - It's" outmaps << "! not a peer's business to know which of the prefixes he may know" outmaps << "! about are also in other groups." outmaps << "! (yes, this also means that group information can get lost:" outmaps << "! consider - A, B and C are in group Foo," outmaps << "! - A and C are also in group Bar." outmaps << "! - connections: A to B, B to C. (a to c is broken atm)" outmaps << "! - now if there is a prefix only in group Bar, originating" outmaps << "! at A, then C will never learn about it.)" outmaps << "!" outmaps << "! Nice side effect: This allows us to drop any prefixes that come" outmaps << "! from a node with tags that it shouldn't know about: it must be lying." host['networks'].each_key{ |net| outmaps << "ip prefix-list pl#{ net.tr(':./', '-_') } seq 10 permit #{net} #{net =~ /\/32/ ? '' : "le 32"}" } host['networks6'].each_key{ |net| outmaps << "ipv6 prefix-list pl#{ net.tr(':./', '-_') } seq 10 permit #{net} #{net =~ /\/128/ ? '' : "le 128"}" } hostlist.each{ |otherhost| next if host['name'] == otherhost['name'] next unless otherhost['bgp_preference'] inmaps << "!" inmaps << "! Create an ACL for anything that passes through AS#{otherhost['asn']} (#{otherhost['name']})" inmaps << "ip as-path access-list passthruAS#{otherhost['asn']} permit _#{otherhost['asn']}_" } mapdups = {} host['peer_map_name'] = {} host['peers'].each{ |peer| sharedGroups = peer['groups'].find_all{ |g| host['groups'].include?(g) } #puts "#{host['name']} shares with #{peer['name']}: "+sharedGroups.join(' '); mapname = 'MAP_'+sharedGroups.join('_') host['peer_map_name'][peer['name']] = mapname next if mapdups.has_key?(mapname) mapdups[mapname] = true shared_groups_comlist = "communities_#{ sharedGroups.join('_') }" shared_groups_comlist_inv = "communitiesNot_#{ sharedGroups.join('_') }" inmaps << "! #{shared_groups_comlist} matches the communities to send" inmaps << "! #{shared_groups_comlist_inv} matches the community tags to clear when sending" sharedGroups.each{ |g| inmaps << "ip community-list standard #{ shared_groups_comlist } permit #{ $config['groups'][g] }" }; have_inverse_groups = false $config['groups'].keys.find_all{|g| not sharedGroups.include?(g) }.each{ |g| inmaps << "ip community-list standard #{ shared_groups_comlist_inv } permit #{ $config['groups'][g] }" have_inverse_groups = true }; seq = 10 inmaps << "!" inmaps << "! * route map from groups "+sharedGroups.join(',')+" *" if have_inverse_groups inmaps << "! It knows about stuff that it should not know about (and if it's" inmaps << "! just the tag on a route where it actually is allowed to know the" inmaps << "! route, but not that it also belongs to a group)." inmaps << "! It's probably lying. Drop" inmaps << "route-map IN#{mapname} deny #{seq}"; seq = seq + 1 inmaps << " match community #{shared_groups_comlist_inv}" end inmaps << "! It sends us stuff that we don't care about. It probably" inmaps << "! shouldn't do that. Drop (probably also caught already in the previous step)" inmaps << "route-map IN#{mapname} deny #{seq}"; seq = seq + 1 inmaps << " match community BoringCommunities" hostlist.each{ |otherhost| next if host['name'] == otherhost['name'] next unless otherhost['bgp_preference'] inmaps << "!" inmaps << "! Anything that passes through AS#{otherhost['asn']} (#{otherhost['name']}) gets a lower preference" inmaps << "route-map IN#{mapname} permit #{seq}"; seq = seq + 1 inmaps << " match as-path passthruAS#{otherhost['asn']}" inmaps << " set local-preference #{otherhost['bgp_preference']}" } inmaps << "route-map IN#{mapname} permit #{seq}" inmaps << "!" inmaps << "!" outmaps << "!" outmaps << "! * route map to groups "+sharedGroups.join(',')+" *" seq = 10 ['', '6'].each{ |type| outmaps << "route-map OUT#{mapname + type} permit #{seq}"; seq = seq + 1 outmaps << " match community #{shared_groups_comlist}" outmaps << " set comm-list #{shared_groups_comlist_inv} delete" if have_inverse_groups if type == '' t = [''] else t = ['', '6'] end t.each{ |t| host['networks'+t].each_pair{ |net, communities| communities = makelist(communities) setCommunities = [] communities.each{ |c| throw "net #{net} declared community #{c} but host isn't even in it" unless host['groups'].include?(c) setCommunities << c if sharedGroups.include?(c) } next unless setCommunities.length > 0 outmaps << "route-map OUT#{mapname + type} permit #{seq}"; seq = seq + 1 outmaps << " match ip"+(t=="6" ? 'v6' : '')+" address prefix-list pl#{ net.tr(':./', '-_') }" outmaps << " set community "+setCommunities.collect{|c| $config['groups'][c] }.join(' ')+" additive" } } outmaps << "!" } } bgpd.puts inmaps.join("\n") bgpd.puts outmaps.join("\n") bgpd.puts "!" bgpd.puts "!ipv6 access-list all permit any" bgpd.puts "!route-map set-nexthop permit 10" bgpd.puts "! match ipv6 address all" bgpd.puts "! set ipv6 nexthop global #{host['vpn_address6']}" bgpd.puts "!" bgpd.puts "! ** BGP ROUTER **" bgpd.puts "router bgp #{host['asn']}" bgpd.puts " bgp router-id #{host['vpn_address']}" bgpd.puts "!" bgpd.puts "! ** our networks **" host['networks'].each_key{ |net| bgpd.puts " network #{net}" } if host['ipv6'] and host['networks6'].length > 0 bgpd.puts " address-family ipv6" host['networks6'].each_key{ |net| bgpd.puts " network #{net}" } bgpd.puts " exit-address-family" end host['peers'].each{ |peer| if can_connect(peer, host) and can_connect(host, peer) host_is_server = host['host_no'] < peer['host_no'] elsif can_connect(peer, host) host_is_server = true elsif can_connect(host, peer) host_is_server = false else throw "No connection is possible between #{host['name']} and #{peer['name']} but peer is in peerlist anyway?" end throw "host is server but doesn't have an address" if host_is_server and host['host_address'] == nil throw "peer is server but doesn't have an address" if !host_is_server and peer['host_address'] == nil upscriptnamebase = "#{$NAMESPACE}-#{peer['name']}.up" upscriptname = "#{dir}/"+upscriptnamebase conffile = File.new("#{dir}/#{$NAMESPACE}-%s.conf"%[peer['name']], "w") conffile.puts "dev %s"%[peer['ifacename']] conffile.puts "tun-ipv6" if host['ipv6'] and peer['ipv6'] #mtu = 1434 #conffile.puts "fragment #{mtu}" #conffile.puts "mssfix #{mtu}" #conffile.puts "tun-mtu #{mtu}" mtu = 1200 conffile.puts "fragment #{mtu}" conffile.puts "ifconfig %s %s"%[host['vpn_address'], peer['vpn_address']] remote_addr = [] remote_addr << peer['host_address'] if peer['host_address'] remote_addr += peer['host_address_extra'] if peer['host_address_extra'] remote_port = [ host['inet_port'][ peer['name'] ]['remote'] ] remote_port += peer['host_port_extra'] if peer['host_port_extra'] if not host_is_server if remote_addr.size > 0 then remote_addr.each do |raddr| remote_port.each do |rport| conffile.puts "remote #{raddr} #{rport}" end end else throw "have host_port_extra but no host_address(_extra)" if peer['host_port_extra'] conffile.puts "rport #{host['inet_port'][ peer['name'] ]['remote']}" end end conffile.puts "lport #{host['inet_port'][ peer['name'] ]['local']}" unless host['accepts'].nil? if host['accepts'].nil? conffile.puts "nobind" else conffile.puts "multihome" end #if host['bind_address'] # conffile.puts "local %s"%[host['bind_address']] #elsif host['host_address'] # conffile.puts "local %s"%[host['host_address']] #end if host_is_server conffile.puts "proto udp6" unless host['avoid_udp6'] conffile.puts "tls-server" conffile.puts "dh #{$NAMESPACE}.dh2048.pem" else conffile.puts "tls-client" end conffile.puts "cipher AES-256-CBC" if host['openvpn_ge_23'] conffile.puts "verify-x509-name \"%s%s.%s\""%[$config['x509nameprefix'], peer['name'], $config['cadndomain']] else conffile.puts "tls-remote %s.%s"%[peer['name'], $config['cadndomain']] end if host['pkcs12'] conffile.puts "pkcs12 #{$NAMESPACE}-%s.%s.p12"%[host['name'], $config['cadndomain']] else conffile.puts "ca #{$NAMESPACE}-CA.crt" conffile.puts "cert #{$NAMESPACE}-%s.%s.crt"%[host['name'], $config['cadndomain']] conffile.puts "key #{$NAMESPACE}-%s.%s.key"%[host['name'], $config['cadndomain']] end conffile.puts "up /etc/openvpn/#{upscriptnamebase}" conffile.puts "up-delay" conffile.puts "#user nobody" conffile.puts "#group nogroup" conffile.puts "#persist-tun" conffile.puts "persist-key" conffile.puts "comp-lzo" conffile.puts "keepalive 7 30" conffile.puts "verb 3" conffile.puts "script-security 2" conffile.puts "redirect-gateway def1" if host['default-via'] and host['default-via'] == peer['name'] conffile.close ########## upscript = File.new(upscriptname, "w") upscript.puts UPSCRIPT if host['ipv6'] and peer['ipv6'] upscript.puts "ip -6 addr add #{ host['link-local'][ peer['name'] ]['me'] }/64 dev $TUN_DEV" upscript.puts "ip -6 addr add #{ host['vpn_address6'] }/128 dev $TUN_DEV" end upscript.puts "true" upscript.close File.chmod(0755, upscriptname) == 1 or throw "Cannot chmod #{upscriptname}" ########## # do not filter on remote port as NAT gateways may change ports iptables.puts "#\n# to/from #{peer['name']}" ip6tables.puts "#\n# to/from #{peer['name']}" ipferm.puts "#\n# to/from #{peer['name']}" ip6ferm.puts "#\n# to/from #{peer['name']}" src = [] + remote_addr src << '0.0.0.0/0' << '::/0' if src.size == 0 src = src.select {|x| IPAddr.new(x) rescue nil } dst = [] dst << host['host_address'] if host['host_address'] dst += host['host_address_extra'] if host['host_address_extra'] dst << '0.0.0.0/0' << '::/0' if dst.size == 0 dst = dst.select {|x| IPAddr.new(x) rescue nil } src.each do |s| s6 = IPAddr.new(s).ipv6? dst.each do |d| d6 = IPAddr.new(d).ipv6? next if s6 != d6 o = d6 ? ip6tables : iptables i = d6 ? 'ip6tables' : 'iptables' o.puts "#{i} --append vpn-#{$NAMESPACE} --source #{ s } --destination #{ d } \\" o.puts " --protocol udp --destination-port #{ host['inet_port'][ peer['name'] ]['local'] } \\" o.puts " --jump ACCEPT" o = d6 ? ip6ferm : ipferm o.puts " saddr #{ s } daddr #{ d } " + " proto udp dport #{ host['inet_port'][ peer['name'] ]['local'] } " + " ACCEPT;" end end iptables.puts "iptables --append vpn-#{$NAMESPACE} --source #{ peer['vpn_address'] } --destination #{ host['vpn_address'] } \\" iptables.puts " --protocol tcp --destination-port #{ host['bgp_port'] or '179' } \\" iptables.puts " --in-interface #{ peer['ifacename'] } \\" iptables.puts " --jump ACCEPT" if host['ipv6'] and peer['ipv6'] ip6tables.puts "ip6tables --append vpn-#{$NAMESPACE} \\" ip6tables.puts " --source #{ host['link-local'][ peer['name'] ]['peer'] } \\" ip6tables.puts " --destination #{ host['link-local'][ peer['name'] ]['me'] } \\" ip6tables.puts " --protocol tcp --destination-port #{ host['bgp_port'] or '179' } \\" ip6tables.puts " --in-interface #{ peer['ifacename'] } \\" ip6tables.puts " --jump ACCEPT" end ipferm.puts " saddr #{ peer['vpn_address'] } daddr #{ host['vpn_address'] } " + " proto tcp dport #{ host['bgp_port'] or '179' } " + " interface #{ peer['ifacename'] } " + " ACCEPT;" if host['ipv6'] and peer['ipv6'] ip6ferm.puts " saddr #{ host['link-local'][ peer['name'] ]['peer'] } \\" ip6ferm.puts " daddr #{ host['link-local'][ peer['name'] ]['me'] } \\" ip6ferm.puts " proto tcp dport #{ host['bgp_port'] or '179' } \\" ip6ferm.puts " interface #{ peer['ifacename'] } \\" ip6ferm.puts " ACCEPT;" end ########## bgpd.puts "!" bgpd.puts "! ** peer #{peer['name']} **" if host['ipv6'] and peer['ipv6'] n = host['link-local'][ peer['name'] ]['peer'] else n = peer['vpn_address'] end bgpd.puts " neighbor #{n} remote-as #{peer['asn']}" bgpd.puts " neighbor #{n} send-community" bgpd.puts " neighbor #{n} interface #{peer['ifacename']}" bgpd.puts " neighbor #{n} description #{peer['name']}" bgpd.puts " neighbor #{n} port #{peer['bgp_port']}" if peer.has_key?('bgp_port') bgpd.puts "!" filtername = (host.has_key?('extra_export') and host['extra_export'].has_key?(peer['name'])) ? makelistname(peer['name'], 'export') : 'VPNn2' bgpd.puts " neighbor #{peer['vpn_address']} prefix-list #{filtername} out" filtername = (host.has_key?('extra_import') and host['extra_import'].has_key?(peer['name'])) ? makelistname(peer['name'], 'import') : 'VPNn2' bgpd.puts " neighbor #{peer['vpn_address']} prefix-list #{filtername} in" unless host['ipv6'] and peer['ipv6'] bgpd.puts " neighbor #{peer['vpn_address']} route-map IN#{ host['peer_map_name'][peer['name']] } in" bgpd.puts " neighbor #{peer['vpn_address']} route-map OUT#{ host['peer_map_name'][peer['name']] } out" bgpd.puts "!" else #if host['ipv6'] and peer['ipv6'] bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } route-map IN#{ host['peer_map_name'][peer['name']] } in" bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } route-map OUT#{ host['peer_map_name'][peer['name']] }6 out" bgpd.puts " address-family ipv6" bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } activate" bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } route-map IN#{ host['peer_map_name'][peer['name']] } in" bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } route-map OUT#{ host['peer_map_name'][peer['name']] }6 out" bgpd.puts " exit-address-family" end } #ipv6stuff << " exit-address-family" #bgpd.puts ipv6stuff.join("\n") iptables.close ip6tables.close ipferm.puts "}" ipferm.close ip6ferm.puts "}" ip6ferm.close bgpd.close File.chmod(0600, bgpdfilename) == 1 or throw "Cannot chmod #{bgpdfilename}" }