Archlinux avec LUKS et LVM sur un SSD

Dans mon article précédent C’est décidé, je retourne sous Linux, je me moquais gentiment sur le Windows livré sur mon nouveau pc (un Dell Vostro 3360, comme ça vous saurez tout). Après tant d’amusement trollesque, j’ai voulu installer un système Archlinux aux petits oignons qui me convenait. Comme je trouve que la ligne de commande reste le moyen le plus efficace et rapide de faire du peaufinage de système, on va en bouffer ! La procédure ci-dessous explique comment obtenir une installation d’Archlinux dans un conteneur LVM chiffré avec dm-crypt et LUKS (système et SWAP) avec l’activation de TRIM pour SSD et une utilisation de Enlightenment E17 comme windows manager.

Kwa ? G rien compri !

Voici ce dont on parle :

  • Archlinux : mon coup de coeur dans les distributions de linux, les explications devraient pouvoir être adaptable à d’autres systèmes tant que vous avez un terminal à disposition.
  • LVM : méthode de gestion de partitions non continues. On peut ajouter, supprimer, modifier les partitions un peu comme on veut, plus besoin de rester 3h devant GParted en priant le dieu Bécup.
  • LUKS : norme de chiffrement par blocs, idéal sous linux pour chiffrer une partition (en opposition à par exemple à eCryptfs qui sert à chiffrer un dossier comme le propose Ubuntu avec le dossier home)
  • dm-crypt : sous-système permettant de gérer le chiffrement LUKS (où se trouvent les clefs…) utilisé par le noyau Linux. Propose notamment cryptsetup que l’on utilise ici.
  • SSD : nouvelle génération de disques remplaçant les vieux HDD. Plus rapides, silencieux, économes en énergie et surtout plus chers. Mon Vostro est équipé d’un SSD de 128GB (pas de HDD en hybride)
  • TRIM : sur un HDD, lorsque l’on écrit là où se trouvaient des données « supprimées », on écrase l’ancienne valeur par la nouvelle. Avec un SSD, on doit d’abord supprimer le contenu du secteur puis écrire la nouvelle (perte de performances). TRIM permet d’indiquer au système lorsque l’on n’a plus besoin de certaines données, l’autorise à les supprimer et l’on gagner du temps pour la future réécriture. Une sorte de garbage collector. De par son mode de fonctionnement, TRIM rend aussi inefficace toute tentative de récupération de données en cas de rm mal placé.
  • SWAP : espace du disque dédié à soulager la RAM lorsque celle-ci se retrouve surchargée. Il est nécessaire de la chiffrer pour éviter les fuites d’information (des mots de passe pouvant être chargé dans la RAM).
  • E17 : window manager léger et hyper personnalisable (*tousse*opposé de GNOME*tousse*). Une version stable vient de sortir après 12 ans de développement. Au passage ne venez pas m’ennuyez sur le window manager vs desktop environment, on met un peu n’importe quoi dans les deux catégories et puis c’est moi qui décide ici, na !

Voici une image permettant de mieux comprendre ce que l’on va réaliser :

luks-graph

Le /boot est sur une petite partition séparée (on a besoin d’un certain nombre de modules pour déchiffrer la partition), le reste du système se trouvant dans une partition chiffrée. En déchiffrant cette partition, on ouvre un conteneur LVM. Cela permet d’avoir toutes les partitions chiffrées avec la même clef. Si l’on voulait chiffrer une seule partition (ou plusieurs avec des clefs différentes), l’on aurait fait le contraire (LUKS dans LVM). Dans mon cas, n’ayant qu’Archlinux sur un petit disque, j’ai préféré utiliser uniquement une SWAP et une partition pour le système. Libre à vous de diviser le conteur LVM en plus de parties pour un home séparé ou pour du multiboot.

La manipulation va donc permettre de chiffrer sa machine, cela ne rendra pas la machine invisible contre tout types d’attaque. Les implications exactes en termes de sécurité sont mentionnées dans la conclusion, lisez là bien avant de commencer toute manipulation si vous n’êtes pas certains de bien comprendre.

Préparation

Attention, cette procédure va écraser lamentablement vos précédentes données tel le scarabée rhumatisant sous la patte du vaillant hippopotame. Si vous voulez conserver une copie de votre disque, je vous conseille de suivre cette procédure. Suumitsu a donné une procédure permettant de convertir un disque en machine virtuelle. J’ai testé mais perso la machine virtuelle ne fonctionne pas chez moi (récup système qui échoue).

