Archives pour mars, 2009

Nginx : authentification utilisateur

C’est bien beau l’authentification utilisateur avec Nginx mais on a encore besoin de htpasswd ! Alors pour pas recompiler Apache et s’endormir pendant 15 minutes (voire beaucoup plus), on peut se rabattre sur cette ligne :

perl -le 'print crypt ("mot_de_passe", "grain_de_sel")'

Les entrées du fichier des mots de passe ont également le même format :

<user1>:<password>:<comment>
<user2>:<password>

Le commentaire n’est pas obligatoire. Pour le reste, cf la documentation du module.

Pour une authentification avancée du type LDAP ou MySQL, un module d’authentification PAM existe et est téléchargeable sur cette page.

FreeBSD 64 bits : statistiques réseaux par SNMP

Les statistiques des interfaces réseaux par SNMP (net-mgmt/net-snmp) ne sont pas directement fonctionnels sous FreeBSD 64 bits (aucun problème pour les OS 32 bits) :

# snmpwalk -v 2c -c public 127.0.0.1 .1.3.6.1.2.1.31.1.1.1
IF-MIB::ifName.1 = STRING: re0
IF-MIB::ifName.2 = STRING: lo0

Seuls les noms d’interfaces sont renvoyés. Les compteurs systèmes étant codés sur 64 bits et n’étant pas supportés par défaut par SNMP, il faut recompiler le port avec l’option WITH_MFD_REWRITES pour y remédier:

# make deinstall && make clean
# make -DWITH_MFD_REWRITES
# make install
# /usr/local/etc/rc.d/snmpd restart

SNMP peut alors reporter l’ensemble des statistiques :

# snmpwalk -v 2c -c public 127.0.0.1 .1.3.6.1.2.1.31.1.1.1
IF-MIB::ifName.1 = STRING: re0
IF-MIB::ifName.2 = STRING: lo0
IF-MIB::ifInMulticastPkts.1 = Counter32: 368234
IF-MIB::ifInMulticastPkts.2 = Counter32: 0
IF-MIB::ifInBroadcastPkts.1 = Counter32: 0
IF-MIB::ifInBroadcastPkts.2 = Counter32: 0
IF-MIB::ifOutMulticastPkts.1 = Counter32: 0
IF-MIB::ifOutMulticastPkts.2 = Counter32: 0
IF-MIB::ifOutBroadcastPkts.1 = Counter32: 0
IF-MIB::ifOutBroadcastPkts.2 = Counter32: 0
IF-MIB::ifHCInOctets.1 = Counter64: 86439204
IF-MIB::ifHCInOctets.2 = Counter64: 1355164
IF-MIB::ifHCInUcastPkts.1 = Counter64: 549735
IF-MIB::ifHCInUcastPkts.2 = Counter64: 11430
IF-MIB::ifHCInMulticastPkts.1 = Counter64: 368234
IF-MIB::ifHCInMulticastPkts.2 = Counter64: 0
IF-MIB::ifHCInBroadcastPkts.1 = Counter64: 0
IF-MIB::ifHCInBroadcastPkts.2 = Counter64: 0
IF-MIB::ifHCOutOctets.1 = Counter64: 41458007
IF-MIB::ifHCOutOctets.2 = Counter64: 1355164
IF-MIB::ifHCOutUcastPkts.1 = Counter64: 121314
IF-MIB::ifHCOutUcastPkts.2 = Counter64: 11430
IF-MIB::ifHCOutMulticastPkts.1 = Counter64: 0
IF-MIB::ifHCOutMulticastPkts.2 = Counter64: 0
IF-MIB::ifHCOutBroadcastPkts.1 = Counter64: 0
IF-MIB::ifHCOutBroadcastPkts.2 = Counter64: 0
IF-MIB::ifHighSpeed.1 = Gauge32: 1000
IF-MIB::ifHighSpeed.2 = Gauge32: 0
IF-MIB::ifPromiscuousMode.1 = INTEGER: false(2)
IF-MIB::ifPromiscuousMode.2 = INTEGER: false(2)
IF-MIB::ifConnectorPresent.1 = INTEGER: true(1)
IF-MIB::ifConnectorPresent.2 = INTEGER: true(1)
IF-MIB::ifAlias.1 = STRING:
IF-MIB::ifAlias.2 = STRING:
IF-MIB::ifCounterDiscontinuityTime.1 = Timeticks: (0) 0:00:00.00
IF-MIB::ifCounterDiscontinuityTime.2 = Timeticks: (0) 0:00:00.00

