Commit 5a6e393d authored by Nigel Kukard's avatar Nigel Kukard

Added --check-only, --force, --live and --only options

parent cc285544
......@@ -26,6 +26,8 @@ package AWIT::CertMaster;
use strict;
use warnings;
use List::MoreUtils qw( any );
our $VERSION = '1.00';
......@@ -97,7 +99,7 @@ sub accountInit
}
# Check if the file exists, if it does, we need to create else login
my $keyFile = "/etc/awit-certmaster/account.key";
my $keyFile = sprintf("/etc/awit-certmaster/account.key%s",$self->{'file_suffix'});
if (-e $keyFile) {
my $keyPEM = "";
......@@ -182,6 +184,7 @@ sub webserverCheckNginx
my $self = shift;
# Grab the current datetime
my $now = DateTime->now();
# Open the main nginx sites-available directory and take a peek
......@@ -262,11 +265,33 @@ sub webserverCheckNginx
# Lets look for the certificate files...
(my $certBaseName = $vhostName) =~ s/\./-/g;
# Grab our filenames
$vhost->{'cert_file'} = sprintf('/etc/ssl/private/%s.crt',$certBaseName);
$vhost->{'cert_cafile'} = sprintf('/etc/ssl/private/%s.cacrt',$certBaseName);
$vhost->{'cert_bundlefile'} = sprintf('/etc/ssl/private/%s.bundle',$certBaseName);
$vhost->{'cert_keyfile'} = sprintf('/etc/ssl/private/%s.key',$certBaseName);
$vhost->{'cert_file'} = sprintf('/etc/ssl/private/%s.crt%s',$certBaseName,$self->{'file_suffix'});
$vhost->{'cert_cafile'} = sprintf('/etc/ssl/private/%s.cacrt%s',$certBaseName,$self->{'file_suffix'});
$vhost->{'cert_bundlefile'} = sprintf('/etc/ssl/private/%s.bundle%s',$certBaseName,$self->{'file_suffix'});
$vhost->{'cert_keyfile'} = sprintf('/etc/ssl/private/%s.key%s',$certBaseName,$self->{'file_suffix'});
# Check if we're only processing a certain vhost
if (defined(my $onlyVhosts = $self->{'only_vhosts'})) {
# Check if this is the host we're forcing
if (@{$onlyVhosts} > 0) {
# Check if its not in the list
if (! any {$_ eq $vhostName} @{$onlyVhosts}) {
$self->logger("INFO","WEBSERVER: - Skipping regeneration of certificate for '%s', not in --only list",$vhostName);
goto SKIP;
}
}
}
# Check if we need to force certificate generation
if (defined(my $forceVhosts = $self->{'force_vhosts'})) {
# Check if this is the host we're forcing
if (any {$_ eq $vhostName} @{$forceVhosts}) {
$self->logger("WARNING","WEBSERVER: - Forcing regeneration of certificate for '%s'",$vhostName);
next;
}
}
# Check if the files exist
if (! -f $vhost->{'cert_file'} || ! -f $vhost->{'cert_cafile'} || ! -f $vhost->{'cert_bundlefile'} ||
......@@ -295,19 +320,22 @@ sub webserverCheckNginx
}
# Loop with server names and check if the domain is in the certificate
my @missingDomains = ();
foreach my $domain (keys %{$vhost->{'server_names'}}) {
# Check if the domain is in the certificate
if (!defined($vhost->{'certificate_names'}->{$domain})) {
$self->logger("WARNING","WEBSERVER: - The vhost '%s' contains a domain '%s' not in the certificate, adding to regeneration list",$vhostName,$domain);
next;
push(@missingDomains,$domain);
}
}
next if (@missingDomains);
# Output a notice if the cert will be re-applied for in the next 3 days
if ($daysToExpire < 34) {
$self->logger("NOTICE","WEBSERVER: - Certificate for vhost '%s' expires soon, it will be renewed in %s days",$vhostName,$daysToExpire);
}
SKIP:
# All is good, remove from the vhost from further processing
delete($vhostConfig->{$vhostName});
}
......@@ -317,6 +345,25 @@ sub webserverCheckNginx
return $self;
}
# Check if we're going to check only...
if ($self->{'check_only'}) {
$self->logger("NOTICE","WEBSERVER: Check only...");
# Loop with vhosts
foreach my $vhostName (keys %{$vhostConfig}) {
my $vhost = $vhostConfig->{$vhostName};
$self->logger("NOTICE","WEBSERVER: - Vhost '%s' needs a certificate",$vhostName);
# Loop with domains in the vhost
foreach my $domain (keys %{$vhost->{'server_names'}}) {
$self->logger("NOTICE","WEBSERVER: - Domain '%s'",$domain);
}
}
return $self;
}
# If we still have entries, we need to start processing!
$self->accountInit();
......@@ -336,7 +383,7 @@ sub webserverCheckNginx
# Write out files
umask(0122);
if (open(my $fh,'>',my $filename = $vhost->{'cert_file'}.".new")) {
if (open(my $fh,'>',my $filename = $vhost->{'cert_file'})) {
print($fh $certs->{'certificate'});
close($fh);
$self->logger("INFO","WEBSERVER: - Certificate file '$filename'");
......@@ -345,7 +392,7 @@ sub webserverCheckNginx
next;
}
if (open(my $fh,'>',my $filename = $vhost->{'cert_cafile'}.".new")) {
if (open(my $fh,'>',my $filename = $vhost->{'cert_cafile'})) {
print($fh $certs->{'chain'});
close($fh);
$self->logger("INFO","WEBSERVER: - Certificate CA file '$filename'");
......@@ -354,7 +401,7 @@ sub webserverCheckNginx
next;
}
if (open(my $fh,'>',my $filename = $vhost->{'cert_bundlefile'}.".new")) {
if (open(my $fh,'>',my $filename = $vhost->{'cert_bundlefile'})) {
print($fh $certs->{'certificate'});
print($fh $certs->{'chain'});
close($fh);
......@@ -365,7 +412,7 @@ sub webserverCheckNginx
}
umask(0127);
if (open(my $fh,'>',my $filename = $vhost->{'cert_keyfile'}.".new")) {
if (open(my $fh,'>',my $filename = $vhost->{'cert_keyfile'})) {
print($fh $certs->{'key'});
close($fh);
$self->logger("INFO","WEBSERVER: - Certificate key file '$filename'");
......@@ -376,13 +423,25 @@ sub webserverCheckNginx
}
# Check if the configtest passes
system('service nginx configtest > /dev/null 2>&1');
if ($? >> 8) {
$self->logger("ERROR","NGINX: Failed configtest, reload NOT done");
return $self;
}
if ($self->{'live'}) {
# Check if Nginx config is OK
system('service nginx configtest > /dev/null 2>&1');
if ($? >> 8) {
$self->logger("ERROR","NGINX: Failed configtest, reload NOT done");
return $self;
}
$self->logger("INFO","NGINX: Successful configtest");
$self->logger("INFO","NGINX: Successful configtest");
# Reload Nginx config
system('service nginx reload > /dev/null 2>&1');
if ($? >> 8) {
$self->logger("ERROR","NGINX: Failed reload");
return $self;
}
$self->logger("INFO","NGINX: Successful reload");
}
return $self;
}
......@@ -411,10 +470,34 @@ sub logger
# Internal _init function
sub _init
{
my $self = shift;
my ($self,$opts) = @_;
# If we're running in live mode, don't use a suffix
if (defined($opts->{'live'}) && $opts->{'live'}) {
$self->{'file_suffix'} = '';
$self->{'live'} = 1;
} else {
$self->{'file_suffix'} = '.test';
$self->{'live'} = 0;
}
# Check if we're checking only...
if (defined($opts->{'check_only'}) && $opts->{'check_only'}) {
$self->{'check_only'} = 1;
} else {
$self->{'check_only'} = 0;
}
# Init properties
$self->{'key'} = undef;
# Keep track of what we're processing
$self->{'force_vhosts'} = $opts->{'force_vhosts'} // [ ];
$self->{'only_vhosts'} = $opts->{'only_vhosts'} // [ ];
return $self;
}
......@@ -649,13 +732,20 @@ sub NID_SUBJECT_ALT_NAME { 85; }
# Class initialization
sub _init
{
my $self = shift;
my ($self,$opts,@args) = @_;
# Call parent _init(), VERY important!
$self->SUPER::_init($opts,@args);
$self->SUPER::_init();
# Set server to use
if ($self->{'live'}) {
$self->{'server'} = "https://acme-v01.api.letsencrypt.org/directory";
} else {
$self->{'server'} = "https://acme-staging.api.letsencrypt.org/directory";
}
# Initialize properties
$self->{'server'} = "https://acme-staging.api.letsencrypt.org/directory";
# We need to track the nonce we get from the remote server
$self->{'nonce'} = undef;
# Setup user agent
......@@ -1144,13 +1234,30 @@ sub leHandleChallenge
$self->logger("INFO","LE: Creating challenge for domain '%s'",$domain);
# Create JWK
my $jwk = $self->_encode_json({
'kty' => "RSA",
'e' => $self->{'key'}->{'e'},
'n' => $self->{'key'}->{'n'},
});
# Create fingerprint
my $fingerprint = encode_base64url(sha256($jwk));
# Find the HTTP challenge
my $httpChallenge;
foreach my $challenge (@{$self->{'challenges'}->{$domain}}) {
$self->logger("INFO","LE: - Challeng for '%s' received, URI is '%s'",$challenge->{'type'},
$challenge->{'uri'});
# Create authorization key
my $keyAuthorization = sprintf('%s.%s',$challenge->{'token'},$fingerprint);
# Check if this is the challenge we're looking for
if ($challenge->{'type'} eq "http-01") {
$challenge->{'key_authorization'} = $keyAuthorization;
$httpChallenge = $challenge;
last;
next;
}
}
......@@ -1172,17 +1279,6 @@ sub leHandleChallenge
exit 1;
}
# Create JWK
my $jwk = $self->_encode_json({
'kty' => "RSA",
'e' => $self->{'key'}->{'e'},
'n' => $self->{'key'}->{'n'},
});
# Create fingerprint
my $fingerprint = encode_base64url(sha256($jwk));
my $keyAuthorization = sprintf('%s.%s',$httpChallenge->{'token'},$fingerprint);
#
......@@ -1207,7 +1303,7 @@ sub leHandleChallenge
umask(0022);
if (open(my $FH,'>',$challengeFile)) {
# Write out contents
print($FH $keyAuthorization);
print($FH $httpChallenge->{'key_authorization'});
close($FH);
# If the open failed, ERR out
} else {
......@@ -1228,7 +1324,7 @@ sub leHandleChallenge
# Build the verification we need to send back to state we've complied
my $json = $self->_encode_json({
'resource' => "challenge",
'keyAuthorization' => $keyAuthorization,
'keyAuthorization' => $httpChallenge->{'key_authorization'},
});
my $jws = $self->_leCreateJWS($json);
......@@ -1414,7 +1510,7 @@ use Getopt::Long;
my $NAME = "AWIT-CertMaster";
our $VERSION = "1.0.0";
our $VERSION = "1.0.1";
......@@ -1428,6 +1524,10 @@ GetOptions(\%optctl,
"help|?",
"version",
"check-only",
"force=s@",
"live",
"only=s@",
) or exit 1;
# Check for help
......@@ -1444,7 +1544,12 @@ if (defined($optctl{'version'})) {
my $cm = AWIT::CertMaster::LetsEncrypt->new();
my $cm = AWIT::CertMaster::LetsEncrypt->new({
'check_only' => $optctl{'check-only'},
'force_vhosts' => $optctl{'force'},
'live' => $optctl{'live'},
'only_vhosts' => $optctl{'only'},
});
$cm->webserverCheckNginx();
......@@ -1474,7 +1579,10 @@ sub displayHelp
--version Display version.
Certificate Functions:
--xxxxx yyyyyyyyy something here.
--check-only Only check, don't do anything.
--force <VHOST> Force certificate generation.
--live Run live and manage production certs.
--only <VHOST> Only process this VHOST.
EOF
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment