# **IMPLEMENTATION D'UN ENVIRONNEMENT DE TRAVAIL POUR EPSILON CORPORATION** > Xavier RACINE > Bilal AGHARBI > Pierrick HERVE > Benoit DEJONCHEERE > CAP GEMINI > POEI Ingénieur système Scripting ## Sommaire 1. [Contexte](#I-Contexte) 1. [Cahier des charges](#1-Cahier-des-charges) 2. [Spécifications](#2-Spécifications) 2. [Implémentation](#II-Implémentation) 1. [Bonnes pratiques](#1-Bonnes-pratiques) 2. [Provisioning](#2-Provisioning) i. [Identifier le VPC à utiliser](#i-Identifier-le-VPC-à-utiliser) ii. [Groupes de sécurité](#ii-Groupes-de-sécurité) iii. [Création de clé ssh](#iii-Création-de-clé-ssh) iv. [Le vif du sujet](#iv-Le-vif-du-sujet) v. [Ajout d’un volume de stockage pour la machine sauvegardes](#v-Ajout-d’un-volume-de-stockage-pour-la-machine-sauvegardes) vi. [Modification du fichier hosts d'ansible](#vi-Modification-du-fichier-hosts-d'ansible) 3. [Configuration globale machine ansible](#3-Configuration-globale-machine-ansible) i. [Durcissement ssh](#i-Durcissement-ssh) ii. [Installation Docker](#ii-Installation-Docker) iii. [Exploitation Gitlab](#iii-Exploitation-Gitlab) 4. [Configuration machine frontend](#4-Configuration-machine-frontend) i. [Configuration de ProFTP](#i-Configuration-de-ProFTP) ii. [Configuration du VPN](#ii-Configuration-du-VPN) 5. [Configuration machine applications](#5-Configuration-machine-applications) i. [Pipeline jenkins](#i-Pipeline-jenkins) ii. [Automatisation de Jekyll: deuxième tentative](#ii-Automatisation-de-Jekyll:-deuxième-tentative) 6. [Configuration machine sauvegarde](#6-Configuration-machine-sauvegarde) i. [Configuration NFS](#i-) 3. [Implémentations des fonctionnalités en dehors de l'architecture machine](#III-Implémentations-des-fonctionnalités-en-dehors-de-l-architecture-machine) 1. [Traduction de la documentation en Latex](#1-Traduction-de-la-documentation-en-Latex) --- ## I. Contexte ### 1. Cahier des charges Vous trouverez ci-dessous le cahier des charges du projet final. ![](https://i.imgur.com/wlpBI8H.jpg) ![](https://i.imgur.com/bJNWtUk.png) ### 2. Spécifications Au vu du cahier des charges très fourni nous avons commencé par une étape de brainstorming individuel puis de brainstorming collectif pour construire ensemble une architecture logicielle qui réponde aux besoins de l'entreprise Epsilon Corporation. Nous avons abouti à l'architecture suivante. La machine ansible servira de machine cliente. ![](https://i.imgur.com/kOCbdLE.png) Nous avons choisi les technologies suivantes * ansible pour le déploiement automatisé pour sa versatilité (de très nombreux modules) * docker pour isoler les applications pour sa puissance et sa simplicité * jenkins pour la publication automatique des nouvelles versions des applications * openvpn pour sa robustesse et le fait qu'il soit libre de droit * RAID 5 pour la résistance aux pannes * apache2 comme serveur web pour sa simplicité et sa praticité d'utilisation * le projet communautaire "Witiko" pour traduire la documentation markdown en Latex * proftpd pour ... Puis nous avons réparti les tâches à réaliser sous la forme de différents rôles ansible. Vous trouverez une version non exhaustive de cette répartition ci-dessous. Rôle ansible * ansible * node manager qui éxecutera le méga script * aws * créer le groupe de sécurité * configurer le parfeu/groupe de sécurité * création de machines * ip fixe * docker * installer docker * 1 role pour chaque sujet, réseau interne * ssh * nfs * ftp * firewall * pipeline jenkins, manuel, écrire doc pour reproduire le build/run/test pour la machine applications pour les trois sites web * 1 par machine pour faire les installations spécifiques * pour les 3 * installer les logiciels * création users et projects gitlab * installer un conteneur gitlab et cloner le projet sur un volume persistant * configuration des machines (nom de l'utilisateur, etc) * frontend * installation et configuration vpn * pas de conteneur pour jekyll mais plutôt le gitlab-ci.yml * applications * sauvegarde * gestion haute dispo --- ## II. Implémentation ### 1. Bonnes pratiques L'implémentation étant principalement basée sur la rédaction de playbook ansible nous avons pu facilement travailler en parallèle. Vous trouverez ci-dessous les bonnes pratiques de travail qui ont été respectée durant l'implémentation des spécifications. Travail en équipe * interagir fréquemment pour favoriser l'entraide, le partage de connaissance et résoudre les points bloquants (sous la forme d'un point quotidien au minimum) Praticité/Outils * développer les scripts sur vs-code puis push en scp sur les machines virtuelles cibles (pour avoir accès aux fonctionnalités de développement avancés notamment le copier-coller, l'autocompletion et la pagination syntaxique) Viabilité/Fonctionnement * au fur et à mesure de l'implémentation des spécifications, des tests seront réalisés pour s'assurer du bon fonctionnement (sous forme de commandes isolées ou de scripts) Sauvegardes * faire des clones et des snapshots des machines virtuelles après les étapes clés d'installation et de configuration Documentation * remplir la documentation au fur et à mesure ### 2. Provisioning Dans cette étape, nous nous occupons de la création des trois VM frontent, applications et sauvegardes. #### i. Identifier le VPC à utiliser On commence par identifier le VPC de la région par défaut: ```yaml - name: info VPC ec2_vpc_net_info: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ region }}" filters: "isDefault": "true" register: default_vpc ``` Dans ce VPC par défaut, nous choisissons un subnet dans lequel seront nos trois machines: ```yaml - name: subnets du VPC defaut ec2_vpc_subnet_info: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ region }}" filters: vpc-id: "{{ default_vpc['vpcs'][0]['vpc_id'] }}" register: subnet_info - name: pick d'un subnet random set_fact: default_vpc_id: "{{ default_vpc['vpcs'][0]['vpc_id'] }}" random_subnet: "{{ subnet_info.subnets|map(attribute='id')|list|random }}" ``` #### ii. Groupes de sécurité Dans cette section, le but est de créer les groupes de sécurité associés à chaque machine. L'idée est d'ouvrir les ports correspondant à nos différentes applications. Le port 22, quand à lui, est ouvert uniquement pour la machine ansible. ```yaml - name: info ip publique ipify_facts: - name: groupe de securite frontend ec2_group: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" name: groupe_gr2_frontend description: frontend rules vpc_id: "{{ default_vpc_id }}" region: "{{ region }}" state: present rules: - proto: tcp ports: - 80 - 21 cidr_ip: 0.0.0.0/0 rule_desc: autoriser tout ports 21 et 80 - proto: tcp ports: - 22 cidr: "{{ ipify_public_ip }}/32" rule_desc: autoriser connexion SSH depuis machine hote register: groupe_frontend - name: groupe de securite applications ec2_group: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" name: groupe_gr2_applications description: applications rules vpc_id: "{{ default_vpc_id }}" region: "{{ region }}" state: present rules: - proto: tcp ports: - 22 cidr_ip: "{{ ipify_public_ip }}/32" rule_desc: autoriser connexion SSH depuis machine hote - proto: tcp ports: - 4000 #jekyll - 9080 #jenkins, /!\ ce n'est pas le port par défaut /!\ - 8080 #tomcat - 8081 #php-myadmin - 3306 #php-mysql - 8888 #python cidr_ip: 0.0.0.0/0 rule_desc: restriction des ports applicatifs au subnet register: groupe_applications - name: groupe de securite sauvegardes ec2_group: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" name: groupe_gr2_sauvegardes description: sauvegardes rules vpc_id: "{{ default_vpc_id }}" region: "{{ region }}" state: present rules: - proto: tcp ports: - 22 cidr_ip: "{{ ipify_public_ip }}/32" rule_desc: autoriser connexion SSH depuis machine hote register: groupe_sauvegardes ``` Cette partie reste à débugger, et n'a pas été implémentée. #### iii. Création de clé ssh Avant de s'occuper des VM, il reste à créer une clé SSH qui sera commune aux trois machines. ```yaml= - name: suppression ancienne cle ssh ec2_key: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ region }}" name: ssh_pair state: absent - name: creation cle ssh ec2_key: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ region }}" name: ssh_pair state: present register: ec2_pair - name: sauvegarde cle privee copy: content="{{ ec2_pair.key.private_key }}" dest="/home/centos/.aws/ssh_pair.pem" mode=0400 ``` #### iv. Le vif du sujet Maintenant, tout est prêt pour créer les instances EC2. ```yaml= vars: ec2_list: #ignorer l'item "ip", fixer l'ip privée n'a pas été implémenté - { name: 'gr2_frontend', ip: '10.10.10.8' } - { name: 'gr2_applications', ip: '10.10.10.9' } - { name: 'gr2_sauvegardes', ip: '10.10.10.10' } image_id: ami-02334c45dd95ca1fc #image de CentOS 7 - name: creation instances ec2_instance: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" name: "{{ item.name }}" key_name: "ssh_pair" vpc_subnet_id: "{{ random_subnet }}" instance_type: t3.xlarge security_group: default region: "{{ region }}" network: assign_public_ip: yes image_id: "{{ image_id }}" state: present loop: "{{ ec2_list }}" register: device_list ``` Remarque: avec des groupes de sécurité fonctionnels, on pourrait remplacer `security_group: default` par `security_group: "{{ 'groupe_' + item.name }}"` On enregistre dans `device_list` toutes les données renvoyées par `ec2_instance` pour ne pas perdre les informations sur les machines que l'on vient de créer. ```yaml - name: sauvegarde public DNS - ID instance - IP privee lineinfile: path: /home/centos/.aws/public_dns_instance line: "{{ item.item.name }}:{{ item.instances[0].public_dns_name }}:{{ item.instances[0].instance_id }}:{{ item.instances[0].private_ip_address }}" insertafter: EOF state: present create: yes with_items: "{{ device_list.results }}" ``` On enregistre les données importantes des machines créées dans un fichier au format suivant: `"nom":"dns public":"id instance":"ip privée"` Ce format facilite la récupération et l'utilisation de ces informations, comme par exemple pour la modification automatisée du fichier hosts d'ansible. #### v. Ajout d'un volume de stockage pour la machine sauvegardes Comme la machine sauvegardes a vocation a accueillir de grandes quantités de données, on lui rajoute un volume de stockage. ```yaml= - name: isolement id sauvegardes shell: cat /home/centos/.aws/public_dns_instance | grep sauvegardes | cut -d":" -f3 register: sauvegarde - name: ajout volume sauvegardes ec2_vol: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" instance: "{{ sauvegarde.stdout }}" volume_size: 16 #taille en Go state: present region: "{{ region }}" zone: "{{ random_subnet }}" ``` #### vi. Modification du fichier hosts d'ansible Comme mentionné plus haut, on modifie le fichier hosts de ansible, pour pouvoir éxecuter les autres playbooks ciblant directement ces machines en toute sérénité. ```yaml= - name: modifications ansible hosts shell: ansible_hosts.sh args: executable: /bin/bash ``` ```bash= #!/bin/bash hosts_file="/home/centos/projet_final/hosts" info_file="/home/centos/.aws/public_dns_instance" while read line do echo -e "[$(echo "$line" | cut -d: -f1)]" >> $hosts_file echo -e "$(echo "$line" | cut -d: -f2) ansible_ssh_private_key_file=/home/centos/.aws/ssh_pair.pem" >> $hosts_file done < $info_file ``` En suivant le format du fichier `/home/centos/.aws/public_dns_instance`, on rajoute donc des blocs de la forme ``` [nom de machine] dns-public ansible_ssh_private_key_file=/home/centos/.aws/ssh_pair.pem ``` ### 3. Configuration globale machine ansible Les trois machines devant être accessible en ssh, nous avons choisi de durcir le ssh pour renforcer la sécurité. #### i. Durcissement ssh Pour cela nous avons écrit le playbook ansible suivant, qui se charge d'installer openssh et de modifier certaines lignes du fichier `/etc/ssh/sshd_config` en prenant en compte le fait que les lignes peuvent commencer par un # ou non. Nous pourrions [aller plus](https://www.digitalocean.com/community/tutorials/how-to-harden-openssh-on-ubuntu-18-04-fr) loin en restreignant aussi les autres méthodes d'authentification possible, interdire aux utilisateurs de passer des variables d'environnement personnalisées, désactiver la bannière de connexion, modifier les options de tunneling et de redirections, etc. ```yaml --- - hosts: all become: yes vars: tasks: # Installation de openssh - name: installation de openssh package: name: openssh state: latest # Durcissement de l'accès en ssh des serveurs ... # ... en modifiant les lignes du fichier /etc/ssh/sshd_config - name: changement du port par défaut lineinfile: path: /etc/ssh/sshd_config regexp: '^(#?)Port' line: Port 2222 - name: interdiction de la connexion par mot de passe lineinfile: path: /etc/ssh/sshd_config regexp: '^(#?)PasswordAuthentication' line: PasswordAuthentication no - name: authorisation de la connexion par clé lineinfile: path: /etc/ssh/sshd_config regexp: '^(#?)PubkeyAuthentication' line: PubkeyAuthentication yes - name: interdiction de la connexion en root lineinfile: path: /etc/ssh/sshd_config regexp: '^(#?)PermitRootLogin' line: PermitRootLogin no - name: maximum trois essais d'affilés pour se connecter lineinfile: path: /etc/ssh/sshd_config regexp: '^(#?)MaxAuthTries' line: MaxAuthTries 3 - name: maximum cinq sessions en parallèle lineinfile: path: /etc/ssh/sshd_config regexp: '^(#?)MaxSessions' line: MaxSessions 10 ... ``` #### ii. Installation Python3.10 Nous avons automatisé l'installation de Python3 afin d'éviter les effets de bord, dues notamment à l'utilisation de Docker. Voici le playbook: ```yaml= --- - name: yum groupinstall yum: name: "@Development Tools" state: latest - name: Installation de packages divers yum: name: - openssl-devel - libffi-devel - bzip2-devel state: latest - name: importer la compression python3.10.tar.gz get_url: url: https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tgz dest: /tmp mode: '0440' - name: decompresser l'archive python unarchive: src: /tmp/Python-3.10.0.tgz dest: /tmp remote_src: yes - name: commande ./configure --enable-optimizations shell: ./configure --enable-optimizations args: chdir: /tmp/Python-3.10.0 - name: commande make altinstall shell: make altinstall args: chdir: /tmp/Python-3.10.0 - name: Installation boto #Nécessaire pourle provisioning pip: name: "{{ item }}" with_items: - boto - boto3 - botocore ... ``` #### iii. Installation Docker Nous avons choisi d'automatiser l'installation de [docker](https://docs.docker.com/engine/install/centos/) sur les machines du réseau, en écrivant le playbook suivant. Nous prenons garde à bien supprimer les installations pré-existantes qui pourrait rentrer en conflit avec la version "latest" que l'on installe. On a récupéré la "baseurl" du dépôt du fichier "/etc/yum.repos.d/docker-ce.repo" présent à l'installation de docker sur une autre machine. ```yaml --- - hosts: localhost become: yes vars: paquets_desinstall_docker: - docker - docker-engine - docker.io - containerd - runc paquets: - git - ca-certificates - wget - gnupg2 - curl - yum-utils - device-mapper-persistent-data - lvm2 - python3 paquets_install_docker: - docker - docker-client - containerd - python2-pip tasks: # Desinstallation des anciennes versions de docker - name: desinstallation des anciennes versions de docker package: name="{{ paquets_desinstall_docker }}" state=absent update_cache=yes # Installation de docker avec un depot # - gestion des paquets - name: mise a jour du cache package: name="*" state=latest update_cache=yes - name: installation des paquets package: name={{ paquets }} state=latest update_cache=yes # - gestion du depot - name: ajout de la cle rpm_key: key=https://download.docker.com/linux/centos/gpg state=present - name: ajout du depot yum_repository: name: docker description: docker repo baseurl: baseurl=https://download.docker.com/linux/centos/$releasever/$basearch/stable state: present - name: mise a jour du cache package: name="*" state=latest update_cache=yes # - installation de docker - name: installation de Docker package: name={{ paquets_install_docker }} state=latest update_cache=yes - name: Start Docker service service: name=docker state=started enabled=yes ... ``` On execute le playbook avec la commande suivante pour forcer l'utilisation du python 3 au cas où plusieurs versions de python soient installées. `ansible-playbook -i /etc/ansible/hosts gestion_gitlab.yml --private-key=~/POEIgroupe2.pem -e 'ansible_python_interpreter=/usr/bin/python3'` Apreès l'execution avec succès du playbook on vérifie le bon fonctionnement de docker avec la commande `sudo docker run hello-world` #### iv. Exploitation Gitlab Nous avons dans un premier temps envisager d'automatiser la création des groupes, utilisateurs et projets gitlab à l'aide de ansible. Néanmoins nous avons rapidement réalisé la non faisabilité de cette entreprise. Du fait d'[erreurs 403](https://gitlab.com/gitlab-org/gitlab/-/issues/244345) qui seront résolues dans une prochaine version de gitlab et qui empêche de créer des groupes ou des utilisateurs via les modules gitlab de ansible. Nous avons donc choisi de réaliser manuellement la partie de création du groupe, des projets et des utilisateurs gitlab avec l'aide de la GUI web. Et réaliser seulement la partie exploitation avec ansible. ```yaml --- - hosts: sauvegarde become: yes vars: gitlab_host: "https://gitlab.com" gitlab_token: "glpat-9xxraELonvEDvXSrge7P" gitlab_api_url: "{{ gitlab_host }}/api/v4" tasks: # Installation du prérequis python (les versions antérieures ou égal à la 1.12.1 sont les seules compatibles) - name: Install python-gitlab pip: name: python-gitlab==1.12.1 # Creation du groupe gitlab qui contiendra les 3 projets - name: supprimer l'ancien groupe gitlab gitlab_group: api_url: "{{ gitlab_host }}" api_token: "glpat-9xxraELonvEDvXSrge7P" validate_certs: True name: POEI_groupe_2 state: absent - name: "Create GitLab Group" gitlab_group: api_url: "{{ gitlab_host }}" api_token: "glpat-9xxraELonvEDvXSrge7P" validate_certs: True name: POEI_groupe_2 state: present # Mise en fonction gitlab : machine "sauvegarde" # - creation des utilisateurs gitlab - name: Supprimer l'ancien utilisateur gitlab_user: api_url: "{{ gitlab_host }}" api_token: "glpat-9xxraELonvEDvXSrge7P" username: "gitlab_sauvegarde" validate_certs: True name: "rien" password: "rien" email: "gitlab_sauvegarde@gmail.com" state: absent # - generation d'une paire de cle ssh - name: generation d'une paire de cle ssh, de longueur 4096 bits et avec rsa openssh_keypair: path: /home/centos/.id_rsa_keys_gitlab # mettre un deploy_key pour associer la cle privee au projet et faire le lien avec l'utilisateur - name: creation de l'utilisateur gitlab gitlab_sauvegarde gitlab_user: api_url: "{{ gitlab_host }}" api_token: "glpat-9xxraELonvEDvXSrge7P" username: "gitlab_sauvegarde" validate_certs: True name: "none" password: "none" email: "gitlab_sauvegarde@gmail.com" state: present sshkey_name: .id_rsa_keys_gitlab.pub sshkey_file: #group: POEI groupe 2 access_level: owner # - creation des projets - name: suppression de l'ancien projet gitlab_project: api_url: "{{ gitlab_host }}" api_token: "glpat-9xxraELonvEDvXSrge7P" validate_certs: True name: sauvegarde state: absent - name: Create GitLab Project in group Ansible gitlab_project: api_url: "{{ gitlab_host }}" api_token: "glpat-9xxraELonvEDvXSrge7P" validate_certs: True name: data #group: POEI_groupe_2 import_url: "https://gitlab.com/p3061/data.git" state: present # Mise en fonction gitlab : machine "applications" # - creation des utilisateurs gitlab # - creation du groupe # - creation des projets ... ``` Le gitlab token a lui aussi été généré par avec la GUI web. Il nous sert à nous authentifier auprès de l'API gitlab qui ne permet plus d'utiliser username et password pour se connecter depuis la version 10.2. Vous trouverez ci-dessous la partie exploitation de gitlab traitée à l'aide de commandes bash (lancement conteneur gitlab, clone des projets). Nous aurions pu choisir deux autres solutions pour l'implémentation : * utiliser un Dockerfile avec ansible(https://hub.docker.com/r/gitlab/gitlab-ce/dockerfile/) * utiliser un docker-compose.yml avec ansible (https://docs.gitlab.com/ee/install/docker.html) Nous avons délaissé les solutions automatisées au profit d'une solution manuelle étant donné le temps imparti. Vous trouverez ci-dessous la manipulation que nous avons réalisée pour mettre en place le projet gitlab "data" sur la machine "sauvegarde" (la manipulation étant identique sur la machine "applications" avec les projets gitlab "web" et "ansible"). Nous avons utilisé l'image docker [gitlab/gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce). Le fait que les projets gitlab soient privées nécessite de s'authentifier pour les cloner, bien que les manipulations soient plus longues l'on a choisi de laisser les projets en privée. ```bash # On lance le conteneur gitlab/gitlab-ce export GITLAB_HOME=$HOME/gitlab sudo docker run --detach \ --publish 443:443 --publish 80:80 \ --name gitlab \ --restart always \ --volume $GITLAB_HOME/config:/etc/gitlab:Z \ --volume $GITLAB_HOME/logs:/var/log/gitlab:Z \ --volume $GITLAB_HOME/data:/var/opt/gitlab:Z \ --volume $GITLAB_HOME/project:/home/:Z \ gitlab/gitlab-ee:latest # On clone le projet "data" sudo ssh-keygen -t rsa -C "pierrick.herve56@gmail.com" -b 4096 # en précisant comme chemin : /home/centos/gitlab/.ssh/id_rsa # A l'aide de la GUI web de gitlab on enregistre la clé publique dans les paramètres de l'utilisateur # on clone le projet en dehors du conteneur dans un des volumes cd /home/centos/gitlab/project sudo git clone https://PierrickHERVE@gitlab.com/p3061/data.git # en renseignant le mot de passe # On aurait aussi pu cloner le projet en envoyant la commande au conteneur sudo docker exec -ti gitlab "git clone https://PierrickHERVE@gitlab.com/p3061/data.git" # On vérifie nos commandes en observant le contenu des volumes persistants créés sudo ls /home/centos/gitlab/config sudo ls /home/centos/gitlab/logs sudo ls /home/centos/gitlab/data # PS : si l'on souhaite supprimer le conteneur déjà existant pour repartir de zéro sudo docker stop gitlab \ && sudo docker rm gitlab \ && sudo docker system prune -y ``` ### 4. Configuration machine frontend #### i. Configuration du serveur ProFTP Nous avons configuré le serveur FTP avec deux playbooks ansible, l'un pour l'installation et le démarrage des services, l'autre pour sa configuration. L'arborescence est décrite ci-dessous: ![](https://i.imgur.com/aGkbXdr.png) Voici les playbooks utilisés: ```yaml= --- # main.yml - vars_files: - noms_users_file.yml tasks: - name: Ajouter le dépot epel-release yum: name: epel-release state: latest - name: Installer proftpd yum: name: - proftpd - proftpd-utils state: latest - name: démarrer le service proftpd service: name: proftpd enabled: yes - name: Autoriser le service proftpd firewalld: service: proftpd permanent: yes state: enabled notify: redemarrer firewalld - include: conf_ftp.yml handlers: - name: redemarrer firewalld service: name: firewalld state: restarted ... ``` Puis: ```yaml= --- # conf_ftp.yml - name: ajouter le shell /bin/false dans /etc/shells lineinfile: dest: /etc/shells line: '/bin/false' - name: créer les utilisateurs FTP user: name: groupe2 state: present uid: 2222 shell: /bin/false - name: copie du fichier de configuration proftpd command: cp /etc/proftpd.conf /etc/proftpd_backup.conf - name: configurer le nouveau port dans le fichier de configuration lineinfile: path: /etc/proftpd.conf line: 'Port 5000' ... ``` #### ii. Configuration du VPN Nous avons décidé d'utiliser OpenVPN. Sa configuration se fait en plusieurs parties, avec notamment la création de clés et de certificats. Nous avons automatisé la configuration via Ansible, à travers plusieurs playbooks, qui seront appelés à la fin par le fichier `tasks/main.yml`. Seront présentés respectivement ci-dessous les playbooks : - de l'installation des packages - de la configuration du fichier `/etc/openvpn/server.conf` - de la création des clés et certifications - de la gestion du pare-feu et du routage réseau ```yaml= --- - name: installation epel-release yum: name: epel-release state: latest update_cache: yes - name: installation OpenVPN et wget yum: name: - openvpn - wget state: latest update_cache: yes - name: importation de l'archive easy-rsa get_url: url: https://github.com/OpenVPN/easy-rsa/archive/v3.0.8.tar.gz dest: /home/centos mode: '0440' - name: extraire les fichiers de l'archive unarchive: src: /home/centos/v3.0.8.tar.gz dest: /home/centos/ - name: créer le répertoire easy-rsa dans /etc/openvpn file: path: /etc/openvpn/easy-rsa state: directory owner: root group: root mode: '0755' - name: déplacer le dossier easy-rsa-3.0.8/ vers /etc/openvpn/easy-rsa command: mv /home/centos/easy-rsa-3.0.8 /etc/openvpn/easy-rsa ... ``` ```yaml= --- - name: copier le fichier server.conf vers /etc/openvpn copy: src: /usr/share/doc/openvpn/sample/sample-config-files/server.conf dest: /etc/openvpn/ owner: root group: root mode: '0644' - name: modification du fichier /etc/openvpn/server.conf lineinfile: path: /etc/openvpn/server.conf regexp: "{{ item.regexp }}" line: "{{ item.line }}" with_items: - { regexp: '^(.*)topology subnet', line: 'topology subnet' } - { regexp: '^(.*)push "redirect-gateway def1 bypass-dhcp"', line: 'push "redirect-gateway def1 bypass-dhcp"' } - { regexp: '^(.*)push "dhcp-option DNS 208.67.222.222"', line: 'push "dhcp-option DNS 208.67.222.222"' } - { regexp: '^(.*)push "dhcp-option DNS 208.67.220.220"', line: 'push "dhcp-option DNS 208.67.220.220"' } - { regexp: '^(.*)user nobody', line: 'user nobody' } - { regexp: '^(.*)group nobody', line: 'group nobody' } - { regexp: '^(.*)tls-auth ta.key 0', line: ';tls-auth ta.key 0' } - name: ajouter ligne "tls-crypt myvpn.tlsauth" après ";tls-auth ta.key 0" lineinfile: path: /etc/openvpn/server.conf insertafter: '^(.*)tls-auth ta.key 0' line: "tls-crypt myvpn.tlsauth" state: present - name: generation de la clé de cryptage specifié dans le fichier de config command: openvpn --genkey --secret /etc/openvpn/myvpn.tlsauth ... ``` ```yaml= --- - name: copie du fichier vars.example en vars copy: src: /etc/openvpn/easy-rsa/easyrsa3/vars.example dest: /etc/openvpn/easy-rsa/easyrsa3/vars owner: root group: root mode: '0644' remote_src: yes - name: modification du fichier /etc/openvpn/easy-rsa/easyrsa3/vars lineinfile: path: /etc/openvpn/easy-rsa/easyrsa3/vars regexp: "{{ item.regexp }}" line: "{{ item.line }}" with_items: - { regexp: '^(.*)set_var EASYRSA_REQ_COUNTRY "US"', line: 'set_var EASYRSA_REQ_COUNTRY "FR"' } - { regexp: '^(.*)set_var EASYRSA_REQ_PROVINCE "California"', line: 'set_var EASYRSA_REQ_PROVINCE "Bretagne"' } - { regexp: '^(.*)set_var EASYRSA_REQ_CITY "San Francisco"', line: 'set_var EASYRSA_REQ_CITY "Lorient"' } - { regexp: '^(.*)set_var EASYRSA_REQ_ORG "Copyleft Certificate Co"', line: 'set_var EASYRSA_REQ_ORG "Groupe"' } - { regexp: '^(.*)set_var EASYRSA_REQ_EMAIL "me@example.net"', line: 'set_var EASYRSA_REQ_EMAIL "capge@groupe2.org"' } - { regexp: '^(.*)set_var EASYRSA_REQ_OU "My Organizational Unit"', line: 'set_var EASYRSA_REQ_OU "POEI gr2"' } - name: ajout des lignes KEY_NAME et KEY_CN lineinfile: path: /etc/openvpn/easy-rsa/easyrsa3/vars line: - export KEY_NAME="server" - export KEY_CN=openvpn.poeigroupe2.com - name: execution ./easyrsa clean-all command: ./easyrsa clean-all args: chdir: /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3 - name: execution ./easyrsa build-ca expect: command: ./easyrsa build-ca chdir: /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3 responses: (?i)Enter New CA Key Passphrase: "stage" (?i)(.*)Enter New CA Key Passphrase: "stage" (?i)Common Name(.*): "groupe 2" - name: execution ./easyrsa build-server-full server expect: command: ./easyrsa build-server-full server chdir: /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3 responses: (?i)Enter PEM pass phrase: "stage" (?i)(.*)Enter PEM pass phrase: "stage" (?i)Enter pass phrase(.*): "stage" - name: execution ./easyrsa gen-dh command: ./easyrsa gen-dh args: chdir: /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3 - name: execution ./easyrsa build-client-full client1 command: ./easyrsa build-client-full client1 args: chdir: /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3 - name: copie des clés et certificats copy: src: - /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3/pki/ca.crt - /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3/pki/dh.pem - /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3/pki/private/ca.key - /etc/openvpn/easy-rsa/easy-rsa-3.0.8/easyrsa3/pki/private/server.key dest: /etc/openvpn remote_src: yes ... ``` ```yaml= --- - name: ajout du service openvpn firewalld: name: openvpn zone: public permanent: yes state: enabled - name: ajout d'un masquerade firewalld: masquerade: yes state: enabled permanent: yes zone : public - name: creation de la variable du premier reseau command: VAR=$(ip route get 208.67.222.222 | awk 'NR==1 {print $(NF-2)}') - name: ajouter règle de routage utilisant la variable VAR command: firewall-cmd --permanent --direct --passthrough ipv4 -t nat -A POSTROUTING -s 10.8.0.0/24 -o $VAR -j MASQUERADE - name: reload firewalld service: name: firewalld state: reloaded - name: modifier le fichier /etc/sysctl.conf lineinfile: path: /etc/sysctl.conf line: 'net.ipv4.ip_forward = 1' - name: redemarrer NetworkManager service: name: NetworkManager state: restarted ... ``` ### 5. Configuration machine applications Playbook ansible et arborescence des rôles #### i. Pipeline jenkins Pour réaliser l'automatisation de la mise à jour des sites web nous avons avions le choix entre [gitlab CI/CD](https://docs.gitlab.com/ee/ci/) et [jenkins](https://www.jenkins.io/). Nous avons choisi jenkins pour sa simplicité d'utilisation et sa praticité et afin de consacrer plus de temps au premières étapes de la mise en place de l'architecture telles que la création des machines et les paramètrages réseaux. Bien que conscient du fait que jenkins soit automatisable moins facilement. Le pipeline jenkins que nous avons mis en place, sur le modèle build/run/test et qui est déclenché par un commit du développeur sur le projet gitlab "applications" est le suivant. 1. démarrer le conteneur jenkins 2. faire un jekyll build pour construire la nouvelle page web 3. vérifier que la page web a été mise à jour Commençons d'abord par faire fonctionner le tout manuellement. Pour réaliser l'implémentation d'un environnement Jekyll via Docker, nous suivons les étapes suivantes: 1. Installer un container Ruby ```bash $ docker pull ruby $ docker run --name jekyll-template -it ruby /bin/bash ``` 2. Installer Jekyll, et initialiser un dossier source Une fois dans le conteneur ``` # gem install jekyll bundler # jekyll new src # exit ``` 3. Récupérer le dossier /src sur la machine hôte ```bash $ docker cp jekyll-template:/src . $ docker rm jekyll-template ``` Le container jekyll-template n'étant plus nécessaire à ce stade, il peut être supprimé. 4. Créer un dossier dans lequel récupérer les pages web `$ mkdir site` 5. Ecrire le Dockerfile du container Ruby sur lequel tournera Jekyll ```dockerfile FROM ruby:3.0 RUN gem install jekyll bundler WORKDIR /src ``` 6. Build une image depuis ce Dockerfile ```bash $ bundle install $ sudo docker build -t my-jekyll -f Dockerfile . ``` 7. Lancer un container basé sur cette image ```bash $ cd ~ $ sudo docker run --name jekyll --volume $(pwd)/src:/src:z \ --volume $(pwd)/site:/site:z -p 4000:4000 -it my-jekyll ``` L'option :z du montage des volumes est essentielle pour permettre un partage effectif du contenu des dossiers. 8. C'est parti ! ```bash $ sudo docker start jekyll $ sudo docker exec jekyll bundle install $ sudo docker exec jekyll bundle exec jekyll build --destination /site $ sudo docker stop jekyll ``` Nous aurions aussi pu choisir d'utiliser le [Dockerfile proposé par liulantao](https://hub.docker.com/r/liulantao/jenkins-ruby/dockerfile) déjà concu pour travailler avec des projets développés en ruby. Néanmoins nous avons préféré nous servir d'un conteneur plus petit et plus simple suffisant amplement à notre besoin et réduisant ainsi les effets de bord. Regroupons maintenant ces étapes en créant un pipeline jenkins composé de trois tâches/jobs. On se servira d'un [conteneur jenkins](https://github.com/jenkinsci/docker/blob/master/README.md) en complément que l'on initialisera avec un playbook ansible (docker_image, docker_container, uri). 1. démarrer le conteneur jenkins Nouvel item 1_BUILD nom : construire un projet freestyle description : démarrer le conteneur jenkins paramètre string gitlabSourceBranch : master to prevent errors due to manual addition of a file directly through web GUI (https://github.com/jenkinsci/gitlab-plugin/issues/539) ce qui declenche le build scrutation de l'outil de gestion de version planning : * * * * * (chaque minute) gestion de code source git credentials name : PierrickHERVE psswd: ........ et choisir le credential nouvellement créée executer un script shell sudo docker build --no-cache -t my-jekyll -f Dockerfile . && cd ~ && sudo docker run --name jekyll --volume $(pwd)/src:/src:z --volume $(pwd)/site:/site:z -p 4000:4000 -it my-jekyll On en profite pour ajouter l'option `--no-cache` pour forcer l'execution des étapes du Dockerfile et pouvoir télécharger la version la plus récente des paquets utilisés même si le Dockerfile n'a pas été modifié. ```bash $ sudo docker build --no-cache -t my-jekyll -f Dockerfile . $ cd ~ $ sudo docker run --name jekyll --volume $(pwd)/src:/src:z \ --volume $(pwd)/site:/site:z -p 4000:4000 -it my-jekyll ``` Et si l'on souhaite redémarrer le conteneur de zéro. ```bash sudo docker stop jekyll && sudo docker rm jekyll && sudo docker system prune -y ``` 2. faire un jekyll build pour construire la nouvelle page web nouvel item 2_RUN description : faire un jekyll build pour construire la nouvelle page web ce qui délcenche le build construire après le build sur d'autres projets projet à surveiller : 1_BUILD déclencher que si la construction est stable ```bash $ sudo docker start jekyll $ sudo docker exec jekyll bundle install $ sudo docker exec jekyll bundle exec jekyll build --destination /site $ sudo docker stop jekyll ``` 3. vérifier que la page web a été mise à jour nouvel item 3_TEST visualiser la page ou vérifier qu'il y a un niouveau dossier ou plus récent ou une page de plus #### ii. Automatisation de Jekyll: deuxième tentative Dans cette section se trouve une ébauche d'autmatisation d'installation de Jekyll et Jenkins. De nombreux points sont simplement la traduction des commandes ci-dessus par Ansible. Le lancement manuel de la tâche sur Jenkins est fonctionnel, mais le déclenchement via gitlab n'est pas implémenté. 1. La première étape est d'installer les modules pythons nécessaire. On rappelle que Docker est déjà installé sur les différentes machines (voir [Installation Docker](#ii-Installation-Docker)) ```yaml= - name: python3-pip installation yum: name: - python3-pip state: present update_cache: true - name: setuptools installation yum: name: - python-setuptools state: present update_cache: true - name: Docker-python yum: name: - python-docker state: present update_cache: true ``` 2. Cette étape n'est que la traduction de l'installation d'un container Jekyll détaillée plus haut dans [Pipeline jenkins]( #i-Pipeline-jenkins) ```yaml= - name: installation d'un ruby template docker_container: name: jekyll-template image: ruby command: gem install jekyll bundler state: started - name: création src shell: docker exec jekyll-template jekyll new src - name: copie template shell: docker cp jekyll-template:/src /home/centos - name: suppression template docker_container: name: jekyll-template state: absent - name: creation dossier cible file: name: /home/centos/site state: directory - name: copie dockerfile copy: src: ./Dockerfile dest: /home/centos/Dockerfile owner: root group: root mode: '0644' - name: building image docker_image: name: my-jekyll build: source: build path: /home/centos state: present - name: docker jekyll docker_container: name: jekyll image: my-jekyll volumes: - /home/centos/src:/src:z - /home/centos/site:/site:z ports: - "4000:4000" state: started ``` avec pour Dockerfile ```dockerfile FROM ruby:3.0 RUN gem install jekyll bundler WORKDIR /src ``` 3. Ensuite, on prépare le terrain pour Jenkins: ```yaml= - name: creation depots file: name: - /home/centos/www - /home/centos/www_converted - /home/centos/jenkins state: directory - name: envoi cle ssh copy: src: /home/centos/.aws/ssh_pair.pem dest: /home/centos/jenkins mode: '0400' - name: envoi script bash copy: src: ./arnaque.sh dest: /home/centos/jenkins - name: envoi script principal template template: src: converter.j2 dest: /home/centos/jenkins/converter.sh mode: '0755' ``` Les dossiers www et www_converted ont vocation à être chacun un dépot Git: le premier contenant la source, et le deuxième contenant les fichiers convertis. Le but du script est de prendre chaque fichier markdown du dossier www, de le convertir par Jekyll, et de l'envoyer dans www_converted, en conservant l'arborescence. Voici le template converter.j2: ```bash= #!/bin/bash FILES="/www/*" dns_applications="{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}" for f in $FILES do if [[ $f =~ *.md ]] or [[ $f =~ *.markdown ]] then mv $f /src/to_be_converted.md ssh -i /jenkins/POEIgroupe2.pem centos@$dns_applications bash /home/centos/jenkins/arnaque.sh mw /site/to_be_converted.html www_converted/$f fi done ``` et le script arnaque.sh ```bash= #!/bin/bash sudo docker start jekyll sudo docker exec jekyll bundle install sudo docker exec jekyll bundle exec jekyll build --destination /site sudo docker stop jekyll ``` Le script converter.sh sera lancé depuis le container Jenkins, et a besoin de se connecter sur gr2_applications par ssh pour pouvoir lancer la conversion fichier par fichier avec des appels répétés de arnaque.sh. 4. La dernière étape est de lancer enfin le container Jenkins. ```yaml= - name: installer container jenkins docker_container: name: jenkins image: jenkins/jenkins:lts-jdk11 state: started ports: - "{{ jenkins_port }}:{{ jenkins_port }}" volumes: - jenkins_home:/var/jenkins_home - /home/centos/src:/src:z - /home/centos/site:/site:z - /home/centos/www:/www:z - /home/centos/www_converted:/www_converted:z register: jenkins_container ``` On monte tous les volumes nécessaires au bon fonctionnement du script sur le container. Il reste enfin à configurer Jenkins. La configuration pourrait être faite manuellement, mais pour ne pas avoir à installer un navigateur, on peut utiliser le playbook suivant: ```yaml= - name: token admin initial shell: docker exec {{jenkins_container.container.hostname}} cat /var/jenkins_home/secrets/initialAdminPassword register: jenkins_admin_password - name: authentification crumb uri: url: 'http://localhost:{{ jenkins_port }}/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)' user: admin password: '{{ jenkins_admin_password.stdout }}' force_basic_auth: yes return_content: yes register: crumb - name: creation admin jenkins uri: method: POST url: 'http://localhost:{{ jenkins_port }}/securityRealm/createAccountByAdmin' user: admin password: '{{ jenkins_admin_password.stdout }}' force_basic_auth: yes follow_redirects: all headers: Jenkins-Crumb: '{{ crumb.content.split(":")[1] }}' Cookie: '{{ crumb.set_cookie }}' body: 'username={{ jenkins_user }}&password1={{ jenkins_pass }}&password2={{ jenkins_pass }}&fullname={{ jenkins_fullname }}&email={{ jenkins_email }}' ``` Il ne reste plus qu'à créer la tâche qui execute converter.sh ! ### 6. Configuration machine sauvegarde La machine sauvegarde permettra de sauvegarder les données importants émanant de la machine contenant les applications. Ainsi, la machine "applications" est le serveur NFS, et la machine "sauvegarde" sera la machine cliente NFS. #### i. Configuration NFS La configuration du NFS sera faite avec Ansible, dans le rôle "NFS, dont l'arborescence est montrée ci-dessous : ![](https://i.imgur.com/f5lFjeG.png) Ici, nous avons utilisé 3 playbooks. Le premier (nfs_serveur.yml) créera le serveur NFS, le deuxieme (nfs_client.yml) configurera le client NFS, et le troisième (main.yml) permettra d'appeler les 2 autres playbooks et fera office de lanceur. Les voici: ```yaml= --- # nfs_server.yml - tasks: - name: installer nfs-utils yum: name: nfs-utils state: latest notify: - demarrer nfs - autoriser nfs dans firewalld - redemarrer firewalld - name: créer le dossier de montage serveur file: path: /nfs_sauvegarde state: directory owner: root group: root mode: '0775' - name: copie du fichier /etc/exports template: src: /etc/ansible/roles/nfs/templates/exports.j2 dest: /etc/exports owner: root group: root mode: '0644' - name: redemarrer nfs service: name: nfs-server state: restarted - name: commande exportfs pour tout rafraichir command: exportfs -var ... ``` ```yaml= --- # nfs_client.yml - tasks: - name: Installation de nfs-utils yum: name: - nfs-utils - nfs4-acl-tools state: latest notify: - démarrer nfs - ajouter nfs dans firewalld - redemarrer firewalld - name: Création du point de montage file: path: /mnt/sauvegarde state: directory owner: root group: root mode: '0775' - name: Montage du volume mount: path: /mnt/sauvegarde src: {{ ansible_local.ip_facts.applications.ip }}:/nfs_sauvegarde fstype: nfs state: mounted ... ``` Le fichier handlers/main.yml contient les commandes nécessaires aux deux playbooks (demarrage des services, configuration de Firewalld). ```yaml= -- # handlers file for nfs handlers: - name: demarrer nfs service: name: - nfs-server - rpcbind enabled: yes - name: autoriser nfs dans firewalld firewalld: name: - nfs - rpc-bind - mountd permanent: yes state: enabled - name: redemarrer firewalld service: name: firewalld state: reloaded ... ``` Le fichier templates/exports.j2 permet de copier dans /etc/exports du serveur NFS le fichier contenant le dossier de sauvegarde, les utilisateurs autorisés, ainsi que les permissions: `/nfs_sauvegarde *(rw)` --- ## III. Implémentations des fonctionnalités en dehors de l architecture machine ### 1. Traduction de la documentation en Latex Pour traduire la documentation en Latex et générer un pdf du code latex en question nous avons réalisé les tâches suivantes. * création d'une machine virtuelle avec un disque dur système de 32 Go pour acceuillir le conteneur [wikito](https://github.com/Witiko/markdown) qui est très volumineux * executions des commandes `docker run --rm -i witiko/markdown markdown-cli hybrid=true < documentation_v1.md` `sudo apt install texlive` `pdftex documentation_v1.tex` Bien sûr il ne s'agit là que de la première étape de traduction et de nombreux arguments pourraient être transmis à la commande pdftex pour améliorer le rendu du document latex généré. [Retour au sommaire](#Sommaire)