Skip to content

Commit 560543e

Browse files
committed
feat(ns-openvpn): log VPN auth events
Add structured syslog audit lines to the local and remote OpenVPN authentication helpers plus the connection and disconnection hooks. These events provide stable input for victoria-logs queries so the audit report can identify OpenVPN authentication results and VPN session lifecycle activity without scraping fragile free-form log messages. Assisted-by: Copilot:gpt-5.4
1 parent c446f3d commit 560543e

4 files changed

Lines changed: 52 additions & 2 deletions

File tree

packages/ns-openvpn/files/80-save-connection

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import re
99
import os
1010
import sys
1111
import sqlite3
12+
import syslog
1213
from euci import EUci
1314
from nethsec import users
1415

@@ -37,6 +38,8 @@ instance = re.sub(r'^openvpn-|\.conf$', '', config_path)
3738
virtual_ip_addr = os.environ.get("ifconfig_pool_remote_ip")
3839
uci = EUci()
3940

41+
syslog.openlog("openvpn-connect", syslog.LOG_PID, syslog.LOG_AUTHPRIV)
42+
4043
# The OpenVPN server sets the virtual IP address of the client in the environment variable ifconfig_pool_remote_ip,
4144
# still this value is not reliable in case of an IP reservation.
4245
try:
@@ -63,6 +66,10 @@ try:
6366
start_time = int(os.environ.get('time_unix'))
6467

6568
c.execute("INSERT INTO connections (common_name, virtual_ip_addr, remote_ip_addr, start_time) VALUES (?, ?, ?, ?)", (common_name, virtual_ip_addr, remote_ip_addr, start_time))
69+
syslog.syslog(
70+
syslog.LOG_INFO,
71+
f"event=connect instance={instance} user={common_name} remote_ip={remote_ip_addr} virtual_ip={virtual_ip_addr} start_time={start_time}"
72+
)
6673

6774
conn.commit()
6875
conn.close()

packages/ns-openvpn/files/80-save-disconnection

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import sys
1010
import sqlite3
11+
import syslog
1112
from euci import EUci
1213
from nethsec import users
1314

@@ -20,6 +21,9 @@ def get_db_path(u, instance):
2021
uci = EUci()
2122
conn = sqlite3.connect(get_db_path(uci, sys.argv[1]))
2223
c = conn.cursor()
24+
instance = sys.argv[1]
25+
26+
syslog.openlog("openvpn-disconnect", syslog.LOG_PID, syslog.LOG_AUTHPRIV)
2327

2428
env = os.environ
2529
common_name = env.get('common_name')
@@ -29,6 +33,8 @@ start_time = int(env.get('time_unix', '0'))
2933
duration = int(env.get('time_duration', '0'))
3034
bytes_received = int(env.get('bytes_received', '0'))
3135
bytes_sent = int(env.get('bytes_sent', '0'))
36+
virtual_ip_addr = env.get('ifconfig_pool_remote_ip', '')
37+
remote_ip_addr = env.get('untrusted_ip', '')
3238

3339
# Update connection data
3440
c.execute("UPDATE connections SET duration=?, bytes_received=?, bytes_sent=? WHERE common_name=? and start_time=?", (duration, bytes_received, bytes_sent, common_name, start_time))
@@ -47,8 +53,6 @@ if c.rowcount == 0:
4753
pass
4854

