Tag Archives: server

Ansible par la pratique – Première partie: les bases

Ici, on a seulement notre hôte physique (localhost) faisant fonctionner une machine virtuelle (ipv4:192.168.122.243). Bien entendu, notre hôte physique Fedora a le paquet RPM ansible installé.

dnf install ansible

Notre machine virtuelle est une CentOS fraichement installée (minimal), sans utilisateur (pour l’instant).

Lancer des commandes manuellement

On va, dans un premier temps essayé de contacter notre machine virtuelle avec un simple ping.

ansible 192.168.122.243 -m ping
[WARNING]: Could not match supplied host pattern, ignoring: all
[WARNING]: provided hosts list is empty, only localhost is available
[WARNING]: Could not match supplied host pattern, ignoring: 192.168.122.243
[WARNING]: No hosts matched, nothing to do

Cela ne fonctionne pas.

En effet, Ansible ne connait pas notre cible et refuse de communiquer avec elle. De plus notre utilisateur n’existe pas sur la cible

Il nous faudrait déclarer l’hôte distant dans le fichier /etc/ansible/hosts pour que ça fonctionne. Mais comme on n’est pas le super-utilisateur, on va faire autrement, et en suivant le guide ansible des bonnes pratiques petit à petit.

On va créer deux répertoires, le nom peut-être ce que vous voulez, il faudra juste adapter les commandes pour utiliser ces répertoires:

  • production
  • staging (recettage ou pré-production)

et dans chaque répertoire on va créer un fichier nommé hosts, où l’on définira des groupes dans lesquels on déclarera nos hôtes. Ce fichier est au format INI.

Dans l’exemple suivant, on déclare notre machine virtuelle dans le groupe server et notre hôte local dans le groupe ansible.

Fichier staging/hosts
[server]
192.168.122.243

[ansible]
localhost

Et au lancement de la commande, on va spécifier le répertoire de notre environnement avec l’option -i, et pour utiliser le super-utilisateur sur notre cible, on le spéficie (avec demande de mot de passe)

ansible -i staging 192.168.122.243 --user root --ask-pass -m ping
192.168.122.243 | SUCCESS => {
"changed": false,
"failed": false,
"ping": "pong"
}

Cette fois ça fonctionne.
Essayons maintenant une commande

ansible -i staging 192.168.122.243 --user root --ask-pass -a whoami
192.168.122.243 | SUCCESS | rc=0 >>
root

Par défaut Ansible utilise le même utilisateur sur l’hôte distant. On s’assure donc que celui existe sur notre machine virtuelle ou que l’on spécifie l’utilisateur à utiliser dans la ligne de commande.

Lancer une série de commandes

Le fichier permettant de lister les commande à lancer s’appelle un playbook. Il peut être monolithique ou divisé en plusieurs parties. Ansible recommande la séparation par rôle, ce qui permet de réutiliser les commandes d’un playbook et de les partager facilement avec la communauté.

Les rôles

Il est important de bien réfléchir à la séparation des différents rôles que devra comporter notre serveur, afin d’avoir la meilleur modularité possible.

On va prendre l’exemple d’un serveur personnel (vps), qui comporterait un blog (wordpress), un outil d’analyse du trafic web (piwik alias matomo) et tout le monitoring nécessaire à tous cela. On arrive à ce stade à 3 rôles. Mais sachant que le blog et piwik ont tous les deux besoin d’un serveur web et d’une base de données, que le système à besoin d’être mis à jour, que certaines actions seront nécessaires à plusieurs rôles (ex: redémarrer le service httpd après un changement), on arrive à

Rôles de notre serveur:

  • system: concernera la post-installation du système
    • mettre le système à jour
    • Définition des pools ntp (fr)
    • Définition du nom d’hôte
    • Définition basique du firewall: SSH (22)
    • Redirection des mails locaux vers une adresse externe
    • etc…
  • pki: Concernera l’obtention et la mise à jour des certificats SSL
    • Copie de nos clés privées
    • Copies de nos fichiers et scripts gérant la pki (systemd)
  • webserver: Concernera l’installation et la configuration d’apache
    • Installation et configuration d’apache
    • Mise à jour des extensions (ex modsecurity), hors RPM (les paquets RPMs seront mis à jour avec le rôle system)
    • Ajout des ports HTTP (80) et HTTPS (443) au firewall
  • database: Concernera l’installation et la configuration de mariadb
    • Installation et configuration de mariadb
  • blog: Concernera l’installation et la configuration de wordpress
    • Installation et configuration du blog
    • définition de l’alias ou du vhost pour le webserver
    • import de la sauvegarde si elle existe
    • Mise à jour des extensions, hors RPM (les paquets RPMs seront mis à jour avec le rôle system)
  • webstat: Concernera l’installation et la configuration de piwik
    • Installation et configuration de piwik
    • définition de l’alias ou du vhost pour le webserver
    • import de la sauvegarde si elle existe
  • monitoring: Concernera l’installation et la configuration du monitoring
    • Installation et configuration des outils
  • ids: Concernera l’installation et la configuration des outils de détection et de vérification d’intrusion
    • Installation et configuration des outils

On remarque que le changement d’un élément ne perturbe pas (ou peu) les autres rôles. Ainsi le changement de produit n’est pas une tâche trop difficile. Le changement de piwik par awstats sera transparent pour les autres rôles, mais le changement du serveur web ou de la base de données impactera les rôles du blog et de l’outil d’analyse web.

Les playbooks

Un playbook est un fichier texte au format YAML.

Il s’agit dune succession de tâches à accomplir. L’erreur sur l’une d’entre elle entraine l’arrêt du processus (par défaut). Il existe un très grand nombre de modules et la documentation officielle des modules Ansible les explique tous.

L’exemple suivant se sert de deux modules:

  • yum: pour manager les paquets RPMs de la distribution Linux
  • shell: pour lancer des commandes

et effectue les tâches suivantes:

  • Installation du dépôt EPEL
  • Mise à jour de la distribution
  • Installation d’une liste de paquets RPMs
  • Initialisation de etckeeper
  • Premier commit de etckeeper

pour chaque hôte membre du groupe server (fichier hosts du répertoire production ou staging).

Fichier site.yml
--- 
- hosts: server
  tasks:
    - name: Ensure epel repository is set
      yum:
        name: epel-release
        state: latest
      become: true
      
    # System update
    - name: Ensure all pkgs are up-to-date
      yum:
        name: '*'
        state: latest
      become: true
      tags: update

    # Install packages
    - name: Ensure system RPMs are installed and up-to-date
      yum: 
        pkg: "{{ item }}"
        state: latest
      become: true
      with_items:
        - git
        - postfix
        - chrony
        - mlocate
        - screen
        - vim-enhanced
        - yum-utils
        - bzip2
        - unzip
        - bind-utils
        - man-pages
        - net-tools
        - etckeeper
      
    # Manage etckeeper
    - name: Ensure etc is versionned
      shell: "etckeeper init"
      args:
        executable: /bin/bash
        creates: /etc/.git
        chdir: /etc        
      become: true
      
    - name: Ensure first commit is done for etc
      shell: "etckeeper commit 'First commit'"
      args:
        executable: /bin/bash
        creates: /etc/.git/refs/heads/master
        chdir: /etc        
      become: true

Quelques remarques sur les tâches:

  • Toutes les tâches doivent être exécutées par le super-utilisateur, c’est le rôle de l’instruction become: true (root par défaut, mais on peut le spécifier avec become_user)
  • Les tâches d’initialisation d’etckeeper ne doivent pas être exécuter plusieurs fois. On spécifie donc un nom de fichier avec l’instruction creates, qui, s’il existe, ne lancera pas la commande. On se place également dans un répertoire précis avant de lancer la commande avec l’instruction chdir

Le playbook se lance ainsi:

ansible-playbook -i staging --ask-become-pass site.yml
SUDO password:

PLAY [server] ******************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.122.243]

TASK [Ensure epel repository is set] *******************************************
changed: [192.168.122.243]

TASK [Ensure all pkgs are up-to-date] ******************************************
changed: [192.168.122.243]

