[1/2] OpenVPN: Show indication when OpenVPN certificates expire

Message ID 20230222122534.259972-1-michael.tremer@ipfire.org
State Accepted
Headers
Series [1/2] OpenVPN: Show indication when OpenVPN certificates expire |

Commit Message

Michael Tremer Feb. 22, 2023, 12:25 p.m. UTC
  This will help with #11742 - OpenVPN: No method to replace expired
certificates.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
---
 doc/language_issues.en    |  2 ++
 doc/language_issues.es    |  2 ++
 doc/language_issues.fr    |  2 ++
 doc/language_issues.it    |  2 ++
 doc/language_issues.nl    |  2 ++
 doc/language_issues.pl    |  2 ++
 doc/language_issues.ru    |  2 ++
 doc/language_issues.tr    |  2 ++
 doc/language_missings     | 14 +++++++++++
 html/cgi-bin/ovpnmain.cgi | 51 +++++++++++++++++++++++++--------------
 langs/de/cgi-bin/de.pl    |  2 ++
 langs/en/cgi-bin/en.pl    |  2 ++
 12 files changed, 67 insertions(+), 18 deletions(-)
  

Comments

Peter Müller March 5, 2023, 2:41 p.m. UTC | #1
Acked-by: Peter Müller <peter.mueller@ipfire.org>

