Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 72 additions & 6 deletions packages/ns-api/files/ns.ovpntunnel
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -294,13 +317,20 @@ 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", ""),
"topology": topology,
"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')
Expand Down Expand Up @@ -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()
Expand All @@ -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({
Expand All @@ -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]
Expand Down Expand Up @@ -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))
3 changes: 2 additions & 1 deletion packages/ns-openvpn/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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/
Expand Down
16 changes: 11 additions & 5 deletions packages/ns-openvpn/files/ns-openvpnrw-init-pki
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion packages/ns-openvpn/files/ns-openvpntunnel-add-client
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 28 additions & 0 deletions packages/ns-openvpn/files/ns-openvpntunnel-regenerate-certs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh

#
# Copyright (C) 2026 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
EASYRSA_REQ_CN=$cn

cd /etc/openvpn/$instance

# regenerate server and client certificates
/usr/bin/easyrsa build-server-full server nopass &>/dev/null
/usr/bin/easyrsa build-client-full client nopass &>/dev/null
Loading