Author: didier

Connexion automatique d’un utilisateur avec une session verrouillée (sddm)

Connexion automatique d’un utilisateur

Il faut éditer le fichier de configuration de sddm afin d’ajouter les entrées de connexion automatique d’un utilisateur, dans la section Autologin:

  • Session: spécifier la session utilisateur (plasma, gnome, etc…). La liste des sessions disponible (installée sur votre poste) se trouve dans le répertoire /usr/share/xsessions/
    ls /usr/share/xsessions/
  • Relogin: Spécifier si sddm doit reconnecter l’utilisateur lorsque celui-ci se déconnecte
  • User: Le login de l’utilisateur à connecter automatiquement
Fichier /etc/sddm.conf
...
[Autologin]
Relogin=false
Session=plasma.desktop
User=tartarefr
...

Verrouillage de la session au démarrage

Pour verrouiller la session d’un utilisateur au démarrage de celle-ci, il faut ajouter au fichier (ou le créer s’il n’existe pas) ~/.profile

Fichier ~/.profile
export DESKTOP_LOCKED=yes

La ligne de commande correspondante (pour un copier/coller)

echo "export DESKTOP_LOCKED=yes" >> ~/.profile

Remplacer les mots de passe par l’insertion d’une clé USB

Ou comment remplacer ce que l’on sait (le mot de passe) par ce que l’on possède (une clé USB particulière). Cela permet de donner une seconde vie à des clés USB totalement obsolètes de par leur taille vraiment faible (inférieure à 1Go).

Pamusb permet de se connecter sans avoir à taper un mot de passe. Pour cela un module PAM est ajouté et la sécurité repose sur la possession de cette fameuse clé. Bien que cela ne soit pas impossible, il est très difficile de remplacer cette clé, qui sera reconnue par son ID (iSerial) et qui possédera le même secret partagé que l’ordinateur. C’est cette dernière partie qui est très difficile à reproduire car le secret partagé est changé à chaque utilisation.

Pour obtenir ce numéro de série, on fait la différence de la commande lsusb avant et après l’insertion de la clé pour en déduire le numéro du bus et de device. Pour moi c’est ça

Bus 002 Device 007: ID 04e8:1623 Samsung Electronics Co., Ltd

J’interroge donc lsusb (en tant que superutilisateur root) sur ce périphérique et je vais comparer ce chiffre à celui que me proposera pam-usb lors de la configuration de ma clé USB.

sudo lsusb -s 002:007 -v | grep iSerial
iSerial 3 0123456789AB

L’installation de pam-usb est triviale

sudo dnf install pam_usb

On branche ensuite notre clé USB, si ce n’est pas déjà fait, (sans montage de partition) et on la déclare comme clé utilisable par pam-usb en lui donnant un nom

sudo pamusb-conf --add-device masterkey
Please select the device you wish to add.
* Using "Samsung Mighty Drive (0123456789AB)" (only option)

Which volume would you like to use for storing data ?
* Using "/dev/sdh1 (UUID: 99899601-c7de-4086-bc81-f2ff36a0b509)" (only option)

Name            : masterkey
Vendor          : Samsung
Model           : Mighty Drive
Serial          : 0123456789AB
UUID            : 99899601-c7de-4086-bc81-f2ff36a0b509

Save to /etc/pamusb.conf ?
[Y/n]

Impossible d’écrire dans le fichier /etc/pamusb.conf

Le fichier /etc/pamusb.conf par défaut comporte des commentaires multi-lignes.

Bien que cela soit valide d’un point de vue XML, pam-usb n’aime vraiment pas ça.

De plus la bonne indentation du fichier est le cadet de ses soucis.

Donc on écrit un fichier valide pour pam-usb et on s’occupera de l’indentation manuellement à la fin.

sudo mv/etc/pamusb.conf /etc/pamusb.conf.orig
sudo echo '<configuration><defaults></defaults><devices></devices><users></users><services></services></configuration>' > /etc/pamusb.conf

On ajoute notre utilisateur

sudo pamusb-conf --add-user tartarefr
Which device would you like to use for authentication ?
* Using "masterkey" (only option)

User            : tartarefr
Device          : masterkey

Save to /etc/pamusb.conf ?
[Y/n]

On modifie les fichiers /etc/pam.d/system-auth et /etc/pam.d/system-auth-ac pour ajouter la ligne autorisant l’authentification par le module pam-usb avant celle de l’authentification unix (ligne existante qui ne doit pas être modifiée)

Fichiers /etc/pam.d/system-auth et /etc/pam.d/system-auth-ac
auth        sufficient    pam_usb.so
auth        sufficient    pam_unix.so ....

On vérifie

pamusb-check tartarefr
* Authentication request for user "tartarefr" (pamusb-check)
* Device "masterkey" is connected (good).
* Performing one time pad verification...
* Access granted
sudo cat /etc/fedora-release
* pam_usb v0.5.0
* Authentication request for user "didier" (sudo)
* Device "masterkey" is connected (good).
* Performing one time pad verification...
* Access granted.
Fedora release 28 (Twenty Eight)

Au secours, ça ne fonctionne plus
Il suffit de supprimer le répertoire local comportant le secret partagé (en se connectant avec le mot de passe unix). Le secret partagé sera réinitialisé avec la commande pamusb-check.

rm -rf ~/.pamusb
pamusb-check $USER

Relayer ses courriels via gmail

Afin de s’affranchir du relais SMTP de son F.A.I. on peut utiliser son compte google (gmail) pour envoyer les courriels avec son serveur de courrier postfix. C’est bon pour la planète car cela supprime les intermédiaires entre son courrier et les services de renseignements peu scrupuleux de notre vie privée. En parlant de ça, quelqu’un a déjà réussi à faire une restauration à partir de la solution NSACloudBackup ?

Il suffit de modifier un tout petit peu la configuration de postfix (qui a été le service mail par défaut de fedora et qui est toujours celui de centos).

On édite le fichier, afin de rajouter ou de modifier les entrées suivantes:

Fichier /etc/postfix/main.cf
relayhost = [smtp.gmail.com]:587

# use tls
smtp_use_tls=yes

# use sasl when authenticating to foreign SMTP servers
smtp_sasl_auth_enable = yes

# path to password map file
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd

# list of CAs to trust when verifying server certificate
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.trust.crt

# eliminates default security options which are imcompatible with gmail
smtp_sasl_security_options = noanonymous
smtp_sasl_mechanism_filter = plain

On génère le fichier fichier contenant les identifiants.

Fichier /etc/postfix/sasl_passwd
[smtp.gmail.com]:587  <email gmail>:<motdepasse>

On transforme ce fichier dans un langage compréhensible par postfix (fichiers indexés) et on supprime la version en clair

sudo postmap /etc/postfix/sasl_passwd
sudo rm /etc/postfix/sasl_passwd

On redémarre le service postfix et le tour est joué.

sudo systemctl restart postfix

Google considère que la seule méthode d’autentification sécurisée est OAuth2, et que SASL ne l’est pas. Il faudra donc autoriser les connexions “moins sécurisée” à son compte Google sur la page https://www.google.com/settings/security/lesssecureapps.

On teste la bonne réception d’un courriel envoyé avec la commande suivante:

echo 'Test from CLI' | mail -s 'gmail relay' <mon adresse email>

Dockerisation du service cobbler

Étude d’un système cobbler existant

Un système cobbler comporte plusieurs services:

  • le service cobbler à proprement parlé
  • un service web (apache) pour distribuer les RPMs
  • un service de transfert de fichier basique (tftp) pour distribuer le noyau bootable permettant d’afficher le menu, et tout ce qui va avec.

