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.

07 avr. 2010

Nginx - Vhost - php - perl - ssl

banniere-nginx.png
Bon , le billet qui sert de pont de départ ... pas toujours évident sur un blog de s'y retrouver .. sans sommaire ...
Voici donc le sommaire concernant les billets de Nginx

Installation , et utilisation de base

Installer Nginx manuellement , options de compilation , découvrez les emplacements des différents fichiers de configuration , démarrage automatique , options de base ect ...

Gérer les pages PHP et PERL

Nginx seul c'est bien , mais pouvoir l'utiliser avec php perl et .. tant d'autres , c'est mieux , dans ce billet , est éxpliqué la mise en place de php et perl en fast-cgi , et la liaison avec nginx .

SSL , vhost , et autres petites options

Mise en place de pages sécurisées par ssl , création de certificats , redirection automatique , mod_rewrite , compatibilité avec certains framework qui ont besoin du mod rewrite commezend framework , symphony , etc ...

09 mar. 2010

Maj sécurité Apache

Multiples vulnérabilités du serveur HTTP Apache

faille-majeure-securite-systeme-dns-L-1.png

Les failles

Plusieurs vulnérabilités dans les modules mod_isapi, mod_headers et mod_proxy_ajp permettent à un utilisateur malveillant de provoquer un déni de service ou d'exécuter du code à distance.

Versions concernées :

2.2.0 à 2.2.14.

Solution

Mettre à jour apache , pour installer la version 2.2.15

Sources

http://www.certa.ssi.gouv.fr/site/CERTA-2010-AVI-112/CERTA-2010-AVI-112.html

Ne tardez donc pas à faire vos maj .

Bonne journée , Ch.

20 janv. 2010

Apprendre par la pratique

Wablab

wablab-logo.png

C'est un site qui vous propose des "défis" , d'exploitation de failles , les plus connus pour commencer ...
Aprés une inscription très rapide , sur le site , vous voilà prêt à relever les 7 premières épreuves .

Chaque épreuve pointe une faille bien précise , de plus , ils ne vous laissent pas sans information , ce qui les rends toutes largement faisable , sans non plus passer trop de temps à tâtonner .
Voilà de bon exercices qui par la pratique vous permet d'apprendre à connaître donc à éviter ce genre de failles dans vos développements , de plus je trouve ce genre d'exercice très addictifs .

Seul le niveau "Beginner" avec ses 7 défis est actuellement en ligne , il faut espérer l'ouverture des niveaux "Intermédiaire" et "Expert" dans un future proche , en attendant , allez y , faites chauffer votre matière grise et surtout votre bon sens

Voici le lien

www.wablab.com



21 sept. 2009

big brother is watching you !

Surveiller la disponibilité d'un site camera.jpg

Rien de bien extraordinaire , mais le principe m'amuse alors autant partager , et pourquoi pas récupérer des idées dans vos commentaires .

Le principe :

L'on me demande un moyen de vérifier si des sites distants sont disponibles , ou non de façon permanente .

J'ai donc décidé de développer un bout de script , qui ne fait ni plus ni moins que:

  • Tester la résolution DNS ( Type A uniquement pour le moment )
  • récupérer les en-têtes envoyés par le serveur en réponse à une requête HTTP.
  • Récupération de ce qui m'intéresse , à savoir le code de status http , et le temps qu'a mis la réponse à me parvenir .

Ensuite , grâce à ces informations , je peux faire toute sorte de graphiques m'indiquant la disponibilité du/des sites surveillés dans le temps .

Exemple :

viewer.png

Le code

pour récupérer des données le plus proche de la réalité possible Comme j'ai dit plus haut , le code est très simple et rapide à mettre en place , il vous faudra cependant activer curl pour l'utiliser .

Vérifier la résolution de nom

Pour cette étape , nous allons utiliser la fonction native à php 4&5 checkdnsrr() :

checkdnsrr($adresse,"A");

Récupérer les entêtes HTTP

Pas besoin de récupérer la totalité , celà surchargerais inutilement les deux cotés , une première solution consiste à utiliser la fonction get_headers() en natif dans php5 :

get_headers  ( "htp://".$adresse  , true );

L'inconvénient de cette fonction , est qu'elle ne possède pas de timeout hormis celui de php , qui est bien trop long et peut pratique .
Les tests avec cette fonction se sont donc révélé bon , mais a généré l'augmentation de la consommation de mémoire de façon spectaculaire , voyez vous-même !

eluna.png

La solution , est donc d'utiliser curl , qui s'est révélé bien plus efficace , voici donc la fonction de remplacement de get_headers() ( sources fr2.php.net )

function get_headers_curl($url)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL,            $url);
    curl_setopt($ch, CURLOPT_HEADER,         true);
    curl_setopt($ch, CURLOPT_NOBODY,         true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT,        20);

    $r = curl_exec($ch);
    $r = split("\n", $r);
    return $r;
} 

