lindev.fr

Aller au contenu | Aller au menu | Aller à la recherche

28 déc. 2016

HAProxy LetsEncrypt et Varnish en front .. rien que ça

L'objectif final

Avoir l'ensemble de ses sites accessibles en https, avec des certificats letsencrypt ( gratuits et surtout renouvelés automatiquement ) et un serveur de cache ( varnish ) pour augmenter la capacité de charge ( bref optimiser les perfs des sites ) pour enfin passer le relais au backend ( Apache, Nginx .. peu importe ).

network_diagram_user_haproxy_varnish_apache.png

le hic

Il y en a toujours un ... et pas des moindres cette fois, varnish ne gère pas l'https !

La solution - TLS Termination Proxy

L'utilisation d'un proxy ( HAProxy ) pour gérer les transactions Https, puis relayer en clair les requêtes vers varnish qui à son tour si nécessaire va relayer les requêtes au(x) serveur(s) ( Apache/Nginx/... )

Plusieurs avantages à cette solution ( Terminaison SSL ), soulager les serveurs finaux ( Apache/Nginx ... ) de la gestion du SSL et surtout pour nous admins, nous simplifier la tâche pour ce qui est de la gestion des certificats.

Dans ce billet

Dans ce billet, je ne vais parler que de HAProxy et letsencrypt pour commencer, varnish étant un assez gros morceau, il fera l'objet d'un second billet.

Mise en place de la situation

Vous pouvez utiliser un seul et même serveur pour héberger l'ensemble de ces services ... mais franchement ça ne devrait jamais être le cas ! Donc pour ce billet nous allons partir du principe que nous possédons 2 serveurs .

Le premier pour héberger HAProxy et Varnish ( 10.0.0.1 ) et le second pour héberger le serveur HTTP ( 10.0.0.2 )

Les deux serveurs sont sous Debian 8 (Jessie).

Serveur HAProxy

Commençons par Installer HAProxy ( plus quelque trucs utils dans ce "tuto" ) ... qui se fait simplement par

apt install haproxy bc vim 

Commençons par copier le fichier de configuration par defaut

cd /etc/haproxy/
cp haproxy.cfg haproxy.cfg.orig


Maintenant nous pouvons éditer le fichier /etc/haproxy/haproxy.cfg

vim /etc/haproxy/haproxy.cfg

Remplacer tout le contenu par :

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3

        maxconn 2048
        tune.ssl.default-dh-param 2048

defaults
        #log    global
        log     /dev/log local0
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

        option forwardfor
        option http-server-close


#################################

frontend www-http
        bind *:80
        mode http

        #option httpclose
        option forwardfor
        option httplog
        option dontlognull

        reqadd X-Forwarded-Proto:\ http
        default_backend www-backend


frontend www-https
        bind *:443 ssl crt /etc/haproxy/certs/
        mode http
        reqadd X-Forwarded-Proto:\ https

        #option httpclose
        option forwardfor
        option httplog
        option dontlognull

        acl letsencrypt-acl path_beg /.well-known/acme-challenge/
        use_backend letsencrypt-backend if letsencrypt-acl

        default_backend www-backend


backend www-backend

        #option httpclose
        option forwardfor

        http-request set-header X-Forwarder-Port %[dst_port]
        redirect scheme https if !{ ssl_fc }
        server www-1 10.0.0.2:80 check

backend letsencrypt-backend
        server letsencrypt 127.0.0.1:54321


listen stats *:1936
        stats enable
        stats uri /
        stats hide-version
        stats auth username:password

Comme vous l'avez certainement remarqué, nous allons chercher les certificats dans le répertoire /etc/haproxy/certs/ qui n'éxiste pas encore.
Créons le

sudo mkdir /etc/haproxy/certs
sudo chmod -R go-rwx /etc/haproxy/certs

Voilà pour HAProxy, nous verrons ensuite dans l'interface de statistics si tout est ok .

Letsencrypt et certbot

