Initial commit

This commit is contained in:
Dan Guido 2016-07-29 22:21:33 -04:00
commit 5fbb821848
22 changed files with 1206 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.retry
configs/*.mobileconfig
configs/*.p12
configs/*.crt

21
LICENSE Normal file
View file

@ -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.

22
README.md Normal file
View file

@ -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
```

4
ansible.cfg Normal file
View file

@ -0,0 +1,4 @@
[defaults]
inventory = inventory
pipelining = True
retry_files_enabled = False

78
cloud.yml Normal file
View file

@ -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

96
common.yml Normal file
View file

@ -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

28
config.cfg Normal file
View file

@ -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

0
configs/.gitinit Normal file
View file

6
deploy.yml Normal file
View file

@ -0,0 +1,6 @@
---
- include: cloud.yml
- include: common.yml
- include: security.yml
- include: vpn.yml

5
inventory Normal file
View file

@ -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

2
inventory_users Normal file
View file

@ -0,0 +1,2 @@
[users-management]
45.55.244.205

134
security.yml Normal file
View file

@ -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

15
templates/CIS.conf.j2 Normal file
View file

@ -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

101
templates/audit.rules.j2 Normal file
View file

@ -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

32
templates/auditd.conf.j2 Normal file
View file

@ -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

198
templates/easy-rsa.vars.j2 Normal file
View file

@ -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 }}"

29
templates/ipsec.conf.j2 Normal file
View file

@ -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

View file

@ -0,0 +1,2 @@
: ECDSA {{ server_name }}.key

144
templates/mobileconfig.j2 Normal file
View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>IKEv2</key>
<dict>
<key>AuthenticationMethod</key>
<string>Certificate</string>
<key>ChildSecurityAssociationParameters</key>
<dict>
<key>DiffieHellmanGroup</key>
<integer>19</integer>
<key>EncryptionAlgorithm</key>
<string>AES-128-GCM</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
</dict>
<key>DeadPeerDetectionRate</key>
<string>Medium</string>
<key>DisableMOBIKE</key>
<integer>0</integer>
<key>DisableRedirect</key>
<integer>0</integer>
<key>EnableCertificateRevocationCheck</key>
<integer>0</integer>
<key>EnablePFS</key>
<true/>
<key>IKESecurityAssociationParameters</key>
<dict>
<key>DiffieHellmanGroup</key>
<integer>19</integer>
<key>EncryptionAlgorithm</key>
<string>AES-128-GCM</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
</dict>
<key>LocalIdentifier</key>
<string>{{ item.0 }}</string>
<key>PayloadCertificateUUID</key>
<string>1FB2907D-14D3-4BAB-A472-B304F4B7F7D9</string>
<key>CertificateType</key>
<string>ECDSA256</string>
<key>ServerCertificateIssuerCommonName</key>
<string>{{ server_name }}</string>
<key>RemoteAddress</key>
<string>{{ server_name }}</string>
<key>RemoteIdentifier</key>
<string>{{ server_name }}</string>
<key>UseConfigurationAttributeInternalIPSubnet</key>
<integer>0</integer>
</dict>
<key>IPv4</key>
<dict>
<key>OverridePrimary</key>
<integer>1</integer>
</dict>
<key>PayloadDescription</key>
<string>Configures VPN settings</string>
<key>PayloadDisplayName</key>
<string>VPN</string>
<key>PayloadIdentifier</key>
<string>com.apple.vpn.managed.D247A30B-6023-4C8E-B3E3-FF1910A65E53</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>PayloadUUID</key>
<string>D247A30B-6023-4C8E-B3E3-FF1910A65E53</string>
<key>PayloadVersion</key>
<real>1</real>
<key>Proxies</key>
<dict>
<key>HTTPEnable</key>
<integer>0</integer>
<key>HTTPSEnable</key>
<integer>0</integer>
</dict>
<key>UserDefinedName</key>
<string>{{ server_name }} IKEv2</string>
<key>VPNType</key>
<string>IKEv2</string>
</dict>
<dict>
<key>Password</key>
<string>{{ easyrsa_p12_export_password }}</string>
<key>PayloadCertificateFileName</key>
<string>{{ item.0 }}.p12</string>
<key>PayloadContent</key>
<data>
{{ item.1.stdout }}
</data>
<key>PayloadDescription</key>
<string>Adds a PKCS#12-formatted certificate</string>
<key>PayloadDisplayName</key>
<string>{{ item.0 }}.p12</string>
<key>PayloadIdentifier</key>
<string>com.apple.security.pkcs12.1FB2907D-14D3-4BAB-A472-B304F4B7F7D9</string>
<key>PayloadType</key>
<string>com.apple.security.pkcs12</string>
<key>PayloadUUID</key>
<string>1FB2907D-14D3-4BAB-A472-B304F4B7F7D9</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>PayloadCertificateFileName</key>
<string>ca.crt</string>
<key>PayloadContent</key>
<data>
{{ PayloadContentCA.stdout }}
</data>
<key>PayloadDescription</key>
<string>Adds a CA root certificate</string>
<key>PayloadDisplayName</key>
<string>{{ server_name }}</string>
<key>PayloadIdentifier</key>
<string>com.apple.security.root.32EA3AAA-D19E-43EF-B357-608218745A38</string>
<key>PayloadType</key>
<string>com.apple.security.root</string>
<key>PayloadUUID</key>
<string>32EA3AAA-D19E-43EF-B357-608218745A38</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>{{ server_name }} IKEv2</string>
<key>PayloadIdentifier</key>
<string>donut.local.37CA79B1-FC6A-421F-960A-90F91FC983BE</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>743B04A8-5725-45A2-B1BB-836F8C16DB0A</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

61
templates/rsyslog.conf.j2 Normal file
View file

@ -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

74
users.yml Normal file
View file

@ -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

150
vpn.yml Normal file
View file

@ -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