Import the old haproxy role.

This commit is contained in:
Andrea Dell'Amico 2020-06-22 13:41:05 +02:00
parent df6bcb2b0b
commit 38e97f9d09
14 changed files with 1111 additions and 68 deletions

View File

@ -1,31 +1,59 @@
Role Name Role Name
========= =========
A brief description of the role goes here. A role that installs Haproxy, <https://www.haproxy.org>.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables Role Variables
-------------- --------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. The most important variables are listed below:
``` yaml
haproxy_latest_release: True
haproxy_version: 2.0
haproxy_repo_key: 'http://haproxy.debian.net/bernat.debian.org.gpg'
haproxy_debian_latest_repo: "deb http://haproxy.debian.net {{ ansible_lsb.codename }}-backports-{{ haproxy_version }} main"
haproxy_ubuntu_latest_repo: "ppa:vbernat/haproxy-{{ haproxy_version }}"
haproxy_pkg_state: latest
haproxy_enabled: True
haproxy_k_bind_non_local_ip: True
haproxy_default_port: 80
haproxy_terminate_tls: False
haproxy_ssl_port: 443
haproxy_admin_port: 8880
haproxy_admin_socket: /run/haproxy/admin.sock
haproxy_letsencrypt_managed: True
haproxy_cert_dir: '{{ pki_dir }}/haproxy'
haproxy_nagios_check: False
# It's a percentage
haproxy_nagios_check_w: 70
haproxy_nagios_check_c: 90
haproxy_check_interval: 3s
haproxy_backend_maxconn: 2048
haproxy_sysctl_conntrack_max: 131072
```
Additional tasks
------------
The user of this role will need to write a haproxy.cfg template and install it with a dedicated task. Something like
```yaml
- name: Configure haproxy
template: src=haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg owner=root group=haproxy mode=0440
notify: Reload haproxy
tags: [ 'haproxy', 'haproxy_conf' ]
```
Dependencies Dependencies
------------ ------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. * letsencrypt-acme-sh
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License License
------- -------
@ -35,4 +63,4 @@ EUPL-1.2
Author Information Author Information
------------------ ------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed). Andrea Dell'Amico, <andrea.dellamico@isti.cnr.it>

View File

@ -1,2 +1,29 @@
--- ---
# defaults file for ansible-role-template haproxy_latest_release: True
haproxy_version: 2.0
haproxy_repo_key: 'http://haproxy.debian.net/bernat.debian.org.gpg'
haproxy_debian_latest_repo: "deb http://haproxy.debian.net {{ ansible_lsb.codename }}-backports-{{ haproxy_version }} main"
haproxy_ubuntu_latest_repo: "ppa:vbernat/haproxy-{{ haproxy_version }}"
haproxy_pkg_state: latest
haproxy_enabled: True
haproxy_k_bind_non_local_ip: True
haproxy_default_port: 80
haproxy_terminate_tls: False
haproxy_ssl_port: 443
haproxy_admin_port: 8880
haproxy_admin_socket: /run/haproxy/admin.sock
haproxy_letsencrypt_managed: True
haproxy_cert_dir: '{{ pki_dir }}/haproxy'
haproxy_nagios_check: False
# It's a percentage
haproxy_nagios_check_w: 70
haproxy_nagios_check_c: 90
haproxy_check_interval: 3s
haproxy_backend_maxconn: 2048
haproxy_sysctl_conntrack_max: 131072

225
files/check_haproxy_stats Normal file
View File

