Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions files/haproxy/acmetool_sync.sh

This file was deleted.

71 changes: 70 additions & 1 deletion files/haproxy/update_acmetool_challenge_script.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,75 @@
#!/bin/sh

thumbprint=`/usr/bin/acmetool account-thumbprint | awk '{ print $1 }'`
# This is a new implementation of the update acmetool challange script.
# We no longer use the actual acme-tool, as it is not good for may domains.
# We switched to certbot, but it does not support getting challange directly so we need to calculate it.

# We first need to chec if there is already an account, and if not then we create a new one.
if [ ! -f "/etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/*/private_key.json" ]
then
logger "Could not find Let's encrypt account info, creating new account with certbot..."
certbot register --agree-tos -m noreply@atomia.com
fi

# Certbot saves the private_key info in the .json file, so we need a few values from the whole json file, not the whole file.
# We parse the n and e keys from the file which are used in calculation of the jwk (account thumbprint).
n="`cat /etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/*/private_key.json | python -c "import sys, json; print json.load(sys.stdin)['n']"`"
e="`cat /etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/*/private_key.json | python -c "import sys, json; print json.load(sys.stdin)['e']"`"

jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'

# These are the functions that are pulled from.
# https://github.com/Neilpang/acme.sh/blob/master/acme.sh
# That's a Bash implementation of acme utility for Lets encrypt.
_url_replace() {
tr '/+' '_-' | tr -d '= '
}