TASK [Ensure system RPMs are installed and up-to-date] *************************
changed: [192.168.122.243] => (item=[u'git', u'postfix', u'chrony', u'mlocate', u'screen', u'vim-enhanced', u'yum-utils', u'bzip2', u'unzip', u'bind-utils', u'man-pages', u'net-tools', u'etckeeper'])

TASK [Ensure etc is versionned] ************************************************
changed: [192.168.122.243]

TASK [Ensure first commit is done for etc] *************************************
changed: [192.168.122.243]

PLAY RECAP *********************************************************************
192.168.122.243            : ok=6    changed=5    unreachable=0    failed=0

Si on relance le même playbook, les tâches déjà effectuées ne seront pas relancées.

Et c’est une règle d’or des playbooks, ils peuvent être relancé n’importe quand et le résultat sera toujours prévisible (idempotent), c’est à dire qu’il n’y aura pas d’effet de bord, en cas de doublon par exemple.

En effet, dans notre exemple, on ne peut pas initialiser le dépôt git d’etckeeper plusieurs fois et c’est au playbook de gérer cette situation (en posant un fichier et en testant sa présence).

ansible-playbook -i staging --ask-become-pass site.yml
SUDO password:

PLAY [server] ******************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.122.243]

TASK [Ensure epel repository is set] *******************************************
ok: [192.168.122.243]

TASK [Ensure all pkgs are up-to-date] ******************************************
ok: [192.168.122.243]

TASK [Ensure system RPMs are installed and up-to-date] *************************
ok: [192.168.122.243] => (item=[u'git', u'postfix', u'chrony', u'mlocate', u'screen', u'vim-enhanced', u'yum-utils', u'bzip2', u'unzip', u'bind-utils', u'man-pages', u'net-tools', u'etckeeper'])

TASK [Ensure etc is versionned] ************************************************
ok: [192.168.122.243]

TASK [Ensure first commit is done for etc] *************************************
ok: [192.168.122.243]

PLAY RECAP *********************************************************************
192.168.122.243            : ok=6    changed=0    unreachable=0    failed=0

Si l’utilisateur courant n’a pas besoin de mot de passe pour les commandes sudo, on ne sera pas obligé de spécifier l’option –ask-become-pass.

Protéger ses variables en les mettant dans un coffre

Si Ansible s’avère bien pratique pour gérer les hôtes distants, il apparait que certaines pratiques sont dangereuses au niveau de la sécurité. En effet comment affecter un mot de passe à l’utilisateur MySQL root et en même temps commiter les fichiers Ansibles sur un dépôt public distant, sans compromettre la sécurité. Ansible a résolu le problème avec vault (coffre).

Ansible-vault encode les données avec l’algorithme AES256

Il y a deux manière de l’utiliser:

  • Chiffrer la totalité du fichier de variables et utiliser les commandes ad-hoc pour éditer le fichier
  • Chiffrer seulement la valeur de la variable dans un fichier texte en clair

Prenons comme exemple un fichier de variable host_vars/server qui contiendrait l’entrée suivante:

mysql_root_password = "monsupermotdepasse"

Méthode 1: Encrypter le fichier

On créé notre fichier chiffré et on inscrit notre variable une fois le fichier ouvert par l’éditeur par défaut (variable EDITOR) avec la commande

ansible-vault create host_vars/server
New Vault password: 
Confirm New Vault password:

On pourra éditer notre fichier chiffré, avec l’éditeur par défaut

ansible-vault edit host_vars/server

Alternativement, on peut créer un fichier en clair et le chiffrer par la suite

echo 'mysql_root_password = "monsupermotdepasse"' > host_vars/server
ansible-vault encrypt host_vars/server

Ce qui donnera le fichier suivant

Fichier host_vars/server
$ANSIBLE_VAULT;1.1;AES256
61346330393736306566356131633264323234353664653034646239326439633261643630393162
6633646264326134373230343935636361353033313262630a356566336261623261343233666135
33333635353331663434366137653934633238633561346463626130633063663636373330663031
3738363239663635380a326532386133343732306535393466393433306434323265366336323037
39356531316631366161376161633166336562366330313038343961626261333237356237333062
3830373337656561653364313064353866306132653864623834

Méthode 2: Chiffrer la valeur de la variable

On chiffre la valeur avec la commande idoine

