Skip to content

Instantly share code, notes, and snippets.

@voodoojello
Created October 22, 2018 17:04
Show Gist options
  • Save voodoojello/be0054c850e1a0f260d6bb3b5b4e2dbb to your computer and use it in GitHub Desktop.
Save voodoojello/be0054c850e1a0f260d6bb3b5b4e2dbb to your computer and use it in GitHub Desktop.
Certbot DNS Vaildator for AWS Route53 (w/pkcs12 packager)
#!/usr/bin/perl
#
# Certbot DNS Vaildator for AWS Route53 (w/pkcs12 packager)
# ----------------------------------------------------------
# Author: Mark Page [mark@very3.net]
# Modified: Mon Oct 22 12:01:39 2018 -0500
# Requires: awscli, cerbot, openssl
#
# References:
# https://certbot.eff.org/docs/using.html
# https://docs.aws.amazon.com/cli/latest/reference/route53/index.html
#
#
use strict;
use warnings;
use Data::Dumper;
my $app = {
'le_email' => 'XXXX@XXXXX.XXX',
'aws_aki' => 'XXXXXXXXXXXXXXXXXXXX', # AWS_ACCESS_KEY_ID
'aws_sak' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', # AWS_SECRET_ACCESS_KEY
'aws_dr' => 'us-west-2', # AWS_DEFAULT_REGION
'aws_do' => 'json', # AWS_DEFAULT_OUTPUT
'debug' => 0,
};
if ($app->{'debug'}) {
$ENV{'CERTBOT_DOMAIN'} = $ARGV[0];
$ENV{'CERTBOT_VALIDATION'} = localtime(time);
$ENV{'CERTBOT_AUTH_OUTPUT'} = 1;
}
if ($ENV{'CERTBOT_DOMAIN'}) {
if (defined($ENV{'CERTBOT_VALIDATION'}) and !defined($ENV{'CERTBOT_AUTH_OUTPUT'})) {
my $_result = aws::txt_rec({
'host' => $ENV{'CERTBOT_DOMAIN'},
'action' => 'UPSERT',
'validation' => $ENV{'CERTBOT_VALIDATION'},
});
logger::write(Dumper($_result));
exit;
}
if (defined($ENV{'CERTBOT_VALIDATION'}) and defined($ENV{'CERTBOT_AUTH_OUTPUT'})) {
my $_result = aws::txt_rec({
'host' => $ENV{'CERTBOT_DOMAIN'},
'action' => 'DELETE',
'validation' => $ENV{'CERTBOT_VALIDATION'},
});
logger::write(Dumper($_result));
exit;
}
}
else {
if (!$ARGV[0]) {
die "\nERROR: You must provide a host name!\n\n";
}
$ENV{'DOMAIN_NAME'} = $ARGV[0];
my $_mkdir = `mkdir -p "$ENV{'PWD'}/letsencrypt"`;
my $_certbot = 'certbot certonly --agree-tos --manual-public-ip-logging-ok --email "'.$app->{'le_email'}.'" --non-interactive --manual --manual-auth-hook "'.$0.'" --manual-cleanup-hook "'.$0.'" --preferred-challenge dns --config-dir "'.$ENV{'PWD'}.'/letsencrypt" --work-dir "'.$ENV{'PWD'}.'/letsencrypt" --logs-dir "'.$ENV{'PWD'}.'/letsencrypt" --domains "'.$ENV{'DOMAIN_NAME'}.'"';
chomp(my $_certbot_result = `$_certbot`);
logger::write($_certbot_result);
if ($_certbot_result) {
my $_pfx = pfx::make({
'certpath' => $ENV{'PWD'}.'/letsencrypt/archive/'.$ENV{'DOMAIN_NAME'},
'host' => $ENV{'DOMAIN_NAME'},
});
logger::write(Dumper($_pfx));
}
}
exit;
#-------------------------------------------------------------------------------
package logger;
sub write {
my ($_a) = @_;
my ($_r);
my $_logpath = '';
if ($ENV{'CERTBOT_DOMAIN'}) {
$_logpath = $ENV{'PWD'}.'/letsencrypt/'.$ENV{'CERTBOT_DOMAIN'}.'.log';
}
if ($ENV{'DOMAIN_NAME'}) {
$_logpath = $ENV{'PWD'}.'/letsencrypt/'.$ENV{'DOMAIN_NAME'}.'.log';
}
open(my $_log,'>>',"$_logpath");
print $_log localtime(time).': '.join("\n",@_);
print $_log '=' x 60 . "\n";
close($_log);
return 1;
}
return 1;
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
package aws;
sub txt_rec {
my ($_a) = @_;
my ($_r);
my $_start = time;
$ENV{'AWS_ACCESS_KEY_ID'} = $app->{'aws_aki'};
$ENV{'AWS_SECRET_ACCESS_KEY'} = $app->{'aws_sak'};
$ENV{'AWS_DEFAULT_REGION'} = $app->{'aws_dr'};
$ENV{'AWS_DEFAULT_OUTPUT'} = $app->{'aws_do'};
if (defined($_a->{'host'}) and defined($_a->{'action'})) {
my $_dn = (split(/\./,$_a->{'host'}))[-2].'.'.(split(/\./,$_a->{'host'}))[-1];
chomp(my $_zoneid = `aws route53 list-hosted-zones --query 'HostedZones[?Name == \`$_dn.\`]|[?Config.PrivateZone == \`false\`].Id' --output text`);
my $_txtrec_cmd = 'aws route53 change-resource-record-sets --hosted-zone-id "'.$_zoneid.'" --query ChangeInfo.Id --output text --change-batch "{\"Changes\":[{\"Action\":\"'.$_a->{'action'}.'\",\"ResourceRecordSet\":{\"Name\":\"_acme-challenge.'.$_a->{'host'}.'.\",\"ResourceRecords\":[{\"Value\":\"\\\\\"'.$_a->{'validation'}.'\\\\\"\"}],\"Type\":\"TXT\",\"TTL\":30 }}]}"';
chomp($_r->{'txtrec_return'} = `$_txtrec_cmd`);
my $_aws_chk_cmd = 'aws route53 wait resource-record-sets-changed --id '.$_r->{'txtrec_return'};
chomp($_r->{'aws_chk_return'} = `$_aws_chk_cmd`);
}
else {
$_r->{'error'} = 'Host and/or action not defined';
}
$_r->{'time'} = (time - $_start);
return $_r;
}
return 1;
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
package pfx;
sub make {
my ($_a) = @_;
my ($_r);
my $_start = time;
if (defined($_a->{'host'}) and defined($_a->{'certpath'})) {
my $_make_pfx_cmd = 'openssl pkcs12 -nodes -export -out "'.$_a->{'certpath'}.'/'.$_a->{'host'}.'.pfx" -inkey "'.$_a->{'certpath'}.'/privkey1.pem" -in "'.$_a->{'certpath'}.'/cert1.pem" -certfile "'.$_a->{'certpath'}.'/fullchain1.pem" -passout pass:';
chomp($_r->{'result'} = `$_make_pfx_cmd`);
}
else {
$_r->{'error'} = 'Host and/or certificate path not defined';
}
$_r->{'time'} = (time - $_start);
return $_r;
}
return 1;
#-------------------------------------------------------------------------------
__END__
CERTBOT_DOMAIN: The domain being authenticated
CERTBOT_VALIDATION: The validation string (HTTP-01 and DNS-01 only)
CERTBOT_TOKEN: Resource name part of the HTTP-01 challenge (HTTP-01 only)
CERTBOT_CERT_PATH: The challenge SSL certificate (TLS-SNI-01 only)
CERTBOT_KEY_PATH: The private key associated with the aforementioned SSL certificate (TLS-SNI-01 only)
CERTBOT_SNI_DOMAIN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment