diff --git a/defaults/main.yml b/defaults/main.yml index 012f4b4..e7d76e7 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -64,8 +64,9 @@ openvpn_client_cert_days: "{{ openvpn_cert_days }}" openvpn_ipv4_network: 192.168.250.0/24 # Verify the CN of the client -openvpn_verify_cn: no -openvpn_verify_x509_name: "OpenVPN-Client-{{inventory_hostname}} name-prefix" +openvpn_verify_cn_enabled: no +openvpn_verify_cn_client: "OpenVPN-Client-{{inventory_hostname}} name-prefix" +openvpn_verify_cn_server: "OpenVPN-Server-{{inventory_hostname}} name" # Configure the OpenVPN management socket openvpn_management_enabled: yes @@ -83,6 +84,10 @@ openvpn_management_user: root # key: (not stored on the server, only used to generate the client config) openvpn_clients: [] +# Enables generation of .ovpn client configurations for managed clients +openvpn_clients_config_generate: yes +openvpn_clients_config_path: "{{ openvpn_key_path }}" + openvpn_server_config: port: "{{ openvpn_port }}" proto: "{{ openvpn_protocol }}" @@ -91,7 +96,7 @@ openvpn_server_config: cert: "{{ openvpn_key_path }}/server.crt" key: "{{ openvpn_key_path }}/server.key" dh: "{{ openvpn_key_path }}/dh.pem" - tls-auth: "{{ openvpn_key_path }}/tls-auth.key" + tls-auth: "{{ openvpn_key_path }}/tls-auth.key 0" crl-verify: "{{ openvpn_key_path }}/ca.crl" tls-server: '' auth: SHA256 @@ -105,13 +110,34 @@ openvpn_server_config: group: "{{ openvpn_group }}" status: "{{ openvpn_status_file }}" verb: 3 - verify-x509-name: "{{ openvpn_verify_x509_name if (openvpn_verify_cn | bool) else [] }}" - remote-cert-tls: "{{ 'client' if (openvpn_verify_cn | bool) else [] }}" + verify-x509-name: "{{ openvpn_verify_cn_client if (openvpn_verify_cn_enabled | bool) else [] }}" + remote-cert-tls: client + tls-version-min: 1.2 management: "{{ openvpn_management_bind if (openvpn_management_enabled | bool) else [] }}" management-client-user: "{{ openvpn_management_user if (openvpn_management_enabled | bool) else [] }}" -openvpn_client_config: {} +openvpn_client_config: + client: '' + proto: "{{ openvpn_protocol }}" + remote: "{{ ansible_host }} {{ openvpn_port }}" + auth: SHA256 + cipher: AES-256-CBC + dev: tun + remote-cert-tls: server + tls-version-min: 1.2 + resolv-retry: infinite + nobind: '' + keepalive: '5 30' + comp-lzo: '' + persist-key: '' + persist-tun: '' + key-direction: 1 + verb: 3 + verify-x509-name: "{{ openvpn_verify_cn_server if (openvpn_verify_cn_enabled | bool) else [] }}" # Configure settings in this dict to override the base configuration. +# NOTE(logan): An empty list ie. [] is excluded from the config. +# NOTE(logan): An empty string is outputted as a key-only var with no +# parameter in the config. openvpn_server_config_overrides: {} openvpn_client_config_overrides: {} diff --git a/handlers/main.yml b/handlers/main.yml index 7f486ef..bf72e05 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -13,6 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +- name: Update the CRL + command: > + openssl ca + -config {{ openvpn_key_path }}/openssl.cnf + -gencrl + -out {{ openvpn_key_path }}/ca.crl + changed_when: false + - name: Restart OpenVPN service: name: "{{ openvpn_systemd_unit }}" diff --git a/tasks/openvpn_client_configs.yml b/tasks/openvpn_client_configs.yml new file mode 100644 index 0000000..2fd2c42 --- /dev/null +++ b/tasks/openvpn_client_configs.yml @@ -0,0 +1,47 @@ +--- +# Copyright 2017, Logan Vig +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Store CA key + slurp: + src: "{{ openvpn_key_path }}/ca.crt" + register: ca_cert + +- name: Store tls-auth key + slurp: + src: "{{ openvpn_key_path }}/tls-auth.key" + register: tls_auth_key + +- name: Store client certificates + slurp: + src: "{{ openvpn_key_path }}/{{ item }}.crt" + register: client_certs + with_items: "{{ openvpn_clients_generate }}" + +- name: Store client keys + slurp: + src: "{{ openvpn_key_path }}/{{ item }}.key" + register: client_keys + with_items: "{{ openvpn_clients_generate }}" + +- name: Generate client configurations + template: + src: client.ovpn.j2 + dest: "{{ openvpn_clients_config_path }}/{{ item.0.item }}.ovpn" + owner: root + group: root + mode: '0600' + with_together: + - "{{ client_certs.results }}" + - "{{ client_keys.results }}" diff --git a/tasks/openvpn_keys.yml b/tasks/openvpn_keys.yml index 9cf6c31..4de7164 100644 --- a/tasks/openvpn_keys.yml +++ b/tasks/openvpn_keys.yml @@ -72,3 +72,7 @@ changed_when: false - include: openvpn_keys_client.yml +- include: openvpn_client_configs.yml + when: + - openvpn_clients_config_generate | bool + - openvpn_clients_generate | length > 0 diff --git a/tasks/openvpn_keys_ca.yml b/tasks/openvpn_keys_ca.yml index eb7e606..df121a2 100644 --- a/tasks/openvpn_keys_ca.yml +++ b/tasks/openvpn_keys_ca.yml @@ -40,6 +40,8 @@ state: touch owner: root group: root + notify: + - Update the CRL with_items: - "{{ openvpn_key_path }}/index.txt" - "{{ openvpn_key_path }}/ca.crl" diff --git a/tasks/openvpn_keys_client.yml b/tasks/openvpn_keys_client.yml index ab56990..94852f4 100644 --- a/tasks/openvpn_keys_client.yml +++ b/tasks/openvpn_keys_client.yml @@ -77,16 +77,9 @@ -revoke {{ openvpn_key_path }}/{{ item }}.crt with_items: "{{ openvpn_clients_revoke }}" register: cert_revoke + notify: + - Update the CRL changed_when: cert_revoke.rc == 0 failed_when: - cert_revoke.rc == 1 - "'Already revoked' not in cert_revoke.stdout" - -- name: Update the CRL file - command: > - openssl ca - -config {{ openvpn_key_path }}/openssl.cnf - -gencrl - -out {{ openvpn_key_path }}/ca.crl - when: cert_revoke | changed - changed_when: false diff --git a/templates/client.ovpn.j2 b/templates/client.ovpn.j2 new file mode 100644 index 0000000..637f227 --- /dev/null +++ b/templates/client.ovpn.j2 @@ -0,0 +1,33 @@ +# {{ ansible_managed }} + +{% set _ = openvpn_client_config.update(openvpn_client_config_overrides) %} + +{% for i in openvpn_client_config | dictsort %} +{% set key = i.0 %} +{% set param = i.1 %} +{% if param is iterable and param is not string %} +{# The key is a list so we output multiple config items with the same key #} +{% for inner_param in param %} +{{ key }} {{ inner_param }} +{% endfor %} +{% else %} +{{ key }}{% if param != '' %} {{ param }}{% endif %} + +{% endif %} +{% endfor %} + + +{{ ca_cert.content | b64decode }} + + + +{{ tls_auth_key.content | b64decode }} + + + +{{ item.0.content | b64decode }} + + + +{{ item.1.content | b64decode }} + diff --git a/templates/server.conf.j2 b/templates/server.conf.j2 index 0582717..a472462 100644 --- a/templates/server.conf.j2 +++ b/templates/server.conf.j2 @@ -18,6 +18,7 @@ {{ key }} {{ inner_item }} {% endfor %} {% else %} -{{ key }} {{ item }} +{{ key }}{% if item != '' %} {{ item }}{% endif %} + {% endif %} {% endfor %} diff --git a/tests/test.yml b/tests/test.yml index 481d2b5..dbd52e0 100644 --- a/tests/test.yml +++ b/tests/test.yml @@ -50,5 +50,32 @@ failed_when: openvpn_pid.rc != 0 register: openvpn_pid changed_when: false + - name: Fetch the openvpn client configuration + slurp: + src: "{{ openvpn_key_path }}/{{ openvpn_clients[0] }}.ovpn" + register: test_client_config + - name: Set the ping test IP + set_fact: + test_ping_ip: "{{ openvpn_ipv4_network | ipaddr(1) | ipaddr('address') }}" vars_files: - test-vars.yml + +- name: Test the OpenVPN client connection + hosts: openvpn_clients + tasks: + - name: Install OpenVPN in the client + package: + name: openvpn + - name: Drop the client configuration + copy: + content: "{{ hostvars[groups['openvpn_servers'][0]].test_client_config.content | b64decode }}" + dest: /etc/openvpn/client.conf + - name: Start the OpenVPN client + service: + name: 'openvpn@client.service' + state: restarted + post_tasks: + - name: Ping the OpenVPN server over the tunnel + command: > + ping -c4 {{ hostvars[groups['openvpn_servers'][0]].test_ping_ip }} + changed_when: false