Pour la génération des certificats, nous allons utiliser l'outil certbot .
Pour l'installer nous allons devoir activer ( si ce n'est déjà fait ) le dépôt BlackPorts de Debian

sudo echo 'deb http://ftp.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/blackports.list
apt update

On install certbot depuis ce dépôt

apt-get install certbot -t jessie-backports

Afin d'initialiser letsencrypt, nous allons le lancer une première fois

certbot certonly

Celà ne va pas aboutir, c'est normal, c'est juste pour qu'il crée les répertoires de base.

Celà ne vous a pas échappé, le port 80 est déjà utilisé par HAProxy, et ne peu donc être utilisé par Letsencrypt pour générer ou renouveler un certificat.
L'idée de couper HAProxy le temps de cette action est inconcevable, nous allons simplement utiliser un autre port. ( ici : 54321 )

Pour ne pas avoir à le spécifier à chaque commande, nous allons mettre en place un petit fichier de configuration pour définir quelques paramètres qui ne changent pas . ( ce qui va nous permettre de raccourcir nos lignes de commandes plus tard ).

sudo cd /etc/letsencrypt/ && vim cli.ini

Voici le contenu

# This is an example of the kind of things you can do in a configuration file.
# All flags used by the client can be configured here. Run Certbot with
# "--help" to learn more about the available options.

# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 4096

# Uncomment and update to register with the specified e-mail address
email = admin@your.net

# Uncomment to use a text interface instead of ncurses
text = True

standalone-supported-challenges = http-01

Afin de tester l'ensemble de notre configuration, nous allons générer notre premier certificat ( manuellement ) .
Imaginons que le domaine soit "lindev.fr" :

sudo certbot certonly --standalone --http-01-port 54321 -d lindev.fr -d www.lindev.fr
sudo cat /etc/letsencrypt/live/lindev.fr/fullchain.pem /etc/letsencrypt/live/lindev.fr/privkey.pem > /etc/haproxy/certs/lindev.fr.pem
sudo service haproxy restart

Bien entendu, il faut que le nom de domaine "lindev.fr" et "www.lindev.fr" pointent sur l'ip de mon serveur ( HAProxy ).

HAProxy est maintenant prêt . Je suppose que le backend ( Nginx ou Apache ) est déjà configuré avec un vhost écoutant sur le port 80 pour le domaine "lindev.fr et www.lindev.fr".

Et voilà le résultat. Capture_d_e_cran_2017-01-03_a__21.15.37.png

Renouvellement automatique

Nous allons maintenant faire en sorte que les certificats LetsEncrypt se régénèrent automatiquement avant expiration et ce tant qu'à faire sans intervention nécessaire d'une admin sys sur le serveur HAProxy.

Le script

Pour celà j'ai concocter un petit script batch générique qui fait très bien le travail.

sudo mkdir /root/scripts & cd /root/scripts
vim /root/scripts/renew_all.sh

Le voici

#!/bin/bash

#Configuration variables
certbot_bin="/usr/bin/certbot"
haproxy_pem_path="/etc/haproxy/certs"
http_01_port='54321'
exp_limit=30

#Then, create domain.pem containing fullchain et privkey for haproxy
for domainconf in $(ls /etc/letsencrypt/renewal/); do
        domain=${domainconf%.conf}

        cert_file="${haproxy_pem_path}/${domain}.pem"
        exp=$(date -d "`openssl x509 -in $cert_file -text -noout|grep "Not After"|cut -c 25-`" +%s)
        datenow=$(date -d "now" +%s)
        days_exp=$(echo \( $exp - $datenow \) / 86400 |bc)

        if [ "$days_exp" -gt "$exp_limit" ] ; then
                echo "[${domain}] : The certificate is up to date, no need for renewal ($days_exp days left)."
                #exit 0;
        else
                echo "The certificate for $domain is about to expire soon. Starting Let's Encrypt (HAProxy:$http_01_port) renewal script..."
                $certbot_bin certonly --standalone --renew-by-default --http-01-port $http_01_port -d ${domain} -d www.${domain}
                cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > ${haproxy_pem_path}/${domain}.pem
        fi