Créez un live CD ou USB, démarrez dessus jusqu’à obtenir un terminal utilisable (je passe cette étape, pour arch c’est ici que ça se passe, jusqu’au 2.2). Fait ? Maintenant on efface le tout. D’abord on met de l’aléatoire pour maximiser la sécurité (on ne peut pas différencier les données aléatoires des données chiffrées). Note : l’activation de TRIM réduit l’intérêt de cette manipulation, lisez la dernière section sur les limites pour vous faire une opinion. Si vous jugez cela inutile, passez cette étape (je la laisse sans être certain à 100% de ce que j’avance). Si vous avez un HDD, faites le.

$ dd if=/dev/urandom of=/dev/sda

Ouille pauvre disque, ça prend du temps (6 MB/s sur mon disque de 128GB, soit 6 heures, glups). C’est pas le mode le plus parano du monde mais ça suffit. Faire plusieurs réécritures a été montré comme superflu. En parlant de suppression efficace, n’utilisez pas non plus des outils comme shred en pensant bien effacer vos fichiers, c’est inefficace sur un SSD.

Partitions

On crée ensuite une nouvelle table de partition. J’ai utilisé cfdisk, c’est très facile et interactif, même pas besoin de lire la man page pour comprendre. Les partitions sont à définir selon vos gouts mais comme je vous le disais plus haut, je vais uniquement séparer /boot du reste. 200MB pour /boot sur sda1, le reste sur sda2.

# cfisk /dev/sda
# mkfs.ext4 /dev/sda1 -L boot # EXT4 est bien mais c’est un choix personnel après

Pour chiffrer sda2, on a plusieurs choix d’algorithmes, de modes de stockage de clef, etc. On peut par exemple utiliser une suite de bytes aléatoires qui sont stockés sur une clefs USB chiffrée avec GPG (not bad). Mais nous, on ne vise pas la top sécurité (trop contraignant je trouve), on va faire au plus simple : un mot de passe à entrer au boot. Lisez la man page ou ce wiki pour des variations. (désolé pour cet idiot de wordpress qui me remplace les doubles tiret par un caractère spécial)

# cryptsetup –cipher aes-cbc-essiv:256 –key-size 256 –hash sha256 –iter-time 1000 –use-random –verify-passphrase luksFormat /dev/sda2

On choisi un super mot de passe (que vous irez écrire sur un post-it collé à l’écran) et on confirme (n’oubliez pas les majuscules pour le « yes »).

Pour ouvrir le conteneur, on utilise la commande suivante (très important, c’est cette commande que vous allez utiliser si jamais vous crachez votre système et devez faire la réparation avec un live cd, notez la aussi sur le post-it) :

# cryptsetup luksOpen /dev/sda2 sda2_crypt

La partition est maintenant accessible à /dev/mapper/sda2_crypt (vous pouvez évidement choisir un autre nom).

La partition sda2_crypt va être convertie en Primary Volume LVM contenant la swap et /. Je conseille encore une fois la doc archlinux pour les variations mais dans mon cas :

# modprobe dm-mod
# pvcreate /dev/mapper/sda2_crypt
# vgcreate MyGroup /dev/mapper/sda2_crypt
# lvcreate -C y -L 1G MyGroup -n lvswap
# lvcreate -l +100%FREE MyGroup -n lvarch
# mkswap /dev/mapper/MyGroup-lvswap
# mkfs.ext4 /dev/mapper/MyGroup-lvarch -L arch
squelette à un bar
Kevin attend patiemment que son algorithme de brute force trouve votre clef LUKS

Installation

Maintenant on peut continuer normalement l’installation comme n’importe quel système. Dans le cas d’Archlinux avec arch-chroot, il faut d’abord monter les partitions dans /mnt.

# mount /dev/mapper/MyGroup-lvarch /mnt
# swapon /dev/mapper/MyGroup-lvswap
# mkdir /mnt/boot
# mount /dev/sda1 /mnt/boot

Dans le beginner’s guide d’archlinux, on peut reprendre au point 2.6. select a mirror. Les partitions montées étant déchiffrées, la génération du fichier fstab devrait donner les bons UUID.