@ -0,0 +1,225 @@
#!/usr/bin/env perl
# vim: se et ts=4:
#
# Copyright (C) 2012, Giacomo Montagner <giacomo@entirelyunlike.net>
#
# This program is free software; you can redistribute it and/or modify it
# under the same terms as Perl 5.10.1.
# For more details, see http://dev.perl.org/licenses/artistic.html
#
# This program is distributed in the hope that it will be
# useful, but without any warranty; without even the implied
# warranty of merchantability or fitness for a particular purpose.
#
our $VERSION = "1.0.1";
# CHANGELOG:
# 1.0.0 - first release
# 1.0.1 - fixed empty message if all proxies are OK
#
use strict;
use warnings;
use 5.010.001;
use File::Basename qw/basename/;
use IO::Socket::UNIX;
use Getopt::Long;
sub usage {
my $me = basename $0;
print <<EOU;
NAME
$me - check haproxy stats for errors, using UNIX socket interface
SYNOPSIS
$me [OPTIONS]
DESCRIPTION
Get haproxy statistics via UNIX socket and parse information searching for errors.
OPTIONS
-c, --critical
Set critical threshold for sessions number (chacks current number of sessions
against session limit, if enforced) to the specified percentage.
If no session limit (slim) was specified for the given proxy, this option has
no effect.
-d, --dump
Just dump haproxy stats and exit;
-h, --help
Print this message.
-p, --proxy
Check only named proxies, not every one. Use comma to separate proxies
in list.
-s, --sock, --socket
Use named UNIX socket instead of default (/run/haproxy/admin.sock)
-w, --warning
Set warning threshold for sessions number to the specified percentage (see -c)
CHECKS AND OUTPUT
$me checks every proxy (or the named ones, if -p was given)
for status. It returns an error if any of the checked FRONTENDs is not OPEN,
any of the checked BACKENDs is not UP, or any of the checkes servers is not UP;
$me reports any problem it found.
EXAMPLES
$me -s /var/spool/haproxy/sock
Use /var/spool/haproxy/sock to communicate with haproxy.
$me -p proxy1,proxy2 -w 60 -c 80
Check only proxies named "proxy1" and "proxy2", and set sessions number
thresholds to 60% and 80%.
AUTHOR
Written by Giacomo Montagner
REPORTING BUGS
Please report any bug to bugs\@entirelyunlike.net
COPYRIGHT
Copyright (C) 2012 Giacomo Montagner <giacomo\@entirelyunlike.net>.
$me is distributed under GPL and the Artistic License 2.0
SEE ALSO
Check out online haproxy documentation at <http://haproxy.1wt.eu/>
EOU
}
my %check_statuses = (
UNK => "unknown",
INI => "initializing",
SOCKERR => "socket error",
L4OK => "layer 4 check OK",
L4CON => "connection error",
L4TMOUT => "layer 1-4 timeout",
L6OK => "layer 6 check OK",
L6TOUT => "layer 6 (SSL) timeout",
L6RSP => "layer 6 protocol error",
L7OK => "layer 7 check OK",
L7OKC => "layer 7 conditionally OK",
L7TOUT => "layer 7 (HTTP/SMTP) timeout",
L7RSP => "layer 7 protocol error",
L7STS => "layer 7 status error",
);
my @status_names = (qw/OK WARNING CRITICAL UNKNOWN/);
# Defaults
my $swarn = 80.0;
my $scrit = 90.0;
my $sock = "/run/haproxy/admin.sock";
my $dump;
my $proxy;
my $help;
# Read command line
Getopt::Long::Configure ("bundling");
GetOptions (
"c|critical=i" => \$scrit,
"d|dump" => \$dump,
"h|help" => \$help,
"p|proxy=s" => \$proxy,
"s|sock|socket=s" => \$sock,
"w|warning=i" => \$swarn,
);
# Want help?
if ($help) {
usage;
exit 3;
}
# Connect to haproxy socket and get stats
my $haproxy = new IO::Socket::UNIX (
Peer => $sock,
Type => SOCK_STREAM,
);
die "Unable to connect to haproxy socket: $@" unless $haproxy;
print $haproxy "show stat\n" or die "Print to socket failed: $!";
# Dump stats and exit if requested
if ($dump) {
while (<$haproxy>) {
print;
}
exit 0;
}
# Get labels from first output line and map them to their position in the line
my $labels = <$haproxy>;
chomp($labels);
$labels =~ s/^# // or die "Data format not supported.";
my @labels = split /,/, $labels;
{
no strict "refs";
my $idx = 0;
map { $$_ = $idx++ } @labels;
}
# Variables I will use from here on:
our $pxname;
our $svname;
our $status;
my @proxies = split ',', $proxy if $proxy;
my $exitcode = 0;
my $msg;
my $checked = 0;
while (<$haproxy>) {
chomp;
next if /^[[:space:]]*$/;
my @data = split /,/, $_;
if (@proxies) { next unless grep {$data[$pxname] eq $_} @proxies; };
# Is session limit enforced?
our $slim;
if ($data[$slim]) {
# Check current session # against limit
our $scur;
my $sratio = $data[$scur]/$data[$slim];
if ($sratio >= $scrit || $sratio >= $swarn) {
$exitcode = $sratio >= $scrit ? 2 :
$exitcode < 2 ? 1 : $exitcode;
$msg .= sprintf "%s:%s sessions: %.2f%%; ", $data[$pxname], $data[$svname], $sratio;
}
}
# Check of BACKENDS
if ($data[$svname] eq 'BACKEND') {
if ($data[$status] ne 'UP') {
$msg .= sprintf "BACKEND: %s is %s; ", $data[$pxname], $data[$status];
$exitcode = 2;
}
# Check of FRONTENDS
} elsif ($data[$svname] eq 'FRONTEND') {
if ($data[$status] ne 'OPEN') {
$msg .= sprintf "FRONTEND: %s is %s; ", $data[$pxname], $data[$status];
$exitcode = 2;
}
# Check of servers
} else {
if ($data[$status] ne 'UP') {
next if $data[$status] eq 'no check'; # Ignore server if no check is configured to be run
$exitcode = 2;
our $check_status;
$msg .= sprintf "server: %s:%s is %s", $data[$pxname], $data[$svname], $data[$status];
$msg .= sprintf " (check status: %s)", $check_statuses{$data[$check_status]} if $check_statuses{$data[$check_status]};
$msg .= "; ";
}
}
++$checked;
}
unless ($msg) {
$msg = @proxies ? sprintf("checked proxies: %s", join ', ', sort @proxies) : "checked $checked proxies.";
}
say "Check haproxy $status_names[$exitcode] - $msg";
exit $exitcode;

View File

@ -0,0 +1,29 @@
#!/bin/bash
LE_SERVICES_SCRIPT_DIR=/usr/local/lib/letsencrypt
LE_CERTS_DIR=/etc/letsencrypt/live/$HOSTNAME
LE_LOG_DIR=/var/log/letsencrypt
HAPROXY_CERTDIR=/etc/pki/haproxy
HAPROXY_CERTFILE=$HAPROXY_CERTDIR/haproxy.pem
DATE=$( date )
echo "$DATE" >> $LE_LOG_DIR/haproxy.log
if [ -f /etc/default/letsencrypt ] ; then
. /etc/default/letsencrypt
else
echo "No letsencrypt default file" >> $LE_LOG_DIR/haproxy.log
fi
[ ! -d $HAPROXY_CERTDIR ] && mkdir $HAPROXY_CERTDIR
echo "Building the new certificate file" >> $LE_LOG_DIR/haproxy.log
cat ${LE_CERTS_DIR}/{fullchain.pem,privkey.pem} > ${HAPROXY_CERTFILE}
chmod 440 ${HAPROXY_CERTFILE}
chgrp haproxy ${HAPROXY_CERTFILE}
echo "Reload the haproxy service" >> $LE_LOG_DIR/haproxy.log
service haproxy reload >/dev/null 2>&1
echo "Done." >> $LE_LOG_DIR/haproxy.log
exit 0

View File

@ -1,2 +1,13 @@
--- ---
# handlers file for ansible-role-template - name: Restart haproxy
service: name=haproxy state=restarted
when: haproxy_enabled
- name: Reload haproxy
service: name=haproxy state=reloaded
when: haproxy_enabled
- name: Reload rsyslog
service: name=rsyslog state=reloaded
when: haproxy_enabled

View File

@ -1,61 +1,23 @@
galaxy_info: galaxy_info:
author: your name author: Andrea Dell'Amico
description: your description description: Systems Architect
company: ISTI-CNR company: ISTI-CNR
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
issue_tracker_url: https://redmine-s2i2s.isti.cnr.it/projects/provisioning issue_tracker_url: https://redmine-s2i2s.isti.cnr.it/projects/provisioning
# Some suggested licenses: license: EUPL 1.2+
# - BSD (default)
# - MIT
# - GPLv2
# - GPLv3
# - Apache
# - CC-BY
license: EUPL-1.2
min_ansible_version: 2.8 min_ansible_version: 2.8
# If this a Container Enabled role, provide the minimum Ansible Container version.
# min_ansible_container_version:
# Optionally specify the branch Galaxy will use when accessing the GitHub
# repo for this role. During role install, if no tags are available,
# Galaxy will use this branch. During import Galaxy will access files on
# this branch. If Travis integration is configured, only notifications for this
# branch will be accepted. Otherwise, in all cases, the repo's default branch
# (usually master) will be used.
#github_branch:
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit: # To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/ # https://galaxy.ansible.com/api/v1/platforms/
# #
# platforms: platforms:
# - name: Fedora - name: Ubuntu
# versions: versions:
# - all - bionic
# - 25
# - name: SomePlatform
# versions:
# - all
# - 1.0
# - 7
# - 99.99
galaxy_tags: [] galaxy_tags:
# List tags for your role here, one per line. A tag is a keyword that describes - haproxy
# and categorizes the role. Users find roles by searching for tags. Be sure to
# remove the '[]' above, if you add tags to this list.
#
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies: [] dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.

View File

@ -0,0 +1,16 @@
---
- block:
- name: Create the acme hooks directory if it does not yet exist
file: dest={{ letsencrypt_acme_sh_services_scripts_dir }} state=directory owner=root group=root
- name: Install a script that fix the letsencrypt certificate for haproxy and then reload the service
template: src=haproxy-letsencrypt-acme.sh.j2 dest={{ letsencrypt_acme_sh_services_scripts_dir }}/haproxy owner=root group=root mode=4555
- name: When we are going to install letsencrypt certificates, create a preliminary path and a self signed cert. Now handle the haproxy special case
shell: mkdir {{ pki_dir }}/haproxy ; cat {{ letsencrypt_acme_user_home | default(omit) }}/live/{{ ansible_fqdn }}/privkey {{ letsencrypt_acme_user_home | default(omit) }}/live/{{ ansible_fqdn }}/cert > {{ pki_dir }}/haproxy/haproxy.pem
args:
creates: '{{ pki_dir }}/haproxy/haproxy.pem'
tags: [ 'pki', 'ssl', 'letsencrypt', 'haproxy', 'letsencrypt_acme_sh' ]
when: letsencrypt_acme_sh_install
tags: [ 'haproxy', 'letsencrypt', 'letsencrypt_acme_sh' ]

10
tasks/haproxy-nagios.yml Normal file
View File

@ -0,0 +1,10 @@
---
- name: Install the haproxy NRPE nagios check
copy: src=check_haproxy_stats dest={{ nagios_local_plugdir }}/check_haproxy_stats owner=root group=root mode=0555
when: haproxy_nagios_check
- name: Install the haproxy NRPE command configuration
template: src=lb.cfg.j2 dest={{ nrpe_include_dir }}/lb.cfg owner=root group=root mode=0444
notify: Reload NRPE server
when: haproxy_nagios_check