> This will help with #11742 - OpenVPN: No method to replace expired
> certificates.
> 
> Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
> ---
>  doc/language_issues.en    |  2 ++
>  doc/language_issues.es    |  2 ++
>  doc/language_issues.fr    |  2 ++
>  doc/language_issues.it    |  2 ++
>  doc/language_issues.nl    |  2 ++
>  doc/language_issues.pl    |  2 ++
>  doc/language_issues.ru    |  2 ++
>  doc/language_issues.tr    |  2 ++
>  doc/language_missings     | 14 +++++++++++
>  html/cgi-bin/ovpnmain.cgi | 51 +++++++++++++++++++++++++--------------
>  langs/de/cgi-bin/de.pl    |  2 ++
>  langs/en/cgi-bin/en.pl    |  2 ++
>  12 files changed, 67 insertions(+), 18 deletions(-)
> 
> diff --git a/doc/language_issues.en b/doc/language_issues.en
> index c29e3bed6..474921415 100644
> --- a/doc/language_issues.en
> +++ b/doc/language_issues.en
> @@ -1413,6 +1413,8 @@ WARNING: untranslated string: only digits allowed in max retries field = Only di
>  WARNING: untranslated string: only digits allowed in the idle timeout = Only digits allowed in the idle timeout.
>  WARNING: untranslated string: open connections = Open Connections
>  WARNING: untranslated string: openssl produced an error = OpenSSL produced an error
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: openvpn client = OpenVPN client
>  WARNING: untranslated string: openvpn default = Default
>  WARNING: untranslated string: openvpn destination port used = The destination port is already used by another OpenVPN server.
> diff --git a/doc/language_issues.es b/doc/language_issues.es
> index 0bd390d5d..91240dbe6 100644
> --- a/doc/language_issues.es
> +++ b/doc/language_issues.es
> @@ -980,6 +980,8 @@ WARNING: untranslated string: hardware vulnerabilities = Hardware Vulnerabilitie
>  WARNING: untranslated string: info messages = unknown string
>  WARNING: untranslated string: invalid ip or hostname = Invalid IP Address or Hostname
>  WARNING: untranslated string: no data = unknown string
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: pakfire ago = ago.
>  WARNING: untranslated string: route config changed = unknown string
>  WARNING: untranslated string: routing config added = unknown string
> diff --git a/doc/language_issues.fr b/doc/language_issues.fr
> index 56d69d86e..f70cda819 100644
> --- a/doc/language_issues.fr
> +++ b/doc/language_issues.fr
> @@ -946,6 +946,8 @@ WARNING: untranslated string: guardian logtarget_file = unknown string
>  WARNING: untranslated string: guardian logtarget_syslog = unknown string
>  WARNING: untranslated string: guardian no entries = unknown string
>  WARNING: untranslated string: guardian service = unknown string
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: pakfire ago = ago.
>  WARNING: untranslated string: retbleed = Retbleed
>  WARNING: untranslated string: route config changed = unknown string
> diff --git a/doc/language_issues.it b/doc/language_issues.it
> index 9999f947c..cfde3f8e4 100644
> --- a/doc/language_issues.it
> +++ b/doc/language_issues.it
> @@ -1174,6 +1174,8 @@ WARNING: untranslated string: one month = One Month
>  WARNING: untranslated string: one week = One Week
>  WARNING: untranslated string: one year = One Year
>  WARNING: untranslated string: open connections = Open Connections
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: optional = Optional
>  WARNING: untranslated string: otp qrcode = OTP QRCode
>  WARNING: untranslated string: outgoing compression in bytes per second = Outgoing compression
> diff --git a/doc/language_issues.nl b/doc/language_issues.nl
> index 14a7b420e..738d7c706 100644
> --- a/doc/language_issues.nl
> +++ b/doc/language_issues.nl
> @@ -1197,6 +1197,8 @@ WARNING: untranslated string: one month = One Month
>  WARNING: untranslated string: one week = One Week
>  WARNING: untranslated string: one year = One Year
>  WARNING: untranslated string: open connections = Open Connections
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: optional = Optional
>  WARNING: untranslated string: otp qrcode = OTP QRCode
>  WARNING: untranslated string: outgoing compression in bytes per second = Outgoing compression
> diff --git a/doc/language_issues.pl b/doc/language_issues.pl
> index a53a208d9..ab21f1381 100644
> --- a/doc/language_issues.pl
> +++ b/doc/language_issues.pl
> @@ -1355,6 +1355,8 @@ WARNING: untranslated string: one month = One Month
>  WARNING: untranslated string: one week = One Week
>  WARNING: untranslated string: one year = One Year
>  WARNING: untranslated string: open connections = Open Connections
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: openvpn default = Default
>  WARNING: untranslated string: openvpn destination port used = The destination port is already used by another OpenVPN server.
>  WARNING: untranslated string: openvpn fragment allowed with udp = Using fragment is only allowed when using the UDP protocol.
> diff --git a/doc/language_issues.ru b/doc/language_issues.ru
> index c5dc1aa61..6b2622f26 100644
> --- a/doc/language_issues.ru
> +++ b/doc/language_issues.ru
> @@ -1353,6 +1353,8 @@ WARNING: untranslated string: one month = One Month
>  WARNING: untranslated string: one week = One Week
>  WARNING: untranslated string: one year = One Year
>  WARNING: untranslated string: open connections = Open Connections
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: openvpn default = Default
>  WARNING: untranslated string: openvpn destination port used = The destination port is already used by another OpenVPN server.
>  WARNING: untranslated string: openvpn fragment allowed with udp = Using fragment is only allowed when using the UDP protocol.
> diff --git a/doc/language_issues.tr b/doc/language_issues.tr
> index 552082a96..6b86ebc7e 100644
> --- a/doc/language_issues.tr
> +++ b/doc/language_issues.tr
> @@ -1091,6 +1091,8 @@ WARNING: untranslated string: no entries = No entries at the moment.
>  WARNING: untranslated string: not affected = Not Affected
>  WARNING: untranslated string: not validating = Not validating
>  WARNING: untranslated string: open connections = Open Connections
> +WARNING: untranslated string: openvpn cert expires soon = Expires Soon
> +WARNING: untranslated string: openvpn cert has expired = Expired
>  WARNING: untranslated string: optional = Optional
>  WARNING: untranslated string: otp qrcode = OTP QRCode
>  WARNING: untranslated string: ovpn connection name = Connection Name
> diff --git a/doc/language_missings b/doc/language_missings
> index 65d38b422..934c5a60c 100644
> --- a/doc/language_missings
> +++ b/doc/language_missings
> @@ -105,6 +105,8 @@
>  < dns servers
>  < hardware vulnerabilities
>  < invalid ip or hostname
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < service boot setting unavailable
>  < transport mode does not support vti
>  < wlanap
> @@ -127,6 +129,8 @@
>  < retbleed
>  < service boot setting unavailable
>  < show dh
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < upload fcdsl.o
>  ############################################################################
>  # Checking cgi-bin translations for language: it                           #
> @@ -470,6 +474,8 @@
>  < one week
>  < one year
>  < open connections
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < optional
>  < otp qrcode
>  < outgoing compression in bytes per second
> @@ -997,6 +1003,8 @@
>  < one week
>  < one year
>  < open connections
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < optional
>  < otp qrcode
>  < outgoing compression in bytes per second
> @@ -1829,6 +1837,8 @@
>  < one week
>  < one year
>  < open connections
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < openvpn default
>  < openvpn destination port used
>  < openvpn disabled
> @@ -2812,6 +2822,8 @@
>  < one week
>  < one year
>  < open connections
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < openvpn default
>  < openvpn destination port used
>  < openvpn disabled
> @@ -3316,6 +3328,8 @@
>  < not validating
>  < okay
>  < open connections
> +< openvpn cert expires soon
> +< openvpn cert has expired
>  < optional
>  < otp qrcode
>  < ovpn connection name
> diff --git a/html/cgi-bin/ovpnmain.cgi b/html/cgi-bin/ovpnmain.cgi
> index 42a7354fc..87bda4f1e 100644
> --- a/html/cgi-bin/ovpnmain.cgi
> +++ b/html/cgi-bin/ovpnmain.cgi
> @@ -33,6 +33,7 @@ use File::Temp qw/ tempfile tempdir /;
>  use strict;
>  use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
>  use Sort::Naturally;
> +use Date::Parse;
>  require '/var/ipfire/general-functions.pl';
>  require "${General::swroot}/lang.pl";
>  require "${General::swroot}/header.pl";
> @@ -5352,31 +5353,45 @@ END
>  END
>  		}
>  	if ($confighash{$key}[0] eq 'on') { $gif = 'on.gif'; } else { $gif = 'off.gif'; }
> -	if ($id % 2) {
> -		print "<tr>";
> -		$col="bgcolor='$color{'color20'}'";
> -	} else {
> -		print "<tr>";
> -		$col="bgcolor='$color{'color22'}'";
> -	}
> -	print "<td align='center' nowrap='nowrap' $col>$confighash{$key}[1]</td>";
> -	print "<td align='center' nowrap='nowrap' $col>" . $Lang::tr{"$confighash{$key}[3]"} . " (" . $Lang::tr{"$confighash{$key}[4]"} . ")</td>";
> -	#if ($confighash{$key}[4] eq 'cert') {
> -	    #print "<td align='left' nowrap='nowrap'>$confighash{$key}[2]</td>";
> -	#} else {
> -	    #print "<td align='left'>&nbsp;</td>";
> -	#}
> -	my @cavalid = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/certs/$confighash{$key}[1]cert.pem");
> -	my $cavalid;
>  
> +	# Fetch information about the certificate
> +	my @cavalid = &General::system_output("/usr/bin/openssl", "x509", "-text",
> +		"-in", "${General::swroot}/ovpn/certs/$confighash{$key}[1]cert.pem");
> +
> +	my $expiryDate = 0;
> +
> +	# Parse the certificate information
>  	foreach my $line (@cavalid) {
>  		if ($line =~ /Not After : (.*)[\n]/) {
> -			$cavalid    = $1;
> -
> +			$expiryDate = &Date::Parse::str2time($1);
>  			last;
>  		}
>  	}
>  
> +	# Calculate the remaining time
> +	my $remainingTime = $expiryDate - time();
> +
> +	# Create some simple booleans to check the status
> +	my $hasExpired = ($remainingTime <= 0);
> +	my $expiresSoon = ($remainingTime <= 30 * 24 * 3600);
> +
> +	print "<tr>";
> +
> +	if ($hasExpired || $expiresSoon) {
> +		$col="bgcolor='$color{'color14'}'";
> +	} elsif ($id % 2) {
> +		$col="bgcolor='$color{'color20'}'";
> +	} else {
> +		$col="bgcolor='$color{'color22'}'";
> +	}
> +	print "<td align='center' nowrap='nowrap' $col>$confighash{$key}[1]";
> +	if ($hasExpired) {
> +		print " ($Lang::tr{'openvpn cert has expired'})";
> +	} elsif ($expiresSoon) {
> +		print " ($Lang::tr{'openvpn cert expires soon'})";
> +	}
> +	print "</td>";
> +	print "<td align='center' nowrap='nowrap' $col>" . $Lang::tr{"$confighash{$key}[3]"} . " (" . $Lang::tr{"$confighash{$key}[4]"} . ")</td>";
>  	print "<td align='center' $col>$confighash{$key}[25]</td>";
>  	$col1="bgcolor='${Header::colourred}'";
>  	my $active = "<b><font color='#FFFFFF'>$Lang::tr{'capsclosed'}</font></b>";
> diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl
> index 5fbab2ff8..a57b62ad8 100644
> --- a/langs/de/cgi-bin/de.pl
> +++ b/langs/de/cgi-bin/de.pl
> @@ -1884,6 +1884,8 @@
>  'open connections' => 'Offene Verbindungen',
>  'open to all' => 'Überschreibe externen Zugang zu ALL',
>  'openssl produced an error' => 'OpenSSL hat einen Fehler verursacht',
> +'openvpn cert expires soon' => 'Läuft bald ab',
> +'openvpn cert has expired' => 'Abgelaufen',
>  'openvpn client' => 'OpenVPN-Client',
>  'openvpn default' => 'Vorgabe',
>  'openvpn destination port used' => 'Der Zielport wird bereits von einer anderen OpenVPN-Server-Instanz genutzt.',
> diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl
> index 80753b841..4dc9d8577 100644
> --- a/langs/en/cgi-bin/en.pl
> +++ b/langs/en/cgi-bin/en.pl
> @@ -1940,6 +1940,8 @@
>  'open connections' => 'Open Connections',
>  'open to all' => 'Override external access to ALL',
>  'openssl produced an error' => 'OpenSSL produced an error',
> +'openvpn cert expires soon' => 'Expires Soon',
> +'openvpn cert has expired' => 'Expired',
>  'openvpn client' => 'OpenVPN client',
>  'openvpn default' => 'Default',
>  'openvpn destination port used' => 'The destination port is already used by another OpenVPN server.',
  

