Journal Migration complète vers Bitwarden à l’aide de rbw

Posté par  . Licence CC By‑SA.
34
16
août
2020

Avec un premier commit en avril 2014, on peut dire que je suis un utilisateur de longue date de pass. Enfin, j’étais. J’ai changé pour Bitwarden en auto-hébergé, et ça raconte un peu cette migration, ainsi que le pourquoi.

pass avait tout pour me plaire :

  • En ligne de commande
  • Synchronisé à l’aide de git
  • Le chiffrement s’appuie sur une solution reconnue (gpg)
  • Libre
  • Stockage simple à l’aide de fichiers
  • Plusieurs extensions de navigateurs disponibles

Bien sûr, le chiffrement avec gpg, ça demande un peu de technique initiale, mais une fois que c’est en place, ce n’est plus trop un problème.

Tout ça, ça va bien quand on est tout seul. Et puis arrive la famille, et le besoin de partager. Partager ses outils (c’est quand même cool un gestionnaire de mots de passe), et partager ces mots de passe. La CAF par exemple utilise un compte pour toute la famille. Il peut être également rassurant de partager certains comptes importants comme le compte Améli pour la CPAM. Ou encore les comptes des Espaces Numériques de Travail de ses enfants trop jeunes pour en gérer l’accès.

pass ne permet pas vraiment de partager. Enfin si, mais c’est tout ou rien. J’ai bien vu gopass qui est une alternative sérieuse à pass, avec une API compatible, et des outils supplémentaires pour travailler en équipe. Mais voilà, il me fallait également un outil clicodrome, sympa, bref une belle IHM, parce que si pour moi, la ligne de commande est plutôt rassurante, ce n’est pas le cas de tout le monde. J’ai cherché des interfaces pour pass ou gopass, mais là, c’est un peu plus la dèche. Ça ne m’a pas vraiment étonné.

Donc, je suis allé voir ailleurs. Je vous passe les différents choix, de toutes façons, je les ai oubliés, et je suis tombé sur Bitwarden. Vous le connaissez sûrement, c’est un gestionnaire de mots de passe libre et qui se rémunère à l’aide d’offre d’hébergements. Le prix n’est pas exorbitant d’ailleurs, puisque ça va du compte gratuit au compte à 1$ par mois pour 5 utilisateurs.

Mais moi, je veux auto-héberger. C’est possible avec l’outil officiel mais il faut quand même .NET Core et SQL Server, c’est un peu trop. Je sais que Microsoft fait un grand effort pour le libre (allez, faut le reconnaître), mais je ne suis pas encore prêt à faire autant de pas à la fois.

Et ça tombe bien, parce qu’il n’y a pas besoin, grâce à une réimplementation en Rust : bitwarden_rs. Installer un outil en Rust, ça, ça me branche. Par contre, c’est à utiliser avec Docker. Ah, bon. Je vous passe le pourquoi du comment, mais je n’ai pas envie d’avoir un outil qui tourne dans un conteneur Docker en production.

Et ça tombe bien (bis), parce qu’il n’y a pas besoin, grâce à une chaîne de fabrication de paquet Debian : bitwarden_rs-debian. Ça utilise Docker, mais juste pour faire un paquet Debian. Je dis banco.

bitwarden_rs embarque l’interface Web de Bitwarden, ce qui évite de réinventer la roue quand elle marche bien. Cette interface ne sert au final pas beaucoup, puisqu’on utilise principalement les extensions du navigateur ou l’outil en ligne de commande. Mais quand on se retrouve sur un autre ordinateur sans ligne de commande, on est bien content de l’avoir.

Cet outil remplit presque toutes les cases :

  • Interface Web utilisable par n’importe qui
  • Extension de navigateur idem
  • Gestion des organisations avec collections partagées, tout en gardant certaines entrées privées
  • Une cryptographie correcte PBKDF2 SHA256. Une demande vers Argon2 est en cours, sans trop de retour pour l’instant.

Par contre, il me faut un outil en ligne de commande. J’utilise mon gestionnaire pour stocker des mots de passe utiles à d’autres outils en ligne de commande, et c’est indispensable de pouvoir y accéder de cette manière. J’ai essayé l’outil officiel, et c’est une grosse blague : c’est en Javascript. Vous pouvez aller voir mon problème mais grosso modo, attendre 6 secondes pour avoir un mot de passe, c’est pas très 2020.

Et ça tombe bien (ter), un autre gentil développeur s’est fendu d’un outil alternatif : rbw. Ça s’installe avec cargo install rbw, et ça fait à peu près la même chose que gpg utilisé par pass, en utilisant notamment pinentry. Et ça va vite.

