# **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.


### 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.

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:

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 :

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)