summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xGenerate667
-rw-r--r--Makefile6
-rwxr-xr-xPush122
3 files changed, 795 insertions, 0 deletions
diff --git a/Generate b/Generate
new file mode 100755
index 0000000..9de41fa
--- /dev/null
+++ b/Generate
@@ -0,0 +1,667 @@
+#!/usr/bin/ruby
+
+require 'yaml'
+
+
+THISHOST = `hostname`.chomp
+RIGHTNOW = "<removed timestamp as it breaks checksumming>"
+#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: <daemon>=(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.
+#
+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
+!
+'
+QUAGGA_BGPD_DEFAULT_PREFIX_LIST_PERMIT=
+'
+ip prefix-list LISTNAME seq 10 permit 10.200.0.0/16 le 32
+ip prefix-list LISTNAME seq 11 permit 172.22.0.0/16 le 32
+ip prefix-list LISTNAME seq 12 permit 192.168.224.0/24 le 32'
+
+QUAGGA_BGPD_DEFAULT_PREFIX_LIST_STARTEXTRASEQ = 31
+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
+'
+
+######################################################################################
+######################################################################################
+
+$config = YAML::load( File.open( 'Hosts' ) )
+
+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 -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 -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
+
+
+iface_dup_check = {}
+$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['inet_port'] = $config['baseport']+host['host_no']
+ host['asn'] = $config['baseasn']+host['host_no']
+ host['ifacename'] = "tun-n2-#{host['name']}"[0..14]
+ iface_dup_check[host['ifacename']] = true
+ 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['networks6'] = {} unless host['networks6']
+ if host['ipv6']
+ host['networks6'][host['vpn_address6']+"/128"] = host['groups'].join(',')
+ end
+}
+throw "Duplicate iface names" unless iface_dup_check.size == $config['hosts'].size
+
+
+hostlist = $config['hosts'].values.sort{ |a,b| a['host_no'] <=> b['host_no'] }
+hostlist.each{ |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))}
+
+ check_keys host
+ cn = host['name'] +'.'+ $config['cadndomain']
+ dir = "noreply2-#{host['name']}"
+ Dir.mkdir(dir) unless FileTest.directory?(dir)
+
+ sys("rm -f #{dir}/noreply2-#{cn}.p12")
+ sys("rm -f #{dir}/noreply2-#{cn}.crt")
+ sys("rm -f #{dir}/noreply2-#{cn}.key")
+ sys("rm -f #{dir}/noreply2-CA.crt")
+ if host['pkcs12']
+ sys("ln CA/keys/#{cn}.p12 #{dir}/noreply2-#{cn}.p12")
+ else
+ sys("ln CA/keys/#{cn}.crt #{dir}/noreply2-#{cn}.crt")
+ sys("ln CA/keys/#{cn}.key #{dir}/noreply2-#{cn}.key")
+ sys("ln CA/keys/ca.crt #{dir}/noreply2-CA.crt")
+ end
+ sys("rm -f #{dir}/noreply2.dh2048.pem && ln CA/keys/dh2048.pem #{dir}/noreply2.dh2048.pem")
+
+
+ iptables = File.new("#{dir}/noreply2.iptables.sh", "w")
+ ip6tables = File.new("#{dir}/noreply2.ip6tables.sh", "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 noreply2 VPN rules.'"
+ iptables.puts "iptables --new-chain vpn-noreply2"
+ iptables.puts "iptables --flush vpn-noreply2"
+
+ 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 noreply2 VPN rules.'"
+ if host['ipv6']
+ ip6tables.puts "ip6tables --new-chain vpn-noreply2"
+ ip6tables.puts "ip6tables --flush vpn-noreply2"
+ end
+
+ daemonsfilename = "#{dir}/noreply2.quagga.daemons"
+ daemons = File.new(daemonsfilename, "w")
+ daemons.puts QUAGGA_DAEMONS.gsub('HOSTNAME', host['name'])
+ daemons.close
+
+ zebrafilename = "#{dir}/noreply2.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}/noreply2.quagga.bgpd"
+ bgpd = File.new(bgpdfilename, "w")
+ bgpd.puts QUAGGA_BGPD_HEAD.gsub('HOSTNAME', host['name'])
+ bgpd.puts QUAGGA_BGPD_DEFAULT_PREFIX_LIST_PERMIT.gsub('LISTNAME', 'VPNn2')
+ bgpd.puts "!"
+ %w(import export).each{ |inout|
+ if host.has_key?('extra_'+inout)
+ host['extra_'+inout].each_pair{ |peer, extras|
+ listname = makelistname(peer, inout)
+ bgpd.puts QUAGGA_BGPD_DEFAULT_PREFIX_LIST_PERMIT.gsub('LISTNAME', listname) unless host.has_key?('no_defaults_'+inout) and
+ host['no_defaults_'+inout]
+ count = QUAGGA_BGPD_DEFAULT_PREFIX_LIST_STARTEXTRASEQ
+ 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 <name> (permit|deny) <community>
+ # 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['link-local'] = {}
+ 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
+ }
+ }
+
+ 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"}"
+ }
+ mapdups = {}
+ host['peer_map_name'] = {}
+ 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 just the tag on a route where it actually is allowed to know the route, but not that it also belongs to a group). 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 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 << "ip as-path access-list passthruAS#{otherhost['asn']} permit _#{otherhost['asn']}_"
+ 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
+
+ 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 = 'noreply2-%s.up'%[peer['name']]
+ upscriptname = "#{dir}/"+upscriptnamebase
+
+ conffile = File.new("#{dir}/noreply2-%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']]
+ conffile.puts "rport %s"%[host['inet_port']]
+ conffile.puts "lport %s"%[peer['inet_port']]
+ conffile.puts "remote %s"%[peer['host_address']] if peer['host_address']
+ conffile.puts "local %s"%[host['host_address']] if host['host_address']
+ if host_is_server
+ conffile.puts "tls-server"
+ conffile.puts "dh noreply2.dh2048.pem"
+ else
+ conffile.puts "tls-client"
+ end
+ conffile.puts "tls-remote %s.%s"%[peer['name'], $config['cadndomain']]
+ if host['pkcs12']
+ conffile.puts "pkcs12 noreply2-%s.%s.p12"%[host['name'], $config['cadndomain']]
+ else
+ conffile.puts "ca noreply2-CA.crt"
+ conffile.puts "cert noreply2-%s.%s.crt"%[host['name'], $config['cadndomain']]
+ conffile.puts "key noreply2-%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 10 30"
+ conffile.puts "verb 0"
+ 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.close
+ File.chmod(0755, upscriptname) == 1 or throw "Cannot chmod #{upscriptname}"
+
+ ##########
+ iptables.puts "iptables --append vpn-noreply2 --source #{ peer['host_address'] || '0.0.0.0/0' } --destination #{ host['host_address'] || '0.0.0.0/0' } \\"
+ iptables.puts " --protocol udp --destination-port #{ peer['inet_port'] } \\"
+ iptables.puts " --jump ACCEPT"
+
+ iptables.puts "iptables --append vpn-noreply2 --source #{ peer['vpn_address'] } --destination #{ host['vpn_address'] } \\"
+ iptables.puts " --protocol tcp --destination-port #{ host['bgp_port'] or 'bgp' } \\"
+ iptables.puts " --in-interface #{ peer['ifacename'] } \\"
+ iptables.puts " --jump ACCEPT"
+ if host['ipv6'] and peer['ipv6']
+ ip6tables.puts "ip6tables --append vpn-noreply2 \\"
+ 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 'bgp' } \\"
+ ip6tables.puts " --in-interface #{ peer['ifacename'] } \\"
+ ip6tables.puts " --jump ACCEPT"
+ end
+
+ ##########
+ bgpd.puts "!"
+ bgpd.puts "! ** peer #{peer['name']} **"
+ unless host['ipv6'] and peer['ipv6']
+ bgpd.puts " neighbor #{peer['vpn_address']} remote-as #{peer['asn']}"
+ bgpd.puts " neighbor #{peer['vpn_address']} send-community"
+ bgpd.puts " neighbor #{peer['vpn_address']} interface #{peer['ifacename']}"
+ bgpd.puts " neighbor #{peer['vpn_address']} description #{peer['name']}"
+ bgpd.puts " neighbor #{peer['vpn_address']} 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"
+ 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'] } remote-as #{peer['asn']}"
+ bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } send-community"
+ bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } interface #{peer['ifacename']}"
+ bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } description #{peer['name']}"
+ bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } port #{peer['bgp_port']}" if peer.has_key?('bgp_port')
+ 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 " exit-address-family"
+ end
+ }
+
+ #ipv6stuff << " exit-address-family"
+ #bgpd.puts ipv6stuff.join("\n")
+
+ iptables.close
+ ip6tables.close
+ bgpd.close
+
+ File.chmod(0600, bgpdfilename) == 1 or throw "Cannot chmod #{bgpdfilename}"
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9ebbc7c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+all:
+ rm -rf noreply2-* *.p12
+ ./Generate
+
+clean:
+ rm -rf noreply2-* *.p12
diff --git a/Push b/Push
new file mode 100755
index 0000000..9b9846f
--- /dev/null
+++ b/Push
@@ -0,0 +1,122 @@
+#!/usr/bin/ruby
+
+require 'yaml';
+
+$config = YAML::load( File.open( 'Hosts' ) )
+
+def sys(command)
+ puts "} #{command}"
+ system(command) or throw "Command '#{command}' failed"
+end
+
+
+def getFilesMD5(host, ssh_hostname)
+ cmd = ssh_hostname ?
+ "ssh root@#{ssh_hostname} /bin/ls -1 /etc/openvpn" :
+ "ls -1 noreply2-#{host}"
+ puts "| " + cmd
+ f = IO.popen( cmd, aModeString="r" );
+ files = f.readlines.delete_if{|e| not e =~ /^noreply2[.-]/ }.collect{|e| e.chomp}
+ f.close
+
+ md5 = {}
+ if (files.length > 0)
+ cmd = ssh_hostname ?
+ "ssh root@#{ssh_hostname} 'cd /etc/openvpn && md5sum " + files.join(' ') + "'" :
+ "cd noreply2-#{host} && md5sum " + files.join(' ')
+ puts "| " + cmd
+ f = IO.popen( cmd, aModeString="r" );
+ f.each_line{ |l|
+ l.chomp!
+ (h,file) = l.split(/\s+/,2)
+ md5[file] = h
+ }
+ f.close
+ end
+
+ return md5
+end
+
+hosts = ARGV
+if hosts.size == 0
+ hosts = $config['hosts'].keys
+end
+hosts.each{ |hostname|
+ puts "Pushing to #{hostname}."
+ throw "Host #{hostname} not in config" unless $config['hosts'][hostname]
+ ssh_hostnames = $config['hosts'][hostname]['ssh_hostname']
+ unless ssh_hostnames
+ STDERR.puts "*** #{hostname} has no ssh_hostname!"
+ next
+ end
+
+ ssh_hostname = nil
+ ssh_hostnames.delete(' ').split(',').each{ |host|
+ if not system("ping -W2 -c3 #{host}")
+ STDERR.puts "*** #{host} is not reachable!"
+ next
+ else
+ ssh_hostname = host
+ break
+ end
+ }
+ unless ssh_hostname
+ STDERR.puts "*** no reachable host found for #{hostname}!"
+ next
+ end
+
+
+ remote = getFilesMD5(hostname, ssh_hostname)
+ local = getFilesMD5(hostname, nil)
+
+ delete = []
+ copy = []
+ stop = []
+ restart = []
+ do_iptables = nil
+ do_ip6tables = nil
+ do_quagga = nil
+ remote.each_pair{|f,h|
+ if ! local.has_key?(f)
+ delete << f
+ if f =~ /\.conf$/
+ stop << f.gsub(/\.conf$/, '')
+ end
+ end
+ }
+ local.each_pair{|f,h|
+ if ! remote.has_key?(f) || local[f] != remote[f]
+ copy << f
+ if f =~ /\.iptables\.sh$/
+ do_iptables = f
+ elsif f =~ /\.ip6tables\.sh$/
+ do_ip6tables = f
+ elsif f =~ /\.quagga\.(bgpd|zebra|daemons)$/
+ do_quagga = true
+ elsif f =~ /\.conf$/
+ restart << f.gsub(/\.conf$/, '')
+ end
+ end
+ }
+
+ dir = "noreply2-#{hostname}/"
+ sys("scp "+copy.collect{|f| dir+f }.join(' ')+" root@#{ssh_hostname}:/etc/openvpn/") if copy.size > 0
+ commands = []
+ commands << "sh ./#{do_iptables} &&\n" if do_iptables
+ commands << "sh ./#{do_ip6tables} &&\n" if do_ip6tables
+ commands << "/etc/init.d/openvpn stop "+stop.join(' ')+" &&\n" if stop.size > 0
+ commands << "/etc/init.d/openvpn restart "+restart.join(' ')+" && \n" if restart.size > 0
+ commands << "rm "+delete.join(' ')+" &&\n" if delete.size > 0
+ command = "cd /etc/openvpn && \n";
+ command << commands.join('')
+ command << "echo 'all done'"
+ sys("ssh root@#{ssh_hostname} '#{command}'") if commands.size > 0
+ if (do_quagga)
+ sys("ssh root@#{ssh_hostname} 'cp -a /etc/openvpn/noreply2.quagga.bgpd /etc/quagga/bgpd.conf &&
+ cp -a /etc/openvpn/noreply2.quagga.zebra /etc/quagga/zebra.conf &&
+ cp -a /etc/openvpn/noreply2.quagga.daemons /etc/quagga/daemons &&
+ chmod 640 /etc/quagga/bgpd.conf /etc/quagga/zebra.conf &&
+ chgrp quagga /etc/quagga/bgpd.conf /etc/quagga/zebra.conf &&
+ /etc/init.d/quagga restart'")
+ end
+}