commit 4f9511f313a769425227db80b15d00eef2d42b08 Author: lucasdpt Date: Thu Jun 4 09:45:51 2026 +0200 feat: initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..91c2cb4 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# TP Final - Vagrant + Terraform + Ansible + +## Objectif + +- 1 VM Ubuntu et 1 VM Debian sur le réseau privé `192.168.56.0/24` +- MySQL dans Docker sur Ubuntu +- Mot de passe MySQL généré par Terraform +- UFW sur Ubuntu : ports 22 et 3306, avec 3306 seulement depuis `192.168.56.0/24` +- Test MySQL depuis Debian via Ansible +- Copie du fichier SQL sur Debian +- Exécution du SQL depuis Debian vers MySQL Ubuntu +- Vérification avec `SHOW TABLES` sur Ubuntu +- Bonus : utilisateur Debian non sudo `mysqlcaller` + +## Prérequis + +- VirtualBox +- Vagrant +- Terraform +- Ansible installé sur la machine hôte +- Collection Ansible `community.general` pour le module `ufw` : + +```bash +ansible-galaxy collection install community.general +``` + +## Installation rapide sous Linux + +Un script est fourni pour Debian/Ubuntu : + +```bash +bash scripts/install-deps-linux.sh +``` + +Le script ajoute les dépôts officiels HashiCorp et Oracle VirtualBox, installe `VirtualBox`, `Vagrant`, `Terraform`, `Ansible`, puis la collection `community.general`. + +## Lancer + +```bash +terraform init +terraform apply +``` + +## Récupérer le mot de passe MySQL + +```bash +terraform output --raw mysql_password +``` + +## Relancer uniquement Ansible + +```bash +ANSIBLE_CONFIG=ansible/ansible.cfg ansible-playbook -i ansible/inventory.ini ansible/site.yml +``` + +## Nettoyer + +```bash +terraform destroy +``` + +Si vous avez modifié manuellement le `Vagrantfile` entre deux exécutions et que Vagrant garde des entrées obsolètes, vous pouvez nettoyer son cache avec : + +```bash +vagrant global-status --prune +``` diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..1955916 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,31 @@ +# Deux VMs sur un réseau privé Host-Only : 192.168.56.0/24 +Vagrant.configure("2") do |config| + config.vm.provider "virtualbox" do |vb| + vb.gui = false + vb.check_guest_additions = false + end + + config.vm.define "ubuntu" do |ubuntu| + ubuntu.vm.box = "generic/ubuntu2204" + ubuntu.vm.hostname = "ubuntu-mysql" + ubuntu.vm.network "private_network", ip: "192.168.56.10" + + ubuntu.vm.provider "virtualbox" do |vb| + vb.name = "tp-final-ubuntu-mysql" + vb.memory = "2048" + vb.cpus = 2 + end + end + + config.vm.define "debian" do |debian| + debian.vm.box = "generic/debian12" + debian.vm.hostname = "debian-client" + debian.vm.network "private_network", ip: "192.168.56.11" + + debian.vm.provider "virtualbox" do |vb| + vb.name = "tp-final-debian-client" + vb.memory = "1024" + vb.cpus = 1 + end + end +end diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..128dbaf --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +host_key_checking = False +retry_files_enabled = False +stdout_callback = yaml +interpreter_python = auto_silent diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..91b47bb --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,8 @@ +[ubuntu] +ubuntu_mysql ansible_host=192.168.56.10 ansible_user=vagrant ansible_ssh_private_key_file=.vagrant/machines/ubuntu/virtualbox/private_key + +[debian] +debian_client ansible_host=192.168.56.11 ansible_user=vagrant ansible_ssh_private_key_file=.vagrant/machines/debian/virtualbox/private_key + +[all:vars] +ansible_ssh_common_args='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' diff --git a/ansible/playbooks/01-common.yml b/ansible/playbooks/01-common.yml new file mode 100644 index 0000000..bf3938d --- /dev/null +++ b/ansible/playbooks/01-common.yml @@ -0,0 +1,9 @@ +--- +- name: Préparer les deux VMs + hosts: all + become: yes + tasks: + - name: Mettre à jour le cache apt + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 diff --git a/ansible/playbooks/02-ubuntu-docker-mysql.yml b/ansible/playbooks/02-ubuntu-docker-mysql.yml new file mode 100644 index 0000000..05b0fbd --- /dev/null +++ b/ansible/playbooks/02-ubuntu-docker-mysql.yml @@ -0,0 +1,52 @@ +--- +- name: Installer Docker et lancer MySQL sur Ubuntu + hosts: ubuntu + become: yes + tasks: + - name: Installer Docker + ansible.builtin.apt: + name: + - docker.io + state: present + + - name: Démarrer Docker au boot + ansible.builtin.service: + name: docker + state: started + enabled: yes + + - name: Vérifier si le conteneur MySQL existe déjà + ansible.builtin.command: docker inspect tp-mysql + register: mysql_container + failed_when: false + changed_when: false + + - name: Lancer le conteneur MySQL + ansible.builtin.command: >- + docker run -d + --name tp-mysql + --restart unless-stopped + -e MYSQL_ROOT_PASSWORD={{ mysql_root_password }} + -e MYSQL_DATABASE={{ mysql_database }} + -p {{ mysql_host }}:{{ mysql_port }}:3306 + mysql:8.0 + when: mysql_container.rc != 0 + + - name: Attendre que MySQL réponde dans le conteneur + ansible.builtin.command: >- + docker exec tp-mysql mysqladmin ping + -uroot -p{{ mysql_root_password }} + register: mysql_ping + retries: 30 + delay: 5 + until: mysql_ping.rc == 0 + changed_when: false + + - name: Afficher docker ps + ansible.builtin.command: docker ps --filter name=tp-mysql + register: docker_ps + changed_when: false + + - name: Debug docker ps + ansible.builtin.debug: + var: docker_ps.stdout_lines diff --git a/ansible/playbooks/03-ubuntu-ufw.yml b/ansible/playbooks/03-ubuntu-ufw.yml new file mode 100644 index 0000000..5c337b9 --- /dev/null +++ b/ansible/playbooks/03-ubuntu-ufw.yml @@ -0,0 +1,28 @@ +--- +- name: Configurer UFW sur Ubuntu + hosts: ubuntu + become: yes + tasks: + - name: Installer UFW + ansible.builtin.apt: + name: ufw + state: present + + - name: Autoriser SSH + community.general.ufw: + rule: allow + port: '22' + proto: tcp + + - name: Autoriser MySQL seulement depuis le réseau privé des VMs + community.general.ufw: + rule: allow + from_ip: "{{ mysql_network_cidr }}" + to_port: "{{ mysql_port }}" + proto: tcp + + - name: Activer UFW avec refus entrant par défaut + community.general.ufw: + state: enabled + policy: deny + direction: incoming diff --git a/ansible/playbooks/04-debian-client.yml b/ansible/playbooks/04-debian-client.yml new file mode 100644 index 0000000..60802b4 --- /dev/null +++ b/ansible/playbooks/04-debian-client.yml @@ -0,0 +1,27 @@ +--- +- name: Installer le client MySQL sur Debian et tester le port + hosts: debian + become: yes + tasks: + - name: Installer le client MySQL/MariaDB + ansible.builtin.apt: + name: default-mysql-client + state: present + + - name: Vérifier que le port 3306 est joignable depuis Debian + ansible.builtin.wait_for: + host: "{{ mysql_host }}" + port: "{{ mysql_port }}" + timeout: 30 + + - name: Tester la connexion MySQL depuis Debian + ansible.builtin.command: >- + mysql -h {{ mysql_host }} -P {{ mysql_port }} + -uroot -p{{ mysql_root_password }} + -e "SELECT 'Connexion OK depuis Debian' AS test;" + register: mysql_test + changed_when: false + + - name: Afficher le test de connexion + ansible.builtin.debug: + var: mysql_test.stdout_lines diff --git a/ansible/playbooks/05-debian-import-sql.yml b/ansible/playbooks/05-debian-import-sql.yml new file mode 100644 index 0000000..a7b58a1 --- /dev/null +++ b/ansible/playbooks/05-debian-import-sql.yml @@ -0,0 +1,19 @@ +--- +- name: Copier et exécuter le fichier SQL depuis Debian vers MySQL Ubuntu + hosts: debian + become: yes + tasks: + - name: Copier le fichier schema.sql sur Debian + ansible.builtin.copy: + src: ../../sql/schema.sql + dest: /tmp/schema.sql + mode: '0644' + + - name: Créer la table définie dans le fichier SQL sur MySQL Ubuntu + ansible.builtin.shell: >- + mysql -h {{ mysql_host }} -P {{ mysql_port }} + -uroot -p{{ mysql_root_password }} + {{ mysql_database }} < /tmp/schema.sql + args: + executable: /bin/bash + changed_when: true diff --git a/ansible/playbooks/06-ubuntu-verify.yml b/ansible/playbooks/06-ubuntu-verify.yml new file mode 100644 index 0000000..348f383 --- /dev/null +++ b/ansible/playbooks/06-ubuntu-verify.yml @@ -0,0 +1,17 @@ +--- +- name: Vérifier la création de table directement sur Ubuntu + hosts: ubuntu + become: yes + tasks: + - name: SHOW TABLES dans le conteneur MySQL + ansible.builtin.command: >- + docker exec tp-mysql mysql + -uroot -p{{ mysql_root_password }} + {{ mysql_database }} + -e "SHOW TABLES;" + register: show_tables + changed_when: false + + - name: Afficher les tables + ansible.builtin.debug: + var: show_tables.stdout_lines diff --git a/ansible/playbooks/07-bonus-debian-user.yml b/ansible/playbooks/07-bonus-debian-user.yml new file mode 100644 index 0000000..397129c --- /dev/null +++ b/ansible/playbooks/07-bonus-debian-user.yml @@ -0,0 +1,34 @@ +--- +- name: Bonus - créer un utilisateur non sudo capable d'appeler MySQL + hosts: debian + become: yes + tasks: + - name: Créer l'utilisateur non sudo mysqlcaller + ansible.builtin.user: + name: mysqlcaller + shell: /bin/bash + create_home: yes + state: present + + - name: Créer un fichier .my.cnf pour mysqlcaller + ansible.builtin.copy: + dest: /home/mysqlcaller/.my.cnf + owner: mysqlcaller + group: mysqlcaller + mode: '0600' + content: | + [client] + host={{ mysql_host }} + port={{ mysql_port }} + user=root + password={{ mysql_root_password }} + database={{ mysql_database }} + + - name: Tester MySQL avec l'utilisateur non sudo + ansible.builtin.command: sudo -u mysqlcaller mysql -e "SHOW TABLES;" + register: mysqlcaller_test + changed_when: false + + - name: Afficher le test du user non sudo + ansible.builtin.debug: + var: mysqlcaller_test.stdout_lines diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..d38be55 --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,8 @@ +--- +- import_playbook: playbooks/01-common.yml +- import_playbook: playbooks/02-ubuntu-docker-mysql.yml +- import_playbook: playbooks/03-ubuntu-ufw.yml +- import_playbook: playbooks/04-debian-client.yml +- import_playbook: playbooks/05-debian-import-sql.yml +- import_playbook: playbooks/06-ubuntu-verify.yml +- import_playbook: playbooks/07-bonus-debian-user.yml diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..16747b3 --- /dev/null +++ b/main.tf @@ -0,0 +1,75 @@ +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + local = { + source = "hashicorp/local" + version = "~> 2.5" + } + vagrant = { + source = "bmatcuk/vagrant" + version = "~> 4.0" + } + } +} + +locals { + vagrantfile_hash = filemd5("${path.module}/Vagrantfile") + + provisioning_files = sort(concat( + [for file in fileset("${path.module}/ansible", "**") : "ansible/${file}"], + [for file in fileset("${path.module}/sql", "**") : "sql/${file}"], + )) + + provisioning_hash = sha256(join("", [ + for file in local.provisioning_files : filesha256("${path.module}/${file}") + ])) +} + +resource "random_password" "mysql" { + length = 16 + special = false +} + +resource "local_file" "ansible_vars" { + filename = "${path.module}/ansible/group_vars/all.yml" + content = <&2 +} + +die() { + printf '[install] Error: %s\n' "$*" >&2 + exit 1 +} + +run_root() { + if [[ "${EUID}" -eq 0 ]]; then + "$@" + else + require_command sudo + sudo "$@" + fi +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "Commande requise introuvable: $1" +} + +contains() { + local value="$1" + shift + + local item + for item in "$@"; do + if [[ "$item" == "$value" ]]; then + return 0 + fi + done + + return 1 +} + +get_codename() { + if [[ -n "${VERSION_CODENAME:-}" ]]; then + printf '%s\n' "${VERSION_CODENAME}" + return 0 + fi + + if command -v lsb_release >/dev/null 2>&1; then + lsb_release -cs + return 0 + fi + + die "Impossible de determiner le codename de la distribution." +} + +ensure_supported_distribution() { + require_command apt-get + require_command dpkg + + if [[ ! -r /etc/os-release ]]; then + die "Le fichier /etc/os-release est introuvable." + fi + + # shellcheck disable=SC1091 + . /etc/os-release + + DISTRO_ID="${ID:-}" + CODENAME="$(get_codename)" + ARCH="$(dpkg --print-architecture)" + + if [[ "${ARCH}" != "amd64" ]]; then + die "Ce script cible les hotes Debian/Ubuntu amd64 pour VirtualBox. Architecture detectee: ${ARCH}." + fi + + case "${DISTRO_ID}" in + ubuntu) + if ! contains "${CODENAME}" focal jammy noble oracular; then + warn "Ubuntu ${CODENAME} n'etait pas dans la liste testee au moment d'ecrire ce script. Je continue avec les depots officiels." + fi + ;; + debian) + if ! contains "${CODENAME}" bullseye bookworm; then + warn "Debian ${CODENAME} n'etait pas dans la liste testee au moment d'ecrire ce script. Je continue avec les depots officiels." + fi + ;; + *) + die "Distribution non prise en charge: ${DISTRO_ID:-inconnue}. Ce script supporte Debian et Ubuntu." + ;; + esac +} + +install_base_packages() { + log "Installation des prerequis APT..." + run_root apt-get update + run_root apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release \ + software-properties-common \ + wget +} + +configure_hashicorp_repo() { + local tmpdir + tmpdir="$(mktemp -d)" + + log "Configuration du depot HashiCorp..." + curl -fsSL "https://apt.releases.hashicorp.com/gpg" -o "${tmpdir}/hashicorp.gpg" + run_root gpg --yes --dearmor -o "${HASHICORP_KEYRING}" "${tmpdir}/hashicorp.gpg" + printf 'deb [signed-by=%s] https://apt.releases.hashicorp.com %s main\n' "${HASHICORP_KEYRING}" "${CODENAME}" \ + | run_root tee "${HASHICORP_LIST}" >/dev/null + rm -rf "${tmpdir}" +} + +configure_virtualbox_repo() { + local tmpdir + tmpdir="$(mktemp -d)" + + log "Configuration du depot Oracle VirtualBox..." + curl -fsSL "https://www.virtualbox.org/download/oracle_vbox_2016.asc" -o "${tmpdir}/oracle_vbox_2016.asc" + run_root gpg --yes --dearmor -o "${VIRTUALBOX_KEYRING}" "${tmpdir}/oracle_vbox_2016.asc" + printf 'deb [arch=amd64 signed-by=%s] https://download.virtualbox.org/virtualbox/debian %s contrib\n' "${VIRTUALBOX_KEYRING}" "${CODENAME}" \ + | run_root tee "${VIRTUALBOX_LIST}" >/dev/null + rm -rf "${tmpdir}" +} + +select_virtualbox_package() { + local packages=() + local package="" + + mapfile -t packages < <(apt-cache search --names-only '^virtualbox-[0-9]+\.[0-9]+$' | awk '{print $1}' | sort -V) + + if [[ "${#packages[@]}" -gt 0 ]]; then + package="${packages[$(("${#packages[@]}" - 1))]}" + elif apt-cache show virtualbox >/dev/null 2>&1; then + package="virtualbox" + else + die "Aucun paquet VirtualBox compatible n'a ete trouve apres l'ajout du depot Oracle." + fi + + VIRTUALBOX_PACKAGE="${package}" + log "Paquet VirtualBox selectionne: ${VIRTUALBOX_PACKAGE}" +} + +install_project_dependencies() { + select_virtualbox_package + + log "Installation de VirtualBox, Vagrant, Terraform et Ansible..." + run_root apt-get update + run_root apt-get install -y \ + ansible \ + terraform \ + vagrant \ + "${VIRTUALBOX_PACKAGE}" +} + +install_ansible_collection() { + log "Installation de la collection Ansible community.general..." + run_root mkdir -p /usr/share/ansible/collections + run_root ansible-galaxy collection install community.general --collections-path /usr/share/ansible/collections --force +} + +print_versions() { + log "Verification des versions installees:" + VBoxManage --version + vagrant --version + terraform version + ansible --version +} + +print_next_steps() { + cat <<'EOF' + +Installation terminee. + +Etapes suivantes: + 1. terraform init + 2. terraform apply + +Note: + - Si Secure Boot est actif, VirtualBox peut demander une validation ou une signature de modules noyau avant de pouvoir demarrer des VMs. +EOF +} + +main() { + ensure_supported_distribution + install_base_packages + configure_hashicorp_repo + configure_virtualbox_repo + install_project_dependencies + install_ansible_collection + print_versions + print_next_steps +} + +DISTRO_ID="" +CODENAME="" +ARCH="" + +main "$@" diff --git a/sql/schema.sql b/sql/schema.sql new file mode 100644 index 0000000..9937abf --- /dev/null +++ b/sql/schema.sql @@ -0,0 +1,11 @@ +CREATE DATABASE IF NOT EXISTS ma_base_de_donnees; +USE ma_base_de_donnees; + + +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL +); + +INSERT INTO users (username, email) VALUES ('admin', 'admin@exemple.com'); \ No newline at end of file