documentation/services/belenios.md

291 lines
11 KiB
Markdown

# Belenios
Belenios est un service développé par l'Inria recréant un bureau de vote
complet en ligne avec autant voire plus de sécurité grâce à des bulletins
chiffrés. Il a été mis en place pour l'assemblée générale du Crans en
2020, alors que la pandémie de Covid-19 empêchait toute réunion physique.
## Installation
On considère que l'on dispose d'une machine virtuelle neuve installée sous
Debian Bullseye.
Belenios n'est malheureusement pas encore packagé dans Debian. Par chance,
toutes ses dépendances le sont. On installe donc toutes les dépendances via
APT, puis on compile Belenios manuellement.
### Dépendances
Belenios est intégralement écrit en OCaml. On installe toutes ses
dépendances Debian, comme indiqué dans le README :
```bash
sudo apt update
sudo apt install --no-install-recommends bubblewrap build-essential ca-certificates cracklib-runtime jq libgd-securityimage-perl libgmp-dev libncurses-dev libpcre3-dev libssl-dev libsqlite3-dev m4 pkg-config unzip wget zip zlib1g-dev
```
On installe ensuite toutes les dépendances OCamL depuis les paquets Debian, ce
qui est plus économe par rapport à compiler toutes les bibliothèques avec
Opam :
```bash
sudo apt install --no-install-recommends dune eliom libatdgen-ocaml-dev libcalendar-ocaml-dev libcmdliner-ocaml-dev libcryptokit-ocaml-dev libcsv-ocaml-dev libgettext-ocaml-dev libzarith-ocaml-dev
```
On doit enfin installer `ocsigenserver`, qui sert de serveur web tel Nginx ou
Apache adapté pour les projets OCaml :
```bash
sudo apt install --no-install-recommends ocsigenserver
```
### Compilation de Belenios
On commence par cloner le dépôt dans un endroit adapté :
```bash
sudo git clone https://gitlab.inria.fr/belenios/belenios.git /opt/belenios -b 1.14
```
Il suffit ensuite dans le dossier `/opt/belenios` de lancer
`make build-release-server`.
### Liens symboliques
Pour avoir des binaires à des endroits cohérents avec la distribution Debian,
on ajoute des liens symboliques à des endroits pertinents :
```bash
sudo ln -s /opt/belenios/_run/usr/lib/belenios /usr/lib/ocaml/belenios
sudo ln -s /opt/belenios/_run/usr/lib/belenios-platform /usr/lib/ocaml/belenios-platform
sudo ln -s /opt/belenios/_run/usr/lib/belenios-platform-js /usr/lib/ocaml/belenios-platform-js
sudo ln -s /opt/belenios/_run/usr/lib/belenios-platform-native /usr/lib/ocaml/belenios-platform-native
sudo ln -s /opt/belenios/_run/usr/lib/belenios-server /usr/lib/ocaml/belenios-server
sudo ln -s /opt/belenios/_run/usr/lib/belenios-tool /usr/lib/ocaml/belenios-tool
sudo ln -s /opt/belenios/_run/usr/share/belenios-server /usr/share/belenios-server
```
### Configuration de ocsigenserver
On commence par créer les dossiers de données qui nous intéressent, avec les
bonnes permissions :
```bash
sudo -u ocsigen mkdir -p /etc/ocsigenserver/conf.d /var/lib/belenios /var/lib/belenios/data /var/lib/belenios/upload /var/lib/belenios/spool /var/log/belenios
```
On ajoute enfin le fichier de configuration fourni par Belenios dans
`/etc/ocsigenserver/conf.d/belenios.conf`, en remplaçant les bonnes
variables :
```xml
<!-- -*- Mode: Xml -*- -->
<ocsigen>
<server>
<port>8001</port>
<logdir>/var/log/belenios</logdir>
<datadir>/var/lib/belenios/data</datadir>
<uploaddir>/var/lib/belenios/upload</uploaddir>
<!--
The following limits are there to avoid flooding the server.
<maxuploadfilesize> might need to be increased for handling large
elections.
<maxconnected> is related to the number of simultaneous voters
visiting the server.
-->
<maxuploadfilesize>1024kB</maxuploadfilesize>
<maxconnected>500</maxconnected>
<commandpipe>/var/run/ocsigenserver_command</commandpipe>
<charset>utf-8</charset>
<findlib path="/usr/lib/ocaml"/>
<extension findlib-package="ocsigenserver.ext.staticmod"/>
<extension findlib-package="ocsigenserver.ext.redirectmod"/>
<extension findlib-package="ocsigenserver.ext.ocsipersist-sqlite">
<database file="/var/lib/belenios/data/ocsidb"/>
</extension>
<extension findlib-package="eliom.server"/>
<extension findlib-package="belenios-platform-native"/>
<host charset="utf-8" hostfilter="*" defaulthostname="{{ belenios.domain }}">
<!-- <redirect suburl="^$" dest="http://www.example.org"/> -->
<site path="static" charset="utf-8">
<static dir="/usr/share/belenios-server" cache="0"/>
</site>
<site path="monitor">
<eliom findlib-package="eliom.server.monitor.start"/>
</site>
<eliom findlib-package="belenios-server">
<!-- Domain name used in Message-ID -->
<domain name="https://{{ belenios.domain }}/"/>
<!--
The following can be adjusted to the capacity of your system.
If <maxrequestbodysizeinmemory> is too small, large elections
might fail, in particular with so-called alternative questions
with many voters.
<maxmailsatonce> depends heavily on how sending emails is
handled by your system.
-->
<maxrequestbodysizeinmemory value="1048576"/>
<maxmailsatonce value="1000"/>
<uuid length="14"/>
<gdpr uri="https://www.belenios.org/rgpd.html"/>
<contact uri="mailto:{{ belenios.email_contact }}"/>
<server mail="{{ belenios.email_from }}"/>
<auth name="{{ belenios.cas.name }}"><cas server="{{ belenios.cas.server }}"/></auth>
<source file="/usr/share/belenios-server/belenios.tar.gz"/>
<default-group file="/usr/share/belenios-server/groups/default.json"/>
<nh-group file="/usr/share/belenios-server/groups/rfc3526-2048.json"/>
<log file="/var/log/belenios/security.log"/>
<locales dir="/usr/share/belenios-server/locales"/>
<spool dir="/var/lib/belenios/spool"/>
<!-- <warning file="/opt/belenios/belenios/_run/warning.html"/> -->
</eliom>
</host>
</server>
</ocsigen>
```
Il faut noter que ``ocsigenserver`` n'est pas lancé au démarrage par défaut
et un ``systemctl start ocsigenserver`` ne fonctionne pas, un
`service ocsigenserver start` en revanche si. Pour autoriser `ocsigenserver` à
se lancer au démarrage, il faut modifier le fichier
`/etc/default/ocsigenserver` pour écrire `LAUNCH_AT_STARTUP=true`. Il sera
ensuite possible de démarrer le serveur grâce à systemd.
En redémarrant `ocsigenserver`, Belenios sera accessible sur son port 80.
Après une configuration de reverse-proxy près, Belenios est déjà accessible :)
Pour modifier le logo, il faut regarder autour de
`/usr/share/belenios-server/logo.png`.
### Déployer avec Ansible
L'ensemble des opérations décrites sont déployées par le rôle Ansible du Crans
<https://gitlab.crans.org/nounous/ansible/-/tree/newinfra/roles/belenios>.
Il prend en configuration le nom de domaine utilisé (``belenios.domain``), des
adresses mails de contact (`belenios.email_contact` et `belenios.email_from`)
ainsi que les paramètres du CAS (`belenios.cas.name` et `belenios.cas.server`,
voir ci-dessous).
## Utilisation
Toute personne ayant un compte Crans peut créer une élection sur Belenios en
se connectant via le CAS du Crans. Cela se configure sur la ligne du fichier de
configuration ocsigenserver :
```xml
<auth name="{{ belenios.cas.name }}"><cas server="{{ belenios.cas.server }}"/></auth>
```
On peut remplacer le nom par ``CAS Cr@ns`` et le serveur par
<https://cas.crans.org/>.
Une fois connecté, on peut créer et administrer une élection.
### Gestion des mots de passe
Belenios propose deux modes pour gérer les mots de passe :
* Automatique (mode dégradé : les codes de vote seront générés par le
serveur)
* Manuel (mode sécurisé : un tiers se chargera des codes de vote)
Dans le premier mode, Belenios se charge de générer les codes de vote et les
envoie lui-même par mail. Dans le second mode, recommandé, c'est vous qui
envoyez vous même les codes de vote aux électeurs.
Le code de vote ne servant qu'à débuter l'élection pour une personne, la
sécurité ne réside pas ici. C'est plus ou moins l'équivalent de donner son
nom au bureau de vote. Ce n'est pas une information sensible. À la fin du vote
on demande aux votants de réellement s'identifier avant de glisser les
bulletins dans l'urne. On dispose de deux moyens d'authentification :
* Générer des mots de passe confidentiels (hashés sur le serveur) par
personne, géré par Belenios ;
* Utiliser un CAS.
Il va de soi qu'on vous recommande la deuxième option. Cela peut être
n'importe quel serveur CAS, pas nécessairement celui du Crans. Par exemple, on
utilisera <https://cas.crans.org/> pour le Crans,
<https://note.crans.org/cas> pour la Note Kfet, ou
<https://cas.inria.fr/cas> pour celui de l'Inria.
Pensez bien à autoriser Belenios à utiliser votre serveur CAS, et à faire
attention à ce que les pseudos renvoyés soient en ASCII !
Plus d'informations ici : <https://www.belenios.org/setup.html>.
### Inscription des électeurs
La liste des électeurs doit être arrêtée avant la création de l'élection.
La liste doit être donnée sous la forme `ADRESSE@EMAIL,PSEUDO`.
Depuis la dernière version, il est également possible de définir un poids par
électeur.
**Atention :**
Belenios ne supporte pas l'UTF-8 dans les pseudos. Assurez-vous de garder des
pseudos ASCII. Et dans les adresses e-mail, mais cela va de soi.
#### Générer la liste des adhérents avec Re2o
Pas besoin de plus d'une ligne :
```python
"\n".join(map(lambda user: f"{user.get_mail},{user.pseudo}", utils.all_adherent(including_asso=False).filter(adherent__isnull=False)))
```
C'est faux : il faut importer `utils` et sans doute écrire dans un fichier.
### Déclaration des autorités de déchiffrement
Il s'agit de l'équivalent des assesseurs qui vont contrôler et dépouiller
l'élection. Il peut y en avoir plusieurs. Chacun aura sa clé, et la clé sera
nécessaire pour le dépouillement. Dans tous les cas, Belenios lui-même est
une autorité de déchiffrement mais il est recommandé d'avoir une autorité
extérieure.
**IMPORTANT :**
Cela tient plus du mauvais pressentiment que de l'expérience, mais pensez bien
à VRAIMENT conserver la clé privée en lieu sûr, elle n'est pas
récupérable. Jeter sa clé privée, c'est comme acheter une urne en verre
incassable avec une serrure qui tient le coup et jeter le trousseau dans les
égouts, en veillant à la fondre et la broyer avant. Vous pourrez ajouter des
bulletins dans l'urne mais jamais la dépouiller.
### Élection
Une fois l'élection créée, il n'est plus possible de la modifier, ce qui est
normal. Il est possible de définir les horaires d'ouverture de l'élection
(attention : les horaires sont sur le fuseau UTC !).
Pour voter, chaque participant doit présenter son code d'élection, voter pour
chacune des questions, puis soumettre son bulletin en s'authentifiant via le
moyen défini. Son bulletin est ensuite chiffré et enregistré. Il est possible
de revoter autant de fois que nécessaire, chaque vote annulant le précédent.
### Dépouillement
Après l'élection, et pas avant (ce qui n'est de toute façon pas possible),
chaque autorité de déchiffrement doit soumettre sa clé privée pour ouvrir
l'urne. Une fois cela fait, les résultats sont publics sur la page de
l'élection.