Dans le cas de mon Dell Vostro 3360, le wifi était reconnu nativement mais pas ethernet, la faute à l’Atheros AR8161. J’ai expliqué la marche à suivre mais à ce jour (à adapter à sa version du noyau, deviendra compat-driver dès linux 3.7), les commandes sont :

# wget http://www.orbit-lab.org/kernel/compat-wireless-3-stable/v3.6/compat-wireless-3.6.8-1-snpc.tar.bz2
# tar xjf compat-wireless-3.6.8-1-snpc.tar.bz2
# cd compat-wireless-3.6.8-1-snpc
# ./scripts/driver-select alx
# make
# make install
# modprobe alx

Avant que vous ne redémarriez, il faut faire quelques modifications pour activer le déchiffrement et TRIM. Dans le fichier /etc/mkinitcpio.conf, on défini les modules à charger dans le noyau au démarrage. Voici ce qu’il faut dans notre cas :

MODULES="... sd_mod .."
...
HOOKS="... keymap encrypt lvm2 resume filesystems ..."

sd_mod étant pour LVM, keymap pour charger la disposition du clavier que vous aurez spécifié dans /etc/vconsole.conf (attention, si pas présente, vous devrez déchiffrer votre partition en qwerty), encrypt pour LUKS, LVM2 pour LVM (inattendu je sais), resume pour sortir de l’hibernation et tout cela devant se trouver avant filesystems. Vous pouvez également faire un nettoyage ici en supprimant et déplaçant quelques modules. Je ne suis pas spécialiste de cette partie donc ne vais pas trop m’aventurer là dedans de peur de dire des conneries. Si ça vous intéresse Postblue en donne quelques unes (des optimisations, pas des conneries).

On régénère l’image à charger avec :

# mkinitcpio -p linux

(ou autres paramètres en cas de noyau différent ou optimisation)

Pour indiquer à GRUB la marche à suivre, on va modifier le fichier /etc/default/grub à la ligne GRUB_CMDLINE_LINUX pour mettre :

GRUB_CMDLINE_LINUX="cryptdevice=/dev/sda2:sda2_crypt:allow-discards resume=/dev/mapper/MyGroup-lvswap pcie_aspm=force elevator=noop"

N’oubliez pas de regénérer un nouveau fichier grub.cfg avec grub-mkconfig. Le allow-discards permettant d’activer TRIM. On va d’ailleurs également modifier le fichier /etc/fstab qui chez moi ressemble à ça :

# /dev/mapper/MyGroup-lvarch LABEL=arch
UUID=8f42... / ext4 defaults,noatime,nodiratime,discard 0 1

# /dev/sda1 LABEL=boot
UUID=dd30... /boot ext4 rw,relatime,data=ordered 0 2

# /dev/mapper/MyGroup-lvswap
UUID=2142... none swap defaults,noatime,nodiratime,discard 0 0

J’avoue ne pas être certain à 100% des paramètres en dehors du discard, les nomachin permettant de réduire le nombre d’écriture nécessaire (mais peut poser des problèmes dans certains cas de figure). Lisez SSD Tuning For Linux ou Archlinux SSD pour plus d’info.

Démarrage et E17

J’ai choisi d’installer E17 comme environnement de bureau. C’est très léger et très personnalisable. Tellement personnalisable que je n’ai pas encore réussi à l’adapter complètement à mes envies mais je suis sur que ça arrivera (j’ai commencé à mettre une série d’astuces ici). Voila à quoi ça ressemble chez moi après installation.

bureau-e17
Sobre et léger, moi j’aime ( fond d’écran)

Il suffit d’installer le paquet enlightenment17 pour obtenir le bureau ainsi que tous vos programmes favoris et légers pour les tâches voulues (connman pour la gestion du wifi, mirage pour les images, 7-zip pour les archives… à vous de voir). Comme pour n’importe quel environnement de bureau en somme.

Là par contre où une modification devient intéressante est la connexion automatique. Si l’on chiffre le disque, l’intérêt de demander le mot de passe de l’utilisateur directement après est diminué. L’optimisation vient de chez postblue qui la tient du wiki Archlinux. Connexion automatique sur votre utilisateur qui va lancer Enlightenment avec un exec enlightenment_start dans le fichier ~/.xinitrc.

Grâce à ces optimisations, sur mon Vostro, une fois le mot de passe LUKS entré, j’ai besoin de 3 secondes pour être sur un bureau prêt à l’utilisation, c’est moins que ce qu’il me fallait pour sortir d’hibernation sur mon ancienne machine !