ansible-vault encrypt_string --vault-id @prompt 'monsupermotdepasse' --name 'mysql_root_password' >> host_vars/server
New vault password (default): 
Confirm vew vault password (default): 
Encryption successful

Ce qui donnera le fichier suivant

Fichier host_vars/server
mysql_root_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          65343663373236316433393833616164353236303666663437613438306630336135353238326137
          3566333939353435613539613066373463323231656635320a666633366637343738653634396137
          33316530396338316638303765636165363132363934376234316430633432613632663439326661
          3530613339653038640a396436373763363332313336623061313834353238613766393662396533
          30653531653265623165333037396539396632393535636166646538646638373261

Et maintenant ?

Les bases sont posées et un exemple concret sera bientôt disponible dans la deuxième partie: Premier playbook avec les rôles.

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

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…

Un petit point sur les options SSL et les en-têtes HTTP

Apache Logo

Options générales

  • ServerTokens définit ce qu’apache peut diffuser comme information à propos de lui-même. Le fanfaronnage est incompatible avec la sécurité.
    • Prod: c’est la valeur la plus sécurisée et le serveur n’enverra que son nom:
      Server: Apache
    • Major: le serveur n’enverra que son nom et son numéro de version majeur:
      Server: Apache/2
    • Minor: le serveur n’enverra que son nom et son numéro de version complet:
      Server: Apache/2.4.25
    • Os: le serveur n’enverra que son nom, son numéro de version complet et le nom du système d’exploitation:
      Server: Apache/2.4.25 (Fedora)
    • Full: le serveur enverra son nom, son numéro de version, le nom du système d’exploitation et la liste des modules actifs avec leur numéro de version:
      Server: Apache/2.4.25 (Fedora) OpenSSL/1.0.2k-fips mod_auth_kerb/5.4 mod_wsgi/4.4.23 Python/2.7.13 PHP/7.0.18 mod_perl/2.0.10 Perl/v5.24.1
  • ServerSignature définit la signature (pied de page) apposée par apache sur les page générées par lui-même (typiquement les pages d’erreurs)
    • Off: aucune signature sur les page générées
    • On: signature présente avec les mêmes informations défini par la directive ServerTokens, le domaine et le port
      <address>Apache/2.4.25 (Fedora) OpenSSL/1.0.2k-fips mod_auth_kerb/5.4 mod_wsgi/4.4.23 Python/2.7.13 PHP/7.0.18 mod_perl/2.0.10 Perl/v5.24.1 Server at www.tartarefr.eu Port 80</address>
    • Email: signature présente avec les mêmes informations défini par la directive ServerTokens, le domaine, le port et l’email de l’administrateur du domaine
      <address>Apache/2.4.25 (Fedora) OpenSSL/1.0.2k-fips mod_auth_kerb/5.4 mod_wsgi/4.4.23 Python/2.7.13 PHP/7.0.18 mod_perl/2.0.10 Perl/v5.24.1 Server at <a href="mailto:fake@tartarefr.eu">www.tartarefr.eu</a> Port 80</address>
      
  • TraceEnable définit si la méthode HTTP Trace est autorisée. Cette méthode sert surtout pour des tests ou des diagnostiques et n’a pas sa place sur un serveur en production. Elle peut prendre deux valeurs: On (la méthode est permise) ou Off (méthode désactivée)

Options SSL

Hormis les directives habituelles (SSLEngine, SSLCertificateFile, SSLCertificateKeyFile, SSLCertificateChainFile) pour le paramétrage SSL, il y en a quelques unes qui méritent une petite explication.

  • SSLCipherSuite définit la liste des ciphers autorisés. Actuellement, pour obtenir un A+ sur SSLlabs, il faut désactiver certains ciphers medium ou high.
    HIGH:MEDIUM:!aNULL:!eNULL:!MD5:!RC4:!EXP:!3DES:!LOW:!SEED:!IDEA:!CBC
  • SSLHonorCipherOrder définit si l’ordre des ciphers de la directive SSLCipherSuite doit être suivi. Il est recommandé de suivre l’ordre défini en mettant la valeur On. Typiquement ici, le serveur essaiera d’abord tous les ciphers HIGH avant d’essayer les MEDIUM.
  • SSLProtocol définit les protocoles autorisés: ici on accepte tous les protocoles sauf SSL version 2 et version 3. On peut commencer à envisager d’exclure aussi TLSv1 (TLSv1.0)
    SSLProtocol all -SSLv2 -SSLv3
  • SSLCompression active ou désactive la compression sur SSL. Comme la faille CRIME exploite une faille de la compression, on désactive cette fonctionnalité en mettant le paramètre à off.