Patch

diff --git a/doc/language_issues.en b/doc/language_issues.en
index c29e3bed6..474921415 100644
--- a/doc/language_issues.en
+++ b/doc/language_issues.en
@@ -1413,6 +1413,8 @@  WARNING: untranslated string: only digits allowed in max retries field = Only di
 WARNING: untranslated string: only digits allowed in the idle timeout = Only digits allowed in the idle timeout.
 WARNING: untranslated string: open connections = Open Connections
 WARNING: untranslated string: openssl produced an error = OpenSSL produced an error
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: openvpn client = OpenVPN client
 WARNING: untranslated string: openvpn default = Default
 WARNING: untranslated string: openvpn destination port used = The destination port is already used by another OpenVPN server.
diff --git a/doc/language_issues.es b/doc/language_issues.es
index 0bd390d5d..91240dbe6 100644
--- a/doc/language_issues.es
+++ b/doc/language_issues.es
@@ -980,6 +980,8 @@  WARNING: untranslated string: hardware vulnerabilities = Hardware Vulnerabilitie
 WARNING: untranslated string: info messages = unknown string
 WARNING: untranslated string: invalid ip or hostname = Invalid IP Address or Hostname
 WARNING: untranslated string: no data = unknown string
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: pakfire ago = ago.
 WARNING: untranslated string: route config changed = unknown string
 WARNING: untranslated string: routing config added = unknown string
