Latest Entries »

Récemment, je cherchais une solution libre pour faire proprement et efficacement des diagrammes de séquence de message pour un rapport. Qui dit diagrammes dit généralement clicodrômes chronophages, ennuyeux, et où l’on passe perd son temps à peaufiner des détails cosmétiques.

Lorsque j’ai lu la documentation de mscgen (pour Message Sequence Chart GENerator) que je vais vous présenter ultérieurement, non seulement j’ai compris que j’avais affaire à une merveille respectueuse du principe KISS, mais qu’il existait également des possibilités d’interfaçage intéressantes.

Et le meilleur concurrent de la souris est…

… la combinaison d’un clavier et d’un langage efficace et simple à prendre en main, lisible et éditable à la fois par un humain et une machine! Pour vous montrer la puissance de mscgen, entrons directement dans la matière avec la source d’un petit exemple (qui est loin d’exploiter la totalité les fonctionnalités) qui illustre le mécanisme de retransmission sur rejet utilisé par le protocole LAPB:

msc {
    arcgradient = 10;
    tx[label="Transmitter"], rx[label="Receiver"];
    tx => rx [label="(I,0)"];
    tx -x rx [label="(I,1)"];
    ...;
    tx => rx [label="(I,2)"];
    rx => tx [label="(REJ,1)", textbgcolor="#FFAF5F"];
    tx => rx [label="(I,1)"];
    tx => rx [label="(I,2)"];
}

Ci dessous, on trouve le résultat associé à la source précédente

Diagramme généré par MSCGEN

Mscgen vous permet de produire vos graphiques aux formats png, svg, eps ou ismap sous Windows et Linux (il est même dans le dépôt main de Debian). La source MSC peut être fournie via l’entrée standard ou par un fichier. Enfin, il est possible de modifier la police par défaut.

Le seul inconvénient de ce logiciel est qu’il n’est utilisable qu’en ligne de commande, au grand dam de nos amis Windowsiens. Je vous invite à consulter la page web du développeur pour consulter la documentation du langage MSC et vous faire une idée plus complète des possibilités offertes. Pour le téléchargement, les liens sont disponibles en bas de la page de documentation.

Intégration à des préprocesseurs

Le choix de créer un langage simple et exploitable à la fois par un humain et une machine a permet le développement facile de « préprocesseurs » et de générateurs de trace. L’exemple le plus pratique que j’ai en tête est pcap2msc qui permet de convertir un fichier PCAP (trace d’échanges réseau) en une source MSC. Le résultat du traitement n’est pas parfait, mais la correction est facile et le gain de temps par rapport à l’utilisation une solution graphique est non négligeable, surtout quand les échanges sont longs.

Lors de la phase de conception, de débuggage et de documentation d’un logiciel, on peut utiliser le langage MSC pour aider à journaliser et rendre facilement lisibles les communications entre différents composants/threads/processus/nœuds d’une application. Si j’ai un peu de temps libre, ou le besoin de réaliser un tel générateur, je vous tiendrai au courant en temps voulu.

Récemment, j’ai eu besoin de générer des écrans à afficher sur une gameboy advance. On pourrait se dire « oui, c’est facile, tu n’as qu’à faire chauffer imagemagick pour les conversions », mais ce génial outil ne supporte pas le mode d’affichage de la console. En effet, Nintendo a utilisé l’espace de couleur RGB15 Big endian (tant qu’à faire, c’est plus amusant).

Du coup, j’ai le petit programme suivant en C89 pour l’occasion. Il permet de convertir une image bitmap de dimensions 240×160 (résolution de la GBA) avec des couleurs dans l’espace RGB24 vers un fichier RAW (les pixels écrits en dur dans le fichier sans en-tête) encodé avec le bon espace (le format binaire est décrit dans les sources).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Screen size for a GBA */
#define GBA_WIDTH    240
#define GBA_HEIGHT    160

typedef unsigned char	uint8;
typedef unsigned short	uint16;

const int BmpHeaderSize = 54;

/* BMP file info */
typedef struct
{
    char ID[2];
    long FileSize;
    long RESERVED;
    long BitmapDataOffset;
    long BitmapHeaderSize;
    long Width;
    long Height;
    short NrOfPlanes;
    short BitsPerPixel;
    long Compression;
    long BitmapDataSize;
    long HorizontalResolution;
    long VerticalResolution;
    long Colors;
    long ImportantColors;
} BmpHeader;

typedef struct
{
    uint8 lsb;
    uint8 msb;
} TwoBytes;