En-têtes HTTP concernant la sécurité

  • Set-Cookie permet de sécuriser les cookies en ajoutant deux paramètres qui rendent les cookies non accessibles aux scripts du client (HttpOnly) et ils ne sont transmis que sur une connexion sécurisée (Secure), c’est à dire en HTTPS.
    Header always edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
  • X-Frame-Options autorise le navigateur à encapsuler ou non la page web dans une frame. En empêchant la page d’être encapsulée par un site externe, on se protège du clickjacking.
    Il y a 3 valeurs possibles:

    • DENY: aucune encapsulation possible
    • SAMEORIGIN: encapsulation uniquement en provenance du même domaine et du même protocole (HTTP ou HTTPS)
    • ALLOW-FROM: encapsulation uniquement en provenance de l’URI passée en argument
  • X-XSS-Protection active les filtres cross-site scripting embarqués dans la plupart des navigateurs. La meilleure configuration est d’activer la protection des navigateurs: “X-XSS-Protection: 1; mode=block“.
  • X-Content-Type-Options désactive la détection automatique du type MIME par le navigateur et force celui-ci à utiliser uniquement le type déclaré avec Content-Type. La seule valeur valide est nosniff.
  • Referrer-Policy définit la politique d’envoi d’information de navigation dans l’en-tête Referer
    • no-referrer: L’en-tête sera absente de la réponse à la requête.
    • no-referrer-when-downgrade: L’en-tête sera absente s’il y a une diminution de la sécurité (HTTPS->HTTP). Sinon elle sera envoyée.
    • same-origin: L’en-tête sera présente que si le domaine de destination est identique à celui d’origine.
    • origin: L’en-tête sera présente mais ne comprendra que le domaine de la page d’origine.
    • strict-origin: L’en-tête sera absente s’il y a une diminution de la sécurité (HTTPS->HTTP). Sinon elle ne comprendra que le domaine de la page d’origine.
    • origin-when-cross-origin: L’en-tête sera présente et l’URI sera complète si le domaine de destination est identique à celui d’origine, mais ne comprendra que le domaine de la page d’origine si le domaine de destination diffère de celui d’origine.
    • strict-origin-when-cross-origin: L’en-tête sera absente s’il y a une diminution de la sécurité (HTTPS->HTTP). Sinon elle sera complète si le domaine de destination est identique à celui d’origine, mais ne comprendra que le domaine de la page d’origine si le domaine de destination diffère de celui d’origine.
    • unsafe-url: L’en-tête sera présente et l’URI sera complète
  • Content-Security-Policy regroupe les sources autorisées à être incluses dans la page web. En listant uniquement les sources nécessaires, on empêche le téléchargement de sources malicieuses par le navigateur. Le mot clé self représente le domaine appelé.
    • default-src : Définit les sources autorisées par défaut de tous les types de ressources
    • script-src : Définit les sources autorisées pour les scripts
    • object-src : Définit les sources autorisées pour les objets
    • style-src : Définit les sources autorisées pour les feuilles de styles
    • img-src : Définit les sources autorisées pour les images
    • media-src : Définit les sources autorisées pour les médias (vidéo et audio)
    • frame-src : Définit les sources autorisées pour les frames
    • font-src : Définit les sources autorisées pour les polices de caractères
    • connect-src : Définit les sources autorisées à être chargée par les scripts
    • form-action : Définit les sources autorisées pour l’action d’un formulaire
    • plugin-types : Définit les sources autorisées pour les plugins
    • script-nonce : Définit les sources autorisées pour les scripts ayant le même argument nonce
    • sandbox : Définit la politique de bac-à-sable
    • reflected-xss : Active ou désactive les filtres des navigateurs pour la protection XSS
    • report-uri : Définit une URI vers laquelle envoyer un rapport en cas de violation de politique
  • HTTP-Strict-Transport-Security force le navigateur à modifier tous les liens non sécurisés par des liens sécurisés (HTTP->HTTPS) durant le temps indiqué par le paramètre max-age. Celui-ci doit donc être configuré pour être supérieur à la durée de navigation. La page ne sera pas affichée si le certificat SSL n’est pas valide.
  • Public-Key-Pins protège contre les attaques man-in-the-middle avec des certificats X.509 volés (mais valides). En spécifiant l’empreinte des certificats du site au navigateur, celui-ci ne fera pas
    confiance aux autres certificats valides non listés pour le site.
  • Expect-CT annonce le statut du site en rapport aux futurs pré-requis de Chrome. L’en-tête comporte le mot enforce s’il est prêt. Mais dans un premier temps il vaut mieux le mettre en test avec la directive max-age=0 et éventuellement le paramètre report-uri

