ConnLogs were never populated because AppendConnLog was never called.
Added StartStatusSync() goroutine that runs every 10 seconds:
1. Iterates all running instances
2. Reads status.log via ParseStatus (status-version 3)
3. For each CLIENT_LIST entry:
- If no active ConnLog exists → AppendConnLog (new connection)
- If active ConnLog exists → UpdateActiveConnStats (traffic)
4. For active ConnLogs with no matching CLIENT_LIST → CloseActiveConn
Store additions:
ListActiveConns(instanceID) — returns all unclosed connections
UpdateActiveConnStats() — updates bytes in/out without flush
StartStatusSync is called from main.go right after Service creation.
UpdateActiveConnStats deliberately skips flush() to avoid writing
db.json every 10 seconds; stats are persisted on disconnect.
OpenVPN 2.7 Windows DCO passes username but NOT password as
environment variable with via-env mode. Switched to via-file:
server.conf: auth-user-pass-verify script via-file
verify.sh reads credentials from temp file ():
line 1 = username, line 2 = password
Also added pass_len to debug log so we can immediately see
if the password was actually received by the script.
The verify.sh had 'set -e' which could cause premature exit in
edge cases. Replaced with explicit logging to /tmp/openvpn-verify.log
so we can diagnose AUTH_FAILED on production servers.
Log shows: timestamp, username, HTTP code, response body, ALLOW/DENY.
Also redirected curl stderr to log file instead of /dev/null so
connection errors are visible for debugging.
OpenVPN now requires BOTH a valid client certificate AND a
username/password to establish a VPN connection.
Architecture:
Client .ovpn has 'auth-user-pass' → prompts for credentials
Server.conf has 'auth-user-pass-verify verify.sh via-env'
verify.sh (bash+curl) calls POST /api/vpn/verify on the manager
Manager verifies bcrypt hash via Go's golang.org/x/crypto/bcrypt
Endpoint is localhost-only (127.0.0.1) for security
Model changes:
Instance: new AuthMode field ('cert' | 'cert+password', default cert+password)
VPNUser: new PasswordHash (bcrypt) + Password (plaintext, transient)
Backend:
model: AuthMode type, VPNUser.PasswordHash, VPNUser.Password (transient)
store: ListUsers clears PasswordHash before returning
service: CreateUser hashes password with bcrypt, enforces min 4 chars
service: ResetVPNPassword for admin password reset
service: UpdateUser preserves PasswordHash from old record
service: CreateInstance defaults AuthMode=cert+password
api: POST /api/vpn/verify (no JWT, localhost-only, bcrypt verify)
api: POST /instances/:id/users/:uid/password (admin reset VPN pwd)
openvpn: WriteVerifyScript generates bash+curl verify script
openvpn: WriteServerConf adds script-security/auth-user-pass-verify
openvpn: GenerateClientOVPN adds auth-user-pass directive
Frontend:
Users.vue: password field on create form
Users.vue: '重置密码' button in table + dialog
Users.vue: '证书+密码' tag in auth column
api: Inst.resetVPNPassword() method
Security:
/api/vpn/verify rejects non-127.0.0.1 clients (403)
PasswordHash never exposed via any API response
verify.sh uses localhost curl (no external dependencies)
bcrypt cost=10 (same as admin passwords)
Verified: correct pwd → 200, wrong pwd → 401, missing user → 401,
non-localhost → 403, .ovpn has auth-user-pass, server.conf has
script-security 2 + auth-user-pass-verify + verify-client-cert require.
Four bugs prevented OpenVPN from actually listening on its UDP port:
1. server.conf line 'server 10.8.0.0/24' — OpenVPN 2.5 rejects CIDR.
Fix: cidrToServerDirective() converts CIDR to 'NETWORK NETMASK'
(e.g. '10.8.0.0 255.255.255.0') using net.ParseCIDR.
2. push_dns '1.1.1.1 8.8.8.8' was emitted as a single push directive
instead of multiple 'push dhcp-option DNS x' lines.
Fix: split space-separated IPs into individual push directives;
pass through if already 'dhcp-option ...' form.
3. client-config-dir referenced a ccd/ directory that was never created.
Fix: CreateInstance now MkdirAll(ccd); WriteServerConf also
defensively MkdirAll(extraDir) before writing the directive.
4. dh.pem generated with 1024-bit DH params → OpenSSL 3.0 refuses
with 'dh key too small'. Fix: use 'dh none' in server.conf so
OpenVPN 2.4+ uses ECDHE key exchange — no DH params needed at all.
Removed the slow openssl dhparam generation from EnsureCA.
Verified: instance starts, OpenVPN parses config successfully,
now fails only at TUNSETIFF (expected: requires root/CAP_NET_ADMIN).