done

# At the end, reload haproxy
echo "$(date +%c) Reload haproxy"
/usr/sbin/service haproxy reload

Les deux seuls paramètres que vous pourriez vouloir changer sont :

  • http_01_port='54321'
  • exp_limit=30



A savoir, le port utiliser pour la génération des certificats, et la limite (en jours) à partir de laquelle vous souhaitez régénérer un certificat .

sudo chmod +x /root/scripts/renew_all.sh
sudo -s
crontab -e

On ajoute cette ligne dans la crontab ( afin de vérifier chaque jour les certificats à régénérer )

@daily /bin/bash /root/scripts/renew_all.sh 2>&1 | mail -s "[SSL Lindev] Vérification des certificats SSL HAProxy" admin@lindev.fr

Et voilà chaque jour, le script sera exécuté et vous recevrez un petit mail résumant le temps restant pour chaque certificat et potentiellement les logs de régénération s'il y en a eu.

Nouveau domaine à gérer

Imaginons que vous souhaitez gérer un autre nom de domaine, vous devrez la première fois créer le nouveau certificat.
Voici un petit script pour vous simplifier la vie.

sudo vim /root/script/new_cert.sh

le voici

#!/bin/bash

#Configuration variables
certbot_bin="/usr/bin/certbot"
haproxy_pem_path="/etc/haproxy/certs"
http_01_port='54321'
domain=$1

$certbot_bin certonly --standalone --renew-by-default --http-01-port $http_01_port -d ${domain} -d www.${domain}
cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > ${haproxy_pem_path}/${domain}.pem

# At the end, reload haproxy
echo "$(date +%c) Reload haproxy"
service haproxy restart

On le rend exécutable

sudo chmod +x /root/scripts/new_cert.sh

A partir de là pour générer un nouveau certificat (qui sera par la suite automatiquement renouvelé), il vous suffit (en root) d'executer ce script comme ceci

cd /root/scripts
./new_cert.sh mon_nouveau_domaine.com

Et voilà , le certificat sera généré, renouvelé automatiquement et pris en charge par HAProxy .

Conclusion

Nous avons mis en place notre front (HAProxy) qui se charge de la partie SSL puis redirige les flus vers un ou plusieurs backends .
L'avantage de cette configuration et la simplicité de gestion des certificats, même avec plusieurs backends physiquement différents ( load balancing, différents services etc ... ).

Prochaine étape intercaler entre HAProxy et le backend, un serveur de cache ( Varnish ) .

Dans un prochain billet... N'hésitez pas à commenter.

Ch.

26 janv. 2016

Php7, installation/compilation ... et rollback