63
tasks/haproxy-service.yml Normal file
View File

@ -0,0 +1,63 @@
---
- name: Get the haproxy repo key
apt_key: url={{ haproxy_repo_key }} state=present
when: haproxy_latest_release
tags: haproxy
- name: Define the haproxy repository
apt_repository: repo='{{ haproxy_ubuntu_latest_repo }}' state=present update_cache=yes
when:
- haproxy_latest_release
- is_ubuntu
tags: haproxy
- name: Define the haproxy repository
apt_repository: repo='{{ haproxy_debian_latest_repo }}' state=present update_cache=yes
when:
- haproxy_latest_release
- is_debian
tags: haproxy
- name: Install the haproxy package
apt: name=haproxy state=present default_release={{ ansible_lsb.codename }}-backports update_cache=yes cache_valid_time=3600
when: not haproxy_latest_release
register: install_haproxy
tags: haproxy
- name: Install the haproxy package
apt: name=haproxy state=latest default_release={{ ansible_lsb.codename }}-backports-{{ haproxy_version }} update_cache=yes cache_valid_time=3600
when:
- haproxy_latest_release
- is_debian
register: install_haproxy
tags: haproxy
- name: Install the haproxy package
apt: name=haproxy state=latest update_cache=yes cache_valid_time=3600
when:
- haproxy_latest_release
- is_ubuntu
register: install_haproxy
tags: haproxy
- name: Enable kernel binding non local IP addresses
sysctl: name={{ item }} value=1 reload=yes state=present
with_items:
- net.ipv4.ip_nonlocal_bind
when: haproxy_k_bind_non_local_ip
tags: [ 'haproxy', 'haproxy_sysctl' ]
- name: Disable kernel binding non local IP addresses
sysctl: name={{ item }} value=0 reload=yes state=present
with_items:
- net.ipv4.ip_nonlocal_bind
when: not haproxy_k_bind_non_local_ip
tags: [ 'haproxy', 'haproxy_sysctl' ]
- name: Increase the connection tracking table capacity
sysctl: name={{ item }} value={{ haproxy_sysctl_conntrack_max }} reload=yes state=present
with_items:
- net.nf_conntrack_max
when: is_not_debian9
tags: [ 'haproxy', 'haproxy_sysctl' ]

17
tasks/haproxy-ssl.yml Normal file
View File

@ -0,0 +1,17 @@
---
- block:
- name: Install the socat binary needed to talk to the haproxy socket
apt: name=socat state=latest update_cache=yes cache_valid_time=3600
- name: Install a script that refreshes the OCSP configuration and reloads haproxy if needed
template: src=hapos-upd.j2 dest=/usr/local/bin/hapos-upd owner=root group=root mode=0755
- name: Install a cron job that refreshes the OCSP configuration
cron:
name: "Refresh the haproxy OCSP information"
user: root
special_time: daily
job: "/usr/local/bin/hapos-upd --cert {{ haproxy_cert_dir }}/haproxy.pem -v {{ letsencrypt_acme_certs_dir }}/fullchain -s {{ haproxy_admin_socket }} >/var/log/hapos-upd.log 2>&1"
tags: [ 'haproxy', 'letsencrypt', 'ssl', 'ssl_ocsp' ]

View File

@ -1,2 +1,32 @@
--- ---
# tasks file for ansible-role-template - import_tasks: haproxy-service.yml
- import_tasks: haproxy-letsencrypt-acme-sh.yml
when:
- haproxy_letsencrypt_managed
- letsencrypt_acme_sh_install
- import_tasks: haproxy-ssl.yml
when:
- haproxy_letsencrypt_managed
- import_tasks: haproxy-nagios.yml
when:
- nagios_enabled is defined
- nagios_enabled
- name: Ensure that haproxy is enabled and started
service: name=haproxy state=restarted enabled=yes
when: haproxy_enabled
ignore_errors: True
tags: haproxy
- name: Haproxy puts a new rsyslog directive. Restart rsyslog to activate it. Reload is not sufficient
service: name=rsyslog state=restarted
when:
- haproxy_enabled
- install_haproxy is changed
tags: haproxy
- name: Ensure that haproxy is stopped and disabled if needed
service: name=haproxy state=stopped enabled=no
when: not haproxy_enabled
tags: haproxy

571
templates/hapos-upd.j2 Normal file
View File

