diff --git a/itamae/cookbooks/systemd-container/default.rb b/itamae/cookbooks/systemd-container/default.rb new file mode 100644 index 0000000..723c6f7 --- /dev/null +++ b/itamae/cookbooks/systemd-container/default.rb @@ -0,0 +1,14 @@ +package 'systemd-container' +package 'mmdebstrap' + +directory '/var/lib/machines' do + owner 'root' + group 'root' + mode '0700' +end + +directory '/etc/systemd/nspawn' do + owner 'root' + group 'root' + mode '0755' +end diff --git a/itamae/cookbooks/tpm/default.rb b/itamae/cookbooks/tpm/default.rb new file mode 100644 index 0000000..a24a68c --- /dev/null +++ b/itamae/cookbooks/tpm/default.rb @@ -0,0 +1 @@ +package 'tpm2-tools' diff --git a/itamae/hosts/wire-01.venue.rubykaigi.net/default.rb b/itamae/hosts/wire-01.venue.rubykaigi.net/default.rb new file mode 100644 index 0000000..d5cab14 --- /dev/null +++ b/itamae/hosts/wire-01.venue.rubykaigi.net/default.rb @@ -0,0 +1,53 @@ +node.reverse_merge!( + systemd_networkd: { + migrate_netplan: false, + }, + wire: { + interfaces: { + loopback: { + address4: %w[10.33.0.41], + address6: %w[2001:df0:8500:ca00::41], + }, + management: { + name: 'me0', + path: '*14.0-usb-0:*:1.0', + duid: '00:00:ba:2c:33:00:41', + }, + overlay: { + name: 'enp3s0', + ipv6_token: 'static:::8888:aaaa:0:1', + }, + downstream: { + name: 'enp2s0', + # local_as: 65026, + # peer_as: 65030, + # link4: { + # local: '10.33.22.64', + # peer: '10.33.22.65', + # }, + # link6: { + # local: '2001:df0:8500:ca22:64::a', + # peer: '2001:df0:8500:ca22:64::b', + # }, + }, + }, + tunnels: { + wg_wire03: { + listen_port: 8703, + peer_endpoint: 'rknet-wire-03.i.open.ad.jp:8701', + peer_public_key: 'TADiMxPz1fqm5LjWH44/Q1kpt9XkAyl+umB8OEJgoGA=', # dummy + link4: { + local: '10.33.22.90', + peer: '10.33.22.91', + }, + link6: { + local: '2001:df0:8500:ca22:90::a', + peer: '2001:df0:8500:ca22:90::b', + }, + }, + }, + }, +) + +include_role 'wire' +include_cookbook 'bluetooth-getty' diff --git a/itamae/roles/wire/default.rb b/itamae/roles/wire/default.rb new file mode 100644 index 0000000..5e84515 --- /dev/null +++ b/itamae/roles/wire/default.rb @@ -0,0 +1,75 @@ +node.reverse_merge!( + systemd_networkd: { + manage_foreign_routes: false, + }, + wire: { + }, + bird: { + router_id: [*node.dig(:wire, :interfaces, :loopback).fetch(:address4)].first, + }, +) + +include_role 'base' +include_cookbook 'ruby' + +include_cookbook 'cpufreq' +#include_cookbook 'nftables' + +package 'wireguard-tools' + + +%w[ + 00-lo.network +].each do |fname| + template "/etc/systemd/network/#{fname}" do + owner 'root' + group 'root' + mode '0644' + notifies :restart, 'service[systemd-networkd]' + end +end + +if node.dig(:wire, :interfaces, :management) + %w[ + 00-management.network + 00-management.link + ].each do |fname| + template "/etc/systemd/network/#{fname}" do + owner 'root' + group 'root' + mode '0644' + notifies :restart, 'service[systemd-networkd]' + end + end +end + +file "/etc/wire.json" do + content "#{JSON.pretty_generate(node[:wire])}\n" + owner 'root' + group 'root' + mode '0644' +end + +include_recipe './key.rb' +include_recipe './overlay.rb' +include_recipe './wireguard.rb' + +service 'systemd-nspawn@overlay.service' do + action [:enable, :start] +end + + + +# template '/etc/nftables/plat.conf' do +# owner 'root' +# group 'root' +# mode '0644' +# notifies :reload, 'service[nftables]' +# end +# +# template '/etc/bird/bird.conf.d/plat.conf' do +# owner 'root' +# group 'bird' +# mode '0644' +# notifies :reload, 'service[bird]' +# end diff --git a/itamae/roles/wire/key.rb b/itamae/roles/wire/key.rb new file mode 100644 index 0000000..f066a22 --- /dev/null +++ b/itamae/roles/wire/key.rb @@ -0,0 +1,6 @@ +include_cookbook 'tpm' + +execute 'wg genkey' do + command "touch /etc/wire.key; chmod 600 /etc/wire.key; wg genkey|sudo systemd-creds encrypt --name network.wireguard.private.default --with-key 'host+tpm2' - /etc/wire.key" + not_if 'test -e /etc/wire.key' +end diff --git a/itamae/roles/wire/overlay.rb b/itamae/roles/wire/overlay.rb new file mode 100644 index 0000000..c308d19 --- /dev/null +++ b/itamae/roles/wire/overlay.rb @@ -0,0 +1,70 @@ +include_cookbook 'systemd-container' + +execute 'mmdebstrap for overlay' do + command <<~EOF + set -xe + mmdebstrap \ + --include=dbus,libpam-systemd,libnss-systemd,systemd,systemd-resolved,iproute2,iputils-ping,curl,wireguard-tools \ + --dpkgopt='path-exclude=/usr/share/man/*' \ + --dpkgopt='path-include=/usr/share/man/man[1-9]/*' \ + --dpkgopt='path-exclude=/usr/share/locale/*' \ + --dpkgopt='path-include=/usr/share/locale/locale.alias' \ + --dpkgopt='path-exclude=/usr/share/doc/*' \ + --dpkgopt='path-include=/usr/share/doc/*/copyright' \ + --dpkgopt='path-include=/usr/share/doc/*/changelog.Debian.*' \ + --variant=essential "$(lsb_release -sc)" /root/overlay.tar + importctl import-tar --class=machine /root/overlay.tar overlay + EOF + not_if 'test -e /var/lib/machines/overlay/etc/hostname' +end + +template '/etc/systemd/nspawn/overlay.nspawn' do + owner 'root' + group 'root' + mode '0644' +end + +directory '/etc/systemd/system/systemd-nspawn@overlay.service.d' do + owner 'root' + group 'root' + mode '0755' +end + +template '/etc/systemd/system/systemd-nspawn@overlay.service.d/dropin.conf' do + owner 'root' + group 'root' + mode '0644' + notifies :run, 'execute[systemctl daemon-reload]', :immediately +end + +directory '/var/lib/machines/overlay/etc/systemd/network' do + owner 'root' + group 'root' + mode '0755' +end + +template '/var/lib/machines/overlay/etc/systemd/network/00-overlay.network' do + owner 'root' + group 'root' + mode '0644' +end + +execute 'chroot /var/lib/machines/overlay systemctl enable systemd-networkd.service' do + not_if 'chroot /var/lib/machines/overlay systemctl is-enabled systemd-networkd.service' +end + +execute 'chroot /var/lib/machines/overlay systemctl enable systemd-resolved.service' do + not_if 'chroot /var/lib/machines/overlay systemctl is-enabled systemd-resolved.service' +end + +execute 'chroot /var/lib/machines/overlay systemctl enable systemd-networkd-wait-online.service' do + not_if 'chroot /var/lib/machines/overlay systemctl is-enabled systemd-networkd-wait-online.service' +end + +execute 'chroot /var/lib/machines/overlay systemctl enable network-online.target' do + not_if 'chroot /var/lib/machines/overlay systemctl is-enabled network-online.target' +end + +service 'systemd-nspawn@overlay.service' do + action [:enable, :start] +end diff --git a/itamae/roles/wire/templates/etc/systemd/network/00-lo.network b/itamae/roles/wire/templates/etc/systemd/network/00-lo.network new file mode 100644 index 0000000..d7001a5 --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/network/00-lo.network @@ -0,0 +1,14 @@ +<%- + addrs4 = node.dig(:wire, :interfaces, :loopback).fetch(:address4) + addrs6 = node.dig(:wire, :interfaces, :loopback).fetch(:address6) +-%> +[Match] +Name=lo + +[Network] +<%- addrs4.each do |addr| -%> +Address=<%= addr %>/32 +<%- end -%> +<%- addrs6.each do |addr| -%> +Address=<%= addr %>/128 +<%- end -%> diff --git a/itamae/roles/wire/templates/etc/systemd/network/00-management.link b/itamae/roles/wire/templates/etc/systemd/network/00-management.link new file mode 100644 index 0000000..b84e189 --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/network/00-management.link @@ -0,0 +1,7 @@ +<%- iface = node.dig(:wire, :interfaces, :management) -%> +[Match] +Path=<%= iface.fetch(:path) %> + +[Link] +Name=<%= iface.fetch(:name) %> +AlternativeNamesPolicy=mac diff --git a/itamae/roles/wire/templates/etc/systemd/network/00-management.network b/itamae/roles/wire/templates/etc/systemd/network/00-management.network new file mode 100644 index 0000000..77b6b98 --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/network/00-management.network @@ -0,0 +1,24 @@ +<%- iface = node.dig(:wire, :interfaces, :management) -%> +[Match] +Name=<%= iface.fetch(:name) %> + +[Network] +IPForward=no +IPv6AcceptRA=yes +DHCP=ipv4 +LLDP=yes +EmitLLDP=yes +LinkLocalAddressing=yes + +[DHCPv4] +SendHostname=yes +UseDNS=yes +UseMTU=yes +ClientIdentifier=duid +DUIDType=vendor +DUIDRawData=<%= iface.fetch(:duid) %> + + +[IPv6AcceptRA] +UseDNS=yes +UseMTU=yes diff --git a/itamae/roles/wire/templates/etc/systemd/network/10-wireguard.network b/itamae/roles/wire/templates/etc/systemd/network/10-wireguard.network new file mode 100644 index 0000000..794d1ba --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/network/10-wireguard.network @@ -0,0 +1,19 @@ +<%- iface = @attr -%> +<%- link4 = iface.fetch(:link4) -%> +<%- link6 = iface.fetch(:link6) -%> +[Match] +Name=<%= @name %> + +[Network] +IPv4Forwarding=yes +IPv6Forwarding=yes +LLDP=yes +EmitLLDP=yes + +[Address] +Address=<%= link4.fetch(:local) %>/32 +Peer=<%= link4.fetch(:peer) %>/32 + +[Address] +Address=<%= link6.fetch(:local) %>/128 +Peer=<%= link6.fetch(:peer) %>/128 diff --git a/itamae/roles/wire/templates/etc/systemd/nspawn/overlay.nspawn b/itamae/roles/wire/templates/etc/systemd/nspawn/overlay.nspawn new file mode 100644 index 0000000..f443866 --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/nspawn/overlay.nspawn @@ -0,0 +1,11 @@ +[Exec] +Hostname=overlay +LinkJournal=try-host +PrivateUsers=no + +[Files] +ReadOnly=yes + +[Network] +Private=yes +Interface=<%= node.dig(:wire, :interfaces, :overlay).fetch(:name) %> diff --git a/itamae/roles/wire/templates/etc/systemd/system/rk-move-overlay-wg-iface.service b/itamae/roles/wire/templates/etc/systemd/system/rk-move-overlay-wg-iface.service new file mode 100644 index 0000000..9c7d64b --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/system/rk-move-overlay-wg-iface.service @@ -0,0 +1,10 @@ +[Unit] +Description=rk-move-overlay-wg-iface +After=systemd-nspawn@overlay.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/rk-move-overlay-wg-iface + +[Install] +WantedBy=systemd-nspawn@overlay.service diff --git a/itamae/roles/wire/templates/etc/systemd/system/systemd-nspawn@overlay.service.d/dropin.conf b/itamae/roles/wire/templates/etc/systemd/system/systemd-nspawn@overlay.service.d/dropin.conf new file mode 100644 index 0000000..1fcb968 --- /dev/null +++ b/itamae/roles/wire/templates/etc/systemd/system/systemd-nspawn@overlay.service.d/dropin.conf @@ -0,0 +1,4 @@ +[Service] +ExecStart= +ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth -U --settings=override --machine=%i --load-credential=network.wireguard.private.default:%d/network.wireguard.private.default +LoadCredentialEncrypted=network.wireguard.private.default:/etc/wire.key diff --git a/itamae/roles/wire/templates/usr/bin/rk-move-overlay-wg-iface b/itamae/roles/wire/templates/usr/bin/rk-move-overlay-wg-iface new file mode 100755 index 0000000..a16c1ba --- /dev/null +++ b/itamae/roles/wire/templates/usr/bin/rk-move-overlay-wg-iface @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +require 'fileutils' +require 'json' + +config = JSON.parse(File.read('/etc/wire.json')) +tunnel_ifaces = config.fetch('tunnels', {}).keys.sort + +puts "Waiting for overlay container to be online" +10.times do |i| + if system(*%w(systemd-run --machine overlay --wait --quiet --pipe --collect id)) + break + else + sleep 2 + end +end +puts "Waiting for overlay network to be online" +system(*%w(systemd-run --machine overlay --wait --quiet --pipe --collect /lib/systemd/systemd-networkd-wait-online --ipv6 -o routable --dns), exception: true) + +# Reconfigure tunnels to populate Endpoint= where DNS name is specified +tunnel_ifaces.each do |iface| + puts "Reconfiguring #{iface} in overlay" + system(*%w(systemd-run --machine overlay --wait --quiet --pipe --collect networkctl reconfigure), iface) +end + +leader = IO.popen(%w(machinectl show -p Leader --value overlay), 'r', &:read) +puts "Leader PID: #{leader.inspect}" +leader_i = leader.to_i +raise "Failed to get leader PID" if leader_i <= 0 + +FileUtils.mkdir_p '/var/run/netns' +FileUtils.rm_f '/var/run/netns/overlay' rescue nil +FileUtils.ln_s "/proc/#{leader_i}/ns/net", "/var/run/netns/overlay" + +system(*%w(ip netns exec overlay ip link), exception: true) + +tunnel_ifaces.each do |iface| + if File.exist?(File.join("/", "sys", "class", "net", iface)) + puts "Interface #{iface} already exists" + system(*%w(ip link delete ), iface, exception: true) + end + puts "Acquiring #{iface}" + system(*%w(ip netns exec overlay ip link set), iface, *%w(netns 1), exception: true) +end diff --git a/itamae/roles/wire/templates/var/lib/machines/overlay/etc/systemd/network/00-overlay.network b/itamae/roles/wire/templates/var/lib/machines/overlay/etc/systemd/network/00-overlay.network new file mode 100644 index 0000000..385ee5b --- /dev/null +++ b/itamae/roles/wire/templates/var/lib/machines/overlay/etc/systemd/network/00-overlay.network @@ -0,0 +1,19 @@ +[Match] +Name=<%= node.dig(:wire, :interfaces, :overlay).fetch(:name) %> + +[Link] +RequiredForOnline=routable + +[Network] +DHCP=ipv6 +IPv6AcceptRA=yes +IPv4Forwarding=off +IPv6Forwarding=off + +<%= node.fetch(:wire).fetch(:tunnels, {}).each_key.map { |name| "Tunnel=#{name}" }.join("\n") %> + +[IPv6AcceptRA] +Token=<%= node.dig(:wire, :interfaces, :overlay).fetch(:ipv6_token) %> + +[DHCPv6] +UseDNS=yes diff --git a/itamae/roles/wire/templates/var/lib/machines/overlay/etc/systemd/network/10-wireguard.netdev b/itamae/roles/wire/templates/var/lib/machines/overlay/etc/systemd/network/10-wireguard.netdev new file mode 100644 index 0000000..bc61474 --- /dev/null +++ b/itamae/roles/wire/templates/var/lib/machines/overlay/etc/systemd/network/10-wireguard.netdev @@ -0,0 +1,15 @@ +[NetDev] +Name=<%= @name %> +Kind=wireguard + +[WireGuard] +ListenPort=<%= @attr.fetch(:listen_port) %> +PrivateKey=<%= @attr.fetch(:private_key, "@network.wireguard.private.default") %> + +[WireGuardPeer] +<%- if @attr[:peer_endpoint] -%> +Endpoint=<%= @attr.fetch(:peer_endpoint) %> +<%- end -%> +PublicKey=<%= @attr.fetch(:peer_public_key) %> +AllowedIPs=<%= @attr.fetch(:peer_allowed_ips, %w(0.0.0.0/0 ::/0)).join(", ") %> +PersistentKeepalive=<%= @attr.fetch(:persistent_keepalive, 25) %> diff --git a/itamae/roles/wire/wireguard.rb b/itamae/roles/wire/wireguard.rb new file mode 100644 index 0000000..489fab3 --- /dev/null +++ b/itamae/roles/wire/wireguard.rb @@ -0,0 +1,41 @@ +node.fetch(:wire).fetch(:tunnels).each do |name, attr| + template "/var/lib/machines/overlay/etc/systemd/network/10-#{name}.netdev" do + source 'templates/var/lib/machines/overlay/etc/systemd/network/10-wireguard.netdev' + owner 'root' + group 'root' + mode '0644' + variables( + name: name, + attr: attr, + ) + end + + template "/etc/systemd/network/10-#{name}.network" do + source 'templates/etc/systemd/network/10-wireguard.network' + owner 'root' + group 'root' + mode '0644' + variables( + name: name, + attr: attr, + ) + notifies :run, 'execute[networkctl reload]' + end +end + +template '/usr/bin/rk-move-overlay-wg-iface' do + owner 'root' + group 'root' + mode '0755' +end + +template '/etc/systemd/system/rk-move-overlay-wg-iface.service' do + owner 'root' + group 'root' + mode '0644' + notifies :run, 'execute[systemctl daemon-reload]', :immediately +end + +service 'rk-move-overlay-wg-iface.service' do + action [:enable] +end