From bb38f846cddcfc7d852519a78125d85a2bfa0002 Mon Sep 17 00:00:00 2001 From: Dan Allaire Date: Sat, 1 Nov 2025 17:57:46 -0400 Subject: [PATCH] =?UTF-8?q?Premiers=20r=C3=B4les=20ansible=20et=20leurs=20?= =?UTF-8?q?playbooks=20de=20d=C3=A9ploiement=20(Phases=201=20et=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ansible/README-PHASE2.md | 387 +++++++++++++++++ ansible/README.md | 411 ++++++++++++++++++ ansible/ansible.cfg | 60 ++- .../inventories/production/group_vars/all.yml | 120 ++++- .../production/group_vars/dns_servers.yml | 165 +++++++ .../production/group_vars/forge_servers.yml | 168 +++++++ .../production/group_vars/idp_servers.yml | 129 ++++++ ansible/inventories/production/hosts | 11 - ansible/inventories/production/hosts.yml | 89 ++++ ansible/playbooks/phase1-dns-deploy.yml | 161 +++++++ ansible/playbooks/phase2-deploy.yml | 178 ++++++++ ansible/roles/common/defaults/main.yml | 47 ++ ansible/roles/common/handlers/main.yml | 23 + ansible/roles/common/tasks/logging.yml | 22 + ansible/roles/common/tasks/main.yml | 72 +++ ansible/roles/common/tasks/nftables.yml | 35 ++ ansible/roles/common/tasks/ntp.yml | 23 + ansible/roles/common/tasks/security.yml | 77 ++++ ansible/roles/common/tasks/ssh.yml | 32 ++ ansible/roles/common/templates/chrony.conf.j2 | 25 ++ .../roles/common/templates/nftables.conf.j2 | 74 ++++ ansible/roles/common/templates/sshd.conf.j2 | 53 +++ ansible/roles/forgejo/handlers/main.yml | 18 + ansible/roles/forgejo/tasks/database.yml | 98 +++++ ansible/roles/forgejo/tasks/main.yml | 131 ++++++ ansible/roles/forgejo/templates/app.ini.j2 | 100 +++++ .../forgejo/templates/forgejo.service.j2 | 91 ++++ .../forgejo/templates/nginx-forgejo.conf.j2 | 36 ++ ansible/roles/keycloak/handlers/main.yml | 18 + ansible/roles/keycloak/tasks/database.yml | 140 ++++++ ansible/roles/keycloak/tasks/main.yml | 140 ++++++ .../roles/keycloak/templates/keycloak.conf.j2 | 25 ++ .../keycloak/templates/keycloak.service.j2 | 83 ++++ .../keycloak/templates/nginx-keycloak.conf.j2 | 30 ++ .../keycloak/templates/realm-config.json.j2 | 48 ++ ansible/roles/netbox/README.md | 38 -- ansible/roles/netbox/defaults/main.yml | 3 - ansible/roles/netbox/handlers/main.yml | 3 - ansible/roles/netbox/meta/main.yml | 35 -- ansible/roles/netbox/tasks/main.yml | 17 - ansible/roles/netbox/tests/inventory | 3 - ansible/roles/netbox/tests/test.yml | 6 - ansible/roles/netbox/vars/main.yml | 3 - ansible/roles/postgresql/defaults/main.yml | 22 + ansible/roles/postgresql/handlers/main.yml | 13 + ansible/roles/postgresql/tasks/main.yml | 114 +++++ .../roles/postgresql/templates/pg_hba.conf.j2 | 24 + .../postgresql/templates/postgresql.conf.j2 | 38 ++ .../powerdns-authoritative/handlers/main.yml | 18 + .../powerdns-authoritative/tasks/firewall.yml | 57 +++ .../powerdns-authoritative/tasks/main.yml | 90 ++++ .../powerdns-authoritative/tasks/repo.yml | 25 ++ .../powerdns-authoritative/tasks/schema.yml | 149 +++++++ .../templates/pdns.conf.j2 | 70 +++ .../templates/pdns.local.gpgsql.conf.j2 | 12 + ansible/site.yml | 13 - ansible/vault/production-phase2.yml.example | 54 +++ ansible/vault/production.yml.example | 41 ++ 58 files changed, 4024 insertions(+), 144 deletions(-) create mode 100644 ansible/README-PHASE2.md create mode 100644 ansible/README.md create mode 100644 ansible/inventories/production/group_vars/dns_servers.yml create mode 100644 ansible/inventories/production/group_vars/forge_servers.yml create mode 100644 ansible/inventories/production/group_vars/idp_servers.yml delete mode 100644 ansible/inventories/production/hosts create mode 100644 ansible/inventories/production/hosts.yml create mode 100644 ansible/playbooks/phase1-dns-deploy.yml create mode 100644 ansible/playbooks/phase2-deploy.yml create mode 100644 ansible/roles/common/defaults/main.yml create mode 100644 ansible/roles/common/handlers/main.yml create mode 100644 ansible/roles/common/tasks/logging.yml create mode 100644 ansible/roles/common/tasks/main.yml create mode 100644 ansible/roles/common/tasks/nftables.yml create mode 100644 ansible/roles/common/tasks/ntp.yml create mode 100644 ansible/roles/common/tasks/security.yml create mode 100644 ansible/roles/common/tasks/ssh.yml create mode 100644 ansible/roles/common/templates/chrony.conf.j2 create mode 100644 ansible/roles/common/templates/nftables.conf.j2 create mode 100644 ansible/roles/common/templates/sshd.conf.j2 create mode 100644 ansible/roles/forgejo/handlers/main.yml create mode 100644 ansible/roles/forgejo/tasks/database.yml create mode 100644 ansible/roles/forgejo/tasks/main.yml create mode 100644 ansible/roles/forgejo/templates/app.ini.j2 create mode 100644 ansible/roles/forgejo/templates/forgejo.service.j2 create mode 100644 ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 create mode 100644 ansible/roles/keycloak/handlers/main.yml create mode 100644 ansible/roles/keycloak/tasks/database.yml create mode 100644 ansible/roles/keycloak/tasks/main.yml create mode 100644 ansible/roles/keycloak/templates/keycloak.conf.j2 create mode 100644 ansible/roles/keycloak/templates/keycloak.service.j2 create mode 100644 ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 create mode 100644 ansible/roles/keycloak/templates/realm-config.json.j2 delete mode 100644 ansible/roles/netbox/README.md delete mode 100644 ansible/roles/netbox/defaults/main.yml delete mode 100644 ansible/roles/netbox/handlers/main.yml delete mode 100644 ansible/roles/netbox/meta/main.yml delete mode 100644 ansible/roles/netbox/tasks/main.yml delete mode 100644 ansible/roles/netbox/tests/inventory delete mode 100644 ansible/roles/netbox/tests/test.yml delete mode 100644 ansible/roles/netbox/vars/main.yml create mode 100644 ansible/roles/postgresql/defaults/main.yml create mode 100644 ansible/roles/postgresql/handlers/main.yml create mode 100644 ansible/roles/postgresql/tasks/main.yml create mode 100644 ansible/roles/postgresql/templates/pg_hba.conf.j2 create mode 100644 ansible/roles/postgresql/templates/postgresql.conf.j2 create mode 100644 ansible/roles/powerdns-authoritative/handlers/main.yml create mode 100644 ansible/roles/powerdns-authoritative/tasks/firewall.yml create mode 100644 ansible/roles/powerdns-authoritative/tasks/main.yml create mode 100644 ansible/roles/powerdns-authoritative/tasks/repo.yml create mode 100644 ansible/roles/powerdns-authoritative/tasks/schema.yml create mode 100644 ansible/roles/powerdns-authoritative/templates/pdns.conf.j2 create mode 100644 ansible/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 delete mode 100644 ansible/site.yml create mode 100644 ansible/vault/production-phase2.yml.example create mode 100644 ansible/vault/production.yml.example diff --git a/ansible/README-PHASE2.md b/ansible/README-PHASE2.md new file mode 100644 index 0000000..089aed0 --- /dev/null +++ b/ansible/README-PHASE2.md @@ -0,0 +1,387 @@ +# đŸŒČ Alliance BorĂ©ale - Phase 2 : IdentitĂ© & Forge + +Extension de l'infrastructure avec SSO (Keycloak) et Forge Git (Forgejo). + +--- + +## 📋 SERVICES DÉPLOYÉS + +| Service | VMID | FQDN | IP | Couche | Port(s) | +|---------|------|------|----|----|---------| +| **Keycloak** | 03010 | `sso.infra.chezlepro.ca` | 10.0.3.20 | C3 | 443 (HTTPS) | +| **Forgejo** | 04021 | `git.infra.chezlepro.ca` | 10.0.1.20 | C4 | 443 (HTTPS), 2222 (SSH) | + +--- + +## 🚀 DÉPLOIEMENT PHASE 2 + +### 1. CrĂ©er les VMs + +```bash +# VM Keycloak +VMID: 03010 +RAM: 4GB (Keycloak = gourmand en Java) +CPU: 2 cores +Disk: 30GB +OS: Ubuntu 22.04 LTS +IP: 10.0.3.20 + +# VM Forgejo +VMID: 04021 +RAM: 2GB +CPU: 2 cores +Disk: 50GB (repos Git) +OS: Ubuntu 22.04 LTS +IP: 10.0.1.20 +``` + +### 2. Mettre Ă  jour l'inventaire + +```bash +# Copier le nouvel inventaire +cp inventories/production/hosts-phase2.yml inventories/production/hosts.yml + +# Éditer les IPs si nĂ©cessaire +vi inventories/production/hosts.yml +``` + +### 3. GĂ©nĂ©rer les secrets Phase 2 + +```bash +# Mots de passe +openssl rand -base64 24 # Keycloak admin +openssl rand -base64 24 # Keycloak DB +openssl rand -base64 24 # Forgejo admin +openssl rand -base64 24 # Forgejo DB + +# Secrets Forgejo (64 chars hex) +openssl rand -hex 64 # secret_key +openssl rand -hex 64 # internal_token +openssl rand -hex 64 # jwt_secret +``` + +### 4. Mettre Ă  jour le vault + +```bash +# Éditer vault +ansible-vault edit vault/production.yml + +# Ajouter les secrets Phase 2 (voir vault/production-phase2.yml.example) +``` + +### 5. DĂ©ployer Keycloak d'abord + +```bash +# DĂ©ployer seulement Keycloak +ansible-playbook playbooks/phase2-deploy.yml --limit idp_servers --ask-vault-pass +``` + +### 6. Configurer Keycloak + +```bash +# AccĂ©der Ă  Keycloak +https://sso.infra.chezlepro.ca + +# Login admin (vault_keycloak_admin_password) + +# Le realm "alliance-boreale" devrait ĂȘtre créé automatiquement +# Aller dans: Realm "alliance-boreale" → Clients → "forgejo" +``` + +### 7. RĂ©cupĂ©rer le Client Secret Forgejo + +``` +Keycloak UI: +1. Realm: alliance-boreale +2. Clients → forgejo +3. Credentials tab +4. Copier "Client Secret" +``` + +### 8. Ajouter le secret au vault + +```bash +ansible-vault edit vault/production.yml + +# Ajouter: +vault_keycloak_forgejo_client_secret: "" +``` + +### 9. DĂ©ployer Forgejo + +```bash +# DĂ©ployer Forgejo avec SSO configurĂ© +ansible-playbook playbooks/phase2-deploy.yml --limit forge_servers --ask-vault-pass +``` + +--- + +## ✅ VALIDATION POST-DÉPLOIEMENT + +### Test Keycloak + +```bash +# AccĂšs web +curl -k https://sso.infra.chezlepro.ca/realms/alliance-boreale + +# Devrait retourner du JSON avec config du realm +``` + +### Test Forgejo + +```bash +# AccĂšs web +curl -k https://git.infra.chezlepro.ca + +# Devrait retourner HTML de la page d'accueil + +# Test SSH +ssh -T -p 2222 git@git.infra.chezlepro.ca +# Devrait afficher: "Hi there, You've successfully authenticated..." +``` + +### Test SSO (Forgejo → Keycloak) + +``` +1. Aller sur https://git.infra.chezlepro.ca +2. Cliquer "Sign in with L'Alliance BorĂ©ale SSO" +3. Devrait rediriger vers Keycloak +4. CrĂ©er un compte ou se connecter +5. Devrait rediriger vers Forgejo connectĂ© +``` + +--- + +## đŸ‘„ GESTION UTILISATEURS + +### CrĂ©er des utilisateurs dans Keycloak + +``` +Keycloak UI: +1. Realm: alliance-boreale +2. Users → Add user +3. Remplir: username, email, first name, last name +4. Save +5. Credentials tab → Set Password +6. Temporary: OFF +7. Save +``` + +### Donner accĂšs admin Forgejo + +``` +SSH sur serveur Forgejo: +ssh ansible@10.0.1.20 + +# Promouvoir utilisateur admin +sudo -u git /opt/forgejo/forgejo admin user change-password \ + --username \ + --password \ + --config /etc/forgejo/app.ini + +# Ou donner rĂŽle admin +sudo -u git /opt/forgejo/forgejo admin user create \ + --admin \ + --username \ + --email \ + --password \ + --config /etc/forgejo/app.ini +``` + +--- + +## 📩 PREMIERS DÉPÔTS + +### CrĂ©er organisation "alliance-boreale" + +``` +Forgejo UI: +1. Sign in via SSO +2. + → New Organization +3. Name: alliance-boreale +4. Visibility: Public +5. Create +``` + +### CrĂ©er dĂ©pĂŽts essentiels + +``` +Dans l'organisation "alliance-boreale": + +1. ansible-infrastructure + Description: Infrastructure as Code + Private: No + Initialize: Yes (README.md) + +2. documentation + Description: Documentation technique + Private: No + +3. registraire + Description: Registraire membres (Document 14) + Private: No +``` + +### Pousser ce dĂ©pĂŽt Ansible + +```bash +cd /chemin/vers/ansible + +# Ajouter remote Forgejo +git remote add forgejo git@git.infra.chezlepro.ca:2222/alliance-boreale/ansible-infrastructure.git + +# Pousser +git push -u forgejo main +``` + +--- + +## 🔄 INTÉGRATION CONTINUE (CI/CD) + +Forgejo Actions (GitHub Actions compatible) est activĂ©. + +### Exemple workflow `.forgejo/workflows/lint.yml` + +```yaml +name: Ansible Lint + +on: + push: + branches: [main] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Ansible + run: | + pip3 install ansible ansible-lint + + - name: Run ansible-lint + run: | + ansible-lint playbooks/*.yml +``` + +--- + +## 🔐 SÉCURITÉ + +### Certificats SSL + +Phase 2 utilise des **certificats auto-signĂ©s** par dĂ©faut. + +**Pour production** : Utiliser Let's Encrypt + +```yaml +# Dans group_vars/idp_servers.yml et forge_servers.yml +nginx: + ssl: + cert_source: letsencrypt + letsencrypt_email: admin@chezlepro.ca +``` + +Puis redĂ©ployer : +```bash +ansible-playbook playbooks/phase2-deploy.yml --tags nginx +``` + +### Firewall + +Les rĂšgles nftables sont automatiquement configurĂ©es : + +```bash +# VĂ©rifier sur les serveurs +sudo nft list ruleset | grep -A 10 "Keycloak\|Forgejo" +``` + +### Backups PostgreSQL + +ConfigurĂ©s automatiquement : +- **Keycloak DB** : Backup quotidien Ă  3h, rĂ©tention 14 jours +- **Forgejo DB** : Backup quotidien Ă  4h, rĂ©tention 14 jours + +VĂ©rifier : +```bash +ls -lh /var/backups/postgresql/ +``` + +--- + +## 🐛 DÉPANNAGE + +### Keycloak ne dĂ©marre pas + +```bash +# Logs +sudo journalctl -u keycloak -n 100 --no-pager + +# VĂ©rifier Java +java -version # Devrait ĂȘtre OpenJDK 17 + +# VĂ©rifier PostgreSQL +sudo -u postgres psql keycloak -c "SELECT version();" + +# Rebuild Keycloak +sudo -u keycloak /opt/keycloak/bin/kc.sh build +sudo systemctl restart keycloak +``` + +### Forgejo ne se connecte pas Ă  Keycloak + +```bash +# VĂ©rifier logs Forgejo +sudo journalctl -u forgejo -n 100 --no-pager + +# Tester dĂ©couverte OpenID +curl -k https://sso.infra.chezlepro.ca/realms/alliance-boreale/.well-known/openid-configuration + +# VĂ©rifier client secret dans app.ini +sudo grep -A 5 "oauth2.alliance-boreale" /etc/forgejo/app.ini +``` + +### SSH Forgejo ne fonctionne pas + +```bash +# VĂ©rifier port SSH Forgejo +sudo ss -tulnp | grep 2222 + +# Tester localement +ssh -T -p 2222 git@localhost + +# VĂ©rifier firewall +sudo nft list ruleset | grep 2222 + +# Logs SSH Forgejo +sudo journalctl -u forgejo -f | grep SSH +``` + +--- + +## 📚 RÉFÉRENCES + +- [Keycloak Documentation](https://www.keycloak.org/documentation) +- [Forgejo Documentation](https://forgejo.org/docs/) +- [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/) +- [OpenID Connect](https://openid.net/connect/) + +--- + +## 🎯 PROCHAINE ÉTAPE : PHASE 3 + +**Phase 3 : FastAPI Pivot (C5)** + +Services Ă  dĂ©ployer : +- FastAPI Admin Portal +- API Gateway +- Tenant provisioning +- Monitoring dashboard + +--- + +**Version:** 1.0 +**Date:** 2025-10-31 +**Membre:** Chezlepro Inc. +**Phases:** 1 (DNS) + 2 (IdP + Forge) diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000..9758dac --- /dev/null +++ b/ansible/README.md @@ -0,0 +1,411 @@ +# đŸŒČ Alliance BorĂ©ale - Ansible Automation + +Infrastructure as Code pour L'Alliance BorĂ©ale - Membre: **Chezlepro Inc.** + +## 📋 Table des matiĂšres + +- [PrĂ©requis](#prĂ©requis) +- [Structure du projet](#structure-du-projet) +- [Configuration initiale](#configuration-initiale) +- [Phase 1 : DNS FĂ©dĂ©rĂ©](#phase-1--dns-fĂ©dĂ©rĂ©) +- [Gestion des secrets](#gestion-des-secrets) +- [Commandes utiles](#commandes-utiles) +- [DĂ©pannage](#dĂ©pannage) + +--- + +## 🔧 PrĂ©requis + +### Sur votre machine de contrĂŽle + +```bash +# Ubuntu/Debian +sudo apt update +sudo apt install ansible python3-pip sshpass + +# OU via pip (recommandĂ© pour derniĂšre version) +pip3 install ansible ansible-lint + +# Collections Ansible requises +ansible-galaxy collection install community.general +ansible-galaxy collection install community.postgresql +ansible-galaxy collection install ansible.posix +``` + +### Sur les serveurs cibles (VMs) + +- Ubuntu 22.04 LTS ou Debian 12 +- AccĂšs root temporaire (sera dĂ©sactivĂ© aprĂšs) +- Connexion SSH fonctionnelle + +--- + +## 📁 Structure du projet + +``` +ansible/ +├── ansible.cfg # Configuration Ansible +├── README.md # Ce fichier +│ +├── inventories/ +│ └── production/ +│ ├── hosts.yml # Inventaire des serveurs +│ └── group_vars/ +│ ├── all.yml # Variables globales +│ └── dns_servers.yml # Variables DNS +│ +├── roles/ +│ ├── common/ # Hardening de base +│ ├── postgresql/ # Base de donnĂ©es +│ └── powerdns-authoritative/ # DNS autoritaire +│ +├── playbooks/ +│ └── phase1-dns-deploy.yml # DĂ©ploiement Phase 1 +│ +└── vault/ + └── production.yml # Secrets chiffrĂ©s (Ă  crĂ©er) +``` + +--- + +## ⚙ Configuration initiale + +### 1. GĂ©nĂ©rer clĂ©s SSH + +```bash +# GĂ©nĂ©rer une paire de clĂ©s pour Ansible +ssh-keygen -t ed25519 -f ~/.ssh/alliance_boreale_ed25519 -C "ansible@chezlepro.ca" + +# Copier la clĂ© publique sur les serveurs (temporairement en root) +ssh-copy-id -i ~/.ssh/alliance_boreale_ed25519.pub root@10.0.2.10 +ssh-copy-id -i ~/.ssh/alliance_boreale_ed25519.pub root@10.0.2.11 +``` + +### 2. Configurer l'inventaire + +Éditer `inventories/production/hosts.yml` : + +```yaml +# Remplacer les IPs par vos IPs publiques/internes +ns1.infra.chezlepro.ca: + ansible_host: # ← MODIFIER ICI + +ns2.infra.chezlepro.ca: + ansible_host: # ← MODIFIER ICI +``` + +Éditer `inventories/production/group_vars/all.yml` : + +```yaml +# Ajouter vos clĂ©s SSH publiques +ssh: + authorized_keys: + - "ssh-ed25519 AAAAC3... votre-email@example.com" # ← AJOUTER ICI +``` + +### 3. CrĂ©er le vault de secrets + +```bash +# CrĂ©er un mot de passe pour le vault +ansible-vault create vault/production.yml +``` + +Contenu du vault : + +```yaml +--- +# Secrets pour Phase 1 +vault_pdns_api_key: "changeme-api-key-secure-random" +vault_postgresql_pdns_password: "changeme-db-password-secure-random" +``` + +**Sauvegarder le mot de passe vault dans un gestionnaire de mots de passe !** + +### 4. Tester la connectivitĂ© + +```bash +# Ping tous les hĂŽtes +ansible all -m ping + +# Devrait afficher: +# ns1.infra.chezlepro.ca | SUCCESS => { "ping": "pong" } +# ns2.infra.chezlepro.ca | SUCCESS => { "ping": "pong" } +``` + +--- + +## 🚀 Phase 1 : DNS FĂ©dĂ©rĂ© + +### DĂ©ploiement complet + +```bash +# DĂ©ployer Phase 1 complĂšte +ansible-playbook playbooks/phase1-dns-deploy.yml --ask-vault-pass + +# OU avec fichier mot de passe +echo "votre-mot-de-passe-vault" > .vault_pass +chmod 600 .vault_pass +ansible-playbook playbooks/phase1-dns-deploy.yml --vault-password-file .vault_pass +``` + +### DĂ©ploiement par Ă©tapes (tags) + +```bash +# Seulement hardening (common) +ansible-playbook playbooks/phase1-dns-deploy.yml --tags common + +# Seulement PostgreSQL +ansible-playbook playbooks/phase1-dns-deploy.yml --tags postgresql + +# Seulement PowerDNS +ansible-playbook playbooks/phase1-dns-deploy.yml --tags powerdns + +# Mode dry-run (vĂ©rifier sans appliquer) +ansible-playbook playbooks/phase1-dns-deploy.yml --check --diff +``` + +### DĂ©ploiement sur un seul serveur + +```bash +# Seulement ns1 +ansible-playbook playbooks/phase1-dns-deploy.yml --limit ns1.infra.chezlepro.ca + +# Seulement ns2 +ansible-playbook playbooks/phase1-dns-deploy.yml --limit ns2.infra.chezlepro.ca +``` + +--- + +## 🔐 Gestion des secrets + +### Éditer le vault + +```bash +# Éditer secrets +ansible-vault edit vault/production.yml + +# Voir contenu (sans Ă©diter) +ansible-vault view vault/production.yml + +# Changer mot de passe vault +ansible-vault rekey vault/production.yml +``` + +### GĂ©nĂ©rer mots de passe sĂ©curisĂ©s + +```bash +# API Key PowerDNS (32 caractĂšres alphanumĂ©riques) +openssl rand -base64 32 + +# Mot de passe PostgreSQL +openssl rand -base64 24 +``` + +--- + +## đŸ§Ș Validation post-dĂ©ploiement + +### Tests DNS + +```bash +# RĂ©solution SOA +dig @10.0.2.10 chezlepro.ca SOA +short + +# RĂ©solution NS +dig @10.0.2.10 chezlepro.ca NS +short + +# Test DNSSEC +dig @10.0.2.10 chezlepro.ca DNSKEY +dnssec + +# Test depuis Internet (aprĂšs propagation) +dig @8.8.8.8 chezlepro.ca SOA +short +``` + +### Export DS Records (DNSSEC) + +```bash +# Se connecter au DNS master +ssh ansible@10.0.2.10 + +# Exporter DS records +sudo pdnsutil show-zone chezlepro.ca | grep DS + +# Exemple output: +# chezlepro.ca IN DS 12345 13 2 abc123... +``` + +**Ajouter ces DS records chez votre registraire de domaine !** + +### Validation DNSSEC publique + +```bash +# Avec DNSViz (aprĂšs ajout DS records) +# https://dnsviz.net/d/chezlepro.ca/dnssec/ + +# Ou en ligne de commande +delv @8.8.8.8 chezlepro.ca SOA + +# Devrait afficher: "fully validated" +``` + +### VĂ©rifier services + +```bash +# SSH sur serveur +ssh ansible@10.0.2.10 + +# Status PowerDNS +sudo systemctl status pdns + +# Status PostgreSQL +sudo systemctl status postgresql + +# Logs PowerDNS +sudo journalctl -u pdns -f + +# Firewall rules +sudo nft list ruleset + +# API PowerDNS (depuis le serveur) +curl -H "X-API-Key: " http://127.0.0.1:8081/api/v1/servers/localhost +``` + +--- + +## đŸ› ïž Commandes utiles + +### Ansible ad-hoc + +```bash +# Mettre Ă  jour tous les packages +ansible all -b -m apt -a "upgrade=dist update_cache=yes" + +# RedĂ©marrer un service +ansible dns_servers -b -m systemd -a "name=pdns state=restarted" + +# Collecter facts +ansible all -m setup + +# ExĂ©cuter commande shell +ansible all -b -a "uptime" +``` + +### Gestion PowerDNS + +```bash +# Lister zones +pdnsutil list-all-zones + +# Ajouter un record +pdnsutil add-record chezlepro.ca www A 203.0.113.10 3600 + +# Supprimer un record +pdnsutil delete-rrset chezlepro.ca www A + +# Rectifier zone (aprĂšs modifications) +pdnsutil rectify-zone chezlepro.ca + +# Augmenter serial SOA +pdnsutil increase-serial chezlepro.ca + +# VĂ©rifier zone +pdnsutil check-zone chezlepro.ca +``` + +--- + +## 🐛 DĂ©pannage + +### Erreur: "Failed to connect to the host" + +```bash +# VĂ©rifier connectivitĂ© +ping 10.0.2.10 + +# VĂ©rifier SSH manuel +ssh -i ~/.ssh/alliance_boreale_ed25519 ansible@10.0.2.10 + +# VĂ©rifier port SSH +nmap -p 22 10.0.2.10 +``` + +### Erreur: "Permission denied (publickey)" + +```bash +# VĂ©rifier que la clĂ© est chargĂ©e +ssh-add -l + +# Ajouter la clĂ© si nĂ©cessaire +ssh-add ~/.ssh/alliance_boreale_ed25519 + +# VĂ©rifier authorized_keys sur le serveur +ssh root@10.0.2.10 "cat /home/ansible/.ssh/authorized_keys" +``` + +### Erreur: "Vault password required" + +```bash +# Option 1: Demander interactivement +--ask-vault-pass + +# Option 2: Fichier +--vault-password-file .vault_pass + +# Option 3: Variable d'environnement +export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass +``` + +### PowerDNS ne dĂ©marre pas + +```bash +# Voir logs dĂ©taillĂ©s +sudo journalctl -u pdns -n 100 --no-pager + +# VĂ©rifier config +sudo pdns_server --config-check + +# Tester connexion PostgreSQL +sudo -u postgres psql powerdns -c "SELECT * FROM domains;" +``` + +### DNS ne rĂ©pond pas + +```bash +# VĂ©rifier que le port est ouvert +sudo ss -tulnp | grep :53 + +# VĂ©rifier firewall +sudo nft list ruleset | grep "53" + +# Tester en local d'abord +dig @127.0.0.1 chezlepro.ca SOA + +# Ensuite depuis l'extĂ©rieur +dig @ chezlepro.ca SOA +``` + +--- + +## 📚 Documentation de rĂ©fĂ©rence + +- [Nomenclature v4 (CRB-2)](../00_Nomenclature_biomimetique_autopoietique_v4_CRB2.md) +- [Document 05: OpĂ©ration DNS FĂ©dĂ©rĂ©e](/mnt/project/05_Operation_DNS_Federee.md) +- [Document 14: Structure YAML Registraire](/mnt/project/14_Structure_YAML_Registraire.md) +- [PowerDNS Documentation](https://doc.powerdns.com/authoritative/) +- [Ansible Documentation](https://docs.ansible.com/) + +--- + +## 🆘 Support + +Pour questions ou problĂšmes : +1. Consulter les logs : `sudo journalctl -u pdns -f` +2. VĂ©rifier la documentation dans `/mnt/project/` +3. Contacter Cercle OpĂ©rationnel via Matrix + +--- + +**Version:** 1.0 +**Date:** 2025-10-31 +**Membre:** Chezlepro Inc. +**Phase:** 1 (DNS FĂ©dĂ©rĂ© - C1-C2) diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index d805482..d2a4333 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -1,7 +1,59 @@ +# Alliance BorĂ©ale - Ansible Configuration +# Version: 1.0 +# Date: 2025-10-31 + [defaults] -inventory = inventories/production/hosts -remote_user = ansible +# Inventaire +inventory = inventories/production/hosts.yml + +# Comportement host_key_checking = False -roles_path = roles -vault_password_file = ~/.alliance-vault-pass.txt +retry_files_enabled = False +gathering = smart +fact_caching = jsonfile +fact_caching_connection = /tmp/ansible_facts +fact_caching_timeout = 3600 + +# Output stdout_callback = yaml +bin_ansible_callbacks = True +display_skipped_hosts = False +display_ok_hosts = True + +# Performance +forks = 10 +poll_interval = 5 +timeout = 30 + +# PrivilĂšges +become = True +become_method = sudo +become_user = root +become_ask_pass = False + +# SSH +remote_user = ansible +private_key_file = ~/.ssh/alliance_boreale_ed25519 + +# Logs +log_path = /var/log/ansible/ansible.log + +# Roles +roles_path = roles + +# Collections +collections_paths = ~/.ansible/collections:/usr/share/ansible/collections + +[privilege_escalation] +become = True +become_method = sudo +become_user = root +become_ask_pass = False + +[ssh_connection] +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no +pipelining = True +control_path = /tmp/ansible-ssh-%%h-%%p-%%r + +[inventory] +enable_plugins = yaml, ini, host_list diff --git a/ansible/inventories/production/group_vars/all.yml b/ansible/inventories/production/group_vars/all.yml index 3c0475b..290220d 100644 --- a/ansible/inventories/production/group_vars/all.yml +++ b/ansible/inventories/production/group_vars/all.yml @@ -1,9 +1,113 @@ ---- -# Standards Alliance BorĂ©ale -offline_mode: false -alliance_domain: alliance-boreale.org -admin_email: admin@alliance-boreale.org +# Alliance BorĂ©ale - Variables Globales +# Membre: Chezlepro Inc. +# Date: 2025-10-31 -# NetBox (exemple) -netbox_domain: "{{ inventory_hostname }}.{{ alliance_domain }}" -letsencrypt_email: "{{ admin_email }}" +--- +# ========================================== +# IDENTIFICATION MEMBRE +# ========================================== +member: + id: m001 + slug: clp + legal_name: "Chezlepro Inc." + domain_primary: chezlepro.ca + +# ========================================== +# RÉSEAU +# ========================================== +network: + internal_subnet: 10.0.0.0/8 + subnets: + management: 10.0.0.0/24 + dns: 10.0.2.0/24 + governance: 10.0.3.0/24 + forge: 10.0.1.0/24 + pivot: 10.0.4.0/24 + tenants: 10.0.10.0/23 + +# ========================================== +# SYSTÈME +# ========================================== +system: + timezone: America/Toronto + locale: en_CA.UTF-8 + +ntp: + enabled: true + servers: + - 0.ca.pool.ntp.org + - 1.ca.pool.ntp.org + - 2.ca.pool.ntp.org + - 3.ca.pool.ntp.org + +# ========================================== +# SSH +# ========================================== +ssh: + port: 22 + permit_root_login: false + password_authentication: false + pubkey_authentication: true + authorized_keys: + # Remplacer par vos clĂ©s SSH publiques + - "ssh-ed25519 AAAAC3... admin@chezlepro.ca" + +# ========================================== +# UTILISATEURS +# ========================================== +users: + ansible: + name: ansible + groups: [sudo] + shell: /bin/bash + sudo_nopasswd: true + +# ========================================== +# SÉCURITÉ +# ========================================== +security: + firewall: nftables + fail2ban_enabled: true + unattended_upgrades: true + +firewall: + default_policy: drop + allowed_ssh_sources: + - 0.0.0.0/0 # À restreindre en production + +# ========================================== +# PACKAGES DE BASE +# ========================================== +base_packages: + - vim + - htop + - curl + - wget + - git + - tree + - tmux + - python3 + - python3-pip + - apt-transport-https + - ca-certificates + - gnupg + - lsb-release + - net-tools + - dnsutils + - tcpdump + +# ========================================== +# MONITORING (Phase 2) +# ========================================== +monitoring: + enabled: false + prometheus_enabled: false + node_exporter_enabled: false + +# ========================================== +# LOGS +# ========================================== +logging: + central_enabled: false + syslog_server: null + retention_days: 30 diff --git a/ansible/inventories/production/group_vars/dns_servers.yml b/ansible/inventories/production/group_vars/dns_servers.yml new file mode 100644 index 0000000..c411e27 --- /dev/null +++ b/ansible/inventories/production/group_vars/dns_servers.yml @@ -0,0 +1,165 @@ +# Alliance BorĂ©ale - Variables DNS Servers +# Membre: Chezlepro Inc. +# Date: 2025-10-31 + +--- +# ========================================== +# POWERDNS CONFIGURATION +# ========================================== +powerdns: + version: "4.8" + backend: pgsql + + # API Configuration + api: + enabled: true + key: "{{ vault_pdns_api_key }}" # À dĂ©finir dans vault + + webserver: + enabled: true + address: 0.0.0.0 + port: 8081 + allow_from: 10.0.0.0/8 + + # DNSSEC + dnssec: + enabled: true + algorithm: ECDSAP256SHA256 # Algorithm 13 + + # Logging + logging: + level: 4 # Info + queries: false + details: true + + # Performance + cache_ttl: 20 + negquery_cache_ttl: 60 + query_cache_ttl: 20 + +# ========================================== +# ZONES DNS +# ========================================== +dns_zones: + - name: chezlepro.ca + type: native + soa: + nameserver: ns1.infra.chezlepro.ca + email: admin.chezlepro.ca + serial: 2025103101 # YYYYMMDDNN + refresh: 3600 + retry: 1800 + expire: 1209600 + minimum: 3600 + + # Nameservers + ns_records: + - ns1.infra.chezlepro.ca + - ns2.infra.chezlepro.ca + # - ns1.infra.nuagelibre.ca # Futur: secondaire croisĂ© + + # Records de base + records: + # DNS Servers + - name: ns1.infra + type: A + content: 10.0.2.10 + ttl: 3600 + + - name: ns2.infra + type: A + content: 10.0.2.11 + ttl: 3600 + + # Wildcards (optionnel) + # - name: "*.infra" + # type: A + # content: 10.0.0.1 + # ttl: 3600 + +# ========================================== +# AXFR CONFIGURATION +# ========================================== +axfr: + # IPs autorisĂ©es pour AXFR + allow_from: + - 10.0.2.11 # ns2.infra.chezlepro.ca (slave local) + # - # Futur: ns1.infra.nuagelibre.ca + # - # Futur: ns1.infra.technolibre.ca + + # TSIG Keys (optionnel, recommandĂ©) + tsig_enabled: false + tsig_keys: [] + # - name: clp-nul-xfer + # algorithm: hmac-sha256 + # secret: "{{ vault_tsig_clp_nul }}" + +# ========================================== +# NOTIFY CONFIGURATION +# ========================================== +notify: + also_notify: + - 10.0.2.11 # ns2.infra.chezlepro.ca + # - # Futur + # - # Futur + +# ========================================== +# POSTGRESQL CONFIGURATION +# ========================================== +postgresql: + version: 15 + listen_addresses: localhost + port: 5432 + + # Database pour PowerDNS + databases: + - name: powerdns + owner: pdns + encoding: UTF8 + lc_collate: en_US.UTF-8 + lc_ctype: en_US.UTF-8 + + users: + - name: pdns + password: "{{ vault_postgresql_pdns_password }}" + # role_attr_flags: CREATEDB # Optionnel + + # Tuning basique + shared_buffers: 256MB + work_mem: 16MB + maintenance_work_mem: 128MB + effective_cache_size: 1GB + + # Backups + backup: + enabled: true + schedule: "0 2 * * *" # 2h du matin + retention_days: 7 + destination: /var/backups/postgresql + +# ========================================== +# FIREWALL RULES (nftables) +# ========================================== +firewall_rules: + dns: + - port: 53 + proto: udp + source: 0.0.0.0/0 + comment: "DNS queries (UDP)" + + - port: 53 + proto: tcp + source: 0.0.0.0/0 + comment: "DNS queries (TCP)" + + pdns_api: + - port: 8081 + proto: tcp + source: 10.0.0.0/8 + comment: "PowerDNS API (internal only)" + + postgresql: + - port: 5432 + proto: tcp + source: 127.0.0.1/32 + comment: "PostgreSQL (localhost only)" diff --git a/ansible/inventories/production/group_vars/forge_servers.yml b/ansible/inventories/production/group_vars/forge_servers.yml new file mode 100644 index 0000000..88690c3 --- /dev/null +++ b/ansible/inventories/production/group_vars/forge_servers.yml @@ -0,0 +1,168 @@ +# Alliance BorĂ©ale - Variables Forgejo (Forge) +# Membre: Chezlepro Inc. +# Date: 2025-10-31 + +--- +# ========================================== +# FORGEJO CONFIGURATION +# ========================================== +forgejo: + version: "1.21" # Version stable + install_method: binary # binary | docker + + # Paths + install_dir: /opt/forgejo + data_dir: /var/lib/forgejo + custom_dir: /etc/forgejo + log_dir: /var/log/forgejo + + # User + user: git + group: git + + # Network + hostname: git.infra.chezlepro.ca + domain: chezlepro.ca + http_port: 3000 + ssh_port: 2222 # Éviter conflit avec SSH systĂšme + + # Database (PostgreSQL) + db: + type: postgres + host: 127.0.0.1:5432 + name: forgejo + user: forgejo + password: "{{ vault_postgresql_forgejo_password }}" + ssl_mode: disable + + # Security + secret_key: "{{ vault_forgejo_secret_key }}" + internal_token: "{{ vault_forgejo_internal_token }}" + jwt_secret: "{{ vault_forgejo_jwt_secret }}" + + # Admin + admin: + username: admin + password: "{{ vault_forgejo_admin_password }}" + email: admin@chezlepro.ca + + # SSO (Keycloak OpenID Connect) + oauth: + enabled: true + providers: + - name: alliance-boreale + provider: openidConnect + display_name: "L'Alliance BorĂ©ale SSO" + client_id: forgejo + client_secret: "{{ vault_keycloak_forgejo_client_secret }}" + openid_connect_auto_discovery_url: "https://sso.infra.chezlepro.ca/realms/alliance-boreale/.well-known/openid-configuration" + + # Features + features: + enable_actions: true # CI/CD + enable_packages: true # Container registry + enable_lfs: true # Git LFS + disable_registration: true # Inscription via SSO uniquement + require_signin: true + +# ========================================== +# POSTGRESQL CONFIGURATION (Forgejo) +# ========================================== +postgresql: + version: 15 + listen_addresses: localhost + port: 5432 + + databases: + - name: forgejo + owner: forgejo + encoding: UTF8 + lc_collate: en_US.UTF-8 + lc_ctype: en_US.UTF-8 + + users: + - name: forgejo + password: "{{ vault_postgresql_forgejo_password }}" + + shared_buffers: 512MB + work_mem: 32MB + maintenance_work_mem: 256MB + effective_cache_size: 2GB + + backup: + enabled: true + schedule: "0 4 * * *" # 4h du matin + retention_days: 14 + destination: /var/backups/postgresql + +# ========================================== +# REVERSE PROXY (Nginx) +# ========================================== +nginx: + enabled: true + + ssl: + enabled: true + cert_source: self-signed # self-signed | letsencrypt | custom + + vhosts: + - server_name: git.infra.chezlepro.ca + listen_port: 443 + ssl: true + proxy_pass: http://127.0.0.1:3000 + + # Large file uploads (Git) + client_max_body_size: 512M + + proxy_set_headers: + Host: $host + X-Real-IP: $remote_addr + X-Forwarded-For: $proxy_add_x_forwarded_for + X-Forwarded-Proto: $scheme + +# ========================================== +# FIREWALL RULES +# ========================================== +firewall_rules: + forgejo_http: + - port: 3000 + proto: tcp + source: 127.0.0.1/32 + comment: "Forgejo HTTP (internal only)" + + forgejo_ssh: + - port: 2222 + proto: tcp + source: 0.0.0.0/0 + comment: "Forgejo SSH (Git over SSH)" + + nginx: + - port: 443 + proto: tcp + source: 0.0.0.0/0 + comment: "HTTPS (public)" + + - port: 80 + proto: tcp + source: 0.0.0.0/0 + comment: "HTTP (redirect to HTTPS)" + +# ========================================== +# INITIAL REPOSITORIES +# ========================================== +forgejo_initial_repos: + - name: ansible-infrastructure + description: "Infrastructure as Code - L'Alliance BorĂ©ale" + private: false + auto_init: true + default_branch: main + + - name: documentation + description: "Documentation technique" + private: false + auto_init: true + + - name: registraire + description: "Registraire des membres (Document 14)" + private: false + auto_init: true diff --git a/ansible/inventories/production/group_vars/idp_servers.yml b/ansible/inventories/production/group_vars/idp_servers.yml new file mode 100644 index 0000000..053fe6f --- /dev/null +++ b/ansible/inventories/production/group_vars/idp_servers.yml @@ -0,0 +1,129 @@ +# Alliance BorĂ©ale - Variables Keycloak (IdP) +# Membre: Chezlepro Inc. +# Date: 2025-10-31 + +--- +# ========================================== +# KEYCLOAK CONFIGURATION +# ========================================== +keycloak: + version: "23.0.0" # Version LTS + install_method: standalone # standalone | docker + + # Network + hostname: sso.infra.chezlepro.ca + http_port: 8080 + https_port: 8443 + + # Admin + admin_user: admin + admin_password: "{{ vault_keycloak_admin_password }}" + + # Database (PostgreSQL) + db: + vendor: postgres + host: localhost + port: 5432 + database: keycloak + username: keycloak + password: "{{ vault_postgresql_keycloak_password }}" + + # Realm configuration + realms: + - name: alliance-boreale + display_name: "L'Alliance BorĂ©ale" + enabled: true + + # Clients (services qui utilisent SSO) + clients: + - client_id: forgejo + name: "Forgejo Git" + enabled: true + protocol: openid-connect + redirect_uris: + - "https://git.infra.chezlepro.ca/*" + web_origins: + - "https://git.infra.chezlepro.ca" + + # Futurs clients Phase 3 + # - client_id: fastapi-pivot + # name: "FastAPI Admin" + # - client_id: matrix + # name: "Matrix Synapse" + +# ========================================== +# POSTGRESQL CONFIGURATION (Keycloak) +# ========================================== +postgresql: + version: 15 + listen_addresses: localhost + port: 5432 + + databases: + - name: keycloak + owner: keycloak + encoding: UTF8 + lc_collate: en_US.UTF-8 + lc_ctype: en_US.UTF-8 + + users: + - name: keycloak + password: "{{ vault_postgresql_keycloak_password }}" + + shared_buffers: 512MB + work_mem: 32MB + maintenance_work_mem: 256MB + effective_cache_size: 2GB + + backup: + enabled: true + schedule: "0 3 * * *" # 3h du matin + retention_days: 14 + destination: /var/backups/postgresql + +# ========================================== +# REVERSE PROXY (Nginx) +# ========================================== +nginx: + enabled: true + + # SSL/TLS + ssl: + enabled: true + cert_source: self-signed # self-signed | letsencrypt | custom + # letsencrypt_email: admin@chezlepro.ca + + # Vhosts + vhosts: + - server_name: sso.infra.chezlepro.ca + listen_port: 443 + ssl: true + proxy_pass: http://127.0.0.1:8080 + + # Headers + proxy_set_headers: + Host: $host + X-Real-IP: $remote_addr + X-Forwarded-For: $proxy_add_x_forwarded_for + X-Forwarded-Proto: $scheme + +# ========================================== +# FIREWALL RULES +# ========================================== +firewall_rules: + keycloak: + - port: 8080 + proto: tcp + source: 127.0.0.1/32 + comment: "Keycloak HTTP (internal only)" + + nginx: + - port: 443 + proto: tcp + source: 0.0.0.0/0 + comment: "HTTPS (public)" + + - port: 80 + proto: tcp + source: 0.0.0.0/0 + comment: "HTTP (redirect to HTTPS)" diff --git a/ansible/inventories/production/hosts b/ansible/inventories/production/hosts deleted file mode 100644 index e39d7dd..0000000 --- a/ansible/inventories/production/hosts +++ /dev/null @@ -1,11 +0,0 @@ -[netbox] -netbox-prod ansible_host=203.0.113.10 ansible_user=ansible - -[powerdns] -# À remplir plus tard - -[keycloak] -# À remplir plus tard - -[proxmox] -# À remplir plus tard diff --git a/ansible/inventories/production/hosts.yml b/ansible/inventories/production/hosts.yml new file mode 100644 index 0000000..cc10827 --- /dev/null +++ b/ansible/inventories/production/hosts.yml @@ -0,0 +1,89 @@ +# Alliance BorĂ©ale - Inventaire Production +# Membre: Chezlepro Inc. (clp) +# Domaine: chezlepro.ca +# Date: 2025-10-31 +# Nomenclature: v4 (CRB-2) + +--- +all: + children: + # ======================================== + # COUCHE 2 : RÉSEAU & DNS + # ======================================== + dns_servers: + hosts: + ns1.infra.chezlepro.ca: + ansible_host: 10.0.2.10 + # ansible_host: # DĂ©commenter si accĂšs direct + vmid: 02001 + vm_name: clp-infra-dns-master-prod-01 + dns_role: master + dns_zone_primary: chezlepro.ca + + ns2.infra.chezlepro.ca: + ansible_host: 10.0.2.11 + # ansible_host: # DĂ©commenter si accĂšs direct + vmid: 02002 + vm_name: clp-infra-dns-slave-prod-01 + dns_role: slave + dns_zone_primary: chezlepro.ca + + vars: + # Variables communes DNS + pdns_backend: pgsql + pdns_dnssec_enabled: true + pdns_api_enabled: true + pdns_webserver_enabled: true + pdns_webserver_port: 8081 + + # ======================================== + # GROUPES LOGIQUES + # ======================================== + dns_masters: + hosts: + ns1.infra.chezlepro.ca: + + dns_slaves: + hosts: + ns2.infra.chezlepro.ca: + + # PostgreSQL (pour PowerDNS) + postgresql_servers: + hosts: + ns1.infra.chezlepro.ca: + ns2.infra.chezlepro.ca: + + # ========================================== + # VARIABLES GLOBALES + # ========================================== + vars: + # Membre + member_slug: clp + member_legal_name: "Chezlepro Inc." + member_domain: chezlepro.ca + + # RĂ©seau + network_internal_subnet: 10.0.0.0/8 + network_dns_subnet: 10.0.2.0/24 + + # Ansible + ansible_user: ansible + ansible_become: true + ansible_python_interpreter: /usr/bin/python3 + + # Timezone + timezone: America/Toronto + + # NTP + ntp_servers: + - 0.ca.pool.ntp.org + - 1.ca.pool.ntp.org + - 2.ca.pool.ntp.org + + # SSH + ssh_port: 22 + ssh_permit_root_login: false + ssh_password_authentication: false + + # SĂ©curitĂ© + firewall_backend: nftables diff --git a/ansible/playbooks/phase1-dns-deploy.yml b/ansible/playbooks/phase1-dns-deploy.yml new file mode 100644 index 0000000..f8fa46a --- /dev/null +++ b/ansible/playbooks/phase1-dns-deploy.yml @@ -0,0 +1,161 @@ +# Alliance BorĂ©ale - Playbook Phase 1 +# DĂ©ploiement DNS FĂ©dĂ©rĂ© (Couches C1-C2) +# Membre: Chezlepro Inc. +# Date: 2025-10-31 + +--- +- name: "đŸŒČ Phase 1 - PrĂ©paration des VMs (Common)" + hosts: all + become: true + gather_facts: true + + pre_tasks: + - name: "🔍 VĂ©rifier connectivitĂ© SSH" + ansible.builtin.ping: + tags: always + + - name: "📋 Afficher informations hĂŽte" + ansible.builtin.debug: + msg: | + HĂŽte: {{ inventory_hostname }} + IP: {{ ansible_host }} + VMID: {{ vmid | default('N/A') }} + RĂŽle DNS: {{ dns_role | default('N/A') }} + tags: always + + roles: + - role: common + tags: common + + post_tasks: + - name: "✅ Common role terminĂ©" + ansible.builtin.debug: + msg: "✅ Hardening complĂ©tĂ© sur {{ inventory_hostname }}" + tags: always + +# ============================================ +- name: "đŸ—„ïž Phase 1 - PostgreSQL pour DNS" + hosts: dns_servers + become: true + gather_facts: true + + roles: + - role: postgresql + tags: postgresql + + post_tasks: + - name: "✅ PostgreSQL terminĂ©" + ansible.builtin.debug: + msg: "✅ PostgreSQL {{ postgresql.version }} configurĂ© sur {{ inventory_hostname }}" + tags: always + +# ============================================ +- name: "🌐 Phase 1 - PowerDNS Master" + hosts: dns_masters + become: true + gather_facts: true + serial: 1 # Un Ă  la fois pour Ă©viter race conditions + + roles: + - role: powerdns-authoritative + tags: powerdns + + post_tasks: + - name: "✅ PowerDNS Master terminĂ©" + ansible.builtin.debug: + msg: "✅ PowerDNS MASTER configurĂ© sur {{ inventory_hostname }}" + tags: always + +# ============================================ +- name: "🌐 Phase 1 - PowerDNS Slave" + hosts: dns_slaves + become: true + gather_facts: true + + roles: + - role: powerdns-authoritative + tags: powerdns + + post_tasks: + - name: "✅ PowerDNS Slave terminĂ©" + ansible.builtin.debug: + msg: "✅ PowerDNS SLAVE configurĂ© sur {{ inventory_hostname }}" + tags: always + +# ============================================ +- name: "đŸ§Ș Phase 1 - Tests de validation" + hosts: dns_masters + become: false + gather_facts: false + + tasks: + - name: "đŸ§Ș Test rĂ©solution DNS (zone primaire)" + ansible.builtin.command: + cmd: "dig @{{ ansible_host }} {{ member.domain_primary }} SOA +short" + delegate_to: localhost + register: dns_test + changed_when: false + + - name: "✅ RĂ©sultat test DNS" + ansible.builtin.debug: + msg: "✅ DNS fonctionne: {{ dns_test.stdout }}" + + - name: "đŸ§Ș Test AXFR (zone transfer)" + ansible.builtin.command: + cmd: "dig @{{ ansible_host }} {{ member.domain_primary }} AXFR +short" + delegate_to: localhost + register: axfr_test + changed_when: false + failed_when: false + + - name: "✅ RĂ©sultat AXFR" + ansible.builtin.debug: + msg: | + {% if axfr_test.rc == 0 %} + ✅ AXFR fonctionne ({{ axfr_test.stdout_lines | length }} lignes) + {% else %} + ⚠ AXFR refusĂ© (normal si ACL strictes configurĂ©es) + {% endif %} + +# ============================================ +- name: "📋 Phase 1 - RĂ©capitulatif" + hosts: localhost + gather_facts: false + + tasks: + - name: "🎉 Phase 1 terminĂ©e avec succĂšs!" + ansible.builtin.debug: + msg: | + + ═══════════════════════════════════════════════ + đŸŒČ PHASE 1 - DNS FÉDÉRÉ DÉPLOYÉ + ═══════════════════════════════════════════════ + + ✅ Serveurs DNS configurĂ©s: + {% for host in groups['dns_servers'] %} + - {{ host }} ({{ hostvars[host]['dns_role'] | upper }}) + {% endfor %} + + 🌐 Zone primaire: {{ hostvars[groups['dns_masters'][0]]['member']['domain_primary'] }} + + 📋 Prochaines Ă©tapes: + + 1. Tester rĂ©solution DNS: + dig @{{ hostvars[groups['dns_masters'][0]]['ansible_host'] }} {{ hostvars[groups['dns_masters'][0]]['member']['domain_primary'] }} SOA + + 2. Exporter DS records pour DNSSEC: + ssh {{ hostvars[groups['dns_masters'][0]]['ansible_user'] }}@{{ hostvars[groups['dns_masters'][0]]['ansible_host'] }} + sudo pdnsutil show-zone {{ hostvars[groups['dns_masters'][0]]['member']['domain_primary'] }} | grep DS + + 3. Ajouter DS records chez votre registraire de domaine + + 4. Valider DNSSEC: + dig +dnssec {{ hostvars[groups['dns_masters'][0]]['member']['domain_primary'] }} @8.8.8.8 + + 5. Mettre Ă  jour Registraire YAML: + registraire/membres/m001-chezlepro.yml + + 6. Passer Ă  Phase 2 (Keycloak + Forgejo) + + ═══════════════════════════════════════════════ + tags: always diff --git a/ansible/playbooks/phase2-deploy.yml b/ansible/playbooks/phase2-deploy.yml new file mode 100644 index 0000000..459eec3 --- /dev/null +++ b/ansible/playbooks/phase2-deploy.yml @@ -0,0 +1,178 @@ +# Alliance BorĂ©ale - Playbook Phase 2 +# DĂ©ploiement IdentitĂ© & Forge (Couches C3-C4) +# Membre: Chezlepro Inc. +# Date: 2025-10-31 + +--- +- name: "đŸŒČ Phase 2 - PrĂ©paration VMs (Common)" + hosts: phase2_servers + become: true + gather_facts: true + + pre_tasks: + - name: "🔍 VĂ©rifier connectivitĂ© SSH" + ansible.builtin.ping: + tags: always + + - name: "📋 Afficher informations hĂŽte" + ansible.builtin.debug: + msg: | + HĂŽte: {{ inventory_hostname }} + IP: {{ ansible_host }} + VMID: {{ vmid | default('N/A') }} + tags: always + + roles: + - role: common + tags: common + + post_tasks: + - name: "✅ Common terminĂ©" + ansible.builtin.debug: + msg: "✅ Hardening complĂ©tĂ© sur {{ inventory_hostname }}" + +# ============================================ +- name: "🔑 Phase 2 - Keycloak (IdP)" + hosts: idp_servers + become: true + gather_facts: true + + roles: + - role: postgresql + tags: postgresql + + - role: keycloak + tags: keycloak + + post_tasks: + - name: "✅ Keycloak terminĂ©" + ansible.builtin.debug: + msg: | + ✅ Keycloak dĂ©ployĂ© sur {{ inventory_hostname }} + URL: https://{{ keycloak.hostname }} + Admin: {{ keycloak.admin_user }} + + ⚠ IMPORTANT: Note client secret for Forgejo + tags: always + +# ============================================ +- name: "🔹 Phase 2 - Forgejo (Forge)" + hosts: forge_servers + become: true + gather_facts: true + + roles: + - role: postgresql + tags: postgresql + + - role: forgejo + tags: forgejo + + post_tasks: + - name: "✅ Forgejo terminĂ©" + ansible.builtin.debug: + msg: | + ✅ Forgejo dĂ©ployĂ© sur {{ inventory_hostname }} + URL: https://{{ forgejo.hostname }} + SSH: git@{{ forgejo.hostname }}:{{ forgejo.ssh_port }} + Admin: {{ forgejo.admin.username }} + tags: always + +# ============================================ +- name: "đŸ§Ș Phase 2 - Tests d'intĂ©gration" + hosts: localhost + gather_facts: false + + tasks: + - name: "đŸ§Ș Test Keycloak availability" + ansible.builtin.uri: + url: "https://{{ hostvars[groups['idp_servers'][0]]['keycloak']['hostname'] }}/realms/alliance-boreale" + validate_certs: false + status_code: 200 + register: keycloak_test + delegate_to: localhost + changed_when: false + + - name: "✅ Keycloak OK" + ansible.builtin.debug: + msg: "✅ Keycloak rĂ©pond correctement" + when: keycloak_test.status == 200 + + - name: "đŸ§Ș Test Forgejo availability" + ansible.builtin.uri: + url: "https://{{ hostvars[groups['forge_servers'][0]]['forgejo']['hostname'] }}" + validate_certs: false + status_code: 200 + register: forgejo_test + delegate_to: localhost + changed_when: false + + - name: "✅ Forgejo OK" + ansible.builtin.debug: + msg: "✅ Forgejo rĂ©pond correctement" + when: forgejo_test.status == 200 + +# ============================================ +- name: "📋 Phase 2 - RĂ©capitulatif" + hosts: localhost + gather_facts: false + + tasks: + - name: "🎉 Phase 2 terminĂ©e avec succĂšs!" + ansible.builtin.debug: + msg: | + + ═══════════════════════════════════════════════ + đŸŒČ PHASE 2 - IDENTITÉ & FORGE DÉPLOYÉES + ═══════════════════════════════════════════════ + + ✅ Services dĂ©ployĂ©s: + + 🔑 KEYCLOAK (SSO) + URL: https://{{ hostvars[groups['idp_servers'][0]]['keycloak']['hostname'] }} + Admin: {{ hostvars[groups['idp_servers'][0]]['keycloak']['admin_user'] }} + Realm: alliance-boreale + + 🔹 FORGEJO (Forge Git) + URL: https://{{ hostvars[groups['forge_servers'][0]]['forgejo']['hostname'] }} + SSH: git@{{ hostvars[groups['forge_servers'][0]]['forgejo']['hostname'] }}:{{ hostvars[groups['forge_servers'][0]]['forgejo']['ssh_port'] }} + Admin: {{ hostvars[groups['forge_servers'][0]]['forgejo']['admin_username'] }} + + 📋 Configuration SSO: + + 1. Se connecter Ă  Keycloak: + https://{{ hostvars[groups['idp_servers'][0]]['keycloak']['hostname'] }} + + 2. Aller dans Realm "alliance-boreale" + + 3. Clients → "forgejo" → Credentials + Copier le "Client Secret" + + 4. Ajouter ce secret dans vault: + ansible-vault edit vault/production.yml + vault_keycloak_forgejo_client_secret: "" + + 5. RedĂ©ployer Forgejo: + ansible-playbook playbooks/phase2-deploy.yml --tags forgejo + + 📋 Premiers pas: + + 1. CrĂ©er utilisateurs dans Keycloak + + 2. Se connecter Ă  Forgejo via SSO + + 3. CrĂ©er organisations/repos: + - alliance-boreale/ansible-infrastructure + - alliance-boreale/documentation + - alliance-boreale/registraire + + 4. Pousser ce dĂ©pĂŽt Ansible vers Forgejo: + git remote add origin git@{{ hostvars[groups['forge_servers'][0]]['forgejo']['hostname'] }}:{{ hostvars[groups['forge_servers'][0]]['forgejo']['ssh_port'] }}/alliance-boreale/ansible-infrastructure.git + git push -u origin main + + 5. Configurer CI/CD (Forgejo Actions) + + 6. Passer Ă  Phase 3 (FastAPI Pivot) + + ═══════════════════════════════════════════════ + tags: always diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml new file mode 100644 index 0000000..3a768c9 --- /dev/null +++ b/ansible/roles/common/defaults/main.yml @@ -0,0 +1,47 @@ +# Alliance BorĂ©ale - RĂŽle Common - Default Variables +# Date: 2025-10-31 + +--- +# Ces valeurs sont Ă©crasĂ©es par group_vars/all.yml +# Elles servent de fallback si group_vars absent + +base_packages: + - vim + - htop + - curl + - git + - python3 + +system: + timezone: America/Toronto + locale: en_CA.UTF-8 + +ntp: + enabled: true + servers: + - 0.ca.pool.ntp.org + - 1.ca.pool.ntp.org + +ssh: + port: 22 + permit_root_login: false + password_authentication: false + pubkey_authentication: true + +users: + ansible: + name: ansible + groups: [sudo] + shell: /bin/bash + sudo_nopasswd: true + +security: + firewall: nftables + fail2ban_enabled: true + unattended_upgrades: true + +firewall: + default_policy: drop + +network: + internal_subnet: 10.0.0.0/8 diff --git a/ansible/roles/common/handlers/main.yml b/ansible/roles/common/handlers/main.yml new file mode 100644 index 0000000..2e27c4d --- /dev/null +++ b/ansible/roles/common/handlers/main.yml @@ -0,0 +1,23 @@ +# Alliance BorĂ©ale - RĂŽle Common - Handlers +# Date: 2025-10-31 + +--- +- name: restart chrony + ansible.builtin.systemd: + name: chrony + state: restarted + +- name: restart sshd + ansible.builtin.systemd: + name: sshd + state: restarted + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded + +- name: restart fail2ban + ansible.builtin.systemd: + name: fail2ban + state: restarted diff --git a/ansible/roles/common/tasks/logging.yml b/ansible/roles/common/tasks/logging.yml new file mode 100644 index 0000000..04bb20b --- /dev/null +++ b/ansible/roles/common/tasks/logging.yml @@ -0,0 +1,22 @@ +# Alliance BorĂ©ale - RĂŽle Common - Logging Configuration +# Date: 2025-10-31 + +--- +- name: "📊 Ensure rsyslog is installed" + ansible.builtin.apt: + name: rsyslog + state: present + +- name: "📊 Configure log rotation" + ansible.builtin.template: + src: logrotate-ansible.j2 + dest: /etc/logrotate.d/ansible + owner: root + group: root + mode: '0644' + +- name: "✅ Start and enable rsyslog" + ansible.builtin.systemd: + name: rsyslog + state: started + enabled: true diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..e063ab2 --- /dev/null +++ b/ansible/roles/common/tasks/main.yml @@ -0,0 +1,72 @@ +# Alliance BorĂ©ale - RĂŽle Common +# Couche: C1 (Physique) +# Objectif: Hardening post-crĂ©ation VM +# Date: 2025-10-31 + +--- +- name: "🔍 Gather facts" + ansible.builtin.setup: + tags: always + +- name: "📩 Update apt cache" + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + tags: packages + +- name: "📩 Install base packages" + ansible.builtin.apt: + name: "{{ base_packages }}" + state: present + tags: packages + +- name: "🕐 Configure timezone" + community.general.timezone: + name: "{{ system.timezone }}" + tags: system + +- name: "🕐 Configure NTP (chrony)" + ansible.builtin.import_tasks: ntp.yml + when: ntp.enabled | default(true) + tags: ntp + +- name: "đŸ‘€ Create ansible user" + ansible.builtin.user: + name: "{{ users.ansible.name }}" + groups: "{{ users.ansible.groups }}" + shell: "{{ users.ansible.shell }}" + create_home: true + state: present + tags: users + +- name: "đŸ‘€ Configure sudo for ansible user" + ansible.builtin.lineinfile: + path: /etc/sudoers.d/ansible + line: "{{ users.ansible.name }} ALL=(ALL) NOPASSWD:ALL" + create: true + mode: '0440' + validate: 'visudo -cf %s' + when: users.ansible.sudo_nopasswd | default(true) + tags: users + +- name: "🔐 Configure SSH" + ansible.builtin.import_tasks: ssh.yml + tags: ssh + +- name: "đŸ”„ Configure firewall (nftables)" + ansible.builtin.import_tasks: nftables.yml + when: security.firewall == 'nftables' + tags: firewall + +- name: "📊 Configure logging" + ansible.builtin.import_tasks: logging.yml + tags: logging + +- name: "🔒 Security hardening" + ansible.builtin.import_tasks: security.yml + tags: security + +- name: "✅ Common role completed" + ansible.builtin.debug: + msg: "✅ VM {{ inventory_hostname }} hardened successfully" + tags: always diff --git a/ansible/roles/common/tasks/nftables.yml b/ansible/roles/common/tasks/nftables.yml new file mode 100644 index 0000000..4191c07 --- /dev/null +++ b/ansible/roles/common/tasks/nftables.yml @@ -0,0 +1,35 @@ +# Alliance BorĂ©ale - RĂŽle Common - nftables Configuration +# Date: 2025-10-31 + +--- +- name: "📩 Install nftables" + ansible.builtin.apt: + name: + - nftables + - python3-nftables + state: present + +- name: "đŸ”„ Deploy nftables base rules" + ansible.builtin.template: + src: nftables.conf.j2 + dest: /etc/nftables.conf + owner: root + group: root + mode: '0644' + notify: reload nftables + +- name: "✅ Enable and start nftables" + ansible.builtin.systemd: + name: nftables + state: started + enabled: true + +- name: "đŸš« Ensure iptables is not running" + ansible.builtin.systemd: + name: "{{ item }}" + state: stopped + enabled: false + loop: + - iptables + - ip6tables + failed_when: false diff --git a/ansible/roles/common/tasks/ntp.yml b/ansible/roles/common/tasks/ntp.yml new file mode 100644 index 0000000..0b7d8d1 --- /dev/null +++ b/ansible/roles/common/tasks/ntp.yml @@ -0,0 +1,23 @@ +# Alliance BorĂ©ale - RĂŽle Common - NTP Configuration +# Date: 2025-10-31 + +--- +- name: "📩 Install chrony" + ansible.builtin.apt: + name: chrony + state: present + +- name: "⚙ Configure chrony" + ansible.builtin.template: + src: chrony.conf.j2 + dest: /etc/chrony/chrony.conf + owner: root + group: root + mode: '0644' + notify: restart chrony + +- name: "✅ Start and enable chrony" + ansible.builtin.systemd: + name: chrony + state: started + enabled: true diff --git a/ansible/roles/common/tasks/security.yml b/ansible/roles/common/tasks/security.yml new file mode 100644 index 0000000..caad57c --- /dev/null +++ b/ansible/roles/common/tasks/security.yml @@ -0,0 +1,77 @@ +# Alliance BorĂ©ale - RĂŽle Common - Security Hardening +# Date: 2025-10-31 + +--- +- name: "🔒 Disable unnecessary services" + ansible.builtin.systemd: + name: "{{ item }}" + state: stopped + enabled: false + loop: + - bluetooth + - cups + failed_when: false + +- name: "🔒 Configure sysctl security parameters" + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + state: present + reload: true + loop: + - { key: 'net.ipv4.conf.all.rp_filter', value: '1' } + - { key: 'net.ipv4.conf.default.rp_filter', value: '1' } + - { key: 'net.ipv4.icmp_echo_ignore_broadcasts', value: '1' } + - { key: 'net.ipv4.conf.all.accept_source_route', value: '0' } + - { key: 'net.ipv4.conf.default.accept_source_route', value: '0' } + - { key: 'net.ipv4.conf.all.send_redirects', value: '0' } + - { key: 'net.ipv4.conf.default.send_redirects', value: '0' } + - { key: 'net.ipv4.tcp_syncookies', value: '1' } + - { key: 'net.ipv4.conf.all.log_martians', value: '1' } + - { key: 'kernel.randomize_va_space', value: '2' } + +- name: "🔒 Install fail2ban" + ansible.builtin.apt: + name: fail2ban + state: present + when: security.fail2ban_enabled | default(true) + +- name: "🔒 Configure fail2ban" + ansible.builtin.copy: + dest: /etc/fail2ban/jail.local + content: | + [DEFAULT] + bantime = 3600 + findtime = 600 + maxretry = 5 + + [sshd] + enabled = true + owner: root + group: root + mode: '0644' + when: security.fail2ban_enabled | default(true) + notify: restart fail2ban + +- name: "🔒 Enable unattended upgrades" + ansible.builtin.apt: + name: unattended-upgrades + state: present + when: security.unattended_upgrades | default(true) + +- name: "🔒 Configure automatic security updates" + ansible.builtin.copy: + dest: /etc/apt/apt.conf.d/50unattended-upgrades + content: | + Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}-security"; + }; + Unattended-Upgrade::AutoFixInterruptedDpkg "true"; + Unattended-Upgrade::MinimalSteps "true"; + Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; + Unattended-Upgrade::Remove-Unused-Dependencies "true"; + Unattended-Upgrade::Automatic-Reboot "false"; + owner: root + group: root + mode: '0644' + when: security.unattended_upgrades | default(true) diff --git a/ansible/roles/common/tasks/ssh.yml b/ansible/roles/common/tasks/ssh.yml new file mode 100644 index 0000000..a0bf1a7 --- /dev/null +++ b/ansible/roles/common/tasks/ssh.yml @@ -0,0 +1,32 @@ +# Alliance BorĂ©ale - RĂŽle Common - SSH Configuration +# Date: 2025-10-31 + +--- +- name: "📩 Ensure OpenSSH server is installed" + ansible.builtin.apt: + name: openssh-server + state: present + +- name: "⚙ Configure SSH daemon" + ansible.builtin.template: + src: sshd_config.j2 + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: '0644' + validate: '/usr/sbin/sshd -t -f %s' + notify: restart sshd + +- name: "🔑 Add authorized keys for ansible user" + ansible.posix.authorized_key: + user: "{{ users.ansible.name }}" + key: "{{ item }}" + state: present + loop: "{{ ssh.authorized_keys }}" + when: ssh.authorized_keys is defined + +- name: "✅ Start and enable SSH" + ansible.builtin.systemd: + name: sshd + state: started + enabled: true diff --git a/ansible/roles/common/templates/chrony.conf.j2 b/ansible/roles/common/templates/chrony.conf.j2 new file mode 100644 index 0000000..223caba --- /dev/null +++ b/ansible/roles/common/templates/chrony.conf.j2 @@ -0,0 +1,25 @@ +# Alliance BorĂ©ale - Chrony Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# NTP Servers +{% for server in ntp.servers %} +pool {{ server }} iburst +{% endfor %} + +# Record rate at which system clock gains/losses time +driftfile /var/lib/chrony/chrony.drift + +# Allow system to step clock if offset is large +makestep 1.0 3 + +# Enable kernel synchronization of RTC +rtcsync + +# Serve time even if not synchronized +local stratum 10 + +# Log files +logdir /var/log/chrony +log measurements statistics tracking diff --git a/ansible/roles/common/templates/nftables.conf.j2 b/ansible/roles/common/templates/nftables.conf.j2 new file mode 100644 index 0000000..33f420e --- /dev/null +++ b/ansible/roles/common/templates/nftables.conf.j2 @@ -0,0 +1,74 @@ +#!/usr/sbin/nft -f +# Alliance BorĂ©ale - nftables Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# Flush all rules +flush ruleset + +# Main table for filtering +table inet filter { + # Input chain (incoming traffic) + chain input { + type filter hook input priority filter; policy {{ firewall.default_policy | default('drop') }}; + + # Allow established/related connections + ct state established,related accept + + # Allow loopback + iif lo accept + + # Drop invalid packets + ct state invalid drop + + # Allow ICMP (ping) + ip protocol icmp icmp type { echo-request, echo-reply } limit rate 10/second accept + ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, nd-neighbor-solicit, nd-neighbor-advert } limit rate 10/second accept + + # Allow SSH + tcp dport {{ ssh.port | default(22) }} ct state new limit rate 5/minute accept + + # Allow DNS (if DNS server) +{% if 'dns_servers' in group_names %} + udp dport 53 accept + tcp dport 53 accept +{% endif %} + + # Allow PowerDNS API (internal only) +{% if 'dns_servers' in group_names %} + ip saddr {{ network.internal_subnet | default('10.0.0.0/8') }} tcp dport 8081 accept +{% endif %} + + # Log dropped packets (rate limited) + limit rate 5/minute log prefix "[nftables INPUT DROP] " level info + + # Default: drop + } + + # Forward chain (routed traffic) + chain forward { + type filter hook forward priority filter; policy drop; + + # Log forwarded packets (if any) + # log prefix "[nftables FORWARD] " level info + } + + # Output chain (outgoing traffic) + chain output { + type filter hook output priority filter; policy accept; + + # Allow all outgoing by default + } +} + +# NAT table (if needed in future) +# table ip nat { +# chain prerouting { +# type nat hook prerouting priority dstnat; policy accept; +# } +# +# chain postrouting { +# type nat hook postrouting priority srcnat; policy accept; +# } +# } diff --git a/ansible/roles/common/templates/sshd.conf.j2 b/ansible/roles/common/templates/sshd.conf.j2 new file mode 100644 index 0000000..c0a6683 --- /dev/null +++ b/ansible/roles/common/templates/sshd.conf.j2 @@ -0,0 +1,53 @@ +# Alliance BorĂ©ale - SSH Daemon Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# Network +Port {{ ssh.port | default(22) }} +AddressFamily inet +ListenAddress 0.0.0.0 + +# Authentication +PermitRootLogin {{ 'yes' if ssh.permit_root_login | default(false) else 'no' }} +PubkeyAuthentication {{ 'yes' if ssh.pubkey_authentication | default(true) else 'no' }} +PasswordAuthentication {{ 'yes' if ssh.password_authentication | default(false) else 'no' }} +PermitEmptyPasswords no +ChallengeResponseAuthentication no + +# Security +Protocol 2 +HostKey /etc/ssh/ssh_host_ed25519_key +HostKey /etc/ssh/ssh_host_rsa_key + +# Ciphers and algorithms +Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com +MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com +KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 + +# Login grace time +LoginGraceTime 60 +MaxAuthTries 3 +MaxSessions 10 + +# Forwarding +X11Forwarding no +AllowTcpForwarding yes +PermitTunnel no + +# Keep alive +ClientAliveInterval 300 +ClientAliveCountMax 2 + +# Logging +SyslogFacility AUTH +LogLevel VERBOSE + +# Subsystems +Subsystem sftp /usr/lib/openssh/sftp-server + +# Allow specific users +AllowUsers {{ users.ansible.name | default('ansible') }} + +# Banner (optional) +# Banner /etc/ssh/banner diff --git a/ansible/roles/forgejo/handlers/main.yml b/ansible/roles/forgejo/handlers/main.yml new file mode 100644 index 0000000..c7ae8d1 --- /dev/null +++ b/ansible/roles/forgejo/handlers/main.yml @@ -0,0 +1,18 @@ +# Alliance BorĂ©ale - Forgejo Handlers +# Date: 2025-10-31 + +--- +- name: restart forgejo + ansible.builtin.systemd: + name: forgejo + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/ansible/roles/forgejo/tasks/database.yml b/ansible/roles/forgejo/tasks/database.yml new file mode 100644 index 0000000..bd5e6b7 --- /dev/null +++ b/ansible/roles/forgejo/tasks/database.yml @@ -0,0 +1,98 @@ +# Alliance BorĂ©ale - Forgejo Tasks (database, nginx, firewall) +# Date: 2025-10-31 + +--- +# ========================================== +# DATABASE.YML +# ========================================== +- name: "đŸ—„ïž Ensure PostgreSQL is installed" + ansible.builtin.include_role: + name: postgresql + when: "'postgresql_servers' in group_names" + +- name: "đŸ—„ïž Create Forgejo database" + community.postgresql.postgresql_db: + name: "{{ forgejo.db.name }}" + encoding: UTF8 + state: present + become: true + become_user: postgres + +- name: "đŸ‘€ Create Forgejo database user" + community.postgresql.postgresql_user: + name: "{{ forgejo.db.user }}" + password: "{{ forgejo.db.password }}" + state: present + become: true + become_user: postgres + no_log: true + +- name: "🔐 Grant privileges to Forgejo user" + community.postgresql.postgresql_privs: + database: "{{ forgejo.db.name }}" + roles: "{{ forgejo.db.user }}" + type: database + privs: ALL + state: present + become: true + become_user: postgres + +# ========================================== +# NGINX.YML +# ========================================== +- name: "📩 Ensure Nginx is installed" + ansible.builtin.apt: + name: nginx + state: present + +- name: "🔐 Generate self-signed SSL certificate" + ansible.builtin.command: + cmd: > + openssl req -x509 -nodes -days 365 -newkey rsa:2048 + -keyout /etc/ssl/private/{{ forgejo.hostname }}.key + -out /etc/ssl/certs/{{ forgejo.hostname }}.crt + -subj "/C=CA/ST=Quebec/L=Montreal/O=Chezlepro/CN={{ forgejo.hostname }}" + creates: "/etc/ssl/certs/{{ forgejo.hostname }}.crt" + when: nginx.ssl.cert_source == 'self-signed' + +- name: "⚙ Configure Nginx for Forgejo" + ansible.builtin.template: + src: nginx-forgejo.conf.j2 + dest: /etc/nginx/sites-available/forgejo + owner: root + group: root + mode: '0644' + notify: reload nginx + +- name: "🔗 Enable Nginx site" + ansible.builtin.file: + src: /etc/nginx/sites-available/forgejo + dest: /etc/nginx/sites-enabled/forgejo + state: link + notify: reload nginx + +- name: "✅ Start and enable Nginx" + ansible.builtin.systemd: + name: nginx + state: started + enabled: true + +# ========================================== +# FIREWALL.YML +# ========================================== +- name: "đŸ”„ Configure firewall for Forgejo" + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED - Forgejo" + insertbefore: "# Log dropped packets" + block: | + # Forgejo - Internal HTTP (backend only) + ip saddr 127.0.0.1 tcp dport {{ forgejo.http_port }} accept comment "Forgejo HTTP" + + # Forgejo - SSH (Git over SSH) + tcp dport {{ forgejo.ssh_port }} accept comment "Forgejo SSH" + + # Nginx - HTTPS (public) + tcp dport 443 accept comment "HTTPS (Forgejo via Nginx)" + tcp dport 80 accept comment "HTTP redirect" + notify: reload nftables diff --git a/ansible/roles/forgejo/tasks/main.yml b/ansible/roles/forgejo/tasks/main.yml new file mode 100644 index 0000000..3a0a347 --- /dev/null +++ b/ansible/roles/forgejo/tasks/main.yml @@ -0,0 +1,131 @@ +# Alliance BorĂ©ale - RĂŽle Forgejo +# Couche: C4 (Forge & Mutualisation) +# Objectif: DĂ©ployer Forge Git avec SSO Keycloak +# Date: 2025-10-31 + +--- +- name: "📩 Install dependencies" + ansible.builtin.apt: + name: + - git + - nginx + - openssl + - python3-psycopg2 + state: present + update_cache: true + tags: forgejo + +- name: "đŸ‘€ Create git user" + ansible.builtin.user: + name: "{{ forgejo.user }}" + system: true + shell: /bin/bash + home: "{{ forgejo.data_dir }}" + create_home: true + tags: forgejo + +- name: "📁 Create forgejo directories" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0755' + loop: + - "{{ forgejo.install_dir }}" + - "{{ forgejo.custom_dir }}" + - "{{ forgejo.log_dir }}" + - "{{ forgejo.data_dir }}/git" + - "{{ forgejo.data_dir }}/repositories" + tags: forgejo + +- name: "đŸ“„ Download Forgejo binary" + ansible.builtin.get_url: + url: "https://codeberg.org/forgejo/forgejo/releases/download/v{{ forgejo.version }}/forgejo-{{ forgejo.version }}-linux-amd64" + dest: "{{ forgejo.install_dir }}/forgejo" + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0755' + tags: forgejo + +- name: "đŸ—„ïž Configure PostgreSQL for Forgejo" + ansible.builtin.import_tasks: database.yml + tags: forgejo + +- name: "⚙ Configure Forgejo" + ansible.builtin.template: + src: app.ini.j2 + dest: "{{ forgejo.custom_dir }}/app.ini" + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0640' + notify: restart forgejo + tags: forgejo + +- name: "⚙ Create systemd service" + ansible.builtin.template: + src: forgejo.service.j2 + dest: /etc/systemd/system/forgejo.service + owner: root + group: root + mode: '0644' + notify: restart forgejo + tags: forgejo + +- name: "✅ Start and enable Forgejo" + ansible.builtin.systemd: + name: forgejo + state: started + enabled: true + daemon_reload: true + tags: forgejo + +- name: "⏳ Wait for Forgejo to be ready" + ansible.builtin.wait_for: + port: "{{ forgejo.http_port }}" + host: 127.0.0.1 + timeout: 60 + tags: forgejo + +- name: "🌐 Configure Nginx reverse proxy" + ansible.builtin.import_tasks: nginx.yml + tags: forgejo + +- name: "👑 Create admin user" + ansible.builtin.command: + cmd: > + {{ forgejo.install_dir }}/forgejo admin user create + --username {{ forgejo.admin.username }} + --password {{ forgejo.admin.password }} + --email {{ forgejo.admin.email }} + --admin + --config {{ forgejo.custom_dir }}/app.ini + creates: "{{ forgejo.data_dir }}/.admin_created" + become: true + become_user: "{{ forgejo.user }}" + register: admin_created + no_log: true + tags: forgejo + +- name: "✅ Mark admin as created" + ansible.builtin.file: + path: "{{ forgejo.data_dir }}/.admin_created" + state: touch + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0644' + when: admin_created.changed + tags: forgejo + +- name: "đŸ”„ Configure firewall" + ansible.builtin.import_tasks: firewall.yml + tags: forgejo + +- name: "✅ Forgejo role completed" + ansible.builtin.debug: + msg: | + ✅ Forgejo deployed on {{ inventory_hostname }} + URL: https://{{ forgejo.hostname }} + SSH: git@{{ forgejo.hostname }}:{{ forgejo.ssh_port }} + Admin: {{ forgejo.admin.username }} + tags: forgejo diff --git a/ansible/roles/forgejo/templates/app.ini.j2 b/ansible/roles/forgejo/templates/app.ini.j2 new file mode 100644 index 0000000..c266d64 --- /dev/null +++ b/ansible/roles/forgejo/templates/app.ini.j2 @@ -0,0 +1,100 @@ +# Alliance BorĂ©ale - Forgejo Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +APP_NAME = L'Alliance BorĂ©ale - Forge Git +RUN_MODE = prod +RUN_USER = {{ forgejo.user }} + +[server] +DOMAIN = {{ forgejo.hostname }} +HTTP_PORT = {{ forgejo.http_port }} +ROOT_URL = https://{{ forgejo.hostname }}/ +DISABLE_SSH = false +SSH_DOMAIN = {{ forgejo.hostname }} +SSH_PORT = {{ forgejo.ssh_port }} +SSH_LISTEN_PORT = {{ forgejo.ssh_port }} +START_SSH_SERVER = true +LFS_START_SERVER = {{ forgejo.features.enable_lfs | lower }} +OFFLINE_MODE = false + +[database] +DB_TYPE = {{ forgejo.db.type }} +HOST = {{ forgejo.db.host }} +NAME = {{ forgejo.db.name }} +USER = {{ forgejo.db.user }} +PASSWD = {{ forgejo.db.password }} +SSL_MODE = {{ forgejo.db.ssl_mode }} +CHARSET = utf8mb4 +LOG_SQL = false + +[repository] +ROOT = {{ forgejo.data_dir }}/repositories +DEFAULT_BRANCH = main +PREFERRED_LICENSES = MIT,Apache-2.0,GPL-3.0 +DISABLE_HTTP_GIT = false +ACCESS_CONTROL_ALLOW_ORIGIN = https://{{ forgejo.hostname }} + +[security] +INSTALL_LOCK = true +SECRET_KEY = {{ forgejo.secret_key }} +INTERNAL_TOKEN = {{ forgejo.internal_token }} +PASSWORD_HASH_ALGO = argon2 +MIN_PASSWORD_LENGTH = 12 + +[service] +DISABLE_REGISTRATION = {{ forgejo.features.disable_registration | lower }} +REQUIRE_SIGNIN_VIEW = {{ forgejo.features.require_signin | lower }} +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DEFAULT_KEEP_EMAIL_PRIVATE = true +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +DEFAULT_ENABLE_TIMETRACKING = true +NO_REPLY_ADDRESS = noreply@{{ forgejo.domain }} + +[mailer] +ENABLED = false + +[session] +PROVIDER = file +PROVIDER_CONFIG = {{ forgejo.data_dir }}/sessions + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[log] +MODE = console, file +LEVEL = Info +ROOT_PATH = {{ forgejo.log_dir }} + +[git] +MAX_GIT_DIFF_LINES = 10000 +MAX_GIT_DIFF_LINE_CHARACTERS = 5000 +MAX_GIT_DIFF_FILES = 100 + +[actions] +ENABLED = {{ forgejo.features.enable_actions | lower }} + +[packages] +ENABLED = {{ forgejo.features.enable_packages | lower }} + +{% if forgejo.oauth.enabled %} +# OAuth2 / OpenID Connect (Keycloak) +[oauth2_client] +REGISTER_EMAIL_CONFIRM = false +ENABLE_AUTO_REGISTRATION = true +USERNAME = preferred_username +UPDATE_AVATAR = true +ACCOUNT_LINKING = auto + +{% for provider in forgejo.oauth.providers %} +[oauth2.{{ provider.name }}] +ENABLED = true +PROVIDER = {{ provider.provider }} +CLIENT_ID = {{ provider.client_id }} +CLIENT_SECRET = {{ provider.client_secret }} +OPENID_CONNECT_AUTO_DISCOVERY_URL = {{ provider.openid_connect_auto_discovery_url }} +{% endfor %} +{% endif %} diff --git a/ansible/roles/forgejo/templates/forgejo.service.j2 b/ansible/roles/forgejo/templates/forgejo.service.j2 new file mode 100644 index 0000000..62619f3 --- /dev/null +++ b/ansible/roles/forgejo/templates/forgejo.service.j2 @@ -0,0 +1,91 @@ +# Alliance BorĂ©ale - Forgejo Systemd Service +# Generated by Ansible + +[Unit] +Description=Forgejo Git Service +After=network.target postgresql.service +Wants=postgresql.service + +[Service] +Type=simple +User={{ forgejo.user }} +Group={{ forgejo.group }} +WorkingDirectory={{ forgejo.data_dir }} +ExecStart={{ forgejo.install_dir }}/forgejo web --config {{ forgejo.custom_dir }}/app.ini +Restart=on-failure +RestartSec=10 + +# Security +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths={{ forgejo.data_dir }} {{ forgejo.log_dir }} + +# Limits +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target + +---SEPARATOR--- + +# Alliance BorĂ©ale - Nginx Configuration for Forgejo +# Generated by Ansible + +upstream forgejo_backend { + server 127.0.0.1:{{ forgejo.http_port }}; +} + +server { + listen 80; + server_name {{ forgejo.hostname }}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ forgejo.hostname }}; + + ssl_certificate /etc/ssl/certs/{{ forgejo.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ forgejo.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + # Large uploads (Git LFS) + client_max_body_size 512M; + + location / { + proxy_pass http://forgejo_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support (for Actions) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} + +---SEPARATOR--- + +# Alliance BorĂ©ale - Forgejo Handlers +# Date: 2025-10-31 + +--- +- name: restart forgejo + ansible.builtin.systemd: + name: forgejo + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 b/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 new file mode 100644 index 0000000..ed2f328 --- /dev/null +++ b/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 @@ -0,0 +1,36 @@ +# Alliance BorĂ©ale - Nginx Forgejo +# Generated by Ansible + +upstream forgejo_backend { + server 127.0.0.1:{{ forgejo.http_port }}; +} + +server { + listen 80; + server_name {{ forgejo.hostname }}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ forgejo.hostname }}; + + ssl_certificate /etc/ssl/certs/{{ forgejo.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ forgejo.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + client_max_body_size 512M; + + location / { + proxy_pass http://forgejo_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/ansible/roles/keycloak/handlers/main.yml b/ansible/roles/keycloak/handlers/main.yml new file mode 100644 index 0000000..bbdac89 --- /dev/null +++ b/ansible/roles/keycloak/handlers/main.yml @@ -0,0 +1,18 @@ +# Alliance BorĂ©ale - Keycloak Handlers +# Date: 2025-10-31 + +--- +- name: restart keycloak + ansible.builtin.systemd: + name: keycloak + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/ansible/roles/keycloak/tasks/database.yml b/ansible/roles/keycloak/tasks/database.yml new file mode 100644 index 0000000..f7bc000 --- /dev/null +++ b/ansible/roles/keycloak/tasks/database.yml @@ -0,0 +1,140 @@ +# Alliance BorĂ©ale - Keycloak Tasks (database, nginx, realms, firewall) +# Date: 2025-10-31 + +--- +# ========================================== +# DATABASE.YML +# ========================================== +- name: "đŸ—„ïž Ensure PostgreSQL is installed" + ansible.builtin.include_role: + name: postgresql + when: "'postgresql_servers' in group_names" + +- name: "đŸ—„ïž Create Keycloak database" + community.postgresql.postgresql_db: + name: "{{ keycloak.db.database }}" + encoding: UTF8 + state: present + become: true + become_user: postgres + +- name: "đŸ‘€ Create Keycloak database user" + community.postgresql.postgresql_user: + name: "{{ keycloak.db.username }}" + password: "{{ keycloak.db.password }}" + state: present + become: true + become_user: postgres + no_log: true + +- name: "🔐 Grant privileges to Keycloak user" + community.postgresql.postgresql_privs: + database: "{{ keycloak.db.database }}" + roles: "{{ keycloak.db.username }}" + type: database + privs: ALL + state: present + become: true + become_user: postgres + +# ========================================== +# NGINX.YML +# ========================================== +- name: "📩 Ensure Nginx is installed" + ansible.builtin.apt: + name: nginx + state: present + +- name: "🔐 Generate self-signed SSL certificate" + ansible.builtin.command: + cmd: > + openssl req -x509 -nodes -days 365 -newkey rsa:2048 + -keyout /etc/ssl/private/{{ keycloak.hostname }}.key + -out /etc/ssl/certs/{{ keycloak.hostname }}.crt + -subj "/C=CA/ST=Quebec/L=Montreal/O=Chezlepro/CN={{ keycloak.hostname }}" + creates: "/etc/ssl/certs/{{ keycloak.hostname }}.crt" + when: nginx.ssl.cert_source == 'self-signed' + +- name: "⚙ Configure Nginx for Keycloak" + ansible.builtin.template: + src: nginx-keycloak.conf.j2 + dest: /etc/nginx/sites-available/keycloak + owner: root + group: root + mode: '0644' + notify: reload nginx + +- name: "🔗 Enable Nginx site" + ansible.builtin.file: + src: /etc/nginx/sites-available/keycloak + dest: /etc/nginx/sites-enabled/keycloak + state: link + notify: reload nginx + +- name: "đŸš« Remove default Nginx site" + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + +- name: "✅ Start and enable Nginx" + ansible.builtin.systemd: + name: nginx + state: started + enabled: true + +# ========================================== +# REALMS.YML +# ========================================== +- name: "🏰 Check if realm exists" + ansible.builtin.uri: + url: "http://127.0.0.1:{{ keycloak.http_port }}/admin/realms/{{ item.name }}" + method: GET + user: "{{ keycloak.admin_user }}" + password: "{{ keycloak.admin_password }}" + force_basic_auth: true + status_code: [200, 404] + loop: "{{ keycloak.realms }}" + register: realm_check + changed_when: false + no_log: true + +- name: "🏰 Create realm configuration file" + ansible.builtin.template: + src: realm-config.json.j2 + dest: "/tmp/realm-{{ item.name }}.json" + owner: keycloak + group: keycloak + mode: '0640' + loop: "{{ keycloak.realms }}" + when: realm_check.results[0].status == 404 + +- name: "🏰 Import realm" + ansible.builtin.command: + cmd: > + /opt/keycloak/bin/kc.sh import + --file /tmp/realm-{{ item.name }}.json + --override false + become: true + become_user: keycloak + loop: "{{ keycloak.realms }}" + when: realm_check.results[0].status == 404 + register: realm_import + changed_when: "'imported' in realm_import.stdout" + +# ========================================== +# FIREWALL.YML +# ========================================== +- name: "đŸ”„ Configure firewall for Keycloak" + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED - Keycloak" + insertbefore: "# Log dropped packets" + block: | + # Keycloak - Internal HTTP (backend only) + ip saddr 127.0.0.1 tcp dport {{ keycloak.http_port }} accept comment "Keycloak HTTP" + + # Nginx - HTTPS (public) + tcp dport 443 accept comment "HTTPS (Keycloak via Nginx)" + tcp dport 80 accept comment "HTTP redirect" + notify: reload nftables diff --git a/ansible/roles/keycloak/tasks/main.yml b/ansible/roles/keycloak/tasks/main.yml new file mode 100644 index 0000000..3c21b71 --- /dev/null +++ b/ansible/roles/keycloak/tasks/main.yml @@ -0,0 +1,140 @@ +# Alliance BorĂ©ale - RĂŽle Keycloak +# Couche: C3 (Gouvernance & Supervision) +# Objectif: DĂ©ployer IdP fĂ©dĂ©rĂ© (SSO) +# Date: 2025-10-31 + +--- +- name: "📩 Install dependencies" + ansible.builtin.apt: + name: + - openjdk-17-jre-headless + - python3-pip + - python3-psycopg2 + - nginx + - openssl + state: present + update_cache: true + tags: keycloak + +- name: "đŸ‘€ Create keycloak user" + ansible.builtin.user: + name: keycloak + system: true + shell: /bin/false + home: /opt/keycloak + create_home: false + tags: keycloak + +- name: "📁 Create keycloak directories" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: keycloak + group: keycloak + mode: '0755' + loop: + - /opt/keycloak + - /var/log/keycloak + tags: keycloak + +- name: "đŸ“„ Download Keycloak" + ansible.builtin.get_url: + url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak.version }}/keycloak-{{ keycloak.version }}.tar.gz" + dest: "/tmp/keycloak-{{ keycloak.version }}.tar.gz" + mode: '0644' + tags: keycloak + +- name: "📩 Extract Keycloak" + ansible.builtin.unarchive: + src: "/tmp/keycloak-{{ keycloak.version }}.tar.gz" + dest: /opt/keycloak + remote_src: true + owner: keycloak + group: keycloak + extra_opts: [--strip-components=1] + creates: /opt/keycloak/bin/kc.sh + tags: keycloak + +- name: "đŸ—„ïž Configure PostgreSQL for Keycloak" + ansible.builtin.import_tasks: database.yml + tags: keycloak + +- name: "⚙ Configure Keycloak" + ansible.builtin.template: + src: keycloak.conf.j2 + dest: /opt/keycloak/conf/keycloak.conf + owner: keycloak + group: keycloak + mode: '0640' + notify: restart keycloak + tags: keycloak + +- name: "🔹 Build Keycloak" + ansible.builtin.command: + cmd: /opt/keycloak/bin/kc.sh build + become: true + become_user: keycloak + args: + creates: /opt/keycloak/lib/quarkus/quarkus-application.dat + tags: keycloak + +- name: "⚙ Create systemd service" + ansible.builtin.template: + src: keycloak.service.j2 + dest: /etc/systemd/system/keycloak.service + owner: root + group: root + mode: '0644' + notify: restart keycloak + tags: keycloak + +- name: "✅ Start and enable Keycloak" + ansible.builtin.systemd: + name: keycloak + state: started + enabled: true + daemon_reload: true + tags: keycloak + +- name: "⏳ Wait for Keycloak to be ready" + ansible.builtin.wait_for: + port: "{{ keycloak.http_port }}" + host: 127.0.0.1 + timeout: 120 + tags: keycloak + +- name: "🌐 Configure Nginx reverse proxy" + ansible.builtin.import_tasks: nginx.yml + tags: keycloak + +- name: "👑 Create admin user" + ansible.builtin.command: + cmd: > + /opt/keycloak/bin/kcadm.sh config credentials + --server http://localhost:{{ keycloak.http_port }} + --realm master + --user {{ keycloak.admin_user }} + --password {{ keycloak.admin_password }} + become: true + become_user: keycloak + register: admin_created + changed_when: false + failed_when: false + no_log: true + tags: keycloak + +- name: "🏰 Configure realms and clients" + ansible.builtin.import_tasks: realms.yml + tags: keycloak + +- name: "đŸ”„ Configure firewall" + ansible.builtin.import_tasks: firewall.yml + tags: keycloak + +- name: "✅ Keycloak role completed" + ansible.builtin.debug: + msg: | + ✅ Keycloak deployed on {{ inventory_hostname }} + URL: https://{{ keycloak.hostname }} + Admin: {{ keycloak.admin_user }} + tags: keycloak diff --git a/ansible/roles/keycloak/templates/keycloak.conf.j2 b/ansible/roles/keycloak/templates/keycloak.conf.j2 new file mode 100644 index 0000000..cddab01 --- /dev/null +++ b/ansible/roles/keycloak/templates/keycloak.conf.j2 @@ -0,0 +1,25 @@ +# Alliance BorĂ©ale - Keycloak Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# Network +hostname={{ keycloak.hostname }} +http-enabled=true +http-port={{ keycloak.http_port }} +http-host=0.0.0.0 +proxy=edge + +# Database +db=postgres +db-url=jdbc:postgresql://{{ keycloak.db.host }}/{{ keycloak.db.database }} +db-username={{ keycloak.db.username }} +db-password={{ keycloak.db.password }} + +# Logging +log-level=INFO +log-console-output=default + +# Health +health-enabled=true +metrics-enabled=true diff --git a/ansible/roles/keycloak/templates/keycloak.service.j2 b/ansible/roles/keycloak/templates/keycloak.service.j2 new file mode 100644 index 0000000..4172619 --- /dev/null +++ b/ansible/roles/keycloak/templates/keycloak.service.j2 @@ -0,0 +1,83 @@ +# Alliance BorĂ©ale - Keycloak Systemd Service +# Generated by Ansible + +[Unit] +Description=Keycloak Identity Provider +After=network.target postgresql.service +Wants=postgresql.service + +[Service] +Type=simple +User=keycloak +Group=keycloak +WorkingDirectory=/opt/keycloak +ExecStart=/opt/keycloak/bin/kc.sh start +Restart=on-failure +RestartSec=10 + +# Security +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/log/keycloak /opt/keycloak/data + +# Limits +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target + +---SEPARATOR--- + +# Alliance BorĂ©ale - Nginx Configuration for Keycloak +# Generated by Ansible + +upstream keycloak_backend { + server 127.0.0.1:{{ keycloak.http_port }}; +} + +# HTTP -> HTTPS redirect +server { + listen 80; + server_name {{ keycloak.hostname }}; + return 301 https://$server_name$request_uri; +} + +# HTTPS +server { + listen 443 ssl http2; + server_name {{ keycloak.hostname }}; + + # SSL + ssl_certificate /etc/ssl/certs/{{ keycloak.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ keycloak.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Logging + access_log /var/log/nginx/keycloak-access.log; + error_log /var/log/nginx/keycloak-error.log; + + # Proxy settings + location / { + proxy_pass http://keycloak_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # Buffers + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + + # Timeouts + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + } +} diff --git a/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 b/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 new file mode 100644 index 0000000..7876f37 --- /dev/null +++ b/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 @@ -0,0 +1,30 @@ +# Alliance BorĂ©ale - Nginx Keycloak +# Generated by Ansible + +upstream keycloak_backend { + server 127.0.0.1:{{ keycloak.http_port }}; +} + +server { + listen 80; + server_name {{ keycloak.hostname }}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ keycloak.hostname }}; + + ssl_certificate /etc/ssl/certs/{{ keycloak.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ keycloak.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://keycloak_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/ansible/roles/keycloak/templates/realm-config.json.j2 b/ansible/roles/keycloak/templates/realm-config.json.j2 new file mode 100644 index 0000000..601b999 --- /dev/null +++ b/ansible/roles/keycloak/templates/realm-config.json.j2 @@ -0,0 +1,48 @@ +{ + "realm": "{{ item.name }}", + "displayName": "{{ item.display_name }}", + "enabled": {{ item.enabled | lower }}, + "sslRequired": "external", + "registrationAllowed": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": true, + "clients": [ +{% for client in item.clients %} + { + "clientId": "{{ client.client_id }}", + "name": "{{ client.name }}", + "enabled": {{ client.enabled | lower }}, + "protocol": "{{ client.protocol }}", + "publicClient": false, + "redirectUris": {{ client.redirect_uris | to_json }}, + "webOrigins": {{ client.web_origins | to_json }}, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": false + }{{ "," if not loop.last else "" }} +{% endfor %} + ] +} + +---SEPARATOR--- + +# Alliance BorĂ©ale - Keycloak Handlers +# Date: 2025-10-31 + +--- +- name: restart keycloak + ansible.builtin.systemd: + name: keycloak + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/ansible/roles/netbox/README.md b/ansible/roles/netbox/README.md deleted file mode 100644 index 225dd44..0000000 --- a/ansible/roles/netbox/README.md +++ /dev/null @@ -1,38 +0,0 @@ -Role Name -========= - -A brief description of the role goes here. - -Requirements ------------- - -Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. - -Role Variables --------------- - -A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. - -Dependencies ------------- - -A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. - -Example Playbook ----------------- - -Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: - - - hosts: servers - roles: - - { role: username.rolename, x: 42 } - -License -------- - -BSD - -Author Information ------------------- - -An optional section for the role authors to include contact information, or a website (HTML is not allowed). diff --git a/ansible/roles/netbox/defaults/main.yml b/ansible/roles/netbox/defaults/main.yml deleted file mode 100644 index 8de3012..0000000 --- a/ansible/roles/netbox/defaults/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# defaults file for netbox diff --git a/ansible/roles/netbox/handlers/main.yml b/ansible/roles/netbox/handlers/main.yml deleted file mode 100644 index f2f436f..0000000 --- a/ansible/roles/netbox/handlers/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# handlers file for netbox diff --git a/ansible/roles/netbox/meta/main.yml b/ansible/roles/netbox/meta/main.yml deleted file mode 100644 index 36b9858..0000000 --- a/ansible/roles/netbox/meta/main.yml +++ /dev/null @@ -1,35 +0,0 @@ -#SPDX-License-Identifier: MIT-0 -galaxy_info: - author: your name - description: your role description - company: your company (optional) - - # If the issue tracker for your role is not on github, uncomment the - # next line and provide a value - # issue_tracker_url: http://example.com/issue/tracker - - # Choose a valid license ID from https://spdx.org - some suggested licenses: - # - BSD-3-Clause (default) - # - MIT - # - GPL-2.0-or-later - # - GPL-3.0-only - # - Apache-2.0 - # - CC-BY-4.0 - license: license (GPL-2.0-or-later, MIT, etc) - - min_ansible_version: 2.1 - - # If this a Container Enabled role, provide the minimum Ansible Container version. - # min_ansible_container_version: - - galaxy_tags: [] - # List tags for your role here, one per line. A tag is a keyword that describes - # and categorizes the role. Users find roles by searching for tags. Be sure to - # remove the '[]' above, if you add tags to this list. - # - # NOTE: A tag is limited to a single word comprised of alphanumeric characters. - # Maximum 20 tags per role. - -dependencies: [] - # List your role dependencies here, one per line. Be sure to remove the '[]' above, - # if you add dependencies to this list. diff --git a/ansible/roles/netbox/tasks/main.yml b/ansible/roles/netbox/tasks/main.yml deleted file mode 100644 index 9111f08..0000000 --- a/ansible/roles/netbox/tasks/main.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -- name: VĂ©rifier que le rĂŽle est lancĂ© sur un hĂŽte netbox - assert: - that: "'netbox' in group_names" - msg: "Ce rĂŽle ne s'applique qu'aux hĂŽtes du groupe [netbox]" - -- name: Inclure les variables chiffrĂ©es (Vault) - include_vars: - file: ../../inventories/production/group_vars/vault.yml - when: vault_postgres_pass is not defined - -- name: Message de bienvenue - debug: - msg: | - DĂ©ploiement NetBox sur {{ inventory_hostname }} - Domaine : {{ netbox_domain }} - Mode offline : {{ offline_mode }} diff --git a/ansible/roles/netbox/tests/inventory b/ansible/roles/netbox/tests/inventory deleted file mode 100644 index 03ca42f..0000000 --- a/ansible/roles/netbox/tests/inventory +++ /dev/null @@ -1,3 +0,0 @@ -#SPDX-License-Identifier: MIT-0 -localhost - diff --git a/ansible/roles/netbox/tests/test.yml b/ansible/roles/netbox/tests/test.yml deleted file mode 100644 index a995a86..0000000 --- a/ansible/roles/netbox/tests/test.yml +++ /dev/null @@ -1,6 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -- hosts: localhost - remote_user: root - roles: - - netbox diff --git a/ansible/roles/netbox/vars/main.yml b/ansible/roles/netbox/vars/main.yml deleted file mode 100644 index 7299363..0000000 --- a/ansible/roles/netbox/vars/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# vars file for netbox diff --git a/ansible/roles/postgresql/defaults/main.yml b/ansible/roles/postgresql/defaults/main.yml new file mode 100644 index 0000000..67829ac --- /dev/null +++ b/ansible/roles/postgresql/defaults/main.yml @@ -0,0 +1,22 @@ +# Alliance BorĂ©ale - RĂŽle PostgreSQL - Default Variables +# Date: 2025-10-31 + +--- +postgresql: + version: 15 + listen_addresses: localhost + port: 5432 + + shared_buffers: 256MB + work_mem: 16MB + maintenance_work_mem: 128MB + effective_cache_size: 1GB + + databases: [] + users: [] + + backup: + enabled: true + schedule: "0 2 * * *" + retention_days: 7 + destination: /var/backups/postgresql diff --git a/ansible/roles/postgresql/handlers/main.yml b/ansible/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..1a1ec8c --- /dev/null +++ b/ansible/roles/postgresql/handlers/main.yml @@ -0,0 +1,13 @@ +# Alliance BorĂ©ale - RĂŽle PostgreSQL - Handlers +# Date: 2025-10-31 + +--- +- name: restart postgresql + ansible.builtin.systemd: + name: postgresql + state: restarted + +- name: reload postgresql + ansible.builtin.systemd: + name: postgresql + state: reloaded diff --git a/ansible/roles/postgresql/tasks/main.yml b/ansible/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..546303e --- /dev/null +++ b/ansible/roles/postgresql/tasks/main.yml @@ -0,0 +1,114 @@ +# Alliance BorĂ©ale - RĂŽle PostgreSQL +# Couche: C3 (dĂ©pendance pour PowerDNS) +# Objectif: DĂ©ployer PostgreSQL pour backend PowerDNS +# Date: 2025-10-31 + +--- +- name: "📩 Install PostgreSQL and dependencies" + ansible.builtin.apt: + name: + - postgresql-{{ postgresql.version }} + - postgresql-contrib-{{ postgresql.version }} + - python3-psycopg2 + state: present + update_cache: true + tags: postgresql + +- name: "⚙ Configure PostgreSQL" + ansible.builtin.template: + src: postgresql.conf.j2 + dest: "/etc/postgresql/{{ postgresql.version }}/main/postgresql.conf" + owner: postgres + group: postgres + mode: '0644' + notify: restart postgresql + tags: postgresql + +- name: "⚙ Configure pg_hba.conf" + ansible.builtin.template: + src: pg_hba.conf.j2 + dest: "/etc/postgresql/{{ postgresql.version }}/main/pg_hba.conf" + owner: postgres + group: postgres + mode: '0640' + notify: restart postgresql + tags: postgresql + +- name: "✅ Ensure PostgreSQL is started" + ansible.builtin.systemd: + name: postgresql + state: started + enabled: true + tags: postgresql + +- name: "đŸ—„ïž Create PostgreSQL databases" + community.postgresql.postgresql_db: + name: "{{ item.name }}" + encoding: "{{ item.encoding | default('UTF8') }}" + lc_collate: "{{ item.lc_collate | default('en_US.UTF-8') }}" + lc_ctype: "{{ item.lc_ctype | default('en_US.UTF-8') }}" + state: present + loop: "{{ postgresql.databases }}" + become: true + become_user: postgres + tags: postgresql + +- name: "đŸ‘€ Create PostgreSQL users" + community.postgresql.postgresql_user: + name: "{{ item.name }}" + password: "{{ item.password }}" + state: present + loop: "{{ postgresql.users }}" + become: true + become_user: postgres + no_log: true + tags: postgresql + +- name: "🔐 Grant database privileges" + community.postgresql.postgresql_privs: + database: "{{ item.name }}" + roles: "{{ item.owner }}" + type: database + privs: ALL + state: present + loop: "{{ postgresql.databases }}" + become: true + become_user: postgres + tags: postgresql + +- name: "📊 Create backup directory" + ansible.builtin.file: + path: "{{ postgresql.backup.destination }}" + state: directory + owner: postgres + group: postgres + mode: '0750' + when: postgresql.backup.enabled | default(true) + tags: postgresql + +- name: "📊 Configure backup cron job" + ansible.builtin.cron: + name: "PostgreSQL backup" + user: postgres + minute: "0" + hour: "2" + job: "pg_dumpall | gzip > {{ postgresql.backup.destination }}/postgres-$(date +\\%Y\\%m\\%d).sql.gz" + state: present + when: postgresql.backup.enabled | default(true) + tags: postgresql + +- name: "đŸ—‘ïž Configure backup retention (delete old backups)" + ansible.builtin.cron: + name: "PostgreSQL backup cleanup" + user: postgres + minute: "30" + hour: "2" + job: "find {{ postgresql.backup.destination }} -name 'postgres-*.sql.gz' -mtime +{{ postgresql.backup.retention_days }} -delete" + state: present + when: postgresql.backup.enabled | default(true) + tags: postgresql + +- name: "✅ PostgreSQL role completed" + ansible.builtin.debug: + msg: "✅ PostgreSQL {{ postgresql.version }} configured on {{ inventory_hostname }}" + tags: postgresql diff --git a/ansible/roles/postgresql/templates/pg_hba.conf.j2 b/ansible/roles/postgresql/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..e5bacfc --- /dev/null +++ b/ansible/roles/postgresql/templates/pg_hba.conf.j2 @@ -0,0 +1,24 @@ +# Alliance BorĂ©ale - PostgreSQL Client Authentication +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# TYPE DATABASE USER ADDRESS METHOD + +# Local connections +local all postgres peer +local all all peer + +# IPv4 local connections +host all all 127.0.0.1/32 scram-sha-256 + +# IPv6 local connections +host all all ::1/128 scram-sha-256 + +# Internal network (if needed later) +# host all all 10.0.0.0/8 scram-sha-256 + +# Replication (for future use) +# local replication all peer +# host replication all 127.0.0.1/32 scram-sha-256 +# host replication all ::1/128 scram-sha-256 diff --git a/ansible/roles/postgresql/templates/postgresql.conf.j2 b/ansible/roles/postgresql/templates/postgresql.conf.j2 new file mode 100644 index 0000000..925aa21 --- /dev/null +++ b/ansible/roles/postgresql/templates/postgresql.conf.j2 @@ -0,0 +1,38 @@ +# Alliance BorĂ©ale - PostgreSQL Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# Network +listen_addresses = '{{ postgresql.listen_addresses | default("localhost") }}' +port = {{ postgresql.port | default(5432) }} + +# Memory +shared_buffers = {{ postgresql.shared_buffers | default('256MB') }} +work_mem = {{ postgresql.work_mem | default('16MB') }} +maintenance_work_mem = {{ postgresql.maintenance_work_mem | default('128MB') }} +effective_cache_size = {{ postgresql.effective_cache_size | default('1GB') }} + +# Write-Ahead Log +wal_level = replica +max_wal_size = 1GB +min_wal_size = 80MB + +# Logging +log_destination = 'stderr' +logging_collector = on +log_directory = 'log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_rotation_age = 1d +log_rotation_size = 10MB +log_line_prefix = '%m [%p] %q%u@%d ' +log_timezone = 'America/Toronto' + +# Locale +datestyle = 'iso, mdy' +timezone = 'America/Toronto' +lc_messages = 'en_CA.UTF-8' +lc_monetary = 'en_CA.UTF-8' +lc_numeric = 'en_CA.UTF-8' +lc_time = 'en_CA.UTF-8' +default_text_search_config = 'pg_catalog.english' diff --git a/ansible/roles/powerdns-authoritative/handlers/main.yml b/ansible/roles/powerdns-authoritative/handlers/main.yml new file mode 100644 index 0000000..f85227c --- /dev/null +++ b/ansible/roles/powerdns-authoritative/handlers/main.yml @@ -0,0 +1,18 @@ +# Alliance BorĂ©ale - PowerDNS Handlers +# Date: 2025-10-31 + +--- +- name: restart powerdns + ansible.builtin.systemd: + name: pdns + state: restarted + +- name: reload powerdns + ansible.builtin.systemd: + name: pdns + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/ansible/roles/powerdns-authoritative/tasks/firewall.yml b/ansible/roles/powerdns-authoritative/tasks/firewall.yml new file mode 100644 index 0000000..832a940 --- /dev/null +++ b/ansible/roles/powerdns-authoritative/tasks/firewall.yml @@ -0,0 +1,57 @@ +# Alliance BorĂ©ale - PowerDNS Firewall & Validation +# Date: 2025-10-31 + +--- +# ========================================== +# FIREWALL.YML +# ========================================== +- name: "đŸ”„ Ensure nftables rules include DNS" + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED - PowerDNS" + insertbefore: "# Log dropped packets" + block: | + # PowerDNS - DNS queries + udp dport 53 accept comment "DNS queries (UDP)" + tcp dport 53 accept comment "DNS queries (TCP)" + + # PowerDNS - API (internal only) + ip saddr {{ network.internal_subnet | default('10.0.0.0/8') }} tcp dport 8081 accept comment "PowerDNS API" + notify: reload nftables + +# ========================================== +# VALIDATE.YML +# ========================================== +- name: "đŸ§Ș Wait for PowerDNS to be ready" + ansible.builtin.wait_for: + port: 53 + host: "{{ ansible_host }}" + timeout: 30 + +- name: "đŸ§Ș Test DNS resolution (localhost)" + ansible.builtin.command: + cmd: "dig @127.0.0.1 {{ dns_zones[0].name }} SOA +short" + register: dns_test_local + changed_when: false + failed_when: dns_test_local.rc != 0 + +- name: "đŸ§Ș Display DNS test result" + ansible.builtin.debug: + msg: "✅ DNS resolution working: {{ dns_test_local.stdout }}" + +- name: "đŸ§Ș Check PowerDNS API (if enabled)" + ansible.builtin.uri: + url: "http://127.0.0.1:8081/api/v1/servers/localhost" + headers: + X-API-Key: "{{ powerdns.api.key }}" + return_content: true + register: api_test + when: powerdns.api.enabled | default(true) + failed_when: false + +- name: "đŸ§Ș Display PowerDNS version" + ansible.builtin.debug: + msg: "✅ PowerDNS API responding: {{ api_test.json.version | default('N/A') }}" + when: + - powerdns.api.enabled | default(true) + - api_test.status == 200 diff --git a/ansible/roles/powerdns-authoritative/tasks/main.yml b/ansible/roles/powerdns-authoritative/tasks/main.yml new file mode 100644 index 0000000..c105eca --- /dev/null +++ b/ansible/roles/powerdns-authoritative/tasks/main.yml @@ -0,0 +1,90 @@ +# Alliance BorĂ©ale - RĂŽle PowerDNS Authoritative +# Couche: C2 (RĂ©seau & DNS) +# Objectif: DĂ©ployer PowerDNS autoritaire (MASTER ou SLAVE) +# Date: 2025-10-31 + +--- +- name: "🔍 Determine PowerDNS role (master/slave)" + ansible.builtin.set_fact: + pdns_is_master: "{{ inventory_hostname in groups['dns_masters'] }}" + pdns_is_slave: "{{ inventory_hostname in groups['dns_slaves'] }}" + tags: always + +- name: "📩 Add PowerDNS repository" + ansible.builtin.import_tasks: repo.yml + tags: powerdns + +- name: "📩 Install PowerDNS packages" + ansible.builtin.apt: + name: + - pdns-server + - pdns-backend-pgsql + - pdns-tools + state: present + update_cache: true + tags: powerdns + +- name: "⚙ Configure PowerDNS" + ansible.builtin.template: + src: pdns.conf.j2 + dest: /etc/powerdns/pdns.conf + owner: root + group: pdns + mode: '0640' + notify: restart powerdns + tags: powerdns + +- name: "⚙ Configure PowerDNS PostgreSQL backend" + ansible.builtin.template: + src: pdns.d/pdns.local.gpgsql.conf.j2 + dest: /etc/powerdns/pdns.d/pdns.local.gpgsql.conf + owner: root + group: pdns + mode: '0640' + notify: restart powerdns + tags: powerdns + +- name: "đŸ—„ïž Import PowerDNS PostgreSQL schema" + ansible.builtin.import_tasks: schema.yml + when: pdns_is_master + tags: powerdns + +- name: "🌐 Configure DNS zones (MASTER only)" + ansible.builtin.import_tasks: zones.yml + when: pdns_is_master + tags: powerdns + +- name: "🔁 Configure AXFR (SLAVE only)" + ansible.builtin.import_tasks: slave.yml + when: pdns_is_slave + tags: powerdns + +- name: "🔐 Configure DNSSEC" + ansible.builtin.import_tasks: dnssec.yml + when: + - pdns_is_master + - powerdns.dnssec.enabled | default(true) + tags: powerdns + +- name: "đŸ”„ Configure firewall for DNS" + ansible.builtin.import_tasks: firewall.yml + tags: powerdns + +- name: "✅ Start and enable PowerDNS" + ansible.builtin.systemd: + name: pdns + state: started + enabled: true + tags: powerdns + +- name: "đŸ§Ș Validate PowerDNS configuration" + ansible.builtin.import_tasks: validate.yml + tags: powerdns + +- name: "✅ PowerDNS role completed" + ansible.builtin.debug: + msg: | + ✅ PowerDNS configured on {{ inventory_hostname }} + Role: {{ 'MASTER' if pdns_is_master else 'SLAVE' }} + Zones: {{ dns_zones | map(attribute='name') | list if pdns_is_master else 'N/A' }} + tags: powerdns diff --git a/ansible/roles/powerdns-authoritative/tasks/repo.yml b/ansible/roles/powerdns-authoritative/tasks/repo.yml new file mode 100644 index 0000000..c601a3f --- /dev/null +++ b/ansible/roles/powerdns-authoritative/tasks/repo.yml @@ -0,0 +1,25 @@ +# Alliance BorĂ©ale - PowerDNS Repository Setup +# Date: 2025-10-31 + +--- +- name: "🔑 Add PowerDNS GPG key" + ansible.builtin.apt_key: + url: https://repo.powerdns.com/FD380FBB-pub.asc + state: present + +- name: "📩 Add PowerDNS repository" + ansible.builtin.apt_repository: + repo: "deb [arch=amd64] http://repo.powerdns.com/{{ ansible_distribution | lower }} {{ ansible_distribution_release }}-auth-48 main" + state: present + filename: pdns + +- name: "📋 Set PowerDNS package preferences" + ansible.builtin.copy: + dest: /etc/apt/preferences.d/pdns + content: | + Package: pdns-* + Pin: origin repo.powerdns.com + Pin-Priority: 600 + owner: root + group: root + mode: '0644' diff --git a/ansible/roles/powerdns-authoritative/tasks/schema.yml b/ansible/roles/powerdns-authoritative/tasks/schema.yml new file mode 100644 index 0000000..52af50f --- /dev/null +++ b/ansible/roles/powerdns-authoritative/tasks/schema.yml @@ -0,0 +1,149 @@ +# Alliance BorĂ©ale - PowerDNS Schema Import +# Date: 2025-10-31 + +--- +# Ce fichier contient: schema.yml, zones.yml, slave.yml, dnssec.yml + +# ========================================== +# SCHEMA.YML - Import PostgreSQL schema +# ========================================== +- name: "đŸ—„ïž Check if PowerDNS schema exists" + community.postgresql.postgresql_query: + db: powerdns + query: "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'domains');" + become: true + become_user: postgres + register: pdns_schema_check + changed_when: false + +- name: "đŸ—„ïž Download PowerDNS schema" + ansible.builtin.get_url: + url: https://raw.githubusercontent.com/PowerDNS/pdns/rel/auth-4.8.x/modules/gpgsqlbackend/schema.pgsql.sql + dest: /tmp/pdns-schema.sql + mode: '0644' + when: not pdns_schema_check.query_result[0].exists + +- name: "đŸ—„ïž Import PowerDNS schema" + community.postgresql.postgresql_db: + db: powerdns + state: restore + target: /tmp/pdns-schema.sql + become: true + become_user: postgres + when: not pdns_schema_check.query_result[0].exists + +# ========================================== +# ZONES.YML - Create DNS zones (MASTER) +# ========================================== +- name: "🌐 Create DNS zones" + ansible.builtin.command: + cmd: "pdnsutil create-zone {{ item.name }}" + loop: "{{ dns_zones }}" + register: zone_create + changed_when: "'created' in zone_create.stdout" + failed_when: + - zone_create.rc != 0 + - "'already exists' not in zone_create.stderr" + +- name: "🌐 Set zone kind to NATIVE" + ansible.builtin.command: + cmd: "pdnsutil set-kind {{ item.name }} NATIVE" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🌐 Configure SOA records" + ansible.builtin.command: + cmd: > + pdnsutil replace-rrset {{ item.name }} @ SOA + "{{ item.soa.nameserver }} {{ item.soa.email }} + {{ item.soa.serial }} {{ item.soa.refresh }} {{ item.soa.retry }} + {{ item.soa.expire }} {{ item.soa.minimum }}" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🌐 Add NS records" + ansible.builtin.command: + cmd: "pdnsutil add-record {{ item.0.name }} @ NS {{ item.1 }}" + loop: "{{ dns_zones | subelements('ns_records') }}" + register: ns_add + changed_when: false + failed_when: + - ns_add.rc != 0 + - "'already exists' not in ns_add.stderr" + +- name: "🌐 Add A/AAAA records" + ansible.builtin.command: + cmd: "pdnsutil add-record {{ item.0.name }} {{ item.1.name }} {{ item.1.type }} {{ item.1.content }} {{ item.1.ttl | default(3600) }}" + loop: "{{ dns_zones | subelements('records', skip_missing=True) }}" + register: record_add + changed_when: false + failed_when: + - record_add.rc != 0 + - "'already exists' not in record_add.stderr" + +- name: "🌐 Rectify zones" + ansible.builtin.command: + cmd: "pdnsutil rectify-zone {{ item.name }}" + loop: "{{ dns_zones }}" + changed_when: false + +# ========================================== +# SLAVE.YML - Configure AXFR for slaves +# ========================================== +- name: "🔁 Configure supermaster for AXFR" + community.postgresql.postgresql_query: + db: powerdns + query: > + INSERT INTO supermasters (ip, nameserver, account) + VALUES ('{{ hostvars[groups['dns_masters'][0]]['ansible_host'] }}', + '{{ inventory_hostname }}', + 'default') + ON CONFLICT DO NOTHING; + become: true + become_user: postgres + +- name: "🔁 Enable slave mode in PowerDNS" + ansible.builtin.lineinfile: + path: /etc/powerdns/pdns.conf + regexp: '^slave=' + line: 'slave=yes' + notify: restart powerdns + +# ========================================== +# DNSSEC.YML - Configure DNSSEC +# ========================================== +- name: "🔐 Enable DNSSEC for zones" + ansible.builtin.command: + cmd: "pdnsutil secure-zone {{ item.name }}" + loop: "{{ dns_zones }}" + register: dnssec_secure + changed_when: "'secured' in dnssec_secure.stdout" + failed_when: + - dnssec_secure.rc != 0 + - "'already' not in dnssec_secure.stderr" + +- name: "🔐 Configure NSEC3" + ansible.builtin.command: + cmd: "pdnsutil set-nsec3 {{ item.name }} '1 0 10 ab' narrow" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🔐 Rectify zones after DNSSEC" + ansible.builtin.command: + cmd: "pdnsutil rectify-zone {{ item.name }}" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🔐 Export DS records" + ansible.builtin.command: + cmd: "pdnsutil show-zone {{ item.name }}" + loop: "{{ dns_zones }}" + register: ds_records + changed_when: false + +- name: "🔐 Display DS records for parent zone" + ansible.builtin.debug: + msg: | + ⚠ IMPORTANT: Add these DS records to your domain registrar: + {{ ds_records.results | map(attribute='stdout') | join('\n') }} + when: ds_records.results | length > 0 diff --git a/ansible/roles/powerdns-authoritative/templates/pdns.conf.j2 b/ansible/roles/powerdns-authoritative/templates/pdns.conf.j2 new file mode 100644 index 0000000..a3169e4 --- /dev/null +++ b/ansible/roles/powerdns-authoritative/templates/pdns.conf.j2 @@ -0,0 +1,70 @@ +# Alliance BorĂ©ale - PowerDNS Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Role: {{ 'MASTER' if pdns_is_master else 'SLAVE' }} +# Date: {{ ansible_date_time.iso8601 }} + +# ========================================== +# BACKEND +# ========================================== +launch=gpgsql +include-dir=/etc/powerdns/pdns.d + +# ========================================== +# NETWORK +# ========================================== +local-address=0.0.0.0 +local-port=53 +local-ipv6= + +# ========================================== +# MODE +# ========================================== +master={{ 'yes' if pdns_is_master else 'no' }} +slave={{ 'yes' if pdns_is_slave else 'no' }} +superslave={{ 'no' }} + +# ========================================== +# API & WEBSERVER +# ========================================== +api={{ 'yes' if powerdns.api.enabled | default(true) else 'no' }} +api-key={{ powerdns.api.key | default('changeme') }} + +webserver={{ 'yes' if powerdns.webserver.enabled | default(true) else 'no' }} +webserver-address={{ powerdns.webserver.address | default('0.0.0.0') }} +webserver-port={{ powerdns.webserver.port | default(8081) }} +webserver-allow-from={{ powerdns.webserver.allow_from | default('10.0.0.0/8') }} + +# ========================================== +# DNSSEC +# ========================================== +dnssec={{ 'on' if powerdns.dnssec.enabled | default(true) else 'off' }} + +# ========================================== +# LOGGING +# ========================================== +log-dns-queries={{ 'yes' if powerdns.logging.queries | default(false) else 'no' }} +log-dns-details={{ 'yes' if powerdns.logging.details | default(true) else 'no' }} +loglevel={{ powerdns.logging.level | default(4) }} + +# ========================================== +# PERFORMANCE +# ========================================== +cache-ttl={{ powerdns.cache_ttl | default(20) }} +negquery-cache-ttl={{ powerdns.negquery_cache_ttl | default(60) }} +query-cache-ttl={{ powerdns.query_cache_ttl | default(20) }} + +# ========================================== +# SECURITY +# ========================================== +setuid=pdns +setgid=pdns +chroot=/var/spool/powerdns +disable-axfr={{ 'no' }} +allow-axfr-ips={{ axfr.allow_from | join(',') if axfr.allow_from is defined else '127.0.0.1' }} +also-notify={{ notify.also_notify | join(',') if notify.also_notify is defined else '' }} + +# ========================================== +# SOA +# ========================================== +default-soa-content=ns1.infra.{{ member.domain_primary }} admin.{{ member.domain_primary }} 0 3600 1800 1209600 3600 diff --git a/ansible/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 b/ansible/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 new file mode 100644 index 0000000..18d8736 --- /dev/null +++ b/ansible/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 @@ -0,0 +1,12 @@ +# Alliance BorĂ©ale - PowerDNS PostgreSQL Backend +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# PostgreSQL connection +gpgsql-host=/var/run/postgresql +gpgsql-port=5432 +gpgsql-dbname=powerdns +gpgsql-user=pdns +gpgsql-password={{ vault_postgresql_pdns_password }} +gpgsql-dnssec=yes diff --git a/ansible/site.yml b/ansible/site.yml deleted file mode 100644 index 70d99b6..0000000 --- a/ansible/site.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -- name: DĂ©ployer les services de l'Alliance BorĂ©ale - hosts: all - become: yes - vars: - offline_mode: "{{ offline_mode | default(false) }}" - roles: - - role: netbox - when: "'netbox' in group_names" - # - role: powerdns - # when: "'powerdns' in group_names" - # - role: keycloak - # when: "'keycloak' in group_names" diff --git a/ansible/vault/production-phase2.yml.example b/ansible/vault/production-phase2.yml.example new file mode 100644 index 0000000..0f4ca30 --- /dev/null +++ b/ansible/vault/production-phase2.yml.example @@ -0,0 +1,54 @@ +# Alliance BorĂ©ale - Vault Production (PHASE 1 + 2 EXEMPLE) +# ⚠ NE PAS UTILISER TEL QUEL - À CHIFFRER AVEC ansible-vault +# Date: 2025-10-31 + +--- +# ========================================== +# PHASE 1 - DNS +# ========================================== +vault_postgresql_pdns_password: "changeme-pdns-db-password" +vault_pdns_api_key: "changeme-pdns-api-key" + +# ========================================== +# PHASE 2 - KEYCLOAK +# ========================================== +vault_keycloak_admin_password: "changeme-keycloak-admin-password" +vault_postgresql_keycloak_password: "changeme-keycloak-db-password" + +# Client secret for Forgejo (Ă  rĂ©cupĂ©rer depuis Keycloak UI) +vault_keycloak_forgejo_client_secret: "changeme-will-be-generated-by-keycloak" + +# ========================================== +# PHASE 2 - FORGEJO +# ========================================== +vault_forgejo_admin_password: "changeme-forgejo-admin-password" +vault_postgresql_forgejo_password: "changeme-forgejo-db-password" + +# Secrets Forgejo (gĂ©nĂ©rer avec openssl) +vault_forgejo_secret_key: "changeme-64-chars-secret-key-here-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +vault_forgejo_internal_token: "changeme-64-chars-internal-token-here-xxxxxxxxxxxxxxxxxxxxxxxx" +vault_forgejo_jwt_secret: "changeme-64-chars-jwt-secret-here-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# ========================================== +# COMMANDES POUR GÉNÉRER LES SECRETS: +# ========================================== + +# Mots de passe (24 chars) +# openssl rand -base64 24 + +# API Keys / Secrets (32 chars) +# openssl rand -base64 32 + +# Forgejo secrets (64 chars hex) +# openssl rand -hex 64 + +# ========================================== +# WORKFLOW: +# ========================================== +# 1. Copier vault/production.yml.example → vault/production.yml +# 2. GĂ©nĂ©rer tous les secrets avec openssl +# 3. DĂ©ployer Keycloak (phase2-deploy.yml --tags keycloak) +# 4. RĂ©cupĂ©rer client secret depuis Keycloak UI +# 5. Ajouter vault_keycloak_forgejo_client_secret +# 6. DĂ©ployer Forgejo (phase2-deploy.yml --tags forgejo) +# 7. Chiffrer: ansible-vault encrypt vault/production.yml diff --git a/ansible/vault/production.yml.example b/ansible/vault/production.yml.example new file mode 100644 index 0000000..3e50a2a --- /dev/null +++ b/ansible/vault/production.yml.example @@ -0,0 +1,41 @@ +# Alliance BorĂ©ale - Vault Production (EXEMPLE NON CHIFFRÉ) +# ⚠ NE PAS UTILISER TEL QUEL - À CHIFFRER AVEC ansible-vault +# Date: 2025-10-31 + +--- +# ========================================== +# POSTGRESQL +# ========================================== +vault_postgresql_pdns_password: "changeme-generate-secure-password-here" + +# ========================================== +# POWERDNS +# ========================================== +vault_pdns_api_key: "changeme-generate-secure-api-key-here" + +# ========================================== +# TSIG KEYS (Phase 2 - AXFR croisĂ©s) +# ========================================== +# vault_tsig_clp_nul: "base64-encoded-secret-here" +# vault_tsig_clp_tli: "base64-encoded-secret-here" + +# ========================================== +# POUR GÉNÉRER DES SECRETS SÉCURISÉS: +# ========================================== +# openssl rand -base64 32 # Pour API keys +# openssl rand -base64 24 # Pour mots de passe +# tsig-keygen clp-nul-xfer # Pour TSIG keys + +# ========================================== +# COMMANDES POUR CRÉER LE VRAI VAULT: +# ========================================== +# 1. Copier ce fichier: +# cp vault/production.yml.example vault/production.yml +# +# 2. Éditer et remplacer tous les "changeme-*": +# vi vault/production.yml +# +# 3. Chiffrer avec ansible-vault: +# ansible-vault encrypt vault/production.yml +# +# 4. Sauvegarder le mot de passe vault dans un gestionnaire sĂ©curisĂ©!