From d0d7370629bf48a00454ba89723b9ae8f3e15306 Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Sat, 7 Jan 2006 14:55:37 +0000 Subject: Add files git-svn-id: svn+ssh://asteria.noreply.org/svn/weaselutils/trunk@41 bc3d92e2-beff-0310-a7cd-cc87d7ac0ede --- Generate | 667 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 6 + Push | 122 ++++++++++++ 3 files changed, 795 insertions(+) create mode 100755 Generate create mode 100644 Makefile create mode 100755 Push 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 = "" +#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. +# +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 (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['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 +} -- cgit v1.2.3