Vérification TRIM

Alors là, pour être honnête, j’ai une incertitude. Cela a été le sujet d’une discussion sur les forums Archlinux (ici) sans arriver à de solution convenant tout le monde. De nombreux sites affirment que le flag « allow-discards » suffit à activer TRIM. On peut le vérifier ainsi :

# dmsetup table /dev/mapper/sda2_crypt –showkeys
0 249583634 crypt aes-cbc-essiv:sha256 f7[… ]95 0 8:2 4096 1 allow_discards

Si on leur fait confiance (ce que j’ai envie de faire), cela devrait suffire.

Seulement « discard » indique que l’on active TRIM, pas que TRIM fonctionne (vous voyez la subtilité ?). Un test un peu plus sophistiqué est donné ici. Cela consiste en la création d’un fichier, regarder où il se trouve en mémoire, le supprimer et vérifier que le registre a bien été vidé (les commandes sont dans le lien). Cependant lors du test, je découvre que les secteurs en questions sont vides avant d’avoir supprimé le fichier.

Avec un peu de recherche, j’ai trouvé ce blog donnant une piste de réponse. Il faudrait en réalité faire une addition sur l’alignement des partitions pour obtenir la bonne valeur. J’ai réalisé l’exercice ici avec un résultat différent mais tout autant décevant : une série de bytes aléatoires dans le secteur calculé mais pas supprimés à la suppression du fichier. Erreur de calcul ? Non activation de TRIM ? Problème avec la couche d’abstraction LUKS+LVM ? Mystère. Si quelqu’un a plus d’idées, je suis sur que ça en intéressera certains.

Conclusion et limites du chiffrement

La sécurité obtenue n’est pas absolue mais est déjà très raisonnable. Chiffrer votre disque ne vous protègera pas complètement. Il n’y a pas de problèmes si vous perdez votre machine hors ligne (pas de logiciels types Prey donc, pas grave, je préfère) mais il existe des cas plus problématiques. Le fait d’entrer votre mot de passe signifie qu’un attaquant ayant accès à votre PC est potentiellement capable de modifier vos fichiers de boot pour y ajouter un keylogger par exemple. Le cas de la clef sur une clef USB diminue un peu ce risque mais est assez contraignant (à vous de voir votre besoin de sécurité).

L’activation de TRIM réduit également la sécurité. Lisez cet article par un développeur de cryptsetup pour comprendre le problème. En autorisant le système à vider tous les secteurs inutilisés, l’on va permettre la détection des secteurs utilisés et ceux qui ne le sont pas. C’est pour cela que j’affirmais plus haut que le remplissage du disque de random était sans doute inutile. Cela ne me pose pas de problème pour mon utilisation de la machine mais il faut le savoir.

Une fois la partition déchiffrée, LUKS ne sert plus à rien évidement. Le chiffrement ne vous protègera pas contre les failles de sécurité dans vos logiciels. Il est par exemple important de verrouiller votre machine en veille. Il n’est pas à ma connaissance possible d’accéder au contenu d’une machine verrouillée sans faire d’attaque physique (contenu de la RAM par ex), vous devriez donc être à l’abri des script-kiddies si vous n’avez pas laissé de failles grosse comme une maison (si vous voulez vous protégez contre des gouvernements, pensez qu’ils possèdent d’autres moyens d’obtenir les clefs que de pirater votre machine).

Au passage, je conseille vivement de ne pas choisir le même mot de passe pour LUKS et votre utilisateur. Il est beaucoup plus facile d’avoir un keylogger ou d’accéder aux hash de votre mot de passe lorsque votre machine fonctionne.

Bonus : détectez les modifications de /boot

Je disais plus haut que le problème sécurité le plus préoccupant avec cette configuration était qu’une manipulation offline pouvait tout foutre en l’air. Si l’attaquant arrive à bidouiller les fichiers dans /boot, il peut faire beaucoup ; même une clef présente sur une clef USB peut se faire enregistrer avec un script malveillant. Heureusement, une publication dans ct-magazine donne une solution : vérifier le hash du MBR et des fichiers une fois la partition déchiffrée (les hash sont chiffrés évidement). Si une modification est détectée, grosse alerte !

