La Lanterne Rouge

La Lanterne Rouge

Warning: Geek Inside

Publié le par Nanawel
Publié dans : #raspberry pi | #linux | #lighttpd | #motion | #scripts | #vidéo-surveillance | #webcam | #php | #https | #streaming

Suite de mon précédent article - qui rencontre apparemment son petit succès d'après mes stats Google Analytics - je vous propose ici quelques scripts en PHP et la configuration à mettre en place pour faciliter et améliorer la vidéo-surveillance au travers de Motion que nous avons mise en place sur le Raspberry Pi (mais ces instructions s'appliquent potentiellement à toute installation de Motion sur GNU/Linux).

Motion : un outil puissant, mais pas tout puissant

Car vous l'aurez peut-être remarqué mais Motion, aussi puissant qu'il soit, reste un outil simple auquel il manque parfois quelques "petits plus".

J'en note principalement deux majeurs :

  • La page de streaming est appréciable mais le format du flux en multipart/x-mixed-replace n'est pas géré correctement par tous les navigateurs. Il serait intéressant en plus du flux en streaming d'obtenir une image statique, compatible avec tous les navigateurs (y compris mobiles) mais également plus économe en bande passante.
  • Si l'accès au flux HTTP est accessible depuis un réseau non-contrôlé (Internet par exemple) il serait nécessaire de pouvoir configurer une authentification minimale empêchant l'accès non-autorisé au flux des images, ainsi que du chiffrement pour protéger cette authentification.

 

​Afin de pallier ces lacunes, je vous propose dans cet article de mettre en place les services suivants en HTTP et HTTPS :

  • /latest/ : image statique extraite du flux en streaming généré par Motion
  • /streaming/ : Copie du flux en streaming de Motion
  • /motdet/ : Dernière image capturée par Motion suite à une détection de mouvement

Scripts PHP et création de l'arborescence

Vous pouvez utiliser les scripts que je mets à disposition dans l'archive suivante : http://nanawel.free.fr/dev/motion-scripts/motion-scripts-0.1.0.tar.gz

Ces scripts nécessitent PHP 5, qui sera installé dans la section suivante.

Pour simplifier, l'archive contient déjà l'arborescence que nous allons utiliser dans la suite. Pour extraire son contenu et positionner les permissions adéquates les deux commandes qui suivent devraient suffire :

# tar xf motion-scripts-0.1.0.tar.gz -C /srv/http
# chown -R http:http /srv/http

On peut ensuite vérifier le résultat (si vous n'avez pas tree, un petit pacman -S tree pour j'ajouter) :

$ tree /srv/http
/srv/http
├── include
│   ├── config.inc.php
│   └── HttpHelper.inc.php
├── latest
│   └── index.php
├── motdet
│   └── index.php
└── streaming
    └── index.php

Installation de Lighttpd

Pour la mise en place de ces fonctionnalités, nous allons avoir besoin d'un serveur HTTP complet. Je vais supposer que nous travaillons ici sur le Raspberry Pi, en conséquence je privilégierai Lighttpd à la place d'Apache pour des questions évidentes de légèreté. Je rappelle aussi que j'utilise Archlinux ARM, basée comme son nom l'indique sur Archlinux.

Les phases détaillées sont, comme habituellement avec Archlinux, présentées de manière concise mais efficace dans le wiki. Je me suis basé dessus pour la mise en place.

L'installation se résume au simple :

# pacman -Sy lighttpd php php-cgi

À cela il faut ajouter une petite phase de configuration.

Déjà pour lancer le daemon au boot :

# systemctl enable lighttpd