Vérification

Exemple de configuration Apache

On peut mettre la définition de ces en-têtes dans le fichier /etc/httpd/conf.d/common.conf (On le créé s’il n’existe pas), il sera inclut par la directive IncludeOptional conf.d/*.conf

ServerTokens    Prod
ServerSignature Off

# Disable Trace
# Otherwise host is vulnerable to XST
TraceEnable Off

# Secure cookie with HttpOnly
Header always edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure

Header always set X-Frame-Options SAMEORIGIN
Header always set X-XSS-Protection: "1;mode=block"
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy same-origin
Header always set Content-Security-Policy "default-src 'self' ; script-src 'self' report.tartarefr.eu https://s.w.org ; style-src 'self' fonts.googleapis.com fonts.gstatic.com ; report-uri https://report.tartarefr.eu/"
Header always set Strict-Transport-Security "max-age=31536000;includeSubDomains"
Header always set Expect-CT 'max-age=0; report-uri="https://report.tartarefr.eu/"'
Header always set Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="1B/6/luv+TW+JQWmX4Qb8mcm4uFrNUwgNzmiCcDDpyY=";max-age=2592000;includeSubdomains; report-uri="https://report.tartarefr.eu/"'

Partage NFS

Pour partager facilement des dossiers entre plusieurs ordinateurs *nix, on peut se servir de NFS. Il est vraiment très triviale à mettre en place

yum install nfs-utils

d’exporter les dossiers dans le fichier /etc/exports

/data/save 192.168.0.x(rw,sync,no_root_squash)

et de démarrer le server NFS

systemctl start nfs-server
systemctl enable nfs-server

Mais c’est surtout la partie configuration du firewall qui n’est pas précisement détaillée.
Celle-ci est grandement simplifiée avec firewalld car elle se résume à

firewall-cmd --permanent --add-service mountd
firewall-cmd --permanent --add-service rpc-bind
firewall-cmd --permanent --add-service nfs
firewall-cmd --reload

On peut maintenant lister les services autorisés par le firewall

firewall-cmd --list-all
FedoraServer (default, active)
interfaces: eth0
sources:
services: mountd nfs rpc-bind ssh
ports:
protocols:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:

Cette simplicité est rendue possible grâce aux fichiers situés dans /usr/lib/firewalld/services/:

  • Fichier nfs.xml
    <?xml version="1.0" encoding="utf-8"?>
    <service>
    <short>NFS4</short>
    <description>The NFS4 protocol is used to share files via TCP networking. You will need to have the NFS tools installed and properly configure your NFS server for this option to be useful.</description>
    <port protocol="tcp" port="2049"/>
    </service>
  • Fichier mountd.xml
    <?xml version="1.0" encoding="utf-8"?>
    <service>
    <short>mountd</short>
    <description>NFS Mount Lock Daemon</description>
    <port protocol="tcp" port="20048"/>
    <port protocol="udp" port="20048"/>
    </service>
  • Fichier rpc-bind.xml
    <?xml version="1.0" encoding="utf-8"?>
    <service>
    <short>rpc-bind</short>
    <description>Remote Procedure Call Bind</description>
    <port protocol="tcp" port="111"/>
    <port protocol="udp" port="111"/>
    </service>