4955
if enabled:
50-
virtual_ip_addr = env.get('ifconfig_pool_remote_ip', '')
51-
remote_ip_addr = env.get('untrusted_ip', '')
5256
c.execute(
5357
"INSERT INTO connections (common_name, virtual_ip_addr, remote_ip_addr, start_time, duration, bytes_received, bytes_sent) "
5458
"VALUES (?, ?, ?, ?, ?, ?, ?)",
@@ -57,4 +61,8 @@ if c.rowcount == 0:
5761

5862
conn.commit()
5963
conn.close()
64+
syslog.syslog(
65+
syslog.LOG_INFO,
66+
f"event=disconnect instance={instance} user={common_name} remote_ip={remote_ip_addr} virtual_ip={virtual_ip_addr} start_time={start_time} duration={duration} bytes_received={bytes_received} bytes_sent={bytes_sent}"
67+
)
6068
sys.exit(0)

packages/ns-openvpn/files/openvpn-local-auth

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import os
1414
import re
15+
import syslog
1516
import sys
1617
from euci import EUci
1718
from nethsec import users
@@ -21,29 +22,46 @@ password = os.environ.get("password")
2122
config_path = os.environ.get("config")
2223

2324
instance = re.sub(r'^openvpn-|\.conf$', '', config_path)
25+
remote_ip = os.environ.get("untrusted_ip", "unknown")
26+
27+
syslog.openlog("openvpn-auth", syslog.LOG_PID, syslog.LOG_AUTHPRIV)
28+
29+
30+
def audit(outcome, reason=None):
31+
message = f"event=auth outcome={outcome} instance={instance} user={username} remote_ip={remote_ip}"
32+
if reason:
33+
message = f"{message} reason={reason}"
34+
priority = syslog.LOG_INFO if outcome == "success" else syslog.LOG_WARNING
35+
syslog.syslog(priority, message)
2436

2537
uci = EUci()
2638
try:
2739
db = uci.get("openvpn", instance, "ns_user_db")
2840
except:
2941
# user db not set
42+
audit("failure", "user_db_not_set")
3043
sys.exit(5)
3144

3245
user = users.get_user_by_name(uci, username, db)
3346
if user is None:
3447
# user not found
48+
audit("failure", "user_not_found")
3549
sys.exit(4)
3650

3751
if not "openvpn_enabled" in user or user["openvpn_enabled"] != "1":
3852
# user not enabled
53+
audit("failure", "user_disabled")
3954
sys.exit(3)
4055

4156
if "password" not in user:
4257
# password not set
58+
audit("failure", "password_not_set")
4359
sys.exit(2)
4460

4561
if users.check_password(password, user["password"]):
62+
audit("success")
4663
sys.exit(0)
4764
else:
4865
# password do not match
66+
audit("failure", "password_mismatch")
4967
sys.exit(1)

packages/ns-openvpn/files/openvpn-remote-auth

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import re
1414
import os
1515
import sys
16+
import syslog
1617
import subprocess
1718
from euci import EUci
1819
from nethsec import users
@@ -21,11 +22,21 @@ def debug(msg):
2122
if os.environ.get("debug", False):
2223
print(f'[DEBUG] {msg}', file=sys.stderr)
2324

25+
26+
def audit(outcome, reason=None):
27+
message = f"event=auth outcome={outcome} instance={instance} user={username} remote_ip={remote_ip}"
28+
if reason:
29+
message = f"{message} reason={reason}"
30+
priority = syslog.LOG_INFO if outcome == "success" else syslog.LOG_WARNING
31+
syslog.syslog(priority, message)
32+
2433
username = os.environ.get("username")
2534
password = os.environ.get("password")
2635
config_path = os.environ.get("config")
36+
remote_ip = os.environ.get("untrusted_ip", "unknown")
2737

2838
instance = re.sub(r'^openvpn-|\.conf$', '', config_path)
39+
syslog.openlog("openvpn-auth", syslog.LOG_PID, syslog.LOG_AUTHPRIV)
2940
debug(f"Instance: {instance}")
3041

3142
ldap = {}
@@ -36,6 +47,7 @@ try:
3647
except:
3748
# user db not set or not found
3849
debug("Error: user db not found")
50+
audit("failure", "user_db_not_found")
3951
sys.exit(5)
4052

4153
debug(f"Username: {username}")
@@ -45,11 +57,13 @@ debug(f"User: {user}")
4557
if user is None:
4658
# user not found
4759
debug(f"Error: user '{user}' not found")
60+
audit("failure", "user_not_found")
4861
sys.exit(4)
4962

5063
if not "openvpn_enabled" in user or user["openvpn_enabled"] != "1":
5164
# user not enabled
5265
debug(f"Error: user '{user}' disabled")
66+
audit("failure", "user_disabled")
5367
sys.exit(3)
5468

5569
uri = ldap.get("uri", "")
@@ -64,6 +78,7 @@ user_bind_dn = ldap.get("user_bind_dn")
6478
if not uri or not base_dn:
6579
# no info to connect ldap db
6680
debug(f"Error: invalid LDAP URI or base DN")
81+
audit("failure", "invalid_ldap_configuration")
6782
sys.exit(2)
6883

6984
if user_bind_dn:
@@ -94,7 +109,9 @@ debug(f"Command: LDAPTLS_REQCERT={tls_reqcert} " + " ".join(ldapsearch_command))
94109
try:
95110
subprocess.run(ldapsearch_command, env=env, check=True, capture_output=True)
96111
debug("Success")
112+
audit("success")
97113
sys.exit(0)
98114
except Exception as e:
99115
print(e, file=sys.stderr)
116+
audit("failure", "ldap_authentication_failed")
100117
sys.exit(1)

0 commit comments

Comments
 (0)