Puis, comme indiqué dans le wiki, on ajoute la configuration fastcgi pour PHP dans le fichier /etc/lighttpd/conf.d/fastcgi.conf (que l'on crée au passage) :

# mkdir /etc/lighttpd/conf.d
# nano /etc/lighttpd/conf.d/fastcgi.conf
____
server.modules += ( "mod_fastcgi" )

#server.indexfiles += ( "index.php" ) #this is deprecated
index-file.names += ( "index.php" )

fastcgi.server = (
    ".php" => (
      "localhost" => ( 
        "bin-path" => "/usr/bin/php-cgi",
        "socket" => "/tmp/php-fastcgi.sock",
        "max-procs" => 4, # default value
        "bin-environment" => (
          "PHP_FCGI_CHILDREN" => "1", # default value
        ),
        "broken-scriptfilename" => "enable"
      ))
)

et on inclut ce fichier à la fin de la configuration principale :

# echo include \"conf.d/fastcgi.conf\" >> /etc/lighttpd/lighttpd.conf

Configuration des mimetypes (optionnel)

Dans le fichier lighttpd.conf par défaut, quelques mimetypes sont déclarés pour gérer les pages HTML, les fichiers textes et les images JPG et PNG.

Même si nous n'aurons pas besoin de plus ici, il est recommandé d'ajouter au minimum les déclarations nécessaires pour les fichiers Javascript, CSS et GIF. Dans le cas inverse, l'utilisation d'un de ces fichiers dans le futur provoquera des erreurs dans le navigateur qui ne saura pas interpréter leur contenu. Ça ne coûte pas grand chose, alors à y être...

Pour cela, on remplace la ligne

mimetype.assign         = ( ".html" => "text/html", ".txt" => "text/plain", ".jpg" => "image/jpeg", ".png" => "image/png", "" => "application/octet-stream" )

par :

mimetype.assign         = (
                            ".html" => "text/html",
                            ".txt"  => "text/plain",
                            ".jpg"  => "image/jpeg",
                            ".png"  => "image/png",
                            ".gif"  => "image/gif",
                            ".js"   => "text/javascript",
                            ".css"  => "text/css",
                            ""      => "application/octet-stream" )

(J'en profite au passage pour indenter le bloc afin d'améliorer sa lisibilité)

Ajout du module "auth" pour lighttpd

Afin de protéger  par un couple login/mot de passe l'accès aux images issues de Motion, nous allons configurer le module "auth" pour Lighttpd.

Nous placerons la configuration spécifique aux modules dans le dossier /etc/lighttpd/conf.d à des fins de clarté et de maintenabilité, bien qu'il serait possible dans l'absolu de tout mettre dans /etc/lighttpd/lighttpd.conf.

Nous créerons ensuite un fichier auth.conf dans celui-ci dans lequel on collera la configuration préparée par mes soins : http://pastebin.com/yM42jqGB (le modèle vierge dont je me suis inspiré est disponible sur le wiki du projet).

# mkdir -p /etc/lighttpd/conf.d
# nano /etc/lighttpd/conf.d/auth.conf
[coller ensuite le contenu]

Les seules modifications que j'ai faites dans la première partie du fichier sont les suivantes :

auth.backend                = "plain"
auth.backend.plain.userfile = "/etc/lighttpd/auth/plain"

J'utilise "plain" à la place de "htpasswd" ce qui signifie que les mots de passe d'accès seront stockés dans un fichier texte en clair au lieu d'être légèrement chiffrés. C'est une entorse à la sécurité qui me semble raisonnable dans ce contexte.

Le fichier contenant les couples logins/mots de passe sera placé au chemin /etc/lighttpd/auth/plain. Nous le créerons plus loin.

Dans la seconde partie du fichier nous définissons les modalités d'accès aux trois chemins cibles qui mèneront aux services présentés dans l'introduction :

auth.require = ( "/latest/" =>
                 (
                   "method"  => "basic",
                   "realm"   => "Restricted Area",
                   "require" => "valid-user"
                 ),
                 "/streaming/" =>
                 (
                   "method"  => "basic",
                   "realm"   => "Restricted Area",
                   "require" => "valid-user"
                 ),
                "/motdet/" =>
                 (
                   "method"  => "basic",
                   "realm"   => "Restricted Area",
                   "require" => "valid-user"
                 )
               )

Le texte "Restricted Area" est au choix de chacun : c'est le message qui apparaîtra dans l'invite de login/mot de passe dans le navigateur du client. Autant rester vague sur le contenu protégé derrière...

La boîte de dialogue d'authentification sous Firefox

La boîte de dialogue d'authentification sous Firefox

Il faut maintenant créer le fichier des mots de passe auquel nous faisons référence. Le format est d'une simplicité enfantine : un couple par ligne, et en séparant le login du mot de passe par deux-points ":" :

# mkdir -p /etc/lighttpd/auth
# nano /etc/lighttpd/auth/plain
____
toto:monmotdepasse
titi:minmitdepasse

(oui c'est très drôle)

Puis on sauve. Mais ces mots de passe en clair sont au minimum à protéger par les permissions UNIX. Donc :

# chown http:root /etc/lighttpd/auth/plain
# chmod 660 /etc/lighttpd/auth/plain

De cette manière, seul l'utilisateur http, exécutant Lighttpd, pourra accéder au fichier.
Il est inutile de préciser que si un attaquant venait à détourner votre serveur Web, cela n'aurait pas grande efficacité pour le freiner... Mais les scripts que je vous propose ne traitent pas de données fournies par le navigateur client, donc ce risque est minimal.

 

À présent que le module est configuré, nous pouvons l'activer et ajouter la directive d'inclusion de notre nouveau fichier de configuration /etc/lighttpd/conf.d/auth.conf dans le fichier principal /etc/lighttpd/lighttpd.conf.

# echo "server.modules += ( \"mod_auth\" )" >> /etc/lighttpd/lighttpd.conf
# echo "include \"conf.d/auth.conf\"" >> /etc/lighttpd/lighttpd.conf

On peut à présent relancer Lighttpd et accéder à l'URL http://[ip_raspberry_pi]/ pour nous assurer que la configuration est valide. On tombe normalement sur une page d'index listant les dossiers au chemin /srv/http, extraits de l'archive au tout début.

Quelques scripts pour motion (et votre Raspberry Pi)

Mise en place de SSL (HTTPS)

Ici comme ailleurs je ne vais rien inventer, et simplement suivre les instructions données sur le wiki d'Archlinux :

Génération du certificat dans le dossier /etc/lighttpd/certs :

# mkdir /etc/lighttpd/certs
# openssl req -x509 -nodes -days 7300 -newkey rsa:2048 -keyout /etc/lighttpd/certs/comeye.pem -out /etc/lighttpd/certs/comeye.pem
# chmod 600 /etc/lighttpd/certs/comeye.pem

Lors de l'exécution de la deuxième commande, plusieurs informations sont demandées telles que l'identifiant du pays, l'état, la ville, etc. Comme toujours ici elles ne sont pas importantes car notre certificat restera privé, donc on peut saisir ce qu'on veut.

 

Ce que vous pouvez changer :

  • Le nom du fichier généré : j'utilise comeye.pem car ma machine s'appelle "ComEye", mais libre à vous de mettre le nom que vous souhaitez.
  • La durée de validité du certificat : ici de 20 ans (7300 jours), il n'y a pas de raison de mettre moins dans ce contexte. Vous n'avez certainement pas envie de regénérer un certificat tous les ans par exemple.

 

Il faut préciser que le dossier /etc/lighttpd/certs n'a pas besoin d'être accessible par l'utilisateur http contrairement au dossier /etc/lighttpd/auth vu plus haut. Les certificats PEM sont en effet lus et chargés en mémoire au lancement du daemon, avant d'abaisser les privilèges. Seul root doit donc avoir accès à ces fichiers (Source).

 

Dans le fichier /etc/lighttpd/lighttpd.conf, ajouter les lignes suivantes afin que Lighttpd réponde aux requêtes HTTPS sur le port 443 en plus des requêtes HTTP sur le port 80 comme il le fait par défaut :

$SERVER["socket"] == ":443" {
    ssl.engine  = "enable" 
    ssl.pemfile = "/etc/lighttpd/certs/comeye.pem"     # remplacez ici par le chemin vers votre fichier
}

On peut à présent redémarrer Lighttpd et vérifier que l'accès à cette URL en HTTPS donne bien le même résultat que précédemment en HTTP : https://[ip_raspberry_pi]/

L'avertissement du navigateur indiquant que le certificat n'est pas valide est bien sûr à ignorer... nous venons juste de le générer nous-même.

Peaufinage

Maintenant que nous avons vérifié que tout fonctionne, il est possible de désactiver l'indexation automatique des répertoires très simplement depuis le fichier /etc/lighttpd/lighttpd.conf en modifiant la ligne :

dir-listing.activate    = "enable"

et en passant - comme c'est original - le "enable" en "disable".

De cette manière, l'accès à l'URL http(s)://[ip_raspberry_pi]/ retournera désormais une page 404, sans dévoiler le contenu du dossier racine Web.

Configuration et test des scripts

À présent tout est en place pour que les scripts déposés dans les dossiers publics de Lighttpd soient fonctionnels.

Mais avant d'en tester le résultat, il convient de vérifier leur configuration. Pour cela, éditer le fichier /srv/http/include/config.inc.php.

Trois constantes sont à vérifier, et adapter si nécessaire :

  • MOTION_DETECTIONS_FOLDER_PATH : le chemin vers le dossier où Motion enregistre les clichés ("target_dir" dans /etc/motion/motion.conf)
  • MOTION_STREAMING_HOST : l'adresse IP de l'hôte exécutant Motion. Normalement, c'est localhost, donc 127.0.0.1.
  • MOTION_STREAMING_PORT : le port utilisé par Motion pour le streaming. Par défaut c'est 8081 ("webcam_port" dans /etc/motion/motion.conf)

Ceci fait, on peut tester les URLs suivantes :

  • https://[ip_raspberry_pi]/latest/
  • https://[ip_raspberry_pi]/streaming/
  • https://[ip_raspberry_pi]/motdet/

L'accès à celles-ci demandera systématiquement une authentification. Il faudra alors utiliser un des couples login/mot de passe renseignés précédemment dans le fichier /etc/lighttpd/auth/plain.

Accès depuis Internet (NAT)

Mon RPi étant comme mes autres ordinateurs derrière une box, il m'a fallu créer les règles de NAT/PAT afin de pouvoir accéder aux ports de Lighttpd. Cependant, je n'ai volontairement redirigé que le port 443 - sécurisé donc - et pas le port 80. Seul l'accès HTTPS est donc possible depuis l'extérieur.
Il m'est par contre tout à fait possible d'accéder aux pages HTTP depuis le VPN hébergé par Usul grâce à mon montage NETMAP.

Epilogue

Je comptais initialement présenter un peu comment les scripts fonctionnent et m'attarder sur la structure des contenus multipart/x-mixed-replace, mais l'article est déjà long et il me semble inopportun de l'alourdir davantage.

Je m'arrête donc là et remets peut-être à plus tard cette présentation complémentaire. Les scripts fonctionnent et sont bien sûr libres de modification, adaptation et réutilisation ! N'hésitez alors pas à jeter un oeil à leur fonctionnement.

Commenter cet article

Bob 09/08/2013 08:48

Excellent article.
Serait-il possible d'ajouter un tuto pour une interface web de consultation des événements enregistrés ?
Il existe visiblement Motion Browser (http://www.lavrsen.dk/foswiki/bin/view/Motion/MotionBrowser) mais il semble un peu ancien, et peut-être a t-on mieux pour un Raspberry sous Arch Linux ?

Nanawel 09/08/2013 09:28

Salut Bob,
Personnellement j'ai mis en place un système alternatif pour consulter les captures issues de la détection de mouvements au moyen d'un simple script PHP de galerie ("Vision", disparu depuis).

Ce n'est pas présenté comme MotionBrowser mais pour mon utilisation c'est suffisant (notamment car je n'ai que des images et pas de vidéos).
Le principe est de déposer les images dans un dossier accessible au travers du serveur HTTP et de laisser le script de galerie faire son boulot. Comme les noms de fichiers utilisent la date, il est facile de les parcourir chronologiquement (ou à l'envers).

Tu trouveras quelques infos dans mon précédent article : http://lanterne-rouge.over-blog.org/article-un-raspberry-pi-pour-la-video-surveillance-113889745.html

spynaej 04/08/2013 09:11

salut,merci pour le partage des scripts !
J'ai moi aussi installé un petit raspi pour de la video surveillance ; je me suis inspiré de ça (http://through-the-interface.typepad.com/through_the_interface/2012/09/creating-a-motion-detecting-security-cam-with-a-raspberry-pi-part-2.html) avec notamment l'upload sur google drive des videos après détection de mouvement. Je retiens tes solutions de sécurisation pour améliorer le tout. Il me reste un petit problème : quand j'arrête motion, la camera reste "occupée" et pas moyen de relancer motion sans redémarrer le raspberry... Si tu as une idée...

Nanawel 05/08/2013 11:34

Salut !
C'est aussi un article que j'avais parcouru lors de mes recherches, c'est un oubli de ma part de ne pas l'avoir cité car il est aussi très intéressant !

Concernant ton problème je n'ai pas vraiment d'idée précise sur ce qui peut le provoquer. Quand tu dis que tu arrêtes Motion, tu parles bien du daemon ? (donc commande : systemctl stop motion)
Ce que tu peux essayer de faire c'est décharger le module noyau qui gère la webcam, puis le recharger. Si ça fonctionne tu peux ajouter ces commandes à l'unité Motion de systemd. Pour ces étapes par contre je ne pourrai pas t'aider, il faudra que tu cherches par toi-même...