Le monitoring réseau par SNMP est alors possible.

Nginx : scripts CGI & Perl

Suite à la configuration du support PHP sous Nginx, il m’a été nécessaire de pouvoir exécuter les scripts CGI et Perl. Comme avec PHP, la configuration s’articule autour de FastCGI côté Nginx. Par contre côté interpréteur, spawn-fcgi ne peut pas être utilisé. Un interpréteur Perl doit donc être « préchargé » et en attente des requêtes clients par la socket dont il a au préalable créée. Socket au choix : UNIX ou TCP si l’exécution doit être locale ou déportée.

Voici le script de l’interpréteur perl-wrapper.pl :

#!/usr/bin/perl

use FCGI;
use Socket;
use POSIX qw(setsid);

require 'syscall.ph';

&daemonize;

END() { } BEGIN() { }
*CORE::GLOBAL::exit = sub { die "fakeexit\nrc=".shift()."\n"; };
eval q{exit};
if ($@) {
	exit unless $@ =~ /^fakeexit/;
};

&main;

sub daemonize() {
    chdir '/'                 or die "Can't chdir to /: $!";
    defined(my $pid = fork)   or die "Can't fork: $!";
    exit if $pid;
    setsid                    or die "Can't start a new session: $!";
    umask 0;
}

sub main {
        $socket = FCGI::OpenSocket( "/tmp/fcgi-perl.sock", 10 );
        $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket );
        if ($request) { request_loop()};
            FCGI::CloseSocket( $socket );
}

sub request_loop {
        while( $request->Accept() >= 0 ) {

           $stdin_passthrough ='';
           $req_len = 0 + $req_params{'CONTENT_LENGTH'};
           if (($req_params{'REQUEST_METHOD'} eq 'POST') && ($req_len != 0) ){
                my $bytes_read = 0;
                while ($bytes_read < $req_len) {
                        my $data = '';
                        my $bytes = read(STDIN, $data, ($req_len - $bytes_read));
                        last if ($bytes == 0 || !defined($bytes));
                        $stdin_passthrough .= $data;
                        $bytes_read += $bytes;
                }
            }

            if ( (-x $req_params{SCRIPT_FILENAME}) &&  #can I execute this?
                 (-s $req_params{SCRIPT_FILENAME}) &&  #Is this file empty?
                 (-r $req_params{SCRIPT_FILENAME})     #can I read this file?
            ){
		pipe(CHILD_RD, PARENT_WR);
		my $pid = open(KID_TO_READ, "-|");
		unless(defined($pid)) {
			print("Content-type: text/plain\r\n\r\n");
                        print "Error: CGI app returned no output - \
Executing $req_params{SCRIPT_FILENAME} failed !\n";
			next;
		}
		if ($pid > 0) {
			close(CHILD_RD);
			print PARENT_WR $stdin_passthrough;
			close(PARENT_WR);

			while(my $s = <KID_TO_READ>) { print $s; }
			close KID_TO_READ;
			waitpid($pid, 0);
		} else {
	                foreach $key ( keys %req_params){
        	           $ENV{$key} = $req_params{$key};
                	}
        	        # cd to the script's local directory
	                if ($req_params{SCRIPT_FILENAME} =~ /^(.*)\/[^\/]+$/) {
                        	chdir $1;
                	}

			close(PARENT_WR);
			close(STDIN);
			#fcntl(CHILD_RD, F_DUPFD, 0);
			syscall(&SYS_dup2, fileno(CHILD_RD), 0);
			#open(STDIN, "<&CHILD_RD");
			exec($req_params{SCRIPT_FILENAME});
			die("exec failed");
		}
            }
            else {
                print("Content-type: text/plain\r\n\r\n");
                print "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not \
exist or is not executable by this process.\n";
            }
        }
}

Ensuite, le script de startup fcgi-perl.sh :

#!/bin/sh
#
# Boris HUISGEN <bhuisgen@hbis.fr>
#

PROVIDES=fcgi-perl

WRAPPER=/usr/local/etc/nginx/perl-wrapper.pl
SOCKET=/tmp/fcgi-perl.sock
SOCKET_USER=www
SOCKET_GROUP=www
SOCKET_PERMS=600

# script

cmd=$1

