diff --git a/OpenVPN_by_Zabbix_agent.yaml b/OpenVPN_by_Zabbix_agent.yaml new file mode 100644 index 0000000..8c36d03 --- /dev/null +++ b/OpenVPN_by_Zabbix_agent.yaml @@ -0,0 +1,597 @@ +zabbix_export: + version: '6.0' + date: '2023-07-27T10:47:37Z' + groups: + - + uuid: a571c0d144b14fd4a87a9d9b2aa9fcd6 + name: Templates/Applications + templates: + - + uuid: c3272861e3ff46e2b3daa302066c53c7 + template: 'OpenVPN by Zabbix agent' + name: 'OpenVPN by Zabbix agent' + description: 'OpenVPN by Zabbix agent' + groups: + - + name: Templates/Applications + items: + - + uuid: 51151af0bb704668a1bb3b390cee2039 + name: 'OpenVPN stats' + key: discovery.ovpn + history: 14d + trends: '0' + value_type: TEXT + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: raw + - + uuid: 877b2f94cd4645fcaae13543f42d79be + name: 'OpenVPN clients limit' + key: ovpn.clients.limit + delay: 1h + history: 14d + units: client + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN server' + value: 'clients limit' + - + uuid: 00c5526a838e4f7791b4edafc20bb094 + name: 'OpenVPN expiration ca' + key: ovpn.expiration.ca + delay: 1h + history: 14d + units: s + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN server' + value: expiration + triggers: + - + uuid: 34fbc5a346d0458c8e529b92f0aa39c5 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.ca,#1)<86400' + name: 'OpenVPN ca certificate expires in 1 day' + priority: DISASTER + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 016c7d7c40b342c883ff81d0a5817b75 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.ca,#1)<604800' + name: 'OpenVPN ca certificate expires in 7 days' + priority: HIGH + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 2d9f9da08348499ab9b7584a9386abfc + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.ca,#1)<2592000' + name: 'OpenVPN ca certificate expires in 30 days' + priority: AVERAGE + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 65912ffeb36a4b2c8bae996c0b865f69 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.ca,#1)<7776000' + name: 'OpenVPN ca certificate expires in 90 days' + priority: WARNING + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 591d03b0553645788c5b178670cb8bc9 + name: 'OpenVPN expiration cert' + key: ovpn.expiration.cert + delay: 1h + history: 14d + units: s + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN server' + value: expiration + triggers: + - + uuid: 6a2bc06ed9944e95bfdec45af330bd53 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.cert,#1)<86400' + name: 'OpenVPN cert certificate expires in 1 day' + priority: DISASTER + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: da6ae766472541e8addb2712584289c7 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.cert,#1)<604800' + name: 'OpenVPN cert certificate expires in 7 days' + priority: HIGH + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 5571d2aa00a2479889bdd853b37d7160 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.cert,#1)<2592000' + name: 'OpenVPN cert certificate expires in 30 days' + priority: AVERAGE + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 083b3ed043db4d209b093056c04605c3 + expression: 'last(/OpenVPN by Zabbix agent/ovpn.expiration.cert,#1)<7776000' + name: 'OpenVPN cert certificate expires in 90 days' + priority: WARNING + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: expiration + - + uuid: 5ba0b1d455444ec8851c4cdda408ed24 + name: 'Clients count' + type: DEPENDENT + key: ovpn.stats.clients_count + delay: '0' + history: 14d + units: clients + preprocessing: + - + type: JSONPATH + parameters: + - $.clients_count + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: 'clients count' + - + uuid: 731f61d192f944769aaf82c2fb05676b + name: 'Clients found' + type: DEPENDENT + key: ovpn.stats.clients_found + delay: '0' + history: 14d + units: clients + preprocessing: + - + type: JSONPATH + parameters: + - $.clients_found + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: 'clients found' + - + uuid: f38e82ba64f14385bd60a1397eda278c + name: 'Stats updated' + type: DEPENDENT + key: ovpn.stats.updated + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - $.stats_updated + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN stats' + value: updated + discovery_rules: + - + uuid: f6b3ac3373544c1f820c207234177816 + name: 'Discovery openvpn clients' + type: DEPENDENT + key: get.ovpn.stats + delay: '0' + item_prototypes: + - + uuid: fc55509717fc4ee7bef6f684932ee01a + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" bytes recieved' + type: DEPENDENT + key: 'ovpn.client.b_rx.name[{#OVPN_CLIENT_NAME}]' + delay: '0' + history: 14d + units: B + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#OVPN_CLIENT_NAME}")].b_rx.first()' + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN client' + value: '{#OVPN_CLIENT_NAME}' + - + uuid: e75ed01ce6cd45e0822e021f6733b115 + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" bytes transmitted' + type: DEPENDENT + key: 'ovpn.client.b_tx.name[{#OVPN_CLIENT_NAME}]' + delay: '0' + history: 14d + units: B + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#OVPN_CLIENT_NAME}")].b_tx.first()' + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN client' + value: '{#OVPN_CLIENT_NAME}' + - + uuid: 332feedbbd314a479ba27d43c7bb7523 + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" real ip' + type: DEPENDENT + key: 'ovpn.client.r_ip.name[{#OVPN_CLIENT_NAME}]' + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#OVPN_CLIENT_NAME}")].r_ip.first()' + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN client' + value: '{#OVPN_CLIENT_NAME}' + - + tag: 'OpenVPN stats' + value: 'real ip' + - + uuid: 0dc7671cdc9b47c6a7c06b47a8de25ca + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" connect duration' + type: DEPENDENT + key: 'ovpn.client.t_cd.name[{#OVPN_CLIENT_NAME}]' + delay: '0' + history: 14d + units: s + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#OVPN_CLIENT_NAME}")].t_cd.first()' + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN client' + value: '{#OVPN_CLIENT_NAME}' + - + uuid: 127fd46950194d9ba5325f183cb6e940 + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" connect time' + type: DEPENDENT + key: 'ovpn.client.t_cs.name[{#OVPN_CLIENT_NAME}]' + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#OVPN_CLIENT_NAME}")].t_cs.first()' + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN client' + value: '{#OVPN_CLIENT_NAME}' + - + uuid: 2bb9be1dd4a041b09e68f7e9022676d4 + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" virtual ip' + type: DEPENDENT + key: 'ovpn.client.v_ip.name[{#OVPN_CLIENT_NAME}]' + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#OVPN_CLIENT_NAME}")].v_ip.first()' + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN client' + value: '{#OVPN_CLIENT_NAME}' + - + tag: 'OpenVPN stats' + value: 'virtual ip' + graph_prototypes: + - + uuid: 0e740374d0d7435990fba99d19211947 + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" connect duration' + show_work_period: 'NO' + show_triggers: 'NO' + graph_items: + - + color: FFBF00 + item: + host: 'OpenVPN by Zabbix agent' + key: 'ovpn.client.t_cd.name[{#OVPN_CLIENT_NAME}]' + - + uuid: 48b62a9621c64082919a6e041defc546 + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" traffic' + graph_items: + - + drawtype: GRADIENT_LINE + color: 00FF00 + item: + host: 'OpenVPN by Zabbix agent' + key: 'ovpn.client.b_rx.name[{#OVPN_CLIENT_NAME}]' + - + sortorder: '1' + drawtype: BOLD_LINE + color: 0080FF + item: + host: 'OpenVPN by Zabbix agent' + key: 'ovpn.client.b_tx.name[{#OVPN_CLIENT_NAME}]' + master_item: + key: discovery.ovpn + lld_macro_paths: + - + lld_macro: '{#OVPN_CLIENT_NAME}' + path: $..name.first() + - + lld_macro: '{#OVPN_CLIENT_R_IP}' + path: $..r_ip.first() + - + lld_macro: '{#OVPN_CLIENT_V_IP}' + path: $..v_ip.first() + - + lld_macro: '{#OVPN_CLIENT_B_RX}' + path: $..b_rx.first() + - + lld_macro: '{#OVPN_CLIENT_B_TX}' + path: $..b_tx.first() + - + lld_macro: '{#OVPN_CLIENT_T_CS}' + path: $..t_cs.first() + - + lld_macro: '{#OVPN_CLIENT_T_CD}' + path: $..t_cd.first() + dashboards: + - + uuid: d25222f632c74c83ac80c8cbce480db0 + name: OpenVPN + auto_start: 'NO' + pages: + - + name: Server + widgets: + - + type: ITEM + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + key: ovpn.expiration.ca + host: 'OpenVPN by Zabbix agent' + - + type: ITEM + x: '6' + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + key: ovpn.expiration.cert + host: 'OpenVPN by Zabbix agent' + - + type: GRAPH_CLASSIC + 'y': '2' + width: '11' + height: '5' + hide_header: 'YES' + fields: + - + type: GRAPH + name: graphid + value: + name: 'OpenVPN certificates expiration' + host: 'OpenVPN by Zabbix agent' + - + name: Clients + widgets: + - + type: ITEM + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + key: ovpn.stats.clients_count + host: 'OpenVPN by Zabbix agent' + - + type: ITEM + x: '6' + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + key: ovpn.stats.clients_found + host: 'OpenVPN by Zabbix agent' + - + type: GRAPH_PROTOTYPE + 'y': '7' + width: '11' + height: '5' + fields: + - + type: INTEGER + name: columns + value: '1' + - + type: GRAPH_PROTOTYPE + name: graphid + value: + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" traffic' + host: 'OpenVPN by Zabbix agent' + - + type: GRAPH_CLASSIC + 'y': '2' + width: '11' + height: '5' + hide_header: 'YES' + fields: + - + type: GRAPH + name: graphid + value: + name: 'OpenVPN clients sum' + host: 'OpenVPN by Zabbix agent' + - + type: URL + x: '11' + width: '13' + height: '12' + hide_header: 'YES' + fields: + - + type: STRING + name: url + value: '/zabbix.php?name=virtual ip&tags%5B0%5D%5Btag%5D=Application&tags%5B0%5D%5Boperator%5D=0&tags%5B0%5D%5Bvalue%5D=OpenVPN&show_tags=0&action=latest.view&kiosk=1' + triggers: + - + uuid: 022e1211349c48a4be951588cda2dba0 + expression: 'max(/OpenVPN by Zabbix agent/ovpn.stats.clients_count,#1)>=max(/OpenVPN by Zabbix agent/ovpn.clients.limit,#1)' + name: 'Maximum number of OpenVPN clients reached' + priority: AVERAGE + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN server' + value: 'clients limit' + graphs: + - + uuid: a23bdc304f5d49e1b42597f9cbd4e840 + name: 'OpenVPN certificates expiration' + graph_items: + - + drawtype: GRADIENT_LINE + color: FFBF00 + item: + host: 'OpenVPN by Zabbix agent' + key: ovpn.expiration.ca + - + sortorder: '1' + drawtype: BOLD_LINE + color: FF8000 + item: + host: 'OpenVPN by Zabbix agent' + key: ovpn.expiration.cert + - + uuid: 94d1e1cc40424214a291067790d3db89 + name: 'OpenVPN clients sum' + graph_items: + - + color: FF8000 + item: + host: 'OpenVPN by Zabbix agent' + key: ovpn.stats.clients_count + - + sortorder: '1' + color: FFBF00 + item: + host: 'OpenVPN by Zabbix agent' + key: ovpn.stats.clients_found diff --git a/README.md b/README.md index 1b83014..62cf9e8 100644 --- a/README.md +++ b/README.md @@ -2,45 +2,70 @@ OpenVPN management and monitoring utils. -* [`ovpn-cert-expiration`.sh](https://git.hmp.today/pavel.muhortov/openvpn-management#ovpn-cert-expiration-sh) +* [`ovpn_status`.py](https://git.hmp.today/pavel.muhortov/openvpn-management#ovpn_status-py) * [`ovpn-client-management`.sh](https://git.hmp.today/pavel.muhortov/openvpn-management#ovpn-client-management-sh) -* [`ovpn-connect-handling`.sh](https://git.hmp.today/pavel.muhortov/openvpn-management#ovpn-connect-handling-sh) ____ -## `ovpn-cert-expiration`.sh +## `ovpn_status`.py **Description:** -> Checking openvpn server certificates expiration and preparing stats for monitoring. +> OpenVPN server status parser. **Dependencies:** > > * privileged rights -> * [openssl](https://www.openssl.org/) (tested version 1.1.1k on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> * [Python 3](https://www.python.org/downloads/) (tested version 3.9.5 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> * Existing `status`, `server`, `ca`, `cert` options in [server.conf](https://openvpn.net/community-resources/reference-manual-for-openvpn-2-5/#options) | POSITION | PARAMETERS | DESCRIPTION | DEFAULT | |-----------|--------------|------------------------|---------------| -| 1 |**[qn]**|execution without pauses|| -| 2 |**[/path/to/conf]**|openvpn server config file path|/etc/openvpn/server/server.conf| +|**[-s, --server_conf]**|path to OpenVPN server configuration file|**REQUIRED**| +|**[-f, --filter]**|client names filter by regex|`.*`| +|**[-g, --geo]**|check client real ip geo location (may be slow)|`.*`| -Example usage: +Example usage with Zabbix agent: + +```bash +# add options to openvpn server config file +sudo tee -a /etc/openvpn/server/server.conf > /dev/null <<'EOF' +status /var/log/openvpn/openvpn-status.log +status-version 2 +server 10.0.0.0 255.0.0.0 +ca /etc/openvpn/easy-rsa/pki/ca.crt +cert /etc/openvpn/easy-rsa/pki/issued/server.crt +EOF +sudo systemctl restart openvpn@server +``` ```bash # download -sudo wget https://git.hmp.today/pavel.muhortov/openvpn-management/raw/branch/master/ovpn-cert-expiration.sh -O /etc/openvpn/server/ovpn-cert-expiration.sh -sudo chmod +x /etc/openvpn/server/ovpn-cert-expiration.sh +sudo wget https://git.hmp.today/pavel.muhortov/openvpn-management/raw/branch/master/ovpn_status.py -O /etc/openvpn/server/ovpn_status.py +sudo chmod +x /etc/openvpn/server/ovpn_status.py ``` ```bash -# sudo crontab -e -0 * * * * bash /etc/openvpn/server/ovpn-cert-expiration.sh qn +# edit sudoers +sudo sh -c "echo ' +zabbix ALL=(ALL) NOPASSWD:/etc/openvpn/server/ovpn_status.py -s /etc/openvpn/server/server.conf +' > /etc/sudoers.d/zabbix_agentd" +# check permission +sudo -u zabbix sudo /etc/openvpn/server/ovpn_status.py -s /etc/openvpn/server/server.conf ``` ```bash -# check stats -watch cat /var/log/openvpn/ovpn-cert-expiration.log +# add UserParameter to Zabbix agent +sudo sh -c "echo ' +Timeout=30 +AllowRoot=0 +UserParameter=discovery.ovpn, sudo /etc/openvpn/server/ovpn_status.py -s /etc/openvpn/server/server.conf +' >> /etc/zabbix/zabbix_agentd.conf" +sudo systemctl restart zabbix-agent ``` +Download [OpenVPN_by_Zabbix_agent.yaml](https://git.hmp.today/pavel.muhortov/openvpn-management/raw/branch/master/OpenVPN_by_Zabbix_agent.yaml) template +Zabbix Server -> Configuration -> Templates -> Import template + ____ ## `ovpn-client-management`.sh @@ -92,51 +117,3 @@ sudo ./ovpn del username -f # check journal tail -f /var/log/openvpn/ovpn.log ``` - -____ - -## `ovpn-connect-handling`.sh - -**Description:** -> Handling client connection and preparing stats for monitoring. - -**Dependencies:** -> -> * executing by [openvpn](https://openvpn.net/) server (tested version 2.5.1 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> * [jq](https://github.com/stedolan/jq) (tested version 1.6 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> * [grepcidr](https://github.com/ryantig/grepcidr) (tested version 2.0 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> * [Python 3](https://www.python.org/downloads/) (tested version 3.9.5 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> * existing [/usr/local/bin/sendmail.py](https://git.hmp.today/pavel.muhortov/utils#sendmail-py) -> * [bash](https://www.gnu.org/software/bash/) (tested versions: 5.1.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/), 5.0.17 on [Ubuntu 20](https://wiki.ubuntu.com/FocalFossa/ReleaseNotes), 4.2.46 on [CentOS 7](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS7.2009)) - -| POSITION | PARAMETERS | DESCRIPTION | DEFAULT | -|-----------|--------------|------------------------|---------------| -| 1 |**inc\|dec**|increment or decrement counter|**REQUIRED**| -| 2 |****|root path for counter, names, log|**REQUIRED**| -| 3 |**[mail]**|send email notification|| -| 4 |**[geo]**|check client address geolocation|| - -Example usage: - -```bash -# download -sudo wget https://git.hmp.today/pavel.muhortov/openvpn-management/raw/branch/master/ovpn-connect-handling.sh -O /etc/openvpn/server/ovpn-connect-handling.sh -sudo chmod +x /etc/openvpn/server/ovpn-connect-handling.sh -``` - -```bash -# add options to openvpn server config file -sudo tee -a /etc/openvpn/server/server.conf > /dev/null <<'EOF' -script-security 2 -client-connect "/etc/openvpn/server/ovpn-connect-handling.sh inc /var/log/openvpn mail geo" -client-disconnect "/etc/openvpn/server/ovpn-connect-handling.sh dec /var/log/openvpn - -" -EOF -sudo systemctl restart openvpn@server -``` - -```bash -# check counter and names -watch cat /var/log/openvpn/openvpn-counts.log -# check journal -tail -f /var/log/openvpn/ovpn-connect-handling.log -``` diff --git a/ovpn-cert-expiration.sh b/ovpn-cert-expiration.sh deleted file mode 100644 index 3a53bcb..0000000 --- a/ovpn-cert-expiration.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash - -# DESCRIPTION: -# checking openvpn server certificates expiration -# and -# preparing stats for monitoring -# -# DEPENDENCIES: -# - privileged rights -# - openssl -# -# PARAMETERS: -# 1: "qn" - execution without pauses -# 2: openvpn server config file path -# -# FUNCTIONS: -# - -####################################### -# Print message and add to log. -# Globals: -# logs -# Arguments: -# 1: message to print and logging -####################################### -addtologs() { - echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" -} - -####################################### -# Waiting for press [ENTER]. -# Globals: -# None -# Arguments: -# None -####################################### -execpause() { - read -r -p "Press [ENTER] to continue... " -} - -####################################### -# Exit procedure. -# Globals: -# show -# Arguments: -# None -####################################### -execquite() { - addtologs "execution time is $(($(date +%s)-time)) seconds, exit" - if [ "${show}" != "qn" ]; then - execpause - fi - exit -} - -####################################### -# Error exit procedure -# Globals: -# None -# Arguments: -# 1: message to print and logging -####################################### -execerror() { - addtologs "error: $1" - execquite -} - -####################################### -# Parsing config file and creating global vars. -# Globals: -# None -# Arguments: -# None -####################################### -getconfig() { - cacrpath=$(grep ^ca "${conf}" | cut -d' ' -f2) - certpath=$(grep ^cert "${conf}" | cut -d' ' -f2) - statfile="$(dirname "$(grep ^log /etc/openvpn/server/server.conf | cut -d' ' -f2)")/$(basename -s .sh "$0").log" -} - -####################################### -# Checking user rights. -# Globals: -# None -# Arguments: -# None -# return: -# 0 - if privileged rights, 1 - if not privileged rights -####################################### -checkroot() { - if [ "${EUID}" -ne 0 ]; then - return 1 # false - else - return 0 # true - fi -} - -####################################### -# Print certificate expiration date in epoch -# Globals: -# None -# Arguments: -# 1: certificate path -####################################### -checkcert() { - printf '%s\n' "$(date -d "$(openssl x509 -text -noout -in "${1}" | grep 'Not After' | cut -d':' -f2-)" +%s)" -} - -# -# VARIABLES: -# - -show=$1 -conf=$2 -logs=/dev/null -if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then - conf=/etc/openvpn/server/server.conf -fi - -time=$(date +%s) -cd "$(dirname "$(realpath "$0")")" || execerror -if [ ! -e "${conf}" ]; then - execerror "${conf} not found" -else - getconfig -fi - -if ! command -v openssl &> /dev/null; then - execerror "Not found dependencies" -fi - -# -# MAIN: -# - -if checkroot; then - cacrtime=$(checkcert "${cacrpath}") - certtime=$(checkcert "${certpath}") - cacrremain=$(( cacrtime - time )) - certremain=$(( certtime - time)) - addtologs "${cacrpath} remains only ${cacrremain} seconds" - addtologs "${certpath} remains only ${certremain} seconds" - printf '%s\n' "ca=${cacrremain}" > "${statfile}" - printf '%s\n' "cert=${certremain}" >> "${statfile}" - addtologs "stats wrote to ${statfile}" - execquite -else - execerror "Restart this as root!" -fi diff --git a/ovpn-connect-handling.sh b/ovpn-connect-handling.sh deleted file mode 100644 index 0904ea9..0000000 --- a/ovpn-connect-handling.sh +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env bash - -# DESCRIPTION: -# handling client connection -# and -# preparing stats for monitoring -# -# DEPENDENCIES: -# - executing by openvpn server -# - jq -# - grepcidr -# - Python 3 -# - existing /usr/local/bin/sendmail.py -# -# PARAMETERS: -# 1: "inc|dec" - increment or decrement counter -# 2: root path for counter, names, log -# 3: "mail" - send email notification -# 4: "geo" - check client address geolocation -# -# FUNCTIONS: -# - -####################################### -# Print message and add to log. -# Globals: -# logs -# Arguments: -# 1: message to print and logging -####################################### -addtologs() { - echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" -} - -####################################### -# Exit procedure. -# Globals: -# show -# Arguments: -# None -####################################### -execquite() { - addtologs "execution time is $(($(date +%s)-time)) seconds, exit" - exit -} - -####################################### -# Error exit procedure -# Globals: -# None -# Arguments: -# 1: message to print and logging -####################################### -execerror() { - addtologs "error: $1" - execquite -} - -####################################### -# Incrementing counter with adding client name -# Globals: -# counts_file -# common_name (variable by openvpn server) -# ifconfig_pool_remote_ip (variable by openvpn server) -# Arguments: -# None -####################################### -# shellcheck disable=SC2154 -incremcounter() { - summary_cur=$(grep 'total=' "${counts_file}" | cut -d= -f2) - summary_new=${summary_cur} && (( summary_new += 1 )) - counts_temp=$(sed -e "s/total=${summary_cur}/total=${summary_new}/g" "${counts_file}" \ - | sed -e '$a'"${common_name}"'_'"${ifconfig_pool_remote_ip}"'') - addtologs "client ${common_name} connected, counter increment to ${summary_new}" - printf "%s\n" "${counts_temp}" > "${counts_file}" -} - -####################################### -# Decrementing counter with deleting client name -# Globals: -# counts_file -# common_name (variable by openvpn server) -# Arguments: -# None -####################################### -decremcounter(){ - summary_cur=$(grep 'total=' "${counts_file}" | cut -d= -f2) - summary_new=${summary_cur} && (( summary_new -= 1 )) - counts_temp=$(sed -e "s/total=${summary_cur}/total=${summary_new}/g" "${counts_file}" \ - | sed '0,/'"${common_name}"'/{/'"${common_name}"'/d}') - addtologs "client ${common_name} disconnected, counter decrement to ${summary_new}" - printf "%s\n" "${counts_temp}" > "${counts_file}" -} - -####################################### -# Get information about client address -# Globals: -# flaggeol -# untrusted_ip (variable by openvpn server) -# Arguments: -# None -####################################### -# shellcheck disable=SC2154 -expandaddress() { - ipinfo="Source address is ${untrusted_ip}" - localnetworks="10.0.0.0/8 - 100.64.0.0/10 - 127.0.0.1/8 - 172.16.0.0/12 - 192.168.0.0/16 - " - if ! grepcidr "${localnetworks}" <(echo "${untrusted_ip}") >/dev/null; then - if [ "${flaggeol}" == "geo" ]; then - ipinfo=$(curl "https://api.ipbase.com/v1/json/${untrusted_ip}") - if [ "$(jq -r '.country_name' <<< "$ipinfo")" != "" ]; then - z=$(jq -r '.zip_code' <<< "$ipinfo") - c=$(jq -r '.country_name' <<< "$ipinfo") - r=$(jq -r '.region_name' <<< "$ipinfo") - t=$(jq -r '.city' <<< "$ipinfo") - ipinfo="Source address ${untrusted_ip} is from ${z}, ${c}, ${r}, ${t}" - fi - fi - fi - addtologs "client ${common_name} checked. ${ipinfo}" -} - -####################################### -# Send email notification about client connect -# Globals: -# ipinfo -# common_name (variable by openvpn server) -# ifconfig_pool_remote_ip (variable by openvpn server) -# Arguments: -# None -####################################### -startsendmail() { - subj="[VPN Connected] $(cat /etc/hostname): ${common_name} connect to ${ifconfig_pool_remote_ip}" - ( - python3 /usr/local/bin/sendmail.py \ - -u "$(grep "from=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ - -p "$(grep "pass=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ - -d "$(grep "dest=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ - --smtp "$(grep "smtp=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ - --port "$(grep "port=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ - --stls "True" \ - --subj "${subj}" \ - --text "${ipinfo}" \ - >> /dev/null 2>&1 & - ) - addtologs "sent mail with subject '${subj}'" -} - -# -# VARIABLES: -# - -flagmath=$1 -pathroot=$2 -flagmail=$3 -flaggeol=$4 - -time=$(date +%s) -logs="${pathroot}/$(basename -s .sh "$0").log" -counts_file="${pathroot}/ovpn-counts.log" -if [ -z "${pathroot}" ]; then - logs=/dev/null - execerror "Usage example: $0 'inc|dec' '/var/log/openvpn' '-' '-'" -elif [ ! -e "${logs}" ]; then - touch "${logs}" -fi - -if ! command -v curl &> /dev/null || \ - ! command -v /usr/local/bin/sendmail.py &> /dev/null || \ - ! command -v python3 &> /dev/null || \ - ! command -v grepcidr &> /dev/null || \ - ! command -v jq &> /dev/null; then - execerror "Not found dependencies" -fi - -# -# MAIN: -# - -if [ "${flagmath}" == "inc" ]; then - incremcounter - expandaddress - if [ "${flagmail}" == "mail" ]; then - startsendmail - fi -elif [ "${flagmath}" == "dec" ]; then - decremcounter -else - execerror "Usage example: $0 'inc|dec' '/var/log/openvpn' '-' '-'" -fi -execquite diff --git a/ovpn_status.py b/ovpn_status.py new file mode 100644 index 0000000..74fda5a --- /dev/null +++ b/ovpn_status.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +"""It's the OpenVPN server status parser. +""" + +import json +import re +import time +from argparse import ArgumentParser +from ipaddress import IPv4Network +from os import path +from cryptography import x509 +import requests + + +def status(stats_file: str, client_filter: str = '.*', client_geo: bool = False) -> dict: + """OpenVPN status log parser. + + Args: + stats_file (str): path to OpenVPN status log file. + client_filter (str, optional): client names filter by regex. Defaults to '.*'. + client_geo (bool, optional): check client real ip geo location Defaults to False. + + Returns: + dict: { + 'stats_updated': timestamp, + 'clients_count': int, + 'clients_found': int, + 'data': [ + { + "name": str, + "r_ip": str, + "v_ip": str, + "b_rx": int, + "b_tx": int, + "t_cs": timestamp, + "t_cd": int + }, + ] + } + """ + with open(stats_file, mode='r', encoding='utf-8') as file: + stats_data = file.read() + + if re.match("^OpenVPN CLIENT LIST", stats_data): + # status-version 1 + stats_vers = 1 + dlm = ',' + elif re.match("^TITLE,", stats_data): + # status-version 2 + stats_vers = 2 + dlm = ',' + elif re.match("^TITLE\t", stats_data): + # status-version 3 + stats_vers = 3 + dlm = '\t' + else: + stats_vers = 0 + + clients_array = [] + clients_count = -1 + clients_found = 0 + stats_updated = -1 + + if stats_vers == 0: + pass + elif stats_vers == 1: + updated_r = re.search('Updated' + dlm + '.*', stats_data).group(0) + stats_updated = updated_r.replace('Updated' + dlm, '') + updated_t = time.mktime(time.strptime(stats_updated, "%Y-%m-%d %H:%M:%S")) + + clients_s = 'Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since' + clients_e = 'ROUTING TABLE' + clients_r = re.search(clients_s + "(.*)" + clients_e, stats_data, re.DOTALL).group(0) + stats_clients = re.sub(clients_s + '\n', '', re.sub(clients_e, '', clients_r)) + + routing_s = 'Virtual Address,Common Name,Real Address,Last Ref' + routing_e = 'GLOBAL STATS' + routing_r = re.search(routing_s + "(.*)" + routing_e, stats_data, re.DOTALL).group(0) + stats_routing = re.sub(routing_s + '\n', '', re.sub(routing_e, '', routing_r)) + + clients_count = len(stats_clients.splitlines()) + if clients_count > 0: + for client in stats_clients.splitlines(): + client_name = client.split(dlm)[0] + client_r_ip = client.split(dlm)[1].split(':')[0] + client_r_cc = '--' + if client_geo: + client_r_cc = ip_geo(addr=client_r_ip) + if re.findall(client_filter, client_name): + regex_v_ip = re.compile('.*' + dlm.join(client.split(dlm)[:2]) + '.*') + clients_array.append( + { + 'name': client_name, + 'r_ip': client_r_ip, + 'r_cc': client_r_cc, + 'v_ip': regex_v_ip.search(stats_routing).group(0).split(dlm)[0], + 'b_rx': int(client.split(dlm)[2]), + 'b_tx': int(client.split(dlm)[3]), + 't_cs': client.split(dlm)[4], + 't_cd': int(updated_t) - int( + time.mktime( + time.strptime(client.split(dlm)[4], "%Y-%m-%d %H:%M:%S") + ) + ) + } + ) + clients_found += 1 + else: + updated_r = re.search('TIME' + dlm + '.*', stats_data).group(0) + updated_t = updated_r.split(dlm)[2] + stats_updated = updated_r.split(dlm)[1] + stats_clients = '\n'.join(re.findall("^CLIENT_LIST.*", stats_data, re.MULTILINE)) + + clients_count = len(stats_clients.splitlines()) + if clients_count > 0: + for client in stats_clients.splitlines(): + client_name = client.split(dlm)[1] + client_r_ip = client.split(dlm)[2].split(':')[0] + client_r_cc = '--' + if client_geo: + client_r_cc = ip_geo(addr=client_r_ip) + if re.search(client_filter, client_name): + clients_array.append( + { + 'name': client_name, + 'r_ip': client_r_ip, + 'r_cc': client_r_cc, + 'v_ip': client.split(dlm)[3], + 'b_rx': int(client.split(dlm)[5]), + 'b_tx': int(client.split(dlm)[6]), + 't_cs': client.split(dlm)[7], + 't_cd': int(updated_t) - int(client.split(dlm)[8]) + } + ) + clients_found += 1 + + clients_stats = { + 'stats_updated': stats_updated, + 'clients_count': clients_count, + 'clients_found': clients_found, + 'data': clients_array, + } + return clients_stats + + +def ip_num(addr: str, mask: (str, int)) -> int: + """OpenVPN client ip limit calculator (without --ifconfig-pool-linear). + + Args: + addr (str): server subnet. + mask (str, int): server mask. + + Returns: + int: ip limit. + """ + return int(IPv4Network(addr + '/' + mask).num_addresses/4-1) + + +def ce_exp(cert_path: str) -> int: + """Get certificate expiration time. + + Args: + cert_path (str): path to certificate file. + + Returns: + int: certificate expiration time in epoch. + """ + with open(cert_path, mode='rb') as cert_file: + cert_data = x509.load_pem_x509_certificate(cert_file.read()) + return int(cert_data.not_valid_after.timestamp()) + + +def ip_geo(addr: str) -> str: + """Get ip address geo location. + + Args: + addr (str): ip address. + + Returns: + str: country code. + """ + try: + request = 'https://geolocation-db.com/json/' + addr + response = requests.get(request, timeout=5) + result = json.loads(response.content.decode()) + return result['country_code'] + except requests.exceptions.RequestException: + return '--' + + +if __name__ == "__main__": + args = ArgumentParser( + prog='ovpn_status', + description='OpenVPN server status parser', + epilog='Dependencies: ' + '- Python 3 (tested version 3.9.5), ' + ) + args.add_argument('-s', '--server_conf', type=str, required=True, + help='path to OpenVPN server configuration file') + args.add_argument('-f', '--filter', type=str, default='.*', required=False, + help='client names filter by regex') + args.add_argument('-g', '--geo', action='store_true', required=False, + help='check client real ip geo location (may be slow)') + args = vars(args.parse_args()) + + if path.exists(args['server_conf']): + with open(args['server_conf'], mode='r', encoding='utf-8') as conf_file: + conf_data = conf_file.read() + + st_file_conf = re.search(r'status\s+\S*', conf_data, re.MULTILINE).group(0) + st_file_path = re.sub(r'status\s+', '', st_file_conf) + json_data = status( + stats_file=st_file_path, + client_filter=args['filter'], + client_geo=args['geo'] + ) + + ca_file_conf = re.search(r'ca\s+\S*', conf_data, re.MULTILINE).group(0) + ca_file_path = re.sub(r'ca\s+', '', ca_file_conf) + json_data['ca_expiration'] = ce_exp(cert_path=ca_file_path) + + ce_file_conf = re.search(r'cert\s+\S*', conf_data, re.MULTILINE).group(0) + ce_file_path = re.sub(r'cert\s+', '', ce_file_conf) + json_data['ce_expiration'] = ce_exp(cert_path=ce_file_path) + + network_conf = re.search(r'server\s+\S*\s+\S*', conf_data, re.MULTILINE).group(0) + network_pool = re.sub(r'server\s+', '', network_conf) + network_addr = re.sub(r'\s+\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', '', network_pool) + network_mask = re.sub(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}\s+', '', network_pool) + json_data['clients_limit'] = ip_num(addr=network_addr, mask=network_mask) + + print(json.dumps(json_data, indent=2))