diff --git a/defaults/main.yml b/defaults/main.yml index 71590e4..f47c75d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -172,6 +172,11 @@ openvpn_ccd_configs: [] # The above will create a file named `client` under the ccd folder containing # the `ifconfig-push` directive. This will be applied to the `client` when it # connects to the openvpn server. + +# Use a custom template for client configuration. In that case, you have to +# take care of which of the above variables will actually have an effect on +# the client config. +openvpn_client_conf_template: client.conf.j2 # }}} # Authentication {{{ # Use PAM authentication @@ -199,13 +204,14 @@ openvpn_tls_key: "ta.key" # Scripting {{{ # A list of directories that the role should create and that should be # accessible by the OpenVPN server to write into after it has dropped -# privileges. The OpenVPN server should run with limited privileges, eg with +# privileges. The OpenVPN server should run with limited privileges, e.g. with # `openvpn_user` set to `nobody`. Such a user will not be able to access many # files and directories in the file system. This means that if you want one of -# your scripts to write to some file, that file will need to be writable by the -# OpenVPN server. The directories included in this variable will be created by -# the role and your scripts will be able to create and write to files inside -# them. Eg, `/var/log/openvpn-script-out/` +# your scripts to write to some file (e.g. under `/var/log`), that file will +# need to be writable by the OpenVPN server. The directories included in this +# variable will be created by the role with permissions that will allow the +# OpenVPN server to write into them, thus your scripts will be able to create +# and write to files inside them. Example: [`/var/log/openvpn-script-out/`]. openvpn_script_output_directories: [] # A path on the OpenVPN server where OpenVPN scripts should be uploaded to. diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index e8d8515..a7c008b 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -21,6 +21,19 @@ - name: client2 content: '# pass' + ## scripting + openvpn_script_output_directories: + - /var/log/openvpn-script-out/ + + openvpn_script_files: + - scripts/client-disconnect.sh.j2 + + openvpn_inline_scripts: + - name: my-up-script.sh + content: | + #!/usr/bin/env + echo 'Up!' >> "/var/up.log" + # Enabled them openvpn_download_clients: false openvpn_open_firewall: false diff --git a/tasks/core/clients.yml b/tasks/core/clients.yml index 49eb49c..9252178 100644 --- a/tasks/core/clients.yml +++ b/tasks/core/clients.yml @@ -1,13 +1,8 @@ --- -- name: Create client configuration directory - file: - path: "{{ openvpn_etcdir }}/ovpns" - state: directory - - name: Generate client configurations template: - src: client.conf.j2 + src: "{{ openvpn_client_conf_template }}" dest: "{{ openvpn_etcdir }}/ovpns/{{ item }}.ovpn" loop: "{{ openvpn_clients }}" register: openvpn_clients_changed diff --git a/tasks/core/configure.yml b/tasks/core/configure.yml index 40a6f75..6916598 100644 --- a/tasks/core/configure.yml +++ b/tasks/core/configure.yml @@ -17,3 +17,11 @@ src: server.conf.j2 dest: "{{ openvpn_etcdir }}/server.conf" notify: openvpn restart + +# Needed by both tls-authentication tasks and client-configuration tasks. Placed +# here to avoid repeating it twice in both places where the tls and +# client-config tasks are located. +- name: Create client configuration directory + file: + path: "{{ openvpn_etcdir }}/ovpns" + state: directory diff --git a/tasks/openvpn.yml b/tasks/openvpn.yml index e008ef4..13df633 100644 --- a/tasks/openvpn.yml +++ b/tasks/openvpn.yml @@ -28,8 +28,6 @@ - include_tasks: core/read-client-files.yml when: openvpn_unified_client_profiles -- import_tasks: core/clients.yml - - include_tasks: authentication/ldap.yml - include_tasks: authentication/pam.yml @@ -38,6 +36,8 @@ - include_tasks: authentication/tls.yml +- import_tasks: core/clients.yml + - include_tasks: scripts.yml - include_tasks: "system/bridge/{{ ansible_os_family }}.yml" diff --git a/tasks/scripts.yml b/tasks/scripts.yml index bb71ed5..9244054 100644 --- a/tasks/scripts.yml +++ b/tasks/scripts.yml @@ -16,14 +16,14 @@ - name: Upload script files template: src: "{{ item }}" - dest: "{{ openvpn_scripts_dir }}{{ item | basename | replace('.j2', '') }}" + dest: "{{ openvpn_scripts_dir }}/{{ item | basename | replace('.j2', '') }}" owner: "{{ openvpn_user }}" group: "{{ openvpn_group }}" mode: 0o744 loop: "{{ openvpn_script_files }}" - name: Upload inline scripts - template: + copy: content: "{{ item.content }}" dest: "{{ openvpn_scripts_dir }}/{{ item.name }}" owner: "{{ openvpn_user }}" diff --git a/tasks/system/bridge/Debian.yml b/tasks/system/bridge/Debian.yml index 8538bf5..cca7ee3 100644 --- a/tasks/system/bridge/Debian.yml +++ b/tasks/system/bridge/Debian.yml @@ -4,11 +4,13 @@ template: src: bridge/bridge-interface.deb.j2 dest: "/etc/network/interfaces.d/{{ openvpn_dev }}" - when: openvpn_bridge | bool + when: + - openvpn_bridge is defined + - openvpn_bridge | length > 0 notify: restart networking debian - name: Remove interface configuration for "{{ openvpn_dev }}" file: path: "/etc/network/interfaces.d/{{ openvpn_dev }}" state: absent - when: not openvpn_bridge | bool + when: openvpn_bridge is not defined or openvpn_bridge | length == 0 diff --git a/tasks/system/bridge/RedHat.yml b/tasks/system/bridge/RedHat.yml index 05e540a..8444889 100644 --- a/tasks/system/bridge/RedHat.yml +++ b/tasks/system/bridge/RedHat.yml @@ -1,28 +1,29 @@ --- -- name: Setup up script - when: openvpn_bridge | bool - template: - src: bridge/up.sh.j2 - dest: "{{ openvpn_scripts_dir }}/up.sh" - mode: o+x +- block: + - name: Setup up script + template: + src: bridge/up.sh.j2 + dest: "{{ openvpn_scripts_dir }}/up.sh" + mode: o+x -- name: Setup down script - template: - src: bridge/down.sh.j2 - dest: "{{ openvpn_scripts_dir }}/down.sh" - mode: o+x - when: openvpn_bridge | bool + - name: Setup down script + template: + src: bridge/down.sh.j2 + dest: "{{ openvpn_scripts_dir }}/down.sh" + mode: o+x -- name: Setup bridge - template: - src: bridge/bridge-interface.rh.j2 - dest: "/etc/sysconfig/network-scripts/ifcfg-br-{{ openvpn_dev }}" - when: openvpn_bridge | bool + - name: Setup bridge + template: + src: bridge/bridge-interface.rh.j2 + dest: "/etc/sysconfig/network-scripts/ifcfg-br-{{ openvpn_dev }}" + when: + - openvpn_bridge is defined + - openvpn_bridge | length > 0 notify: restart networking redhat - name: Remove interface configuration for "{{ openvpn_dev }}" file: path: "/etc/sysconfig/network-scripts/ifcfg-br-{{ openvpn_dev }}" state: absent - when: not openvpn_bridge | bool + when: openvpn_bridge is not defined or openvpn_bridge | length == 0 diff --git a/meta/beats/elasticsearch-ingest-pipelines.yml b/templates/beats/elasticsearch.openvpn.ingest.pipelines.yml similarity index 81% rename from meta/beats/elasticsearch-ingest-pipelines.yml rename to templates/beats/elasticsearch.openvpn.ingest.pipelines.yml index 2d12930..479ca04 100644 --- a/meta/beats/elasticsearch-ingest-pipelines.yml +++ b/templates/beats/elasticsearch.openvpn.ingest.pipelines.yml @@ -15,6 +15,8 @@ pipelines: pattern_definitions: TIMESTAMP: "%{DAY} %{MONTH} ?%{MONTHDAY} %{TIME} %{YEAR}" - gsub: + # the month day is ' 3' or '24'. The space before '3' will break the + # date filter that follows, so removing it. field: "openvpn.date" pattern: " " replacement: ' ' @@ -25,6 +27,7 @@ pipelines: - set: field: 'openvpn.event' value: 'client-connected' + # Parses log lines created with the # `templates/etc/openvpn/scripts/client-disconnect.sh` script. - description: "openvpn-disconnection-log-line" @@ -36,7 +39,9 @@ pipelines: field: message ignore_failure: true patterns: - - "%{DATESTAMP_OTHER:openvpn.date},%{DATA:openvpn.common_name},%{IP:openvpn.client_ip}" + - "%{TIMESTAMP:openvpn.date},%{DATA:openvpn.common_name},%{IP:openvpn.client_ip}" + pattern_definitions: + TIMESTAMP: "%{DAY} %{MONTH} ?%{MONTHDAY} %{TIME} %{TZ} %{YEAR}" - gsub: field: "openvpn.date" pattern: " " diff --git a/templates/beats/filebeat.openvpn.fields.yml b/templates/beats/filebeat.openvpn.fields.yml new file mode 100644 index 0000000..2e4b334 --- /dev/null +++ b/templates/beats/filebeat.openvpn.fields.yml @@ -0,0 +1,12 @@ +--- + +- name: openvpn.date + type: date +- name: openvpn.client_ip + type: ip +- name: openvpn.common_name + type: keyword +- name: openvpn.event + type: keyword +- name: openvpn.port + type: long diff --git a/meta/beats/filebeat-inputs.yml b/templates/beats/filebeat.openvpn.inputs.yml similarity index 100% rename from meta/beats/filebeat-inputs.yml rename to templates/beats/filebeat.openvpn.inputs.yml diff --git a/meta/beats/heartbeat-monitors.yml b/templates/beats/heartbeat.openvpn.monitors.yml similarity index 100% rename from meta/beats/heartbeat-monitors.yml rename to templates/beats/heartbeat.openvpn.monitors.yml diff --git a/templates/client.conf.j2 b/templates/client.conf.j2 index 7286eb4..0637f88 100644 --- a/templates/client.conf.j2 +++ b/templates/client.conf.j2 @@ -20,7 +20,7 @@ cipher {{ openvpn_cipher }} # The hostname/IP and port of the server. You can have multiple remote entries # to load balance between the servers. -remote {{openvpn_host}} {{openvpn_port}} +remote {{ openvpn_host }} {{ openvpn_port }} # Keep trying indefinitely to resolve the host name of the OpenVPN server. # Very useful on machines which are not permanently connected to the internet @@ -42,10 +42,10 @@ persist-tun {{ openvpn_ca_file_contents }} -{{ openvpn_client_cert_output |default([{'item':client,'stdout':''}])|selectattr('item', 'match', client)|map(attribute='stdout')|list|first }} +{{ openvpn_client_cert_output | default([{'item':client,'stdout':''}]) | selectattr('item', 'match', client) | map(attribute='stdout') | list | first }} -{{ openvpn_client_keys_output |default([{'item':client,'stdout':''}])|selectattr('item', 'match', client)|map(attribute='stdout')|list|first }} +{{ openvpn_client_keys_output | default([{'item':client,'stdout':''}]) | selectattr('item', 'match', client) | map(attribute='stdout') | list | first }} {% if openvpn_tls_auth %} key-direction 1 @@ -56,23 +56,24 @@ key-direction 1 {% else %} ca ca.crt -cert {{client}}.crt -key {{client}}.key +cert {{ client }}.crt +key {{ client }}.key {% endif %} -# Verify server certificate by checking that the certicate has the nsCertType -# field set to "server". This is an important precaution to protect against a -# potential attack discussed here: http://openvpn.net/howto.html#mitm +# To avoid a possible Man-in-the-Middle attack where an authorized client tries +# to connect to another client by impersonating the server, make sure to enforce +# some kind of server certificate verification by clients. +# This is an important precaution to protect against a potential attack +# discussed here: http://openvpn.net/howto.html#mitm # # To use this feature, you will need to generate your server certificates with -# the nsCertType field set to "server". The build-key-server script in the -# easy-rsa folder will do this. -# ns-cert-type server (Deprecated by 'remote-cert-tls' since OpenVPN 2.1) +# the nsCertType field set to "server". The build-key-server script in the easy-rsa +# folder will do this. See https://openvpn.net/community-resources/rsa-key-management/ remote-cert-tls server {% if openvpn_tls_auth and not openvpn_unified_client_profiles -%} # Use a static pre-shared key (PSK) -tls-auth {{openvpn_tls_key}} 1 +tls-auth {{ openvpn_tls_key }} 1 {% endif %} # Enable compression on the VPN link. Don't enable this unless it is also @@ -84,12 +85,12 @@ comp-lzo {% endif %} # Set log file verbosity. -verb {{openvpn_verb}} +verb {{ openvpn_verb }} {% if openvpn_use_pam or openvpn_use_ldap %} auth-user-pass {% endif %} {% for option in openvpn_client_options %} -{{option}} +{{ option }} {% endfor %} diff --git a/templates/scripts/client-disconnect.sh.j2 b/templates/scripts/client-disconnect.sh.j2 index e7bc837..99199b1 100644 --- a/templates/scripts/client-disconnect.sh.j2 +++ b/templates/scripts/client-disconnect.sh.j2 @@ -3,6 +3,11 @@ {# openvpn_client_disconnect_log is a variable specific to this file and is not mentioned in $(defaults/main.yml) #} +{%- if openvpn_client_disconnect_log is not defined -%} + {% set openvpn_client_disconnect_log = "{{ + openvpn_script_output_directories[0] }}/disconnect.log" %} +{%- endif -%} + if [[ ! -e "{{ openvpn_client_disconnect_log }}" ]]; then echo 'time,common_name,external_ip' >"{{ openvpn_client_disconnect_log }}" fi