pcgi_start(){
 echo "Starting $PROVIDES..."
 perl -I /usr/local/lib/perl5/site_perl/5.8.9/mach/sys/ $WRAPPER &

 until [ -S $SOCKET ]
 do
 sleep 1
 done

 chown $SOCKET_USER:$SOCKET_GROUP $SOCKET
 chmod $SOCKET_PERMS $SOCKET
}

pcgi_stop(){
 echo "Stopping $PROVIDES..."
 pkill -f $WRAPPER
 rm $SOCKET
}

pcgi_restart(){
 pcgi_stop
 pcgi_start
}

pcgi_status(){
 [ -S $SOCKET ] && echo "$PROVIDES running" || echo "$PROVIDES NOT running"
}

pcgi_help(){
 echo "Usage: $0 {start|stop|restart|status}"
}

case ${cmd} in
[Ss][Tt][Aa][Rr][Tt]) pcgi_start;;
[Ss][Tt][Oo][Pp]) pcgi_stop;;
[Rr][Ee][Ss][Tt][Aa][Rr][Tt]) pcgi_restart;;
[Ss][Tt][Aa][Tt][Uu][Ss]) pcgi_status 0;;
*)      pcgi_help ;;
esac

Le support CGI/Perl au niveau de Nginx est calqué sur celui de PHP : un mapping d’extension + un nom de socket :

server {
   listen  80;
   server_name     cgi.site.fr;</pre>
location / {
      root /home/www/site.fr/cgi-bin;
      index index.cgi;
   }

   location ~ \.cgi$ {
      fastcgi_pass   unix:/tmp/fcgi-perl.sock;
      fastcgi_index  index.cgi;
      fastcgi_param  HTTP_ACCEPT_ENCODING gzip,deflate;
      fastcgi_param  SCRIPT_FILENAME  /home/www/site.fr/cgi-bin/$fastcgi_script_name;
      include        fastcgi_params;
   }
}

Pour détecter tout problème d’exécution, lancez le wrapper Perl en console. Vérifiez que le module Perl perl-sys-syscall (devel/p5-Sys-Syscall) est installé. Si le wrapper ne parvient pas à trouver le fichier syscall.ph, il faut le générer :

# cd /usr/include
# h2ph * sys/*

Le fichier va être ajouté dans un nouveau répertoire, pour ma part /usr/local/lib/perl5/site_perl/5.8.9/mach/sys/, que j’ajoute en option à Perl dans le script de startup. Ces modifications seront à effectuer en concordance avec votre installation Perl.

Nginx : FastCGI PHP par socket UNIX

L’exécution de scripts PHP avec le serveur Web Nginx s’effectue par le biais du module FastCGI. Ce module assure la liaison entre Nginx et un interpréteur PHP par socket UNIX ou TCP (ce dernier cas permet une exécution sur un serveur distinct). L’utilitaire spawn-fcgi fourni avec le serveur Lighttpd assure le lien entre FastCGI et les interpréteurs PHP ; il ouvre la socket et préloade un ou plusieurs interpréteurs php-cgi.

Dans le cas où votre serveur Nginx est en place avec son module FastCGI, il vous reste à créer le script de lancement des interpréteurs PHP. Voici le mien, fcgi-php.sh, qui s’appuie sur une socket UNIX, afin de traiter localement les scripts PHP :

#!/bin/sh
# Boris HUISGEN <bhuisgen@hbis.fr>
# - convert to use UNIX socket support
# - force socket permissions

PROVIDES=php-cgi

SPAWN_FCGI=/usr/local/bin/spawn-fcgi
PHP_CGI=/usr/local/bin/php-cgi
PHP_CGI_CHILDREN=4

SOCKET=/tmp/fcgi-php.sock
SOCKET_USER=www
SOCKET_GROUP=www
SOCKET_PERMS=600

# script

cmd=$1

pcgi_start() {
   echo "Starting $PROVIDES..."
   $SPAWN_FCGI -s $SOCKET -u $SOCKET_USER -g $SOCKET_GROUP -f $PHP_CGI -C $PHP_CGI_CHILDREN
   chown $SOCKET_USER:$SOCKET_GROUP $SOCKET
   chmod $SOCKET_PERMS $SOCKET
}

pcgi_stop() {
   echo "Stopping $PROVIDES..."
   killall $PROVIDES
   rm $SOCKET
}

pcgi_restart(){
   pcgi_stop
   pcgi_start
}

pcgi_status() {
   [ -S $SOCKET ] && echo "$PROVIDES running" || echo "$PROVIDES NOT running"
}

