From dd128c4ffc861f6c3bb480f6098fb46014fc12cf Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Fri, 30 Aug 2019 12:10:15 +0200 Subject: [PATCH] Move the single ipsec tunnel we have to my new system. There are named ipsec "networks". And any host that is in a named network will set up ipsec to all the other hosts on that network. A host can be on more than one network at a time. Currently we only have the fasolo-storace tunnel, though. It is configured in modules/profile/manifests/ipsec/fasolo_storace.pp. --- hieradata/nodes/fasolo.debian.org.yaml | 2 + hieradata/nodes/storace.debian.org.yaml | 2 + modules/ipsec/manifests/init.pp | 119 +++++++++--------- modules/ipsec/manifests/init.pp.orig | 67 ++++++++++ modules/ipsec/manifests/network.pp | 50 ++++++++ modules/ipsec/manifests/peer.pp | 66 ++++++++++ modules/ipsec/templates/ferm.erb | 28 ----- .../ipsec.conf-10-puppet-peers.conf.erb | 48 ------- .../ipsec.secrets-10-puppet-peers.secrets.erb | 27 ---- .../templates/strongswan-charon-logging.conf | 75 +++++++++++ .../profile/manifests/ipsec/fasolo_storace.pp | 15 +++ 11 files changed, 338 insertions(+), 161 deletions(-) create mode 100644 hieradata/nodes/fasolo.debian.org.yaml create mode 100644 hieradata/nodes/storace.debian.org.yaml create mode 100644 modules/ipsec/manifests/init.pp.orig create mode 100644 modules/ipsec/manifests/network.pp create mode 100644 modules/ipsec/manifests/peer.pp delete mode 100644 modules/ipsec/templates/ferm.erb delete mode 100644 modules/ipsec/templates/ipsec.conf-10-puppet-peers.conf.erb delete mode 100644 modules/ipsec/templates/ipsec.secrets-10-puppet-peers.secrets.erb create mode 100644 modules/ipsec/templates/strongswan-charon-logging.conf create mode 100644 modules/profile/manifests/ipsec/fasolo_storace.pp diff --git a/hieradata/nodes/fasolo.debian.org.yaml b/hieradata/nodes/fasolo.debian.org.yaml new file mode 100644 index 000000000..ebfdc3e15 --- /dev/null +++ b/hieradata/nodes/fasolo.debian.org.yaml @@ -0,0 +1,2 @@ +classes: + - profile::ipsec::fasolo_storace diff --git a/hieradata/nodes/storace.debian.org.yaml b/hieradata/nodes/storace.debian.org.yaml new file mode 100644 index 000000000..ebfdc3e15 --- /dev/null +++ b/hieradata/nodes/storace.debian.org.yaml @@ -0,0 +1,2 @@ +classes: + - profile::ipsec::fasolo_storace diff --git a/modules/ipsec/manifests/init.pp b/modules/ipsec/manifests/init.pp index 6952c0656..5464f780c 100644 --- a/modules/ipsec/manifests/init.pp +++ b/modules/ipsec/manifests/init.pp @@ -1,64 +1,67 @@ +# basic ipsec configuration +# +# this configures all packages and required kernel modules, but +# doesn't configure any host, see `ipsec::network` instead. +# +# when first loaded, this will add a list of modules to the kernel, +# but this will only load on reboot. class ipsec { - $ipsec_config = @(EOF) - --- + package { [ + 'strongswan', + 'libstrongswan-standard-plugins' + ]: + ensure => installed + } - storace.debian.org: - address: 93.94.130.161 + service { 'ipsec': + ensure => running, + } - fasolo.debian.org: - address: 138.16.160.17 + file { + '/etc/ipsec.conf': + content => template('ipsec/ipsec.conf.erb'), + notify => Service['ipsec']; + '/etc/ipsec.secrets': + mode => '0400', + content => template('ipsec/ipsec.secrets.erb'), + notify => Service['ipsec']; + '/etc/ipsec.conf.d': + ensure => 'directory', + purge => true, + force => true, + recurse => true, + mode => '0755'; + '/etc/ipsec.secrets.d': + ensure => 'directory', + purge => true, + force => true, + recurse => true, + mode => '0700'; + '/etc/ipsec.conf.d/00-default.conf': + content => template('ipsec/ipsec.conf-00-default.conf.erb'), + notify => Service['ipsec']; + '/etc/strongswan.d/charon-logging.conf': + content => template('ipsec/strongswan-charon-logging.conf'), + notify => Service['ipsec']; + } - | EOF + ferm::rule { + 'ipsec-peers': + description => 'ipsec protocols are allowed from the ipsec peers', + domain => '(ip ip6)', + chain => 'ipsec-peers', + rule => 'DROP', + prio => 'zzz-999'; + 'ipsec': + description => 'ipsec protocols are allowed from the ipsec peers', + domain => '(ip ip6)', + rule => @(EOF), + proto udp dport (isakmp 4500) jump ipsec-peers; + proto esp jump ipsec-peers + | EOF + } - package { [ - 'strongswan', - 'libstrongswan-standard-plugins' - ]: - ensure => installed - } - - service { 'ipsec': - ensure => running, - } - - file { '/etc/ipsec.conf': - content => template("ipsec/ipsec.conf.erb"), - notify => Service['ipsec'], - } - file { '/etc/ipsec.secrets': - mode => '0400', - content => template("ipsec/ipsec.secrets.erb"), - notify => Service['ipsec'], - } - - file { '/etc/ipsec.conf.d': - mode => '0755', - ensure => 'directory', - } - file { '/etc/ipsec.secrets.d': - ensure => 'directory', - mode => '0700', - } - - file { '/etc/ipsec.conf.d/00-default.conf': - content => template("ipsec/ipsec.conf-00-default.conf.erb"), - notify => Service['ipsec'], - } - - file { '/etc/ipsec.conf.d/10-puppet-peers.conf': - content => template("ipsec/ipsec.conf-10-puppet-peers.conf.erb"), - notify => Service['ipsec'], - } - file { '/etc/ipsec.secrets.d/10-puppet-peers.secrets': - mode => '0400', - content => template("ipsec/ipsec.secrets-10-puppet-peers.secrets.erb"), - notify => Service['ipsec'], - } - - file { - "/etc/ferm/dsa.d/10-ipsec": - mode => '0400', - content => template("ipsec/ferm.erb"), - notify => Exec['ferm reload'], - } + # Since we disable module loading after boot, we want to load them all at boot time + $modules = split('af_alg af_key ah4 algif_skcipher ansi_cprng authenc drbg echainiv esp4 ipcomp macvlan macvtap tunnel4 vhost vhost_net xfrm4_mode_tunnel xfrm4_tunnel xfrm6_mode_tunnel xfrm_algo xfrm_ipcomp xfrm_user', ' ') # lint:ignore:140chars + site::linux_module { $modules: } } diff --git a/modules/ipsec/manifests/init.pp.orig b/modules/ipsec/manifests/init.pp.orig new file mode 100644 index 000000000..d20860c8b --- /dev/null +++ b/modules/ipsec/manifests/init.pp.orig @@ -0,0 +1,67 @@ +# basic ipsec configuration +# +# this configures all packages and required kernel modules, but +# doesn't configure any host, see `ipsec::network` instead. +# +# when first loaded, this will add a list of modules to the kernel, +# but this will only load on reboot. +class ipsec { + package { [ + 'strongswan', + 'libstrongswan-standard-plugins' + ]: + ensure => installed + } + + service { 'ipsec': + ensure => running, + } + + file { + '/etc/ipsec.conf': + content => template('ipsec/ipsec.conf.erb'), + notify => Service['ipsec']; + '/etc/ipsec.secrets': + mode => '0400', + content => template('ipsec/ipsec.secrets.erb'), + notify => Service['ipsec']; + '/etc/ipsec.conf.d': + ensure => 'directory', + purge => true, + force => true, + recurse => true, + mode => '0755'; + '/etc/ipsec.secrets.d': + ensure => 'directory', + purge => true, + force => true, + recurse => true, + mode => '0700'; + '/etc/ipsec.conf.d/00-default.conf': + content => template('ipsec/ipsec.conf-00-default.conf.erb'), + notify => Service['ipsec']; + '/etc/strongswan.d/charon-logging.conf': + content => template('ipsec/strongswan-charon-logging.conf'), + notify => Service['ipsec']; + } + + ferm::rule { + 'ipsec-peers': + description => 'ipsec protocols are allowed from the ipsec peers', + domain => '(ip ip6)', + chain => 'ipsec-peers', + rule => 'DROP', + prio => 'zzz-999'; + 'ipsec': + description => 'ipsec protocols are allowed from the ipsec peers', + domain => '(ip ip6)', + rule => @(EOF), + proto udp dport (isakmp 4500) jump ipsec-peers; + proto esp jump ipsec-peers + | EOF + } + + # Since we disable module loading after boot, we want to load them all at boot time + $modules = split('af_alg af_key ah4 algif_skcipher ansi_cprng authenc drbg echainiv esp4 ipcomp macvlan macvtap tunnel4 vhost vhost_net xfrm4_mode_tunnel xfrm4_tunnel xfrm6_mode_tunnel xfrm_algo xfrm_ipcomp xfrm_user', ' ') # lint:ignore:140chars + base::linux_module { $modules: } +} diff --git a/modules/ipsec/manifests/network.pp b/modules/ipsec/manifests/network.pp new file mode 100644 index 000000000..b5d6979dd --- /dev/null +++ b/modules/ipsec/manifests/network.pp @@ -0,0 +1,50 @@ +# make this node a member of a common ipsec network +# +# the name of this resource a tag for an network where nodes in the +# same "network" will have ipsec set up between them. +# +# This is sufficient to setup a tunnel between a cluster of machines, +# but requires a reboot, see the parent ipsec class. +# +# WARNING: default ipsec configuration tunnels only the IP address +# given, which means that this default configuration only tunnels +# IPv4, not IPv6. +# +# Use $peer_networks = [ "${::ipaddress}/32", "${::ipaddress6}/128" ] +# to tunnel both addresses. +define ipsec::network ( + Stdlib::IP::Address $peer_ipaddress = $::ipaddress, + Array[Stdlib::IP::Address] $peer_networks = [], +) { + include ipsec + + $ipsec_conf_file = "/etc/ipsec.conf.d/10-puppet-${name}.conf" + $ipsec_secrets_file = "/etc/ipsec.secrets.d/10-puppet-${name}.secrets" + $stored_conftag = "ipsec::peer::${name}" + + @@ipsec::peer{ "${name}-${::hostname}": + network_name => $name, + peer_name => $::hostname, + peer_ipaddress => $peer_ipaddress, + peer_networks => $peer_networks, + ipsec_conf_file => $ipsec_conf_file, + ipsec_secrets_file => $ipsec_secrets_file, + tag => $stored_conftag, + # those will be overriden on collection, below + local_name => undef, + local_ipaddress => undef, + } + + concat { $ipsec_conf_file: + notify => Service['ipsec'], + } + concat { $ipsec_secrets_file: + notify => Service['ipsec'], + mode => '0400', + } + Ipsec::Peer <<| tag == $stored_conftag and peer_name != $::hostname|>> { + local_name => $::hostname, + local_ipaddress => $peer_ipaddress, + local_networks => $peer_networks, + } +} diff --git a/modules/ipsec/manifests/peer.pp b/modules/ipsec/manifests/peer.pp new file mode 100644 index 000000000..fbbc8ac9b --- /dev/null +++ b/modules/ipsec/manifests/peer.pp @@ -0,0 +1,66 @@ +# an ipsec peer, another node to connect to +define ipsec::peer( + $ipsec_conf_file, + $ipsec_secrets_file, + + $local_name, + $local_ipaddress, + $peer_name, + $peer_ipaddress, + + $local_networks = [], + $peer_networks = [], + + $network_name = 'ipsec', +) { + $leftsubnet = $local_networks ? { + [] => '', + default => "leftsubnet = ${$local_networks.join(', ')}" + } + $rightsubnet = $peer_networks ? { + [] => '', + default => "rightsubnet = ${$peer_networks.join(', ')}" + } + concat::fragment { "${network_name}::${ipsec_conf_file}::${name}": + target => $ipsec_conf_file, + content => @("EOF"), + # peer ${name} + conn ${network_name}::${peer_name} + # left is us (local, ${local_name}) + left = ${local_ipaddress} + ${leftsubnet} + + # right is our peer (remote, ${peer_name}) + right = ${peer_ipaddress} + ${rightsubnet} + + auto=route + | EOF + } + + # create the data portion for the key derivation function + # + # It needs to be the same data on both ends of a connection, so the + # corresponding secrets entry at the peer gets the same PSK. We do + # this by putting the peer's info and our info in some arbitrary, + # yet canonical order by sorting. + $ipsec_psk_data = ("${local_name}(${local_ipaddress})" < "${peer_name}(${peer_ipaddress})") ? { + true => "ipsec-peer-psk-${network_name}-${local_name}(${local_ipaddress})-${peer_name}(${peer_ipaddress})", + false => "ipsec-peer-psk-${network_name}-${peer_name}(${peer_ipaddress})-${local_name}(${local_ipaddress})" + } + $ipsec_psk = hkdf('/etc/puppet/secret', $ipsec_psk_data) + concat::fragment { "${network_name}::${ipsec_secrets_file}::${name}": + target => $ipsec_secrets_file, + content => @("EOF"), + # peer ${peer_name} + ${peer_ipaddress} : PSK "${ipsec_psk}" + | EOF + } + + ferm::rule { "${network_name}-${name}": + description => "allow ipsec protocols for peer ${peer_name}", + domain => '(ip ip6)', + chain => 'ipsec-peers', + rule => "saddr ${peer_ipaddress} ACCEPT", + } +} diff --git a/modules/ipsec/templates/ferm.erb b/modules/ipsec/templates/ferm.erb deleted file mode 100644 index 82b8a6bcb..000000000 --- a/modules/ipsec/templates/ferm.erb +++ /dev/null @@ -1,28 +0,0 @@ -## -## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. -## - -<% -config = YAML.load(@ipsec_config) - -unless config.keys.include?(@fqdn) then - fail("Host #{@fqdn} not found in ipsec config.") -end - -peers = [] -config.keys.each do |host| - next if @fqdn == host - peers << config[host]['address'] -end -%> - -domain ip table filter { - chain ipsec-peers { - saddr (<%= peers.join(" ") %>) ACCEPT; - } - - chain INPUT { - proto udp dport (isakmp) jump ipsec-peers; - proto esp jump ipsec-peers; - } -} diff --git a/modules/ipsec/templates/ipsec.conf-10-puppet-peers.conf.erb b/modules/ipsec/templates/ipsec.conf-10-puppet-peers.conf.erb deleted file mode 100644 index 43a234594..000000000 --- a/modules/ipsec/templates/ipsec.conf-10-puppet-peers.conf.erb +++ /dev/null @@ -1,48 +0,0 @@ -## -## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. -## - -<%= - -lines = [] - -config = YAML.load(@ipsec_config) - -unless config.keys.include?(@fqdn) then - fail("Host #{@fqdn} not found in ipsec config.") -end - -config.keys.each do |host| - next if @fqdn == host - - pair = [@fqdn, host] - pair.sort! - connname = pair.join('-') - - lines << "conn #{connname}" - lines << " # left is us (local): #{@fqdn}" - lines << " left = #{config[@fqdn]['address']}" - - lines << " # right is our peer (remote): #{host}" - lines << " right = #{config[host]['address']}" - - if config[@fqdn].include?('subnet') or config[host].include?('subnet') - lines << " type = tunnel" - if config[@fqdn].include?('subnet') - lines << " leftsubnet = #{config[@fqdn]['subnet'].join(', ')}" - end - if config[host].include?('subnet') - lines << " rightsubnet = #{config[host]['subnet'].join(', ')}" - end - else - lines << " type = transport" - end - lines << "" - lines << " #auto=start" - lines << " #closeaction=restart" - lines << " auto=route" - lines << "" -end -lines.join("\n") - -%> diff --git a/modules/ipsec/templates/ipsec.secrets-10-puppet-peers.secrets.erb b/modules/ipsec/templates/ipsec.secrets-10-puppet-peers.secrets.erb deleted file mode 100644 index 8bd790dfb..000000000 --- a/modules/ipsec/templates/ipsec.secrets-10-puppet-peers.secrets.erb +++ /dev/null @@ -1,27 +0,0 @@ -## -## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. -## - -<%= - -lines = [] - -config = YAML.load(@ipsec_config) - -unless config.keys.include?(@fqdn) then - fail("Host #{@fqdn} not found in ipsec config.") -end - -config.keys.each do |host| - next if @fqdn == host - - pair = [@fqdn, host] - pair.sort! - connname = pair.join('-') - key = scope.function_hkdf(['/etc/puppet/secret', "puppet-key-ipsec:PSK:tor:#{connname}"]) - - lines << "#{config[pair[0]]['address']} #{config[pair[1]]['address']} : PSK \"#{key}\"" -end -lines.join("\n") - -%> diff --git a/modules/ipsec/templates/strongswan-charon-logging.conf b/modules/ipsec/templates/strongswan-charon-logging.conf new file mode 100644 index 000000000..6ce8a87b5 --- /dev/null +++ b/modules/ipsec/templates/strongswan-charon-logging.conf @@ -0,0 +1,75 @@ +charon { + + # Section to define file loggers, see LOGGER CONFIGURATION in + # strongswan.conf(5). + filelog { + + # may be the full path to the log file if it only contains + # characters permitted in section names. Is ignored if path is + # specified. + # { + + # Loglevel for a specific subsystem. + # = + + # If this option is enabled log entries are appended to the existing + # file. + # append = yes + + # Default loglevel. + # default = 1 + + # Enabling this option disables block buffering and enables line + # buffering. + # flush_line = no + + # Prefix each log entry with the connection name and a unique + # numerical identifier for each IKE_SA. + # ike_name = no + + # Optional path to the log file. Overrides the section name. Must be + # used if the path contains characters that aren't allowed in + # section names. + # path = + + # Adds the milliseconds within the current second after the + # timestamp (separated by a dot, so time_format should end with %S + # or %T). + # time_add_ms = no + + # Prefix each log entry with a timestamp. The option accepts a + # format string as passed to strftime(3). + # time_format = + + # } + + } + + # Section to define syslog loggers, see LOGGER CONFIGURATION in + # strongswan.conf(5). + syslog { + + # Identifier for use with openlog(3). + # identifier = + + # is one of the supported syslog facilities, see LOGGER + # CONFIGURATION in strongswan.conf(5). + daemon { + + # Loglevel for a specific subsystem. + # = + + # Default loglevel. + # default = 1 + default = 0 + + # Prefix each log entry with the connection name and a unique + # numerical identifier for each IKE_SA. + # ike_name = no + + } + + } + +} + diff --git a/modules/profile/manifests/ipsec/fasolo_storace.pp b/modules/profile/manifests/ipsec/fasolo_storace.pp new file mode 100644 index 000000000..b2d91530a --- /dev/null +++ b/modules/profile/manifests/ipsec/fasolo_storace.pp @@ -0,0 +1,15 @@ +# the ipsec tunnel between fasolo and storace, so that our backups work +# despite all the evil firewalls that might try to block our traffic. +class profile::ipsec::fasolo_storace { + + # Use the first ipv4 address from LDAP, since the puppet fact is not always + # the IP address we want to use. For instance, for storace $::facts['ipaddress'] + # is 172.29.170.1 (from bond1) instead of 93.94.130.161 from eth0. + $public_ipaddresses = getfromhash($site::nodeinfo, 'misc', 'v4_ldap') + $public_ipaddress = public_ipaddresses[0] + + # we do ipsec on the backend since it traveres over other people's switching infra + ipsec::network { "fasolo_storace": + peer_ipaddress => $public_ipaddress, + } +} -- 2.20.1