--- /dev/null
+classes:
+ - profile::ipsec::fasolo_storace
--- /dev/null
+classes:
+ - profile::ipsec::fasolo_storace
+# 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: }
}
--- /dev/null
+# 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: }
+}
--- /dev/null
+# 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,
+ }
+}
--- /dev/null
+# 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",
+ }
+}
+++ /dev/null
-##
-## 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;
- }
-}
+++ /dev/null
-##
-## 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")
-
-%>
+++ /dev/null
-##
-## 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")
-
-%>
--- /dev/null
+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
+
+ }
+
+ }
+
+}
+
--- /dev/null
+# 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,
+ }
+}