L’analyse du système existant nous montre

  1. Nous n’avons pas besoin de gérer le DHCP et le DNS, ceux-ci sont déjà gérés sur notre réseau et seul l’adresse du next_server sera mis à jour dans la configuration du DHCP.
  2. On utilise plusieurs snippets additionnels pour configurer la cible dans la phase post-installation (notamment une mise à jour de la distribution, l’installation du dépôt epel, etc…).
  3. Seulement quelques répertoires contiennent des données personnalisées:
    • /var/lib/cobbler: Ce répertoire contient deux types de données:
      • Les Données dynamiques propres à chaque installation et qui doivent être persistantes. Elles seront donc intégrées dans un ou plusieurs volumes de données: /var/lib/cobbler/config (la configuration dynamique) et /var/lib/cobbler/backup (le répertoire des sauvegardes)
      • Les Données semi-statiques. Elles seront intégrées directement dans l’image docker: les modèles de kickstart (définition de l’installation), les snippets (bout de code ré-utilisable), etc…
    • /var/www/cobbler: qui contient le dépôt de paquets RPM des distributions importées. Ces fichiers sont servis par le serveur web et ce répertoire sera intégré dans un volume de données.
    • /var/lib/tftp: qui contient le noyau bootable (pxelinux.0) , le menu d’installation, etc… Ces fichiers sont servis par le serveur tftpd et ce répertoire sera intégré dans un volume de données
    • On aura besoin d’un cinquième point de montage (/mnt sur le conteneur) où les isos des distributions seront montées par le serveur hôte.
      mkdir /mnt/centos
      mount -o ro /path/to/iso/CentOS-7-x86_64-DVD-1804.iso /mnt/centos
    • Et même d’un sixième point de montage, si l’hôte du conteneur a un noyau inférieur à 4.7, à cause du bug concernant les sockets unix sur overlayfs (lien): /var/run/supervisor

Bien entendu, pour ajouter un modèle de kickstart ou un snippet, l’image docker devra être reconstruite. Mais à l’usage, on se rend compte qu’on ajoute/modifie moins de fichiers snippets ou kickstart, que la disponibilité des mises à jour du paquet cobbler.

Démarrage

L’image peut être trouvée sur docker.io

docker pull tartarefr/docker-cobbler

Les variables d’environnement

Le service docker peut être personnalisé via les variables d’environnement suivantes:

  • HOST_IP_ADDR: Cette variable est la seule variable obligatoire car c’est elle qui permet la connexion à l’API cobbler et ne peut pas avoir de valeur par défaut. Elle doit prendre la valeur de l’adresse IP de l’hôte hostname --ip-address | awk '{print $1}'
  • HOST_HTTP_PORT: Port de l’hôte branché sur le port 80 du conteneur. Par défaut c’est 80 (-p 80:80)
  • DEFAULT_ROOT_PASSWD: Mot de passe en clair du compte superutilisateur root qui sera configuré sur les cibles (défaut: cobbler)
  • COBBLER_WEB_USER: Login de cobbler web (défaut: cobbler)
  • COBBLER_WEB_PASSWD: Mot de passe de cobbler web (défaut: cobbler)
  • COBBLER_WEB_REALM: Royaume de cobbler web car c’est de l’authentification digest (défaut: cobbler)
  • COBBLER_LANG: La langue à configurer lors de l’installation des cibles (défaut: fr_FR)
  • COBBLER_KEYBOARD: Le clavier à configurer lors de l’installation des cibles (défaut: fr-latin9)
  • COBBLER_TZ: Le fuseau horaire à configurer lors de l’installation des cibles (défaut: Europe/Paris)

Mise en place avant le premier démarrage

  1. On commence par télécharger l’iso DVD de centos 7
  2. Il va nous falloir construire 5 volumes docker pour un conteneur (même ratio que le pastis)
    docker volume create cobbler_www
    docker volume create cobbler_tftp
    docker volume create cobbler_config
    docker volume create cobbler_backup
    docker volume create cobbler_run
    
  3. On créé le point de montage de notre iso
    sudo mkdir /mnt/centos
  4. On monte notre iso dans /mnt/centos
    sudo mount -o ro /path/to/iso/CentOS-7-x86_64-DVD-1804.iso /mnt/centos
    

Démarrage

Je conseille fortement de modifier la valeur de DEFAULT_ROOT_PASSWD. En effet c’est un des tout premiers mot de passe root essayé par les pirates.

Contrairement à la valeur par défaut, notre serveur web (api et cobbler_web) sera accessible depuis l’hôte sur le port 60080.

docker run -d --privileged \
           -v cobbler_www:/var/www/cobbler:z \
           -v cobbler_tftp:/var/lib/tftp:z \
           -v cobbler_config:/var/lib/cobbler/config:z \
           -v cobbler_backup:/var/lib/cobbler/backup:z \
           -v cobbler_run:/var/run/supervisor:z \
           -v /mnt/centos:/mnt:z \
           -e DEFAULT_ROOT_PASSWD=cobbler \
           -e HOST_IP_ADDR=$(hostname --ip-address | awk '{print $1}') \
           -e HOST_HTTP_PORT=60080 \
           -e COBBLER_WEB_USER=cobbler \
           -e COBBLER_WEB_PASSWD=cobbler \
           -e COBBLER_WEB_REALM=Cobbler \
           -e COBBLER_LANG=fr_FR \
           -e COBBLER_KEYBOARD=fr-latin9 \
           -e COBBLER_TZ=Europe/Paris \
           -p 69:69/udp \
           -p 60080:80 \
           -p 60443:443 \
           -p 25151:25151 \
           --name cobbler \
           tartarefr/docker-cobbler:latest

Initialisation

Une fois à l’intérieur du conteneur, on va ajouter la cible memtest à notre menu d’installation, puis on va importer notre distribution centos 7, ajouter un profile supplémentaire qui permettra d’installer centos 7 avec un environnement graphique et synchroniser le tout.

  1. Immersion dans notre conteneur
    docker exec -ti cobbler /bin/bash
  2. Ajout de la cible memtest. On vérifie au préalable que le fichier /boot/memtest86+-5.01 existe bien (la version peut être modifiée et la ligne suivante doit être adaptée en conséquence)
    cobbler image add --name=memtest86+ --file=/boot/memtest86+-5.01 --image-type=direct
  3. Import de la distribution centos 7
    cobbler import --path=/mnt --name=CentOS-7-x86_64
  4. Ajout du profile pour une installation d’une centos desktop
    cobbler profile add --name=CentOS-7-x86_64-Desktop \
        --distro=CentOS-7-x86_64 \
        --kickstart=/var/lib/cobbler/kickstarts/sample_end.ks \
        --virt-file-size=12 \
        --virt-ram=2048
    
    cobbler profile edit --name CentOS-7-x86_64-Desktop --ksmeta="type=desktop"
    
  5. Synchronisation
    cobbler sync


Malheureusement, le changement de fichier iso (démontage et le montage d’un autre iso sur le même point de montage) sur l’hôte ne met pas à jour le contenu du répertoire /mnt dans le conteneur.
Pour ajouter une autre distribution, il faudra:

  1. stopper le conteneur
  2. détruire le conteneur
  3. démonter l’iso sur l’hôte
  4. monter le nouvel iso
  5. démarrer un nouveau conteneur

Modification de l’image

Pour ajouter un modèle de kickstart ou un snippet, il suffit de placer le fichier dans le répertoire idoine et de modifier le fichier Dockerfile:

  1. Ajouter une instruction docker de copie du fichier dans l’image du conteneur
  2. Si c’est un snippet s’éxecutant dans la phase post-installation, ajouter le nom à la liste des snippets post-installation à l’instruction d’activation (Activate personnal snippets)
  3. Reconstruire l’image
  4. Déployer la nouvelle image

Quelques mots sur l’image docker

J’ai préféré construire une image en partant de zéro, car celles disponibles sont soit plus mise à jour, soit elles utilisent systemd. Je préfère garder cette option pour les cas particuliers, inutile de sortir le tank pour écraser un moustique.

Supervisord est un bien meilleur candidat pour le poste d’orchestrateur dans un conteneur (le bon outil pour la bonne tâche) et permet de redémarrer un ou plusieurs services.

supervisorctl restart cobblerd

L’ensemble du projet cobbler est versionné sur gitlab, et en copie sur github (pour la construction automatique des images sur docker.io).

Fichier Dockerfile
FROM centos:7

MAINTAINER Didier FABERT (tartare) <didier@tartarefr.eu>

# RPM REPOs
RUN yum install -y \
    epel-release \
    && yum clean all \
    && rm -rf /var/cache/yum

RUN yum update -y \
    && yum clean all \
    && rm -rf /var/cache/yum

RUN yum install -y \
  cobbler \
  cobbler-web \
  pykickstart \
  debmirror \
  curl wget \
  rsync \
  supervisor \
  net-tools \
  memtest86+ \
  && yum clean all \
  &&  rm -rf /var/cache/yum

# Copy supervisor conf
COPY supervisord/supervisord.conf /etc/supervisord.conf
COPY supervisord/cobblerd.ini /etc/supervisord.d/cobblerd.ini
COPY supervisord/tftpd.ini /etc/supervisord.d/tftpd.ini
COPY supervisord/httpd.ini /etc/supervisord.d/httpd.ini