pcgi_help() {
   echo "Usage: $0 {start|stop|restart|status}"
}

case ${cmd} in
   [Ss][Tt][Aa][Rr][Tt]) pcgi_start;;
   [Ss][Tt][Oo][Pp]) pcgi_stop;;
   [Rr][Ee][Ss][Tt][Aa][Rr][Tt]) pcgi_restart;;
   [Ss][Tt][Aa][Tt][Uu][Ss]) pcgi_status 0;;
   *) pcgi_help ;;
esac

Attention, la socket doit posséder les permissions suffisantes pour que Nginx puisse y écrire.

Au niveau de Nginx, voici la configuration globale du module FastCGI, fichier fastcgi_params :

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

Finalement, la configuration de l’hôte virtuel :

server {
   listen  80;
   server_name     www.site.fr

   location / {
      root           /home/www/site.fr/html;
      index          index.php;
   }

   location ~ \.php$ {
      fastcgi_pass   unix:/tmp/fcgi-php.sock;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  /home/www/site.fr/www/html$fastcgi_script_name;
      include        fastcgi_params;
   }
}

L’option location permet de mapper les fichiers d’extension .php au module FastCGI, plus précisément à la socket de spawn-fcgi. Dans le cas d’une connexion socket TCP, il faut modifier le script de démarrage fcgi-php.sh et l’option fastcgi_pass de l’hôte (tutorial)

Au final, en reproduisant cette configuration, un même serveur ou encore site peut exécuter en parallèle des scripts PHP 4 & 5, si chaque interpréteur possède une socket dédiée. D’autres langages sont d’ailleurs possibles, c’est pourquoi mon prochain billet s’attardera au support des scripts CGI / Perl.

Nouvel hosting

Ce blog est à présent hébergé sur un nouveau serveur dédié. J’en ai profité pour enregistrer mon nom de domaine hbis.fr.

La nouvelle URL d’accès du blog devient donc :

http://blog.hbis.fr

La redirection depuis l’ancien host sera grillée d’ici quelques jours…

Packet Filter : protection d'attaque SSH par brute force

La technique pour éviter les attaques SSH par brute force avec PacketFilter consiste à ajouter les IP fautives dans une table et d’exclure les IP qui y sont contenues pour les connexions SSH ou de façon globale (petit vilain).

Procédure chef :

  1. déclarer la table qui va contenir les IP bannies
  2. bloquer en début de filtrage les connexions entrantes de ces IP.
  3. ajouter les options de collecte des états des connexions sur les règles que l’on souhaite protéger du bruteforce.
  4. vider la table, en tâche cron par exemple.

Exemple d’un parefeu PF rudimentaire, fait main (70% main droite), autorisant le trafic ICMP et les connexions SSH :

#
# pf.conf
#

# macros
ext_if = "re0" # ou mi0, fa0, sol0 çà dépend
myip = "XX.XX.XX.XX" # comme c'est trop secure

# tables
table <bruteforce> persist {}

# options
set block-policy drop
set skip on lo0 # non ce n'est pas la0
set limit { states 20000, frags 5000, src-nodes 2000 }

# normalization
scrub in all fragment reassemble
scrub all reassemble tcp
scrub in all random-id

#
# rules
#

block all
block quick from <bruteforce>
antispoof quick for lo0

pass out inet proto tcp all flags S/SA keep state
pass out inet proto udp all keep state

pass in on $ext_if inet proto icmp from any to any keep state
pass out on $ext_if inet proto icmp from any to any keep state

pass in on $ext_if inet proto tcp from any to any port 22 flags S/SA keep state (source-track rule, max-src-nodes 8, max-src-conn 8, max-src-conn-rate 10/60, overload <bruteforce> flush global)

Dans le cas où un client se  connecte 10 fois par minute, il est banni et bloqué pour toute nouvelle connexion. Il ne reste plus que de lancer la commande permettant de supprimer les adresses IP enregistrées, ici pour le jour précédent :

# /usr/local/sbin/expiretable -t 24h bruteforce

Certificat SSL multi-domaines gratuit signé par CACert.org

Un certificat multi-domaines (à ne pas confondre avec un certificat wildcard) permet d’utiliser un certificat SSL pour différents noms de domaines et sous-domaines. Ceux-ci peuvent alors être hébergés sur un même serveur Web à adresse IP unique. Pour rappel, lors de l’établissement de la connexion sécurisée, le serveur Web envoie  le certificat SSL alors même que le client n’a pas encore envoyé sa requête. Les certificats au standard X509 ne présentant qu’un unique nom de domaine ou sous-domaines (cas d’un certificat wildcard) par le biais du champ CN (Common Name) : le serveur Web ne peut donc que se mapper  localement à un unique site Web.