/* Convert 24bit RGB to 15BPP Big endian format */
TwoBytes rgb_to_gba_be ( uint8 r, uint8 g, uint8 b)
{
    TwoBytes resp;

    /* GBA Pixel are Big endian and described as follow :
     *
     *        -------LSB-------   -------MSB-------
     * Color  G G G B   B B B B   0 R R R   R R G G
     * Order  2 1 0 4   3 2 1 0   X 4 3 2   1 0 4 3
     *
     */

    /* Initialize pixel */
    resp.msb = 0;
    resp.lsb = 0;

    /* Red component */
    resp.msb = resp.msb | ( (r>>1) & 0x7C ) ;

    /* Green component */
    resp.msb = resp.msb | ( (g>>6) & 0x03 ) ;
    resp.lsb = resp.lsb | ( (g<<5) & 0xE0 ) ;

    /* Blue component */
    resp.lsb = resp.lsb | ( (b>>3) & 0x1F ) ;

    return resp;
}

int main( int argc, char** argv)
{
    FILE *pInFile, *pOutRaw;

    BmpHeader* imgProps = NULL;

    uint8 r=0,g=0,b=0;
    TwoBytes gbapix;
    int line, col;
    long i;
    const long image_sz = GBA_HEIGHT * GBA_WIDTH * 2;

    /* Check args  */
    if( argc != 3 )
    {
        printf( "Usage: %s infile outfile\n", argv[0]);
        exit(1);
    }

    /* Open source file */
    pInFile = (FILE *) fopen( argv[1], "rb");
    if( pInFile == NULL )
    {
        printf( "Unable to open input file '%s' for reading.\n", argv[1]);
        exit(1);
    }

    /* Open target file */
    pOutRaw = (FILE *) fopen( argv[2], "wb");
    if( pOutRaw == NULL )
    {
        printf( "Unable to open output file '%s' for writing.\n", argv[2]);
        fclose(pInFile);
        exit(2);
    }

    /* Check source file */
    imgProps = (BmpHeader*)malloc(sizeof(BmpHeader));

    fread( &(imgProps->ID[0]),            1, 1, pInFile);
    fread( &(imgProps->ID[1]),             1, 1, pInFile);
    fread( &(imgProps->FileSize),             4, 1, pInFile);
    fread( &(imgProps->RESERVED),             4, 1, pInFile);
    fread( &(imgProps->BitmapDataOffset),         4, 1, pInFile);
    fread( &(imgProps->BitmapHeaderSize),         4, 1, pInFile);
    fread( &(imgProps->Width),             4, 1, pInFile);
    fread( &(imgProps->Height),             4, 1, pInFile);

    fread( &(imgProps->NrOfPlanes),         2, 1, pInFile);
    fread( &(imgProps->BitsPerPixel),         2, 1, pInFile);

    fread( &(imgProps->Compression),         4, 1, pInFile);
    fread( &(imgProps->BitmapDataSize),        4, 1, pInFile);
    fread( &(imgProps->HorizontalResolution),    4, 1, pInFile);
    fread( &(imgProps->VerticalResolution),     4, 1, pInFile);
    fread( &(imgProps->Colors),             4, 1, pInFile);
    fread( &(imgProps->ImportantColors),         4, 1, pInFile);

    if(     imgProps->ID[0] != 'B' || imgProps->ID[1] != 'M' ||
        imgProps->Width != 240 || imgProps->Height != 160 ||
        imgProps->Compression != 0 || imgProps->BitsPerPixel != 24 )
    {
        printf("*** Invalid input image! ***\n");
        printf("Signature: '%c%c' Expected: 'BM'.\n", imgProps->ID[0], imgProps->ID[1] );
        printf("Width: %ld Expected: %d\n", imgProps->Width, GBA_WIDTH);
        printf("Height: %ld Expected: %d\n", imgProps->Height, GBA_HEIGHT);
        printf("Compression: %s Expected: no\n", (imgProps->Compression != 0)?"yes":"no");
        printf("Color depth: %hd Expected: %d\n", imgProps->BitsPerPixel , 24);
        fclose(pInFile);
        fclose(pOutRaw);
        exit(3);
    }

    /* Fill the output with the appropriate size */
    fseek( pOutRaw, 0, SEEK_SET);
    for ( i=0; i<image_sz; ++i)
        fputc( 0x00, pOutRaw);
    fseek( pOutRaw, 0, SEEK_SET);

    /* Convert image and write to target */
    fseek( pInFile, BmpHeaderSize, SEEK_SET);

    for( line=0; line<GBA_HEIGHT ; ++line)
    {
        fseek( pOutRaw, (GBA_HEIGHT-1-line)*GBA_WIDTH*2, SEEK_SET);

        for( col=0; col<GBA_WIDTH ; ++col)
        {
            b=fgetc( pInFile);
            g=fgetc( pInFile);
            r=fgetc( pInFile);

            gbapix = rgb_to_gba_be ( r, g, b);
            fputc( gbapix.lsb, pOutRaw);
            fputc( gbapix.msb, pOutRaw);
        }
    }

    fclose(pInFile);
    fclose(pOutRaw);
    free(imgProps);
    return 0;
}

