commit 5fbb82184807deebbd2d7a261450957d9ca5dec7 Author: Dan Guido Date: Fri Jul 29 22:21:33 2016 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a309864 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.retry +configs/*.mobileconfig +configs/*.p12 +configs/*.crt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7365a69 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Trail of Bits + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac3f38d --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +##### Local requirements: +* ansible >= 2.2.0 +* python >= 2.6 +* dopy + +##### How to run: +* Open the file `config.cfg` in your favorite text editor and change variables. At least you should change `server_name`, and specify users in `users` list. +* Start to deploy and follow the instructions: +``` +ansible-playbook deploy.yml +``` +* When the process is done, you can see `.mobileconfig` files and certificates in the directory - `configs`. Send `.mobileconfig` to your users for using on iPhones or MacOS or send certificates for using on other clients (StrongSwan client for Android or native IKEv2 client for Windows) + +* When the deploy proccess is done a new server will be placed in the local inventory file - `inventory_users` + +* If you want to add or delete users, just update the (`users`) list in the config file (`config.cfg`) and then run the playbook: +(This command will update users on all your servers in the file `inventory_users`, if you want to limit servers, you can use option `-l` ) +``` +ansible-playbook users.yml -i inventory_users +ansible-playbook users.yml -i inventory_users -l vpnserver.com +``` + diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..4d407ab --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,4 @@ +[defaults] +inventory = inventory +pipelining = True +retry_files_enabled = False diff --git a/cloud.yml b/cloud.yml new file mode 100644 index 0000000..db10f15 --- /dev/null +++ b/cloud.yml @@ -0,0 +1,78 @@ +- name: Configure the server and install required software + hosts: localhost + + vars: + regions: + "1": "ams2" + "2": "ams3" + "3": "fra1" + "4": "lon1" + "5": "nyc1" + "6": "nyc2" + "7": "nyc3" + "8": "sfo1" + "9": "sgp1" + "10": "tor1" + + vars_prompt: + - name: "do_access_token" + prompt: "Enter your API Token (https://cloud.digitalocean.com/settings/api/tokens):\n" + private: yes + + - name: "do_ssh_name" + prompt: "Enter a valid SSH key name (https://cloud.digitalocean.com/settings/security):\n" + private: no + + - name: "do_region" + prompt: > + What region should the server be located in? + 1. Amsterdam (Datacenter 2) + 2. Amsterdam (Datacenter 3) + 3. Frankfurt + 4. London + 5. New York (Datacenter 1) + 6. New York (Datacenter 2) + 7. New York (Datacenter 3) + 8. San Francisco + 9. Singapore + 10. Toronto + Please choose the number of your region. Press enter for default (#7) region. + default: "7" + private: no + + - name: "do_server_name" + prompt: "Name the vpn server:\n" + default: "strongswan.local" + private: no + + tasks: + - name: "Getting your SSH key ID on Digital Ocean..." + digital_ocean: + state: present + command: ssh + name: "{{ do_ssh_name }}" + api_token: "{{ do_access_token }}" + register: do_ssh_key + + - name: "Creating a droplet..." + digital_ocean: + state: present + command: droplet + name: "{{ do_server_name }}" + region_id: "{{ regions[do_region] }}" + size_id: "512mb" + image_id: "ubuntu-16-04-x64" + ssh_key_ids: "{{ do_ssh_key.ssh_key.id }}" + unique_name: yes + api_token: "{{ do_access_token }}" + register: do + + - name: Add the droplet to an inventory group + add_host: + name: "{{ do.droplet.ip_address }}" + groups: vpn-host + ansible_python_interpreter: "/usr/bin/python2.7" + + - name: Pause to let DigitalOcean boot up the VM + pause: seconds=180 + diff --git a/common.yml b/common.yml new file mode 100644 index 0000000..a567270 --- /dev/null +++ b/common.yml @@ -0,0 +1,96 @@ +--- + +- name: Common tools + hosts: vpn-host + remote_user: root + vars_files: + - config.cfg + + tasks: + - name: Wait for port 22 to become available + local_action: "wait_for port=22 host={{ inventory_hostname }}" + + - name: Updating apt-get + raw: apt-get update -qq + + - name: Install python2.7 for Ansible + raw: apt-get install -qq -y python2.7 + + - name: Install Updates, Patches and Additional Security Software + apt: upgrade=dist update_cache=yes + + - name: Check if reboot is required + shell: > + if [[ $(readlink -f /vmlinuz) != /boot/vmlinuz-$(uname -r) ]]; then echo "required"; else echo "no"; fi + args: + executable: /bin/bash + register: reboot_required + + - 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 + + - name: Wait for shutdown + local_action: wait_for host={{ inventory_hostname }} port=22 state=stopped timeout=120 + when: reboot_required is defined and reboot_required.stdout == 'required' + + - name: Wait until SSH becomes ready... + local_action: wait_for host={{ inventory_hostname }} port=22 state=started timeout=120 + when: reboot_required is defined and reboot_required.stdout == 'required' + + # SSH fixes + + - name: SSH config + lineinfile: dest="{{ item.file }}" regexp="{{ item.regexp }}" line="{{ item.line }}" state=present + with_items: + - { regexp: '^PasswordAuthentication.*', line: 'PasswordAuthentication no', file: '/etc/ssh/sshd_config' } + - { regexp: '^PermitRootLogin.*', line: 'PermitRootLogin without-password', file: '/etc/ssh/sshd_config' } + - { regexp: '^UseDNS.*', line: 'UseDNS no', file: '/etc/ssh/sshd_config' } + - { regexp: '^Ciphers', line: 'Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes128-ctr', file: '/etc/ssh/sshd_config' } + - { regexp: '^MACs', line: 'MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160', file: '/etc/ssh/sshd_config' } + - { regexp: '^KexAlgorithms', line: 'KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1', file: '/etc/ssh/sshd_config' } + notify: + - restart ssh + + - name: PAM config + 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' } + + - name: Install tools + apt: name="{{ item }}" state=latest + with_items: + - git + - screen + - apparmor-utils + - uuid-runtime + - coreutils + - auditd + - rsyslog + - sendmail + - unattended-upgrades + + - name: Unattended-upgrades configured + template: src=50unattended-upgrades.j2 dest=/etc/apt/apt.conf.d/50unattended-upgrades owner=root group=root mode=644 + + - name: Periodic upgrades configured + template: src=10periodic.j2 dest=/etc/apt/apt.conf.d/10periodic owner=root group=root mode=644 + + handlers: + - name: restart auditd + service: name=auditd state=restarted + + - name: restart rsyslog + service: name=rsyslog state=restarted + + - name: restart ssh + service: name=ssh state=restarted + + - name: flush routing cache + shell: echo 1 > /proc/sys/net/ipv4/route/flush + + \ No newline at end of file diff --git a/config.cfg b/config.cfg new file mode 100644 index 0000000..7b98fc5 --- /dev/null +++ b/config.cfg @@ -0,0 +1,28 @@ +--- + +# +# IKEv2 currently supports only the following three curves: +# prime256v1 +# secp384r1 +# secp521r1 +easyrsa_dir: /opt/easy-rsa-ipsec +easyrsa_ca_expire: 3650 +easyrsa_cert_expire: 3650 +easyrsa_p12_export_password: vpn + +# if True re-init all existing certificates. Boolean +easyrsa_reinit_existent: False + +# Domain or ip +server_name: www.ivlis.me +server_ip: "{{ ansible_ssh_host }}" + +users: + - mr.smith + - mrs.smith + +# +# auditd options +# email for auditd actions: +auditd_action_mail_acct: e601809@gmail.com + diff --git a/configs/.gitinit b/configs/.gitinit new file mode 100644 index 0000000..e69de29 diff --git a/deploy.yml b/deploy.yml new file mode 100644 index 0000000..44a2bd5 --- /dev/null +++ b/deploy.yml @@ -0,0 +1,6 @@ +--- + +- include: cloud.yml +- include: common.yml +- include: security.yml +- include: vpn.yml diff --git a/inventory b/inventory new file mode 100644 index 0000000..5b1a53f --- /dev/null +++ b/inventory @@ -0,0 +1,5 @@ +[localhost] +127.0.0.1 ansible_connection=local + +[vpn-host] +45.55.244.205 ansible_python_interpreter=/usr/bin/python2.7 diff --git a/inventory_users b/inventory_users new file mode 100644 index 0000000..4b3531e --- /dev/null +++ b/inventory_users @@ -0,0 +1,2 @@ +[users-management] +45.55.244.205 diff --git a/security.yml b/security.yml new file mode 100644 index 0000000..449645a --- /dev/null +++ b/security.yml @@ -0,0 +1,134 @@ +--- + +- name: Security fixes + hosts: vpn-host + remote_user: root + vars_files: + - config.cfg + + tasks: + # Using a two-pass approach for checking directories in order to support symlinks. + - name: Find directories for minimizing access + stat: + path: "{{ item }}" + register: minimize_access_directories + with_items: + - '/usr/local/sbin' + - '/usr/local/bin' + - '/usr/sbin' + - '/usr/bin' + - '/sbin' + - '/bin' + + - name: Minimize access + file: path='{{ item.stat.path }}' mode='go-w' recurse=yes + when: item.stat.isdir + with_items: "{{ minimize_access_directories.results }}" + no_log: True + + - name: Change shadow ownership to root and mode to 0600 + file: dest='/etc/shadow' owner=root group=root mode=0600 + + - name: change su-binary to only be accessible to user and group root + file: dest='/bin/su' owner=root group=root mode=0750 + + # auditd + + - name: Collect Use of Privileged Commands + shell: > + /usr/bin/find {/usr/local/sbin,/usr/local/bin,/sbin,/bin,/usr/sbin,/usr/bin} -xdev \( -perm -4000 -o -perm -2000 \) -type f | awk '{print "-a always,exit -F path=" $1 " -F perm=x -F auid>=500 -F auid!=4294967295 -k privileged" }' + args: + executable: /bin/bash + register: privileged_programs + + - name: Auditd rules configured + template: src=audit.rules.j2 dest=/etc/audit/audit.rules + notify: + - restart auditd + + - name: Auditd configured + template: src=auditd.conf.j2 dest=/etc/audit/auditd.conf + notify: + - restart auditd + + # Rsyslog + + - name: Rsyslog configured + template: src=rsyslog.conf.j2 dest=/etc/rsyslog.conf + notify: + - restart rsyslog + + - name: Rsyslog CIS configured + template: src=CIS.conf.j2 dest=/etc/rsyslog.d/CIS.conf owner=root group=root mode=0644 + notify: + - restart rsyslog + + - name: Enable services + service: name={{ item }} enabled=yes + with_items: + - auditd + - rsyslog + + # Core dumps + + - name: Restrict Core Dumps - using pam limits + lineinfile: dest=/etc/security/limits.conf line="* hard core 0" state=present + + - name: Restrict Core Dumps - using sysctl + sysctl: name=fs.suid_dumpable value=0 ignoreerrors=yes sysctl_set=yes reload=yes state=present + + # Kernel fixes + + - name: Disable Source Routed Packet Acceptance + sysctl: name="{{item}}" value=0 ignoreerrors=yes sysctl_set=yes reload=yes state=present + with_items: + - net.ipv4.conf.all.accept_source_route + - net.ipv4.conf.default.accept_source_route + notify: + - flush routing cache + + - name: Disable ICMP Redirect Acceptance + sysctl: name="{{item}}" value=0 ignoreerrors=yes sysctl_set=yes reload=yes state=present + with_items: + - net.ipv4.conf.all.accept_redirects + - net.ipv4.conf.default.accept_redirects + + - name: Disable Secure ICMP Redirect Acceptance + sysctl: name="{{item}}" value=0 ignoreerrors=yes sysctl_set=yes reload=yes state=present + with_items: + - net.ipv4.conf.all.secure_redirects + - net.ipv4.conf.default.secure_redirects + notify: + - flush routing cache + + - name: Enable Bad Error Message Protection (Scored) + sysctl: name=net.ipv4.icmp_ignore_bogus_error_responses value=1 ignoreerrors=yes sysctl_set=yes reload=yes state=present + notify: + - flush routing cache + + - name: Enable RFC-recommended Source Route Validation (Scored) + sysctl: name="{{item}}" value=1 ignoreerrors=yes sysctl_set=yes reload=yes state=present + with_items: + - net.ipv4.conf.all.rp_filter + - net.ipv4.conf.default.rp_filter + notify: + - flush routing cache + + - name: Enable packet forwarding for IPv4 + sysctl: name=net.ipv4.ip_forward value=1 + + - name: Do not send ICMP redirects (we are not a router) + sysctl: name=net.ipv4.conf.all.send_redirects value=0 + + + handlers: + - name: restart auditd + service: name=auditd state=restarted + + - name: restart rsyslog + service: name=rsyslog state=restarted + + - name: flush routing cache + shell: echo 1 > /proc/sys/net/ipv4/route/flush + + \ No newline at end of file diff --git a/templates/CIS.conf.j2 b/templates/CIS.conf.j2 new file mode 100644 index 0000000..96b3a59 --- /dev/null +++ b/templates/CIS.conf.j2 @@ -0,0 +1,15 @@ +*.emerg :omusrmsg:* +mail.* -/var/log/mail +mail.info -/var/log/mail.info +mail.warning -/var/log/mail.warn +mail.err /var/log/mail.err +news.crit -/var/log/news/news.crit +news.err -/var/log/news/news.err +news.notice -/var/log/news/news.notice +*.=warning;*.=err -/var/log/warn +*.crit /var/log/warn +*.*;mail.none;news.none -/var/log/messages +local0,local1.* -/var/log/localmessages +local2,local3.* -/var/log/localmessages +local4,local5.* -/var/log/localmessages +local6,local7.* -/var/log/localmessages \ No newline at end of file diff --git a/templates/audit.rules.j2 b/templates/audit.rules.j2 new file mode 100644 index 0000000..3464e2a --- /dev/null +++ b/templates/audit.rules.j2 @@ -0,0 +1,101 @@ +# This file contains the auditctl rules that are loaded +# whenever the audit daemon is started via the initscripts. +# The rules are simply the parameters that would be passed +# to auditctl. +# +# First rule - delete all +-D + +# Increase the buffers to survive stress events. +# Make this bigger for busy systems +-b 320 + +# Feel free to add below this line. See auditctl man page + +# Record Events That Modify Date and Time Information +{% if ansible_architecture == "x86_64" %} +-a always,exit -F arch=b64 -S clock_settime -k time-change +-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change +{% endif %} +-a always,exit -F arch=b32 -S clock_settime -k time-change +-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change +-w /etc/localtime -p wa -k time-change + +# Record Events That Modify User/Group Information +-w /etc/group -p wa -k identity +-w /etc/passwd -p wa -k identity +-w /etc/gshadow -p wa -k identity +-w /etc/shadow -p wa -k identity +-w /etc/security/opasswd -p wa -k identity + +# Record Events That Modify the System's Network Environment +{% if ansible_architecture == "x86_64" %} +-a exit,always -F arch=b64 -S sethostname -S setdomainname -k system-locale +{% endif %} +-a exit,always -F arch=b32 -S sethostname -S setdomainname -k system-locale +-w /etc/issue -p wa -k system-locale +-w /etc/issue.net -p wa -k system-locale +-w /etc/hosts -p wa -k system-locale +-w /etc/network/interfaces -p wa -k system-locale + +# Collect Login and Logout Events +-w /var/log/faillog -p wa -k logins +-w /var/log/lastlog -p wa -k logins +-w /var/log/tallylog -p wa -k logins + +# Collect Session Initiation Information +-w /var/run/utmp -p wa -k session +-w /var/log/wtmp -p wa -k session +-w /var/log/btmp -p wa -k session + +# Collect Discretionary Access Control Permission Modification Events +{% if ansible_architecture == "x86_64" %} +-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod +{% endif %} +-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + +# Collect Unsuccessful Unauthorized Access Attempts to Files +{% if ansible_architecture == "x86_64" %} +-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access +-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access +{% endif %} +-a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access +-a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access + +# Collect Use of Privileged Commands +{% if privileged_programs is defined and privileged_programs.stdout_lines|length > 0 %} +{{ privileged_programs.stdout }} +{% endif %} + +# Collect Successful File System Mounts +{% if ansible_architecture == "x86_64" %} +-a always,exit -F arch=b64 -S mount -F auid>=500 -F auid!=4294967295 -k mounts +{% endif %} +-a always,exit -F arch=b32 -S mount -F auid>=500 -F auid!=4294967295 -k mounts + +# Collect File Deletion Events by User +{% if ansible_architecture == "x86_64" %} +-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete +{% endif %} +-a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete + +# Collect Changes to System Administration Scope +-w /etc/sudoers -p wa -k scope + +# Collect System Administrator Actions (sudolog) +-w /var/log/sudo.log -p wa -k actions + +# Collect Kernel Module Loading and Unloading +{% if ansible_architecture == "x86_64" %} +-a always,exit -F arch=b64 -S init_module -S delete_module -k modules +{% endif %} +-a always,exit -F arch=b32 -S init_module -S delete_module -k modules +-w /sbin/insmod -p x -k modules +-w /sbin/rmmod -p x -k modules +-w /sbin/modprobe -p x -k modules + +-e 2 diff --git a/templates/auditd.conf.j2 b/templates/auditd.conf.j2 new file mode 100644 index 0000000..24aac73 --- /dev/null +++ b/templates/auditd.conf.j2 @@ -0,0 +1,32 @@ +# +# This file controls the configuration of the audit daemon +# + +log_file = /var/log/audit/audit.log +log_format = RAW +log_group = root +priority_boost = 4 +flush = INCREMENTAL +freq = 20 +num_logs = 5 +disp_qos = lossy +dispatcher = /sbin/audispd +name_format = NONE +##name = mydomain +max_log_file = 10 +max_log_file_action = keep_logs +space_left = 75 +space_left_action = email +action_mail_acct = {{ auditd_action_mail_acct }} +admin_space_left = 50 +admin_space_left_action = email +disk_full_action = SUSPEND +disk_error_action = SUSPEND +##tcp_listen_port = +tcp_listen_queue = 5 +tcp_max_per_addr = 1 +##tcp_client_ports = 1024-65535 +tcp_client_max_idle = 0 +enable_krb5 = no +krb5_principal = auditd +##krb5_key_file = /etc/audit/audit.key \ No newline at end of file diff --git a/templates/easy-rsa.vars.j2 b/templates/easy-rsa.vars.j2 new file mode 100644 index 0000000..19447c6 --- /dev/null +++ b/templates/easy-rsa.vars.j2 @@ -0,0 +1,198 @@ +# Easy-RSA 3 parameter settings + +# NOTE: If you installed Easy-RSA from your distro's package manager, don't edit +# this file in place -- instead, you should copy the entire easy-rsa directory +# to another location so future upgrades don't wipe out your changes. + +# HOW TO USE THIS FILE +# +# vars.example contains built-in examples to Easy-RSA settings. You MUST name +# this file 'vars' if you want it to be used as a configuration file. If you do +# not, it WILL NOT be automatically read when you call easyrsa commands. +# +# It is not necessary to use this config file unless you wish to change +# operational defaults. These defaults should be fine for many uses without the +# need to copy and edit the 'vars' file. +# +# All of the editable settings are shown commented and start with the command +# 'set_var' -- this means any set_var command that is uncommented has been +# modified by the user. If you're happy with a default, there is no need to +# define the value to its default. + +# NOTES FOR WINDOWS USERS +# +# Paths for Windows *MUST* use forward slashes, or optionally double-esscaped +# backslashes (single forward slashes are recommended.) This means your path to +# the openssl binary might look like this: +# "C:/Program Files/OpenSSL-Win32/bin/openssl.exe" + +# A little housekeeping: DON'T EDIT THIS SECTION +# +# Easy-RSA 3.x doesn't source into the environment directly. +# Complain if a user tries to do this: +if [ -z "$EASYRSA_CALLER" ]; then + echo "You appear to be sourcing an Easy-RSA 'vars' file." >&2 + echo "This is no longer necessary and is disallowed. See the section called" >&2 + echo "'How to use this file' near the top comments for more details." >&2 + return 1 +fi + +# DO YOUR EDITS BELOW THIS POINT + +# This variable should point to the top level of the easy-rsa tree. By default, +# this is taken to be the directory you are currently in. + +set_var EASYRSA "{{ easyrsa_dir }}/easyrsa3/" + +# If your OpenSSL command is not in the system PATH, you will need to define the +# path to it here. Normally this means a full path to the executable, otherwise +# you could have left it undefined here and the shown default would be used. +# +# Windows users, remember to use paths with forward-slashes (or escaped +# back-slashes.) Windows users should declare the full path to the openssl +# binary here if it is not in their system PATH. + +#set_var EASYRSA_OPENSSL "openssl" +# +# This sample is in Windows syntax -- edit it for your path if not using PATH: +#set_var EASYRSA_OPENSSL "C:/Program Files/OpenSSL-Win32/bin/openssl.exe" + +# Edit this variable to point to your soon-to-be-created key directory. +# +# WARNING: init-pki will do a rm -rf on this directory so make sure you define +# it correctly! (Interactive mode will prompt before acting.) + +set_var EASYRSA_PKI "$EASYRSA/pki" + +# Define X509 DN mode. +# This is used to adjust what elements are included in the Subject field as the DN +# (this is the "Distinguished Name.") +# Note that in cn_only mode the Organizational fields further below aren't used. +# +# Choices are: +# cn_only - use just a CN value +# org - use the "traditional" Country/Province/City/Org/OU/email/CN format + +set_var EASYRSA_DN "cn_only" + +# Organizational fields (used with 'org' mode and ignored in 'cn_only' mode.) +# These are the default values for fields which will be placed in the +# certificate. Don't leave any of these fields blank, although interactively +# you may omit any specific field by typing the "." symbol (not valid for +# email.) + +#set_var EASYRSA_REQ_COUNTRY "US" +#set_var EASYRSA_REQ_PROVINCE "California" +#set_var EASYRSA_REQ_CITY "San Francisco" +#set_var EASYRSA_REQ_ORG "Copyleft Certificate Co" +#set_var EASYRSA_REQ_EMAIL "me@example.net" +#set_var EASYRSA_REQ_OU "My Organizational Unit" + +# Choose a size in bits for your keypairs. The recommended value is 2048. Using +# 2048-bit keys is considered more than sufficient for many years into the +# future. Larger keysizes will slow down TLS negotiation and make key/DH param +# generation take much longer. Values up to 4096 should be accepted by most +# software. Only used when the crypto alg is rsa (see below.) + +# set_var EASYRSA_KEY_SIZE 2048 + +# The default crypto mode is rsa; ec can enable elliptic curve support. +# Note that not all software supports ECC, so use care when enabling it. +# Choices for crypto alg are: (each in lower-case) +# * rsa +# * ec + +set_var EASYRSA_ALGO ec + +# Define the named curve, used in ec mode only: + +set_var EASYRSA_CURVE prime256v1 + +# In how many days should the root CA key expire? + +set_var EASYRSA_CA_EXPIRE {{ easyrsa_ca_expire }} + +# In how many days should certificates expire? + +set_var EASYRSA_CERT_EXPIRE {{ easyrsa_cert_expire }} + +# How many days until the next CRL publish date? Note that the CRL can still be +# parsed after this timeframe passes. It is only used for an expected next +# publication date. + +#set_var EASYRSA_CRL_DAYS 180 + +# Support deprecated "Netscape" extensions? (choices "yes" or "no".) The default +# is "no" to discourage use of deprecated extensions. If you require this +# feature to use with --ns-cert-type, set this to "yes" here. This support +# should be replaced with the more modern --remote-cert-tls feature. If you do +# not use --ns-cert-type in your configs, it is safe (and recommended) to leave +# this defined to "no". When set to "yes", server-signed certs get the +# nsCertType=server attribute, and also get any NS_COMMENT defined below in the +# nsComment field. + +#set_var EASYRSA_NS_SUPPORT "no" + +# When NS_SUPPORT is set to "yes", this field is added as the nsComment field. +# Set this blank to omit it. With NS_SUPPORT set to "no" this field is ignored. + +#set_var EASYRSA_NS_COMMENT "Easy-RSA Generated Certificate" + +# A temp file used to stage cert extensions during signing. The default should +# be fine for most users; however, some users might want an alternative under a +# RAM-based FS, such as /dev/shm or /tmp on some systems. + +#set_var EASYRSA_TEMP_FILE "$EASYRSA_PKI/extensions.temp" + +# !! +# NOTE: ADVANCED OPTIONS BELOW THIS POINT +# PLAY WITH THEM AT YOUR OWN RISK +# !! + +# Broken shell command aliases: If you have a largely broken shell that is +# missing any of these POSIX-required commands used by Easy-RSA, you will need +# to define an alias to the proper path for the command. The symptom will be +# some form of a 'command not found' error from your shell. This means your +# shell is BROKEN, but you can hack around it here if you really need. These +# shown values are not defaults: it is up to you to know what you're doing if +# you touch these. +# +#alias awk="/alt/bin/awk" +#alias cat="/alt/bin/cat" + +# X509 extensions directory: +# If you want to customize the X509 extensions used, set the directory to look +# for extensions here. Each cert type you sign must have a matching filename, +# and an optional file named 'COMMON' is included first when present. Note that +# when undefined here, default behaviour is to look in $EASYRSA_PKI first, then +# fallback to $EASYRSA for the 'x509-types' dir. You may override this +# detection with an explicit dir here. +# +#set_var EASYRSA_EXT_DIR "$EASYRSA/x509-types" + +# OpenSSL config file: +# If you need to use a specific openssl config file, you can reference it here. +# Normally this file is auto-detected from a file named openssl-1.0.cnf from the +# EASYRSA_PKI or EASYRSA dir (in that order.) NOTE that this file is Easy-RSA +# specific and you cannot just use a standard config file, so this is an +# advanced feature. + +set_var EASYRSA_SSL_CONF "$EASYRSA/openssl-1.0.cnf" + +# Default CN: +# This is best left alone. Interactively you will set this manually, and BATCH +# callers are expected to set this themselves. + +set_var EASYRSA_REQ_CN "{{ server_name }}" + +# Cryptographic digest to use. +# Do not change this default unless you understand the security implications. +# Valid choices include: md5, sha1, sha256, sha224, sha384, sha512 + +#set_var EASYRSA_DIGEST "sha256" + +# Batch mode. Leave this disabled unless you intend to call Easy-RSA explicitly +# in batch mode without any user input, confirmation on dangerous operations, +# or most output. Setting this to any non-blank string enables batch mode. + +set_var EASYRSA_BATCH "{{ server_name }}" \ No newline at end of file diff --git a/templates/ipsec.conf.j2 b/templates/ipsec.conf.j2 new file mode 100644 index 0000000..6020256 --- /dev/null +++ b/templates/ipsec.conf.j2 @@ -0,0 +1,29 @@ +config setup + uniqueids = never # allow multiple connections per user + charondebug="ike 2, knl 2, cfg 2, net 2, esp 2, dmn 2, mgr 2" + +conn %default + dpdaction=clear + dpddelay=35s + dpdtimeout=300s + rekey=no + keyexchange=ikev2 + ike=aes128gcm16-sha2_256-prfsha256-ecp256! + esp=aes128gcm16-sha2_256-ecp256! + compress=yes + fragmentation=yes + + left=%any + leftauth=pubkey + leftid={{ server_name }} + leftcert={{ server_name }}.crt + leftsendcert=always + leftsubnet=0.0.0.0/0,::/0 + + right=%any + rightauth=pubkey + rightsourceip=10.0.0.0/24 + rightdns=8.8.8.8,8.8.4.4 + +conn ikev2-pubkey + auto=add \ No newline at end of file diff --git a/templates/ipsec.secrets.j2 b/templates/ipsec.secrets.j2 new file mode 100644 index 0000000..4cae96e --- /dev/null +++ b/templates/ipsec.secrets.j2 @@ -0,0 +1,2 @@ +: ECDSA {{ server_name }}.key + diff --git a/templates/mobileconfig.j2 b/templates/mobileconfig.j2 new file mode 100644 index 0000000..1fd2816 --- /dev/null +++ b/templates/mobileconfig.j2 @@ -0,0 +1,144 @@ + + + + + PayloadContent + + + IKEv2 + + AuthenticationMethod + Certificate + ChildSecurityAssociationParameters + + DiffieHellmanGroup + 19 + EncryptionAlgorithm + AES-128-GCM + IntegrityAlgorithm + SHA2-256 + LifeTimeInMinutes + 1440 + + DeadPeerDetectionRate + Medium + DisableMOBIKE + 0 + DisableRedirect + 0 + EnableCertificateRevocationCheck + 0 + EnablePFS + + IKESecurityAssociationParameters + + DiffieHellmanGroup + 19 + EncryptionAlgorithm + AES-128-GCM + IntegrityAlgorithm + SHA2-256 + LifeTimeInMinutes + 1440 + + LocalIdentifier + {{ item.0 }} + PayloadCertificateUUID + 1FB2907D-14D3-4BAB-A472-B304F4B7F7D9 + CertificateType + ECDSA256 + ServerCertificateIssuerCommonName + {{ server_name }} + RemoteAddress + {{ server_name }} + RemoteIdentifier + {{ server_name }} + UseConfigurationAttributeInternalIPSubnet + 0 + + IPv4 + + OverridePrimary + 1 + + PayloadDescription + Configures VPN settings + PayloadDisplayName + VPN + PayloadIdentifier + com.apple.vpn.managed.D247A30B-6023-4C8E-B3E3-FF1910A65E53 + PayloadType + com.apple.vpn.managed + PayloadUUID + D247A30B-6023-4C8E-B3E3-FF1910A65E53 + PayloadVersion + 1 + Proxies + + HTTPEnable + 0 + HTTPSEnable + 0 + + UserDefinedName + {{ server_name }} IKEv2 + VPNType + IKEv2 + + + Password + {{ easyrsa_p12_export_password }} + PayloadCertificateFileName + {{ item.0 }}.p12 + PayloadContent + + {{ item.1.stdout }} + + PayloadDescription + Adds a PKCS#12-formatted certificate + PayloadDisplayName + {{ item.0 }}.p12 + PayloadIdentifier + com.apple.security.pkcs12.1FB2907D-14D3-4BAB-A472-B304F4B7F7D9 + PayloadType + com.apple.security.pkcs12 + PayloadUUID + 1FB2907D-14D3-4BAB-A472-B304F4B7F7D9 + PayloadVersion + 1 + + + PayloadCertificateFileName + ca.crt + PayloadContent + + {{ PayloadContentCA.stdout }} + + PayloadDescription + Adds a CA root certificate + PayloadDisplayName + {{ server_name }} + PayloadIdentifier + com.apple.security.root.32EA3AAA-D19E-43EF-B357-608218745A38 + PayloadType + com.apple.security.root + PayloadUUID + 32EA3AAA-D19E-43EF-B357-608218745A38 + PayloadVersion + 1 + + + PayloadDisplayName + {{ server_name }} IKEv2 + PayloadIdentifier + donut.local.37CA79B1-FC6A-421F-960A-90F91FC983BE + PayloadRemovalDisallowed + + PayloadType + Configuration + PayloadUUID + 743B04A8-5725-45A2-B1BB-836F8C16DB0A + PayloadVersion + 1 + + diff --git a/templates/rsyslog.conf.j2 b/templates/rsyslog.conf.j2 new file mode 100644 index 0000000..2551380 --- /dev/null +++ b/templates/rsyslog.conf.j2 @@ -0,0 +1,61 @@ +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html +# +# Default logging rules can be found in /etc/rsyslog.d/50-default.conf + +# +################# +#### MODULES #### +################# + +module(load="imuxsock") # provides support for local system logging +module(load="imklog") # provides kernel logging support +#module(load="immark") # provides --MARK-- message capability + +# provides UDP syslog reception +#module(load="imudp") +#input(type="imudp" port="514") + +# provides TCP syslog reception +#module(load="imtcp") +#input(type="imtcp" port="514") + +# Enable non-kernel facility klog messages +$KLogPermitNonKernelFacility on + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# Filter duplicated messages +$RepeatedMsgReduction on + +# +# Set the default permissions for all log files. +# +$FileOwner syslog +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 +$PrivDropToUser syslog +$PrivDropToGroup syslog + +# +# Where to place spool and state files +# +$WorkDirectory /var/spool/rsyslog + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + diff --git a/users.yml b/users.yml new file mode 100644 index 0000000..2b68ad9 --- /dev/null +++ b/users.yml @@ -0,0 +1,74 @@ +--- + +- name: Users management + hosts: users-management + gather_facts: false + remote_user: root + vars_files: + - config.cfg + + tasks: + - name: Build the client's pair + shell: > + ./easyrsa build-client-full {{ item }} nopass && + touch '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_initialized' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_initialized' + with_items: "{{ users }}" + + - name: Build the client's p12 + shell: > + openssl pkcs12 -in {{ easyrsa_dir }}/easyrsa3//pki/issued/{{ item }}.crt -inkey {{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.key -export -name {{ item }} -out /{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.p12 -certfile {{ easyrsa_dir }}/easyrsa3//pki/ca.crt -passout pass:{{ easyrsa_p12_export_password }} && + touch '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_p12_initialized' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_p12_initialized' + with_items: "{{ users }}" + + - name: Get active users + shell: > + grep ^V pki/index.txt | grep -v "{{ server_name }}" | awk '{print $5}' | sed 's/\/CN=//g' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + register: valid_certs + + - name: Revoke non-existing users + shell: > + ipsec pki --signcrl --cacert {{ easyrsa_dir }}/easyrsa3//pki/ca.crt --cakey {{ easyrsa_dir }}/easyrsa3/pki/private/ca.key --reason superseded --cert {{ easyrsa_dir }}/easyrsa3//pki/issued/{{ item }}.crt > /etc/ipsec.d/crls/{{ item }}.der && + ./easyrsa revoke {{ item }} && + ipsec rereadcrls + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + when: item not in users + with_items: "{{ valid_certs.stdout_lines }}" + + - name: Register p12 PayloadContent + shell: > + cat /{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.p12 | base64 + register: PayloadContent + with_items: "{{ users }}" + + - name: Register CA PayloadContent + shell: > + cat /{{ easyrsa_dir }}/easyrsa3/pki/ca.crt | base64 + register: PayloadContentCA + + - name: Build the mobileconfigs + template: src=mobileconfig.j2 dest=/{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item.0 }}.mobileconfig mode=0600 + with_together: + - "{{ users }}" + - "{{ PayloadContent.results }}" + no_log: True + + - name: Fetch users P12 + fetch: src=/{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.p12 dest=configs/{{ server_name }}_{{ item }}.p12 flat=yes + with_items: "{{ users }}" + + - name: Fetch users mobileconfig + fetch: src=/{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.mobileconfig dest=configs/{{ server_name }}_{{ item }}.mobileconfig flat=yes + with_items: "{{ users }}" + + - name: Fetch server CA certificate + fetch: src=/{{ easyrsa_dir }}/easyrsa3/pki/ca.crt dest=configs/{{ server_name }}_ca.crt flat=yes + \ No newline at end of file diff --git a/vpn.yml b/vpn.yml new file mode 100644 index 0000000..6abd9f1 --- /dev/null +++ b/vpn.yml @@ -0,0 +1,150 @@ +--- + +- name: VPN Configuration + hosts: vpn-host + gather_facts: false + remote_user: root + vars_files: + - config.cfg + + tasks: + - name: Install StrongSwan + apt: name=strongswan state=latest update_cache=yes + + - name: Enforcing ipsec with apparmor + shell: aa-enforce "{{ item }}" + with_items: + - /usr/lib/ipsec/charon + - /usr/lib/ipsec/lookip + - /usr/lib/ipsec/stroke + notify: + - restart apparmor + + - name: Enable services + service: name={{ item }} enabled=yes + with_items: + - apparmor + - strongswan + + - name: Configure iptables so IPSec traffic can traverse the tunnel + iptables: table=nat chain=POSTROUTING source=10.0.0.0/24 jump=MASQUERADE + + - name: Setup the ipsec.conf file from our template + template: src=ipsec.conf.j2 dest=/etc/ipsec.conf owner=root group=root mode=644 + notify: + - restart strongswan + + - name: Setup the ipsec.secrets file + template: src=ipsec.secrets.j2 dest=/etc/ipsec.secrets owner=root group=root mode=600 + notify: + - restart strongswan + + - name: Fetch easy-rsa-ipsec repo + git: repo=git://github.com/ValdikSS/easy-rsa-ipsec.git dest="{{ easyrsa_dir }}" + + - name: Setup the vars file from our template + template: src=easy-rsa.vars.j2 dest={{ easyrsa_dir }}/easyrsa3/vars + + - name: Ensure the pki directory is not exist + file: dest={{ easyrsa_dir }}/easyrsa3/pki state=absent + when: easyrsa_reinit_existent == True + + - name: Build the pki enviroments + shell: > + ./easyrsa init-pki && + touch '{{ easyrsa_dir }}/easyrsa3/pki/pki_initialized' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/pki_initialized' + + - name: Build the CA pair + shell: > + ./easyrsa build-ca nopass && + touch {{ easyrsa_dir }}/easyrsa3/pki/ca_initialized + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/ca_initialized' + notify: + - restart strongswan + + - name: Build the server pair # TODO: IP and DNS for certificate + shell: > + ./easyrsa build-server-full {{ server_name }} nopass && + touch '{{ easyrsa_dir }}/easyrsa3/pki/server_initialized' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/server_initialized' + notify: + - restart strongswan + + - name: Build the client's pair + shell: > + ./easyrsa build-client-full {{ item }} nopass && + touch '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_initialized' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_initialized' + with_items: "{{ users }}" + + - name: Build the client's p12 + shell: > + openssl pkcs12 -in {{ easyrsa_dir }}/easyrsa3//pki/issued/{{ item }}.crt -inkey {{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.key -export -name {{ item }} -out /{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.p12 -certfile {{ easyrsa_dir }}/easyrsa3//pki/ca.crt -passout pass:{{ easyrsa_p12_export_password }} && + touch '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_p12_initialized' + args: + chdir: '{{ easyrsa_dir }}/easyrsa3/' + creates: '{{ easyrsa_dir }}/easyrsa3/pki/{{ item }}_p12_initialized' + with_items: "{{ users }}" + + - name: Copy the CA cert to the strongswan directory + copy: remote_src=True src='{{ easyrsa_dir }}/easyrsa3/pki/ca.crt' dest=/etc/ipsec.d/cacerts/ca.crt owner=root group=root mode=0600 + notify: + - restart strongswan + + - name: Copy the server cert to the strongswan directory + copy: remote_src=True src='{{ easyrsa_dir }}/easyrsa3/pki/issued/{{ server_name }}.crt' dest=/etc/ipsec.d/certs/{{ server_name }}.crt owner=root group=root mode=0600 + notify: + - restart strongswan + + - name: Copy the server key to the strongswan directory + copy: remote_src=True src='{{ easyrsa_dir }}/easyrsa3/pki/private/{{ server_name }}.key' dest=/etc/ipsec.d/private/{{ server_name }}.key owner=root group=root mode=0600 + notify: + - restart strongswan + + - name: Register p12 PayloadContent + shell: > + cat /{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.p12 | base64 + register: PayloadContent + with_items: "{{ users }}" + + - name: Register CA PayloadContent + shell: > + cat /{{ easyrsa_dir }}/easyrsa3/pki/ca.crt | base64 + register: PayloadContentCA + + - name: Build the mobileconfigs + template: src=mobileconfig.j2 dest=/{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item.0 }}.mobileconfig mode=0600 + with_together: + - "{{ users }}" + - "{{ PayloadContent.results }}" + no_log: True + + - name: Fetch users P12 + fetch: src=/{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.p12 dest=configs/{{ server_name }}_{{ item }}.p12 flat=yes + with_items: "{{ users }}" + + - name: Fetch users mobileconfig + fetch: src=/{{ easyrsa_dir }}/easyrsa3//pki/private/{{ item }}.mobileconfig dest=configs/{{ server_name }}_{{ item }}.mobileconfig flat=yes + with_items: "{{ users }}" + + - name: Fetch server CA certificate + fetch: src=/{{ easyrsa_dir }}/easyrsa3/pki/ca.crt dest=configs/{{ server_name }}_ca.crt flat=yes + + - name: Add server to the inventory file + local_action: lineinfile dest=inventory_users line="{{ inventory_hostname }}" insertafter='\[users-management\]\n' state=present + + handlers: + - name: restart strongswan + service: name=strongswan state=restarted + + - name: restart apparmor + service: name=apparmor state=restarted