#Usage: multiline
_base64() {
[ "" ] #urgly
if [ "$1" ]; then
logger "base64 multiline:'$1'"
${ACME_OPENSSL_BIN:-openssl} base64 -e
else
logger "base64 single line."
${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
fi
}

#Usage: hashalg [outputhex]
#Output Base64-encoded digest
_digest() {
alg="$1"
if [ -z "$alg" ]; then
echo "Usage: _digest hashalg"
return 1
fi

outputhex="$2"

if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
if [ "$outputhex" ]; then
${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
else
${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
fi
else
logger "$alg is not supported yet"
return 1
fi

}

__calc_account_thumbprint() {
printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
}

# Now we are ready to calculate the thumbprint so we can call the function.
# The thumbprint var will now contain the calculated thumbprint in the format that Let's encrypt needs for verification.
thumbprint=$(__calc_account_thumbprint)

# Finally we create the lua file that is loaded by haproxy in order for the challange to pass.
cat > /usr/lib/stateless_acme_challenge.lua <<EOF
global_account_thumbprint = "$thumbprint"

Expand Down
71 changes: 56 additions & 15 deletions manifests/haproxy.pp
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,24 @@
$mail_cluster_ip = hiera('atomia::mailserver::cluster_ip', '')
$ftp_cluster_ip = hiera('atomia::pureftpd::ftp_cluster_ip', '')
$ssh_cluster_ip = hiera('atomia::sshserver::cluster_ip', '')
$awstats_cluster_ip = hiera('atomia::awstats::server_ip','')

class { 'apt': }

if $::operatingsystem == 'Ubuntu' {
package { [
'python-software-properties',
'software-properties-common',
'acmetool',
'software-properties-common'
]:
ensure => present,
}

apt::ppa { 'ppa:vbernat/haproxy-1.5':
require => Package['python-software-properties']
}
apt::ppa { 'ppa:certbot/certbot':
require => Package['software-properties-common']
}

if $ssh_cluster_ip != '' {
class { 'ssh::server':
Expand All @@ -130,6 +133,13 @@
}
}

# Acme tool has been replace with the better certbot.

package { 'certbot':
ensure => present,
require => [ Apt::Ppa['ppa:certbot/certbot'] ]
}

package { 'haproxy':
ensure => present,
require => [ Apt::Ppa['ppa:vbernat/haproxy-1.5'] ]
Expand Down Expand Up @@ -204,30 +214,32 @@
command => '/etc/init.d/keepalived restart',
}
}

# We need to have the following directories in order
# for our support to work as expected

$acme_conf_dirs = [ '/var/lib/acme', '/var/lib/acme/conf', '/var/lib/acme/haproxy' ]
$acme_conf_dirs = [ '/etc/letsencrypt', '/etc/letsencrypt/conf', '/etc/letsencrypt/live', '/etc/letsencrypt/renewal', '/etc/letsencrypt/archive', '/etc/haproxy/le_certs', '/etc/haproxy/synced_apache_config']
file { $acme_conf_dirs:
ensure => directory,
owner => root,
group => root,
mode => '0755',
}

# This file is needed by haproxy configuration to actually be
# able to verify the ownership of the domain by LE.

file { '/usr/lib/stateless_acme_challenge.lua':
ensure => present,
owner => 'root',
group => 'root',
mode => '0644',
require => [ Package['certbot'], File['/etc/letsencrypt/conf'], File['/usr/bin/update_acmetool_challenge_script.sh'] ],
notify => Exec['acmetool-quickstart']
}

file { '/var/lib/acme/conf/responses':
owner => 'root',
group => 'root',
mode => '0644',
content => template('atomia/haproxy/acmetool-quickstart-responses.erb'),
require => [ Package['acmetool'], File['/var/lib/acme/conf'], File['/usr/bin/update_acmetool_challenge_script.sh'] ],
notify => Exec['acmetool-quickstart'],
}
# This file is used to intially create a lua script that is used
# by haproxy to verify the LE challenge. This script is run only once.

file { '/usr/bin/update_acmetool_challenge_script.sh':
owner => 'root',
Expand All @@ -236,16 +248,33 @@
source => 'puppet:///modules/atomia/haproxy/update_acmetool_challenge_script.sh',
}

# This file has been changed to erb because we now include the IP adress of
# the apache cluster so we can validate if the domain is pointing to our IP
# and then we try to issue a certificate for that domain.

file { '/usr/bin/acmetool_sync.sh':
owner => 'root',
group => 'root',
mode => '0700',
source => 'puppet:///modules/atomia/haproxy/acmetool_sync.sh',
content => template('atomia/haproxy/acmetool_sync.sh.erb'),
}

# The haproxy manifest did not have a renewal at all for lets encrypt.
# So this is added in order to support the renewal of certs.

file { '/usr/bin/acmetool_renew.sh':
owner => 'root',
group => 'root',
mode => '0700',
content => template('atomia/haproxy/acmetool_renew.sh.erb'),
}

# This runs the update script that creates the LE account and
# generates the thumbprint which is saved to the lua file, loaded by haproxy.

exec { 'acmetool-quickstart':
refreshonly => true,
command => '/usr/bin/acmetool quickstart --batch && /usr/bin/update_acmetool_challenge_script.sh',
command => '/usr/bin/update_acmetool_challenge_script.sh',
notify => File['/etc/haproxy/haproxy.cfg'],
}

Expand Down Expand Up @@ -366,6 +395,18 @@
content => $sync_ssl_redirects_cron,
require => [ File['/root/.ssh/id_rsa'], File['/etc/haproxy/sync_ssl_redirects.sh'] ]
}

# We need to renew the certificates at some point so we will run the
# script at midnight and start the renewal every day.

file { '/etc/cron.d/atomia-sync-renew-acmetool':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => '0 0 * * * root flock -n /var/lock/acmerenew.lock /usr/bin/acmetool_renew.sh',
require => [ File['/root/.ssh/id_rsa'], File['/usr/bin/acmetool_renew.sh'] ]
}

}

Expand All @@ -382,7 +423,7 @@
} else {
file { '/etc/haproxy/atomia_certificates/default.pem':
ensure => file,
source => 'puppet:///modules/atomiacerts/certificates/wildcard_with_key.pem',
source => 'puppet:///atomiacerts/wildcard_with_key.pem',
owner => 'root',
group => 'root',
mode => '0755',
Expand All @@ -396,7 +437,7 @@
Package['haproxy'],
File['/etc/haproxy/atomia_certificates'],
File['/usr/lib/stateless_acme_challenge.lua'],
File['/var/lib/acme/haproxy'],
File['/etc/haproxy/le_certs'],
File['/etc/haproxy/ssl-redirects.lst']
],
notify => Exec['restart-haproxy'],
Expand Down
103 changes: 103 additions & 0 deletions templates/haproxy/acmetool_renew.sh.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/bin/bash

PATH=$PATH:/usr/sbin
export PATH

ATO_NETS="<%= @apache_cluster_ip %>/32"

CERTS_DIR="/etc/haproxy/le_certs"

# If the ATO_NETS ip is not binded then we need to exit the script as this nodes does not have binded IP
ip a | grep $ATO_NETS > /dev/null
BINDED=$?
if [ $BINDED -eq 1 ]
then
logger "Skipping renew of LE certs, as this nodes does not have apache cluster ip!"
exit 1
fi

curdate=$(date +%s)
renewed_cert=0
revoked_cert=0

in_net() {
perl -e '
use strict;
my $net = shift @ARGV or die "no net";
my $ip = shift @ARGV or die "no ip";
my @pair = split("/", $net);
my $pf = $pair[0];
my $pl = $pair[1];
$pf =~ s/(\d+)([.]|$)/sprintf("%02X", $1)/ge;
$pf = unpack("N", pack("H8", $pf));
$pl = unpack("N", pack("b32", "1" x $pl . "0" x (32 - $pl)));
$ip =~ s/(\d+)([.]|$)/sprintf("%02X", $1)/ge;
$ip = unpack("N", pack("H8", $ip));
exit 1 if ($pf & $pl) ne ($ip & $pl);
' "$1" "$2"
}

in_ato_nets() {
is_in=no
for net in $ATO_NETS; do
in_net $net "$1" && {
is_in=yes
break
}
done
echo $is_in
}

cd $CERTS_DIR
ls -tr | \
while read pem; do
expdate1=$(date --date="$(openssl x509 -enddate -noout -in "$pem"|cut -d= -f 2)" +%s)
expdays=$(( (expdate1-curdate) / 86400))

if [ $expdays -le 25 ];
then

wanted_cert=`echo -n "$pem" | head -c-4`

ip="$(dig +short $wanted_cert | head -1)"

if [ -n "$ip" ] && [ "$(in_ato_nets $ip)" = yes ] ; then
echo "Certificate $wanted_cert expire on $expdate1, in $expdays days !"
echo "Renewing.."

certbot renew --cert-name $wanted_cert --force-renewal
OUT=$? #0 if renew is succesfull, 1 if it's failed

if [ $OUT -eq 0 ];then
sudo -E bash -c "cat /etc/letsencrypt/live/$wanted_cert/fullchain.pem /etc/letsencrypt/live/$wanted_cert/privkey.pem > /etc/haproxy/le_certs/$wanted_cert.pem"
chmod 600 /etc/haproxy/le_certs/$wanted_cert.pem
rm /var/log/letsencrypt/letsencrypt.log
find /var/log/letsencrypt/ -size 0 -delete

((renewed_cert++))
fi
else
echo "Certificate $wanted_cert not hosted on this hosting!"
echo "Revoking.."
certbot revoke -d $wanted_cert --cert-path /etc/letsencrypt/live/${wanted_cert}/cert.pem --non-interactive
rm -f /etc/haproxy/le_certs/$wanted_cert.pem
((revoked_cert++))
fi
fi

if [ $revoked_cert -ge 1000 ];
then
echo "Revoke limit hit - breaking"
echo "Total number of revoked certs is $revoked_cert"
echo "Total number of renewed certs is $renewed_cert"
break
fi

if [ $renewed_cert -ge 1000 ];
then
echo "Renew limit hit - breaking"
echo "Total number of revoked certs is $revoked_cert"
echo "Total number of renewed certs is $renewed_cert"
break
fi
done
Loading