Le code travaille ligne à ligne (attention au format BMP qui stocke les lignes dans l’ordre inversé!), et convertit chaque pixel à la volée. Il est à noter que ce code a été conçu pour mon usage spécifique (vérification du format du fichier et de la taille). La taille prise en charge peut être rendue dynamique moyennant un peu de code supplémentaire. Si ça peut vous être utile, servez-vous et happy hacking ^.^

Vous ne supportez pas de devoir stocker les secrets de vos utilisateurs en clair dans vos fichiers sip.conf sous asterisk?

Pas de soucis, il est en effet possible de stocker les secrets au format MD5. En effet, SIP prévoit que l’on puisse effectuer une authentification de type Digest similaire à celle présente dans HTTP.

Cependant, n’oubliez pas de bien tester la compatibilité avec les clients de votre parc avant d’effectuer un déploiement complet: bien que l’authentification par Digest soit présente dans la spécification de SIP (RFC 3261), on n’est jamais à l’abri de surprises.

Dans un premier temps, il faut définir le realm qui va servir pendant l’authentification. Le realm doit être globalement unique, selon la RFC 3261. Pour la suite de l’exemple, je vais supposer que le realm est siptests.

Dans le fichier sip.conf, allez dans la section [global] décommentez et éditez (ou ajoutez le cas échéant) la ligne avec realm pour y mettre le realm que vous avez choisi:

[global]
...
realm=siptests

Ensuite, rendez vous dans la section où sont stockés vos utilisateurs.

Une entrée d’utilisateur va ressembler plus ou moins à la structure proposée ci-dessous:

[1337](default-phone)
    callerid=Leet user <1337>
    mailbox=1337
    secret=pastressecret
    username=1337

La forme en clair du hash à stocker est la suivante: <username>:<realm>:<secret> soit pour mon cas d’exemple 1337:siptests:pastressecret . Sous linux, vous pouvez générer très simplement le hash grace à la commande suivante:

$ echo -n "1337:siptests:pastressecret" | md5sum
bac2cb492e4eb751544add8b5a2a3d24  -
$

Veillez bien à ne pas oublier le paramètre -n pour ne pas ajouter un retour à la ligne. Dans le cas contraire, le hash calculé serait invalide.

Maintentant que vous avez votre hash, supprimez la ligne secret du profil et ajoutez la ligne md5secret comme suit:

[1337](default-phone)
    callerid=Leet user <1337>
    mailbox=1337
    md5secret=bac2cb492e4eb751544add8b5a2a3d24
    username=1337

Il ne vous reste plus qu’a recharger asterisk et vérifier que le client se connecte bien.

Au cours de mes différentes expérimentations, j’ai voulu renforcer l’authentification de certains de mes services en m’appuyant sur une infrastructure à clés publiques dont je disposais déjà. Étant donné que la configuration du VirtualHost sur Apache n’a pas forcément été simple à mettre en place, je vous propose par la suite un template de Virtualhost que j’ai conçu. Les aspects relatifs à la gestion d’une PKI ou de la génération de certificats ne sera pas traitée dans ce billet.

Prérequis

La présente configuration suppose que vous disposiez au préalable:

  • d’un serveur Apache fonctionnel avec un virtualhost qui vous permettra de accueillir les utilisateurs dont l’authentification a échoué
  • des modules apache suivants (liste non exhaustive): mod_ssl, mod_rewrite, mod_headers
  • de certificats SSL serveurs et clients valides.

Gestion des erreurs

Un des problèmes principaux de l’authentification SSL repose sur le fait que le client se retrouve sur une page d’erreur du navigateur et non sur une page d’erreur de votre site web. La configuration suivante vous permettra de rediriger le client vers une autre page lui proposant éventuellement d’autres modes d’authentification (forte de préférence). La redirection mise en place est de type HTTP/307 et est complètement standard.

Pour notre exemple, je vous fournis une mire d’échec très simpliste en PHP qui permettra au client de relancer la procédure d’authentification vers le bon site une fois les problèmes corrigés de son côté. Pour la suite de ce billet, on supposera cette page accessible via l’URL suivante: https://login.yourdomain.com/sslfail.php

<html>
    <head>
        <title>FAIL</title>
    </head>
    <body>
        <p>SSL authentication failed. <?php
            if(isset($_GET["from"]))
            {
                echo("<a href=\"https://". $_GET["from"] .".yourdomain.com/\">Try Again.</a>");
            }
        ?></p>
    </body>
</html>

Le fichier VirtualHost

Pour la suite, on va considérer que le service à protéger sera accessible aux clients sur secureservice.yourdomain.com.