@ -0,0 +1,571 @@
#!/bin/bash
# HAProxy OCSP Stapling Updater
# Copyright (c) 2015 Pier Carlo Chiodi - http://www.pierky.com
#
# https://github.com/pierky/haproxy-ocsp-stapling-updater
set -o nounset
VERSION="0.4.1-pre1"
PROGNAME="hapos-upd"
if [ -z ${OPENSSL_BIN+x} ]; then
OPENSSL_BIN="openssl"
fi
SOCAT_BIN="socat"
CERT=""
VAFILE=""
HAPROXY_ADMIN_SOCKET_DEFAULT="/run/haproxy/admin.sock"
HAPROXY_ADMIN_SOCKET="$HAPROXY_ADMIN_SOCKET_DEFAULT"
GOOD_ONLY=0
SYSLOG_PRIO=""
DEBUG=0
KEEP_TEMP=0
OCSP_URL=""
OCSP_HOST=""
VERIFY=1
TMP=""
SKIP_UPDATE=0
PARTIAL_CHAIN=""
function Quit() {
if [ $KEEP_TEMP -eq 0 ]; then
if [ -n "$TMP" ]; then
rm -r $TMP &>/dev/null
fi
fi
exit $1
}
function LogError() {
MSG="$1"
if [ -z "$SYSLOG_PRIO" ]; then
echo "$MSG" >&2
else
logger -p "$SYSLOG_PRIO" -s -- "$PROGNAME - $MSG"
fi
echo "$MSG" >>$TMP/log
}
function Error() {
if [ $1 -eq 9 ]; then
MSG="Error: $2"
else
MSG="Error processing '$CERT': $2"
fi
LogError "$MSG"
if [ $1 -eq 9 ]; then
echo "Run $PROGNAME -h for help" >&2
fi
Quit $1
}
function Debug() {
if [ $DEBUG -eq 1 ]; then
echo "$1"
fi
echo "$1" >>$TMP/log
}
function Trap() {
Debug "Aborting"
Quit 9
}
function Usage() {
echo "
HAProxy OCSP Stapling Updater - $VERSION
Copyright (c) 2015 Pier Carlo Chiodi - http://www.pierky.com
https://github.com/pierky/haproxy-ocsp-stapling-updater
Usage:
$PROGNAME [options] --cert crt_full_path
This script extracts and queries the OCSP server present in a
certificate to obtain its revocation status, then updates HAProxy by
writing the '.issuer' and the '.ocsp' files and by sending it the
'set ssl ocsp-response' command through the local UNIX admin socket.
The crt_full_path argument is the full path to the certificate bundle
used in haproxy 'crt' setting. End-entity (EE) certificate plus any
intermediate CA certificates must be concatenated there.
An OCSP query is sent to the OCSP server given on the command line
(--ocsp-url and --ocsp-host argument); if these arguments are missing,
URL and Host header values are automatically extracted from the
certificate.
If the '.issuer' file already exists it's used to build the OCSP
request, otherwise the chain is extracted from crt_full_path and used
to identify the issuer.
Finally, it writes the related '.issuer' and .'ocsp' files and updates
haproxy, using 'socat' and the local UNIX socket (--socket argument,
default $HAPROXY_ADMIN_SOCKET_DEFAULT).
Exit codes:
0 OK
1 openssl certificates handling error
2 OCSP server URL not found
3 string parsing / PEM manipulation error
4 OCSP error
5 haproxy management error
9 program error (wrong arguments, missing dependencies)
Options:
-d, --debug : don't do anything, print debug messages only.
--keep-temp : keep temporary directory after exiting (for
debug purposes).
-g, --good-only : do not update haproxy if OCSP response
certificate status value is not 'good'.
-l, --syslog priority : log errors to syslog system log module.
The priority may be specified numerically
or as a facility.level pair (e.g.
local7.error).
--ocsp-url url : OCSP server URL; use this instead of the
one in the EE certificate.
--ocsp-host host : OCSP server hostname to be used in the
'Host:' header; use this instead of the one
extracted from the OCSP server URL.
--partial-chain : Allow partial certificate chain if at least one certificate
is in trusted store. Useful when validating an intermediate
certificate without the root CA.
-s, --socket file : haproxy admin socket. If omitted,
$HAPROXY_ADMIN_SOCKET_DEFAULT is used by default.
This script is distributed with only one
method to update haproxy: using 'socat'
with a local admin-level UNIX socket.
Feel free to implement other mechanisms as
needed! The right section in the code is
\"UPDATE HAPROXY\", at the end of the script.
-v, --VAfile file : same as the openssl ocsp -VAfile option
with 'file' as argument. For more details:
'man ocsp'.
If file = \"-\" then the chain extracted
from the certificate's bundle (or .issuer
file) is used (useful for OCSP responses
that don't include the signer certificate).
--noverify : Do not verify OCSP response.
-S, --skip-update : Do not notify haproxy of the new OCSP response.
-h, --help : this help."
}
trap Trap INT TERM
TMP="`mktemp -d -q -t $PROGNAME.XXXXXXXXXX`"
# COMMAND LINE PROCESSING
# ----------------------------------
while [[ $# > 0 ]]
do
case "$1" in
-h|--help)
Usage
Quit 0
;;
-d|--debug)
DEBUG=1
;;
--keep-temp)
KEEP_TEMP=1
;;
-g|--good-only)
GOOD_ONLY=1
;;
--noverify)
VERIFY=0
;;
--partial-chain)
PARTIAL_CHAIN="-partial_chain"
;;
-l|--syslog)
if [ $# -le 1 ]; then
Error 9 "mandatory value is missing for $1 argument"
fi
SYSLOG_PRIO="$2"
shift
;;
--ocsp-url)
if [ $# -le 1 ]; then
Error 9 "mandatory value is missing for $1 argument"
fi
OCSP_URL="$2"
shift
;;
--ocsp-host)
if [ $# -le 1 ]; then
Error 9 "mandatory value is missing for $1 argument"
fi
OCSP_HOST="$2"
shift
;;
-c|--cert)
if [ $# -le 1 ]; then
Error 9 "mandatory value is missing for $1 argument"
fi
CERT="$2"
shift
;;
-v|--VAfile)
if [ $# -le 1 ]; then
Error 9 "mandatory value is missing for $1 argument"
fi
VAFILE="$2"
if [ "$VAFILE" == "-" ]; then
VAFILE="$TMP/chain.pem"
else
if [ ! -e "$VAFILE" ]; then
Error 9 "VAfile does not exists: $VAFILE"
fi
fi
shift
;;
-s|--socket)
if [ $# -le 1 ]; then
Error 9 "mandatory value is missing for $1 argument"
fi
HAPROXY_ADMIN_SOCKET="$2"
shift
;;
-S|--skip-update)
SKIP_UPDATE=1
;;
*)
Error 9 "unknown option: $1"
esac
shift
done
Debug "Temporary directory: $TMP"
$OPENSSL_BIN version | grep OpenSSL &>>$TMP/log
if [ $? -ne 0 ]; then
Error 9 "openssl binary not found; adjust OPENSSL_BIN variable in the script"
fi
$SOCAT_BIN -V | grep socat &>>$TMP/log
if [ $? -ne 0 ]; then
Error 9 "socat binary not found; adjust SOCAT_BIN variable in the script"
fi
if [ -z "$CERT" ]; then
Error 9 "certificate not provided (--cert argument)"
fi
# CURRENT RESPONSE EXPIRED?
# ----------------------------------
ISNEW=1
if [ -e $CERT.ocsp ]; then
ISNEW=0
Debug "An OCSP response already exists: checking its expiration."
$OPENSSL_BIN ocsp -respin $CERT.ocsp -text -noverify | \
grep "Next Update:" &>>$TMP/log
if [ $? -eq 0 ]; then
CURR_EXP=`$OPENSSL_BIN ocsp -respin $CERT.ocsp -text -noverify | grep "Next Update:" | cut -d ':' -f 2-`
CURR_EXP_EPOCH=`date --date="$CURR_EXP" +%s`
if [ $? -ne 0 ]; then
Error 3 "can't parse Next Update from current OCSP response"
fi
if [ $CURR_EXP_EPOCH -lt `date +%s` ]; then
Debug "Current OCSP response expiration: $CURR_EXP - expired"
LogError "current OCSP response is expired: please consider running this script more frequently"
else
Debug "Current OCSP response expiration: $CURR_EXP - NOT expired"
fi
fi
fi
# EXTRACT EE CERTIFICATE INFO
# ----------------------------------
# extract EE certificate
$OPENSSL_BIN x509 -in $CERT -outform PEM -out $TMP/ee.pem &>>$TMP/log
if [ $? -ne 0 ]; then
Error 1 "can't extract EE certificate from $CERT"
fi
# get OCSP server URL
if [ -z "$OCSP_URL" ]; then
OCSP_URL="`$OPENSSL_BIN x509 -in $TMP/ee.pem -ocsp_uri -noout`"
if [ $? -ne 0 ]; then
Error 1 "can't obtain OCSP server URL from $CERT"
fi
if [ -z "$OCSP_URL" ]; then
Error 2 "OCSP server URL not found in the EE certificate"
fi
Debug "OCSP server URL found: $OCSP_URL"
else
Debug "Using OCSP server URL from command line: $OCSP_URL"
fi
# check OCSP server URL format (http:// or https://)
echo "$OCSP_URL" | egrep -i "(http://|https://)" &>/dev/null
if [ $? -ne 0 ]; then
Error 3 "OCSP server URL not in http[s]:// format"
fi
# get OCSP server URL host name
if [ -z "$OCSP_HOST" ]; then
OCSP_HOST="`echo "$OCSP_URL" | egrep -i "(http://|https://)" | cut -d'/' -f 3`"
if [ $? -ne 0 -o -z "$OCSP_HOST" ]; then
Error 3 "can't extract hostname from OCSP server URL $OCSP_URL"
fi
Debug "OCSP server hostname: $OCSP_HOST"
else
Debug "Using OCSP server hostname from command line: $OCSP_HOST"
fi
# EXTRACT CHAIN INFO
# ----------------------------------
if [ -e $CERT.issuer ]; then
Debug "Using existing chain ($CERT.issuer)"
# copy .issuer file to temporary chain.pem
cp $CERT.issuer $TMP/chain.pem &>>$TMP/log
if [ $? -ne 0 ]; then
Error 3 "can't copy current chain from $CERT.issuer"
fi
else
Debug "Extracting chain from certificates bundle"
# get EE certificate's fingerprint
FP_EE="`$OPENSSL_BIN x509 -fingerprint -noout -in $TMP/ee.pem`"
if [ $? -ne 0 -o -z "$FP_EE" ]; then
Error 1 "can't obtain EE certificate's fingerprint"
fi
Debug "EE certificate's fingerprint: $FP_EE"
# get BEGIN CERTIFICATE and END CERTIFICATE separators
PEM_BEGIN_CERT="`head $TMP/ee.pem -n 1`"
PEM_END_CERT="`tail $TMP/ee.pem -n 1`"
# get number of certificates in the bundle file
NUM_OF_CERTS=`cat $CERT | grep -e "$PEM_BEGIN_CERT" | wc -l`
if [ $NUM_OF_CERTS -le 1 ]; then
Error 3 "can't obtain the number of certificates in the chain"
fi
Debug "$NUM_OF_CERTS certificates found in the bundle"
# save each certificate in the bundle into $TMP/chain-X.pem
cat $CERT | \
sed -n -e "/$PEM_BEGIN_CERT/,/$PEM_END_CERT/p" | \
awk "/$PEM_BEGIN_CERT/{x=\"$TMP/chain-\" ++i \".pem\";}{print > x;}" &>>$TMP/log
if [ $? -ne 0 ]; then
Error 3 "can't extract certificates from bundle"
fi
# for each certificate that is extracted from the bundle check if
# it's the EE certificate, otherwise uses it to build the chain file
for c in `seq 1 $NUM_OF_CERTS`;
do
# check fingerprint of current and EE certificates
FP="`$OPENSSL_BIN x509 -fingerprint -noout -in $TMP/chain-$c.pem`"
if [ $? -ne 0 -o -z "$FP" ]; then
Error 1 "can't obtain the fingerprint of the certificate n. $c in the bundle"
else
if [ ! "$FP" == "$FP_EE" ]; then
Debug "Bundle certificate n. $c fingerprint: $FP - it's part of the chain"
# current certificate is not the same as the EE; append to the chain
cat $TMP/chain-$c.pem >> $TMP/chain.pem
else
Debug "Bundle certificate n. $c fingerprint: $FP - EE certificate"
fi
fi
done
fi
# check if the EE certificate validates against the chain
$OPENSSL_BIN verify $PARTIAL_CHAIN -CAfile $TMP/chain.pem $TMP/ee.pem &>>$TMP/log
if [ $? -ne 0 ]; then
if [ -e $CERT.issuer ]; then
Error 1 "can't validate the EE certificate against the existing chain; if it has been changed recently consider removing the current $CERT.issuer file and let this script to figure out a new one"
else
Error 1 "can't validate the EE certificate against the extracted chain"
fi
fi
# OCSP
# ----------------------------------
# query the OCSP server and save its response
$OPENSSL_BIN version | grep "OpenSSL 1.0" &>/dev/null
if [ $? -eq 0 ]; then
# OpenSSL 1.0.x
$OPENSSL_BIN ocsp $PARTIAL_CHAIN -issuer $TMP/chain.pem -cert $TMP/ee.pem \
-respout $TMP/ocsp.der -noverify \
-no_nonce -url $OCSP_URL -header "Host" "$OCSP_HOST" &>>$TMP/log
else
$OPENSSL_BIN ocsp $PARTIAL_CHAIN -issuer $TMP/chain.pem -cert $TMP/ee.pem \
-respout $TMP/ocsp.der -noverify \
-no_nonce -url $OCSP_URL -header "Host=$OCSP_HOST" &>>$TMP/log
fi
if [ $? -ne 0 ]; then
Error 1 "can't receive the OCSP server response"
fi
# process the OCSP response
VERIFYOPT=""
if [ $VERIFY -eq 0 ]; then
VERIFYOPT="-noverify"
fi
if [ -z "$VAFILE" ]; then
$OPENSSL_BIN ocsp $PARTIAL_CHAIN $VERIFYOPT -issuer $TMP/chain.pem -cert $TMP/ee.pem \
-respin $TMP/ocsp.der -no_nonce -CAfile $TMP/chain.pem \
-out $TMP/ocsp.txt &>>$TMP/ocsp-verify.txt
else
$OPENSSL_BIN ocsp $PARTIAL_CHAIN $VERIFYOPT -issuer $TMP/chain.pem -cert $TMP/ee.pem \
-respin $TMP/ocsp.der -no_nonce -CAfile $TMP/chain.pem \
-VAfile $VAFILE \
-out $TMP/ocsp.txt &>>$TMP/ocsp-verify.txt
fi
if [ $? -ne 0 ]; then
Error 1 "can't receive OCSP response"
fi
if [ $VERIFY -eq 1 ]; then
Debug "OCSP response verification results: `cat $TMP/ocsp-verify.txt`"
cat $TMP/ocsp-verify.txt | grep "Response verify OK" &>>$TMP/log
if [ $? -ne 0 ]; then
grep "signer certificate not found" $TMP/ocsp-verify.txt &>/dev/null
if [ $? -eq 0 ]; then
Error 4 "OCSP response verification failure: signer certificate not found; try with '--VAfile -' or '--VAfile OCSP-response-signing-certificate-file' arguments"
else
Error 4 "OCSP response verification failure."
fi
fi
fi
Debug "OCSP response: `cat $TMP/ocsp.txt`"
if [ $GOOD_ONLY -eq 1 ]; then
cat $TMP/ocsp.txt | head -n 1 | grep ": good" &>>$TMP/log
if [ $? -ne 0 ]; then
Error 4 "OCSP response, certificate status not good"
fi
fi
# UPDATE HAPROXY
# ----------------------------------
# Status:
# - $TMP/ocsp.der contains the OCSP response, DER format
# - $TMP/ocsp.txt contains the textual OCSP response as produced
# by openssl
# - the OCSP response has been verified against the chain or
# the --VAfile
if [ $DEBUG -eq 0 ]; then
# update .ocsp and .issuer files
cp $TMP/ocsp.der $CERT.ocsp &>>$TMP/log
if [ $? -ne 0 ]; then
Error 5 "can't update $CERT.ocsp file"
fi
if [ ! -e $CERT.issuer ]; then
cp $TMP/chain.pem $CERT.issuer &>>$TMP/log
if [ $? -ne 0 ]; then
Error 5 "can't update $CERT.issuer file"
fi
fi
if [ $SKIP_UPDATE -eq 0 ]; then
if [ $ISNEW -eq 1 ]; then
# no .ocsp file found, maybe it's an initial run
Debug "Reloading haproxy."
service haproxy reload
if [ $? -ne 0 ]; then
Error 5 "can't reload haproxy with 'service haproxy reload'"
fi
else
# update haproxy via local UNIX socket
Debug "Updating haproxy."
echo "set ssl ocsp-response `base64 -w 0 $TMP/ocsp.der`" | $SOCAT_BIN stdio $HAPROXY_ADMIN_SOCKET &>>$TMP/log
if [ $? -ne 0 ]; then
Error 5 "can't update haproxy ssl ocsp-response using $HAPROXY_ADMIN_SOCKET socket"
fi
fi
else
Debug "Not notifying haproxy because skip-update is set."
fi
else
Debug "Debug mode: haproxy update skipped."
fi
# remove temporary files and quit with success
Quit 0
# vim: set tabstop=4 shiftwidth=4 expandtab:

View File

@ -0,0 +1,50 @@
#!/bin/bash
H_NAME="{{ letsencrypt_acme_sh_certs_data_prefix }}"
LE_SERVICES_SCRIPT_DIR=/usr/lib/acme/hooks
LE_CERTS_DIR=/var/lib/acme/live/$H_NAME
LE_LOG_DIR=/var/log/letsencrypt
HAPROXY_CERTDIR=/etc/pki/haproxy
HAPROXY_CERTFILE=$HAPROXY_CERTDIR/haproxy.pem
DATE=$( date )
[ ! -d $HAPROXY_CERTDIR ] && mkdir -p $HAPROXY_CERTDIR
[ ! -d $LE_LOG_DIR ] && mkdir $LE_LOG_DIR
echo "$DATE" >> $LE_LOG_DIR/haproxy.log
{% if letsencrypt_acme_install %}
LE_ENV_FILE=/etc/default/letsencrypt
{% endif %}
{% if letsencrypt_acme_sh_install %}
LE_ENV_FILE=/etc/default/acme_sh_request_env
{% endif %}
if [ -f "$LE_ENV_FILE" ] ; then
. "$LE_ENV_FILE"
else
echo "No letsencrypt default file" >> $LE_LOG_DIR/haproxy.log
fi
echo "Building the new certificate file" >> $LE_LOG_DIR/haproxy.log
cat ${LE_CERTS_DIR}/{fullchain,privkey} > ${HAPROXY_CERTFILE}
chmod 440 ${HAPROXY_CERTFILE}
chgrp haproxy ${HAPROXY_CERTFILE}
echo "Reload the haproxy service" >> $LE_LOG_DIR/haproxy.log
if [ -x /bin/systemctl ] ; then
systemctl reload haproxy >> $LE_LOG_DIR/haproxy.log 2>&1
else
service haproxy reload >> $LE_LOG_DIR/haproxy.log 2>&1
fi
# Run the OCSP stapling script
if [ -x /usr/local/bin/hapos-upd ] ; then
echo "Run the OCSP stapling updater script" >> $LE_LOG_DIR/haproxy.log
/usr/local/bin/hapos-upd --cert {{ haproxy_cert_dir }}/haproxy.pem -v ${LE_CERTS_DIR}/fullchain -s {{ haproxy_admin_socket }} -v - >> $LE_LOG_DIR/haproxy.log 2>&1
else
echo "No OCPS stapling updater script" >> $LE_LOG_DIR/haproxy.log
fi
echo "Done." >> $LE_LOG_DIR/haproxy.log
exit 0

4
templates/lb.cfg.j2 Normal file
View File

@ -0,0 +1,4 @@
# Check the haproxy backends status
command[lb_check_bk_status]=/usr/bin/sudo {{ nagios_local_plugdir }}/check_haproxy_stats -s {{ haproxy_admin_socket }} -w {{ haproxy_nagios_check_w }} -c {{ haproxy_nagios_check_c }}