diff --git a/README.md b/README.md index ff6b4bf..7d090d2 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,64 @@ Wireguard management and monitoring utils. +* [`wg_status`.py](https://git.hmp.today/pavel.muhortov/wireguard-management#wg_status-py) * [`wg-client-management`.sh](https://git.hmp.today/pavel.muhortov/wireguard-management#wg-client-management-sh) -* [`wg-connect-handling`.sh](https://git.hmp.today/pavel.muhortov/wireguard-management#wg-connect-handling-sh) * [`wg-heavy@wg1`.service](https://git.hmp.today/pavel.muhortov/wireguard-management#wg-heavy-wg1-service) ____ +## `wg_status`.py + +**Description:** +> Wireguard server status parser. + +**Dependencies:** +> +> * privileged rights +> * [Python 3](https://www.python.org/downloads/) (tested version 3.9.5 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> * [requests](https://requests.readthedocs.io/) Python 3 module (tested version 2.31.0) +> * to use peer names instead of their public keys, the existence of a directory with configurations or public keys of peers is required + +| PARAMETERS | DESCRIPTION | DEFAULT | +|--------------|------------------------|---------------| +|**[-p, --peers_root]**|root path to peers configs or public keys|`/etc/wireguard/pki`| +|**[-f, --filter]**|client names filter by regex|`.*`| +|**[-g, --geo]**|check client real ip geo location (may be slow)|`None`| + +Example usage with Zabbix agent: + +```bash +# install dependencies +sudo pip install requests +# download +sudo wget https://git.hmp.today/pavel.muhortov/wireguard-management/raw/branch/master/wg_status.py -O /etc/wireguard/wg_status.py +sudo chmod +x /etc/wireguard/wg_status.py +``` + +```bash +# edit sudoers +sudo sh -c "echo ' +zabbix ALL=(ALL) NOPASSWD:/etc/wireguard/wg_status.py +' > /etc/sudoers.d/zabbix_agentd" +# check permission +sudo -u zabbix sudo /etc/wireguard/wg_status.py +``` + +```bash +# add UserParameter to Zabbix agent +sudo sh -c "echo ' +Timeout=30 +AllowRoot=0 +UserParameter=discovery.wg, sudo /etc/wireguard/wg_status.py +' >> /etc/zabbix/zabbix_agentd.conf" +sudo systemctl restart zabbix-agent +``` + +Download [Wireguard_by_Zabbix_agent.yaml](https://git.hmp.today/pavel.muhortov/wireguard-management/raw/branch/master/Wireguard_by_Zabbix_agent.yaml) template +Zabbix Server -> Configuration -> Templates -> Import template + +____ + ## `wg-client-management`.sh **Description:** diff --git a/Wireguard_by_Zabbix_agent.yaml b/Wireguard_by_Zabbix_agent.yaml new file mode 100755 index 0000000..86af2a5 --- /dev/null +++ b/Wireguard_by_Zabbix_agent.yaml @@ -0,0 +1,944 @@ +zabbix_export: + version: '6.0' + date: '2023-08-16T11:10:34Z' + 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' + type: DEPENDENT + key: ovpn.clients.limit + delay: '0' + history: 14d + units: client + preprocessing: + - + type: JSONPATH + parameters: + - $.clients_limit + master_item: + key: discovery.ovpn + tags: + - + tag: Application + value: OpenVPN + - + tag: 'OpenVPN server' + value: 'clients limit' + - + uuid: 00c5526a838e4f7791b4edafc20bb094 + name: 'OpenVPN expiration ca' + type: DEPENDENT + key: ovpn.expiration.ca + delay: '0' + history: 14d + units: s + preprocessing: + - + type: JSONPATH + parameters: + - $.ca_expiration + master_item: + key: discovery.ovpn + 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' + type: DEPENDENT + key: ovpn.expiration.cert + delay: '0' + history: 14d + units: s + preprocessing: + - + type: JSONPATH + parameters: + - $.ce_expiration + master_item: + key: discovery.ovpn + 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: 'OpenVPN 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: 'OpenVPN 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: 'OpenVPN 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: + host: 'OpenVPN by Zabbix agent' + key: ovpn.expiration.ca + - + type: ITEM + x: '6' + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + host: 'OpenVPN by Zabbix agent' + key: ovpn.expiration.cert + - + type: GRAPH_CLASSIC + 'y': '2' + width: '11' + height: '5' + hide_header: 'YES' + fields: + - + type: GRAPH + name: graphid + value: + host: 'OpenVPN by Zabbix agent' + name: 'OpenVPN certificates expiration' + - + name: Clients + widgets: + - + type: ITEM + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + host: 'OpenVPN by Zabbix agent' + key: ovpn.stats.clients_count + - + type: ITEM + x: '6' + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + host: 'OpenVPN by Zabbix agent' + key: ovpn.stats.clients_found + - + type: GRAPH_PROTOTYPE + 'y': '7' + width: '11' + height: '5' + fields: + - + type: INTEGER + name: columns + value: '1' + - + type: GRAPH_PROTOTYPE + name: graphid + value: + host: 'OpenVPN by Zabbix agent' + name: 'OpenVPN client "{#OVPN_CLIENT_NAME}" traffic' + - + type: GRAPH_CLASSIC + 'y': '2' + width: '11' + height: '5' + hide_header: 'YES' + fields: + - + type: GRAPH + name: graphid + value: + host: 'OpenVPN by Zabbix agent' + name: 'OpenVPN clients sum' + - + uuid: 3face906272745a7963b5b4e29eaaadc + template: 'Wireguard by Zabbix agent' + name: 'Wireguard by Zabbix agent' + description: 'Wireguard by Zabbix agent' + groups: + - + name: Templates/Applications + items: + - + uuid: 0c599346296b401cb7eaffa1c78978ed + name: 'Wireguard stats' + key: discovery.wg + history: 14d + trends: '0' + value_type: TEXT + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard stats' + value: raw + - + uuid: 0bbe3c2e64cc4dbd88c6c7722a3f929e + name: 'Wireguard clients count' + type: DEPENDENT + key: wg.stats.clients_count + delay: '0' + history: 14d + units: clients + preprocessing: + - + type: JSONPATH + parameters: + - $.clients_count + master_item: + key: discovery.wg + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard stats' + value: 'clients count' + - + uuid: ee3c34a9ae024592b869f27841e6006f + name: 'Wireguard clients found' + type: DEPENDENT + key: wg.stats.clients_found + delay: '0' + history: 14d + units: clients + preprocessing: + - + type: JSONPATH + parameters: + - $.clients_found + master_item: + key: discovery.wg + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard stats' + value: 'clients found' + - + uuid: 7893f7024c4d45df9ee2774006efe175 + name: 'Wireguard stats updated' + type: DEPENDENT + key: wg.stats.updated + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - $.stats_updated + master_item: + key: discovery.wg + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard stats' + value: updated + discovery_rules: + - + uuid: 8b3ade0f42f84419947638024d2b4d13 + name: 'Discovery wireguard clients' + type: DEPENDENT + key: get.wg.stats + delay: '0' + item_prototypes: + - + uuid: 5f1215ede2b14a9496ab3e3f6bd3e741 + name: 'Wireguard client "{#WG_CLIENT_NAME}" bytes recieved' + type: DEPENDENT + key: 'wg.client.b_rx.name[{#WG_CLIENT_NAME}]' + delay: '0' + history: 14d + units: B + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#WG_CLIENT_NAME}")].b_rx.first()' + master_item: + key: discovery.wg + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard client' + value: '{#WG_CLIENT_NAME}' + - + uuid: 93378d553fd34f9bb99f747024a1a32a + name: 'Wireguard client "{#WG_CLIENT_NAME}" bytes transmitted' + type: DEPENDENT + key: 'wg.client.b_tx.name[{#WG_CLIENT_NAME}]' + delay: '0' + history: 14d + units: B + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#WG_CLIENT_NAME}")].b_tx.first()' + master_item: + key: discovery.wg + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard client' + value: '{#WG_CLIENT_NAME}' + - + uuid: 9a206a744cdb4c8a86ceadc20b2602d4 + name: 'Wireguard client "{#WG_CLIENT_NAME}" real ip' + type: DEPENDENT + key: 'wg.client.r_ip.name[{#WG_CLIENT_NAME}]' + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#WG_CLIENT_NAME}")].r_ip.first()' + master_item: + key: discovery.wg + tags: + - + tag: Application + value: OpenVPN + - + tag: 'Wireguard client' + value: '{#WG_CLIENT_NAME}' + - + tag: 'Wireguard stats' + value: 'real ip' + - + uuid: 7a1b854401714952aaa86e4769dcdcfa + name: 'Wireguard client "{#WG_CLIENT_NAME}" latest handshake' + type: DEPENDENT + key: 'wg.client.t_lh.name[{#WG_CLIENT_NAME}]' + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#WG_CLIENT_NAME}")].t_lh.first()' + master_item: + key: discovery.wg + tags: + - + tag: Application + value: Wireguard + - + tag: 'Wireguard client' + value: '{#WG_CLIENT_NAME}' + - + uuid: 257ade43285748baa5e2c826028a411a + name: 'Wireguard client "{#WG_CLIENT_NAME}" virtual ip' + type: DEPENDENT + key: 'wg.client.v_ip.name[{#WG_CLIENT_NAME}]' + delay: '0' + history: 14d + trends: '0' + value_type: TEXT + preprocessing: + - + type: JSONPATH + parameters: + - '$.data.[?(@.name=="{#WG_CLIENT_NAME}")].v_ip.first()' + master_item: + key: discovery.wg + tags: + - + tag: Application + value: OpenVPN + - + tag: 'Wireguard client' + value: '{#WG_CLIENT_NAME}' + - + tag: 'Wireguard stats' + value: 'virtual ip' + graph_prototypes: + - + uuid: aa1bb73c2276432b96c7d0ee3fc4b292 + name: 'Wireguard client "{#WG_CLIENT_NAME}" traffic' + graph_items: + - + drawtype: GRADIENT_LINE + color: 00FF00 + item: + host: 'Wireguard by Zabbix agent' + key: 'wg.client.b_rx.name[{#WG_CLIENT_NAME}]' + - + sortorder: '1' + drawtype: BOLD_LINE + color: 0080FF + item: + host: 'Wireguard by Zabbix agent' + key: 'wg.client.b_tx.name[{#WG_CLIENT_NAME}]' + master_item: + key: discovery.wg + lld_macro_paths: + - + lld_macro: '{#WG_CLIENT_B_RX}' + path: $..b_rx.first() + - + lld_macro: '{#WG_CLIENT_B_TX}' + path: $..b_tx.first() + - + lld_macro: '{#WG_CLIENT_NAME}' + path: $..name.first() + - + lld_macro: '{#WG_CLIENT_R_IP}' + path: $..r_ip.first() + - + lld_macro: '{#WG_CLIENT_T_LH}' + path: $..t_lh.first() + - + lld_macro: '{#WG_CLIENT_V_IP}' + path: $..v_ip.first() + dashboards: + - + uuid: 282c5db5bd1a4e7a84b856060beafb51 + name: Wireguard + auto_start: 'NO' + pages: + - + name: Server + - + name: Clients + widgets: + - + type: ITEM + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + host: 'Wireguard by Zabbix agent' + key: wg.stats.clients_count + - + type: ITEM + x: '6' + width: '5' + hide_header: 'YES' + fields: + - + type: ITEM + name: itemid + value: + host: 'Wireguard by Zabbix agent' + key: wg.stats.clients_found + - + type: GRAPH_PROTOTYPE + 'y': '7' + width: '11' + height: '5' + fields: + - + type: INTEGER + name: columns + value: '1' + - + type: GRAPH_PROTOTYPE + name: graphid + value: + host: 'Wireguard by Zabbix agent' + name: 'Wireguard client "{#WG_CLIENT_NAME}" traffic' + - + type: GRAPH_CLASSIC + 'y': '2' + width: '11' + height: '5' + hide_header: 'YES' + fields: + - + type: GRAPH + name: graphid + value: + host: 'Wireguard by Zabbix agent' + name: 'Wireguard clients sum' + 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 + - + uuid: 644326c90dac454a94b6b98136d1efaf + name: 'Wireguard clients sum' + graph_items: + - + color: BF00FF + item: + host: 'Wireguard by Zabbix agent' + key: wg.stats.clients_count + - + sortorder: '1' + color: FFBF00 + item: + host: 'Wireguard by Zabbix agent' + key: wg.stats.clients_found diff --git a/wg-connect-handling.sh b/wg-connect-handling.sh deleted file mode 100644 index 2142b53..0000000 --- a/wg-connect-handling.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bash - -# DESCRIPTION: -# handling client connection -# and -# preparing stats for monitoring -# -# DEPENDENCIES: -# - privileged rights -# - jq -# - grepcidr -# - Python 3 -# - existing /usr/local/bin/sendmail.py -# -# PARAMETERS: -# 1: root path for counter, names, log -# 2: "mail" - send email notification -# 3: "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 -} - -####################################### -# 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 -} - -####################################### -# Get information about client address -# Globals: -# flaggeol -# show_from_addr -# conf_client_nm -# Arguments: -# None -####################################### -# shellcheck disable=SC2154 -expandaddress() { - ipinfo="Source address is ${show_from_addr}" - 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 "${show_from_addr}") >/dev/null; then - if [ "${flaggeol}" == "geo" ]; then - ipinfo=$(curl "https://api.ipbase.com/v1/json/${show_from_addr}") - 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 ${show_from_addr} is from ${z}, ${c}, ${r}, ${t}" - fi - fi - fi - addtologs "client ${conf_client_nm} checked. ${ipinfo}" -} - -####################################### -# Send email notification about client connect -# Globals: -# ipinfo -# conf_client_nm -# conf_ipaddress -# Arguments: -# None -####################################### -startsendmail() { - subj="[VPN Connected] $(cat /etc/hostname): ${conf_client_nm} connect to ${conf_ipaddress}" - ( - 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: -# - -pathroot=$1 -flagmail=$2 -flaggeol=$3 - -time=$(date +%s) -logs="${pathroot}/$(basename -s .sh "$0").log" -counts_file="${pathroot}/wg-counts.log" -counts_temp=$(cat "${counts_file}") -if [ -z "${pathroot}" ]; then - logs=/dev/null - execerror "Usage example: $0 '/var/log/wireguard' '-' '-'" -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 checkroot; then - allowed_cfg=$(find /etc/wireguard/ -name "*.conf" | grep -v "wg0.conf") - counter_now=0 - clients_now="" - while read -r file; do - conf_keepalive=$(grep "PersistentKeepalive" "${file}" |cut -d"=" -f2 |tr -d " ") - conf_ipaddress=$(grep "Address" "${file}" |cut -d"=" -f2 |cut -d"/" -f1 |tr -d " ") - conf_client_nm=$(basename -s .conf "${file}") - show_handshake=$(wg show all dump |grep "${conf_ipaddress}" |cut -f6) - show_from_addr=$(wg show all dump |grep "${conf_ipaddress}" |cut -f4 |cut -d":" -f1) - if [ "${show_handshake}" -ne 0 ]; then - calc_handshake="$(date -d "-${conf_keepalive} min" +"%s")" - if [ "${show_handshake}" -ge "${calc_handshake}" ]; then - (( counter_now ++)) - connect_status="connected" - if ping -q -c 1 -W 1 "${conf_ipaddress}" > /dev/null; then - connect_status="connected, ping responded" - else - connect_status="connected, ping loss" - fi - clients_now+=$(printf "%s\n\r" "${conf_client_nm}_${conf_ipaddress}") - if ! grep -q "${conf_client_nm}_${conf_ipaddress}" <<< "${counts_temp}"; then - addtologs "client ${conf_client_nm} ${connect_status}" - expandaddress - if [ "${flagmail}" == "mail" ]; then - startsendmail - fi - fi - else - connect_status="disconnected" - if grep -q "${conf_client_nm}_${conf_ipaddress}" <<< "${counts_temp}"; then - addtologs "client ${conf_client_nm} ${connect_status}" - fi - fi - else - connect_status="never connected" - fi - done <<< "$allowed_cfg" - printf "%s\n" "total=${counter_now}=" > "${counts_file}" - printf "%s\n" "${clients_now}" >> "${counts_file}" -else - execerror "Restart this as root!" -fi -execquite diff --git a/wg_status.py b/wg_status.py new file mode 100755 index 0000000..75e1265 --- /dev/null +++ b/wg_status.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# pylint: disable=W0612 + +"""It's the Wireguard server status parser. +""" + + +import json +import re +import time +from argparse import ArgumentParser +from os import path, walk +from subprocess import Popen, PIPE, STDOUT +import requests + + +def status(configs_root: str, client_filter: str = '.*', client_geo: bool = False) -> dict: + """Wireguard server's peers status parser. + + Args: + configs_root (str): root path to peers configs. + 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, + "r_cc": str, + "v_ip": str, + "b_rx": int, + "b_tx": int, + "t_lh": timestamp, + }, + ] + } + """ + + clients_dump = wg_dmp() + clients_array = [] + clients_count = len(clients_dump) + clients_found = 0 + stats_updated = time.strftime("%Y-%m-%d %H:%M:%S") + + if clients_count > 0: + configs_dump = [] + for root, dirs, files in walk(configs_root, topdown=False): + for file_name in files: + file_path = path.join(path.realpath(root), file_name) + with open(file_path, mode='r', encoding='utf-8') as file: + try: + file_data = file.read() + configs_dump.append( + { + 'file_name': file_name, + 'file_path': file_path, + 'file_data': file_data + } + ) + except UnicodeDecodeError: + pass + + for client in clients_dump: + client_name = client['name'] + for config in configs_dump: + if client_name in config['file_data'] and not client['w_if'] in config['file_name']: + client_name = config['file_name'].replace('.key', '').replace('.conf', '') + break + + if re.findall(client_filter, client_name) or re.findall(client_filter, client['name']): + reject_after_time = 180 + client_dl = int(time.mktime(time.strptime(stats_updated, "%Y-%m-%d %H:%M:%S"))) + + if client['p_lh'] + reject_after_time >= client_dl: + client_r_ip = client['r_ip'] + client_r_cc = '--' + if client_geo and re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', client_r_ip): + client_r_cc = ip_geo(addr=client_r_ip) + + client_t_lh = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(client['p_lh'])) + clients_array.append( + { + 'name': client_name, + 'r_ip': client_r_ip, + 'r_cc': client_r_cc, + 'v_ip': client['v_ip'], + 'b_rx': client['b_rx'], + 'b_tx': client['b_tx'], + 't_lh': client_t_lh + } + ) + clients_found += 1 + + clients_stats = { + 'stats_updated': stats_updated, + 'clients_count': clients_count, + 'clients_found': clients_found, + 'data': clients_array, + } + return clients_stats + + +def wg_dmp() -> list: + """Parse 'wg show all dump' result. + + Returns: + list: [ + { + "name": str, + "r_ip": str, + "v_ip": str, + "b_rx": int, + "b_tx": int, + "p_lh": int, + "i_ka": int, + "w_if": str + }, + ] + """ + process = ['wg', 'show', 'all', 'dump'] + with Popen(process, stdout=PIPE, stderr=STDOUT) as proc: + dlm = '\t' + dump_text = proc.stdout.read().decode('utf-8') + peer_list = [] + for dump_line in dump_text.splitlines(): + line_list = dump_line.split(dlm) + w_if = line_list[0] + if len(line_list) == 5: + pass + if len(line_list) == 9: + peer_list.append( + { + "name": line_list[1], + "r_ip": line_list[3].split(':')[0], + "v_ip": line_list[4], + "b_rx": int(line_list[6]), + "b_tx": int(line_list[7]), + "p_lh": int(line_list[5]), + "i_ka": int(line_list[8]), + "w_if": w_if + } + ) + return peer_list + + +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='wg_status', + description='Wireguard server status parser', + epilog='Dependencies: ' + '- Python 3 (tested version 3.9.5), ' + '- Python 3 modules: requests ' + ) + args.add_argument('-p', '--peers_root', type=str, default='/etc/wireguard/pki', required=False, + help='root path to peers configs or public keys') + 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()) + + json_data = status( + configs_root=args['peers_root'], + client_filter=args['filter'], + client_geo=args['geo'] + ) + + print(json.dumps(json_data, indent=2))