diff --git a/doc/language_issues.fr b/doc/language_issues.fr
index 56d69d86e..f70cda819 100644
--- a/doc/language_issues.fr
+++ b/doc/language_issues.fr
@@ -946,6 +946,8 @@  WARNING: untranslated string: guardian logtarget_file = unknown string
 WARNING: untranslated string: guardian logtarget_syslog = unknown string
 WARNING: untranslated string: guardian no entries = unknown string
 WARNING: untranslated string: guardian service = unknown string
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: pakfire ago = ago.
 WARNING: untranslated string: retbleed = Retbleed
 WARNING: untranslated string: route config changed = unknown string
diff --git a/doc/language_issues.it b/doc/language_issues.it
index 9999f947c..cfde3f8e4 100644
--- a/doc/language_issues.it
+++ b/doc/language_issues.it
@@ -1174,6 +1174,8 @@  WARNING: untranslated string: one month = One Month
 WARNING: untranslated string: one week = One Week
 WARNING: untranslated string: one year = One Year
 WARNING: untranslated string: open connections = Open Connections
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: optional = Optional
 WARNING: untranslated string: otp qrcode = OTP QRCode
 WARNING: untranslated string: outgoing compression in bytes per second = Outgoing compression
diff --git a/doc/language_issues.nl b/doc/language_issues.nl
index 14a7b420e..738d7c706 100644
--- a/doc/language_issues.nl
+++ b/doc/language_issues.nl
@@ -1197,6 +1197,8 @@  WARNING: untranslated string: one month = One Month
 WARNING: untranslated string: one week = One Week
 WARNING: untranslated string: one year = One Year
 WARNING: untranslated string: open connections = Open Connections
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: optional = Optional
 WARNING: untranslated string: otp qrcode = OTP QRCode
 WARNING: untranslated string: outgoing compression in bytes per second = Outgoing compression