Si vous ne pouvez pas utiliser curl , vous pouvez aussi utiliser la fonction suivante , qui se base sur fsockopen() en natif dans php .

Note:merci à clochix de m'avoir signalé cette autre alternative.

/*
 * Fonction de remplacement de get_headers 
 * Attention à ne pas mettre les http:// devant l'adresse 
 */
function get_headers_sock($adresse, $timeout=15){
    //Tableau qui vat contenir les entêtes
    $Theaders   =   array();
    $UserAgent = "User-Agent : Mozilla/4.0 (compatible; MSIE 5.0; Windows 95)";

    $fp = fsockopen($adresse, 80, $errno, $errstr, $timeout);
    if (!$fp) {
        echo "$errstr ($errno)<br />\n";
    } else {
        $out = "HEAD / HTTP/1.1\r\n";
        $out .= "Host: $adresse\r\n";
        $out .= "$UserAgent\r\n";
        $out .= "Connection : Close\r\n\r\n";

        fwrite($fp, $out);
        while (!feof($fp)) {
            $Theaders[] =   fgets($fp, 128);
        }
        fclose($fp);
    }


    //renvoi des entêtes
    if(!empty($Theaders)){
        return $Theaders;
    }else{
        return null;
    }
    
}

Cette fonction retourne tout comme get_headers() un tableau avec une ligne = une entrée .
Le timeout est ici défini à 20 secondes , car je juge que si l'entête n'est toujours pas là en 20secondes , le site n'est pas navigable normalement , donc indisponible .

Le code de status HTTP

Après avoir reçut la réponse HTTP , l'on décortique l'entrée du tableau n°0 qui correspond normalement à : HTTP/1.1 <code http> <etat>

Une petite expression régulière plus tard , on se retrouve avec le code http seul

//récupération des entêtes
$header = get_headers_curl($adresse);
if(!empty($header){

  //le code http sera disponible dans $retour[1]
  preg_match('#([0-9]{3})#',$header[0],$retour);

}

Le temps pour faire tout ça !

Il ne nous reste plus qu'à récupérer le temps total pour effectuer ces opérations ( temps d'envoi + temps de réception ).
Rien de plus simple , comme dans beaucoup d'exemple sur le net , nous utiliserons microtime()

$debut  = microtime(true);
$header =   get_headers_curl("http://".$adresse) ;
//ou  get_headers_sock($adresse); ( attention à ne pas mettre http devant )
$fin    =  microtime(true);
$temps  =   round($fin-$debut,4);

Et voilà , nous avons toutes les infos nécessaire , ne reste plus qu'à les enregistrer .. en base , fichier .. comme vous voulez .

Exemple de code complet :

<?php
//definition du user_agent

$useragent="Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";

//adresse à surveiller ( sans les http )
$adresse = "lindev.fr";
        
$retour = array(0,0);
$dns        =   0;
$temps      =   0;

//test du dns
if( checkdnsrr($adresse,"A")){


	$debut  = microtime(true);
        $header =   get_headers_curl("http://".$adresse) ;
        $fin    =  microtime(true);
        $temps  =   round($fin-$debut,4);

        preg_match('#([0-9]{3})#',$header[0],$retour);
        $dns    =   1;

}else{
        echo "Résolution DNS impossible";   
}

//enregistrement des resulats
$result =   array(
   "DNS"           =>     $dns,
   "reponse"       =>  $retour[1],
   "tempsReponse"  =>  $temps
);

//si timeout , impose le code 503
if($result['reponse'] == NULL){
   $result['reponse'] = 503;
}

//enregistrement dans la base de données / ou fichier
        ... ... ...
function get_headers_curl($url)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL,            $url);
    curl_setopt($ch, CURLOPT_HEADER,         true);
    curl_setopt($ch, CURLOPT_NOBODY,         true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT,        20);

    $r = curl_exec($ch);
    $r = split("\n", $r);
    return $r;
} 
?>

Mise en place

Il faut maintenant lancer ce script de façon régulière afin de surveiller le site de façon précise .
Je vais donc dans mon cas le lancer toutes les 10min , via cron

Vous voilà avec une table qui se remplis toute seule des informations citées ci-dessus , il ne vous reste plus qu'à en faire ce que bon vous semble avec .

Pour info , les graphiques générés ci-dessus sont fait via JPgraph qui fera pourquoi pas l'objet d'un billet pour son initiation .

Conclusion

Voilà une approche d'un outil qui je pense peut s'avérer utile , à partir du moment ou les sites surveillés ne se trouvent pas au même endroit que ce script de monitoring .
Ensuite , n'a de limite que votre imagination pour le faire évoluer , pour par exemple lancer des alertes au bout de X minutes de non-disponibilités , tester d'autres type de services comme les mails , ftp ect ...

J'attends vos retours et vos critiques sur l'idée ou la façon de faire afin d'améliorer tout ça .

A bientôt ,

Ch.

- page 1 de 2