Les articles sur Php7 ne cessent de culpabiliser les développeurs sous php5.x .. , il est plus .. performant, rapide, des nouvelles fonctionnalités qui se rapprochent de python ( ok ... ça c'est un troll ), il est ... bref .. il faut le tester pour se faire une idée, et surtout vérifier si les projets tournent dessus dans devoir faire face à une horde de logs de type Notice , Deprecated, ou encore pire .. Fatal Error

On fait quoi

Vous avez de beaux projets qui tournent sous une version de Php stable 5.X et .. malgré l'adage .. on ne change pas quelque chose qui marche, vous trépigniez d'impatience de voir ce qu'ils donnent avec la dernière monture de la société Zend .

Ok .. mais sans tout casser s'il vous plait.

On va donc voir comment installer ( par compilation ) php7 afin de pouvoir l'utiliser (ou pas) projet par projet. Ou plutôt vhost par vhost .

Installer PHP7

J'ai pris comme base, un serveur tout neuf .. à poil .. Debian Jessie sur la plateforme Google.

Commençons par installer quelques paquets nécessaires pour le bon déroulement de cet article ( et de la compilation de php ).

sudo apt install vim bzip2 build-essential libxml2-dev libc-client-dev libbz2-dev libkrb5-dev libmcrypt-dev librecode-dev

Puis classiquement nous téléchargeons les sources de php

cd /usr/local/src
wget http://us2.php.net/distributions/php-7.0.2.tar.bz2
tar xjf php-7.0.2.tar.bz2
cd php-7.0.2

Nous allons créer un petit fichier de config pour notre version de php ( activer ou pas certains modules, définir l'emplacement de php... )

vim myConfig

Voici le contenu ( nous allons l'installer dans /usr/local/php7 )

#!/bin/sh

mkdir -p /usr/local/php7

export OPTIM=-02
./configure --prefix=/usr/local/php7 \
        --enable-fpm \
        --with-xsl \
        --with-gettext \
        --enable-mbstring \
        --enable-mbregex \
        --disable-debug \
        --enable-ftp \
        --with-mcrypt \
        --enable-zip \
        --enable-calendar \
        --enable-exif \
        --with-pdo-mysql \
        --with-gettext \
        --with-zlib \
        --with-bz2 \
        --enable-inline-optimization \
        --enable-pcntl \
        --enable-opcache \
        --with-mysqli \
        --with-zlib \
        --with-jpeg-dir \
        --with-gd \
        --with-freetype-dir=DIR \
        --with-imap-ssl \
        --with-imap \
        --with-curl \
        --enable-bcmath \
        --with-kerberos \
        --enable-sysvsem \
        --enable-sysvshm \
        --enable-sockets \
        --enable-gd-native-ttf \
        --with-openssl \

Rendons ce script exécutable et ... c'est parti

chmod +x myConfig
./myConfig

Si tout ce passe bien, nous pouvons lancer les deux dernières commandes pour installer php7

make -j `cat /proc/cpuinfo | grep processor | wc -l` 
sudo make install

Configuration

Et bien oui .. vous ne passez pas par les paquets de la distrib .. vous avez donc un poil plus de boulot . Commençons par récupérer le configuration par défaut ( vous 'adapterez au besoin plus tard, ce point sort du cadre de cet article )

cd /usr/local/php7/etc/
cp php-fpm.conf.default php-fpm.conf

Malgré tout, vérifiez bien à dé-commenter la ligne suivante ( vous verrez plus tard pourquoi )

pid = run/php7-fpm.pid
Pools

Enfin il nous faut également créer le pool par défaut de php-fpm

cd php-fpm.d
cp www.conf.default www.conf

Idem il nous faut dé-commencter/éditer/modifier deux trois lignes ( vous pouvez reprendre les valeurs ci-dessous, si vous avez une installation de base . Notamment pour l'utilisateur utilisé pour interpréter vos scripts php )

user = www-data
group = www-data
listen = /var/run/php7-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
php.ini

Aller .. encore un fichier de config à gérer.
On va prendre la version "prod" disponible dans les sources que nous avons téléchargées .

cp /usr/local/src/php-7.0.2/php.ini-production /usr/local/php7/lib/php.ini

Ajoutons le module opcache dans la foulé

vim /usr/local/lib/php.ini

Ajouter en fin de fichier

zend_extension=opcache.so

Ready ! ? ... non pas encore

Lancer php-fpm au démarrage

Commençons par créer notre fichier d'init ( modèle dispo dans les sources également. Je précise ce point car certains articles semblent sortir ces scripts de leur chapeau, sans explication ! )
Les modèles se trouvent dans : /usr/local/src/php-7.0.2/sapi/fpm

vim /etc/init.d/php7-fpm

Le contenu :

#! /bin/sh
### BEGIN INIT INFO
# Provides:          php7-fpm
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts php7-fpm
# Description:       starts the PHP FastCGI Process Manager daemon
### END INIT INFO
php_fpm_BIN=/usr/local/php7/sbin/php-fpm
php_fpm_CONF=/usr/local/php7/etc/php-fpm.conf
php_fpm_PID=/usr/local/php7/var/run/php7-fpm.pid
php_opts="--fpm-config $php_fpm_CONF"
wait_for_pid () {
        try=0
        while test $try -lt 35 ; do
                case "$1" in
                        'created')
                        if [ -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                        'removed')
                        if [ ! -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                esac
                echo -n .
                try=`expr $try + 1`
                sleep 1
        done
}
case "$1" in
        start)
                echo -n "Starting php-fpm "
                $php_fpm_BIN $php_opts
                if [ "$?" != 0 ] ; then
                        echo " failed"
                        exit 1
                fi
                wait_for_pid created $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed"
                        exit 1
                else
                        echo " done"
                fi
        ;;
        stop)
                echo -n "Gracefully shutting down php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -QUIT `cat $php_fpm_PID`
                wait_for_pid removed $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed. Use force-exit"
                        exit 1
                else
                        echo " done"
                       echo " done"
                fi
        ;;
        force-quit)
                echo -n "Terminating php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -TERM `cat $php_fpm_PID`
                wait_for_pid removed $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed"
                        exit 1
                else
                        echo " done"
                fi
        ;;
        restart)
                $0 stop
                $0 start
        ;;
        reload)
                echo -n "Reload service php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -USR2 `cat $php_fpm_PID`
                echo " done"
        ;;
        *)
                echo "Usage: $0 {start|stop|force-quit|restart|reload}"
                exit 1
        ;;