diff --git a/doc/language_issues.pl b/doc/language_issues.pl
index a53a208d9..ab21f1381 100644
--- a/doc/language_issues.pl
+++ b/doc/language_issues.pl
@@ -1355,6 +1355,8 @@  WARNING: untranslated string: one month = One Month
 WARNING: untranslated string: one week = One Week
 WARNING: untranslated string: one year = One Year
 WARNING: untranslated string: open connections = Open Connections
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: openvpn default = Default
 WARNING: untranslated string: openvpn destination port used = The destination port is already used by another OpenVPN server.
 WARNING: untranslated string: openvpn fragment allowed with udp = Using fragment is only allowed when using the UDP protocol.
diff --git a/doc/language_issues.ru b/doc/language_issues.ru
index c5dc1aa61..6b2622f26 100644
--- a/doc/language_issues.ru
+++ b/doc/language_issues.ru
@@ -1353,6 +1353,8 @@  WARNING: untranslated string: one month = One Month
 WARNING: untranslated string: one week = One Week
 WARNING: untranslated string: one year = One Year
 WARNING: untranslated string: open connections = Open Connections
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: openvpn default = Default
 WARNING: untranslated string: openvpn destination port used = The destination port is already used by another OpenVPN server.
 WARNING: untranslated string: openvpn fragment allowed with udp = Using fragment is only allowed when using the UDP protocol.
diff --git a/doc/language_issues.tr b/doc/language_issues.tr
index 552082a96..6b86ebc7e 100644
--- a/doc/language_issues.tr
+++ b/doc/language_issues.tr
@@ -1091,6 +1091,8 @@  WARNING: untranslated string: no entries = No entries at the moment.
 WARNING: untranslated string: not affected = Not Affected
 WARNING: untranslated string: not validating = Not validating
 WARNING: untranslated string: open connections = Open Connections
+WARNING: untranslated string: openvpn cert expires soon = Expires Soon
+WARNING: untranslated string: openvpn cert has expired = Expired
 WARNING: untranslated string: optional = Optional
 WARNING: untranslated string: otp qrcode = OTP QRCode
 WARNING: untranslated string: ovpn connection name = Connection Name
diff --git a/doc/language_missings b/doc/language_missings
index 65d38b422..934c5a60c 100644
--- a/doc/language_missings
+++ b/doc/language_missings
@@ -105,6 +105,8 @@ 
 < dns servers
 < hardware vulnerabilities
 < invalid ip or hostname
+< openvpn cert expires soon
+< openvpn cert has expired
 < service boot setting unavailable
 < transport mode does not support vti
 < wlanap
@@ -127,6 +129,8 @@ 
 < retbleed
 < service boot setting unavailable
 < show dh
+< openvpn cert expires soon
+< openvpn cert has expired
 < upload fcdsl.o
 ############################################################################
 # Checking cgi-bin translations for language: it                           #
@@ -470,6 +474,8 @@ 
 < one week
 < one year
 < open connections
+< openvpn cert expires soon
+< openvpn cert has expired
 < optional
 < otp qrcode
 < outgoing compression in bytes per second
@@ -997,6 +1003,8 @@ 
 < one week
 < one year
 < open connections
+< openvpn cert expires soon
+< openvpn cert has expired
 < optional
 < otp qrcode
 < outgoing compression in bytes per second
@@ -1829,6 +1837,8 @@ 
 < one week
 < one year
 < open connections
+< openvpn cert expires soon
+< openvpn cert has expired
 < openvpn default
 < openvpn destination port used
 < openvpn disabled
@@ -2812,6 +2822,8 @@ 
 < one week
 < one year
 < open connections
+< openvpn cert expires soon
+< openvpn cert has expired
 < openvpn default
 < openvpn destination port used
 < openvpn disabled
@@ -3316,6 +3328,8 @@ 
 < not validating
 < okay
 < open connections
+< openvpn cert expires soon
+< openvpn cert has expired
 < optional
 < otp qrcode
 < ovpn connection name
diff --git a/html/cgi-bin/ovpnmain.cgi b/html/cgi-bin/ovpnmain.cgi
index 42a7354fc..87bda4f1e 100644
--- a/html/cgi-bin/ovpnmain.cgi
+++ b/html/cgi-bin/ovpnmain.cgi
@@ -33,6 +33,7 @@  use File::Temp qw/ tempfile tempdir /;
 use strict;
 use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
 use Sort::Naturally;
+use Date::Parse;
 require '/var/ipfire/general-functions.pl';
 require "${General::swroot}/lang.pl";
 require "${General::swroot}/header.pl";
@@ -5352,31 +5353,45 @@  END
 END
 		}
 	if ($confighash{$key}[0] eq 'on') { $gif = 'on.gif'; } else { $gif = 'off.gif'; }
-	if ($id % 2) {
-		print "<tr>";
-		$col="bgcolor='$color{'color20'}'";
-	} else {
-		print "<tr>";
-		$col="bgcolor='$color{'color22'}'";
-	}
-	print "<td align='center' nowrap='nowrap' $col>$confighash{$key}[1]</td>";
-	print "<td align='center' nowrap='nowrap' $col>" . $Lang::tr{"$confighash{$key}[3]"} . " (" . $Lang::tr{"$confighash{$key}[4]"} . ")</td>";
-	#if ($confighash{$key}[4] eq 'cert') {
-	    #print "<td align='left' nowrap='nowrap'>$confighash{$key}[2]</td>";
-	#} else {
-	    #print "<td align='left'>&nbsp;</td>";
-	#}
-	my @cavalid = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/certs/$confighash{$key}[1]cert.pem");
-	my $cavalid;
 
+	# Fetch information about the certificate
+	my @cavalid = &General::system_output("/usr/bin/openssl", "x509", "-text",
+		"-in", "${General::swroot}/ovpn/certs/$confighash{$key}[1]cert.pem");
+
+	my $expiryDate = 0;
+
+	# Parse the certificate information
 	foreach my $line (@cavalid) {
 		if ($line =~ /Not After : (.*)[\n]/) {
-			$cavalid    = $1;
-
+			$expiryDate = &Date::Parse::str2time($1);
 			last;
 		}
 	}
 
+	# Calculate the remaining time
+	my $remainingTime = $expiryDate - time();
+
+	# Create some simple booleans to check the status
+	my $hasExpired = ($remainingTime <= 0);
+	my $expiresSoon = ($remainingTime <= 30 * 24 * 3600);
+
+	print "<tr>";
+
+	if ($hasExpired || $expiresSoon) {
+		$col="bgcolor='$color{'color14'}'";
+	} elsif ($id % 2) {
+		$col="bgcolor='$color{'color20'}'";
+	} else {
+		$col="bgcolor='$color{'color22'}'";
+	}
+	print "<td align='center' nowrap='nowrap' $col>$confighash{$key}[1]";
+	if ($hasExpired) {
+		print " ($Lang::tr{'openvpn cert has expired'})";
+	} elsif ($expiresSoon) {
+		print " ($Lang::tr{'openvpn cert expires soon'})";
+	}
+	print "</td>";
+	print "<td align='center' nowrap='nowrap' $col>" . $Lang::tr{"$confighash{$key}[3]"} . " (" . $Lang::tr{"$confighash{$key}[4]"} . ")</td>";
 	print "<td align='center' $col>$confighash{$key}[25]</td>";
 	$col1="bgcolor='${Header::colourred}'";
 	my $active = "<b><font color='#FFFFFF'>$Lang::tr{'capsclosed'}</font></b>";
diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl
index 5fbab2ff8..a57b62ad8 100644
--- a/langs/de/cgi-bin/de.pl
+++ b/langs/de/cgi-bin/de.pl
@@ -1884,6 +1884,8 @@ 
 'open connections' => 'Offene Verbindungen',
 'open to all' => 'Überschreibe externen Zugang zu ALL',
 'openssl produced an error' => 'OpenSSL hat einen Fehler verursacht',
+'openvpn cert expires soon' => 'Läuft bald ab',
+'openvpn cert has expired' => 'Abgelaufen',
 'openvpn client' => 'OpenVPN-Client',
 'openvpn default' => 'Vorgabe',
 'openvpn destination port used' => 'Der Zielport wird bereits von einer anderen OpenVPN-Server-Instanz genutzt.',
diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl
index 80753b841..4dc9d8577 100644
--- a/langs/en/cgi-bin/en.pl
+++ b/langs/en/cgi-bin/en.pl
@@ -1940,6 +1940,8 @@ 
 'open connections' => 'Open Connections',
 'open to all' => 'Override external access to ALL',
 'openssl produced an error' => 'OpenSSL produced an error',
+'openvpn cert expires soon' => 'Expires Soon',
+'openvpn cert has expired' => 'Expired',
 'openvpn client' => 'OpenVPN client',
 'openvpn default' => 'Default',
 'openvpn destination port used' => 'The destination port is already used by another OpenVPN server.',