diff --git a/.travis.yml b/.travis.yml index 904dbdb..7b2a394 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,4 +48,4 @@ script: - ansible-playbook deploy.yml -t local,vpn,dns,ssh_tunneling,security,tests -e "server_ip=$LXC_IP server_user=root IP_subject_alt_name=$LXC_IP local_dns=Y" after_script: - - ./tests/update-users.sh \ No newline at end of file + - ./tests/update-users.sh diff --git a/docs/FreeBSD.md b/docs/FreeBSD.md new file mode 100644 index 0000000..f1a8c83 --- /dev/null +++ b/docs/FreeBSD.md @@ -0,0 +1,28 @@ +# FreeBSD / HardenedBSD + +It is only possible to install Algo on existing systems only. We support only 11 version for now. + +## Pre-paring the system + +Ensure that the following kernel options are enabled: + +``` +# sysctl kern.conftxt | grep -iE "IPSEC|crypto" +options IPSEC +options IPSEC_NAT_T +device crypto +``` + +## Available roles + +* vpn +* ssh_tunneling +* dns_adblocking + +## Additional variables + +* rebuild_kernel - set to `true` if you want to let Algo to rebuild your kernel if needed (Takes a lot of time) + +## Installation + +`ansible-playbook deploy.yml -t local,vpn -e "server_ip=$server_ip server_user=$server_user IP_subject_alt_name=$server_ip Store_CAKEY=N" --skip-tags cloud` diff --git a/playbooks/common.yml b/playbooks/common.yml index c195b13..3dce638 100644 --- a/playbooks/common.yml +++ b/playbooks/common.yml @@ -1,10 +1,16 @@ -- name: Install prerequisites - raw: sleep 10 && sudo apt-get update -qq && sudo apt-get install -qq -y python2.7 +--- -- name: Configure defaults - raw: sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 - tags: - - update-alternatives +- name: Check the system + raw: uname -a + register: OS + +- name: Ubuntu pre-tasks + include: ubuntu.yml + when: '"Ubuntu" in OS.stdout' + +- name: FreeBSD pre-tasks + include: freebsd.yml + when: '"FreeBSD" in OS.stdout' - name: Ensure the algo ssh key exist on the server authorized_key: diff --git a/playbooks/facts/FreeBSD.yml b/playbooks/facts/FreeBSD.yml new file mode 100644 index 0000000..0d025fc --- /dev/null +++ b/playbooks/facts/FreeBSD.yml @@ -0,0 +1,10 @@ +--- + +- set_fact: + config_prefix: "/usr/local/" + root_group: wheel + ssh_service_name: sshd + apparmor_enabled: false + strongswan_additional_plugins: + - kernel-pfroute + - kernel-pfkey diff --git a/playbooks/freebsd.yml b/playbooks/freebsd.yml new file mode 100644 index 0000000..8cf0579 --- /dev/null +++ b/playbooks/freebsd.yml @@ -0,0 +1,9 @@ +--- + +- name: FreeBSD / HardenedBSD | Install prerequisites + raw: sleep 10 && env ASSUME_ALWAYS_YES=YES sudo pkg install -y python27 + +- name: FreeBSD / HardenedBSD | Configure defaults + raw: sudo ln -sf /usr/local/bin/python2.7 /usr/bin/python2.7 + +- include: facts/FreeBSD.yml diff --git a/playbooks/ubuntu.yml b/playbooks/ubuntu.yml new file mode 100644 index 0000000..d67cbde --- /dev/null +++ b/playbooks/ubuntu.yml @@ -0,0 +1,9 @@ +--- + +- name: Ubuntu | Install prerequisites + raw: sleep 10 && sudo apt-get update -qq && sudo apt-get install -qq -y python2.7 + +- name: Ubuntu | Configure defaults + raw: sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 + tags: + - update-alternatives diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml index c229685..2272403 100644 --- a/roles/common/handlers/main.yml +++ b/roles/common/handlers/main.yml @@ -1,8 +1,18 @@ - name: restart rsyslog service: name=rsyslog state=restarted +- name: restart ipfw + service: name=ipfw state=restarted + - name: flush routing cache shell: echo 1 > /proc/sys/net/ipv4/route/flush - name: restart loopback shell: ifdown lo:100 && ifup lo:100 + +- name: restart loopback bsd + shell: > + ifconfig lo100 destroy || true && + ifconfig lo100 create && + ifconfig lo100 inet {{ local_service_ip }} netmask 255.255.255.255 && + ifconfig lo100 inet6 FCAA::1/64; echo $? diff --git a/roles/common/tasks/freebsd.yml b/roles/common/tasks/freebsd.yml new file mode 100644 index 0000000..bf86108 --- /dev/null +++ b/roles/common/tasks/freebsd.yml @@ -0,0 +1,51 @@ +--- + +- set_fact: + tools: + - git + - subversion + - screen + - coreutils + - openssl + - bash + - wget + sysctl: + forwarding: + - net.inet.ip.forwarding + - net.inet6.ip6.forwarding + tags: + - always + +- name: Loopback included into the rc config + blockinfile: + dest: /etc/rc.conf + create: yes + block: | + cloned_interfaces="lo100" + ifconfig_lo100="inet {{ local_service_ip }} netmask 255.255.255.255" + ifconfig_lo100="inet6 FCAA::1/64" + notify: + - restart loopback bsd + tags: + - always + +- name: Enable the gateway features + lineinfile: dest=/etc/rc.conf regexp='^{{ item.param }}.*' line='{{ item.param }}={{ item.value }}' + with_items: + - { param: firewall_enable, value: '"YES"' } + - { param: firewall_type, value: '"open"' } + - { param: gateway_enable, value: '"YES"' } + - { param: natd_enable, value: '"YES"' } + - { param: natd_interface, value: '"{{ ansible_default_ipv4.device|default() }}"' } + - { param: natd_flags, value: '"-dynamic -m"' } + notify: + - restart ipfw + tags: + - always + +- name: FreeBSD | Activate IPFW + shell: > + kldstat -n ipfw.ko || kldload ipfw ; sysctl net.inet.ip.fw.enable=0 && + bash /etc/rc.firewall && sysctl net.inet.ip.fw.enable=1 + +- meta: flush_handlers diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 1262d3f..d8f6ec3 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -5,101 +5,24 @@ tags: - always -- name: Install software updates - apt: update_cache=yes upgrade=dist - tags: - - cloud +- include: ubuntu.yml + when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' -- name: Check if reboot is required - shell: > - if [[ -e /var/run/reboot-required ]]; then echo "required"; else echo "no"; fi - args: - executable: /bin/bash - register: reboot_required - tags: - - cloud - -- name: Reboot - shell: sleep 2 && shutdown -r now "Ansible updates triggered" - async: 1 - poll: 0 - when: reboot_required is defined and reboot_required.stdout == 'required' - ignore_errors: true - tags: - - cloud - -- name: Wait until SSH becomes ready... - local_action: - module: wait_for - port: 22 - host: "{{ inventory_hostname }}" - search_regex: OpenSSH - delay: 10 - timeout: 320 - when: reboot_required is defined and reboot_required.stdout == 'required' - become: false - tags: - - cloud - -- name: Disable MOTD on login and SSHD - replace: dest="{{ item.file }}" regexp="{{ item.regexp }}" replace="{{ item.line }}" - with_items: - - { regexp: '^session.*optional.*pam_motd.so.*', line: '# MOTD DISABLED', file: '/etc/pam.d/login' } - - { regexp: '^session.*optional.*pam_motd.so.*', line: '# MOTD DISABLED', file: '/etc/pam.d/sshd' } - tags: - - cloud +- include: freebsd.yml + when: ansible_distribution == 'FreeBSD' - name: Install tools - apt: name="{{ item }}" state=latest + package: name="{{ item }}" state=present with_items: - - git - - screen - - apparmor-utils - - uuid-runtime - - coreutils - - sendmail - - iptables-persistent - - cgroup-tools - - openssl - tags: - - always - -- name: Loopback for services configured - template: src=10-loopback-services.cfg.j2 dest=/etc/network/interfaces.d/10-loopback-services.cfg - notify: - - restart loopback - tags: - - always - -- name: Loopback included into the network config - lineinfile: dest=/etc/network/interfaces line='source /etc/network/interfaces.d/10-loopback-services.cfg' state=present - notify: - - restart loopback - tags: - - always - -- meta: flush_handlers + - "{{ tools }}" tags: - always - name: Enable packet forwarding for IPv4 sysctl: name="{{ item }}" value=1 with_items: - - net.ipv4.ip_forward - - net.ipv4.conf.all.forwarding + - "{{ sysctl.forwarding }}" tags: - always -- name: Enable packet forwarding for IPv6 - sysctl: name=net.ipv6.conf.all.forwarding value=1 - tags: - - always - -- name: Check apparmor support - shell: apparmor_status - ignore_errors: yes - register: apparmor_status - -- set_fact: - apparmor_enabled: true - when: '"profiles are in enforce mode" in apparmor_status.stdout' +- meta: flush_handlers diff --git a/roles/common/tasks/ubuntu.yml b/roles/common/tasks/ubuntu.yml new file mode 100644 index 0000000..e830993 --- /dev/null +++ b/roles/common/tasks/ubuntu.yml @@ -0,0 +1,91 @@ +--- + +- name: Install software updates + apt: update_cache=yes upgrade=dist + tags: + - cloud + +- name: Check if reboot is required + shell: > + if [[ -e /var/run/reboot-required ]]; then echo "required"; else echo "no"; fi + args: + executable: /bin/bash + register: reboot_required + tags: + - cloud + +- name: Reboot + shell: sleep 2 && shutdown -r now "Ansible updates triggered" + async: 1 + poll: 0 + when: reboot_required is defined and reboot_required.stdout == 'required' + ignore_errors: true + tags: + - cloud + +- name: Wait until SSH becomes ready... + local_action: + module: wait_for + port: 22 + host: "{{ inventory_hostname }}" + search_regex: OpenSSH + delay: 10 + timeout: 320 + when: reboot_required is defined and reboot_required.stdout == 'required' + become: false + tags: + - cloud + +- name: Disable MOTD on login and SSHD + replace: dest="{{ item.file }}" regexp="{{ item.regexp }}" replace="{{ item.line }}" + with_items: + - { regexp: '^session.*optional.*pam_motd.so.*', line: '# MOTD DISABLED', file: '/etc/pam.d/login' } + - { regexp: '^session.*optional.*pam_motd.so.*', line: '# MOTD DISABLED', file: '/etc/pam.d/sshd' } + tags: + - cloud + +- name: Loopback for services configured + template: src=10-loopback-services.cfg.j2 dest=/etc/network/interfaces.d/10-loopback-services.cfg + notify: + - restart loopback + tags: + - always + +- name: Loopback included into the network config + lineinfile: dest=/etc/network/interfaces line='source /etc/network/interfaces.d/10-loopback-services.cfg' state=present + notify: + - restart loopback + tags: + - always + +- meta: flush_handlers + tags: + - always + +- name: Check apparmor support + shell: apparmor_status + ignore_errors: yes + register: apparmor_status + +- set_fact: + apparmor_enabled: true + when: '"profiles are in enforce mode" in apparmor_status.stdout' + +- set_fact: + tools: + - git + - screen + - apparmor-utils + - uuid-runtime + - coreutils + - sendmail + - iptables-persistent + - cgroup-tools + - openssl + sysctl: + forwarding: + - net.ipv4.ip_forward + - net.ipv4.conf.all.forwarding + - net.ipv6.conf.all.forwarding + tags: + - always diff --git a/roles/dns_adblocking/tasks/freebsd.yml b/roles/dns_adblocking/tasks/freebsd.yml new file mode 100644 index 0000000..a08e234 --- /dev/null +++ b/roles/dns_adblocking/tasks/freebsd.yml @@ -0,0 +1,4 @@ +--- + +- name: FreeBSD / HardenedBSD | Enable dnsmasq + lineinfile: dest=/etc/rc.conf regexp=^dnsmasq_enable= line='dnsmasq_enable="YES"' diff --git a/roles/dns_adblocking/tasks/main.yml b/roles/dns_adblocking/tasks/main.yml index bf58931..90a86ee 100644 --- a/roles/dns_adblocking/tasks/main.yml +++ b/roles/dns_adblocking/tasks/main.yml @@ -2,55 +2,41 @@ setup: - name: Dnsmasq installed - apt: name=dnsmasq state=latest + package: name=dnsmasq -- name: Dnsmasq profile for apparmor configured - template: src=usr.sbin.dnsmasq.j2 dest=/etc/apparmor.d/usr.sbin.dnsmasq owner=root group=root mode=0600 - when: apparmor_enabled is defined and apparmor_enabled == true - notify: - - restart dnsmasq +- name: Ensure that the dnsmasq user exist + user: name=dnsmasq groups=nogroup append=yes state=present - name: The dnsmasq directory created file: dest=/var/lib/dnsmasq state=directory mode=0755 owner=dnsmasq group=nogroup -- name: Enforce the dnsmasq AppArmor policy - shell: aa-enforce usr.sbin.dnsmasq - when: apparmor_enabled is defined and apparmor_enabled == true - tags: ['apparmor'] +- include: ubuntu.yml + when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' -- name: Ensure that the dnsmasq service directory exist - file: path=/etc/systemd/system/dnsmasq.service.d/ state=directory mode=0755 owner=root group=root - -- name: Setup the cgroup limitations for the ipsec daemon - template: src=100-CustomLimitations.conf.j2 dest=/etc/systemd/system/dnsmasq.service.d/100-CustomLimitations.conf - notify: - - daemon-reload - - restart dnsmasq +- include: freebsd.yml + when: ansible_distribution == 'FreeBSD' - meta: flush_handlers - name: Dnsmasq configured - template: src=dnsmasq.conf.j2 dest=/etc/dnsmasq.conf + template: src=dnsmasq.conf.j2 dest="{{ config_prefix|default('/') }}etc/dnsmasq.conf" notify: - restart dnsmasq - name: Adblock script created - template: src=adblock.sh dest=/opt/adblock.sh owner=root group=root mode=0755 + template: src=adblock.sh dest=/usr/local/sbin/adblock.sh owner=root group="{{ root_group|default('root') }}" mode=0755 - name: Adblock script added to cron cron: name: Adblock hosts update minute: 10 hour: 2 - job: /opt/adblock.sh + job: /usr/local/sbin/adblock.sh user: dnsmasq - name: Update adblock hosts shell: > - /opt/adblock.sh - become: true - become_user: dnsmasq + sudo -u dnsmasq "/usr/local/sbin/adblock.sh" - name: Dnsmasq enabled and started service: name=dnsmasq state=started enabled=yes - diff --git a/roles/dns_adblocking/tasks/ubuntu.yml b/roles/dns_adblocking/tasks/ubuntu.yml new file mode 100644 index 0000000..f0ffb91 --- /dev/null +++ b/roles/dns_adblocking/tasks/ubuntu.yml @@ -0,0 +1,21 @@ +--- + +- name: Ubuntu | Dnsmasq profile for apparmor configured + template: src=usr.sbin.dnsmasq.j2 dest=/etc/apparmor.d/usr.sbin.dnsmasq owner=root group=root mode=0600 + when: apparmor_enabled is defined and apparmor_enabled == true + notify: + - restart dnsmasq + +- name: Ubuntu | Enforce the dnsmasq AppArmor policy + shell: aa-enforce usr.sbin.dnsmasq + when: apparmor_enabled is defined and apparmor_enabled == true + tags: ['apparmor'] + +- name: Ubuntu | Ensure that the dnsmasq service directory exist + file: path=/etc/systemd/system/dnsmasq.service.d/ state=directory mode=0755 owner=root group=root + +- name: Ubuntu | Setup the cgroup limitations for the ipsec daemon + template: src=100-CustomLimitations.conf.j2 dest=/etc/systemd/system/dnsmasq.service.d/100-CustomLimitations.conf + notify: + - daemon-reload + - restart dnsmasq diff --git a/roles/security/handlers/main.yml b/roles/security/handlers/main.yml index e6d614b..ab98db6 100644 --- a/roles/security/handlers/main.yml +++ b/roles/security/handlers/main.yml @@ -1,5 +1,5 @@ - name: restart ssh - service: name=ssh state=restarted + service: name="{{ ssh_service_name|default('ssh') }}" state=restarted - name: flush routing cache shell: echo 1 > /proc/sys/net/ipv4/route/flush diff --git a/roles/ssh_tunneling/handlers/main.yml b/roles/ssh_tunneling/handlers/main.yml index 276ebfe..066d960 100644 --- a/roles/ssh_tunneling/handlers/main.yml +++ b/roles/ssh_tunneling/handlers/main.yml @@ -1,2 +1,2 @@ - name: restart ssh - service: name=ssh state=restarted + service: name="{{ ssh_service_name|default('ssh') }}" state=restarted diff --git a/roles/ssh_tunneling/tasks/main.yml b/roles/ssh_tunneling/tasks/main.yml index 2c667ac..1cf2368 100644 --- a/roles/ssh_tunneling/tasks/main.yml +++ b/roles/ssh_tunneling/tasks/main.yml @@ -6,7 +6,7 @@ - name: Ensure that the sshd_config file has desired options blockinfile: dest: /etc/ssh/sshd_config - marker: '# ANSIBLE_MANAGED_ssh_tunneling_role' + marker: '# {mark} ANSIBLE MANAGED BLOCK ssh_tunneling_role' block: | Match Group algo AllowTcpForwarding local @@ -21,7 +21,7 @@ group: name=algo state=present - name: Ensure that the jail directory exist - file: path=/var/jail/ state=directory mode=0755 owner=root group=root + file: path=/var/jail/ state=directory mode=0755 owner=root group="{{ root_group|default('root') }}" - name: Ensure that the SSH users exist user: diff --git a/roles/vpn/tasks/client_configs.yml b/roles/vpn/tasks/client_configs.yml new file mode 100644 index 0000000..76f5a05 --- /dev/null +++ b/roles/vpn/tasks/client_configs.yml @@ -0,0 +1,79 @@ +--- + +- name: Register p12 PayloadContent + local_action: > + shell cat private/{{ item }}.p12 | base64 + register: PayloadContent + become: no + args: + chdir: "configs/{{ IP_subject_alt_name }}/pki/" + with_items: "{{ users }}" + +- name: Set facts for mobileconfigs + set_fact: + proxy_enabled: false + PayloadContentCA: "{{ lookup('file' , 'configs/{{ IP_subject_alt_name }}/pki/cacert.pem')|b64encode }}" + +- name: Build the mobileconfigs + local_action: + module: template + src: mobileconfig.j2 + dest: configs/{{ IP_subject_alt_name }}/{{ item.0 }}.mobileconfig + mode: 0600 + become: no + with_together: + - "{{ users }}" + - "{{ PayloadContent.results }}" + no_log: True + +- name: Build the strongswan app android config + local_action: + module: template + src: sswan.j2 + dest: configs/{{ IP_subject_alt_name }}/{{ item.0 }}.sswan + mode: 0600 + become: no + with_together: + - "{{ users }}" + - "{{ PayloadContent.results }}" + no_log: True + +- name: Build the client ipsec config file + local_action: + module: template + src: client_ipsec.conf.j2 + dest: configs/{{ IP_subject_alt_name }}/ipsec_{{ item }}.conf + mode: 0600 + become: no + with_items: + - "{{ users }}" + +- name: Build the client ipsec secret file + local_action: + module: template + src: client_ipsec.secrets.j2 + dest: configs/{{ IP_subject_alt_name }}/ipsec_{{ item }}.secrets + mode: 0600 + become: no + with_items: + - "{{ users }}" + +- name: Build the windows client powershell script + local_action: + module: template + src: client_windows.ps1.j2 + dest: configs/{{ IP_subject_alt_name }}/windows_{{ item }}.ps1 + mode: 0600 + become: no + when: Win10_Enabled is defined and Win10_Enabled == "Y" + with_items: "{{ users }}" + +- name: Restrict permissions for the local private directories + local_action: + module: file + path: "{{ item }}" + state: directory + mode: 0700 + become: no + with_items: + - configs/{{ IP_subject_alt_name }} diff --git a/roles/vpn/tasks/distribute_keys.yml b/roles/vpn/tasks/distribute_keys.yml new file mode 100644 index 0000000..d50ecfa --- /dev/null +++ b/roles/vpn/tasks/distribute_keys.yml @@ -0,0 +1,27 @@ +--- + +- name: Copy the keys to the strongswan directory + copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: "{{ item.owner }}" + group: "{{ item.group }}" + mode: "{{ item.mode }}" + with_items: + - src: "configs/{{ IP_subject_alt_name }}/pki/cacert.pem" + dest: "{{ config_prefix|default('/') }}etc/ipsec.d/cacerts/ca.crt" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + - src: "configs/{{ IP_subject_alt_name }}/pki/certs/{{ IP_subject_alt_name }}.crt" + dest: "{{ config_prefix|default('/') }}etc/ipsec.d/certs/{{ IP_subject_alt_name }}.crt" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + - src: "configs/{{ IP_subject_alt_name }}/pki/private/{{ IP_subject_alt_name }}.key" + dest: "{{ config_prefix|default('/') }}etc/ipsec.d/private/{{ IP_subject_alt_name }}.key" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + notify: + - restart strongswan diff --git a/roles/vpn/tasks/freebsd.yml b/roles/vpn/tasks/freebsd.yml new file mode 100644 index 0000000..8964faa --- /dev/null +++ b/roles/vpn/tasks/freebsd.yml @@ -0,0 +1,113 @@ +--- + +- name: FreeBSD / HardenedBSD | Get the existing kernel parameters + command: sysctl -b kern.conftxt + register: kern_conftxt + when: rebuild_kernel is defined and rebuild_kernel == "true" + +- name: FreeBSD / HardenedBSD | Set the rebuild_needed fact + set_fact: + rebuild_needed: true + when: item not in kern_conftxt.stdout and rebuild_kernel is defined and rebuild_kernel == "true" + with_items: + - "IPSEC" + - "IPSEC_NAT_T" + - "crypto" + +- name: FreeBSD / HardenedBSD | Make the kernel config + shell: > + sysctl -b kern.conftxt > /tmp/IPSEC + when: rebuild_needed is defined and rebuild_needed == true + +- name: FreeBSD / HardenedBSD | Ensure the all options are enabled + lineinfile: + dest: /tmp/IPSEC + line: "{{ item }}" + insertbefore: BOF + with_items: + - "options IPSEC" + - "options IPSEC_NAT_T" + - "device crypto" + when: rebuild_needed is defined and rebuild_needed == true + +- name: HardenedBSD | Determine the sources + set_fact: + sources_repo: https://github.com/HardenedBSD/hardenedBSD.git + sources_version: "hardened/{{ ansible_distribution_release.split('.')[0] }}-stable/master" + when: "'Hardened' in ansible_distribution_version" + +- name: FreeBSD | Determine the sources + set_fact: + sources_repo: https://github.com/freebsd/freebsd.git + sources_version: "stable/{{ ansible_distribution_major_version }}" + when: "'Hardened' not in ansible_distribution_version" + +- name: FreeBSD / HardenedBSD | Increase the git postBuffer size + git_config: + name: http.postBuffer + scope: global + value: 1048576000 + +- block: + - name: FreeBSD / HardenedBSD | Fetching the sources... + git: + repo: "{{ sources_repo }}" + dest: /usr/krnl_src + version: "{{ sources_version }}" + accept_hostkey: true + async: 1000 + poll: 0 + register: fetching_sources + + - name: FreeBSD / HardenedBSD | Fetching the sources... + async_status: jid={{ fetching_sources.ansible_job_id }} + when: rebuild_needed is defined and rebuild_needed == true + register: result + until: result.finished + retries: 600 + delay: 30 + rescue: + - debug: var=fetching_sources + + - fail: + msg: "Something went wrong. Check the debug output above." + +- block: + - name: FreeBSD / HardenedBSD | The kernel is being built... + shell: > + mv /tmp/IPSEC /usr/krnl_src/sys/{{ ansible_architecture }}/conf && + make buildkernel KERNCONF=IPSEC && + make installkernel KERNCONF=IPSEC + args: + chdir: /usr/krnl_src + executable: /usr/local/bin/bash + when: rebuild_needed is defined and rebuild_needed == true + async: 1000 + poll: 0 + register: building_kernel + + - name: FreeBSD / HardenedBSD | The kernel is being built... + async_status: jid={{ building_kernel.ansible_job_id }} + when: rebuild_needed is defined and rebuild_needed == true + register: result + until: result.finished + retries: 600 + delay: 30 + rescue: + - debug: var=building_kernel + + - fail: + msg: "Something went wrong. Check the debug output above." + +- name: FreeBSD / HardenedBSD | Reboot + shell: > + sleep 2 && shutdown -r now + args: + executable: /usr/local/bin/bash + when: rebuild_needed is defined and rebuild_needed == true + async: 1 + poll: 0 + ignore_errors: true + +- name: FreeBSD / HardenedBSD | Enable strongswan + lineinfile: dest=/etc/rc.conf regexp=^strongswan_enable= line='strongswan_enable="YES"' diff --git a/roles/vpn/tasks/ipec_configuration.yml b/roles/vpn/tasks/ipec_configuration.yml new file mode 100644 index 0000000..a6b1530 --- /dev/null +++ b/roles/vpn/tasks/ipec_configuration.yml @@ -0,0 +1,46 @@ +--- + +- name: Setup the config files from our templates + template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: "{{ item.owner }}" + group: "{{ item.group }}" + mode: "{{ item.mode }}" + with_items: + - src: strongswan.conf.j2 + dest: "{{ config_prefix|default('/') }}etc/strongswan.conf" + owner: root + group: "{{ root_group|default('root') }}" + mode: "0644" + - src: ipsec.conf.j2 + dest: "{{ config_prefix|default('/') }}etc/ipsec.conf" + owner: root + group: "{{ root_group|default('root') }}" + mode: "0644" + - src: ipsec.secrets.j2 + dest: "{{ config_prefix|default('/') }}etc/ipsec.secrets" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + notify: + - restart strongswan + +- name: Get loaded plugins + shell: > + find {{ config_prefix|default('/') }}etc/strongswan.d/charon/ -type f -name '*.conf' -exec basename {} \; | cut -f1 -d. + register: strongswan_plugins + +- name: Disable unneeded plugins + lineinfile: dest="{{ config_prefix|default('/') }}etc/strongswan.d/charon/{{ item }}.conf" regexp='.*load.*' line='load = no' state=present + notify: + - restart strongswan + when: item not in strongswan_enabled_plugins and item not in strongswan_additional_plugins + with_items: "{{ strongswan_plugins.stdout_lines }}" + +- name: Ensure that required plugins are enabled + lineinfile: dest="{{ config_prefix|default('/') }}etc/strongswan.d/charon/{{ item }}.conf" regexp='.*load.*' line='load = yes' state=present + notify: + - restart strongswan + when: item in strongswan_enabled_plugins or item in strongswan_additional_plugins + with_items: "{{ strongswan_plugins.stdout_lines }}" diff --git a/roles/vpn/tasks/main.yml b/roles/vpn/tasks/main.yml index 567614c..a11e212 100644 --- a/roles/vpn/tasks/main.yml +++ b/roles/vpn/tasks/main.yml @@ -8,7 +8,7 @@ - name: Generate password for the CA key shell: > - < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};echo; + openssl rand -hex 6 register: CA_password - set_fact: @@ -21,304 +21,27 @@ algo_params: "rsa:2048" when: Win10_Enabled is defined and Win10_Enabled == "Y" -- name: Install StrongSwan - apt: name=strongswan state=latest update_cache=yes install_recommends=yes - -- name: Enforcing ipsec with apparmor - shell: aa-enforce "{{ item }}" - when: apparmor_enabled is defined and apparmor_enabled == true - with_items: - - /usr/lib/ipsec/charon - - /usr/lib/ipsec/lookip - - /usr/lib/ipsec/stroke - notify: - - restart apparmor - tags: ['apparmor'] - -- name: Enable services - service: name={{ item }} enabled=yes - with_items: - - apparmor - - strongswan - - netfilter-persistent - -- name: Configure iptables so IPSec traffic can traverse the tunnel - iptables: table=nat chain=POSTROUTING source="{{ vpn_network }}" jump=MASQUERADE - when: (security_enabled is not defined) or - (security_enabled is defined and security_enabled != "y") - notify: - - save iptables - -- name: Configure ip6tables so IPSec traffic can traverse the tunnel - iptables: ip_version=ipv6 table=nat chain=POSTROUTING source="{{ vpn_network_ipv6 }}" jump=MASQUERADE - when: ((security_enabled is not defined) or (security_enabled is defined and security_enabled != "y")) and - (ipv6_support is defined and ipv6_support == true) - notify: - - save iptables - - name: Ensure that the strongswan group exist group: name=strongswan state=present - name: Ensure that the strongswan user exist user: name=strongswan group=strongswan state=present -- name: Ensure that the strongswan service directory exist - file: path=/etc/systemd/system/strongswan.service.d/ state=directory mode=0755 owner=root group=root +- include: ubuntu.yml + when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' -- name: Setup the cgroup limitations for the ipsec daemon - template: src=100-CustomLimitations.conf.j2 dest=/etc/systemd/system/strongswan.service.d/100-CustomLimitations.conf - notify: - - daemon-reload - - restart strongswan +- include: freebsd.yml + when: ansible_distribution == 'FreeBSD' + +- name: Install StrongSwan + package: name=strongswan state=present + +- include: ipec_configuration.yml +- include: openssl.yml +- include: distribute_keys.yml +- include: client_configs.yml - meta: flush_handlers -- name: Setup the strongswan.conf file from our template - template: src=strongswan.conf.j2 dest=/etc/strongswan.conf owner=root group=root mode=0644 - notify: - - restart strongswan - -- name: Setup the ipsec.conf file from our template - template: src=ipsec.conf.j2 dest=/etc/ipsec.conf owner=root group=root mode=0644 - notify: - - restart strongswan - -- name: Setup the ipsec.secrets file - template: src=ipsec.secrets.j2 dest=/etc/ipsec.secrets owner=strongswan group=root mode=0600 - notify: - - restart strongswan - -- name: Get loaded plugins - shell: > - find /etc/strongswan.d/charon/ -type f -name '*.conf' -printf '%f\n' | cut -f1 -d. - register: strongswan_plugins - -- name: Disable unneeded plugins - lineinfile: dest="/etc/strongswan.d/charon/{{ item }}.conf" regexp='.*load.*' line='load = no' state=present - notify: - - restart strongswan - when: item not in strongswan_enabled_plugins - with_items: "{{ strongswan_plugins.stdout_lines }}" - -- name: Ensure that required plugins are enabled - lineinfile: dest="/etc/strongswan.d/charon/{{ item }}.conf" regexp='.*load.*' line='load = yes' state=present - notify: - - restart strongswan - when: item in strongswan_enabled_plugins - with_items: "{{ strongswan_plugins.stdout_lines }}" - -- name: Ensure the pki directory is not exist - local_action: - module: file - dest: configs/{{ IP_subject_alt_name }}/pki - state: absent - become: no - when: easyrsa_reinit_existent == True - -- name: Ensure the pki directories are exist - local_action: - module: file - dest: "configs/{{ IP_subject_alt_name }}/pki/{{ item }}" - state: directory - recurse: yes - become: no - with_items: - - ecparams - - certs - - crl - - newcerts - - private - - reqs - -- name: Ensure the files are exist - local_action: - module: file - dest: "configs/{{ IP_subject_alt_name }}/pki/{{ item }}" - state: touch - become: no - with_items: - - ".rnd" - - "private/.rnd" - - "index.txt" - - "index.txt.attr" - - "serial" - -- name: Generate the openssl server configs - local_action: - module: template - src: openssl.cnf.j2 - dest: "configs/{{ IP_subject_alt_name }}/pki/openssl.cnf" - become: no - -- name: Build the CA pair - local_action: > - shell openssl ecparam -name prime256v1 -out ecparams/prime256v1.pem && - openssl req -utf8 -new -newkey {{ algo_params | default('ec:ecparams/prime256v1.pem') }} -config openssl.cnf -keyout private/cakey.pem -out cacert.pem -x509 -days 3650 -batch -passout pass:"{{ easyrsa_CA_password }}" && - touch {{ IP_subject_alt_name }}_ca_generated - become: no - args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" - creates: "{{ IP_subject_alt_name }}_ca_generated" - environment: - subjectAltName: "DNS:{{ IP_subject_alt_name }},IP:{{ IP_subject_alt_name }}" - -- name: Copy the CA certificate - local_action: - module: copy - src: "configs/{{ IP_subject_alt_name }}/pki/cacert.pem" - dest: "configs/{{ IP_subject_alt_name }}/cacert.pem" - mode: 0600 - become: no - -- name: Generate the serial number - local_action: > - shell echo 01 > serial && - touch serial_generated - become: no - args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" - creates: serial_generated - -- name: Build the server pair - local_action: > - shell openssl req -utf8 -new -newkey {{ algo_params | default('ec:ecparams/prime256v1.pem') }} -config openssl.cnf -keyout private/{{ IP_subject_alt_name }}.key -out reqs/{{ IP_subject_alt_name }}.req -nodes -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ IP_subject_alt_name }}" -batch && - openssl ca -utf8 -in reqs/{{ IP_subject_alt_name }}.req -out certs/{{ IP_subject_alt_name }}.crt -config openssl.cnf -days 3650 -batch -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ IP_subject_alt_name }}" && - touch certs/{{ IP_subject_alt_name }}_crt_generated - become: no - args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" - creates: certs/{{ IP_subject_alt_name }}_crt_generated - environment: - subjectAltName: "DNS:{{ IP_subject_alt_name }},IP:{{ IP_subject_alt_name }}" - -- name: Build the client's pair - local_action: > - shell openssl req -utf8 -new -newkey {{ algo_params | default('ec:ecparams/prime256v1.pem') }} -config openssl.cnf -keyout private/{{ item }}.key -out reqs/{{ item }}.req -nodes -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ item }}" -batch && - openssl ca -utf8 -in reqs/{{ item }}.req -out certs/{{ item }}.crt -config openssl.cnf -days 3650 -batch -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ item }}" && - touch certs/{{ item }}_crt_generated - become: no - args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" - creates: certs/{{ item }}_crt_generated - environment: - subjectAltName: "DNS:{{ item }}" - with_items: "{{ users }}" - -- name: Build the client's p12 - local_action: > - shell openssl pkcs12 -in certs/{{ item }}.crt -inkey private/{{ item }}.key -export -name {{ item }} -out private/{{ item }}.p12 -certfile cacert.pem -passout pass:"{{ easyrsa_p12_export_password }}" - become: no - args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" - with_items: "{{ users }}" - -- name: Copy the p12 certificates - local_action: - module: copy - src: "configs/{{ IP_subject_alt_name }}/pki/private/{{ item }}.p12" - dest: "configs/{{ IP_subject_alt_name }}/{{ item }}.p12" - mode: 0600 - become: no - with_items: - - "{{ users }}" - -- name: Copy the CA cert to the strongswan directory - copy: src='configs/{{ IP_subject_alt_name }}/pki/cacert.pem' dest=/etc/ipsec.d/cacerts/ca.crt owner=strongswan group=root mode=0600 - notify: - - restart strongswan - -- name: Copy the server cert to the strongswan directory - copy: src='configs/{{ IP_subject_alt_name }}/pki/certs/{{ IP_subject_alt_name }}.crt' dest=/etc/ipsec.d/certs/{{ IP_subject_alt_name }}.crt owner=strongswan group=root mode=0600 - notify: - - restart strongswan - -- name: Copy the server key to the strongswan directory - copy: src='configs/{{ IP_subject_alt_name }}/pki/private/{{ IP_subject_alt_name }}.key' dest=/etc/ipsec.d/private/{{ IP_subject_alt_name }}.key owner=strongswan group=root mode=0600 - notify: - - restart strongswan - -- name: Register p12 PayloadContent - local_action: > - shell cat private/{{ item }}.p12 | base64 - register: PayloadContent - become: no - args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" - with_items: "{{ users }}" - -- name: Set facts for mobileconfigs - set_fact: - proxy_enabled: false - PayloadContentCA: "{{ lookup('file' , 'configs/{{ IP_subject_alt_name }}/pki/cacert.pem')|b64encode }}" - -- name: Build the mobileconfigs - local_action: - module: template - src: mobileconfig.j2 - dest: configs/{{ IP_subject_alt_name }}/{{ item.0 }}.mobileconfig - mode: 0600 - become: no - with_together: - - "{{ users }}" - - "{{ PayloadContent.results }}" - no_log: True - -- name: Build the strongswan app android config - local_action: - module: template - src: sswan.j2 - dest: configs/{{ IP_subject_alt_name }}/{{ item.0 }}.sswan - mode: 0600 - become: no - with_together: - - "{{ users }}" - - "{{ PayloadContent.results }}" - no_log: True - -- name: Build the client ipsec config file - local_action: - module: template - src: client_ipsec.conf.j2 - dest: configs/{{ IP_subject_alt_name }}/ipsec_{{ item }}.conf - mode: 0600 - become: no - with_items: - - "{{ users }}" - -- name: Build the client ipsec secret file - local_action: - module: template - src: client_ipsec.secrets.j2 - dest: configs/{{ IP_subject_alt_name }}/ipsec_{{ item }}.secrets - mode: 0600 - become: no - with_items: - - "{{ users }}" - -- name: Build the windows client powershell script - local_action: - module: template - src: client_windows.ps1.j2 - dest: configs/{{ IP_subject_alt_name }}/windows_{{ item }}.ps1 - mode: 0600 - become: no - when: Win10_Enabled is defined and Win10_Enabled == "Y" - with_items: "{{ users }}" - -- name: Restrict permissions for the remote private directories - file: path="{{ item }}" state=directory mode=0700 owner=strongswan group=root - with_items: - - /etc/ipsec.d/private - -- name: Restrict permissions for the local private directories - local_action: - module: file - path: "{{ item }}" - state: directory - mode: 0700 - become: no - with_items: - - configs/{{ IP_subject_alt_name }} - -- include: iptables.yml - tags: iptables +- name: StrongSwan started + service: name=strongswan state=started diff --git a/roles/vpn/tasks/openssl.yml b/roles/vpn/tasks/openssl.yml new file mode 100644 index 0000000..8f9d52a --- /dev/null +++ b/roles/vpn/tasks/openssl.yml @@ -0,0 +1,117 @@ +--- + +- name: Ensure the pki directory is not exist + local_action: + module: file + dest: configs/{{ IP_subject_alt_name }}/pki + state: absent + become: no + when: easyrsa_reinit_existent == True + +- name: Ensure the pki directories are exist + local_action: + module: file + dest: "configs/{{ IP_subject_alt_name }}/pki/{{ item }}" + state: directory + recurse: yes + become: no + with_items: + - ecparams + - certs + - crl + - newcerts + - private + - reqs + +- name: Ensure the files are exist + local_action: + module: file + dest: "configs/{{ IP_subject_alt_name }}/pki/{{ item }}" + state: touch + become: no + with_items: + - ".rnd" + - "private/.rnd" + - "index.txt" + - "index.txt.attr" + - "serial" + +- name: Generate the openssl server configs + local_action: + module: template + src: openssl.cnf.j2 + dest: "configs/{{ IP_subject_alt_name }}/pki/openssl.cnf" + become: no + + +- name: Build the CA pair + local_action: > + shell openssl ecparam -name prime256v1 -out ecparams/prime256v1.pem && + openssl req -utf8 -new -newkey {{ algo_params | default('ec:ecparams/prime256v1.pem') }} -config openssl.cnf -keyout private/cakey.pem -out cacert.pem -x509 -days 3650 -batch -passout pass:"{{ easyrsa_CA_password }}" && + touch {{ IP_subject_alt_name }}_ca_generated + become: no + args: + chdir: "configs/{{ IP_subject_alt_name }}/pki/" + creates: "{{ IP_subject_alt_name }}_ca_generated" + environment: + subjectAltName: "DNS:{{ IP_subject_alt_name }},IP:{{ IP_subject_alt_name }}" + +- name: Copy the CA certificate + local_action: + module: copy + src: "configs/{{ IP_subject_alt_name }}/pki/cacert.pem" + dest: "configs/{{ IP_subject_alt_name }}/cacert.pem" + mode: 0600 + become: no + +- name: Generate the serial number + local_action: > + shell echo 01 > serial && + touch serial_generated + become: no + args: + chdir: "configs/{{ IP_subject_alt_name }}/pki/" + creates: serial_generated + +- name: Build the server pair + local_action: > + shell openssl req -utf8 -new -newkey {{ algo_params | default('ec:ecparams/prime256v1.pem') }} -config openssl.cnf -keyout private/{{ IP_subject_alt_name }}.key -out reqs/{{ IP_subject_alt_name }}.req -nodes -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ IP_subject_alt_name }}" -batch && + openssl ca -utf8 -in reqs/{{ IP_subject_alt_name }}.req -out certs/{{ IP_subject_alt_name }}.crt -config openssl.cnf -days 3650 -batch -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ IP_subject_alt_name }}" && + touch certs/{{ IP_subject_alt_name }}_crt_generated + become: no + args: + chdir: "configs/{{ IP_subject_alt_name }}/pki/" + creates: certs/{{ IP_subject_alt_name }}_crt_generated + environment: + subjectAltName: "DNS:{{ IP_subject_alt_name }},IP:{{ IP_subject_alt_name }}" + +- name: Build the client's pair + local_action: > + shell openssl req -utf8 -new -newkey {{ algo_params | default('ec:ecparams/prime256v1.pem') }} -config openssl.cnf -keyout private/{{ item }}.key -out reqs/{{ item }}.req -nodes -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ item }}" -batch && + openssl ca -utf8 -in reqs/{{ item }}.req -out certs/{{ item }}.crt -config openssl.cnf -days 3650 -batch -passin pass:"{{ easyrsa_CA_password }}" -subj "/CN={{ item }}" && + touch certs/{{ item }}_crt_generated + become: no + args: + chdir: "configs/{{ IP_subject_alt_name }}/pki/" + creates: certs/{{ item }}_crt_generated + environment: + subjectAltName: "DNS:{{ item }}" + with_items: "{{ users }}" + +- name: Build the client's p12 + local_action: > + shell openssl pkcs12 -in certs/{{ item }}.crt -inkey private/{{ item }}.key -export -name {{ item }} -out private/{{ item }}.p12 -certfile cacert.pem -passout pass:"{{ easyrsa_p12_export_password }}" + become: no + args: + chdir: "configs/{{ IP_subject_alt_name }}/pki/" + with_items: "{{ users }}" + +- name: Copy the p12 certificates + local_action: + module: copy + src: "configs/{{ IP_subject_alt_name }}/pki/private/{{ item }}.p12" + dest: "configs/{{ IP_subject_alt_name }}/{{ item }}.p12" + mode: 0600 + become: no + with_items: + - "{{ users }}" diff --git a/roles/vpn/tasks/ubuntu.yml b/roles/vpn/tasks/ubuntu.yml new file mode 100644 index 0000000..d00896f --- /dev/null +++ b/roles/vpn/tasks/ubuntu.yml @@ -0,0 +1,52 @@ +--- + +- set_fact: + strongswan_additional_plugins: [] + +- name: Ubuntu | Install StrongSwan + apt: name=strongswan state=latest update_cache=yes install_recommends=yes + +- name: Ubuntu | Enforcing ipsec with apparmor + shell: aa-enforce "{{ item }}" + when: apparmor_enabled is defined and apparmor_enabled == true + with_items: + - /usr/lib/ipsec/charon + - /usr/lib/ipsec/lookip + - /usr/lib/ipsec/stroke + notify: + - restart apparmor + tags: ['apparmor'] + +- name: Ubuntu | Enable services + service: name={{ item }} enabled=yes + with_items: + - apparmor + - strongswan + - netfilter-persistent + +- name: Ubuntu | Configure iptables so IPSec traffic can traverse the tunnel + iptables: table=nat chain=POSTROUTING source="{{ vpn_network }}" jump=MASQUERADE + when: (security_enabled is not defined) or + (security_enabled is defined and security_enabled != "y") + notify: + - save iptables + +- name: Ubuntu | Configure ip6tables so IPSec traffic can traverse the tunnel + iptables: ip_version=ipv6 table=nat chain=POSTROUTING source="{{ vpn_network_ipv6 }}" jump=MASQUERADE + when: ((security_enabled is not defined) or + (security_enabled is defined and security_enabled != "y")) and + ipv6_support is defined and ipv6_support == "yes" + notify: + - save iptables + +- name: Ubuntu | Ensure that the strongswan service directory exist + file: path=/etc/systemd/system/strongswan.service.d/ state=directory mode=0755 owner=root group=root + +- name: Ubuntu | Setup the cgroup limitations for the ipsec daemon + template: src=100-CustomLimitations.conf.j2 dest=/etc/systemd/system/strongswan.service.d/100-CustomLimitations.conf + notify: + - daemon-reload + - restart strongswan + +- include: iptables.yml + tags: iptables diff --git a/roles/vpn/templates/strongswan.conf.j2 b/roles/vpn/templates/strongswan.conf.j2 index 4eab82f..5e66cb2 100644 --- a/roles/vpn/templates/strongswan.conf.j2 +++ b/roles/vpn/templates/strongswan.conf.j2 @@ -11,6 +11,16 @@ charon { } user = strongswan group = strongswan + + filelog { + /var/log/charon.log { + time_format = %b %e %T + ike_name = yes + append = no + default = 1 + flush_line = yes + } + } } include strongswan.d/*.conf diff --git a/users.yml b/users.yml index 105c9be..314858d 100644 --- a/users.yml +++ b/users.yml @@ -36,6 +36,9 @@ - config.cfg pre_tasks: + - name: Common pre-tasks + include: playbooks/common.yml + - set_fact: IP_subject_alt_name: "{{ IP_subject }}" easyrsa_p12_export_password: "{{ p12_export_password|default((ansible_date_time.iso8601_basic|sha1|to_uuid).split('-')[0]) }}" @@ -117,7 +120,7 @@ - name: Copy the revoked certificates to the vpn server copy: src: configs/{{ IP_subject_alt_name }}/pki/crl/{{ item }}.crt - dest: /etc/ipsec.d/crls/{{ item }}.crt + dest: "{{ config_prefix|default('/') }}etc/ipsec.d/crls/{{ item }}.crt" when: item not in users with_items: "{{ valid_certs.stdout_lines }}" notify: