Fix Let's Encrypt Certbot PEM path parsing
ⓘ Prevent Webmin from swallowing Certbot's key-path output when extracting PEM paths, while preserving IPv6 cert-name support and adding regression coverage.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/perl
|
||||
# Regression tests for Certbot output path parsing used by Webmin SSL.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use File::Basename qw(dirname);
|
||||
use File::Spec;
|
||||
|
||||
our %config;
|
||||
our $module_config_directory = "/etc/webmin/webmin";
|
||||
|
||||
sub has_command { return undef; }
|
||||
|
||||
my $script = File::Spec->rel2abs(
|
||||
File::Spec->catfile(dirname(__FILE__), '..',
|
||||
'webmin', 'letsencrypt-lib.pl'));
|
||||
do $script or die "failed to load $script: $@ $!";
|
||||
|
||||
my $certbot_out = <<'EOF';
|
||||
Successfully received certificate.
|
||||
Certificate is saved at: /etc/letsencrypt/live/test.example/fullchain.pem
|
||||
Key is saved at: /etc/letsencrypt/live/test.example/privkey.pem
|
||||
This certificate expires on 2026-09-25.
|
||||
EOF
|
||||
|
||||
is(main::get_letsencrypt_output_pem_path($certbot_out),
|
||||
'/etc/letsencrypt/live/test.example/fullchain.pem',
|
||||
'certbot output path stops at the first PEM path');
|
||||
|
||||
my $ipv6_out = <<'EOF';
|
||||
Successfully received certificate.
|
||||
Certificate is saved at: /etc/letsencrypt/live/2001:db8::1/fullchain.pem
|
||||
Key is saved at: /etc/letsencrypt/live/2001:db8::1/privkey.pem
|
||||
EOF
|
||||
|
||||
is(main::get_letsencrypt_output_pem_path($ipv6_out),
|
||||
'/etc/letsencrypt/live/2001:db8::1/fullchain.pem',
|
||||
'IPv6 certificate names can still contain colons');
|
||||
|
||||
my $wrapped_out = <<'EOF';
|
||||
Certificate is saved at: /etc/letsencrypt/live/wrapped.example/
|
||||
fullchain.pem
|
||||
EOF
|
||||
|
||||
is(main::get_letsencrypt_output_pem_path($wrapped_out),
|
||||
'/etc/letsencrypt/live/wrapped.example/fullchain.pem',
|
||||
'wrapped PEM paths are normalized');
|
||||
|
||||
done_testing();
|
||||
@@ -131,6 +131,20 @@ return &software::missing_install_link(
|
||||
"certbot", $text{'letsencrypt_certbot'}, $rlink, $rmsg);
|
||||
}
|
||||
|
||||
# get_letsencrypt_output_pem_path(output)
|
||||
# Returns the first certbot PEM path from command output, or undef
|
||||
sub get_letsencrypt_output_pem_path
|
||||
{
|
||||
my ($out) = @_;
|
||||
if ($out =~ /((?:\/usr\/local)?\/etc\/letsencrypt\/(?:live|archive)\/[a-zA-Z0-9\.\_\-:\/\*]+\.pem)/ ||
|
||||
$out =~ /((?:\/usr\/local)?\/etc\/letsencrypt\/(?:live|archive)\/[a-zA-Z0-9\.\_\-:\/\r\n\* ]*?\.pem)/) {
|
||||
my $full = $1;
|
||||
$full =~ s/\s//g;
|
||||
return $full;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
# request_letsencrypt_cert(domain|&domains|&ips, webroot, [email], [keysize],
|
||||
# [request-mode], [use-staging], [account-email],
|
||||
# [key-type], [reuse-key],
|
||||
@@ -387,14 +401,15 @@ if ($letsencrypt_cmd) {
|
||||
goto FAILED;
|
||||
}
|
||||
my ($full, $cert, $key, $chain);
|
||||
if ($out =~ /((?:\/usr\/local)?\/etc\/letsencrypt\/(?:live|archive)\/[a-zA-Z0-9\.\_\-:\/\r\n\* ]*\.pem)/) {
|
||||
if ($full = &get_letsencrypt_output_pem_path($out)) {
|
||||
# Output contained the full path
|
||||
$full = $1;
|
||||
$full =~ s/\s//g;
|
||||
}
|
||||
else {
|
||||
# Try searching common paths
|
||||
my @fulls = (glob("/etc/letsencrypt/live/$certname-*/cert.pem"),
|
||||
my @fulls = grep { -r $_ } (
|
||||
"/etc/letsencrypt/live/$certname/cert.pem",
|
||||
glob("/etc/letsencrypt/live/$certname-*/cert.pem"),
|
||||
"/usr/local/etc/letsencrypt/live/$certname/cert.pem",
|
||||
glob("/usr/local/etc/letsencrypt/live/$certname-*/cert.pem"));
|
||||
if (@fulls) {
|
||||
my %stats = map { $_, [ stat($_) ] } @fulls;
|
||||
|
||||
Reference in New Issue
Block a user