From 0c5ee3d3d1033d5d19e3c9575e76e6da19e0e5c6 Mon Sep 17 00:00:00 2001 From: "Philip (a-0)" <@ph:a-0.me> Date: Wed, 13 Jul 2022 12:52:07 +0200 Subject: [PATCH] Initial commit --- README.md | 18 ++++ defaults/main.yml | 5 ++ example_playbook.yml | 12 +++ handlers/main.yml | 18 ++++ tasks/local.yml | 45 ++++++++++ tasks/main.yml | 28 +++++++ tasks/remote.yml | 125 ++++++++++++++++++++++++++++ templates/local/autossh.service.j2 | 12 +++ templates/local/ssh_config.j2 | 12 +++ templates/remote/authorized_keys.j2 | 1 + templates/remote/sshd_config.j2 | 9 ++ vars/debian.yml | 7 ++ 12 files changed, 292 insertions(+) create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 example_playbook.yml create mode 100644 handlers/main.yml create mode 100644 tasks/local.yml create mode 100644 tasks/main.yml create mode 100644 tasks/remote.yml create mode 100644 templates/local/autossh.service.j2 create mode 100644 templates/local/ssh_config.j2 create mode 100644 templates/remote/authorized_keys.j2 create mode 100644 templates/remote/sshd_config.j2 create mode 100644 vars/debian.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..a16304e --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +An ansible role to set up an ssh tunnel and port forwardings from the remote machine to local machines + +# Caution +Since the current debian version of sshd does not yet support dynamic configuration files (as in `/etc/ssh/sshd_config.d/*`), this role will **overwrite** your current sshd configuration! Normal ssh access on port 22 is still possible, but custom modifications will be lost on the remote machine. + +# Variables you need to set +... e.g. in `group_vars` +- `ssh_tunnel_pubkey`: The complete line to be used in `authorized_keys`, e.g. "ssh-ed25519 AAAA[...]aU root@mylocalmachine" +- `ssh_tunnel_privkey`: The content of the corresponding private key file, including the BEGIN and END tags. It is highly recommended to put this inside an encrypted ansible vault. +- `tunneled_ports`: A list of port forwardings. Example: + ``` + tunneled_ports: + - exposed_port: 80 # public port at the remote machine + ephemeral_port: 10080 # internal port at remote machine's localhost address. The ssh tunnel will fetch traffic from there + dest_host: my-internal-http-server.local.domain.tld # domain or IP address of the destination machine. Must be reachable form the local machine. + dest_port: 80 # open port at the destination machine + protocols: ["tcp"] # list of protocols for this forwarding. "tcp" and "udp" are supported. + ``` diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..eb9ab7c --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,5 @@ +--- +ssh_tunnel_os_supported: False + +ssh_tunnel_autossh_system_user: autossh +ssh_tunnel_sshd_unprivileged_user: ssh-tunnel \ No newline at end of file diff --git a/example_playbook.yml b/example_playbook.yml new file mode 100644 index 0000000..ea52638 --- /dev/null +++ b/example_playbook.yml @@ -0,0 +1,12 @@ +--- +- hosts: my-remote-vps.domain.tld + roles: + - ssh_tunnel + vars: + location: "remote" + +- hosts: my-local-machine.domain.tld + roles: + - ssh_tunnel + vars: + location: "local" diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..41b6747 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,18 @@ +--- +- name: restart autossh + service: + name: autossh + state: restarted + +- name: restart sshd + service: + name: sshd + state: restarted + +- name: save iptables v4 rules + shell: iptables-save > /etc/iptables/rules.v4 + listen: persist iptables + +- name: save iptables v6 rules + shell: ip6tables-save > /etc/iptables/rules.v6 + listen: persist iptables \ No newline at end of file diff --git a/tasks/local.yml b/tasks/local.yml new file mode 100644 index 0000000..8cafe6c --- /dev/null +++ b/tasks/local.yml @@ -0,0 +1,45 @@ +--- +- name: Install autossh + apt: + name: autossh + state: present + update_cache: yes + +- name: Ensure unprivileged ssh user exists + user: + name: "{{ ssh_tunnel_autossh_system_user }}" + system: true + state: present + +- name: Set user's ssh config + template: + src: local/ssh_config.j2 + dest: "{{ ssh_tunnel_local_sshdir }}config" + owner: "{{ ssh_tunnel_autossh_system_user }}" + mode: 0644 + +- name: Set private key + copy: + dest: "{{ ssh_tunnel_local_sshdir }}tunnel-key" + content: "{{ ssh_tunnel_privkey }}" + owner: "{{ ssh_tunnel_autossh_system_user }}" + mode: 0600 + +- name: Set public key + copy: + dest: "{{ ssh_tunnel_local_sshdir }}tunnel-key.pub" + content: "{{ ssh_tunnel_pubkey }}" + owner: "{{ ssh_tunnel_autossh_system_user }}" + mode: 0644 + +- name: Set systemd service file + become: yes + template: + src: local/autossh.service.j2 + dest: "{{ ssh_tunnel_autossh_service_file }}" + +- name: Enable service and run it + service: + name: autossh + state: restarted + enabled: yes \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..c73a97c --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: Set OS dependent variables + ansible.builtin.include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_distribution | lower }}_{{ ansible_distribution_version | lower }}.yml" + - "{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version | lower }}.yml" + - "{{ ansible_distribution | lower }}.yml" + - "{{ ansible_os_family | lower }}.yml" + - "{{ ansible_system | lower }}.yml" + paths: + - '{{ role_path }}/vars' + ignore_errors: True + +- name: OS is supported + ansible.builtin.assert: + that: __os_supported + quiet: True + vars: + __os_supported: "{{ lookup('vars', '{}_os_supported'.format(role_name)) | bool }}" + + +- include_tasks: local.yml + when: location == "local" + +- include_tasks: remote.yml + when: location == "remote" \ No newline at end of file diff --git a/tasks/remote.yml b/tasks/remote.yml new file mode 100644 index 0000000..167401e --- /dev/null +++ b/tasks/remote.yml @@ -0,0 +1,125 @@ +--- +- name: Ensure unprivileged ssh user exists + user: + name: "{{ ssh_tunnel_sshd_unprivileged_user }}" + system: true + state: present + +- name: Set authorized_keys for unprivileged user + template: + src: remote/authorized_keys.j2 + dest: "{{ ssh_tunnel_remote_sshdir }}authorized_keys" + owner: "{{ ssh_tunnel_sshd_unprivileged_user }}" + mode: 0600 + +- name: Set sshd_config + become: yes + template: + src: remote/sshd_config.j2 + dest: "{{ ssh_tunnel_sshd_conf_dir }}tunnel.conf" + mode: 0644 + owner: root + +- name: Enable IPv4 forwarding + sysctl: + name: net.ipv4.ip_forward + value: '1' + sysctl_set: yes + state: present + reload: yes + +- name: Enable IPv6 forwarding + sysctl: + name: net.ipv6.conf.all.forwarding + value: '1' + sysctl_set: yes + state: present + reload: yes + +- name: Enable IPv4 local network forwarding + sysctl: + name: net.ipv4.conf.all.route_localnet + value: '1' + sysctl_set: yes + state: present + reload: yes + +- name: Install iptables-persistent + apt: + name: iptables-persistent + update_cache: yes + state: present + +- name: Flush existing iptables entries (IPv4) + become: yes + iptables: + ip_version: ipv4 + table: nat + flush: yes + +- name: Flush existing iptables entries (IPv6) + become: yes + iptables: + ip_version: ipv6 + table: nat + flush: yes + +- name: Forward privileged ports to ephemeral localhost ports (IPv4, TCP) + become: yes + iptables: + ip_version: ipv4 + table: nat + chain: PREROUTING + in_interface: eth0 + protocol: tcp + destination_port: "{{ item.exposed_port }}" + jump: DNAT + to_destination: "127.0.0.1:{{ item.ephemeral_port }}" + loop: "{{ tunneled_ports }}" + when: "'tcp' in item.protocols" + notify: persist iptables + +- name: Forward privileged ports to ephemeral localhost ports (IPv4, UDP) + become: yes + iptables: + ip_version: ipv4 + table: nat + chain: PREROUTING + in_interface: eth0 + protocol: udp + destination_port: "{{ item.exposed_port }}" + jump: DNAT + to_destination: "127.0.0.1:{{ item.ephemeral_port }}" + loop: "{{ tunneled_ports }}" + when: "'udp' in item.protocols" + notify: persist iptables + +- name: Forward privileged ports to ephemeral localhost ports (IPv6, TCP) + become: yes + iptables: + ip_version: ipv6 + table: nat + chain: PREROUTING + in_interface: eth0 + protocol: tcp + destination_port: "{{ item.exposed_port }}" + jump: DNAT + to_destination: "[::1]:{{ item.ephemeral_port }}" + loop: "{{ tunneled_ports }}" + when: "'tcp' in item.protocols" + notify: persist iptables + +- name: Forward privileged ports to ephemeral localhost ports (IPv6, UDP) + become: yes + iptables: + ip_version: ipv6 + table: nat + chain: PREROUTING + in_interface: eth0 + protocol: udp + destination_port: "{{ item.exposed_port }}" + jump: DNAT + to_destination: "[::1]:{{ item.ephemeral_port }}" + loop: "{{ tunneled_ports }}" + when: "'udp' in item.protocols" + notify: persist iptables \ No newline at end of file diff --git a/templates/local/autossh.service.j2 b/templates/local/autossh.service.j2 new file mode 100644 index 0000000..b15f9a8 --- /dev/null +++ b/templates/local/autossh.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=AutoSSH tunnel service +After=network-online.target + +[Service] +Type=simple +User=autossh +Environment="AUTOSSH_GATETIME=0" +ExecStart=/usr/bin/autossh -M 0 gateway -N + +[Install] +WantedBy=multi-user.target diff --git a/templates/local/ssh_config.j2 b/templates/local/ssh_config.j2 new file mode 100644 index 0000000..ce3c93a --- /dev/null +++ b/templates/local/ssh_config.j2 @@ -0,0 +1,12 @@ +Host gateway + HostName tunnel-end.a-0.me + User ssh-tunnel + Port 22 + IdentityFile /home/autossh/.ssh/tunnel-key + IdentitiesOnly yes + ExitOnForwardFailure yes + ServerAliveInterval 5 + ServerAliveCountMax 3 +{% for forwarding in tunneled_ports %} + RemoteForward localhost:{{ forwarding.ephemeral_port }} {{ forwarding.dest_host }}:{{ forwarding.dest_port }} +{% endfor %} \ No newline at end of file diff --git a/templates/remote/authorized_keys.j2 b/templates/remote/authorized_keys.j2 new file mode 100644 index 0000000..3c90218 --- /dev/null +++ b/templates/remote/authorized_keys.j2 @@ -0,0 +1 @@ +{{ ssh_tunnel_pubkey }} diff --git a/templates/remote/sshd_config.j2 b/templates/remote/sshd_config.j2 new file mode 100644 index 0000000..de2a92c --- /dev/null +++ b/templates/remote/sshd_config.j2 @@ -0,0 +1,9 @@ +ClientAliveInterval 30 +ClientAliveCountMax 3 + +Match User ssh-tunnel + AllowTcpForwarding yes + X11Forwarding no + PermitTunnel no + GatewayPorts clientspecified + ForceCommand echo "Only forwarding" diff --git a/vars/debian.yml b/vars/debian.yml new file mode 100644 index 0000000..2ce1e89 --- /dev/null +++ b/vars/debian.yml @@ -0,0 +1,7 @@ +--- +ssh_tunnel_os_supported: True + +ssh_tunnel_local_sshdir: "/home/{{ ssh_tunnel_autossh_system_user }}/.ssh/" +ssh_tunnel_remote_sshdir: "/home/{{ ssh_tunnel_sshd_unprivileged_user }}/.ssh/" +ssh_tunnel_autossh_service_file: "/etc/systemd/system/autossh.service" +ssh_tunnel_sshd_conf_dir: "/etc/ssh/sshd_config/" \ No newline at end of file