From fd3f2d3814bb723a312de3f5423dcd3c4adbea55 Mon Sep 17 00:00:00 2001 From: Matteo Di Lorenzi Date: Mon, 26 Jan 2026 13:31:18 +0100 Subject: [PATCH 1/3] feat(openvpn-tunnel): update certs duration and add certs regeneration functionality --- packages/ns-api/files/ns.ovpntunnel | 78 +++++++++++++++++-- packages/ns-openvpn/Makefile | 1 + .../ns-openvpn/files/ns-openvpnrw-init-pki | 16 ++-- .../files/ns-openvpntunnel-add-client | 6 +- .../files/ns-openvpntunnel-regenerate-certs | 27 +++++++ 5 files changed, 116 insertions(+), 12 deletions(-) create mode 100755 packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs diff --git a/packages/ns-api/files/ns.ovpntunnel b/packages/ns-api/files/ns.ovpntunnel index 116e60583..0a07eb549 100755 --- a/packages/ns-api/files/ns.ovpntunnel +++ b/packages/ns-api/files/ns.ovpntunnel @@ -13,14 +13,13 @@ import pwd import grp import json import os.path -import socket -import struct -import random import shutil import ipaddress import subprocess from euci import EUci from nethsec import utils, firewall, ovpn +from datetime import datetime +import time # Utils @@ -57,6 +56,30 @@ def setup_firewall(u, name, device, link): else: firewall.add_device_to_zone(u, device, 'openvpn') +def remove_files(file_list): + for file_path in file_list: + try: + os.remove(file_path) + except FileNotFoundError: + pass + except OSError: + pass + +def get_cert_expiry_ts(cert_path): + try: + result = subprocess.run( + ["openssl", "x509", "-noout", "-enddate", "-in", cert_path], + capture_output=True, + text=True, + check=True + ) + # parse output data like "notAfter=Dec 20 23:59:59 2033 GMT" + date_str = result.stdout.split("=")[1].strip() + # convert to Unix timestamp + return int(time.mktime(datetime.strptime(date_str, "%b %d %H:%M:%S %Y %Z").timetuple())) + except: + return None + # APIs def import_client(tunnel): @@ -294,6 +317,11 @@ def list_tunnels(): if 'ns_auth_mode' in vpn: continue topology = vpn.get("topology", "subnet") + + # get certificate expiry timestamp + if vpn.get('cert'): + cert_expiry_timestamp = get_cert_expiry_ts(vpn.get('cert')) + record = { "id": section, "ns_name": vpn.get("ns_name", ""), @@ -301,6 +329,8 @@ def list_tunnels(): "enabled": vpn.get("enabled", "0") == "1", "connected": False } + if cert_expiry_timestamp: + record["cert_expiry_ts"] = cert_expiry_timestamp if vpn.get("client", "0") == "1" or vpn.get("ns_client", "0") == "1": connected = ovpn.list_connected_clients(section, 'p2p') @@ -592,7 +622,6 @@ def setup_client(u, iname, args): olink = f"openvpn/{iname}" setup_firewall(u, args['ns_name'], dev_name, olink) - def edit_client(args): u = EUci() @@ -613,7 +642,41 @@ def add_client(args): setup_client(u, iname, args) return {"id": iname} - +def regenerate_tunnel_certs(name): + u = EUci() + try: + vpn = u.get_all("openvpn", name) + except: + return utils.generic_error("tunnel_not_found") + + cert_dir = f"/etc/openvpn/{name}/pki/" + + # only for subnet topology (certificate-based) + if vpn.get("topology", "subnet") != "subnet": + return utils.generic_error("certs_not_supported_p2p") + + try: + # remove old certificates and request files + files_to_remove = [ + f"{cert_dir}issued/server.crt", + f"{cert_dir}private/server.key", + f"{cert_dir}reqs/server.req", + f"{cert_dir}issued/client.crt", + f"{cert_dir}private/client.key", + f"{cert_dir}reqs/client.req" + ] + remove_files(files_to_remove) + + # regenerate certificates + subprocess.run(["/usr/sbin/ns-openvpntunnel-regenerate-certs", name], check=True) + + # restart openvpn service for this tunnel to load new certificates + subprocess.run(["/etc/init.d/openvpn", "restart", name], check=False) + + return {"result": "success"} + except: + utils.generic_error("certs_regeneration_failed") + cmd = sys.argv[1] if cmd == 'list': print(json.dumps({ @@ -632,6 +695,7 @@ if cmd == 'list': "delete-tunnel": {"id": "ns_tun1"}, "get-tunnel-server": {"id": "ns_tun1"}, "get-tunnel-client": {"id": "ns_tun1"}, + "regenerate-server-certs": {"id": "ns_tun1"}, })) elif cmd == 'call': action = sys.argv[2] @@ -669,4 +733,6 @@ elif cmd == 'call': ret = get_tunnel_server(args['id']) elif action == 'get-tunnel-client': ret = get_tunnel_client(args['id']) - print(json.dumps(ret)) + elif action == 'regenerate-server-certs': + ret = regenerate_tunnel_certs(args['id']) + print(json.dumps(ret)) \ No newline at end of file diff --git a/packages/ns-openvpn/Makefile b/packages/ns-openvpn/Makefile index 417fa846e..13a5fb3fc 100644 --- a/packages/ns-openvpn/Makefile +++ b/packages/ns-openvpn/Makefile @@ -43,6 +43,7 @@ define Package/ns-openvpn/install $(INSTALL_BIN) ./files/ns-openvpnrw-init-pki $(1)/usr/sbin $(INSTALL_BIN) ./files/ns-openvpnrw-extend-crl $(1)/usr/sbin $(INSTALL_BIN) ./files/ns-openvpntunnel-add-client $(1)/usr/sbin + $(INSTALL_BIN) ./files/ns-openvpntunnel-regenerate-certs $(1)/usr/sbin $(INSTALL_BIN) ./files/openvpn-connect $(1)/usr/libexec/ns-openvpn/ $(INSTALL_BIN) ./files/openvpn-disconnect $(1)/usr/libexec/ns-openvpn/ $(INSTALL_BIN) ./files/init-connections-db $(1)/usr/libexec/ns-openvpn/ diff --git a/packages/ns-openvpn/files/ns-openvpnrw-init-pki b/packages/ns-openvpn/files/ns-openvpnrw-init-pki index 11622ec0f..bb150ea17 100755 --- a/packages/ns-openvpn/files/ns-openvpnrw-init-pki +++ b/packages/ns-openvpn/files/ns-openvpnrw-init-pki @@ -16,14 +16,20 @@ if [ -z "$cn" ]; then cn=NethSec fi +# Set environment variables for EasyRSA +export EASYRSA_BATCH=1 +export EASYRSA_CERT_EXPIRE=3650 +export EASYRSA_CRL_DAYS=3650 +export EASYRSA_REQ_CN=$cn + mkdir -p /etc/openvpn/$instance if [ ! -f /etc/openvpn/$instance/pki/ca.crt ]; then cd /etc/openvpn/$instance ( - EASYRSA_BATCH=1 /usr/bin/easyrsa init-pki - EASYRSA_BATCH=1 EASYRSA_REQ_CN=$cn /usr/bin/easyrsa build-ca nopass + /usr/bin/easyrsa init-pki + /usr/bin/easyrsa build-ca nopass openssl dhparam -dsaparam -out pki/dh.pem 2048 - EASYRSA_BATCH=1 EASYRSA_REQ_CN=$cn /usr/bin/easyrsa build-server-full server nopass - EASYRSA_BATCH=1 EASYRSA_CRL_DAYS=3650 EASYRSA_REQ_CN=$cn /usr/bin/easyrsa gen-crl + /usr/bin/easyrsa build-server-full server nopass + /usr/bin/easyrsa gen-crl ) &> /dev/null -fi +fi \ No newline at end of file diff --git a/packages/ns-openvpn/files/ns-openvpntunnel-add-client b/packages/ns-openvpn/files/ns-openvpntunnel-add-client index 88281368a..00563b57c 100755 --- a/packages/ns-openvpn/files/ns-openvpntunnel-add-client +++ b/packages/ns-openvpn/files/ns-openvpntunnel-add-client @@ -11,5 +11,9 @@ if [ -z "$instance" ] ; then exit 1 fi +# Set environment variables for EasyRSA +export EASYRSA_BATCH=1 +export EASYRSA_CERT_EXPIRE=3650 + cd /etc/openvpn/$instance -EASYRSA_BATCH=1 /usr/bin/easyrsa build-client-full "client" nopass &>/dev/null +/usr/bin/easyrsa build-client-full client nopass &>/dev/null \ No newline at end of file diff --git a/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs b/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs new file mode 100755 index 000000000..cb769833d --- /dev/null +++ b/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs @@ -0,0 +1,27 @@ +#!/bin/sh + +# +# Copyright (C) 2022 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +# Regenerate server and client certificates for existing tunnel +instance=$1 +if [ -z "$instance" ]; then + exit 1 +fi + +cn=$(uci get system.@system[0].hostname | cut -d '.' -f 1) +if [ -z "$cn" ]; then + cn=NethSec +fi + +# Set environment variables for EasyRSA +export EASYRSA_BATCH=1 +export EASYRSA_CERT_EXPIRE=3650 + +cd /etc/openvpn/$instance + +# regenerate server and client certificates +EASYRSA_REQ_CN=$cn /usr/bin/easyrsa build-server-full server nopass &>/dev/null +/usr/bin/easyrsa build-client-full client nopass &>/dev/null \ No newline at end of file From fc9ec25d7c7bfe3830ccfb111eabf20a689a3c6d Mon Sep 17 00:00:00 2001 From: Matteo Di Lorenzi Date: Mon, 26 Jan 2026 14:11:29 +0100 Subject: [PATCH 2/3] fix(openvpn-tunnel): update copyright year and correct EasyRSA command syntax --- packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs b/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs index cb769833d..79ddf0280 100755 --- a/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs +++ b/packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (C) 2022 Nethesis S.r.l. +# Copyright (C) 2026 Nethesis S.r.l. # SPDX-License-Identifier: GPL-2.0-only # @@ -19,9 +19,10 @@ fi # Set environment variables for EasyRSA export EASYRSA_BATCH=1 export EASYRSA_CERT_EXPIRE=3650 +EASYRSA_REQ_CN=$cn cd /etc/openvpn/$instance # regenerate server and client certificates -EASYRSA_REQ_CN=$cn /usr/bin/easyrsa build-server-full server nopass &>/dev/null +/usr/bin/easyrsa build-server-full server nopass &>/dev/null /usr/bin/easyrsa build-client-full client nopass &>/dev/null \ No newline at end of file From 57b1206864d42ad1834921c24ba0f4dba3b30a14 Mon Sep 17 00:00:00 2001 From: Matteo Di Lorenzi Date: Tue, 27 Jan 2026 15:09:59 +0100 Subject: [PATCH 3/3] fix(ns-openvpn): bump package version to 1.0.1 --- packages/ns-openvpn/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-openvpn/Makefile b/packages/ns-openvpn/Makefile index 13a5fb3fc..14a0fb422 100644 --- a/packages/ns-openvpn/Makefile +++ b/packages/ns-openvpn/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-openvpn -PKG_VERSION:=1.0.0 +PKG_VERSION:=1.0.1 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-openvpn-$(PKG_VERSION)