Depuis la conjonction de ces outils, j’ai tout migré sur ma propre instance de Bitwarden. pass m’a bien servi, je lui dois beaucoup, mais je ne le regrette pas.

  • # Enjeux de sécurité

    Posté par  (site web personnel) . Évalué à 3.

    Merci pour la découverte de rbw ! Je suis content de voir qu'il existe un client alternatif, qui ne soit pas développé par Bitwarden, inc. Ça me rassure quand à la sécurité et la résilience de l'ensemble.

    J'avais hésité à héberger une instance familiale de bitwarden_rs avec yunohost, mais je ne l'ai pas fait pour des raisons de sécurité. La sécurité du client web de bitwarden est au maximum la sécurité du système qui l'héberge : si mon serveur se retrouve compromis d'une façon ou d'une autre, l'attaquant aura la possibilité de modifier le code envoyé aux clients de façon à exfiltrer les mots de passe.

    Le même problème se pose pour le client web officiel, mais je fais davantage confiance à bitwarden inc. pour sécuriser très solidement leurs serveurs.

    Du coup, ton expérience m'intéresse : as-tu pris en compte cette question de sécurité ? As-tu mis en place des défenses plus spécifiques ?

    • [^] # Re: Enjeux de sécurité

      Posté par  . Évalué à 10.

      La réponse rapide est : pas vraiment, non.

      Mon modèle de menace part du principe que je ne cherche pas à éviter les attaques ciblées. Si quelqu'un cherche à me nuire spécifiquement, je n'aurais pas les moyens de l'éviter. Donc mon serveur est « sécurisé », je gère mon courriel et d'autres services, je le surveille aussi (c'est important), je le mets à jour régulièrement (presque tous les jours), mais je ne l'ai pas particulièrement durci. J'utilisais grsecurity de Debian jusqu'à il y a peu, mais le noyau commençait à se faire trop vieux (c'est un 4.9.0).

      Je pense que ces protections suffisent pour éviter les attaques « simples », non ciblées, qui cherchent des serveurs ouverts ou non mis-à-jour. Je pars du principe qu'actuellement, il est beaucoup plus rentable pour un attaquant de cibler un gros silo plutôt que mon serveur qui ne contient que quelques informations à revendre. Et si la motivation n'est pas l'argent, il y a plein d'autres moyens de me nuire.

  • # Export des mots de passe

    Posté par  (site web personnel) . Évalué à 4.

    On a rapidement une grosse liste de mots de passe quand on utilise un mot de passe différent pour chaque site ou application…
    Du coup, importer tous ses mots de passe peut-être fastidieux…
    J'ai une instance locale Pass, qui remplit bien sa fonction, mais un peu plus de synchro pourrait m'aider…
    Du coup, j'envisage un basculement, mais dans ta description tu ne parles pas du transfert des mots de passe de Pass à Bitwarden. Comment as-tu fait?

    • [^] # Re: Export des mots de passe

      Posté par  . Évalué à 5.

      Oui, j'ai fait un script, méthode La Rache. La variable STORE est à changer, et le résultat est à copier-coller dans l'import générique CSV de l'interface Web de Bitwarden.

      Attention, pass supporte beaucoup de formats différents. À vrai dire, il n'y a même pas de formalisme imposé, ce qui est à la fois une force et une faiblesse. Donc les champs exportés ne seront peut-être pas bons…

      #!/usr/bin/env python3
      
      import sys
      import pathlib
      import subprocess
      import csv
      
      from typing import List
      from dataclasses import dataclass
      
      STORE = "/home/user/.password-store"
      
      
      @dataclass
      class PasswordEntry:
          folder: str
          login: str
          password: str
          uris: List[str]
      
      
      def parse_passfile(store: pathlib.Path, passfile: pathlib.Path):
          run = subprocess.run(
              [
                  "/usr/bin/gpg",
                  "--decrypt",
                  "--quiet",
                  "--yes",
                  "--compress-algo=none",
                  "--no-encrypt-to",
                  store / passfile,
              ],
              text=True,
              capture_output=True,
          )
          if run.returncode != 0:
              print("Decrypting failed")
              return None
      
          content = run.stdout.splitlines()
          password = content[0]
          uris = []
          login = ""
          folder = ""
          for line in content:
              field_line = line.split(":", 1)
              if len(field_line) == 1:
                  continue
              field_name, field_value = [field_line[0].lower(), field_line[1]]
              if field_name in ["password", "pass", "secret"]:
                  password = field_value
              elif field_name in ["login", "username", "user"]:
                  login = field_value
              elif field_name in ["uri", "url", "website", "site", "link", "launch"]:
                  uris.append(field_value)
      
          if not uris:
              uris = [passfile.stem]
              folder = str(passfile.parent)
      
          return PasswordEntry(folder, login, password, uris)
      
      
      def generate_row(entry: PasswordEntry):
          # folder,favorite,type,name,notes,fields,login_uri,login_username,login_password,login_totp
          return [
              entry.folder,
              "",
              "login",
              entry.uris[0],
              "",
              "",
              entry.uris[0],
              entry.login,
              entry.password,
              "",
          ]
      
      
      def iterate_passfile(store_dir: pathlib.Path):
          writer = csv.writer(sys.stdout)
          writer.writerow(
              [
                  "folder",
                  "favorite",
                  "type",
                  "name",
                  "notes",
                  "fields",
                  "login_uri",
                  "login_username",
                  "login_password",
                  "login_totp",
              ]
          )
          for passfile in sorted(
              [p.relative_to(store_dir) for p in store_dir.glob("**/*.gpg")]
          ):
              passname = passfile.stem
              dirname = passfile.parent
              entry = parse_passfile(STORE, passfile)
              writer.writerow(generate_row(entry))
      
      
      if __name__ == "__main__":
          iterate_passfile(pathlib.Path(STORE))
      • [^] # Re: Export des mots de passe

        Posté par  (site web personnel) . Évalué à 2.

        Merci pour le script.
        C'est sur ce genre de retour que je me rends compte que je ne suis vraiment qu'un utilisateur basic, même si la ligne de commande ne me fait pas peur.
        Dans la célèbre analogie de la voiture, je serais le gars qui fait sa vidange et change les plaquettes de frein lui même, mais qui n'irait pas plus loin…

        Du coup, merci bien. Adapter un script pour les champs et les variables, ça reste de mon niveau…

    • [^] # Re: Export des mots de passe

      Posté par  (Mastodon) . Évalué à 4. Dernière modification le 17 août 2020 à 12:06.

      Je crois que j'avais utilisé ça pour la même migration : pass2csv

      Du coup cela va très facilement.

  • # chemin inverse

    Posté par  (site web personnel) . Évalué à 4.

    Pour ma part j'ai fait le chemin inverse, de Bitwarden à pass (en passant par Gopass). Justement à cause de la CLI inutilisable, bon à savoir qu'il existe une alternative qui semble tenir la route, je jetterai un coup d'oeil à rbw. Merci.

  • # Vault

    Posté par  . Évalué à 3.

    Est-ce que Vault a été envisagé ? Et si oui, pourquoi n'a t-il pas été sélectionné ?

    • [^] # Re: Vault

      Posté par  . Évalué à 3.

      J'ai dû le voir passer, mais non, je ne l'ai jamais vraiment envisagé. La cible annoncée est clairement pas le noyau familial :

      Vault is a complex system that has many different pieces. To help both users and developers of Vault build a mental model of how it works, this page documents the system architecture.

  • # Questions bêtes

    Posté par  (site web personnel) . Évalué à 4.

    La base de ta migration est le problème des utilisateurs multiples. Pour j'ai l'impression que pass est capable de le faire, mais n'étant pas expert dans le système, c'est possible que mon raisonnement sois biaisé. Ce commentaire est là autant pour que l'on me corrige si je me trompe que d'apporter une autre solution à ton problème :)

    Il est possible de définir plusieurs clefs de chiffrement dans pass, à travers les fichiers .gpg-id. La commande pass init [-p sous_dossier] clef1 clef2 va chiffrer tous les fichiers présents dans le répertoire donné avec les clefs en question. Du coup, tu peux décider d'avoir :

    • Autant de dossier privés qu'il y a de membres dans ta familles
    • Un dossier commun vers les comptes partagés

    Ta clef publique est importée dans le trousseau de ta compagne, et réciproquement, ce qui permet à chacun de signer les mots de passes conjointement dans le dossier commun. Par contre, tu ne peux pas déchiffrer les mots de passe des autres membres de ta famille car ta clef privée n'est pas partagée.

    Je suis en train de me poser des questions pour migrer sous pass, et je voudrai être sûr de bien comprendre avant de le généraliser à la maisonnée :)

    • [^] # Re: Questions bêtes

      Posté par  . Évalué à 3.

      Non, globalement, c'est bien ça. Par contre, j'ai oublié de mentionner que le nom du fichier n'est pas chiffré par pass. Or pour pouvoir proposer une extension navigateur qui sait remplir les formulaires, il faut mettre le nom du site web dans le nom du fichier (ou d'un répertoire parent). Ce qui pose un certain problème de confidentialité.

      On a tous un jardin secret, et même quand c'est pas le cas, notre esprit est toujours inconsciemment rassuré de savoir qu'on peut cacher des choses sans risque.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.