# Copy personnal snippets
COPY snippets/partition_config /var/lib/cobbler/snippets/partition_config
COPY snippets/configure_X /var/lib/cobbler/snippets/configure_X
COPY snippets/add_repos /var/lib/cobbler/snippets/add_repos
COPY snippets/disable_prelink /var/lib/cobbler/snippets/disable_prelink
COPY snippets/systemd_persistant_journal /var/lib/cobbler/snippets/systemd_persistant_journal
COPY snippets/rkhunter /var/lib/cobbler/snippets/rkhunter
COPY snippets/enable_X /var/lib/cobbler/snippets/enable_X
COPY snippets/yum_update /var/lib/cobbler/snippets/yum_update

# Copy personnal kickstart

# Activate personnal snippets
RUN for kickstart in sample sample_end legacy ; \
    do \
        additional_post_snippets="" ; \
        for snippet in \
                        add_repos \
                        disable_prelink \
                        systemd_persistant_journal \
                        rkhunter \
                        enable_X \
                        yum_update ; \
        do \
          additional_post_snippets="${additional_post_snippets}\n\$SNIPPET('${snippet}')" ; \
        done ; \
        sed -i \
           -e "/post_anamon/ s/$/${additional_post_snippets}/" \
           -e "/^autopart/ s/^.*$/\$SNIPPET('partition_config')/" \
           -e "/^skipx/ s/^.*$/\$SNIPPET('configure_X')/" \
       /var/lib/cobbler/kickstarts/${kickstart}.ks ; \
    done

# Install vim-enhanced by default and desktop packages if profile have el_type set to desktop (ksmeta)
RUN echo -e "@core\n\nvim-enhanced\n#set \$el_type = \$getVar('type', 'minimal')\n#if \$el_type == 'desktop'\n@base\n@network-tools\n@x11\n@graphical-admin-tools\n#set \$el_version = \$getVar('os_version', None)\n#if \$el_version == 'rhel6'\n@desktop-platform\n@basic-desktop\n#else if \$el_version == 'rhel7'\n@gnome-desktop\n#end if\n#end if\nkernel" >> /var/lib/cobbler/snippets/func_install_if_enabled

COPY first-sync.sh /usr/local/bin/first-sync.sh
COPY entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh /usr/local/bin/first-sync.sh

EXPOSE 69 80 443 25151

VOLUME [ "/var/www/cobbler", "/var/lib/tftp", "/var/lib/cobbler/config", "/var/lib/cobbler/backup", "/var/run/supervisor", "/mnt" ]

ENTRYPOINT /entrypoint.sh

Lancement de la construction

docker build -t local/cobbler .

En cas de problème, ne pas hésiter à regarder le fichier /var/log/cobbler/cobbler.log

Protéger apache avec fail2ban

Cet article fait suite à l’article sur la sécuristion de son VPS, où l’on avait installé le service fail2ban et sécurisé le service SSH. On s’attaque maintenant à la protection d’Apache.

Pour éloigner les relous qui essaie d’accéder à w00tw00t ou à phpMyAdmin, Il faut ajouter les deux filtres suivant dans le répertoire /etc/fail2ban/filter.d/.

  • Fichier /etc/fail2ban/filter.d/apache-pma.conf
    # Fail2Ban configuration file
    # Bans bots scanning for non-existing phpMyAdmin installations on your webhost.
    #
    [Definition]
    # Option: failregex
    # Notes.: Regexp to match often probed and not available phpmyadmin paths.
    # Values: TEXT
    #
    failregex = [[]client []] File does not exist: .*(PMA|phpmyadmin|myadmin|mysql|mysqladmin|sqladmin|mypma|admin|xampp|mysqldb|mydb|db|pmadb|phpmyadmin1|myadmin2)
    # Option: ignoreregex
    # Notes.: regex to ignore. If this regex matches, the line is ignored.
    # Values: TEXT
    #
    ignoreregex =
    
  • Fichier /etc/fail2ban/filter.d/apache-w00tw00t.conf
    [Definition]
    # Option:  failregex
    # Notes.:  regex to match the w00tw00t scan messages in the logfile. The
    #          host must be matched by a group named "host". The tag "" can
    #          be used for standard IP/hostname matching.
    # Values:  TEXT
    failregex = ^ -.*"GET \/w00tw00t\.at\.ISC\.SANS\.DFind\:\).*".*
    # Option:  ignoreregex
    # Notes.:  regex to ignore. If this regex matches, the line is ignored.
    # Values:  TEXT
    ignoreregex =
    

Maintenant pour activer les règles concernant apache (incluses dans fail2ban) et nos 2 règles personnelles, on ajoute au fichier jail.local, les lignes suivantes.

Fichier /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 X.X.X.X
...
[apache-auth]
enabled      = true
maxretry     = 2
journalmatch =
backend      = polling

[apache-badbots]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling

[apache-noscript]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling

[apache-overflows]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling

[apache-nohome]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling

[apache-botsearch]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling

[apache-modsecurity]
enabled      = true
maxretry     = 2
journalmatch =
backend      = polling

[apache-shellshock]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling

[apache-pma]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling
logpath      = %(apache_error_log)s
bantime      = 172800
findtime     = 172800

[apache-w00tw00t]
enabled      = true
maxretry     = 1
journalmatch =
backend      = polling
logpath      = %(apache_access_log)s
bantime      = 172800
findtime     = 172800

Les règles qui nécessitent d’avoir accès à des fichiers de log, devront impérativement avoir les définitions backend = polling et journalmatch = car le backend par défaut est réglé sur auto (c’est à dire systemd). D’ailleurs le fichier jail.conf contient les lignes suivantes

# Note: if systemd backend is chosen as the default but you enable a jail
#       for which logs are present only in its own log files, specify some other
#       backend for that jail (e.g. polling) and provide empty value for
#       journalmatch. See https://github.com/fail2ban/fail2ban/issues/959#issuecomment-74901200

C.Q.F.D…

Passage à Fedora 28

Le passage de Fedora 27 à Fedora 28 s’est passé sans douleur et quasiment sans incident. Bien entendu, il faut sauvegarder ses données au préalable et il peut être judicieux d’avoir à disposition un live-CD, dans le cas très improbable où la loi de Murphy fait des siennes …

Montée de version

  1. Mettre à jour la version actuelle
    sudo dnf upgrade --refresh
  2. Installer le plugin dnf de montée de version
    sudo dnf install dnf-plugin-system-upgrade
  3. Démarrer la montée de version
    • ajouter les dépôts correspondants à la nouvelle version de Fedora
    • télécharger les paquets RPM (sans les installer)
    • tester la transaction d’installation
    • etc…
    sudo dnf system-upgrade download --releasever=28

    On peut ajouter l’option ‐‐allowerasing pour régler le problème de paquet non disponible pour la nouvelle version, de dépendances cassées ou de paquets retirés.

  4. Redémarrage et application de la montée de version
    sudo dnf system-upgrade reboot
  5. Faire une longue pause. Le système va installer énormément de paquets et redémarrer (encore) une fois l’opération terminée. C’est l’étape la plus stressante pour ceux qui n’ont pas encore l’habitude de faire des montées de version.

Problèmes rencontrés

  • Le paquet vim-enhanced est en conflit avec vim-common, résolu en autorisant la suppression du paquet vim-enhanced: sudo dnf upgrade --best --allowerasing
  • Le relais SMTP vers GMail ne fonctionne plus, résolu en recréant le fichier contenant mes identifiants GMail et utilisable par postfix (sasl_passwd.db), et en redémarrant postfix.
    sudo postmap /etc/postfix/sasl_passwd && sudo systemctl restart postfix

Mise en place de l’HPKP en utilisant un certificat letsencrypt

Par défaut, letsencrypt génère une clé et une demande de certificat à chaque renouvellement, ce qui modifie l’empreinte du certificat et rend donc HPKP inutilisable. Il existe pourtant un autre moyen de faire: générer un clé et une demande de certificat à la main et laisser à letsencrypt seulement le soin de générer le certificat à partir de ce dernier.

Si letsencrypt est (ou a déjà été utilisé), il est préférable de renommer/déplacer le répertoire /etc/letsencrypt. On commence par construire l’arborescence afin de ne pas mélanger les torchons avec les serviettes. Le fichier de configuration d’un côté, les demandes de certificats d’un autre, les certificats avec les certificats…

mkdir -p /etc/pki/tls/{conf,private,csr}

On va ensuite écrire un petit fichier de configuration openssl pour nos demandes de certificats (csr)