<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName secureservice.yourdomain.com
        DocumentRoot /var/secureservice-wwws

        # Configuration SSL côté serveur
        SSLEngine on
        SSLCertificateFile    /path/to/ca/certs/secureservice.pem
        SSLCertificateKeyFile /path/to/ca/private/secureservice.key
        SSLCertificateChainFile /path/to/ca/keystore/ca.crt

        # Configuration SSL côté client
        SSLCACertificateFile /path/to/ca/certs/ca.pem
        SSLCARevocationFile /path/to/ca/crl/ca-crl.crl
        SSLVerifyDepth  10

        <IfModule mod_rewrite.c>
            # If mod_rewrite is available, redirect clients on failure
            SSLVerifyClient optional

            <IfModule mod_headers.c>
                Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
                Header set Pragma "no-cache"
                Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
            </IfModule>
            RewriteEngine On
            RewriteCond %{SSL:SSL_CLIENT_VERIFY} !^SUCCESS$
            RewriteRule .* https://login.yourdomain.com/sslfail.php?from=secureservice [r=307]
        </IfModule>
        <IfModule !mod_rewrite.c>
            # If mod_rewrite is not available, just deny clients on failure
            SSLVerifyClient require
        </IfModule>

        BrowserMatch "MSIE [2-6]" \
            nokeepalive ssl-unclean-shutdown \
            downgrade-1.0 force-response-1.0
        # MSIE 7 and newer should be able to use keepalive
        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
    </VirtualHost>
</IfModule>

