11#!/usr/bin/python3
22
33#
4- # Copyright (C) 2023 Nethesis S.r.l.
4+ # Copyright (C) 2026 Nethesis S.r.l.
55# SPDX-License-Identifier: GPL-2.0-only
66#
77
@@ -191,6 +191,30 @@ def slurp(path):
191191 with open (path ) as fp :
192192 return fp .read ()
193193
194+ def get_certificate_expiration (pem_path , label ):
195+ """Extract certificate expiration date from PEM file"""
196+ try :
197+ result = subprocess .run (
198+ ["openssl" , "x509" , "-noout" , "-subject" , "-nameopt" , "RFC2253" , "-enddate" ],
199+ stdin = open (pem_path , 'r' ),
200+ capture_output = True ,
201+ text = True ,
202+ check = True
203+ )
204+
205+ # Parse notAfter line: notAfter=Feb 21 13:04:51 2036 GMT
206+ for line in result .stdout .split ('\n ' ):
207+ if line .startswith ("notAfter=" ):
208+ date_str = line .split ("=" )[1 ].strip ()
209+ try :
210+ expiry_dt = datetime .strptime (date_str , "%b %d %H:%M:%S %Y %Z" )
211+ return {label : int (expiry_dt .timestamp ())}
212+ except :
213+ pass
214+ return {}
215+ except :
216+ return {}
217+
194218def instance2number (input_string ):
195219 numbers = re .findall (r'\d+' , input_string )
196220 return int (numbers [0 ]) if numbers else None
@@ -397,6 +421,19 @@ def get_configuration(ovpninstance):
397421 if 'ns_public_ip' not in ret :
398422 ret ['ns_public_ip' ] = ovpn .get_public_addresses (u )
399423
424+ # add certificate expiration dates
425+ certificates = {}
426+ ca_path = f'/etc/openvpn/{ ovpninstance } /pki/ca.crt'
427+ cert_path = f'/etc/openvpn/{ ovpninstance } /pki/issued/server.crt'
428+
429+ if os .path .exists (ca_path ):
430+ certificates .update (get_certificate_expiration (ca_path , 'CA' ))
431+ if os .path .exists (cert_path ):
432+ certificates .update (get_certificate_expiration (cert_path , 'server' ))
433+
434+ if certificates :
435+ ret ['certificates' ] = certificates
436+
400437 return ret
401438
402439def set_configuration (args ):
@@ -547,6 +584,7 @@ def list_users(ovpninstance):
547584 u = EUci ()
548585 ret = []
549586 connected = ovpn .list_connected_clients (ovpninstance )
587+ connected_lower_case = {k .lower (): v for k , v in connected .items ()}
550588 expirations = list_user_expirations (ovpninstance )
551589 db = u .get ("openvpn" , ovpninstance , "ns_user_db" , default = None )
552590 if not db :
@@ -561,18 +599,18 @@ def list_users(ovpninstance):
561599 # migrated users can have the form <user>@>domain>
562600 # make sure to duplicate connected info also removing the domain part
563601 normalized = {}
564- for user in connected :
602+ for user in connected_lower_case :
565603 if '@' in user :
566604 nuser = user .split ('@' )[0 ]
567- normalized [nuser ] = connected [user ]
568- connected .update (normalized )
605+ normalized [nuser ] = connected_lower_case [user ]
606+ connected_lower_case .update (normalized )
569607 for user in db_users :
570608 # exclude users not enabled for OpenVPN
571609 if "openvpn_enabled" not in user :
572610 continue
573- if user ['name' ] in connected :
611+ if user ['name' ]. lower () in connected_lower_case :
574612 user ["connected" ] = True
575- user = user | connected [user ['name' ]]
613+ user = user | connected_lower_case [user ['name' ]. lower () ]
576614 else :
577615 user ["connected" ] = False
578616 user ["expiration" ] = ""
@@ -1041,6 +1079,24 @@ def connection_history(ovpninstance):
10411079
10421080 return connections
10431081
1082+ def renew_server_certificate (ovpninstance ):
1083+ try :
1084+ subprocess .run (["/usr/sbin/ns-openvpn-renew-server" , ovpninstance ], check = True , capture_output = True )
1085+ subprocess .run (["/etc/init.d/openvpn" , "restart" , ovpninstance ], check = False , capture_output = True )
1086+ except Exception as e :
1087+ print (e , file = sys .stderr )
1088+ return utils .validation_error ("instance" , "server_certificate_renewal_failed" , ovpninstance )
1089+ return {"result" : "success" }
1090+
1091+ def regenerate_all_certificates (ovpninstance ):
1092+ try :
1093+ subprocess .run (["/usr/sbin/ns-openvpn-renew-ca" , ovpninstance ], check = True , capture_output = True )
1094+ subprocess .run (["/etc/init.d/openvpn" , "restart" , ovpninstance ], check = False , capture_output = True )
1095+ except Exception as e :
1096+ print (e , file = sys .stderr )
1097+ return utils .validation_error ("instance" , "certificates_regeneration_failed" , ovpninstance )
1098+ return {"result" : "success" }
1099+
10441100cmd = sys .argv [1 ]
10451101
10461102if cmd == 'list' :
@@ -1088,7 +1144,9 @@ if cmd == 'list':
10881144 "download-user-2fa" : {"instance" : "roadwarrior1" , "username" : "myuser" },
10891145 "download_all_user_configurations" : {"instance" : "roadwarrior1" },
10901146 "connection-history-csv" : {"instance" : "roadwarrior1" , "timezone" : "Europe/Rome" },
1091- "connection-history" : {"instance" : "roadwarrior1" }
1147+ "connection-history" : {"instance" : "roadwarrior1" },
1148+ "renew-server-certificate" : {"instance" : "roadwarrior1" },
1149+ "regenerate-all-certificates" : {"instance" : "roadwarrior1" }
10921150 }))
10931151else :
10941152 action = sys .argv [2 ]
@@ -1145,5 +1203,9 @@ else:
11451203 ret = connection_history_csv (args ['instance' ], args ['timezone' ])
11461204 elif action == "connection-history" :
11471205 ret = connection_history (args ['instance' ])
1206+ elif action == "renew-server-certificate" :
1207+ ret = renew_server_certificate (args ["instance" ])
1208+ elif action == "regenerate-all-certificates" :
1209+ ret = regenerate_all_certificates (args ["instance" ])
11481210
11491211 print (json .dumps (ret ))
0 commit comments