esac

On le rend exécutable et crée les liens de démarrage

sudo chmod 755 /etc/init.d/php7-fpm
insserv php7-fpm

Puis on crée le fichier pour initd

vim /lib/systemd/system/php7-fpm.service

Voici le contenu

[Unit]
Description=The PHP 7 FastCGI Process Manager
After=network.target

[Service]
Type=simple
PIDFile=/usr/local/php7/var/run/php7-fpm.pid
ExecStart=/usr/local/php7/sbin/php-fpm --nodaemonize --fpm-config /usr/local/php7/etc/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

On active le service

systemctl enable php7-fpm.service
systemctl daemon-reload
systemctl enable php7-fpm

On lance ENFIN php7-fpm

service php7-fpm start

Petit check

ps -xa | grep php-fpm

qui doit donner un truc du genre:

  861 ?        Ss     0:00 php-fpm: master process (/usr/local/php5/etc/php-fpm.conf)                
  862 ?        S      0:00 php-fpm: pool www                                                         
  863 ?        S      0:00 php-fpm: pool www

Et voilà, vous avez la toute dernière monture de php disponible .
Ok mais ... comment l'utiliser dans le vhost.

Nginx

Installons rapidement Nginx

sudo apt-get install nginx

éditons le vhost par défaut pour prendre en compte notre installation de php

vim /etc/nginx/sites-enabled/default

Et on dé-commente pour avoir ceci

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
        #
        #       # With php5-cgi alone:
        #       fastcgi_pass 127.0.0.1:9000;
        #       # With php5-fpm:
                fastcgi_pass unix:/var/run/php7-fpm.sock;
        }

On demande à Nginx de passer les requêtes pointant les fichiers php au socket : /var/run/php7-fpm.sock

Test

Un rapide script de test ...LE phpinfo() :)

vim /var/www/html/test.php

Puis on colle le code suivant

<?php

echo phpinfo();

Rendez-vous sur l'url de votre serveur http://x.x.x.x/test.php

Et là ... victoire ..

Capture_d_e_cran_2016-01-26_a__22.00.30.png

Conclusion

Vous avez compris comment ajouter une version de php à votre serveur ( en plus de la version des dépôts ). Vous pouvez comme celà installer n'importe quelle version de php et utiliser l'une ou l'autre au besoin, en spécifiant le bon socket dans votre vhost .

N'hésitez pas à laisser un commentaire si vous avez des questions .

Bonne nuit.

26 avr. 2015

Jessie, la bonne nouvelle du jour

2015.04.25 8 Jessie

Ça y est, après de longs mois de développement des équipes Debian, voilà la nouvelle monture disponible "Debian Jessie".

Merci aux développeurs pour ce travail formidable .