Sam & Max » delete http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Soyez paranos avec les codes de suppression 13 http://sametmax.com/soyez-paranos-avec-les-codes-de-suppression/ http://sametmax.com/soyez-paranos-avec-les-codes-de-suppression/#comments Sat, 13 Apr 2013 21:25:23 +0000 http://sametmax.com/?p=5663 L’informatique est généralement une discipline rationnelle (sauf bien entendu dans le cas de l’administration d’un système Windows), et pondre un bout de code pour un cas d’utilisation qui n’aura jamais lieu n’est pas vraiment la qualité première qu’on demande à un développeur.

Sauf. Sauf dans un cas.

Dans le cadre d’un code de suppression.

Au delà de RM

Un code de suppression peut se cacher derrière bien plus qu’un remove :

  • Renommer ou déplacer une donnée ou un fichier.
  • Écrire par dessus ou à la suite du conteneur de données (tel qu’un fichier ou une table).
  • Mettre à jour la donnée elle-même.

C’est valable dans les scripts shell, mais également dans les scripts Python (ou tout langage qui vous sert à faire des opérations sur des systèmes critiques) : os.remove, os.rename, shutil.move, open('file', 'w').write('...'), etc.

Mais c’est aussi toutes les requêtes SQL en écriture.

Les codes à surveiller le plus sont ceux qui peuvent entrainer une suppression massive :

  • SELECT * UPDATE ou DROP table
  • rm -fr ou tout code manipulant un fichier matché avec un glob (*)
  • os.removes, shutil.rmtree, tout code qui manipule un fichier dans une boucle ou un appel récursif

Mais tous les autres restent importants si tant est que la donnée cible est critique (contenu de l’utilisateur, donnée non récupérable par un autre moyen ou alors de manière lente et coûteuse). Même quand vous avez un backup dont vous êtes certains de la perfection – et franchement je me méfie toujours des backups – une restauration, c’est long, ça implique souvent une interruption de service ou au moins une dégradation, et de toute façon c’est du taff dont vous n’aviez vraiment pas besoin.

Pensez au petit fils de Murphy et François Perrin

Quand on a un petit site ou un logiciel en local de taille moyenne, on ne pense pas à ce genre de choses. Mais quand on travaille avec des grosses DB qui sont tout le temps sollicitées et des systèmes de fichiers qui sont blindés de Tera octets de contenus qui prennent des semaines à uploader sur un serveur (j’ai bien dis des SEMAINES), on ne veut pas que ça disparaisse.

La réplication et le load balancing peuvent vous sauver d’un plantage matériel ou d’un bug logique. Mais une corruption de données en cascade, ça se propage à tout un système. Et un RM massif, c’est exactement ça, surtout à l’heure des surcouches d’abstraction d’outils de haut niveau automatiques.

Alors vous allez me dire que les chances que ça arrive sont minimes. En fait votre code est propre, vous faites des checks normaux, il n’y a pas d’injections possibles, tout est sanitizé, bref, il n’y a pas de raison que ça chie.

Et vous n’auriez pas tort, si il n’y avait pas 2 importantes particularité du monde de l’informatique :

  • En informatique tout est multiplié par des milliards en quelque seconde. Une opération qui à très peu de chance d’arriver (une chance sur 10 milliards, c’est plus bas que de gagner au loto), peut donc survenir très facilement puisque l’opération est répétée un très grand nombres de fois en un temps très court.
  • Vous n’avez pas la main sur tout le système. Ce n’est pas sous votre contrôle. La complexité matérielle est logiciel est telle que vous n’en comprenez qu’un centième, et ce blob néo punk dont vous devez vous occuper est en interaction avec un environnement technique et humain gigantesque, parfaitement imprévisible, qui se renouvelle sans cesse.

Et comme la gravité des conséquences d’une merde de suppression massive est du genre “Code Rouge” dans “28 semaines plus tard”, vous ne pouvez pas vous permettre de ne pas blinder les suppressions.

Quelques scénarios à la con qui peuvent vous tuer sur un code aussi con que ça :

from fabric import cd, sudo
 
from django.conf import settings
 
 
...
 
for content in contents:
 
    if not content.published:
 
        with cd(settings.MEDIA_ROOT):
            sudo('rm -fr {}'.format(content.path))

A priori, rien de dangereux : vous contrôlez vos settings, vous contrôlez votre base de données, pas de malveillances, vous vérifiez que le contenu à expiré. Que peut-il arriver de mauvais ?