Téléchargez l’archive suivante 1203-146.zip par l’auteur de l’article ou ma version qui utilise sha256 plutôt que sha1 (qui, si toujours sécurisé aujourd’hui, risque de ne plus l’être très longtemps, surtout depuis cette nouvelle attaque), ajoute le fichier de config systemd, enlève un espace au début de chkboot.sh (plantait le lancement de systemd) et ajoute un fichier .desktop pour chkbook_user.sh. Si je suis pas trop sympa quand même !

# cp chkboot.sh /usr/local/bin/chkboot.sh
# chmod +x /usr/local/bin/chkboot.sh
# cp chkboot_user.sh /usr/local/bin/chkboot_user.sh
# chmod +x /usr/local/bin/chkboot_user.sh
# cp chkboot@.service /etc/systemd/system/chkboot@.service
# systemctl enable chkboot@.service
# mkdir /var/chkboot
# /usr/local/bin/chkboot.sh

Le script chkboot_user.sh utilise zenity pour les alertes. Veillez à ce qu’il soit installé. Démarrez ce script au démarrage de votre session. Cela peut se faire via l’interface graphique de window manager préféré ou en ajoutant un fichier .desktop (comme celui dans l’archive que je vous donne par exemple) dans le dossier ~/.config/autostart/. Dans le cas de E17, le dossier autostart n’est pas utilisé (juste pour GNOME, KDE et XFCE). À la place, placez le fichier .desktop dans ~/.local/share/applications/ et allez ensuite dans settings > apps > startup applications >
applications
et activez l’application Chkboot.

alert-changes-detected-boot
Avec la liste des fichiers ayant changé de hash

Attention, le script chkboot.sh est à faire tourner à chaque modification du dossier /boot. Cela peut arriver souvent sous Archlinux (à chaque mise à jour du noyau ou de grub par exemple). Si vous trouvez cela trop contraignant, n’hésitez pas créer un alias type alias sysupdate="/usr/local/bin/chkboot_user.sh && yaourt -Syua && /usr/local/bin/chkboot.sh".

PS : j’ai commencé la rédaction de la page en français au sujet de LUKS sur le wiki archlinux.fr. N’hésitez pas à venir contribuer, c’est encore bien vide.

Django, les commentaires, les URLs et la modularité

En cours d’écriture Kopi, de mon moteur de blog utilisant Django, je suis arrivé au stade des commentaires. Django propose déjà un module de commentaire assez pratique (avec honeypot, protection csrf…). En voulant rester dans la philosophie « less code is better » (particulièrement quand ça vient de moi), j’ai cherché à l’utiliser et l’étendre pour mes besoins. J’ai passé pas mal de temps à bidouiller et fouiller le code (pas toujours très clair la doc Django) et voulais donc partager mes trouvailles (et entendre vos critiques au cas où je suis complètement à coté de la plaque).

Un des points très important pour mon module de commentaire était de rester indépendant. Si un jour je créais une galerie de photo et que je voulais utiliser mon module sans rien devoir y changer. Il ne pouvait avoir AUCUNE référence à mon module de blog dans le module de commentaire. C’est le module de blog qui utilise le module de commentaire, pas l’inverse. Ce point n’est souvent pas respecté dans les exemples glanés sur le net et pourtant est tout à fait possible, ce grâce à la modularité de Django.

Un des problèmes que j’ai rencontré était la redirection des urls. Nativement, Django me redirige chaque post vers une url /posted/ avec un moche « Thank you for posting » digne d’un « It works » post-installation apache. Heureusement pour éviter cela, Django supporte dans son formulaire une balise <input name="next"/> dont la valeur est, comme on pourrait s’en douter, l’url de la page vers laquelle rediriger. Voici comment je m’y suis pris.

D’abord l’architecture de mon projet :

blog/
	templates/
		blog/
			post_detail.html
			...
comments/
	templates/
		comments/
			form.html
			...
	views.py
	urls.py
	...
kopi/
	settings.py
	urls.py
	...

Dans mon fichier kopi.urls.py, j’inclus de manière habituelle les urls concernant mon blog et mes commentaires, aucun lien entre les deux.

urlpatterns += patterns('',
url(r'^comments/', include('comments.urls')),
url(r'^', include('blog.urls')),
)

Du coté du blog, je gère donc mes articles. Une simple Generic View fait très bien ça en 5 lignes. Dans ma template blog/templates/blog/post_detail.html, j’inclus ma template de cette façon :

{% load comments %}
{% render_comment_form for current_post %}

