diff --git a/README.md b/README.md index 225dd44..fc5d3b8 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,26 @@ -Role Name +OpenVPN ========= -A brief description of the role goes here. +Role that installs OpenVPN Requirements ------------ -Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. +letsencrypt-acme-sh-client role if letsencrypt is being used (best not if you are relying on client certificates for authentication). Role Variables -------------- -A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. +Important in a multihost installation are the variables: + +openvpn_is_master_host: bool +openvpn_net_octet: int + +That must differ from host to host. The first one must be set to *True* on one host only Dependencies ------------ -A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. - Example Playbook ---------------- @@ -30,9 +33,9 @@ Including an example of how to use your role (for instance, with variables passe License ------- -BSD +EUPL 1.2+ Author Information ------------------ -An optional section for the role authors to include contact information, or a website (HTML is not allowed). +Andrea Dell'Amico diff --git a/defaults/main.yml b/defaults/main.yml index 95d3c70..b364963 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,2 +1,111 @@ --- -# defaults file for ansible-role-template \ No newline at end of file +openvpn_enabled: True +openvpn_enable_system_forward: True +openvpn_management_enabled: False +openvpn_management_ip: 127.0.0.1 +openvpn_management_port: 1195 +openvpn_management_file: '{{ openvpn_conf_dir }}/auth/management.txt' +# openvpn_management_password: 'set into a vault file' +openvpn_pkg_state: latest +openvpn_pkgs: + - openvpn + +# Authentication choices +openvpn_cert_auth_enabled: True +openvpn_username_pam_auth: False + +openvpn_radius_auth: False +openvpn_radius_pkg: + - openvpn-auth-radius + +# With openvpn-auth-ldap. Broken on Ubuntu trusty +openvpn_ldap_auth: False +openvpn_ldap_pkg: + - openvpn-auth-ldap + +openvpn_ldap_perl_auth: False +openvpn_perl_pkg: + - libnet-ldap-perl + +# Server conf parameters +openvpn_conf_dir: /etc/openvpn +openvpn_conf_name: openvpn.conf + +openvpn_mode: server +openvpn_dev: tun +openvpn_port: 1194 +openvpn_protocol: udp +openvpn_server_net: '192.168.254.0 255.255.255.0' +#openvpn_push_routes: [] +# - '192.168.253.0 255.255.255.0' + +#openvpn_push_settings: +# - "dhcp-option DNS 10.66.0.4" + +#openvpn_remote_servers: [] + +openvpn_force_ccd: False +# openvpn_users_customizations: +# - { cn: 'Joe Bar', ip: '', netmask: '', routes: [ '192.168.253.0 255.255.255.0' ] } + +openvpn_tls_server: True +openvpn_dh: /etc/openvpn/dh2048.pem +openvpn_tls_auth: '/etc/openvpn/ta.key' +openvpn_install_alternative_ca: False +openvpn_alternative_ca_name: ca.pem +openvpn_ca_dir: False +openvpn_ca: '/var/lib/acme/live/{{ ansible_fqdn }}/chain' +openvpn_cert: '/var/lib/acme/live/{{ ansible_fqdn }}/cert' +openvpn_key: '/var/lib/acme/live/{{ ansible_fqdn }}/privkey' + +openvpn_ha: False +# Not a real master. It is only the host where the dh.pem and ta.key are generated +openvpn_master_host: 'localhost' +openvpn_is_master_host: False + +openvpn_compression_enabled: False +openvpn_keepalive: '10 120' + +openvpn_max_clients: 100 +openvpn_run_unprivileged: True +openvpn_unprivileged_user: nobody +openvpn_unprivileged_group: nogroup +# Not recommended. Use a private CA if possible +openvpn_letsencrypt_managed: False + +openvpn_verbosity_log: 3 +openvpn_mute_after: 20 + +# LDAP conf +openvpn_ldap_uri: 'ldap:' +openvpn_ldap_host: ldap.example.org +openvpn_ldap_url: '{{ openvpn_ldap_uri }}//{{ openvpn_ldap_host }}' +openvpn_ldap_anon_bind: True +openvpn_ldap_binddn: uid=admin +openvpn_ldap_bindpwd: test +openvpn_ldap_ca: '{{ openvpn_ca }}' +openvpn_ldap_use_ca_dir: False +openvpn_ldap_ca_dir: /etc/ssl/certs +openvpn_ldap_starttls: False +openvpn_ldap_tls_auth: False +openvpn_ldap_tls_cert: '{{ openvpn_cert }}' +openvpn_ldap_tls_key: '{{ openvpn_key }}' +openvpn_ldap_tls_ciphersuite: 'ALL:!ADH:@STRENGTH' +# LDAP auth +openvpn_ldap_base_dn: 'ou=People,dc=example,dc=org' +openvpn_ldap_user_search: '(&(uid=%u))' +openvpn_ldap_require_group: False +# See https://github.com/threerings/openvpn-auth-ldap/issues/7 +openvpn_ldap_without_posix_groups: True +openvpn_ldap_group_base: 'ou=Groups,dc=example,dc=org' +openvpn_ldap_group_filter: '(|(cn=developers)(cn=artists))' +openvpn_ldap_group_member_attr: uniqueMember + +# Perl LDAP conf +openvpn_ldap_perl_auth_ssl: True +openvpn_ldap_perl_auth_sslport: 636 +openvpn_ldap_perl_auth_group: vpn_ldap_posix_group + +openvpn_nagios_checks: False +openvpn_nagios_deb_deps: + - libnet-telnet-perl diff --git a/files/openvpn-letsencrypt-acme.sh b/files/openvpn-letsencrypt-acme.sh new file mode 100644 index 0000000..aec4da3 --- /dev/null +++ b/files/openvpn-letsencrypt-acme.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +H_NAME=$( hostname -f ) +LE_SERVICES_SCRIPT_DIR=/usr/lib/acme/hooks +LE_CERTS_DIR=/var/lib/acme/live/$H_NAME +LE_LOG_DIR=/var/log/letsencrypt +DATE=$( date ) + +[ ! -d $LE_LOG_DIR ] && mkdir $LE_LOG_DIR +echo "$DATE" >> $LE_LOG_DIR/openvpn.log + +if [ -f /etc/default/letsencrypt ] ; then + . /etc/default/letsencrypt +else + echo "No letsencrypt default file" >> $LE_LOG_DIR/openvpn.log +fi + +echo "Reload the openvpn service" >> $LE_LOG_DIR/openvpn.log +if [ -x /bin/systemctl ] ; then + systemctl restart openvpn >> $LE_LOG_DIR/openvpn.log 2>&1 +else + service openvpn restart >> $LE_LOG_DIR/openvpn.log 2>&1 +fi + +echo "Done." >> $LE_LOG_DIR/openvpn.log + +exit 0 + diff --git a/handlers/main.yml b/handlers/main.yml index 27474e0..df32d5c 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -1,2 +1,13 @@ --- -# handlers file for ansible-role-template \ No newline at end of file +# OpenVPN does not support a service reload +- name: Reload OpenVPN + service: name=openvpn state=restarted + when: openvpn_enabled + +- name: Restart OpenVPN + service: name=openvpn state=restarted + when: openvpn_enabled + +- name: Reload systemd + systemd: daemon_reload=yes + when: ansible_service_mgr == 'systemd' diff --git a/tasks/letsencrypt-openvpn.yml b/tasks/letsencrypt-openvpn.yml new file mode 100644 index 0000000..fd6ddc1 --- /dev/null +++ b/tasks/letsencrypt-openvpn.yml @@ -0,0 +1,14 @@ +--- +- name: Create the acme hooks directory if it does not yet exist + file: dest={{ letsencrypt_acme_services_scripts_dir }} state=directory owner=root group=root + when: + - openvpn_letsencrypt_managed + - letsencrypt_acme_install + tags: [ 'openvpn', 'letsencrypt' ] + +- name: Install a script that fix the letsencrypt certificate for openvpn and then reload the service + copy: src=openvpn-letsencrypt-acme.sh dest={{ letsencrypt_acme_services_scripts_dir }}/openvpn owner=root group=root mode=4555 + when: + - openvpn_letsencrypt_managed + - letsencrypt_acme_install + tags: [ 'openvpn', 'letsencrypt' ] diff --git a/tasks/main.yml b/tasks/main.yml index 53c6cae..994407f 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,2 +1,8 @@ --- -# tasks file for ansible-role-template \ No newline at end of file +- import_tasks: openvpn.yml +- import_tasks: letsencrypt-openvpn.yml + when: openvpn_letsencrypt_managed | bool +- import_tasks: openvpn-nagios.yml + when: + - nagios_enabled is defined and nagios_enabled | bool + - openvpn_nagios_checks | bool diff --git a/tasks/openvpn-nagios.yml b/tasks/openvpn-nagios.yml new file mode 100644 index 0000000..f3f9294 --- /dev/null +++ b/tasks/openvpn-nagios.yml @@ -0,0 +1,18 @@ +--- +- name: Nagios check configuration for openvpn (NRPE required) + block: + - name: Install the nagios check deb deps + apt: pkg={{ openvpn_nagios_deb_deps }} state={{ openvpn_pkg_state }} update_cache=yes cache_valid_time=1800 + + - name: Install the nagios check script + template: src=check_openvpn.pl.j2 dest={{ nagios_plugins_dir }}/check_openvpn owner=root group=root mode=0755 + + - name: Install the nagios nrpe configuration + template: src=openvpn-nrpe.cfg.j2 dest={{ nrpe_include_dir }}/openvpn.cfg owner=root group=nagios mode=0440 + notify: Reload NRPE server + + when: + - openvpn_enabled | bool + - ansible_distribution_file_variety == "Debian" + tags: [ 'openvpn', 'openvpn_nagios', 'nagios' ] + diff --git a/tasks/openvpn.yml b/tasks/openvpn.yml new file mode 100644 index 0000000..0f17e55 --- /dev/null +++ b/tasks/openvpn.yml @@ -0,0 +1,209 @@ +--- +- block: + - name: Install the OpenVPN main packages + apt: pkg={{ openvpn_pkgs }} state={{ openvpn_pkg_state }} update_cache=yes cache_valid_time=1800 + + - name: Create the auth, ipp, ccd and status subdirs + file: dest={{ openvpn_conf_dir }}/{{ item }} state=directory owner={{ openvpn_unprivileged_user }} group=root mode=0770 + with_items: + - ipp + - status + - auth + - ccd + + when: openvpn_enabled | bool + tags: openvpn + +- block: + - name: Install the OpenVPN radius auth plugin package + apt: pkg={{ openvpn_radius_pkg }} state={{ openvpn_pkg_state }} update_cache=yes cache_valid_time=1800 + + when: openvpn_radius_auth | bool + tags: [ 'openvpn', 'openvpn_radius' ] + +- block: + - name: Install the OpenVPN radius auth plugin package + template: src=management.txt.j2 dest={{ openvpn_management_file }} owner=root group=root mode=0400 + + when: openvpn_management_enabled | bool + tags: [ 'openvpn', 'openvpn_management' ] + +- block: + - name: Install the OpenVPN ldap auth plugin package + apt: pkg={{ openvpn_ldap_pkg }} state={{ openvpn_pkg_state }} update_cache=yes cache_valid_time=1800 + + - name: Install the LDAP auth configuration file + template: src=auth-ldap.conf.j2 dest={{ openvpn_conf_dir }}/auth/auth-ldap.conf owner=root group={{ openvpn_unprivileged_group }} mode=0440 + notify: Reload OpenVPN + + when: openvpn_ldap_auth + tags: [ 'openvpn', 'openvpn_ldap', 'openvpn_basic_conf' ] + +- block: + - name: Remove the LDAP auth configuration file if LDAP is not used + file: dest={{ openvpn_conf_dir }}/auth/auth-ldap.conf state=absent + notify: Reload OpenVPN + + when: not openvpn_ldap_auth + tags: [ 'openvpn', 'openvpn_ldap', 'openvpn_basic_conf' ] + +- block: + - name: Install the perl libraries needed by the LDAP client authentication script + apt: pkg={{ openvpn_perl_pkg }} state={{ openvpn_pkg_state }} update_cache=yes cache_valid_time=1800 + + - name: Install the perl LDAP auth script + template: src=auth-ldap.pl.j2 dest={{ openvpn_conf_dir }}/auth/auth-ldap owner=root group={{ openvpn_unprivileged_group }} mode=0550 + + when: openvpn_ldap_perl_auth + tags: [ 'openvpn', 'openvpn_ldap' ] + +- block: + - name: Install the main OpenVPN configuration file on the servers + template: src=server.conf.j2 dest={{ openvpn_conf_dir }}/{{ openvpn_conf_name }} owner=root group={{ openvpn_unprivileged_group }} mode=0440 + notify: Restart OpenVPN + tags: [ 'openvpn', 'openvpn_conf', 'openvpn_conf_file', 'openvpn_basic_conf' ] + + - name: Install the custom configuration for specific OpenVPN users in the servers + template: src=user-ccd.conf.j2 dest={{ openvpn_conf_dir }}/ccd/{{ item.cn }} owner=root group={{ openvpn_unprivileged_group }} mode=0440 + with_items: '{{ openvpn_users_customizations | default([]) }}' + tags: [ 'openvpn', 'openvpn_conf', 'openvpn_ccd' ] + + - name: Install the easy-rsa package on servers when we use the certificate authentication + apt: pkg=easy-rsa state={{ openvpn_pkg_state }} update_cache=yes cache_valid_time=1800 + when: + - openvpn_cert_auth_enabled | bool + - openvpn_is_master_host | bool + + when: openvpn_mode == 'server' + tags: [ 'openvpn', 'openvpn_conf' ] + +- block: + - name: Install the main OpenVPN configuration file on the clients + template: src=client.conf.j2 dest={{ openvpn_conf_dir }}/{{ openvpn_conf_name }} owner=root group={{ openvpn_unprivileged_group }} mode=0440 + notify: Restart OpenVPN + + when: openvpn_mode != 'server' + tags: [ 'openvpn', 'openvpn_conf' ] + +- block: + - name: Install the OpenVPN init defaults + template: src=openvpn-defaults.j2 dest=/etc/default/openvpn owner=root group=root mode=0444 + notify: + - Restart OpenVPN + - Reload systemd + + tags: [ 'openvpn', 'openvpn_conf' ] + +- block: + - name: Create the dh file + shell: openssl dhparam -out {{ openvpn_conf_dir }}/dh2048.pem 2048 + args: + creates: '{{ openvpn_conf_dir }}/dh2048.pem' + + - name: Fix the dh file permissions + file: dest={{ openvpn_conf_dir }}/dh2048.pem owner=root group=root mode=0444 + + - name: Create the ta key + shell: cd {{ openvpn_conf_dir }} && openvpn --genkey --secret ta.key + args: + creates: '{{ openvpn_conf_dir }}/ta.key' + + - name: Fix the ta.key file permissions + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0400 + + when: openvpn_is_master_host | bool or not openvpn_ha | bool + tags: [ 'openvpn', 'openvpn_conf' ] + +- block: + - name: Get the dh file from the master host + synchronize: + src: '{{ openvpn_conf_dir }}/dh2048.pem' + #dest: 'rsync://root@{{ ansible_fqdn }}/{{ openvpn_conf_dir }}/dh2048.pem' + dest: '/{{ openvpn_conf_dir }}/dh2048.pem' + delegate_to: '{{ openvpn_master_host }}' + ignore_errors: True + + - name: Relax the ta.key file permissions so that it can be copied around + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0444 + delegate_to: '{{ openvpn_master_host }}' + ignore_errors: True + + - name: Get the ta key from the master host + synchronize: + src: '{{ openvpn_conf_dir }}/ta.key' + #dest: 'rsync://root@{{ ansible_fqdn }}/{{ openvpn_conf_dir }}/ta.key' + dest: '/{{ openvpn_conf_dir }}/ta.key' + delegate_to: '{{ openvpn_master_host }}' + ignore_errors: True + + - name: Fix the ta.key file permissions + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0400 + + - name: Fix the ta.key file permissions on the master host + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0400 + delegate_to: '{{ openvpn_master_host }}' + ignore_errors: True + + when: + - openvpn_ha | bool + - not openvpn_is_master_host | bool + tags: [ 'openvpn', 'openvpn_conf', 'openvpn_shared_secrets' ] + +- block: + - name: Get the dh file from the master host + synchronize: + src: '{{ openvpn_conf_dir }}/dh2048.pem' + #dest: 'rsync://root@{{ ansible_fqdn }}/{{ openvpn_conf_dir }}/dh2048.pem' + dest: '/{{ openvpn_conf_dir }}/dh2048.pem' + delegate_to: '{{ openvpn_master_host }}' + + - name: Relax the ta.key file permissions so that it can be copied around + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0444 + delegate_to: '{{ openvpn_master_host }}' + + - name: Get the ta key from the master host + synchronize: + src: '{{ openvpn_conf_dir }}/ta.key' + #dest: 'rsync://root@{{ ansible_fqdn }}/{{ openvpn_conf_dir }}/ta.key' + dest: '/{{ openvpn_conf_dir }}/ta.key' + delegate_to: '{{ openvpn_master_host }}' + ignore_errors: True + + - name: Fix the ta.key file permissions + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0400 + + - name: Fix the ta.key file permissions on the master host + file: dest={{ openvpn_conf_dir }}/ta.key owner=root group=root mode=0400 + delegate_to: '{{ openvpn_master_host }}' + + when: openvpn_mode != 'server' + tags: [ 'openvpn', 'openvpn_conf', 'openvpn_shared_secrets' ] + + +- block: + - name: Enable kernel forwarding + sysctl: name={{ item }} value=1 reload=yes state=present + with_items: + - net.ipv4.ip_forward + # - net.ipv6.conf.all.forwarding + when: + - openvpn_enable_system_forward | bool + - openvpn_enabled | bool + + - name: Disable kernel forwarding + sysctl: name={{ item }} value=0 reload=yes state=present + with_items: + - net.ipv4.ip_forward + # - net.ipv6.conf.all.forwarding + when: not openvpn_enable_system_forward + + - name: Ensure that the OpenVPN service is enabled and running + service: name=openvpn state=started enabled=yes + when: openvpn_enabled | bool + + - name: Ensure that the OpenVPN service is stopped and disabled + service: name=openvpn state=stopped enabled=no + when: not openvpn_enabled | bool + + tags: openvpn + diff --git a/templates/auth-ldap.conf.j2 b/templates/auth-ldap.conf.j2 new file mode 100644 index 0000000..3c83a1f --- /dev/null +++ b/templates/auth-ldap.conf.j2 @@ -0,0 +1,72 @@ + + # LDAP server URL + URL {{ openvpn_ldap_url }} + +{% if not openvpn_ldap_anon_bind %} + # Bind DN (If your LDAP server doesn't support anonymous binds) + BindDN "{{ openvpn_ldap_binddn }}" + # Bind Password + Password "{{ openvpn_ldap_bindpwd }}" +{% endif %} + + # Network timeout (in seconds) + Timeout 15 + +{% if openvpn_ldap_starttls %} + # Enable Start TLS + TLSEnable yes +{% else %} + TLSEnable no +{% endif %} + +{% if not openvpn_ldap_anon_bind %} + # Follow LDAP Referrals (anonymously) + FollowReferrals no +{% else %} + FollowReferrals yes +{% endif %} + + # TLS CA Certificate File + TLSCACertFile {{ openvpn_ldap_ca }} + +{% if openvpn_ldap_use_ca_dir %} + # TLS CA Certificate Directory + # TLSCACertDir {{ openvpn_ldap_ca_dir }} +{% endif %} + +{% if openvpn_ldap_tls_auth %} + # Client Certificate and key + # If TLS client authentication is required + TLSCertFile {{ openvpn_ldap_tls_cert }} + TLSKeyFile {{ openvpn_ldap_tls_key }} +{% endif %} + + # Cipher Suite + # The defaults are usually fine here + #TLSCipherSuite {{ openvpn_ldap_tls_ciphersuite }} + + + + # Base DN + BaseDN "{{ openvpn_ldap_base_dn }}" + + # User Search Filter + SearchFilter "{{ openvpn_ldap_user_search }}" + + # Require Group Membership + RequireGroup {{ openvpn_ldap_require_group }} + +{% if openvpn_ldap_require_group %} + # Add non-group members to a PF table (disabled) + #PFTable ips_vpn_users + + + BaseDN "{{ openvpn_ldap_group_base }}" + SearchFilter "{{ openvpn_ldap_group_filter }}" + RFC2307bis {{ openvpn_ldap_without_posix_groups }} + MemberAttribute {{ openvpn_ldap_group_member_attr }} + # Add group members to a PF table (disabled) + # #PFTable ips_vpn_eng + +{% endif %} + diff --git a/templates/auth-ldap.pl.j2 b/templates/auth-ldap.pl.j2 new file mode 100644 index 0000000..12ec825 --- /dev/null +++ b/templates/auth-ldap.pl.j2 @@ -0,0 +1,42 @@ +#!/usr/bin/perl -w + +{% if openvpn_ldap_perl_auth_ssl %} +use Net::LDAPS; +{% else %} +use Net::LDAP; +{% endif %} +use strict; + +my $ldap; +my $result; + +my $opt_uri = "{{ openvpn_ldap_host }}"; +my $opt_user = $ENV{'username'}; +my $opt_passwd = $ENV{'password'}; +my $opt_group = "cn={{ openvpn_ldap_perl_auth_group }},{{ openvpn_ldap_group_base }}"; +my $opt_binddn = "uid=".$opt_user.",{{ openvpn_ldap_base_dn }}"; + +{% if openvpn_ldap_perl_auth_ssl %} +$ldap = Net::LDAPS->new($opt_uri, version => 3, + port => '{{ openvpn_ldap_perl_auth_sslport }}', + verify => 'require', + {% if openvpn_ca_dir %} + capath => '{{ openvpn_ldap_ca }}' + {% else %} + cafile => '{{ openvpn_ldap_ca }}' + {% endif %} + ) or die("LDAPS connect to $opt_uri failed!"); +{% else %} +$ldap = Net::LDAP->new($opt_uri) or die("LDAP connect to $opt_uri failed!"); +{% endif %} + +{% if openvpn_ldap_nonanon_bind %} +$result = $ldap->bind('{{ openvpn_ldap_binddn }}', password => '{{ openvpn_ldap_bindpwd | default('') }}'); +{% else %} +$result = $ldap->bind($opt_binddn, password => $opt_passwd); +{% endif %} +$result->code and die($result->error); +$result = $ldap->search(base=>$opt_group, filter => "(&({{ openvpn_ldap_group_member_attr }}=$opt_user))"); +$result->code(); +if ($result->count == 1) { exit 0; } +unless($result->count){ exit 1; } diff --git a/templates/check_openvpn.pl.j2 b/templates/check_openvpn.pl.j2 new file mode 100644 index 0000000..ea87160 --- /dev/null +++ b/templates/check_openvpn.pl.j2 @@ -0,0 +1,219 @@ +#!/usr/bin/perl -w + +####################################################################### +# +# Copyright (c) 2007 Jaime Gascon Romero +# +# License Information: +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# $Id: check_openvpn.pl,v 1.0 2007/07/15 16:07:20 jgr Exp jgr $ +# $Revision: 1.0 $ +# Home Site: http://emergeworld.blogspot.com/ +# ##################################################################### + +use diagnostics; +use strict; +use Net::Telnet (); +use Getopt::Long qw(:config no_ignore_case); +use vars qw($PROGNAME $VERSION); +{% if ansible_distribution_file_variety == "Debian" %} +use lib "/usr/lib/nagios/plugins"; +{% elif ansible_distribution_file_variety == "Debian" %} +use lib "/usr/lib64/nagios/plugins"; +{% endif %} +use utils qw(%ERRORS); + +$PROGNAME = "check_openvpn"; +$VERSION = '$Revision: 1.0 $'; + +$ENV{'PATH'}=''; +$ENV{'BASH_ENV'}=''; +$ENV{'ENV'}=''; + +my ($opt_h, $opt_H, $opt_p, $opt_P, $opt_t, $opt_i, $opt_n, $opt_c, $opt_w, $opt_C, $opt_r); + +sub print_help (); +sub print_usage (); + +GetOptions + ("h" => \$opt_h, "help" => \$opt_h, + "H=s" => \$opt_H, "host=s" => \$opt_H, + "p=i" => \$opt_p, "port=i" => \$opt_p, + "P=s" => \$opt_P, "password=s" => \$opt_P, + "t=i" => \$opt_t, "timeout=i" => \$opt_t, + "i" => \$opt_i, "ip" => \$opt_i, + "n" => \$opt_n, "numeric" => \$opt_n, + "c" => \$opt_c, "critical" => \$opt_c, + "w" => \$opt_w, "warning" => \$opt_w, + "C=s" => \$opt_C, "common_name=s" => \$opt_C, + "r=s" => \$opt_r, "remote_ip=s" => \$opt_r, + ) or exit $ERRORS{'UNKNOWN'}; + +# default values +unless ( defined $opt_t ) { + $opt_t = 10; +} + +if ($opt_h) {print_help(); exit $ERRORS{'OK'};} + +if ( ! defined($opt_H) || ! defined($opt_p) ) { + print_usage(); + exit $ERRORS{'UNKNOWN'} +} + +my @lines; +my @clients; +my @clients_ip; +my $t; + +eval { +$t = new Net::Telnet (Timeout => $opt_t, + Port => $opt_p, + Prompt => '/END$/' + ); +$t->open($opt_H); +if ( defined $opt_P ) { + $t->waitfor('/ENTER PASSWORD:$/'); + $t->print($opt_P); +} +$t->waitfor('/^$/'); +@lines = $t->cmd("status 2"); +$t->close; +}; + +if ($@) { + print "OpenVPN Critical: Can't connect to server\n"; + exit $ERRORS{'CRITICAL'}; +} + + +if (defined $opt_i || defined $opt_r) { + foreach (@lines) { + if ($_ =~ /CLIENT_LIST,.*,(\d+\.\d+\.\d+\.\d+):\d+,/) { + push @clients_ip, $1; + } +} + if (defined $opt_i) { + print "OpenVPN OK: "."@clients_ip "; + exit $ERRORS{'OK'}; + } elsif (defined $opt_r) { + if ( ! grep /\b$opt_r\b/, @clients_ip) { + if (defined $opt_c) { + print "OpenVPN CRITICAL: $opt_r don't found"; + exit $ERRORS{'CRITICAL'}; + } else { + print "OpenVPN WARNING: $opt_r don't found"; + exit $ERRORS{'WARNING'}; + } + } + print "OpenVPN OK: "."@clients_ip "; + exit $ERRORS{'OK'}; + } +} + +foreach (@lines) { + if ($_ =~ /CLIENT_LIST,(.*),\d+\.\d+\.\d+\.\d+:\d+,/) { + push @clients, $1; + } +} + +if (defined $opt_C) { + if ( ! grep /\b$opt_C\b/, @clients) { + if (defined $opt_c) { + print "OpenVPN CRITICAL: $opt_C don't found"; + exit $ERRORS{'CRITICAL'}; + } else { + print "OpenVPN WARNING: $opt_C don't found"; + exit $ERRORS{'WARNING'}; + } + } +} + + +if (defined $opt_n) { +print "OpenVPN OK: ".@clients." connected clients."; +exit $ERRORS{'OK'}; +} + +print "OpenVPN OK: "."@clients "; +exit $ERRORS{'OK'}; + +####################################################################### +###### Subroutines #################################################### + +sub print_usage() { + print "Usage: $PROGNAME -H | --host -p | --port [-P | --password] [-t | --timeout] + [-i | --ip] [-n | --numeric] [-C | --common_name] [-r | --remote_ip] [-c | --critical] [-w | --warning]\n\n"; + print " $PROGNAME [-h | --help]\n"; +} + +sub print_help() { + print "$PROGNAME $VERSION\n\n"; + print "Copyright (c) 2007 Jaime Gascon Romero + +Nagios plugin to check the clients connected to a openvpn server. + +"; + print_usage(); + print " +-H | --host + IP address or hostname of the openvpn server. + +-p | --port + Management port interface of the openvpn server. + +-P | --password + Password for the management interface of the openvpn server. + +-t | --timeout + Timeout for the connection attempt. Optional, default 10 seconds. + + + Optional parameters + =================== + +-i | --ip + Prints the IP address of the remote client instead of the common name. + +-n | --numeric + Prints the number of clients connected to the openvpn server. + + + Matching Parameters + =================== + +-C | --common_name + The common name, as it is specified in the client certificate, who is wanted to check. + +-r | --remote_ip + The client remote ip address who is wanted to check. + +-c | --critical + Exits with CRITICAL status if the client specified by the common name or the remote ip address is not connected. + +-w | --warning + Exits with WARNING status if the client specified by the common name or the remote ip address is not connected. + + + Other Parameters + ================ + +-h | --help + Show this help. +"; + +} + +# vim:sts=2:sw=2:ts=2:et diff --git a/templates/client.conf.j2 b/templates/client.conf.j2 new file mode 100644 index 0000000..efc1289 --- /dev/null +++ b/templates/client.conf.j2 @@ -0,0 +1,32 @@ +client +dev {{ openvpn_dev }} +proto {{ openvpn_protocol }} +{% for srv in openvpn_remote_servers %} +remote {{ srv.host }} {{ srv.port }} +{% endfor %} +remote-random +resolv-retry infinite +nobind +{% if openvpn_run_unprivileged %} +# Downgrade privileges after initialization (non-Windows only) +user {{ openvpn_unprivileged_user }} +group {{ openvpn_unprivileged_group }} +{% endif %} +# Try to preserve some state across restarts. +persist-key +persist-tun +ca {{ openvpn_ca }} +cert {{ openvpn_cert }} +key {{ openvpn_key }} +{% if openvpn_cert_auth_enabled %} +tls-client +remote-cert-tls server +{% endif %} +tls-auth {{ openvpn_tls_auth }} 1 +key-direction 1 +cipher AES-256-CBC +keepalive {{ openvpn_keepalive }} +# Set log file verbosity. +verb {{ openvpn_verbosity_log }} +# Silence repeating messages +mute {{ openvpn_mute_after }} diff --git a/templates/management.txt.j2 b/templates/management.txt.j2 new file mode 100644 index 0000000..de14389 --- /dev/null +++ b/templates/management.txt.j2 @@ -0,0 +1 @@ +{{ openvpn_management_password }} diff --git a/templates/openvpn-defaults.j2 b/templates/openvpn-defaults.j2 new file mode 100644 index 0000000..cff032d --- /dev/null +++ b/templates/openvpn-defaults.j2 @@ -0,0 +1,36 @@ +# This is the configuration file for /etc/init.d/openvpn + +# +# Start only these VPNs automatically via init script. +# Allowed values are "all", "none" or space separated list of +# names of the VPNs. If empty, "all" is assumed. +# The VPN name refers to the VPN configutation file name. +# i.e. "home" would be /etc/openvpn/home.conf +# +# If you're running systemd, changing this variable will +# require running "systemctl daemon-reload" followed by +# a restart of the openvpn service (if you removed entries +# you may have to stop those manually) +# +AUTOSTART="all" +#AUTOSTART="none" +#AUTOSTART="home office" +# +# WARNING: If you're running systemd the rest of the +# options in this file are ignored. +# +# Refresh interval (in seconds) of default status files +# located in /var/run/openvpn.$NAME.status +# Defaults to 10, 0 disables status file generation +# +#STATUSREFRESH=10 +#STATUSREFRESH=0 +# Optional arguments to openvpn's command line +OPTARGS="" +# +# If you need openvpn running after sendsigs, i.e. +# to let umountnfs work over the vpn, set OMIT_SENDSIGS +# to 1 and include umountnfs as Required-Stop: in openvpn's +# init.d script (remember to run insserv after that) +# +OMIT_SENDSIGS=0 diff --git a/templates/openvpn-nrpe.cfg.j2 b/templates/openvpn-nrpe.cfg.j2 new file mode 100644 index 0000000..a623b90 --- /dev/null +++ b/templates/openvpn-nrpe.cfg.j2 @@ -0,0 +1,2 @@ +# OpenVPN connected users +command[openvpn_users]={{ nagios_plugins_dir }}/check_openvpn -H {{ openvpn_management_ip }} -p {{ openvpn_management_port }} -P {{ openvpn_management_password }} diff --git a/templates/openvpn.conf.j2 b/templates/openvpn.conf.j2 new file mode 100644 index 0000000..f2bf690 --- /dev/null +++ b/templates/openvpn.conf.j2 @@ -0,0 +1,126 @@ +mode {{ openvpn_mode }} +dev {{ openvpn_dev }} + +port {{ openvpn_port }} +proto {{ openvpn_protocol }} + +{% if openvpn_tls_server %} +tls-server +{% endif %} + +dh {{ openvpn_dh }} +ca {{ openvpn_ca }} +cert {{ openvpn_cert }} +key {{ openvpn_key }} + +topology subnet + +server {{ openvpn_server_net }} + +ifconfig-pool-persist ipp/ipp.txt + +client-config-dir ccd +# EXAMPLE: Suppose the client +# having the certificate common name "Thelonious" +# also has a small subnet behind his connecting +# machine, such as 192.168.40.128/255.255.255.248. +# First, uncomment out these lines: +;client-config-dir ccd +;route 192.168.40.128 255.255.255.248 +# Then create a file ccd/Thelonious with this line: +# iroute 192.168.40.128 255.255.255.248 +# This will allow Thelonious' private subnet to +# access the VPN. This example will only work +# if you are routing, not bridging, i.e. you are +# using "dev tun" and "server" directives. + +# EXAMPLE: Suppose you want to give +# Thelonious a fixed VPN IP address of 10.9.0.1. +# First uncomment out these lines: +;client-config-dir ccd +;route 10.9.0.0 255.255.255.252 +# Then add this line to ccd/Thelonious: +# ifconfig-push 10.9.0.1 10.9.0.2 + +# Suppose that you want to enable different +# firewall access policies for different groups +# of clients. There are two methods: +# (1) Run multiple OpenVPN daemons, one for each +# group, and firewall the TUN/TAP interface +# for each group/daemon appropriately. +# (2) (Advanced) Create a script to dynamically +# modify the firewall in response to access +# from different clients. See man +# page for more info on learn-address script. +;learn-address ./script + +{% for route in openvpn_push_routes %} +push "route {{ route }}" +{% endfor %} + +{% for route in openvpn_push_routes %} +push "route {{ route }}" +{% endfor %} + +{% if openvpn_push_settings is defined %} +{% for dhcp_opt in openvpn_push_settings %} +push "{{ dhcp_opt }}" +{% endfor %} +{% endif %} + +tls-auth {{ openvpn_tls_auth }} + +# Select a cryptographic cipher. +# This config item must be copied to +# the client config file as well. +# Note that v2.4 client/server will automatically +# negotiate AES-256-GCM in TLS mode. +# See also the ncp-cipher option in the manpage +cipher AES-256-CBC + + +{% if openvpn_compression_enabled %} +compress lz4-v2 +push "compress lz4-v2" +{% endif %} + +keepalive {{ openvpn_keepalive }} + +{% if not openvpn_cert_auth_enabled %} +# Disable cert-auth +client-cert-not-required +{% endif %} + +{% if openvpn_username_pam_auth %} +username-as-common-name +# PAM login +plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so login +{% endif %} + +{% if openvpn_ldap_auth %} +plugin /usr/lib/openvpn/openvpn-auth-ldap.so /etc/openvpn/auth/auth-ldap.conf +{% endif %} + +{% if openvpn_ldap_perl_auth %} +auth-user-pass-verify /etc/openvpn/auth/auth-ldap via-env +#script-security 3 execve +{% endif %} + +max-clients {{ openvpn_max_clients }} + +persist-tun +persist-key + +status status/openvpn-status.log + +{% if openvpn_run_unprivileged %} +user {{ openvpn_unprivileged_user }} +group {{ openvpn_unprivileged_group }} +{% endif %} + +verb {{ openvpn_verbosity_log }} +mute {{ openvpn_mute_after }} + +# Notify the client that when the server restarts so it +# can automatically reconnect. +explicit-exit-notify 1 diff --git a/templates/server.conf.j2 b/templates/server.conf.j2 new file mode 100644 index 0000000..37a8d65 --- /dev/null +++ b/templates/server.conf.j2 @@ -0,0 +1,79 @@ +mode {{ openvpn_mode }} +{% if openvpn_management_enabled %} +management {{ openvpn_management_ip }} {{ openvpn_management_port }} {{ openvpn_management_file }} +{% endif %} +dev {{ openvpn_dev }} +port {{ openvpn_port }} +proto {{ openvpn_protocol }} +topology subnet +server {{ openvpn_server_net }} +{% if openvpn_ifconfig_pool is defined %} +# Works in bridge mode only +#ifconfig-pool {{ openvpn_ifconfig_pool }} +{% endif %} +ifconfig-pool-persist ipp/ipp.txt +client-config-dir ccd +{% if openvpn_force_ccd %} +ccd-exclusive +{% endif %} +{% if openvpn_client_routes is defined %} +{% for route in openvpn_client_routes %} +route {{ route }} +{% endfor %} +{% endif %} +{% if openvpn_push_routes is defined %} +{% for route in openvpn_push_routes %} +push "route {{ route }}" +{% endfor %} +{% endif %} +{% if openvpn_push_settings is defined %} +{% for dhcp_opt in openvpn_push_settings %} +push "{{ dhcp_opt }}" +{% endfor %} +{% endif %} +cipher AES-256-CBC +{% if openvpn_compression_enabled %} +compress lz4-v2 +push "compress lz4-v2" +{% endif %} +keepalive {{ openvpn_keepalive }} +{% if openvpn_cert_auth_enabled %} +tls-server +{% endif %} +tls-auth {{ openvpn_tls_auth }} 0 +key-direction 0 +dh {{ openvpn_dh }} +ca {{ openvpn_ca }} +cert {{ openvpn_cert }} +key {{ openvpn_key }} +{% if not openvpn_cert_auth_enabled %} +# Disable cert-auth +client-cert-not-required +{% endif %} +{% if openvpn_username_pam_auth %} +username-as-common-name +# PAM login +plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so login +{% endif %} +{% if openvpn_ldap_auth %} +plugin /usr/lib/openvpn/openvpn-auth-ldap.so /etc/openvpn/auth/auth-ldap.conf +{% endif %} +{% if openvpn_ldap_perl_auth %} +auth-user-pass-verify /etc/openvpn/auth/auth-ldap via-env +script-security 3 execve +{% endif %} +max-clients {{ openvpn_max_clients }} +persist-tun +persist-key +status status/openvpn-status.log +{% if openvpn_run_unprivileged %} +user {{ openvpn_unprivileged_user }} +group {{ openvpn_unprivileged_group }} +{% endif %} +verb {{ openvpn_verbosity_log }} +mute {{ openvpn_mute_after }} +{% if openvpn_protocol == 'udp' %} +# Notify the client that when the server restarts so it +# can automatically reconnect. +explicit-exit-notify 1 +{% endif %} diff --git a/templates/user-ccd.conf.j2 b/templates/user-ccd.conf.j2 new file mode 100644 index 0000000..0ca993a --- /dev/null +++ b/templates/user-ccd.conf.j2 @@ -0,0 +1,4 @@ +ifconfig-push {{ item.ip }} {{ item.netmask }} +{% for net in item.routes %} +push "route {{ net }}" +{% endfor %}