====== Chiffrement des données Medshake EHR/EDC ======= ===== Préambule ===== === Proof of concept ! === La procédure indiquée ici est à l'état de **prototype**. Elle n'est pour le moment pas ni mature, ni validée et demande du travail pour être finalisée. **Ne l'appliquez en aucun cas sur votre instance de production dans savoir ce que vous faites !** Comme recommandé sur la [[medshake>documentation-technique/environnement-de-production-necessaire-a-medshakeehr.html#bases-installation-1| documentation officiel MedShake EHR/EDC sur la mise en production d'une instance]], il faudrait chiffrer l'intégralité des données stockées par Medshake sur le disque dur du serveur sur lequel l'instance est hébergée afin protéger les données des patients en cas de vol du disque dur. Une solution efficace et commode est d'utiliser [[wpfr>LUKS]] et de chiffrer la totalité des données du disque dur, cependant cette solution possède un problème assez gênant : //devoir aller saisir la clé de déchiffrement sur le serveur à chacun de ses redémarrages//, ce qui peut s'avérer problématique si il s'agit d'une machine dédiée utilisée par plusieurs utilisateurs (n'ayant pas forcément les compétences pour déchiffrer la partition) et que la machine ne possède pas d'écran et de clavier branché en permanence. Cette article propose une ébauche de solution pour ce problème en substituant la page normale d'accès à l'instance MedShake par une page offrant un formulaire permettant de saisir une clé de déchiffrement des données si on détecte que celles-ci ne sont pas déchiffrées. La validation de ce formulaire permet l'exécution d'un script chargé de déchiffrer et monter la partition de données si la clé saisie est correcte, puis de rendre la main à Medshake une fois l'opération réussie. En gros cela se passe comme ceci : @startuml start :Tentative d'accès à l'instance MedShake.; if (Données Medshake accessible ?\n//(conteneur LUKS déchiffré et partition de données montée)//) then (Non) repeat :Redirection vers le formulaire\nde saisi de la clé de déchiffrage.; :Validation du formulaire.\n//(Exécution du script de déchiffrement et montage de la partition de données)//; repeat while (Données accessibles ?) is (Non) not (Oui) else (Oui) endif #palegreen:Accès instance MedShake.; stop @enduml Dans ce cas nous ne chiffrons pas l'entièreté du disque dur mais uniquement une partition qui contiendra toutes les données utilisées par MedShake comprenant : * Les fichiers de l'installation Medshake (on inclut tout : le ''homeDirectory'', ''webDirectory'', ''storageLocation'', ''workingDirectory'', le code, etc...). * Les fichiers de la base de données Mysql (le ''datadir''). **Pour être réellement efficace il ne faut pas utiliser de partition //swap// sur le disque dur avec la méthode décrite ici. En effet celle-ci peut éventuellement contenir des informations provenant de la mémoire vive de l'ordinateur, y compris la clé de déchiffrage LUKS**. Il est donc nécessaire de disposer d'un serveur avec suffisamment de mémoire vive (4GB serait bien pour 1 à 5 utilisateurs) on peut aussi jeter un œil du côté de [[wpfr>ZRam]] (voir aussi le paquet Debian ''zram-tools''). Il faudrait aussi placer le ''/tmp'' en mémoire vive comme ceci : cp -a /usr/share/systemd/tmp.mount /etc/systemd/system/ systemctl enable tmp.mount rm -rf /tmp/* # Purge les fichiers actuels du /tmp pour ne pas qu'ils restent présents sur le DD reboot # redémarre l'ordinateur D'autres approches du problèmes existent comme celle-ci : [[https://opensource.com/article/20/11/nbde-linux]] qui est plus lourde et complexe à mettre en place. De plus elle demande une configuration particulière sur les clients. Cette solution se veut une alternative plus simple à mettre en place et plus facilement adaptable (elle ne nécessite pas l'utilisation d'outils particuliers et repose sur une configuration Apache et des scripts bash et php qui peuvent facilement êtres adaptés au besoin). Elle ne nécessite pas non plus d'opération particulière sur les clients. ===== Pré-requis et contrainte ===== ==== Disposer d'un volume LUKS ==== Le cas de la création du volume LUKS n'est pas traité ici. Il peut être crée par l'installateur Debian au moment de l'installation du système, sinon je vous renvois aux documentations déjà existantes : * [[https://wiki.debian.org/Cryptsetup]] * [[https://doc.ubuntu-fr.org/cryptsetup]] ==== On considère aussi les points suivants comme étant valides ==== * Nous utilisons un OS Debian 10 * ''cryptsetup'' est présent sur le système * ''sudo'' est présent sur le système * ''php-fpm'' doit être installé et doit être utilisé à la place du *mod_php* d'Apache (raison expliquée un peu plus bas). * Les modules Apache2 suivants sont activés (pour pouvoir fonctionner avec *php-fpm*) : * ''proxy'' (''a2enmod proxy'') * ''proxy_fcgi'' (''a2enmod proxy_fcgi'') * Ici le volume LUKS une fois ouvert est identifié comme ''/dev/mapper/vg0-cypted''. * Nous démarrons avec celui-ci monté dans ''/srv/medshake/'' * Il ne doit pas y avoir d'entrée dans le ''/etc/crypttab'' ==== Point à travailler ==== * Mise en route du compagnon Apicrypt ? * Service et données Orthanc ? * Actuellement les logs Apache ne sont pas sur la partition chiffrée ? Est-t'il utile de les y placer ? * D’autres cas de figures à traiter ? Sujet abordé sur le [[https://c-medshakeehr.fr/forum/viewtopic.php?f=2&t=89|forum]]. ==== Contrainte ==== Pour le moment on considère que la seule application présente sur le serveur qui utilise la base de données MariaDB est MedShake. //Pour des raisons de simplicité de mise en place nous modifions le ''datadir'' du service "de base" pour le placer sur notre volume chiffré, comme ça le volume chiffré ne sera pas accessible tout de suite au démarrage du système; le service MariaDB ne démarrera pas automatiquement avec le système. Cela le rendra inaccessible pour d'autres éventuelles applications.// Cette solution ne fonctionne pas avec le //mod_php// d'Apache. Quant le script php, chargé de proposé la validation de la saisie de la clé de déchiffrement des données (''/usr/local/lib/medshake/www/index.php'') est exécuté par le //mod_php// d'Apache et que celui-ci appelle le script bash chargé de monter la partition de données chiffrées (''/usr/local/lib/medshake/uncrypt.sh''), à l’intérieur de l’environnement du script bash tout semble normal (le volume chiffré est bien monté et on peut accéder aux données qu'il contient). Par contre pour le reste du système, le volume ne semble pas monté (pas d'entrée dans le ''/etc/mtab'' et l'emplacement du point de montage reste vide). //Je ne trouve pas, pour le moment, d'explication à ce comportement étrange...// Par contre aucun problème si on exécute le script php avec php-fpm. ===== Étape 1 : Récupération et mise en place des scripts additionnels ===== Cette méthode requiert l'utilisation deux petits scripts : * Un script php chargé de présenter un formulaire web pour demander aux utilisateurs tentant de se connecter à l'instance MedShake la saisie de la clé de déchiffrement des données si le volume LUKS n'est pas encore déverrouillé et monté au bon endroit. * Un script bash appelé par le script php à la validation du formulaire et lancé avec les droits //root// et dont le but est d'ouvrir et de monter le volume LUKS ainsi que d'effectuer d'autres opérations comme démarrer le service MariaDB une fois le //datadir// accessible. Commençons par créer un endroit adapté pour y placer nos scripts : mkdir -p /usr/local/lib/medshake/www/ ==== Le script bash de déchiffrage des données ==== #!/bin/sh # # Script bash utilisé pour déchiffrer les données MedShake # # Adapter la configuration suivante au besoin # Paramètre supplémentaire pour la commande 'cryptsetup' CRYPT_SETUP_OPEN_MORE_PARAM="--type luks" # Nom du fichier périphérique contenant le volume LUKS chiffré CRYPTED_DEV="/dev/sdXY" # /!\ MODIFIER /dev/sdXY PAR LE NOM DU PÉRIPHÉRIQUE SUR VOTRE SYSTÈME !!! # Nom du volume LUKS une fois déverrouillé UNCRYPTED_DEV="meduncrypted" # Emplacement pour le montage de la partition des données déchiffrées UNCRYPTED_MNT_PT="/srv/medshake" # Option de montage supplémentaire pour le périphérique déchiffré UNCRYPTED_MNT_OPT="" # Nom du service pour lancer la base de données MariaDB MYSQLD_SERVICE="medshakedb" uncrypt_pass="${1}" if [ $(id -u) -gt 0 ] then echo "ERR: Vous n'êtes pas root" exit 65 fi if [ -z "${uncrypt_pass}" ] then echo "ERR: Clé de déchiffrement vide" exit 66 fi if [ ! -b "${CRYPTED_DEV}" ] then echo "ERR: Périphérique chiffré non trouvable ${CRYPTED_DEV}" exit 67 fi if [ ! -d "${UNCRYPTED_MNT_PT}" ] then echo "ERR: Le point de montage pour (${UNCRYPTED_MNT_PT}) n'existe pas" exit 68 fi if [ -z $(which cryptsetup) ] then echo "ERR: l'utilitaire 'cryptsetup' n'est pas installé" exit 69 fi if ! cryptsetup status "${UNCRYPTED_DEV}" > /dev/null then echo "${uncrypt_pass}" | cryptsetup open ${CRYPT_SETUP_OPEN_MORE_PARAM} "${CRYPTED_DEV}" "${UNCRYPTED_DEV}" if [ $? -gt 0 ] then echo "ERR: échec lors de l'ouverture du volume LUKS" exit 70 else echo "INF: Volume LUKS ouvert ${CRYPTED_DEV} -> ${UNCRYPTED_DEV}" fi else echo "WARN: ${CRYPTED_DEV} est déjà ouvert" fi if ! grep -q "^/dev/mapper/${UNCRYPTED_DEV} ${UNCRYPTED_MNT_PT}" /etc/mtab then echo mount -o "${UNCRYPTED_MNT_OPT}" "/dev/mapper/${UNCRYPTED_DEV}" "${UNCRYPTED_MNT_PT}" mount -o "${UNCRYPTED_MNT_OPT}" "/dev/mapper/${UNCRYPTED_DEV}" "${UNCRYPTED_MNT_PT}" 2>&1 if [ $? -gt 0 ] then echo "ERR: Échec au montage de /dev/mapper/${UNCRYPTED_DEV} sur ${UNCRYPTED_MNT_PT}" exit 71 else echo "INF: monter /dev/mapper/${UNCRYPTED_DEV} sur ${UNCRYPTED_MNT_PT}" fi else echo "WARN: /dev/mapper/${UNCRYPTED_DEV} est déjà monté sur ${UNCRYPTED_MNT_PT}" fi if [ -f "/var/run/mysqld/${MYSQLD_SERVICE}.pid" ] then echo "WARN: ${MYSQLD_SERVICE} est déjà lancé (le redémarrer)" systemctl restart "${MYSQLD_SERVICE}.service" if [ $? -gt 0 ] then echo "ERR: Échec du redémarrage de ${MYSQLD_SERVICE}" exit 72 fi echo "INF: service ${MYSQLD_SERVICE} redémarré" else systemctl start "${MYSQLD_SERVICE}.service" if [ $? -gt 0 ] then echo "ERR: Échec du démarrage du service ${MYSQLD_SERVICE}" exit 72 fi echo "INF: service ${MYSQLD_SERVICE} démarré" fi Une fois récupéré et placé dans ''/usr/local/lib/medshake/uncrypt.sh'' le rendre exécutable : chmode 755 /usr/local/lib/medshake/uncrypt.sh Dans notre cas, le script sera appelé par l'utilisateur ''www-data'' et obtiendra les droits //root// via ''sudo''. Il faut donc disposer d'une entrée //sudoers// adaptée pour ne pas avoir de demande de mot de passe par ''sudo'' : www-data ALL=(ALL) NOPASSWD:/usr/local/lib/medshake/uncrypt.sh ==== Le fichier php pour afficher le formulaire de déchiffrage des données ==== '.$r.''."\n"; } if (! $error) { preg_match('#^(.*)(/uncrypt/.*)$#', $_SERVER['SCRIPT_URI'], $base_url); header('Location: '.$base_url[1]); exit(); } } /* * Vue */ ?> Déchiffrement des données Medshake EHR/EDC

Données Medshake EHR/EDC chiffrées

Les données Medshake EHR/EDC sont actuellement chiffrés. Pour accéder à l'instance veuillez fournir la clé de déchiffrement.

Erreur

===== Étape 2 : Configurer déplacer le ''datadir'' MariaDB ===== Vu que nous voulons que les fichiers de la base de données MariaDB se trouvent sur la partition chiffrée il va falloir adapter la configuration du service. Pour cela il faut modifier le fichier ''/etc/mysql/mariadb.conf.d/50-server.cnf'' et pour le paramètre ''datadir'' remplacer ''/var/lib/mysql/'' par ''/srv/medshake/db/'' : sed -i 's#^\(datadir[[:space:]]*=\).*#\1 /srv/medshake/db/#' /etc/mysql/conf.d/medshakedb.cnf Puis stopper le service : systemctl stop mariadb.service Et copier les fichiers de la base de données dans le nouvel emplacement : cp -a /var/lib/mysql/ /srv/medshake/db/ Si l'opération est effectuée sur un serveur qui contient déjà des données de patients, il est fortement recommandé d'effacer proprement des données avec un outil tel que ''secure-delete'' : apt install secure-delete srm /var/lib/mysql/* Pour finir il faut désactiver le démarrage automatique du service : systemctl disable mariadb.service Le service ''mariadb.service'' ne doit pas être démarré automatiquement car les fichiers de la base de données situés dans ''/srv/medshake/db/'' ne seront pas accessibles au démarrage vu que le volume chiffré est monté après la saisie de la clé de déchiffrement par l'utilisateur. ===== Étape 3 : Configuration de l'accès web ===== ==== Configuration de *php-fpm* ==== Pool php-fpm dédié pour l'application MedShake : [medshake] user = www-data group = www-data chdir = /srv/medshake/EHR/ listen = /run/php/php-fpm-medshake.sock listen.owner = www-data listen.group = www-data pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 php_admin_value[max_input_vars] = 20000 php_admin_value[upload_max_filesize] = 40M php_admin_value[post_max_size] = 40M php_admin_value[max_execution_time] = 300 env[MEDSHAKEEHRPATH] = '/srv/medshake/EHR/' env[MEDSHAKEEHRLOGFILE] = '/var/log/apache2/medshake.log Redémarrer le service *php-fpm* : systemctl restart php7.3-fpm.service ==== Configuration *Apache* ==== Nous avons besoin d'utiliser une configuration Apache alternative pour MedShake utilisant //php-fpm// et renvoyant vers la page de déchiffrage du mot de passe si la partition de données pour MedShake n'est pas accessible : # Redirige automatiquement tout le trafic http vers https # (Ne pas autoriser le trafic en claire) # /!\ Remplacer "mon-medshake.fr" par le domaine utilisé ServerName mon-medshake.fr ServerAdmin admin@mon-medshake.fr RedirectMatch permanent ^(.*)$ https://mon-medshake.fr/$1 # Trafic https # /!\ Remplacer "mon-medshake.fr" par le domaine utilisé ServerName mon-medshake.fr ServerAdmin admin@mon-medshake.fr # # Configuration SSL # # Charge le Module SSL SSLEngine On # /!\ Adapter en fonction du cetificat utilisé SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key # Ajoute l'entête http pour le HSTS Header always set Strict-Transport-Security "max-age=0; includeSubDomains" # Fichier de log dédié avec un format compatible avec le lecteur de log intégré # à Medshake CookieName apacheLogUserID CookieDomain .mon-medshake.fr Define MEDSHAKEEHRLOGFILE /var/log/apache2/medshake.log SetEnv MEDSHAKEEHRLOGFILE ${MEDSHAKEEHRLOGFILE} LogFormat "%{%Y-%m-%d %H:%M:%S}t %{c}a %r %{Cookie}n" usertrack SetEnvIf Request_URI "\.png$|\.gif$|\.jpg$|\.svg$|\.js$|\.css$|\.map$|\.ico$|\.woff2" do_not_log CustomLog ${MEDSHAKEEHRLOGFILE} usertrack env=!do_not_log # Charge le module de réécriture de d'url RewriteEngine On DocumentRoot /srv/medshake/EHR/public_html/ DirectoryIndex index.php # uniquement les clients du réseau local (modfier 192.168.1.0/24 en fonction # de la configuration de votre réseau local) Require ip 192.168.1.0/24 Require local # # Si le fichier /srv/medshake/.uncrypted n'existe pas c'est que les données # MedShake n'ont pas été déchiffrés # On redirige donc vers la page qui permet la saisie de la clé de déchiffrement # RewriteCond /srv/medshake/.uncrypted !-f RewriteRule ^(.*)$ unix:/run/php/php-fpm-medshake.sock|fcgi://localhost/usr/local/lib/medshake/www/index.php [P,L] # Passe tous les fichers php existants à php-fpm RewriteCond /srv/medshake/.uncrypted -f RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} -f RewriteRule ^(.+\.php)$ unix:/run/php/php-fpm-medshake.sock|fcgi://localhost/%{DOCUMENT_ROOT}/$1 [P] # Route /public/ au script public.php RewriteCond /srv/medshake/.uncrypted -f RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f RewriteRule ^(/public/)(.*)$ unix:/run/php/php-fpm-medshake.sock|fcgi://localhost/%{DOCUMENT_ROOT}/public.php [P] # Route /phonecapture/ à phonecapture.php RewriteCond /srv/medshake/.uncrypted -f RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f RewriteRule ^(/phonecapture/)(.*)$ unix:/run/php/php-fpm-medshake.sock|fcgi://localhost/%{DOCUMENT_ROOT}/phonecapture.php [P] # Route toutes les autres destinations où le fichier n'existe pas à index.php RewriteCond /srv/medshake/.uncrypted -f RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f RewriteRule ^(.*)$ unix:/run/php/php-fpm-medshake.sock|fcgi://localhost/%{DOCUMENT_ROOT}/index.php [P] # uniquement les clients du réseau local (modifier 192.168.1.0/24 en fonction # de la configuration de votre réseau local) Require ip 192.168.1.0/24 Require local # Ces ressources peuvent êtres mise en cache Header setifempty Cache-Control "must-revalidate" # Fixe un bug présent dans le version de apache utilisé dans Debian 10(2.4.38) # qui gènère une entête http Etag mal formée et empêche un retrour 304 # quand la ressource est en cache. # voire https://bz.apache.org/bugzilla/show_bug.cgi?id=45023#c22 RequestHeader edit "If-None-Match" '^"((.*)-gzip)"$' '"$1", "$2"' # uniquement les clients du réseau local (modfier 192.168.1.0/24 en fonction # de la configuration de votre réseau local) Require ip 192.168.1.0/24 Require local # Bloque les accès aux fichiers composer (juste c'est plus propre) Require all denied ===== Étape 4 : Mise en place du code MedShake ===== Deux cas de figures se présentent : * Le système possède déjà une installation MedShake. * Nous effectuons une nouvelle installation MedShake. ==== Déplacer son installation existante ==== Admettons que notre instance MedShake se trouve actuellement dans ''/home/EHR/'', nous déplaçons les fichiers sur notre volume chiffré : cp -a /home/EHR/ /srv/medshake/EHR/ Ne pas oublier de supprimer des ancienne données de manière sécurisée après leurs déplacements. srm /home/EHR/ **Ne pas oublier de modifier le fichier de configuration pour MedShake (''/srv/medshake/EHR/config/config.yml'') afin de mettre à jour les paramètres (''webDirectory'', ''stockageLocation'', ''backupLocation'', ''workingDirectory'', ''templatesFolder'') avec le nouvel emplacement de l'instance. ==== Procéder à une nouvelle installation ==== Utiliser la méthode classique mais la faire dans ''/srv/medshake/EHR/''. ===== Étape Finale ===== ==== Création du fichier ''/srv/medshake/.uncrypted'' ==== Le fichier "témoin" ''/srv/medshake/.uncrypted̉'' est utilisé pour indiquer à Apache si les données chiffrées sont bien accessibles ou non. echo -e '!!! NE PAS SUPPRIMER !!!\nCe fichier est utilisé pour vérifier que les données MedShake sont bien déchiffrées.' > /srv/medshake/.uncrypted chmod 444 /srv/medshake/.uncrypted ==== Substitue ''/srv/medshake/EHR/public_html'' quand les données ne sont pas déchiffrés ==== Au moment où le serveur démarre la partition contentant les données chiffrées, elle n'est pas encore montée dans ''/srv/medshake/'' et donc le dossier ''/srv/medshake/EHR/public_html/'' n'est pas présent or il est définit comme ''DocumentRoot'' dans la configuration *Apache* et celui-ci refusera de démarrer si il ne le trouve pas. Il faut donc créer un dossier de substitution à l'emplacement du vrai avant que les données chiffrées ne soient montées. Pour commencer il faut démonter la partition des données chiffrés : # Stopper ces services évite que des fichiers présents sur la partition soient # ouverts ce qui empêcherait le démontage de la partition systemctl stop medshakedb.service systemctl stop php7.3-fpm.service systemctl stop apache2.service umount /srv/medshake Puis créer le dossier de substitution : mkdir -p /srv/medshale/EHR/public_html/ Il ne reste plus qu'à redémarrer le serveur et à accéder à son instance MedShake via le navigateur web. Normalement la page permettant de déchiffrer les données devrait se substituer à l'instance MedShake. Une fois la bonne clé de déchiffrement saisie l'instance MedShake devrait être accessible. {{tag>securite chiffrement installation}}