Fichier /etc/pki/tls/conf/example.com.conf

[req]
default_bits       = 4096
prompt             = no
default_md         = sha512
req_extensions     = req_ext
distinguished_name = dn

[dn]
C            = FR
ST           = Occitanie
L            = Beaucaire
O            = Example
OU           = devops
CN           = example.com
emailAddress = webmaster@example.com

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1  = example.com
DNS.2  = www.example.com
DNS.3  = blog.example.com

On génère la clé privée principale et les deux clés privées de secours.

openssl genrsa -out /etc/pki/tls/private/example.com.1.key 4096
openssl genrsa -out /etc/pki/tls/private/example.com.2.key 4096
openssl genrsa -out /etc/pki/tls/private/example.com.3.key 4096

On génère la demande de certificat principale et les demandes de certificat de secours

openssl req -new -key /etc/pki/tls/private/example.com.1.key -sha512 -subj "/CN=example.com/C=FR/ST=Occitanie/L=Beaucaire/O=Example/OU=devops" -out /etc/pki/tls/csr/example.com.1.csr -config <( cat /etc/pki/tls/conf/example.com.conf )
openssl req -new -key /etc/pki/tls/private/example.com.2.key -sha512 -subj "/CN=example.com/C=FR/ST=Occitanie/L=Beaucaire/O=Example/OU=devops" -out /etc/pki/tls/csr/example.com.2.csr -config <( cat /etc/pki/tls/conf/example.com.conf )
openssl req -new -key /etc/pki/tls/private/example.com.3.key -sha512 -subj "/CN=example.com/C=FR/ST=Occitanie/L=Beaucaire/O=Example/OU=devops" -out /etc/pki/tls/csr/example.com.3.csr -config <( cat /etc/pki/tls/conf/example.com.conf )

On vérifie que les demandes de certificat contiennent bien les noms d’hôtes alternatifs

openssl req -noout -text -in /etc/pki/tls/csr/example.com.1.csr
openssl req -noout -text -in /etc/pki/tls/csr/example.com.2.csr
openssl req -noout -text -in /etc/pki/tls/csr/example.com.3.csr

On demande notre certificat, en fournissant une demande de certificat. letsencrypt refusera de générer un certificat ayant le nom fourni si le fichier existe déjà. C’est la raison d’être de l’ID dans le nom de fichier (0) et qui devra être incrémenter à chaque renouvellement réussi.

letsencrypt certonly --csr /etc/pki/tls/csr/example.com.1.csr --agree-tos --webroot --webroot-path /var/www/html --cert-path /etc/pki/tls/certs/example.com.0.crt --chain-path /etc/pki/tls/certs/example.com.0.chain.pem --fullchain-path /etc/pki/tls/certs/example.com.0.fullchain.pem  --cert-name example.com -m webmaster@example.com

On visualise le certificat reçu

openssl x509 -noout -text -in /etc/pki/tls/certs/example.com.0.crt

On peut enfin obtenir nos précieuses empreintes. On remarque que l’on se sert de la demande de certificats et non du certificat, mais on aurait tout aussi bien pu générer l’empreinte à partir de celui-ci. De toute façon ces empreintes ne changeront plus.

openssl req -pubkey < /etc/pki/tls/csr/example.com.1.csr | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
openssl req -pubkey < /etc/pki/tls/csr/example.com.2.csr | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
openssl req -pubkey < /etc/pki/tls/csr/example.com.3.csr | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

Et on ajoute nos empreintes à la configuration apache.

Header always set Public-Key-Pins 'pin-sha256="yx0XVK0m6iAMItPEcQgfcaqTkDQ1kmabKEyM4NRsGfY=";pin-sha256="JENGnxtpMMePxB2//+UZ5nTjvotOCb/Kn2q1xC8hwoI=";pin-sha256="ufCAWYtHpYA2GL5J28oZsFE/zWoWhcJiO+WDX6LseXA=";max-age=2592000;includeSubdomains"

Se construire un serveur de tuile OpenStreetMap

Le résultat final

Après un discution avec Jean-Yvon Landrac (Orolia SAS) au State of the Map France 2017, à Avignon, j’ai eu envie d’implémenter mon propre serveur de cartographie. Comme je ne suis ni un debianeux ni un ubuntiste, le défi est de l’installer sur CentOS 7 ou Fedora.

Pour construire mon propre serveur de tuiles OpenStreetMap (OSM), l’utilisation de RPM facilite grandement la tâche.