où current_post est le nom de ma variable contenant mon article de blog.

Et voila pour le coté, blog, le reste est du coté des commentaires. Pour le module de base, je suis parti de ce qui a été fait dans django-basic-apps, l’import des méthodes de django.contrib.comments pour que mon module comments fasse exactement la même chose que celui dans les contrib sans avoir à l’importer dans les settings (c’est même conseillé, django est assez chiant quand deux apps portent le même nom). Le fichier comments/urls.py sera par exemple :

from django.conf.urls.defaults import *
from django.contrib.comments.urls import urlpatterns

urlpatterns = patterns('comments.views',
# let's overwrite django.contrib.comments
url(r'^post/$',
view='custom_comment_post',
name='comments-post-comment'),

url( r'^', include( 'django.contrib.comments.urls' ) ),
)

Je reprend donc toutes les urls de django.contrib.comments.urls mais en forçant à utiliser ma view custom_comment_post plutôt que celle de base (Django cherche un match dans l’ordre et s’arrête au premier rencontré). Si vous voulez étendre le modèle pour y ajouter des champs, la logique est la même (Customizing the comments framework).

D’abord, je voulais réécrire ma template de formulaire pour qu’il connaisse la valeur next. J’ai donc créé le fichier comments/templates/comments/form.html avec (version simplifiée)

{% load comments i18n %}

{% csrf_token %}

{% if next %}

{% else %}

{% endif %}

{% for field in form %}

{{ field.label_tag }} {{ field }}

{% endif %}
{% endfor %}

Ainsi soit l’on passe une valeur choisie avec la variable next, soit on est redirigé vers l’url de l’objet pour lequel on affiche le formulaire. En mettant le for current_post dans le render_comment_form, j’indique ainsi que current_post sera le target_object. Et ça c’est très cool parce que ça veut dire que pour ma galerie d’image, je n’aurai qu’à faire {% render_comment_form for current_image %} et ne pas changer ma template form.html.

Après un test qu’est ce qu’on voit ? Le formulaire présent sur /monarticle/ fait une requête POST vers /post/, réponse depuis /posted/ (la page moche) mais ne reste pas dessus car a un header avec le code 302, redirection vers /monarticle/?c=42 où 42 est l’ID du commentaire.

Parfait ? Non pas encore complètement, j’aimerais que le visiteur aie une ancre qui l’amène au niveau de son commentaire. Je n’ai pas besoin de ce vieux ?c=42, par contre un #c42, ça m’intéresserait déjà un peu plus… Justement, c’est là qu’intervient mon overwrite au dessus avec la view custom_comment_post. On va forcer à récupérer l’url du commentaire plutôt que l’url du post un peu modifiée.

def custom_comment_post(request, next=None, using=None):
# on utilise encore le code de la classe contrib
response = contrib_comments.post_comment(request, next, using)

if type(response) == HttpResponseRedirect:
# url in the form /redirect_path/?c=comment_id
redirect_path, comment_id = response.get('Location').split( '?c=' )
if comment_id:
# get the related comment
comment = Comment.objects.get( id=comment_id )
if comment:
# url in the form /comments/cr/object_id/comment_id#ccomment_id
return HttpResponseRedirect( comment.get_absolute_url() )

return response

Et là on laisse la magie de Django travailler, la fonction get_absolute_url du module de commentaire de base est bien foutu et va résoudre l’url /monarticle/#c42. Il vous suffit alors dans votre template d’ajouter une ancre pour ce format.

{% for comm in comment_list %}

{% endfor %}

Vous n’aimez pas le format #c{{id}} ? Vous savez que vous êtes compliqué ? Mais ça tombe bien, Django a prévu ça. La fonction comment.get_absolute_url() prend un paramètre optionnel anchor_pattern="#c%(id)s". Il suffira d’appeler la fonction avec par exemple "#commentaire-id-%(id)s" pour obtenir quelque chose de plus agréable.

J’anticipe la question : que faire si je veux que sur mon application de blog j’ai #commentaire-post-%(id)s et sur ma galerie #commentaire-photo-%(id)s. Et bien là j’avoue que je n’ai pas encore trouvé de moyen simple de le faire. On peut y arriver en écrivant un nouveau templatetag (un overwrite de render_comment_form par exemple) mais franchement, ça fait beaucoup de boulot pour pas grand chose. Si vous avez une suggestion simple et élégante, je suis preneur.