Move the single ipsec tunnel we have to my new system.
authorPeter Palfrader <peter@palfrader.org>
Fri, 30 Aug 2019 10:10:15 +0000 (12:10 +0200)
committerPeter Palfrader <peter@palfrader.org>
Fri, 30 Aug 2019 10:14:27 +0000 (12:14 +0200)
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 [new file with mode: 0644]
hieradata/nodes/storace.debian.org.yaml [new file with mode: 0644]
modules/ipsec/manifests/init.pp
modules/ipsec/manifests/init.pp.orig [new file with mode: 0644]
modules/ipsec/manifests/network.pp [new file with mode: 0644]
modules/ipsec/manifests/peer.pp [new file with mode: 0644]
modules/ipsec/templates/ferm.erb [deleted file]
modules/ipsec/templates/ipsec.conf-10-puppet-peers.conf.erb [deleted file]
modules/ipsec/templates/ipsec.secrets-10-puppet-peers.secrets.erb [deleted file]
modules/ipsec/templates/strongswan-charon-logging.conf [new file with mode: 0644]
modules/profile/manifests/ipsec/fasolo_storace.pp [new file with mode: 0644]

diff --git a/hieradata/nodes/fasolo.debian.org.yaml b/hieradata/nodes/fasolo.debian.org.yaml
new file mode 100644 (file)
index 0000000..ebfdc3e
--- /dev/null
@@ -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 (file)
index 0000000..ebfdc3e
--- /dev/null
@@ -0,0 +1,2 @@
+classes:
+        - profile::ipsec::fasolo_storace
index 6952c06..5464f78 100644 (file)
@@ -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 (file)
index 0000000..d20860c
--- /dev/null
@@ -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 (file)
index 0000000..b5d6979
--- /dev/null
@@ -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 (file)
index 0000000..fbbc8ac
--- /dev/null
@@ -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 (file)
index 82b8a6b..0000000
+++ /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 (file)
index 43a2345..0000000
+++ /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 (file)
index 8bd790d..0000000
+++ /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 (file)
index 0000000..6ce8a87
--- /dev/null
@@ -0,0 +1,75 @@
+charon {
+
+    # Section to define file loggers, see LOGGER CONFIGURATION in
+    # strongswan.conf(5).
+    filelog {
+
+        # <name> may be the full path to the log file if it only contains
+        # characters permitted in section names. Is ignored if path is
+        # specified.
+        # <name> {
+
+            # Loglevel for a specific subsystem.
+            # <subsystem> = <default>
+
+            # 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 =
+
+        # <facility> is one of the supported syslog facilities, see LOGGER
+        # CONFIGURATION in strongswan.conf(5).
+        daemon {
+
+            # Loglevel for a specific subsystem.
+            # <subsystem> = <default>
+
+            # 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 (file)
index 0000000..b2d9153
--- /dev/null
@@ -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,
+  }
+}