Plein de choses :

  • Vous avez merdé dans un merge Git octopussien entre votre fichier, celui de votre collègue qui vient de partir en retraite et celui de la démo de l’open coffee dans laquelle vous avez fait un quick fix après un effet démo bien humiliant. Il se glisse un early return est inséré dans la propriété content.published et comme Python retourne None par défaut, tous les contenu sont supprimés.
  • Vous êtes fatigués, vous avez oublié de changer le local_settings et MEDIA_ROOT n’a pas la même valeur. Vous effacez dans le répertoire de prod ou lieu du répertoire de test.
  • content.path est éditable dans l’admin privée de Django. En restaurant un formulaire avec une extension Firefox, il se glisse une étoile que vous ne remarquez pas dans le nom du contenu automatiquement utilisé pour créer le chemin de fichier, et vous faites un rm -fr avec un beau * dedans.
  • Vous discutez avec votre voisin et tapez du code dans le shell en prod pour faire une migration bénin après vous êtres dit que “c’est pas la peine de lancer south pour ça”. Grâce à la magie du cerveau humain vous tapez ce qu’il vous dit au lieu ce la commande que vous vouliez taper, et la moitié de votre try est excécuté, mais pas le code du except, par contre le finally a bien tout défoncé. Pardon, nettoyé.
  • Vous avez un stagiaire.

Le plus drôle, c’est quand plusieurs trucs improbables se cumulent, là on peut faire des combos qui déchirent bien toute l’infrastructure.

Les tests unitaires et la sanitization en vous protégera pas de la fatigue, inattention, l’éternuement au dessus en pleine session SSH, le chat qui saute sur le clavier… Souvenez-vous de la mouche dans Brazil.

Vous protéger contre vous même

Sur des codes comme ça, il faut donc prendre des mesures supplémentaires. Faire les vérifications qu’on ne fait jamais, de ce qui ne peut pas arriver.

 
import re
 
from fabric import cd, sudo
 
from django.conf import settings
 
 
...
 
for content in contents:
 
    # stylistiquement on ne fait jamais ça en Python, mais ici, on ne prend
    # pas le risque
    if content.expired == False: # None != False
 
        # on évite autant que possible les CD qui peuvent merder grave
        path = os.path.join(settings.MEDIA_ROOT, content.path)
 
        # on check strictement le chemin qu'on va degommer avec une regex qui
        # ne laisse pas la place "/", "/bidule/*" et autre ".."
        # et qui réplique au plus prêt la structure de l'arbo qu'on vise
        if re.match(r'^regex_de_nazi$', path):
            sudo('rm -fr {}'.format(path))

Idéalement il faudrait avoir les droits nécessaires pour éviter le sudo. On peut même exceptionnellement casser le DRY en mettant MEDIA_ROOT en dur dans cette fonction. C’est à double tranchant, mais on ne vit pas dans un monde idéal.

Une regex qu’on a en production ressemble à ça:

re.match(r’^/[\w-]+/[\w-]+/\d/\d/\d/\d/\d/\d+$’, path):

Qui ne laissera passer qu’une suppression d’un truc qui à la forme “/1nom-de_dossier/una_autre-nom/8/1/9/3/5/890709″. Pas une arbo plus courte ou plus longue. Pas d’étoiles ou de guillemets. Pas un dossier qui ne contienne pas exactement cet enchaînent de sous-dossiers numériques.

Et ce, malgré le fait que path est généré depuis un ID extrait d’un auto ID en DB (non accessible dans l’admin). Parce qu’on ne sait jamais. Et on ne peut pas se permettre que ça pète.

Pareil pour le SQL, n’hésitez pas à faire des requêtes en plus avant le DELETE pour voir si les données sont cohérentes. La plupart du temps on s’en branle si une suppression est lente.

Bref, les bonnes pratiques que sont de bien nettoyer ses données, de bien setter ses permissions et de ne pas tout lancer à 3 heures du matin après une session de debugging de 6 heures au Red Bull sont toujours des priorités. Cependant souvenez vous que vous êtes humains, vous allez faire des conneries, les autres aussi.

Vous allez avoir un système imparfait (non, vous n’avez pas mis des checks et des convertions partout, le 100% ça ne marche que dans les labos de recherche, pas quand on a une dead line dans 15 jours).

Alors soyez paranos avec les codes de suppression.

Ce n’est pas une grosse partie du code, ça ne va pas tuer votre productivité ou plomber votre maintenance, mais contrairement aux autres snippets, vous n’aurez probablement pas de seconde chance pour le debug.

C’est l’histoire d’un mec…

Bon ok, de 3 mecs dans un bagnole : un matheux, un physicien et un informaticien.

A la sortie d’un virage, la voiture dérape et s’arrête deux roues dans le vide.

Les 3 compères sortent de la voiture, un peu choqués, et commencent à discuter :

Le mathématicien : “Il faudrait qu’on calcule la probabilité que ça nous arrive sur ce virage, avec cette voiture, dans ces conditions climatiques.”

Le physicien : “Il faudrait qu’on calcule le coefficient de friction et notre énergie cinétique pour mieux comprendre ce qui nous est arrivé.”

L’informaticien : “On reprend le virage avec la caisse pour voir si c’est reproductible ?”

]]>
http://sametmax.com/soyez-paranos-avec-les-codes-de-suppression/feed/ 13