Il sera basé sur:

  • postgresql avec les extensions postgis et hstore qui hébergeront les données OSM
  • Apache et mod_tile pour servir les tuiles au navigateur
  • renderd, le chef d’orchestre du rendu
  • mapnik, la bibliothèque qui construira les images en fonction des données stockées en base.
  • osm-carto, le style officiel d’OSM
  • carto le compilateur de feuille de style
  • osmctools qui manipulera les données OSM (notamment pour fusionner deux extraits de données OSM
  • osm2pgsql qui intégrera en base les extraits de données OSM

Architecture d’un serveur de tuiles

Il faudra prévoir quand même pas mal d’espace disque: la taille de la base de données sera de 500Go environ pour l’Europe, 85Go environ pour la France entière, ou de 11Go pour les deux seules régions utilisées.

Le VPS utilisé ici possède 16Go de RAM et 2 vCPUs.

Installation

Seulement sur CentOS 7

  • Beaucoup de paquets utilisés proviennent du dépôt epel, on commence donc par s’assurer qu’il est installé.
    sudo yum install epel-release
  • Les paquets RPMs contenant les outils OSM proviennent de mes dépôts COPR, on va donc créer le fichier de dépôt /etc/yum.repos.d/tartare.repo
    sudo cat << eOF > /etc/yum.repos.d/tartare.repo
    [tartare-mapnik]
    name=Copr repo for mapnik owned by tartare
    baseurl=https://copr-be.cloud.fedoraproject.org/results/tartare/mapnik/epel-7-$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/tartare/mapnik/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    
    [tartare-python-mapnik]
    name=Copr repo for python-mapnik owned by tartare
    baseurl=https://copr-be.cloud.fedoraproject.org/results/tartare/python-mapnik/epel-7-$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/tartare/python-mapnik/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    
    [tartare-mod_tile]
    name=Copr repo for mod_tile owned by tartare
    baseurl=https://copr-be.cloud.fedoraproject.org/results/tartare/mod_tile/epel-7-$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/tartare/mod_tile/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    
    [tartare-osmosis-bin]
    name=Copr repo for osmosis-bin owned by tartare
    baseurl=https://copr-be.cloud.fedoraproject.org/results/tartare/osmosis-bin/epel-7-$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/tartare/osmosis-bin/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    
    [tartare-osmctools]
    name=Copr repo for osmctools owned by tartare
    baseurl=https://copr-be.cloud.fedoraproject.org/results/tartare/osmctools/epel-7-$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/tartare/osmctools/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    
    [tartare-openstreetmap-carto]
    name=Copr repo for openstreetmap-carto owned by tartare
    baseurl=https://copr-be.cloud.fedoraproject.org/results/tartare/openstreetmap-carto/epel-7-$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/tartare/openstreetmap-carto/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    EOF
    

Seulement sur Fedora

    • On installe mes dépôts COPR
sudo dnf copr enable tartare/mod_tile 
sudo dnf copr enable tartare/osmosis-bin
sudo dnf copr enable tartare/openstreetmap-carto 

Pour les deux distributions
Si on souhaite faire l’installation dans des partitions séparées, il faut préparer le terrain. La partition /var/lib/pgsql sera, de préférence, sur un disque SSD.

Partition 2 régions France Europe
/var/lib/mod_tile 50Go
/var/lib/openstreetmap-carto 3Go
/var/lib/pgsql 20Go 100Go 500Go
  1. Création des 3 partitions et formatage en xfs (ou en ext4)
  2. création des points de montages
    sudo mkdir /var/lib/pgsql /var/lib/mod_tile /var/lib/openstreetmap-carto
  3. Modification de la table des partitions en éditant le fichier /etc/fstab
  4. Montage de nos partions
    sudo mount -a

On installe la base de données postgresql avec l’extension postgis

sudo yum -y install postgresql-server postgresql-contrib postgis postgis-utils

Puis les outils OSM et quelques dépendances

sudo yum install mapnik mapnik-utils python2-mapnik mod_tile osm2pgsql \
                 osmctools openstreetmap-carto PyYAML
sudo yum install npm wget git unzip java

On installe maintenant l’utilitaire carto, via npm

sudo npm -g install carto

Le paquet mod_tile créé un utilisateur système osm mais celui-ci n’a pas de shell (compte système = shell /sbin/nologin). Par flemme simplicité, on lui attribue un shell et on lui ajoute le groupe wheel, et on lui affecte un mot de passe, le temps de l’installation seulement.

usermod -s /bin/bash osm
usermod -aG wheel osm
passwd osm

On créé le répertoire volatile d’accueil de la socket renderd

sudo systemd-tmpfiles --create

Initialisation de la base de données

L’initialisation de postgresql est triviale

sudo postgresql-setup initdb
sudo systemctl enable postgresql httpd
sudo systemctl start postgresql httpd

On va maintenant configurer finement notre base de données en modifiant le fichier /var/lib/pgsql/data/postgresql.conf:

  • shared_buffers = 128MB
  • maintenance_work_mem = 256MB

De manière temporaire, afin d’optimiser le temps d’import de notre fichier français, on peut aussi modifier ces valeurs. Toutefois ces valeurs doivent être remis à on une fois l’import terminé.

  • autovacuum = off

On applique les changements

sudo systemctl restart postgresql

On initialise la base de données

sudo -u postgres createuser -s osm
sudo -u postgres createuser apache
sudo -u postgres createdb -E UTF8 -O osm gis
sudo -u postgres psql
\c gis
CREATE EXTENSION postgis;
CREATE EXTENSION hstore;
ALTER TABLE geometry_columns OWNER TO osm;
ALTER TABLE spatial_ref_sys OWNER TO osm;
\q

Si la création de la base échoue avec l’erreur (ref http://stackoverflow.com)
createdb: database creation failed: ERROR: new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)

sudo -u postgres psql 
psql (9.2.18)
Type "help" for help.

postgres=# UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
UPDATE 1
postgres=# DROP DATABASE template1;
DROP DATABASE
postgres=# CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UNICODE';
CREATE DATABASE
postgres=# UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';
UPDATE 1
postgres=# \c template1
You are now connected to database "template1" as user "postgres".
template1=# VACUUM FREEZE;
VACUUM
template1=# \q

Style osm-carto

A partir d’ici, nous utiliserons le compte utilisateur osm exclusivement (pour toutes les commandes du reste de ce tutorial).

sudo su - osm

Pour pouvoir générer des tuiles, il faudra utiliser une feuille de style (un peu comme le css des pages web). Le style par défaut d’OSM est utilisé.

Il faut environ 2Go de libre dans /var pour les fichiers shapefile

cd /usr/share/openstreetmap-carto/
scripts/get-shapefiles.py
sudo ln -s /var/lib/openstreetmap-carto/style.xml /usr/share/openstreetmap-carto/style.xml
carto -a "3.0.0" project.mml > style.xml

On modifie le fichier /etc/renderd.conf pour spécifier notre feuille de style

[default]
...
XML=/usr/share/openstreetmap-carto/style.xml

Import des données

Pour complexifier un peu la tâche, on souhaite avoir deux régions (c’est cool d’être limitrophe):

  • PACA
  • Languedoc-Roussillon

osmconvert fera le travail de fusion.

On créé le répertoire d’accueil de nos extraits de planet et on s’assure que la partition est assez grande pour contenir le(s) fichier(s)

sudo mkdir /home/osm
sudo chown osm:osm /home/osm
cd /home/osm

On télécharge nos deux extraits de planet, en prenant soin de télécharger d’abord le checksum md5 et le fichier state.txt pour avoir le timestamp de l’extrait. Ce timestamp sera utilisé par l’utilitaire osmosis lors de la mise à jour de la base de données. Par sécurité, on applique le timestamp du site geofabrik sur nos extraits. On aura un backup gratuit d’une information essentielle au processus de mise à jour.

cd /home/osm
mkdir provence-alpes-cote-d-azur
pushd provence-alpes-cote-d-azur
wget http://download.geofabrik.de/europe/france/provence-alpes-cote-d-azur-latest.osm.pbf.md5
wget http://download.geofabrik.de/europe/france/provence-alpes-cote-d-azur-updates/state.txt
wget http://download.geofabrik.de/europe/france/provence-alpes-cote-d-azur-latest.osm.pbf
popd

mkdir languedoc-roussillon
pushd languedoc-roussillon
wget http://download.geofabrik.de/europe/france/languedoc-roussillon-latest.osm.pbf.md5
wget http://download.geofabrik.de/europe/france/languedoc-roussillon-updates/state.txt
wget http://download.geofabrik.de/europe/france/languedoc-roussillon-latest.osm.pbf
popd

On les vérifie

pushd /home/osm/provence-alpes-cote-d-azur
md5sum -c provence-alpes-cote-d-azur-latest.osm.pbf.md5
popd
pushd /home/osm/languedoc-roussillon
md5sum -c languedoc-roussillon-latest.osm.pbf.md5
popd

et on les fusionne

osmconvert languedoc-roussillon/languedoc-roussillon-latest.osm.pbf --out-o5m | osmconvert - provence-alpes-cote-d-azur/provence-alpes-cote-d-azur-latest.osm.pbf -o=sud-est.osm.pbf

Nous allons utiliser la commande osm2pgsql pour importer les données

  • La valeur du paramètre C est d’environ 2/3 de la RAM
  • l’option –slim est obligatoire si des mises à jour sont prévues
  • on spécifie notre feuille de style avec l’option –style
  • on spécifie le nom de notre base de données avec l’option -d
  • on spécifie le nombre de tâches à paralléliser (lorsque c’est possible), à adapter en fonction du nombre de CPU disponible.
  • on importe notre fusion d’extrait du planet

Il est préférable de lancer cette commande avec nohup, ce qui évitera d’arrêter le processus en cas de déconnexion intempestive.

osm2pgsql --slim --hstore --style /usr/share/openstreetmap-carto/openstreetmap-carto.style --tag-transform-script /usr/share/openstreetmap-carto/openstreetmap-carto.lua -d gis -C 10000 --number-processes 2 /home/osm/sud-est.osm.pbf

osm2pgsql version 0.92.0 (64 bit id space)

Using lua based tag processing pipeline with script /usr/share/openstreetmap-carto/openstreetmap-carto.lua
Using projection SRS 3857 (Spherical Mercator)
Setting up table: planet_osm_point
Setting up table: planet_osm_line
Setting up table: planet_osm_polygon
Setting up table: planet_osm_roads
Allocating memory for dense node cache
Allocating dense node cache in one big chunk
Allocating memory for sparse node cache
Sharing dense sparse
Node-cache: cache=10000MB, maxblocks=160000*65536, allocation method=11
Mid: pgsql, scale=100 cache=10000
Setting up table: planet_osm_nodes
Setting up table: planet_osm_ways
Setting up table: planet_osm_rels

Reading in file: /home/osm/sud-est.osm.pbf
Using PBF parser.
Processing: Node(46593k 6.4k/s) Way(6822k 1.91k/s) Relation(39430 79.66/s)  parse time: 11438s
Node stats: total(46593329), max(4919729897) in 7336s
Way stats: total(6822494), max(501069714) in 3577s
Relation stats: total(39438), max(7336004) in 495s
Committing transaction for planet_osm_point
Committing transaction for planet_osm_line
Committing transaction for planet_osm_polygon
Committing transaction for planet_osm_roads
Setting up table: planet_osm_nodes
Setting up table: planet_osm_ways
Setting up table: planet_osm_rels
Using lua based tag processing pipeline with script /usr/share/openstreetmap-carto/openstreetmap-carto.lua
Setting up table: planet_osm_nodes
Setting up table: planet_osm_ways
Setting up table: planet_osm_rels
Using lua based tag processing pipeline with script /usr/share/openstreetmap-carto/openstreetmap-carto.lua

Going over pending ways...
        5962327 ways are pending

Using 2 helper-processes
Finished processing 5962327 ways in 6957 s

5962327 Pending ways took 6957s at a rate of 857.03/s
Committing transaction for planet_osm_point
Committing transaction for planet_osm_line
Committing transaction for planet_osm_polygon
Committing transaction for planet_osm_roads
Committing transaction for planet_osm_point
Committing transaction for planet_osm_line
Committing transaction for planet_osm_polygon
Committing transaction for planet_osm_roads

Going over pending relations...
        0 relations are pending

Using 2 helper-processes
Finished processing 0 relations in 0 s

Committing transaction for planet_osm_point
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_line
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_polygon
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_roads
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_point
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_line
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_polygon
WARNING:  there is no transaction in progress
Committing transaction for planet_osm_roads
WARNING:  there is no transaction in progress
Sorting data and creating indexes for planet_osm_roads
Sorting data and creating indexes for planet_osm_polygon
Sorting data and creating indexes for planet_osm_line
Sorting data and creating indexes for planet_osm_point
Copying planet_osm_roads to cluster by geometry finished
Creating geometry index on planet_osm_roads
Creating osm_id index on planet_osm_roads
Creating indexes on planet_osm_roads finished
All indexes on planet_osm_roads created in 476s
Completed planet_osm_roads
Copying planet_osm_point to cluster by geometry finished
Creating geometry index on planet_osm_point
Copying planet_osm_line to cluster by geometry finished
Creating geometry index on planet_osm_line
Creating osm_id index on planet_osm_point
Creating indexes on planet_osm_point finished
All indexes on planet_osm_point created in 1624s
Completed planet_osm_point
Creating osm_id index on planet_osm_line
Creating indexes on planet_osm_line finished
All indexes on planet_osm_line created in 2291s
Completed planet_osm_line
Copying planet_osm_polygon to cluster by geometry finished
Creating geometry index on planet_osm_polygon
Creating osm_id index on planet_osm_polygon
Creating indexes on planet_osm_polygon finished
All indexes on planet_osm_polygon created in 5913s
Completed planet_osm_polygon
Stopping table: planet_osm_nodes
Stopped table: planet_osm_nodes in 0s
Stopping table: planet_osm_ways
Building index on table: planet_osm_ways
Stopped table: planet_osm_ways in 10042s
Stopping table: planet_osm_rels
Building index on table: planet_osm_rels
Stopped table: planet_osm_rels in 97s
node cache: stored: 46593329(100.00%), storage efficiency: 52.06% (dense blocks: 1698, sparse nodes: 37792830), hit rate: 100.00%

Osm2pgsql took 34458s overall

Durée environ 9 heures et 35 minutes

On génère les index

cd /usr/share/openstreetmap-carto/
scripts/indexes.py | psql -d gis

Le chef d’orchestre du rendu: renderd

On vérifie d’abord la configuration: fichier /etc/renderd.conf

[renderd]
num_threads=2

[mapnik]
plugins_dir=/usr/lib64/mapnik/input
font_dir=/usr/share/fonts

[default]
XML=/usr/share/openstreetmap-carto/style.xml

On adapte le paramètre num_threads de la section [renderd] pour refléter le nombre de vCPUs.

Puis on tente de lancer le démon à la main, avec l’utilisateur osm, afin de vérifier que tout fonctionne comme attendu.

/usr/sbin/renderd -f -c /etc/renderd.conf

On essaie de télécharger une tuile pour vérifier que tout fonctionne: localhost/mod_tiles/0/0/0.png

Si tout est correct, on obtient cette image.

On arrête notre instance de renderd (CTRL+C) et on lance le démon

sudo systemctl start renderd
sudo systemctl enable renderd

Pré-génération des tuiles (optionnel)

Afin d’avoir une bonne réactivité de la carte, il est nécessaire de pré-générer la génération des tuiles. Un niveau de zoom 10 devrait suffir, les niveaux supérieurs seront générer à la demande. Bien évidemment, ça aussi, ça prend énormement du temps.

Le démon renderd doit être démarré et opérationnel
Il est préférable de lancer cette commande avec nohup

render_list -a -n 2 -Z 10

Zoom 01: min: 25.0 avg: 25.0 max: 25.0  over a total of    25.0s in    1 requests
Zoom 02: min: 36.2 avg: 36.2 max: 36.2  over a total of    36.2s in    1 requests
Zoom 03: min: 42.1 avg: 42.1 max: 42.1  over a total of    42.1s in    1 requests
Zoom 04: min:  3.4 avg: 47.9 max: 92.4  over a total of   191.6s in    4 requests
Zoom 05: min:  2.5 avg: 11.8 max: 84.1  over a total of   189.6s in   16 requests
Zoom 06: min:  1.6 avg:  6.7 max: 129.3 over a total of   426.7s in   64 requests
Zoom 07: min:  1.8 avg:  4.0 max: 222.0 over a total of  1025.7s in  256 requests
Zoom 08: min:  1.7 avg:  3.2 max: 12.4  over a total of  3313.9s in 1023 requests
Zoom 09: min:  1.0 avg:  3.7 max: 193.0 over a total of 15224.3s in 4090 requests
Zoom 10: min:  0.9 avg:  3.0 max: 123.8 over a total of 49596.2s in 16373 requests

La page web magique

Si l’installation a été faite en RPM

sudo cp /usr/share/doc/mod_tile-0.5/slippymap.html /var/www/html/index.html

Sinon, la page web est à copier depuis le répertoire des sources de mod_tile (/home/osm dans ce tutorial)

Le rendu est visible sur map.tartarefr.eu
On modifie quelques variables:

  • var lat Mettre la lattitude correspondante au centre des extraits de planet utilisés (43.81 pour mes extraits de planet)
  • var lon Mettre la longitude correspondante au centre des extraits de planet utilisés (4.64 pour mes extraits de planet)
  • var newLayer On modifie l’URL car par défaut, il faut ajouter l’alias /osm_tiles pour pouvoir être servi en tuile par notre serveur (/osm_tiles/${z}/${x}/${y}.png)

On peut aussi rajouter la ligne avant la fonction init(), afin d’éviter les tuiles roses pales (tuile par défaut correspondant à un fichier non trouvé). Le javascript du navigateur fera 5 tentatives avant de déclarer que le fichier n’existe pas.

OpenLayers.IMAGE_RELOAD_ATTEMPTS = 5;

Mise à jour de la base Postgis

L’utilitaire osmosis s’occupera de télécharger les mises à jour et osm2pgsql les intégrera en base. Toujours avec l’utilisateur osm, on initialisera le processus et on le lancera de manière périodique (tâche cron)

  • On initialise la mise à jour pour le premier extrait de planet (l’ordre de traitement des extraits importe peu)
    export WORKDIR_OSM=/var/lib/mod_tile/.osmosis/provence-alpes-cote-d-azur
    mkdir -p ${WORKDIR_OSM}
    cd ${WORKDIR_OSM}
    osmosis --read-replication-interval-init workingDirectory=${WORKDIR_OSM}

    Même chose pour le deuxième

    export WORKDIR_OSM=/var/lib/mod_tile/.osmosis/languedoc-roussillon
    mkdir -p ${WORKDIR_OSM}
    cd ${WORKDIR_OSM}
    osmosis --read-replication-interval-init workingDirectory=${WORKDIR_OSM}
  • On modifie les URL de mise à jour (on utilise geofabrik)
    sed -i -e "/^baseUrl/ s;=.*$;=http://download.geofabrik.de/europe/france/provence-alpes-cote-d-azur-updates;" /var/lib/mod_tile/.osmosis/provence-alpes-cote-d-azur/configuration.txt
    sed -i -e "/^baseUrl/ s;=.*$;=http://download.geofabrik.de/europe/france/languedoc-roussillon-updates;" /var/lib/mod_tile/.osmosis/languedoc-roussillon/configuration.txt
  • On copie le fichier state.txt pour qu’osmosis connaisse le point de départ de la mise à jour
    cp /home/osm/provence-alpes-cote-d-azur/state.txt /var/lib/mod_tile/.osmosis/provence-alpes-cote-d-azur/
    cp /home/osm/languedoc-roussillon/state.txt /var/lib/mod_tile/.osmosis/languedoc-roussillon/
    
  • On lance la première tâche de mise à jour manuellement pour s’assurer que tout fonctionne correctement
    export WORKDIR_OSM=/var/lib/mod_tile/.osmosis/provence-alpes-cote-d-azur
    cd ${WORKDIR_OSM}
    osmosis --read-replication-interval workingDirectory=${WORKDIR_OSM} \
            --write-xml-change /tmp/change.osm.gz
    osm2pgsql --append --slim --hstore \
              --style /usr/share/openstreetmap-carto/openstreetmap-carto.style \
              --tag-transform-script /usr/share/openstreetmap-carto/openstreetmap-carto.lua \
              -d gis -C 10000 --number-processes 2 \
              -e 10-20 -o /tmp/expire.list
              /tmp/change.osm.gz
    render_expired --min-zoom=10 --delete-from=15 < /tmp/expire.list
    rm -f /tmp/change.osm.gz
    rm -f /tmp/expire.list
    
    export WORKDIR_OSM=/var/lib/mod_tile/.osmosis/languedoc-roussillon
    cd ${WORKDIR_OSM}
    osmosis --read-replication-interval workingDirectory=${WORKDIR_OSM} \
            --write-xml-change /tmp/change.osm.gz
    osm2pgsql --append --slim --hstore \
              --style /usr/share/openstreetmap-carto/openstreetmap-carto.style \
              --tag-transform-script /usr/share/openstreetmap-carto/openstreetmap-carto.lua \
              -d gis -C 10000 --number-processes 2 \
              -e 10-20 -o /tmp/expire.list
              /tmp/change.osm.gz
    render_expired --min-zoom=10 --delete-from=15 < /tmp/expire.list
    rm -f /tmp/change.osm.gz
    rm -f /tmp/expire.list

Le lancement des commandes (script de mise à jour osm-update.sh) en tâche planifiée du dernier point est laissé en exercice, juste quelques conseils:

  • il faut absolument lancer les tâches planifiées avec l'utilisateur osm.
  • Je péfère écrire les fichiers temporaires dans /tmp, car c'est un système de fichier en RAM (plus rapide), mais on peut tout à fait l'écrire dans n'importe quel répertoire où l'utilisateur osm a des droits lecture/écriture
  • Le lancement des commandes ci-dessus ne met en base de données que des modifications d'une seule journée. Si plusieurs jours se sont écoulés depuis l'installation, il faudra lancer ces commandes autant de fois que de jours en retard pour rattraper le lag. Le plus simple est d'obtenir le numéro de la dernière mise à jour sur le site de geofabrik et de comparer ce chiffre à celui du fichier state.txt du répertoire de travail d'osmosis (qui est incrémenté de 1 à chaque commande de mise à jour. Si beaucoup de jour ce sont écoulés, il vaut mieux télécharger toutes les mises à jour et les fusionner dans un seule fichier (osmctools: osmconvert).
  • On peut tout à fait appliquer une mise à jour qui a déjà été intégrée. C'est même conseiller dans le cas d'utilisation des minutely changes officielles.
  • On peut utiliser un pipe entre les tâche osmosis et osm2pgsql afin d'éviter de passer par un fichier temporaire mais je profite de ce fichier temporaire pour faire la mise à jour de nominatim (hors sujet de l'installation d'un serveur de tuiles)
  • On régénère les tuiles qui ont été modifiées par la mise à jour si le zoom est compris entre 10 et 14 et on les supprime simplement si le zoom est supérieur ou égal à 15 (elles seront reconstruites à la demande)

Post installation

Maintenant que tout est installé, configuré et opérationnel, on va enlever le shell à l'utilisateur osm, le retirer du groupe wheel, lui affecter un mot de passe vide et on réactive l'autovacuum de postgresql. Ces commandes sont à lancer avec notre utilisateur normal (ayant servi à l'installation des RPMs).

sudo usermod -s /sbin/nologin osm
sudo passwd -d osm
sudo gpasswd -d osm wheel
sudo sed -i -e '/^autovacuum/ s/off/on/' /var/lib/pgsql/data/postgresql.conf
sudo systemctl restart postgresql

Références

Sécuriser son VPS centos/redhat/fedora en 10 étapes.

Pré-requis

Depuis notre poste client, on s’assure qu’une paire de clés (rsa c’est mieux que dsa) existe pour notre utilisateur courant. Celle-ci servira à se connecter à notre VPS sans mot de passe, mais de manière sécurisée. Si ce n’est pas le cas, on n’en créé une et on lui affecte un mot de passe.

ssh-keygen -t rsa

On se sert ensuite de l’agent SSH pour ne renseigner le mot de passe de notre clé privée qu’une seule fois, mais si on préfère le taper à chaque fois ….

ssh-add

Sécurisation

On peut maintenant s’occuper de notre VPS:

  1. Dès réception du mail de confirmation d’installation, on se connecte en SSH sur notre VPS, avec le login root et le mot de passe fourni.
    Cette session ne doit pas être fermée avant de pouvoir se connecter sans mot de passe (par clé donc) avec l’utilisateur qui sera créé (point 7).
  2. On modifie tout de suite le mot de passe root
    passwd
  3. On ajoute un utilisateur standard et on lui affecte un mot de passe
    useradd -m -s /bin/bash -c "<Nom> <Prenom>" <mon-user>
    passwd <mon-user>
  4. Depuis notre poste client, on autorise notre clé sur le serveur
    ssh-copy-id <mon-user>@<ip-de-mon-vps>
  5. On Modifie la configuration du service SSH (typiquement le fichier /etc/ssh/sshd_config):
    • ne plus autoriser le super-utilisateur root
      PermitRootLogin no
    • ne plus autoriser les connexions par mot de passe (n’accepter que les connexions par clé)
      PasswordAuthentication no
    • On redémarre le service ssh (fedora, centos7 ou rhel7)
      systemctl restart sshd

      ou sur les distributions ne prenant pas en charge systemd (centos6 ou rhel6)

      service sshd restart
  6. Dans un autre shell, on s’assure que notre utilisateur peut se connecter au VPS sans mot de passe
    ssh <mon-user>@<ip-de-mon-vps>
  7. Maintenant que la porte d’entrée a été changée, que notre VPS est bien accessible depuis notre poste client avec un utilisateur normal, on peut fermer le shell ouvert en début de procédure, mais cela reste optionnel
  8. On met à jour son système
    yum update
  9. On met en place un firewall
    Si le service firewalld est disponible, il est normalement déjà installé:

    1. On obtient la zone par défaut
      firewall-cmd --get-default-zone
      public
      

      Notre zone par défaut s’appelle public

    2. On vérifie que le service ssh est autorisé sur le firewall
      firewall-cmd --zone=public --list-services
      dhcpv6-client ssh
    3. Si le service ssh n’est pas dans la liste, on l’ajoute (de manière permanente) et on recharge le firewall
      firewall-cmd --zone=public --permanent --add-service=ssh
      firewall-cmd --reload

    Sinon, on utilise le service iptables

    1. Si firewalld est installé mais qu’il ne fonctionne pas (merci à l’hébergeur qui ne sait pas configurer la technologie proxmox correctement), on le supprime
      yum remove firewalld
    2. On installe le service iptables
      yum install iptables-services
    3. On le démarre et on l’active
      systemctl start iptables
      systemctl enable iptables

      Ou sur les distributions ne prenant pas en charge systemd (centos6 ou rhel6)

      service iptables start
      chkconfig iptables on
      
    4. On met en place une configuration minimale dans le fichier /etc/sysconfig/iptables: on autorise le trafic entrant sur la boucle locale (localhost), les pings (protocole icmp), ainsi que le trafic entrant sur les connexions déjà établies ( state RELATED,ESTABLISHED ) plus le trafic entrant sur le port 22 (ssh) et surtout on ignore tout le reste avec la politique par défaut à DROP. Par contre, on autorise le trafic sortant.
      # sample configuration for iptables service
      # you can edit this manually or use system-config-firewall
      # please do not ask us to add additional ports/services to this default configuration                                                                                             
      *filter 
      :INPUT DROP [0:0]
      :FORWARD DROP [0:0]
      :OUTPUT ACCEPT [0:0]
      -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
      -A INPUT -p icmp -j ACCEPT
      -A INPUT -i lo -j ACCEPT
      -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
      COMMIT
      
    5. On redémarre le service iptables
      systemctl restart iptables

      Ou sur les distributions ne prenant pas en charge systemd (centos6 ou rhel6)

      service iptables restart
  10. On met en place le service fail2ban sur le service ssh
    • Installation
      yum install fail2ban
    • On met en place une configuration minimale: on surcharge le fichier jail.conf dans le fichier jail.local pour bannir les gros lourds 48 heures directement, car il ne peut y avoir de mots de passe mal renseigné, vu qu’on accepte uniquement les connexions par clés.
      [DEFAULT]
      ignoreip = 127.0.0.1/8 <ip-de-mon-vps>
      bantime = 3600
      banaction = firewallcmd-ipset
      banaction_allports = firewallcmd-ipset
      backend = systemd
      sender = fail2ban@<mon-domaine>
      destemail = root
      action = %(action_mwl)s
      
      [sshd]
      enabled  = true
      maxretry = 5
      bantime  = 172800
      findtime = 172800
      

      ou si firewalld n’est pas disponible

      [DEFAULT]
      ignoreip = 127.0.0.1/8 <ip-de-mon-vps>
      bantime = 3600
      banaction = iptables-multiport
      banaction_allports = iptables-allports
      backend = systemd
      sender = fail2ban@<mon-domaine>
      destemail = root
      action = %(action_mwl)s
      
      [sshd]
      enabled  = true
      maxretry = 5
      bantime  = 172800
      findtime = 172800
      
    • On démarre et on active le service
      systemctl start fail2ban
      systemctl enable fail2ban

      Ou sur les distributions ne prenant pas en charge systemd (centos6 ou rhel6)

      service fail2ban start
      chkconfig fail2ban on
      

Voilà, le VPS est maintenant sécurisé et les premiers courriels de bannissement des gros relous ne devraient pas tarder…

WordPress et CSP: unsafe-inline et unsafe-eval (script-src)

Ou comment supprimer unsafe-inline et unsafe-eval de script-src dans l’en-tête HTTP Content-Security-Policy avec WordPress et obtenir un A+ sur securityheaders.io.

Déjà, autant prévenir, ça va être long et pénible… Cela se fera à coup de petite modification, test, re petite modification. Oui modification est au singulier, car on ne change qu’un paramètre à la fois entre chaque test.

WordPress seul est dans l’ensemble assez propre, dans le sens où il n’embarque que très peu de javascript inline. Il n’y a donc que quatre problèmes à résoudre

  1. Désactiver les emojis, avec un plugin pour ne pas toucher au code
  2. Choisir un thème n’utilisant pas de javascript inline
  3. N’utiliser que des plugins sans javascript inline
  4. Ajouter une exception pour la partie admin

Ça parait simple dit comme ça, mais dans la pratique, le moindre plugin (surtout ceux de galerie d’images) peut venir violer la politique de sécurité du contenu (Content-Security-Policy). De plus, pour avoir les rapports de violation des CSP, il va falloir un script qui enregistre tout ça.

Prérequis

  • On s’écrit un petit script PHP perfectible qui enregistrera les violations. Vu le peu de sécurité offert par ce script, il est préférable de le durcir et de mutualiser l’enregistrement des rapports: Expect-Certificate-Transparency (ect) et Public-Key-Pins (pkp). Ou bien de simplement le supprimer une fois nos tests effectués.
    Fichier /usr/share/wordpress/report.php
    <?php
    date_default_timezone_set('UTC');
    $LOGPATH = "/var/log/httpd";
    $ROWS = array(
      'violated-directive',
      'effective-directive',
      'blocked-uri',
      'document-uri',
      'line-number',
      'status-code',
      'referrer',
      'disposition',
      'original-policy',
      'script-sample'
    );
    
    $header = date("Y-m-d H:i:s");
    $raw = file_get_contents('php://input');
    
    if ( ! isset( $raw ) or empty ( $raw ) or strlen( $raw ) >= 2048 ) {
      exit( 1 );
    }
    
    $rows = json_decode( $raw );
    $message = $header . ' csp-report' . PHP_EOL;
    foreach( $ROWS as $row ) {
      $message .= "  $row: " . $rows->{'csp-report'}->{$row} . PHP_EOL;
    }
    $message .= PHP_EOL;
    
    $file = file_put_contents( $LOGPATH . "/report.log", $message, FILE_APPEND | LOCK_EX );
    
    echo "For reporting Content-Security-Policy violation";
    exit( 0 );
    
  • On va commencer en mettant une définition très restrictive dans la configuration apache.
    Header always set Content-Security-Policy "default-src 'self' data: ; script-src 'self' ; style-src 'self' 'unsafe-inline' ; font-src 'self' data: ; img-src 'self' data: ; report-uri https://blog.example.com/report.php"
    
  • On désactive tous les plugins WordPress

On verra apparaitre au rechargement de la page, les premiers rapports, dans le fichier /var/log/httpd/report.log

Premier test

Rapports de violation

  • C’est le seul problème du cœur de WordPress (hors partie admin) concernant la politique de sécurité du contenu: les emojis.
    2017-11-12 12:02:29 csp-report
      violated-directive: script-src https://www.tartarefr.eu
      effective-directive: 
      blocked-uri: self
      document-uri: https://www.tartarefr.eu/
      line-number: 12
      status-code: 
      referrer: 
      disposition: 
      original-policy: default-src https://www.tartarefr.eu data:; script-src https://www.tartarefr.eu; style-src https://www.tartarefr.eu 'unsafe-inline'; font-src https://www.tartarefr.eu data:; img-src https://www.tartarefr.eu data:; report-uri https://report.tartarefr.eu/report.php?type=csp
      script-sample: 
                            window._wpemojiSettings = {"baseUrl"...
    
  • Dans le CSS de notre thème, il y a une dépendance à https://fonts.googleapis.com
    2017-11-12 12:02:29 csp-report
      violated-directive: style-src https://www.tartarefr.eu 'unsafe-inline'
      effective-directive: 
      blocked-uri: https://fonts.googleapis.com
      document-uri: https://www.tartarefr.eu/
      line-number: 
      status-code: 
      referrer: 
      disposition: 
      original-policy: default-src https://www.tartarefr.eu data:; script-src https://www.tartarefr.eu; style-src https://www.tartarefr.eu 'unsafe-inline'; font-src https://www.tartarefr.eu data:; img-src https://www.tartarefr.eu data:; report-uri https://report.tartarefr.eu/report.php?type=csp
      script-sample:
    

Résolution

  • On ré-active (installe et active) le module Disable Emojis
  • On ajoute https://fonts.googleapis.com à notre en-tête Content-Security-Policy, dans la section style-src et on recharge Apache.
    Header always set Content-Security-Policy "default-src 'self' data: ; script-src 'self' ; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com ; font-src 'self' data: ; img-src 'self' data: ; report-uri https://report.example.com/report.php?type=csp"

Deuxième test

Rapports de violation

    • C’est la dépendance pour la police de caractère
      2017-11-12 12:06:52 csp-report
        violated-directive: font-src https://www.tartarefr.eu data:
        effective-directive: 
        blocked-uri: https://fonts.gstatic.com
        document-uri: https://www.tartarefr.eu/
        line-number: 
        status-code: 
        referrer: 
        disposition: 
        original-policy: default-src https://www.tartarefr.eu data:; script-src https://www.tartarefr.eu; style-src https://www.tartarefr.eu 'unsafe-inline' https://fonts.googleapis.com; font-src https://www.tartarefr.eu data:; img-src https://www.tartarefr.eu data:; report-uri https://report.tartarefr.eu/report.php?type=csp
        script-sample: 
      

Résolution

      • On ajoute https://fonts.gstatic.com à notre en-tête Content-Security-Policy, dans la section font-src et on recharge Apache.
        Header always set Content-Security-Policy "default-src 'self' data: ; script-src 'self' ; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com ; font-src 'self' data: https://fonts.gstatic.com ; img-src 'self' data: ; report-uri https://report.example.com/report.php?type=csp"

À partir d’ici, on reçoit plus de rapport de violation, sauf si on essaie la partie administrateur ou un plugin de galerie.

La partie administrateur

La partie administrateur du site ne fonctionne pas bien avec notre définition de Content-Security-Policy. On va encore l’adapter mais à la fin il faudra tricher pour la section script-src et modifier l’en-tête HTTP Content-Security-Policy pour la partie admin.
On va donc ajouter à notre définition Content-Security-Policy

  • https://code.jquery.com à notre en-tête Content-Security-Policy, dans la section style-src
  • https://code.jquery.com à notre en-tête Content-Security-Policy, dans la section img-src
  • https://secure.gravatar.com à notre en-tête Content-Security-Policy, dans la section img-src

Pour le reste, pas de recette miracle, hélas. La définition dans Apache devient ceci:

<Location "/wp-admin">
  ...
  Header always set Content-Security-Policy "default-src 'self' data: ; script-src 'self' 'unsafe-inline' 'unsafe-eval' ; style-src 'self' 'unsafe-inline' fonts.googleapis.com ; font-src 'self' fonts.gstatic.com data: ; img-src 'self' data: secure.gravatar.com ; report-uri https://report.example.com/report.php"
</Location>
Header always set Content-Security-Policy "default-src 'self' data: ; script-src 'self' ; style-src 'self' 'unsafe-inline' fonts.googleapis.com https://code.jquery.com ; font-src 'self' fonts.gstatic.com data: ; img-src 'self' data: https://code.jquery.com https://secure.gravatar.com ; report-uri https://report.example.com/report.php"

Test du site sur securityheaders.io

Bien évidemment, on ne teste pas la partie admin …