diff --git a/ansible/inventory/full/group_vars/vpn/main.yml b/ansible/inventory/full/group_vars/vpn/main.yml index 145ff35..32db2a0 100644 --- a/ansible/inventory/full/group_vars/vpn/main.yml +++ b/ansible/inventory/full/group_vars/vpn/main.yml @@ -2,29 +2,33 @@ wireguard_remote_directory: /etc/wireguard wireguard_interface_restart: false -wireguard_service_enabled: false +wireguard_service_enabled: true wireguard_service_state: started -# Keep the NAT mapping open. Should only be needed for server -> client, but -# if the server disconnects, we may never be able to re-establish a connection. -# So this is on both client and server just in case that happens. -wireguard_persistent_keepalive: 25 +# We need to keep the NAT mapping open: +# https://www.wireguard.com/quickstart/#nat-and-firewall-traversal-persistence +# I've tested 25 seconds, which seems to be too low. The mapping still seems +# to be broken every once in a while. +# Or, it might be because PersistentKeepalive is actually also needed on the +# server but it's being omitted currently. See the issue I opened: +# https://github.com/githubixx/ansible-role-wireguard/issues/217#issue-2871281915 +wireguard_persistent_keepalive: 15 wireguard_ipv6_subnet: "fde0:fb5b:2593::/64" -# Setting this here doesn't seem to work. We set it in a playbook later +# Setting this here doesn't seem to work. We set it during runtime later # public_ipv6_subnet: "{{ hostvars[groups['embassy'][0]].ipv6_subnet }}" # We can generate this dynamically, but it really doesn't seem like it's worth # the work. nat_map: moirai-clotho.local: - vpn_ipv6: "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('16') | ansible.utils.ipaddr('address') }}" - vps_ipv6: "{{ public_ipv6_subnet | ansible.utils.ipaddr('16') | ansible.utils.ipaddr('address') }}" + vpn_ipv6: "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('16') }}" + vps_ipv6: "{{ public_ipv6_subnet | ansible.utils.ipaddr('16') }}" moirai-lachesis.local: - vpn_ipv6: "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('17') | ansible.utils.ipaddr('address') }}" - vps_ipv6: "{{ public_ipv6_subnet | ansible.utils.ipaddr('17') | ansible.utils.ipaddr('address') }}" + vpn_ipv6: "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('17') }}" + vps_ipv6: "{{ public_ipv6_subnet | ansible.utils.ipaddr('17') }}" moirai-atropos.local: - vpn_ipv6: "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('18') | ansible.utils.ipaddr('address') }}" - vps_ipv6: "{{ public_ipv6_subnet | ansible.utils.ipaddr('18') | ansible.utils.ipaddr('address') }}" + vpn_ipv6: "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('18') }}" + vps_ipv6: "{{ public_ipv6_subnet | ansible.utils.ipaddr('18') }}" diff --git a/ansible/inventory/full/group_vars/vpn_client/main.yml b/ansible/inventory/full/group_vars/vpn_client/main.yml index 68faac4..455ff51 100644 --- a/ansible/inventory/full/group_vars/vpn_client/main.yml +++ b/ansible/inventory/full/group_vars/vpn_client/main.yml @@ -1,5 +1,5 @@ wireguard_addresses: - - "{{ nat_map[inventory_hostname].vpn_ipv6 }}" + - "{{ nat_map[inventory_hostname].vpn_ipv6 | ansible.utils.ipaddr('address') }}" # wireguard_endpoint: "{{ nat_map[inventory_hostname].vpn_ipv6 }}" wireguard_endpoint: "" diff --git a/ansible/inventory/full/group_vars/vpn_server/main.yml b/ansible/inventory/full/group_vars/vpn_server/main.yml index 8c91b06..5a79c13 100644 --- a/ansible/inventory/full/group_vars/vpn_server/main.yml +++ b/ansible/inventory/full/group_vars/vpn_server/main.yml @@ -8,6 +8,9 @@ wireguard_endpoint: "{{ inventory_hostname }}" wireguard_preup: - echo 1 > /proc/sys/net/ipv4/ip_forward - echo 1 > /proc/sys/net/ipv6/conf/all/forwarding + # Disable autoconf while running. We assign some IP addresses that get removed + # by autoconf otherwise. + - echo 0 > /proc/sys/net/ipv6/conf/eth0/autoconf wireguard_postup: | {% filter from_yaml %} @@ -16,17 +19,19 @@ wireguard_postup: | # Incoming packets to this node's public IP are DNAT'd and forwarded to the # matching internal VPN IP - - ip6tables -t nat -A PREROUTING -p tcp -d {{ value.vps_ipv6 }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 }} + - ip6tables -t nat -A PREROUTING -p tcp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} + # Same for SFTP over TCP. + - ip6tables -t nat -A PREROUTING -p tcp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport 2022 -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} # Incoming packets to an internal VPN IP are SNAT'd to use this node's public # IP. I think `-j MASQUERADE` might work here rather than doing the SNAT # manually(?), but I don't mind being explicit here. - - ip6tables -t nat -A POSTROUTING -p tcp -s {{ value.vpn_ipv6 }} -j SNAT --to-source {{ value.vps_ipv6 }} + - ip6tables -t nat -A POSTROUTING -p tcp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} # Same thing with UDP. We do this selectively so we don't mess with things # like ICMP6 and whatnot. - - ip6tables -t nat -A PREROUTING -p udp -d {{ value.vps_ipv6 }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 }} - - ip6tables -t nat -A POSTROUTING -p udp -s {{ value.vpn_ipv6 }} -j SNAT --to-source {{ value.vps_ipv6 }} + - ip6tables -t nat -A PREROUTING -p udp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} + - ip6tables -t nat -A POSTROUTING -p udp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} {% endfor %} {% endfilter %} @@ -34,15 +39,17 @@ wireguard_postup: | wireguard_predown: | {% filter from_yaml %} {% for value in (nat_map | dict2items | map(attribute='value') | reverse) %} - - ip6tables -t nat -D POSTROUTING -p udp -s {{ value.vpn_ipv6 }} -j SNAT --to-source {{ value.vps_ipv6 }} - - ip6tables -t nat -D PREROUTING -p udp -d {{ value.vps_ipv6 }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 }} - - ip6tables -t nat -D POSTROUTING -p tcp -s {{ value.vpn_ipv6 }} -j SNAT --to-source {{ value.vps_ipv6 }} - - ip6tables -t nat -D PREROUTING -p tcp -d {{ value.vps_ipv6 }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 }} + - ip6tables -t nat -D POSTROUTING -p udp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} + - ip6tables -t nat -D PREROUTING -p udp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} + - ip6tables -t nat -D POSTROUTING -p tcp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} + - ip6tables -t nat -D PREROUTING -p tcp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport 2022 -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} + - ip6tables -t nat -D PREROUTING -p tcp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport 20000:20100 -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} - ip -6 addr del {{ value.vps_ipv6 }} dev eth0 {% endfor %} {% endfilter %} wireguard_postdown: + - echo 1 > /proc/sys/net/ipv6/conf/eth0/autoconf - echo 0 > /proc/sys/net/ipv6/conf/all/forwarding - echo 0 > /proc/sys/net/ipv4/ip_forward diff --git a/ansible/wings.yml b/ansible/wings.yml index 5d1bcfc..6db6db8 100644 --- a/ansible/wings.yml +++ b/ansible/wings.yml @@ -9,6 +9,7 @@ groups['vpn_server'] | length == 1 and groups['vpn_server'] | intersect(groups['embassy']) | length == 1 msg: Expected only one embassy host + - name: Verify ipv6_subnet is set when: inventory_hostname == groups['embassy'][0] ansible.builtin.assert: @@ -22,25 +23,27 @@ - set_fact: public_ipv6_subnet: "{{ hostvars[groups['embassy'][0]].ipv6_subnet }}" -- name: Prepare embassy - hosts: embassy - become: true - tasks: - - name: Disable password-based authentication + - when: inventory_hostname == groups['embassy'][0] + name: Disable password-based authentication + become: true lineinfile: path: "/etc/ssh/sshd_config" regexp: '^()PasswordAuthentication yes()$' line: 'PasswordAuthentication no' register: passwordauthentication - - name: Enable public key authentication in SSH + - when: inventory_hostname == groups['embassy'][0] + name: Enable public key authentication in SSH + become: true lineinfile: path: "/etc/ssh/sshd_config" regexp: '^()PubkeyAuthentication()$' line: 'PubkeyAuthentication yes' register: publickeyauthentication - - name: Restart SSH + - when: inventory_hostname == groups['embassy'][0] + name: Restart SSH + become: true service: name: ssh state: restarted @@ -52,24 +55,24 @@ roles: - githubixx.ansible_role_wireguard -# - name: Install wings - # hosts: moirai_wings - # remote_user: ubuntu - # # Don't forget to create a new disk if creating new wings. This is - # # purposefully manual to give more fine-grained control - # vars: - # pv_disks: - # - /dev/sda - # vg_name: vg1 - # lv_name: pvs - # lv_size: +100%FREE - # fs_type: ext4 - # mount_path: /var/lib/pterodactyl - # extra_docker_daemon_options: | - # "dns": ["10.0.123.123"], - # roles: - # - dns-client - # - lvm - # - docker - # - wings +- name: Install wings + hosts: moirai_wings + remote_user: ubuntu + # Don't forget to create a new disk if creating new wings. This is + # purposefully manual to give more fine-grained control + vars: + pv_disks: + - /dev/sda + vg_name: vg1 + lv_name: pvs + lv_size: +100%FREE + fs_type: ext4 + mount_path: /var/lib/pterodactyl + extra_docker_daemon_options: | + "dns": ["10.0.123.123"], + roles: + - dns-client + - lvm + - docker + - wings diff --git a/docker/stacks/truecommand/docker-stack.yml b/docker/stacks/truecommand/docker-stack.yml new file mode 100644 index 0000000..a64882a --- /dev/null +++ b/docker/stacks/truecommand/docker-stack.yml @@ -0,0 +1,33 @@ +--- +version: '3.9' + +networks: + traefik: + external: true + +services: + ixsystems: + image: 'ghcr.io/ixsystems/truecommand:v3.0.2' + volumes: + - ${TRUECOMMAND_DATA_DIRECTORY:-/mnt/stingray/truecommand/data}:/data + networks: + - traefik + deploy: + labels: + - "traefik.enable=true" + - "traefik.http.routers.truecommand.rule=Host(`${TRUECOMMAND_HOST:-truecommand.stingray.mnke.org}`)" + - "traefik.http.routers.truecommand.entrypoints=websecure" + - "traefik.http.routers.truecommand.tls.certresolver=letsencrypt" + - "traefik.http.services.truecommand.loadbalancer.server.port=80" + - "traefik.swarm.network=traefik" + mode: replicated + replicas: 1 + placement: + constraints: [node.role != manager] + # resources: + # limits: + # cpus: '0.50' + # memory: 1G + # reservations: + # cpus: '0.25' + # memory: 128M diff --git a/tf/modules/embassy/main.tf b/tf/modules/embassy/main.tf index ac84ee0..c918774 100644 --- a/tf/modules/embassy/main.tf +++ b/tf/modules/embassy/main.tf @@ -56,6 +56,15 @@ resource "linode_firewall" "embassy" { ipv6 = ["::/0"] } + inbound { + label = "allow-sftp" + action = "ACCEPT" + protocol = "TCP" + ports = "2022" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + inbound { label = "allow-wireguard" action = "ACCEPT"