Pourtant, une extension existe depuis quelques années (pour ma part je l’ai appris en 2005) et permet de déclarer dans le certificat plusieurs domaines. Celle-ci associe au certificat une liste de domaines : le champ SubjectAltName. Le serveur Web est alors capable de gérer plusieurs domaines, le certificat pouvant être mappé aux hôtes virtuels de cette liste. Seul bémol, la liste des domaines est présente en clair dans le certificat (il n’y aurait pas de magie).

Action ! Pour obtenir un certificat SSL gratuit multi-domaines signé par l’autorité CACert (http://www.cacert.org/) :

  1. créez votre compte sur le site CACert en déclarant votre mailbox et votre nom de domaine
  2. générez la demande de certificat (CSR) grâce à ce script : http://svn.cacert.org/CAcert/Software/CSRGenerator/csr .
  3. copiez la demande sur le formulaire du site et enregistrez le certificat.
  4. installez le certificat racine de CACert si votre navigateur ne l’intègre pas : http://www.cacert.org/index.php?id=3.

Exemple d’exécution du script pour mon domaine hbis.fr (notez bien que je répète le domaine pour l’extension) :

Private Key and Certificate Signing Request Generator
This script was designed to suit the request format needed by
the CAcert Certificate Authority. www.CAcert.org

Short Hostname (ie. imap big_srv www2): hbis
FQDN/CommonName (ie. www.example.com) : hbis.fr
Type SubjectAltNames for the certificate, one per line. Enter a blank line to finish
SubjectAltName: DNS:hbis.fr
SubjectAltName: DNS:www.hbis.fr
SubjectAltName: DNS:admin.hbis.fr
SubjectAltName: DNS:mail.hbis.fr
SubjectAltName: DNS:smtp.hbis.fr
SubjectAltName: DNS:
Running OpenSSL...
Generating a 2048 bit RSA private key
.......................+++
..........................+++
writing new private key to '/root/hbis_privatekey.pem'
-----
Copy the following Certificate Request and paste into CAcert website to obtain a Certificate.
When you receive your certificate, you 'should' name it something like hbis_server.pem

Plus d’informations sur le support de l’extension SubjectAltName par les navigateurs Web : http://wiki.cacert.org/wiki/VhostTaskForce.

Script cleanup pour Firebird sous Mac OS X

Ce script vous sera utile dans le cas d’une désinstallation / réinstallation du serveur de base de données Firebird sous Mac OS X :

#!/bin/sh

SystemStarter stop 'Firebird Server'
dscl localhost -delete /Local/Default/Users/firebird
dscl localhost -delete /Local/Default/Groups/firebird

if [ -f "/Library/StartupItems/Firebird" ]; then
   rm -fr /Library/StartupItems/Firebird
fi

if [ -f "/Library/LaunchDaemons/org.firebird.gds.plist" ]; then
   launchctl unload /Library/LaunchDaemons/org.firebird.gds.plist
   rm /Library/LaunchDaemons/org.firebird.gds.plist
fi

rm -fr /Library/Frameworks/Firebird.framework
rm -fr /Library/Receipts/Firebird*.pkg

Rsync 3 pour Mac OS X

Voici un guide complet d’installation de la dernière version 3 de l’utilitaire rsync sous Mac OS X, incluant les patchs nécessaires à la gestion des flags du filesystem HFS+. Il est disponible à cet endroit : http://www.bombich.com/mactips/rsync.html

Le lancement du démon Rsync par xinetd (Mac OS X 10.4) est expliqué ici :

http://maxpowerindustries.com/docs/rsyncdaemon.html.

Dans le cas d’une version 10.5, il faut créer un script launchd :

http://voice.firefallpro.com/2006/03/backing-up-with-launchd-and-rsync-in.html.

Telnet sécurisé

Le package openssl contient un client telnet sécurisé, très utile pour tester les connexions aux serveurs HTTPS, SMTPS, IMAPS et j’en passe.

# openssl s_client -connect localhost:993

Cet outil permet également d’inspecter les certificats SSL.

Ah oui, la bonne nouvelle c’est qu’il est installé sur tous les systèmes Unix, Linux et Mac OS (OpenSSL oblige).

Haut de page