Concernant les attributs côté client, je tiens à vous préciser quelque points: en plus de la configuration classique du certificat serveur, 3 lignes sont impératives pour mettre en place l’authentification SSL:

  • La ligne qui dit au serveur où chercher les certificats des CA qui ont signé les certificats client (SSLCACertificateFile). Le chemin doit impérativement pointer sur un fichier encodé au format PEM. Si vous avez plusieurs CA à prendre en compte, concaténez vos différents fichiers PEM et faites pointer cette ligne sur le fichier résultant.
  • La ligne qui indique combien de niveaux de validation sont supportés pour les certificats clients (SSLVerifyDepth).  Si la valeur associée vaut zéro, le certificat client doit être auto-signé. Si la valeur vaut 1, le certificat doit être validé par le certificat d’une autorité présente dans le fichier des autorité client. Si la valeur vaut 2 ou plus, le certificat peut avoir été signé par une autorité intermédiaire valide. Avec 2, le client peut avoir été signé par une CA signée par une CA présente dans le fichier des autorités.
  • La ligne qui va spécifier le comportement à tenir en cas d’échec. Lorsque SSLVerifyClient est positionné sur require, la négociation SSL/TLS sera annulée si le certificat de l’utilisateur n’est pas validé par le serveur. Le client sera informé par son navigateur que l’opération a échoué. Si par contre SSLVerifyClient est positionné sur optional, et que le certificat de l’utilisateur n’est toujours pas valide, la connexion s’établit quand même, mais il sera, dans notre cas, redirigé vers la page d’échec (http://login.tourdomain.com/sslfail.php?from=secureservice) par le biais d’une réponse HTTP/307. À ce sujet, il est très important d’ajouter le module headers à apache, car les navigateurs ont tendance à mettre en cache les redirections. Si une redirection est mise en cache, votre client pourrait croire que son authentification échoue en permanence, ce qui n’est pas souhaitable.

La spécification d’une liste de révocation de certificats n’est pas toujours nécessaire, mais il reste préférable de l’utiliser car elle vous permettra d’assurer une gestion des droits d’accès plus propre. La ligne avec SSLCARevocationFile doit pointer sur un fichier qui contient la concaténation de toutes les listes de révocation des autorités client encodées au format PEM.

Parmi les utilisateurs d’applications .Net sous linux, rien n’est plus frustrant que l’erreur suivante:

Missing method System.Type::op_Inequality(Type,Type) in assembly /usr/lib/mono/2.0/mscorlib.dll, referenced in assembly /usr/lib/mono/gac/gtk-sharp/2.12.0.0__35e10195dab3c99f/gtk-sharp.dll

La raison fréquente d’une telle erreur est que vous tentez de lancer une application .Net 4.0 dans un environnement .Net 2.0

Que faire face à ceci? Vous avez déjà essayé d’ajouter –runtime=4.0 dans votre commande, mais mono prétend de ne posséder cette version, comme il vous l’indique avec cet avertissement:

WARNING: The runtime version supported by this application is unavailable.
Using default runtime: v2.0.50727

Je viens de trouver qu’il ne faut pas utiliser –runtime=4.0 mais –runtime=v4.0.30319 sur debian avec mono 2.10.8.
Bonne chance pour la suite.

Le gestionnaire de la mémoire est assez puissant sous Linux, mais sa configuration présente quelques limitations sur les systèmes avec une quantité de RAM restreinte (<1Go).

En effet, par défaut, le gestionnaire de mémoire va recourir très trop rapidement à l’utilisation du swap, et ce bien qu’il reste au moins 50% de RAM utilisable sur la machine. Ce comportement par défaut n’est pas souhaitable, car la RAM est beaucoup plus rapide et moins sensible aux écritures qu’un disque dur ou un SSD. Sur les systèmes avec une quantité de RAM plus conséquente, ceci n’aura pas d’impact.

Comment ça se soigne docteur?

Tout d’abord il faut avoir les privilèges root pour effectuer les opérations suivantes.

On va reconfigurer la stratégie d’allocation en définissant le seuil de pagination à 90% (ça permettra de laisser un peu de place pour un chouilla de cache, de buffers, et/ou de pagination du swap.
Ajoutez la ligne suivante dans le fichier /etc/sysctl.conf, puis sauvez.

vm.swappiness = 10

Pour appliquer la stratégie définie précédemment, exécutez la commande suivante:

sysctl -p /etc/sysctl.conf

Maintenant que vous avez appliqué la stratégie, il faut chasser les données déjà présentes dans le swap. Pour cela, il y a deux cas:

Si la somme de la mémoire utilisée et du cache est inférieure à la quantité de mémoire totale , on désactive le swap, puis on le réactive avec la commande suivante:

swapoff -a && swapon -a

Sinon, vous avez gagné soit un prétexte pour rédémarrer vos applications qui consomment beaucoup de mémoire, soit pour rédémarrer votre machine.

Attention! La technique présentée dans cette brève ne vous dispense en aucun cas d’assurer une sécurisation correcte de votre site et/ou de vos applications web.

Un jour, je me suis rendu compte que des gars pas très malins s’amusaient à scanner des listes de versions vulnérables d’outils. J’ai également remarqué que ces scans étaient réalisés à l’aide d’outils tout fait qui n’ont pas honte de s’annoncer avec un beau User-Agent. N’étant pas spécialement amateur de ce genre de méthodes, j’ai décidé de blacklister ces outils avec un  joli fichier htaccess dont voici un extrait:

SetEnvIfNoCase User-Agent ZmEu getout
SetEnvIfNoCase User-Agent Xenu getout
SetEnvIfNoCase User-Agent dragostea getout
SetEnvIfNoCase User-Agent Morfeus getout
order allow,deny
allow from all
deny from env=getout
deny from 58.218.0.0/16
# et ainsi de suite...

Ce fichier .htaccess est situé à la racine de mon site. La variable getout correspond à un groupe de visiteur qui ont un User-Agent douteux et qui ont l’honneur de se faire expulser de manière systématique.

Nota: Ce blog est hébergé par WordPress, l’astuce décrite ci-dessus n’est donc pas appliquée. Allez plutôt casser vos propres jouets ;-þ

Introduction

Le pré-requis pour ce tutoriel est une connaissance basique d’Apache 2, et des virtualhosts.

J’ai récemment fait l’acquisition d’une caméra de surveillance IP, et je voulais pouvoir y accéder depuis l’extérieur sans que l’on ne puisse m’observer.

Mon réseau domestique est structuré en plusieurs VLAN. 3 de ces VLANs nous intéresseront pour la suite de cet article:

  • Le VLAN Interconnexion Internet:  zone où mon modem et mon firewall sont connectés.
  • Le VLAN DMZ: tous mes équipements devant être accessibles depuis l’Internet y sont connectés.
  • Le VLAN Serveurs internes: tous les équipements dont j’ai besoin, mais qui ne doivent pas être directement accessibles depuis l’Internet.

Ci-dessous, l’extrait de la politique de sécurité qui nous intéresse (valable autant en IPv4 qu’en IPv6).

  • Internet peut initier des connexions TCP et envoyer des paquets UDP dans la DMZ et vice-versa.
  • Internet ne peut pas joindre directement les serveurs internes. L’usage d’un reverse proxy dans la DMZ est obligatoire.
  • Le reverse proxy est l’unique machine de la DMZ autorisée à accéder aux serveurs internes.
  • Les connexions SSH du reverse proxy sont bloquées depuis internet et la DMZ.

Dans cette configuration, pour sécuriser correctement la webcam, j’ai installé cette dernière dans mon VLAN des serveurs internes, et j’ai configuré le reverse-proxy pour qu’il effectue une authentification.

Configuration d’Apache 2 en reverse-proxy

Tout d’abord, j’ai choisi d’utiliser l’authentification HTTP-Digest, qui est simple à mettre en place depuis apache, et qui est un tantinet plus sûre que l’authentification basique, où le mot de passe transite en clair sur le réseau.

Pour concrétiser l’exemple, la racine Web de la webcam est accessible en faisant http://192.168.3.8/ depuis le reverse-proxy. La webcam sera accessible depuis http://services.example.com/webcam/ depuis l’Internet (les noms ont été changés pour protéger les innocents).

Vous devez activer les modules apache suivant pour réussir votre configuration:

  • mod_auth_digest
  • mod_proxy
  • mod_proxy_http
  • mod_proxy_connect
  • mod_proxy_ftp

LoadModule ou a2enmod sont vos amis ;-þ

Séance gratte papier fichiers

Ensuite, il va falloir générer le fichier avec les mots de passes pour l’authentification HTTP Digest (souvent .htdigest). Pour cela, effectuez la commande suivante:

htdigest -c /chemin/vers/htdigest webcam toto

Le mot de passe de ‘toto‘ vous sera demandé interactivement.

L’argument ‘webcam‘ correspond à ce qu’on appelle un realm ou plus communément Domaine de connexion. Avec l’authentification HTTP-Digest, vous pouvez gérer un utilisateur sur plusieurs domaines (avec des mots de passes naturellement différents).

Une fois le fichier htdigest généré, posé au bon endroit, en veillant que www-data puisse y accéder en lecture, on s’attaque à la configuration des sites.

Rendez-vous dans /etc/apache2/sites-enabled et ajoutez les lignes suivantes, par précaution, si vous n’utilisez pas de forward proxy sur la machine:

ProxyRequests Off

Order deny,allow
        Allow from all

Ces lignes vont interdire l’utilisation des fonctions forward-proxy sur la machine, qui sont testées régulièrement par des petits malins qui s’amusent à scanner votre IPv4 ou votre subnet IPv6.

Dans le fichier contenant le(s) virtualhost(s) correspondant à services.example.com, ajoutez les lignes suivantes dans le bloc virtualhost adéquat:

    <Location /webcam/>
        AuthType Digest
        AuthName "webcam"
        AuthDigestDomain /webcam
        AuthUserFile /chemin/vers/htdigest
        Require valid-user
        ProxyPass http://192.168.3.8/
        ProxyPassReverse http://192.168.3.8/
    </Location>

La chaîne après le mot-clé AuthName doit être impérativement celle du Realm que vous avez choisi lors de la génération du fichier htdigest.

Stress du passage en production

Rechargez votre démon apache. Normalement, à cette étape, lorsque vous tapez http://services.example.com/webcam/ (ne pas oublier le ‘/’ à la fin), un prompt d’authentification devrait s’afficher. Après avoir entré les identifiants valides, vous devriez pouvoir accéder à l’interface de votre webcam.

En août 2010, OpenSSH 5.6 a ajouté le support de l’authentification serveur par certificats. Cette fonctionnalité peu évoquée va simplifier votre vie, même sur des réseaux de petite taille.

Si la clé de chacune de vos machines est signée par une autorité, les utilisateurs n’auront besoin que de cette autorité pour authentifier les machines de votre réseau. Ceci signifie entre autre le glas du célèbre et embêtant message:

The authenticity of host 'xxxxx (****:****:****::****:****)' can't be established.
RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.

L’ampleur de la manœuvre est proportionnelle au nombre de machines dont vous disposez (sachant que c’est facilement scriptable dans certaines conditions), et bien plus simple que la génération de certificats avec openssl.

Tout au long du tutoriel suivant, on va considérer que nous sommes dans un dossier dédié, inaccessible à d’autre utilisateurs que root.

Étape 1: Générer une paire de clés pour l’autorité de certification

Cette paire de clés est sensible, gardez-la en lieu sûr et de préférence chiffrée.

ssh-keygen -b 4096 -t rsa -C "Mon_autorite" -f ca_cert

Étape 2: Signer la clé publique d’un serveur préalablement générée ou rapatriée

La clé publique du serveur à signer se nommera myhost_key.pub pour la suite du tutoriel.

Vous pouvez (et il est suggéré de) restreindre les noms d’hôtes valides pour votre serveur (dans notre cas hostname valid.hostname.org et server.example.com ).

L’exemple ci-dessous délivre une signature valable 1 an et un jour (option -V). Cette durée peut être changée à volonté; pour plus d’informations, echo $( rtfm -p ) 😉

ssh-keygen -h -s ca_cert -I IdentifiantDeLaClé -V +52w1d \
-n hostname,valid.hostname.org,server.example.com myhost_key.pub

Étape 3: Installer le certificat sur votre serveur

L’étape 2 vous a généré un fichier nommé myhost_key-cert.pub. Ce fichier est à copier dans le même dossier que la clé hôte sur le serveur.

Ajoutez la ligne suivante dans le fichier de configuration du démon OpenSSH (généralement /etc/ssh/sshd_config):

HostCertificate /chemin/vers/les/cles/myhost_key-cert.pub

Redémarrez le démon ssh de la machine concernée.

Répétez l’opération avec tous vos serveurs.

Étape 4: Installer les données de l’autorité sur votre compte client SSH.

Gardez le contenu de ca_cert.pub sous la main, vous allez en avoir besoin pour la suite.

Allez dans le dossier ~/.ssh de l’utilisateur concerné par l’installation de votre autorité.

Créez le fichier known_hosts_cert et ajoutez la ligne suivante et sauvez.

@cert-authority * <contenu du fichier ca_cert.pub>

C’est en place! Deux méthodes ensuite:

  • Connectez-vous directement à un de vos serveurs avec la commande suivante
ssh -oUserKnownHostsFile=~/.ssh/known_hosts_cert hostname
  • Ajoutez la ligne suivante à votre fichier .ssh/config pour les hôtes concernés
UserKnownHostsFile ~/.ssh/known_hosts_cert

Connectez-vous ensuite avec votre commande habituelle.

Répétez cette étape pour tous vos utilisateurs.

À ce point du tutoriel, l’authentification devrait marcher sans alerte, même si vous renouvelez et re-signez la clé de hôte.

Vous voulez effectuer du traitement d’images en C/C++ sans vous casser la tête? C’est possible avec DevIL, une librairie légère et assez simple à manipuler.

La librairie est portable, open source et des tutoriels sommaires sont disponibles.

Elle est compatible avec:

  • Windows
  • Linux dont:
    • Debian (et probablement Ubuntu) dans le dépot sous les noms de libdevil1c2 et libdevil-dev
    • Red Hat et tout ce qui a des RPM
    • Slackware

Pour les courageux, il est possible de la recompiler, mais des binaires sont déjà fournis.

Il est manifestement possible de s’en servir pour charger des textures avec OpenGL.

Si vous ne voulez pas vous casser la tête avec la librairie, voici des sources perso qui vous permettront de manipuler votre image comme un tableau unidimentionnel:

Voici un exemple de traitement d’image (réhaussement de LSB avec possibilité de filtrer par calque).

fichier lsbManipulator.hpp

#ifndef __LSBMANIPULATOR_HPP
#define __LSBMANIPULATOR_HPP

#include <IL/il.h>

class lsbManipulator {
private:
    //	Layer from the lsb to show.
    static ILuint showLayer;
    static bool showOneLayer;
public:
    static void setVisibleBitLayer( ILuint nLayer);
    static void lsbEnhancer( ILubyte* data, ILuint width, ILuint height);
};

#endif //__LSBMANIPULATOR_HPP

fichier lsbManipulator.cpp

#include <iostream>

#include "lsbManipulator.hpp"

// default init list
ILuint lsbManipulator::showLayer = 0;
bool lsbManipulator::showOneLayer = false;

void lsbManipulator::lsbEnhancer(ILubyte* data, ILuint width, ILuint height)
{
    ILuint picLength = width * height * 3;
    
    ILubyte oneByte;
    
    for( ILuint i=0; i<picLength; ++i)
    {
        // Swap endianness of the byte
        data[i] = ((data[i] * 0x0802LU | 0x22110LU ) | (data[i] * 0x0802LU | 0x88440LU )) * 0x10101LU >> 16;
	
        oneByte = data[i];
        // If applicable, shift to the n-layer and bitmask
        if( lsbManipulator::showOneLayer)
	    data[i] = (oneByte << lsbManipulator::showLayer) & 0x80LU;
	
    }
}

void lsbManipulator::setVisibleBitLayer(ILuint nLayer)
{
    lsbManipulator::showOneLayer = true;
    lsbManipulator::showLayer = nLayer;
}

Ici, vous trouverez tout le code qui va permettre d’ouvrir l’image, de lancer traitement, et de sauver le résultat. La fonction n’est pas encore thread safe, je vous tiendrai au courant quand ça sera le cas, (avec une classe, ça sera vite réglé 😉 ).

fichier pictureProcessor.h

#ifndef __PICTUREPROCESSOR_H
#define __PICTUREPROCESSOR_H

#include <string>
#include <IL/il.h>

/**
 * ilProcessor -- Image stream processor function
 *	 Functions of this type can process pictures as provided by the OpenIL layer.
 *
 * @author Geoffroy GRAMAIZE
 */
typedef void (*ilProcessor)( ILubyte*, ILuint, ILuint);

/**
 * processPicture() -- Process an image using an ilProcessor and OpenIL.
 * 	This is a wrapper function for easy OpenIL handling.
 *
 * @author Geoffroy GRAMAIZE
 *
 * @param inFileName	The input picture file path.
 * @param outFileName	The output picture file path.
 * @param ilProcessingAlg	ilProcessor used to process the input picture.
 */
void processPicture( const std::string& inFileName, const std::string& outFileName, ilProcessor ilProcessingAlg);

#endif //__PICTUREPROCESSOR_H

fichier pictureProcessor.cpp

#include <iostream>

#include <IL/il.h>
#include <IL/ilu.h>
#include <stdlib.h>
#include <string.h>

#include "pictureProcessor.h"

void processPicture( const std::string& inFileName,const std::string& outFileName, ilProcessor ilProcessingAlg)
{
    ilInit();
    ILuint imageName;
    ilGenImages(1, &imageName);
    ilBindImage(imageName);

    if( !ilLoadImage(inFileName.c_str()) )
    {
	std::cerr << "Couldn't load '" << inFileName << "' , aborting..." << std::endl;
    }

    ILuint width = ilGetInteger(IL_IMAGE_WIDTH);
    ILuint height = ilGetInteger(IL_IMAGE_HEIGHT);

    std::clog << "Loaded '" << inFileName << "' (wid: " << width << "px, hei: " << height << "px)." << std::endl;

    ILuint tailleUniDim = width*height*3;

    // grab the original picture, and create a temporary memory array
    ILubyte *dataImg = ilGetData(),
	    *dataTemp = new ILubyte[ tailleUniDim ];

    // copy the picture into the work array
    memcpy (dataTemp, dataImg, tailleUniDim);

    // call the picture processor
    ilProcessingAlg( dataTemp, width, height);

    // restore the work array
    memcpy (dataImg, dataTemp, tailleUniDim);

    // Write the picture in output file
    ilEnable(IL_FILE_OVERWRITE);

    if( ! ilSaveImage( outFileName.c_str()) )
    {
	std::cerr << "Couldn't save work in '" << inFileName << "' , aborting..." << std::endl;
    }

    ilShutDown();
}

Pour vous mettre sur la voie, voiçi un exemple d’utilisation du tout 😉

fichier argParser.hpp

#ifndef __ARGPARSER_HPP
#define __ARGPARSER_HPP

#include <iostream>

struct SAppParams {
  bool renderOneLayer; 
  unsigned int layerToRender; 
  std::string inFile; 
  std::string outFile; 
  
  static void init(SAppParams& p) {
    p.renderOneLayer=false;
    p.layerToRender=0;
    p.inFile = std::string("");
    p.outFile = std::string("");
  }
};

bool processArgs( SAppParams& params, int argc, char** argv);

#endif //__ARGPARSER_HPP

fichier argParser.cpp

#include <string.h>
#include <stdlib.h>

#include "argParser.hpp"


bool processArgs( SAppParams& params, int argc, char** argv)
{
    if( argc<2)
      return false;
    
    for( int i=1; i<argc; ++i)
    {
      
      if( strncmp( argv[i], "-l", 2) == 0 )
      {
	// Show one layer
	if( i>= argc)
	  break;
	
	params.layerToRender = atoi(argv[++i]);
	params.renderOneLayer = true;
      }
      
      else if( strncmp( argv[i], "-o", 2) == 0 )
      {
	// Set the output File
	if( i>= argc)
	  break;
	params.outFile = std::string( argv[i+1]);
      }
      else
      {
	// Set the input file
	params.inFile = std::string( argv[i]);
      }
    }
    
    return true;
}

fichier main.cpp

#include <iostream>
#include <string>

#include <IL/il.h>
#include <stdlib.h>
#include <string.h>

#include "pictureProcessor.h"
#include "lsbManipulator.hpp"
#include "argParser.hpp"

void welcomeBanner(void)
{
    std::cout << std::endl
	      << "Geoffroy Gramaize's image tickler - (c) 2011" << std::endl
	      << "--------------------------------------------" << std::endl
	      << std::endl;
}

void usage(char **argv)
{
    std::cout << "Usage: "<< argv[0] <<" args inputFile" << std::endl
	      << std::endl
	      << "Arguments\tMeaning"<< std::endl
	      << "-l <0-7> \tShow only the N-layer starting from LSB" << std::endl
	      << "-o <file>\tSpecifies in which file the result will be stored." << std::endl
	      << std::endl;
}

int main(int argc, char **argv) {
  
  SAppParams appParams;
  SAppParams::init(appParams);
  
  if( !processArgs( appParams, argc, argv) || appParams.inFile.compare("") == 0 || appParams.outFile.compare("") == 0 )
  {
    usage(argv);
    return 1;
  }
  
  welcomeBanner();
  
  if( appParams.renderOneLayer==true && appParams.layerToRender >= 0 && appParams.layerToRender <8)
  {
    
    std::clog << "Will render layer " << appParams.layerToRender << " from LSB." << std::endl;
    lsbManipulator::setVisibleBitLayer( appParams.layerToRender);
  }
  
  processPicture( appParams.inFile, appParams.outFile, &(lsbManipulator::lsbEnhancer) );
  
  return 0;
}

Bonne chance et amusez-vous bien.