diff --git a/archives/README.md b/archives/README.md new file mode 100644 index 0000000..d874769 --- /dev/null +++ b/archives/README.md @@ -0,0 +1,7 @@ +# Archives + +Contient : +- legacy +- variantes remplacées +- artefacts déplacés pendant migration + diff --git a/docs/vieustoq/01_charte_fondatrice.md b/archives/legacy-docs/vieustoq/01_charte_fondatrice.md similarity index 100% rename from docs/vieustoq/01_charte_fondatrice.md rename to archives/legacy-docs/vieustoq/01_charte_fondatrice.md diff --git a/docs/vieustoq/02_reglement_regie_interne.md b/archives/legacy-docs/vieustoq/02_reglement_regie_interne.md similarity index 100% rename from docs/vieustoq/02_reglement_regie_interne.md rename to archives/legacy-docs/vieustoq/02_reglement_regie_interne.md diff --git a/docs/vieustoq/03_guide_demarrage_rapide.md b/archives/legacy-docs/vieustoq/03_guide_demarrage_rapide.md similarity index 100% rename from docs/vieustoq/03_guide_demarrage_rapide.md rename to archives/legacy-docs/vieustoq/03_guide_demarrage_rapide.md diff --git a/docs/vieustoq/2025-10-11 - L'Alliance Boréale - Partenariats.md b/archives/legacy-docs/vieustoq/2025-10-11 - L'Alliance Boréale - Partenariats.md similarity index 100% rename from docs/vieustoq/2025-10-11 - L'Alliance Boréale - Partenariats.md rename to archives/legacy-docs/vieustoq/2025-10-11 - L'Alliance Boréale - Partenariats.md diff --git a/docs/vieustoq/doc_10_contrat_adhesion.md b/archives/legacy-docs/vieustoq/doc_10_contrat_adhesion.md similarity index 100% rename from docs/vieustoq/doc_10_contrat_adhesion.md rename to archives/legacy-docs/vieustoq/doc_10_contrat_adhesion.md diff --git a/docs/vieustoq/doc_11_resolutions_ca.md b/archives/legacy-docs/vieustoq/doc_11_resolutions_ca.md similarity index 100% rename from docs/vieustoq/doc_11_resolutions_ca.md rename to archives/legacy-docs/vieustoq/doc_11_resolutions_ca.md diff --git a/docs/vieustoq/doc_12_plan_transition_obnl.md b/archives/legacy-docs/vieustoq/doc_12_plan_transition_obnl.md similarity index 100% rename from docs/vieustoq/doc_12_plan_transition_obnl.md rename to archives/legacy-docs/vieustoq/doc_12_plan_transition_obnl.md diff --git a/docs/vieustoq/doc_14_registraire_yaml.md b/archives/legacy-docs/vieustoq/doc_14_registraire_yaml.md similarity index 100% rename from docs/vieustoq/doc_14_registraire_yaml.md rename to archives/legacy-docs/vieustoq/doc_14_registraire_yaml.md diff --git a/docs/vieustoq/document_01_charte_fondatrice.md b/archives/legacy-docs/vieustoq/document_01_charte_fondatrice.md similarity index 100% rename from docs/vieustoq/document_01_charte_fondatrice.md rename to archives/legacy-docs/vieustoq/document_01_charte_fondatrice.md diff --git a/docs/vieustoq/document_04_modele_financement_hybride.md b/archives/legacy-docs/vieustoq/document_04_modele_financement_hybride.md similarity index 100% rename from docs/vieustoq/document_04_modele_financement_hybride.md rename to archives/legacy-docs/vieustoq/document_04_modele_financement_hybride.md diff --git a/docs/vieustoq/document_04_modele_financement_hybride_v1.1.md b/archives/legacy-docs/vieustoq/document_04_modele_financement_hybride_v1.1.md similarity index 100% rename from docs/vieustoq/document_04_modele_financement_hybride_v1.1.md rename to archives/legacy-docs/vieustoq/document_04_modele_financement_hybride_v1.1.md diff --git a/docs/vieustoq/document_05_protocole_audit_pair.md b/archives/legacy-docs/vieustoq/document_05_protocole_audit_pair.md similarity index 100% rename from docs/vieustoq/document_05_protocole_audit_pair.md rename to archives/legacy-docs/vieustoq/document_05_protocole_audit_pair.md diff --git a/docs/vieustoq/document_05_protocole_audit_pair_a_pair.md b/archives/legacy-docs/vieustoq/document_05_protocole_audit_pair_a_pair.md similarity index 100% rename from docs/vieustoq/document_05_protocole_audit_pair_a_pair.md rename to archives/legacy-docs/vieustoq/document_05_protocole_audit_pair_a_pair.md diff --git a/docs/vieustoq/document_06_charte_banque_de_temps.md b/archives/legacy-docs/vieustoq/document_06_charte_banque_de_temps.md similarity index 100% rename from docs/vieustoq/document_06_charte_banque_de_temps.md rename to archives/legacy-docs/vieustoq/document_06_charte_banque_de_temps.md diff --git a/docs/vieustoq/document_06_charte_banque_temps.md b/archives/legacy-docs/vieustoq/document_06_charte_banque_temps.md similarity index 100% rename from docs/vieustoq/document_06_charte_banque_temps.md rename to archives/legacy-docs/vieustoq/document_06_charte_banque_temps.md diff --git a/docs/pile opérateur/README.md b/archives/pre-migration/docs-pile-operateur/README.md similarity index 100% rename from docs/pile opérateur/README.md rename to archives/pre-migration/docs-pile-operateur/README.md diff --git a/docs/00-fondements/00 - Manifeste.md b/archives/pre-migration/duplicates/00 - Manifeste.md similarity index 100% rename from docs/00-fondements/00 - Manifeste.md rename to archives/pre-migration/duplicates/00 - Manifeste.md diff --git a/docs/00-fondements/00-manifeste.md b/archives/pre-migration/duplicates/00-manifeste.md similarity index 100% rename from docs/00-fondements/00-manifeste.md rename to archives/pre-migration/duplicates/00-manifeste.md diff --git a/docs/20-rag/03_Cadre_Conformite_Label_Prestige(1).md b/archives/pre-migration/duplicates/03_Cadre_Conformite_Label_Prestige(1).md similarity index 100% rename from docs/20-rag/03_Cadre_Conformite_Label_Prestige(1).md rename to archives/pre-migration/duplicates/03_Cadre_Conformite_Label_Prestige(1).md diff --git a/docs/20-rag/devis(2).md b/archives/pre-migration/duplicates/devis(2).md similarity index 100% rename from docs/20-rag/devis(2).md rename to archives/pre-migration/duplicates/devis(2).md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..68b6b70 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +# Documentation canonique + +Ce dossier contient la documentation de référence de l'Alliance Boréale. + +Branches principales : +- fondements +- modele +- architecture +- gouvernance +- decisions +- politiques +- ecosystemes +- guides +- corpus-a-integrer +- diffusion + diff --git a/docs/10-architecture/10 - Constitution des couches du modèle Boréal.md b/docs/architecture/10 - Constitution des couches du modèle Boréal.md similarity index 100% rename from docs/10-architecture/10 - Constitution des couches du modèle Boréal.md rename to docs/architecture/10 - Constitution des couches du modèle Boréal.md diff --git a/docs/10-architecture/10 - Les 8 couches du modèle Boréal.md b/docs/architecture/10 - Les 8 couches du modèle Boréal.md similarity index 100% rename from docs/10-architecture/10 - Les 8 couches du modèle Boréal.md rename to docs/architecture/10 - Les 8 couches du modèle Boréal.md diff --git a/docs/10-architecture/10 - Modèle Boréal.md b/docs/architecture/10 - Modèle Boréal.md similarity index 100% rename from docs/10-architecture/10 - Modèle Boréal.md rename to docs/architecture/10 - Modèle Boréal.md diff --git a/docs/10-architecture/10 - Nomenclature mnémotechnique.md b/docs/architecture/10 - Nomenclature mnémotechnique.md similarity index 100% rename from docs/10-architecture/10 - Nomenclature mnémotechnique.md rename to docs/architecture/10 - Nomenclature mnémotechnique.md diff --git a/docs/10-architecture/10 - Stack opérateur de référence (C1-C5).md b/docs/architecture/10 - Stack opérateur de référence (C1-C5).md similarity index 100% rename from docs/10-architecture/10 - Stack opérateur de référence (C1-C5).md rename to docs/architecture/10 - Stack opérateur de référence (C1-C5).md diff --git a/docs/10-architecture/10 - Structure YAML du registraire.md b/docs/architecture/10 - Structure YAML du registraire.md similarity index 100% rename from docs/10-architecture/10 - Structure YAML du registraire.md rename to docs/architecture/10 - Structure YAML du registraire.md diff --git a/docs/10-architecture/2026-01-29 - Cache OVH - NGINX.md b/docs/architecture/2026-01-29 - Cache OVH - NGINX.md similarity index 100% rename from docs/10-architecture/2026-01-29 - Cache OVH - NGINX.md rename to docs/architecture/2026-01-29 - Cache OVH - NGINX.md diff --git a/docs/10-architecture/Architecture_Deterministe_Alliance_Boreale_Whitepaper.md b/docs/architecture/Architecture_Deterministe_Alliance_Boreale_Whitepaper.md similarity index 100% rename from docs/10-architecture/Architecture_Deterministe_Alliance_Boreale_Whitepaper.md rename to docs/architecture/Architecture_Deterministe_Alliance_Boreale_Whitepaper.md diff --git a/docs/10-architecture/Architecture_Deterministe_Diagrammes_Visuels.md b/docs/architecture/Architecture_Deterministe_Diagrammes_Visuels.md similarity index 100% rename from docs/10-architecture/Architecture_Deterministe_Diagrammes_Visuels.md rename to docs/architecture/Architecture_Deterministe_Diagrammes_Visuels.md diff --git a/docs/10-architecture/Forgejo_dans_le_cycle_vivant.md b/docs/architecture/Forgejo_dans_le_cycle_vivant.md similarity index 100% rename from docs/10-architecture/Forgejo_dans_le_cycle_vivant.md rename to docs/architecture/Forgejo_dans_le_cycle_vivant.md diff --git a/docs/10-architecture/checklist_reunion.md b/docs/architecture/checklist_reunion.md similarity index 100% rename from docs/10-architecture/checklist_reunion.md rename to docs/architecture/checklist_reunion.md diff --git a/docs/10-architecture/guide_formation.md b/docs/architecture/guide_formation.md similarity index 100% rename from docs/10-architecture/guide_formation.md rename to docs/architecture/guide_formation.md diff --git a/docs/10-architecture/readme_package.md b/docs/architecture/readme_package.md similarity index 100% rename from docs/10-architecture/readme_package.md rename to docs/architecture/readme_package.md diff --git a/docs/10-architecture/resolution_adoption.md b/docs/architecture/resolution_adoption.md similarity index 100% rename from docs/10-architecture/resolution_adoption.md rename to docs/architecture/resolution_adoption.md diff --git a/docs/10-architecture/scripts_migration.sh b/docs/architecture/scripts_migration.sh similarity index 100% rename from docs/10-architecture/scripts_migration.sh rename to docs/architecture/scripts_migration.sh diff --git a/docs/10-architecture/templates_automation.md b/docs/architecture/templates_automation.md similarity index 100% rename from docs/10-architecture/templates_automation.md rename to docs/architecture/templates_automation.md diff --git a/docs/20-rag/01 - Charte fondatrice.md b/docs/corpus-a-integrer/01 - Charte fondatrice.md similarity index 100% rename from docs/20-rag/01 - Charte fondatrice.md rename to docs/corpus-a-integrer/01 - Charte fondatrice.md diff --git a/docs/20-rag/02 - Règlement de régie interne.md b/docs/corpus-a-integrer/02 - Règlement de régie interne.md similarity index 100% rename from docs/20-rag/02 - Règlement de régie interne.md rename to docs/corpus-a-integrer/02 - Règlement de régie interne.md diff --git a/docs/TODO.md b/docs/corpus-a-integrer/TODO.md similarity index 100% rename from docs/TODO.md rename to docs/corpus-a-integrer/TODO.md diff --git a/docs/TRANSMISSION.md b/docs/corpus-a-integrer/TRANSMISSION.md similarity index 100% rename from docs/TRANSMISSION.md rename to docs/corpus-a-integrer/TRANSMISSION.md diff --git a/docs/50-annexes/05-correspondance-ISO27001.md b/docs/corpus-a-integrer/annexes/05-correspondance-ISO27001.md similarity index 100% rename from docs/50-annexes/05-correspondance-ISO27001.md rename to docs/corpus-a-integrer/annexes/05-correspondance-ISO27001.md diff --git a/docs/index.md b/docs/corpus-a-integrer/index.md similarity index 100% rename from docs/index.md rename to docs/corpus-a-integrer/index.md diff --git a/docs/pile opérateur/adr/ADR-C5-CTRL-001 — FastAPI comme plan de contrôle en C5 et ERPLibre comme portail back-office.md b/docs/decisions/adr/ADR-C5-CTRL-001 — FastAPI comme plan de contrôle en C5 et ERPLibre comme portail back-office.md similarity index 100% rename from docs/pile opérateur/adr/ADR-C5-CTRL-001 — FastAPI comme plan de contrôle en C5 et ERPLibre comme portail back-office.md rename to docs/decisions/adr/ADR-C5-CTRL-001 — FastAPI comme plan de contrôle en C5 et ERPLibre comme portail back-office.md diff --git a/docs/pile opérateur/adr/ADR-OBS-001 — Loki comme backend de logs de référence.md b/docs/decisions/adr/ADR-OBS-001 — Loki comme backend de logs de référence.md similarity index 100% rename from docs/pile opérateur/adr/ADR-OBS-001 — Loki comme backend de logs de référence.md rename to docs/decisions/adr/ADR-OBS-001 — Loki comme backend de logs de référence.md diff --git a/docs/pile opérateur/adr/ADR-OPS-STACK-001 — Pile opérateur de référence (C1–C5).md b/docs/decisions/adr/ADR-OPS-STACK-001 — Pile opérateur de référence (C1–C5).md similarity index 100% rename from docs/pile opérateur/adr/ADR-OPS-STACK-001 — Pile opérateur de référence (C1–C5).md rename to docs/decisions/adr/ADR-OPS-STACK-001 — Pile opérateur de référence (C1–C5).md diff --git a/docs/2026-02-03 - Présentation d'Alliance Boréale - Rencontres Linux.odp b/docs/diffusion/conferences/2026-02-03-Rencontres-Linux.odp similarity index 100% rename from docs/2026-02-03 - Présentation d'Alliance Boréale - Rencontres Linux.odp rename to docs/diffusion/conferences/2026-02-03-Rencontres-Linux.odp diff --git a/docs/Alliance_Boreale_Conferences.pptx b/docs/diffusion/conferences/Alliance_Boreale_Conferences.pptx similarity index 100% rename from docs/Alliance_Boreale_Conferences.pptx rename to docs/diffusion/conferences/Alliance_Boreale_Conferences.pptx diff --git a/docs/Conference_01_Rideau_ou_Coffre_Fort.pptx b/docs/diffusion/conferences/Conference_01_Rideau_ou_Coffre_Fort.pptx similarity index 100% rename from docs/Conference_01_Rideau_ou_Coffre_Fort.pptx rename to docs/diffusion/conferences/Conference_01_Rideau_ou_Coffre_Fort.pptx diff --git a/docs/Conference_02_Colonisation_Numerique.pptx b/docs/diffusion/conferences/Conference_02_Colonisation_Numerique.pptx similarity index 100% rename from docs/Conference_02_Colonisation_Numerique.pptx rename to docs/diffusion/conferences/Conference_02_Colonisation_Numerique.pptx diff --git a/docs/assets/logo-alliance-boreale.svg b/docs/diffusion/visuels/assets/logo-alliance-boreale.svg similarity index 100% rename from docs/assets/logo-alliance-boreale.svg rename to docs/diffusion/visuels/assets/logo-alliance-boreale.svg diff --git a/docs/40-ecosystemes/00-chezlepro.md b/docs/ecosystemes/00-chezlepro.md similarity index 100% rename from docs/40-ecosystemes/00-chezlepro.md rename to docs/ecosystemes/00-chezlepro.md diff --git a/docs/40-ecosystemes/01-technolibre.md b/docs/ecosystemes/01-technolibre.md similarity index 100% rename from docs/40-ecosystemes/01-technolibre.md rename to docs/ecosystemes/01-technolibre.md diff --git a/docs/40-ecosystemes/02-district16.md b/docs/ecosystemes/02-district16.md similarity index 100% rename from docs/40-ecosystemes/02-district16.md rename to docs/ecosystemes/02-district16.md diff --git a/docs/00-fondements/00 - Charte cognitive.md b/docs/fondements/00 - Charte cognitive.md similarity index 100% rename from docs/00-fondements/00 - Charte cognitive.md rename to docs/fondements/00 - Charte cognitive.md diff --git a/docs/00-fondements/00 - Glossaire.md b/docs/fondements/00 - Glossaire.md similarity index 100% rename from docs/00-fondements/00 - Glossaire.md rename to docs/fondements/00 - Glossaire.md diff --git a/docs/gouvernance/Certificate_Policy_Alliance_Boreale_v1.0.md b/docs/gouvernance.pre-migration-20260309-105729/Certificate_Policy_Alliance_Boreale_v1.0.md similarity index 100% rename from docs/gouvernance/Certificate_Policy_Alliance_Boreale_v1.0.md rename to docs/gouvernance.pre-migration-20260309-105729/Certificate_Policy_Alliance_Boreale_v1.0.md diff --git a/docs/gouvernance/Certification_Practice_Statement_Alliance_Boreale_v1.0.md b/docs/gouvernance.pre-migration-20260309-105729/Certification_Practice_Statement_Alliance_Boreale_v1.0.md similarity index 100% rename from docs/gouvernance/Certification_Practice_Statement_Alliance_Boreale_v1.0.md rename to docs/gouvernance.pre-migration-20260309-105729/Certification_Practice_Statement_Alliance_Boreale_v1.0.md diff --git a/docs/gouvernance/INSTRUCTIONS_CLAUDE_v2.1.md b/docs/gouvernance.pre-migration-20260309-105729/INSTRUCTIONS_CLAUDE_v2.1.md similarity index 100% rename from docs/gouvernance/INSTRUCTIONS_CLAUDE_v2.1.md rename to docs/gouvernance.pre-migration-20260309-105729/INSTRUCTIONS_CLAUDE_v2.1.md diff --git a/docs/gouvernance/Pacte_Collaboration_Claude_President.md b/docs/gouvernance.pre-migration-20260309-105729/Pacte_Collaboration_Claude_President.md similarity index 100% rename from docs/gouvernance/Pacte_Collaboration_Claude_President.md rename to docs/gouvernance.pre-migration-20260309-105729/Pacte_Collaboration_Claude_President.md diff --git a/docs/gouvernance/guide-utilisation-claude.md b/docs/gouvernance.pre-migration-20260309-105729/guide-utilisation-claude.md similarity index 100% rename from docs/gouvernance/guide-utilisation-claude.md rename to docs/gouvernance.pre-migration-20260309-105729/guide-utilisation-claude.md diff --git a/docs/gouvernance/pacte-conseiller-strategique.md b/docs/gouvernance.pre-migration-20260309-105729/pacte-conseiller-strategique.md similarity index 100% rename from docs/gouvernance/pacte-conseiller-strategique.md rename to docs/gouvernance.pre-migration-20260309-105729/pacte-conseiller-strategique.md diff --git a/docs/gouvernance/proposition_nouvelle_place.md b/docs/gouvernance.pre-migration-20260309-105729/proposition_nouvelle_place.md similarity index 100% rename from docs/gouvernance/proposition_nouvelle_place.md rename to docs/gouvernance.pre-migration-20260309-105729/proposition_nouvelle_place.md diff --git a/docs/constitution/00_Charte_Cognitive_Alliance_Boreale.md b/docs/gouvernance/00_Charte_Cognitive_Alliance_Boreale.md similarity index 100% rename from docs/constitution/00_Charte_Cognitive_Alliance_Boreale.md rename to docs/gouvernance/00_Charte_Cognitive_Alliance_Boreale.md diff --git a/docs/00_Etat_Depot_Vivant_v1.md b/docs/gouvernance/00_Etat_Depot_Vivant_v1.md similarity index 100% rename from docs/00_Etat_Depot_Vivant_v1.md rename to docs/gouvernance/00_Etat_Depot_Vivant_v1.md diff --git a/docs/constitution/01_charte_fondatrice_v_3.md b/docs/gouvernance/01_charte_fondatrice_v_3.md similarity index 100% rename from docs/constitution/01_charte_fondatrice_v_3.md rename to docs/gouvernance/01_charte_fondatrice_v_3.md diff --git a/docs/constitution/02_Reglement_de_Regie_Interne_biomimetique_autopoietique_v3.md b/docs/gouvernance/02_Reglement_de_Regie_Interne_biomimetique_autopoietique_v3.md similarity index 100% rename from docs/constitution/02_Reglement_de_Regie_Interne_biomimetique_autopoietique_v3.md rename to docs/gouvernance/02_Reglement_de_Regie_Interne_biomimetique_autopoietique_v3.md diff --git a/docs/constitution/03_Cadre_Conformite_Label_Prestige.md b/docs/gouvernance/03_Cadre_Conformite_Label_Prestige.md similarity index 100% rename from docs/constitution/03_Cadre_Conformite_Label_Prestige.md rename to docs/gouvernance/03_Cadre_Conformite_Label_Prestige.md diff --git a/docs/constitution/Registraire des entreprises - Reçu de paiement.pdf b/docs/gouvernance/Registraire des entreprises - Reçu de paiement.pdf similarity index 100% rename from docs/constitution/Registraire des entreprises - Reçu de paiement.pdf rename to docs/gouvernance/Registraire des entreprises - Reçu de paiement.pdf diff --git a/docs/engineering/git-conventions.md b/docs/guides/engineering/git-conventions.md similarity index 100% rename from docs/engineering/git-conventions.md rename to docs/guides/engineering/git-conventions.md diff --git a/docs/00_Glossaire_biomimetique_autopoietique_v3.md b/docs/modele/00_Glossaire_biomimetique_autopoietique_v3.md similarity index 100% rename from docs/00_Glossaire_biomimetique_autopoietique_v3.md rename to docs/modele/00_Glossaire_biomimetique_autopoietique_v3.md diff --git a/docs/00_Modele_8_couches_biomimetique_autopoietique_v3.md b/docs/modele/00_Modele_8_couches_biomimetique_autopoietique_v3.md similarity index 100% rename from docs/00_Modele_8_couches_biomimetique_autopoietique_v3.md rename to docs/modele/00_Modele_8_couches_biomimetique_autopoietique_v3.md diff --git a/docs/00_Nomenclature_v4.md b/docs/modele/00_Nomenclature_v4.md similarity index 100% rename from docs/00_Nomenclature_v4.md rename to docs/modele/00_Nomenclature_v4.md diff --git a/docs/06 - Controles_Conformite_Label_Prestige_par_Couche_v1.0.md b/docs/politiques/06-Controles_Conformite_Label_Prestige_par_Couche_v1.0.md similarity index 100% rename from docs/06 - Controles_Conformite_Label_Prestige_par_Couche_v1.0.md rename to docs/politiques/06-Controles_Conformite_Label_Prestige_par_Couche_v1.0.md diff --git a/docs/pile opérateur/pol/POL-OBS-LOG-001 — Contrat de journalisation (Logs) — Référence Loki.md b/docs/politiques/POL-OBS-LOG-001 — Contrat de journalisation (Logs) — Référence Loki.md similarity index 100% rename from docs/pile opérateur/pol/POL-OBS-LOG-001 — Contrat de journalisation (Logs) — Référence Loki.md rename to docs/politiques/POL-OBS-LOG-001 — Contrat de journalisation (Logs) — Référence Loki.md diff --git a/docs/pile opérateur/pol/POL-OPS-STACK-001 — Conformité pile opérateur (C1–C5).md b/docs/politiques/POL-OPS-STACK-001 — Conformité pile opérateur (C1–C5).md similarity index 100% rename from docs/pile opérateur/pol/POL-OPS-STACK-001 — Conformité pile opérateur (C1–C5).md rename to docs/politiques/POL-OPS-STACK-001 — Conformité pile opérateur (C1–C5).md diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..567e6fb --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,9 @@ +# Infrastructure + +Contient l'implémentation opératoire : +- ansible +- opentofu +- cloud-init +- templates +- netbox + diff --git a/infrastructure/ansible/.gitignore b/infrastructure/ansible/.gitignore new file mode 100644 index 0000000..7eaa6e2 --- /dev/null +++ b/infrastructure/ansible/.gitignore @@ -0,0 +1,2 @@ +*.retry +.ansible/ diff --git a/infrastructure/ansible/README.md b/infrastructure/ansible/README.md new file mode 100644 index 0000000..3044f54 --- /dev/null +++ b/infrastructure/ansible/README.md @@ -0,0 +1,81 @@ +# ansible/ + +Sous-arbre Ansible de l'Alliance Boréale pour la **mise en conformité post-provisionnement** des VMs. + +## Responsabilités + +Ce sous-arbre prend des VMs déjà créées et accessibles en SSH, puis applique : +- la baseline packages commune ; +- le hardening OS ; +- le pare-feu local ; +- les mises à jour de sécurité automatiques ; +- l'agent Icinga2 ; +- l'agent Wazuh en option. + +## Hors périmètre + +Ne relèvent **pas** de ce sous-arbre : +- la création des VMs ; +- le clonage de templates ; +- la gestion Proxmox ; +- la génération initiale des ressources cloud-init. + +Ces responsabilités appartiennent au sous-arbre `opentofu/` du dépôt principal. + +## Structure + +```text +ansible/ +├── ansible.cfg +├── requirements.yml +├── inventories/prod/ +│ ├── hosts.yml +│ ├── group_vars/all.yml +│ └── host_vars/ +├── playbooks/ +│ ├── site.yml +│ ├── baseline.yml +│ └── hardening.yml +├── roles/ +│ ├── baseline_common/ +│ ├── hardening_common/ +│ ├── firewall_nftables/ +│ ├── monitoring_icinga_agent/ +│ └── security_wazuh_agent/ +└── docs/ +``` + +## Démarrage + +1. Laisser OpenTofu générer `inventories/prod/hosts.yml`. +2. Ajuster `inventories/prod/group_vars/all.yml`. +3. Vérifier les réseaux d'administration autorisés dans le firewall. +4. Lancer : + +```bash +ansible-playbook playbooks/site.yml +``` + + +## Intégration supplémentaire issue de `ansible2.zip` + +Cette convergence incorpore maintenant une seconde lignée orientée services : +- `postgresql` +- `keycloak` +- `forgejo` +- `powerdns_authoritative` + +Playbooks ajoutés : +- `playbooks/services/phase1_dns.yml` +- `playbooks/services/phase2_identity_forge.yml` + +Un exemple d'inventaire généré par OpenTofu est fourni dans `inventories/prod/examples/tofu_generated.example.yml`. +Des exemples de `host_vars` et de `vault/` sont aussi inclus pour accélérer l'adaptation. + + +Les playbooks de baseline et hardening ciblent `all`. Les playbooks de services ciblent directement les groupes OpenTofu : `c3_services`, `c4_forge`, `c5_runner`, `c7_tenants`. + + +## Inventaire OpenTofu recommandé + +Le dépôt consomme un inventaire généré par OpenTofu avec un groupe racine `boreale_all`, des groupes de couche (`c3_supervision`, `c3_core`, `c4_forge`, `c5_pivot`) et un sous-arbre `tenants/c7_products`. Des métadonnées minimales par hôte sont attendues : `boreale_layer`, `boreale_role`, `boreale_env`, `boreale_managed_by`. diff --git a/infrastructure/ansible/ansible.cfg b/infrastructure/ansible/ansible.cfg new file mode 100644 index 0000000..d0b424b --- /dev/null +++ b/infrastructure/ansible/ansible.cfg @@ -0,0 +1,19 @@ +[defaults] +inventory = inventories/prod/hosts.yml +roles_path = roles +host_key_checking = False +retry_files_enabled = False +interpreter_python = auto_silent +stdout_callback = ansible.builtin.default +result_format = yaml +bin_ansible_callbacks = True +timeout = 30 +forks = 20 +gathering = smart +fact_caching = jsonfile +fact_caching_connection = .ansible/facts +fact_caching_timeout = 7200 + +[ssh_connection] +pipelining = True +scp_if_ssh = True diff --git a/infrastructure/ansible/docs/CONVERGENCE_NOTES.md b/infrastructure/ansible/docs/CONVERGENCE_NOTES.md new file mode 100644 index 0000000..57e926f --- /dev/null +++ b/infrastructure/ansible/docs/CONVERGENCE_NOTES.md @@ -0,0 +1,51 @@ +# Rapport de convergence — sous-arbre ansible/ + +## Décision d'architecture + +Le dépôt Ansible fusionné a été recentré pour jouer un rôle unique : +**configurer et durcir des VMs déjà provisionnées**. + +La création des VMs, le template Debian 12, le clonage et l'écriture de l'inventaire relèvent désormais du dépôt OpenTofu. + +## Ce qui a été retenu + +- `baseline_common` +- `hardening_common` +- `firewall_nftables` +- `monitoring_icinga_agent` +- `security_wazuh_agent` +- inventaire YAML simple +- playbooks séparés `baseline`, `hardening`, `site` + +## Ce qui a été retiré du chemin principal + +- `proxmox_debian12_template` +- `proxmox_vm_clone` +- `playbooks/proxmox/*` + +## Logique cible + +Le sous-arbre `ansible/` vise une baseline VM commune : +1. petite ; +2. reproductible ; +3. fermée par défaut ; +4. compatible avec l'inventaire généré par OpenTofu ; +5. prête à recevoir ensuite les rôles applicatifs C2/C3/C4/C5. + + +## Intégration de `ansible2.zip` + +Apports retenus : +- rôle `common` utilisé comme source secondaire d'idées, déjà absorbées en grande partie par `baseline_common` + `hardening_common` + `firewall_nftables` +- rôles applicatifs `postgresql`, `keycloak`, `forgejo`, `powerdns-authoritative` intégrés au dépôt convergé +- exemple concret d'inventaire OpenTofu (`inventory/tofu_generated.yml`) conservé comme gabarit +- exemples de vault Phase 1 / Phase 2 conservés + +Décision d'architecture : +- le rôle `common` de `ansible2.zip` n'est pas repris tel quel pour éviter un doublon avec la baseline convergée +- les playbooks originaux ne sont pas repris à l'identique; ils sont réémis sous `playbooks/services/` pour s'aligner avec la séparation OpenTofu → inventaire, puis Ansible → convergence + service + + +## Inventaire OpenTofu recommandé + +Le dépôt consomme un inventaire généré par OpenTofu avec un groupe racine `boreale_all`, des groupes de couche (`c3_supervision`, `c3_core`, `c4_forge`, `c5_pivot`) et un sous-arbre `tenants/c7_products`. Des métadonnées minimales par hôte sont attendues : `boreale_layer`, `boreale_role`, `boreale_env`, `boreale_managed_by`. diff --git a/infrastructure/ansible/inventories/prod/examples/tofu_generated.example.yml b/infrastructure/ansible/inventories/prod/examples/tofu_generated.example.yml new file mode 100644 index 0000000..15d9882 --- /dev/null +++ b/infrastructure/ansible/inventories/prod/examples/tofu_generated.example.yml @@ -0,0 +1,66 @@ +# ══════════════════════════════════════════════════════════════════ +# Inventaire Ansible — généré par OpenTofu +# NE PAS MODIFIER MANUELLEMENT — regénéré à chaque tofu apply +# Forme cible recommandée pour l'Alliance Boréale +# ══════════════════════════════════════════════════════════════════ +--- +all: + children: + boreale_all: + children: + c3_supervision: + hosts: + c3-icinga-01: + ansible_host: 192.168.10.31 + icinga_role: master + boreale_layer: c3 + boreale_role: icinga + boreale_env: prod + boreale_managed_by: opentofu + + c3_core: + hosts: + c3-netbox-01: + ansible_host: 192.168.10.30 + boreale_layer: c3 + boreale_role: netbox + boreale_env: prod + boreale_managed_by: opentofu + + c4_forge: + hosts: + c4-forgejo-01: + ansible_host: 192.168.10.40 + boreale_layer: c4 + boreale_role: forgejo + boreale_env: prod + boreale_managed_by: opentofu + + c5_pivot: + hosts: + c5-runner-01: + ansible_host: 192.168.10.50 + boreale_layer: c5 + boreale_role: runner + boreale_env: prod + boreale_managed_by: opentofu + + tenants: + children: + c7_products: + hosts: + c7-87-16-01: + ansible_host: 192.168.12.45 + boreale_layer: c7 + boreale_role: tenant_app + boreale_env: prod + boreale_managed_by: opentofu + c7-life-noc-01: + ansible_host: 192.168.12.40 + boreale_layer: c7 + boreale_role: tenant_app + boreale_env: prod + boreale_managed_by: opentofu + vars: + ansible_ssh_private_key_file: ~/.ssh/id_ed25519_ansible_chezlepro + ansible_user: ansible diff --git a/infrastructure/ansible/inventories/prod/group_vars/all.yml b/infrastructure/ansible/inventories/prod/group_vars/all.yml new file mode 100644 index 0000000..e83f9b4 --- /dev/null +++ b/infrastructure/ansible/inventories/prod/group_vars/all.yml @@ -0,0 +1,83 @@ +--- +# Paramètres globaux de la baseline commune Alliance Boréale +# Les hôtes et groupes sont générés par OpenTofu dans inventories/prod/hosts.yml + +ab_domain: chezlepro.local +ab_dns_servers: + - 192.168.10.30 +ab_admin_networks: + - 192.168.10.0/24 + - 192.168.12.0/24 +ab_ntp_server: 192.168.10.1 +ab_timezone: America/Montreal +ab_enable_wazuh_agent: false + +# Baseline commune +baseline_common_enabled: true +baseline_common_upgrade: true +baseline_common_remove_packages: [] +baseline_common_packages: + - apt-listchanges + - unattended-upgrades + - needrestart + - debsecan + - ca-certificates + - curl + - jq + - vim + - less + - git + - sudo + - rsync + - python3 + - python3-apt + - acl + - chrony + - qemu-guest-agent + - nftables + - apparmor + - apparmor-utils + - fail2ban + - debian-archive-keyring + +baseline_common_manage_sources: false + +# Hardening commun +hardening_common_enabled: true +hardening_common_fail2ban_enabled: true +hardening_common_unattended_upgrades_enabled: true +hardening_common_debsecan_enabled: true +hardening_common_apparmor_enabled: true +hardening_common_journald_enabled: true +hardening_common_journald_system_max_use: 512M +hardening_common_journald_runtime_max_use: 128M +hardening_common_ssh_port: 22 +hardening_common_ssh_allow_groups: + - sudo +hardening_common_ssh_listen_addresses: [] +hardening_common_fail2ban_sshd_enabled: true + +# Pare-feu local +firewall_nftables_enabled: true +firewall_nftables_allow_ipv6: true +firewall_nftables_policy_input: drop +firewall_nftables_policy_forward: drop +firewall_nftables_policy_output: accept +firewall_nftables_allowed_tcp_in: + - dport: 22 + src: 192.168.10.0/24 + - dport: 22 + src: 192.168.12.0/24 +firewall_nftables_allowed_udp_in: [] +firewall_nftables_allow_icmp: true + +# Monitoring commun +monitoring_icinga_agent_enabled: true +monitoring_icinga_agent_master: "{{ hostvars['c3-icinga-01'].ansible_host | default('c3-icinga-01') }}" +monitoring_icinga_agent_parent_zone: master +monitoring_icinga_agent_zone: "{{ inventory_hostname }}" + +# Wazuh optionnel +security_wazuh_agent_enabled: "{{ ab_enable_wazuh_agent }}" +security_wazuh_agent_manager: c3-wazuh-01 +security_wazuh_agent_registration_password: "" diff --git a/infrastructure/ansible/inventories/prod/group_vars/c3_supervision.yml b/infrastructure/ansible/inventories/prod/group_vars/c3_supervision.yml new file mode 100644 index 0000000..0254529 --- /dev/null +++ b/infrastructure/ansible/inventories/prod/group_vars/c3_supervision.yml @@ -0,0 +1,2 @@ +--- +monitoring_icinga_agent_enabled: false diff --git a/infrastructure/ansible/inventories/prod/group_vars/c5_pivot.yml b/infrastructure/ansible/inventories/prod/group_vars/c5_pivot.yml new file mode 100644 index 0000000..06a6b2c --- /dev/null +++ b/infrastructure/ansible/inventories/prod/group_vars/c5_pivot.yml @@ -0,0 +1,2 @@ +--- +ab_enable_wazuh_agent: true diff --git a/infrastructure/ansible/inventories/prod/group_vars/c7_products.yml b/infrastructure/ansible/inventories/prod/group_vars/c7_products.yml new file mode 100644 index 0000000..328f80a --- /dev/null +++ b/infrastructure/ansible/inventories/prod/group_vars/c7_products.yml @@ -0,0 +1,2 @@ +--- +# Override example for product-facing tenant nodes diff --git a/infrastructure/ansible/inventories/prod/host_vars/examples/c3-keycloak-01.yml b/infrastructure/ansible/inventories/prod/host_vars/examples/c3-keycloak-01.yml new file mode 100644 index 0000000..cb3c335 --- /dev/null +++ b/infrastructure/ansible/inventories/prod/host_vars/examples/c3-keycloak-01.yml @@ -0,0 +1,20 @@ +--- +keycloak: + hostname: sso.example.internal + admin_user: admin + admin_password: "{{ vault_keycloak_admin_password }}" + db_name: keycloak + db_user: keycloak + db_password: "{{ vault_keycloak_db_password }}" + http_port: 8080 + https_enabled: false + +postgresql: + version: 15 + listen_addresses: localhost + databases: + - name: keycloak + owner: keycloak + users: + - name: keycloak + password: "{{ vault_keycloak_db_password }}" diff --git a/infrastructure/ansible/inventories/prod/host_vars/examples/c3-pdns-01.yml b/infrastructure/ansible/inventories/prod/host_vars/examples/c3-pdns-01.yml new file mode 100644 index 0000000..884de54 --- /dev/null +++ b/infrastructure/ansible/inventories/prod/host_vars/examples/c3-pdns-01.yml @@ -0,0 +1,20 @@ +--- +pdns: + api_key: "{{ vault_pdns_api_key }}" + api_enabled: true + webserver_address: 0.0.0.0 + webserver_allow_from: + - 127.0.0.1 + gpgsql_dbname: pdns + gpgsql_user: pdns + gpgsql_password: "{{ vault_postgresql_pdns_password }}" + +postgresql: + version: 15 + listen_addresses: localhost + databases: + - name: pdns + owner: pdns + users: + - name: pdns + password: "{{ vault_postgresql_pdns_password }}" diff --git a/infrastructure/ansible/inventories/prod/host_vars/examples/c4-forgejo-01.yml b/infrastructure/ansible/inventories/prod/host_vars/examples/c4-forgejo-01.yml new file mode 100644 index 0000000..6758132 --- /dev/null +++ b/infrastructure/ansible/inventories/prod/host_vars/examples/c4-forgejo-01.yml @@ -0,0 +1,23 @@ +--- +forgejo: + hostname: git.example.internal + ssh_port: 2222 + admin_username: admin + admin_email: admin@example.internal + admin_password: "{{ vault_forgejo_admin_password }}" + db_name: forgejo + db_user: forgejo + db_password: "{{ vault_forgejo_db_password }}" + secret_key: "{{ vault_forgejo_secret_key }}" + internal_token: "{{ vault_forgejo_internal_token }}" + jwt_secret: "{{ vault_forgejo_jwt_secret }}" + +postgresql: + version: 15 + listen_addresses: localhost + databases: + - name: forgejo + owner: forgejo + users: + - name: forgejo + password: "{{ vault_forgejo_db_password }}" diff --git a/infrastructure/ansible/inventories/prod/hosts.yml b/infrastructure/ansible/inventories/prod/hosts.yml new file mode 100644 index 0000000..7314d3e --- /dev/null +++ b/infrastructure/ansible/inventories/prod/hosts.yml @@ -0,0 +1,50 @@ +# ══════════════════════════════════════════════════════════════════ +# Inventaire Ansible — généré par OpenTofu +# NE PAS MODIFIER MANUELLEMENT — regénéré à chaque tofu apply +# ══════════════════════════════════════════════════════════════════ +--- +"all": + "children": + "boreale_all": + "children": + "c2_edge": + "hosts": {} + "c3_core": + "hosts": {} + "c3_identity": + "hosts": {} + "c3_supervision": + "hosts": {} + "c4_forge": + "hosts": {} + "c5_pivot": + "hosts": {} + "tenants": + "children": + "c6_services": + "hosts": {} + "c7_products": + "hosts": + "c7-87-16-02": + "ansible_host": "192.168.12.46" + "boreale_env": null + "boreale_layer": "c7" + "boreale_managed_by": "opentofu" + "boreale_role": "87-16" + "c7-gestion-01": + "ansible_host": "192.168.8.30" + "boreale_env": null + "boreale_layer": "c7" + "boreale_managed_by": "opentofu" + "boreale_role": "gestion" + "c7-life-noc-01": + "ansible_host": "192.168.12.40" + "boreale_env": null + "boreale_layer": "c7" + "boreale_managed_by": "opentofu" + "boreale_role": "life-noc" + "c8_knowledge": + "hosts": {} + "vars": + "ansible_ssh_private_key_file": "~/.ssh/id_ed25519_ansible_chezlepro" + "ansible_user": "ansible" diff --git a/infrastructure/ansible/playbooks/baseline.yml b/infrastructure/ansible/playbooks/baseline.yml new file mode 100644 index 0000000..8eac6be --- /dev/null +++ b/infrastructure/ansible/playbooks/baseline.yml @@ -0,0 +1,6 @@ +--- +- name: Baseline packages et services communs + hosts: boreale_all + become: true + roles: + - role: baseline_common diff --git a/infrastructure/ansible/playbooks/hardening.yml b/infrastructure/ansible/playbooks/hardening.yml new file mode 100644 index 0000000..47ea76d --- /dev/null +++ b/infrastructure/ansible/playbooks/hardening.yml @@ -0,0 +1,9 @@ +--- +- name: Hardening commun Alliance Boréale + hosts: boreale_all + become: true + roles: + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: security_wazuh_agent diff --git a/infrastructure/ansible/playbooks/services/phase1_dns.yml b/infrastructure/ansible/playbooks/services/phase1_dns.yml new file mode 100644 index 0000000..9bb3f99 --- /dev/null +++ b/infrastructure/ansible/playbooks/services/phase1_dns.yml @@ -0,0 +1,11 @@ +--- +- name: Services C3 coeur / réseau + hosts: c3_core + become: true + roles: + - role: baseline_common + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: postgresql + - role: powerdns_authoritative diff --git a/infrastructure/ansible/playbooks/services/phase2_identity_forge.yml b/infrastructure/ansible/playbooks/services/phase2_identity_forge.yml new file mode 100644 index 0000000..392556b --- /dev/null +++ b/infrastructure/ansible/playbooks/services/phase2_identity_forge.yml @@ -0,0 +1,22 @@ +--- +- name: Services C3 coeur / identité + hosts: c3_core + become: true + roles: + - role: baseline_common + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: postgresql + - role: keycloak + +- name: Services C4 forge + hosts: c4_forge + become: true + roles: + - role: baseline_common + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: postgresql + - role: forgejo diff --git a/infrastructure/ansible/playbooks/services/phase3_runner.yml b/infrastructure/ansible/playbooks/services/phase3_runner.yml new file mode 100644 index 0000000..f52b1f3 --- /dev/null +++ b/infrastructure/ansible/playbooks/services/phase3_runner.yml @@ -0,0 +1,10 @@ +--- +- name: Services C5 pivot / exécution + hosts: c5_pivot + become: true + roles: + - role: baseline_common + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: security_wazuh_agent diff --git a/infrastructure/ansible/playbooks/services/phase7_tenants.yml b/infrastructure/ansible/playbooks/services/phase7_tenants.yml new file mode 100644 index 0000000..e5147e6 --- /dev/null +++ b/infrastructure/ansible/playbooks/services/phase7_tenants.yml @@ -0,0 +1,10 @@ +--- +- name: Services tenants C7 + hosts: c7_products + become: true + roles: + - role: baseline_common + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: security_wazuh_agent diff --git a/infrastructure/ansible/playbooks/site.yml b/infrastructure/ansible/playbooks/site.yml new file mode 100644 index 0000000..c307369 --- /dev/null +++ b/infrastructure/ansible/playbooks/site.yml @@ -0,0 +1,10 @@ +--- +- name: Baseline complète Alliance Boréale + hosts: boreale_all + become: true + roles: + - role: baseline_common + - role: hardening_common + - role: firewall_nftables + - role: monitoring_icinga_agent + - role: security_wazuh_agent diff --git a/infrastructure/ansible/requirements.yml b/infrastructure/ansible/requirements.yml new file mode 100644 index 0000000..bd6dd7f --- /dev/null +++ b/infrastructure/ansible/requirements.yml @@ -0,0 +1,5 @@ +--- +collections: + - name: ansible.posix + - name: community.general + - name: community.postgresql diff --git a/infrastructure/ansible/roles/baseline_common/defaults/main.yml b/infrastructure/ansible/roles/baseline_common/defaults/main.yml new file mode 100644 index 0000000..c79cf4e --- /dev/null +++ b/infrastructure/ansible/roles/baseline_common/defaults/main.yml @@ -0,0 +1,8 @@ +--- +baseline_common_enabled: true +baseline_common_upgrade: true +baseline_common_packages: [] +baseline_common_remove_packages: [] +baseline_common_manage_chrony: true +baseline_common_manage_timezone: true +baseline_common_qemu_guest_agent_service: qemu-guest-agent diff --git a/infrastructure/ansible/roles/baseline_common/handlers/main.yml b/infrastructure/ansible/roles/baseline_common/handlers/main.yml new file mode 100644 index 0000000..52e5605 --- /dev/null +++ b/infrastructure/ansible/roles/baseline_common/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Restart chrony + ansible.builtin.systemd: + name: chrony + state: restarted + +- name: Restart systemd-timesyncd if present + ansible.builtin.systemd: + name: systemd-timesyncd + state: stopped + enabled: false + failed_when: false diff --git a/infrastructure/ansible/roles/baseline_common/tasks/main.yml b/infrastructure/ansible/roles/baseline_common/tasks/main.yml new file mode 100644 index 0000000..1e8509c --- /dev/null +++ b/infrastructure/ansible/roles/baseline_common/tasks/main.yml @@ -0,0 +1,61 @@ +--- +- name: Gate + ansible.builtin.meta: end_host + when: not baseline_common_enabled | bool + +- name: Mettre à jour le cache APT + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + +- name: Installer les paquets communs + ansible.builtin.apt: + name: "{{ baseline_common_packages }}" + state: present + +- name: Retirer les paquets explicitement exclus + ansible.builtin.apt: + name: "{{ baseline_common_remove_packages }}" + state: absent + purge: true + when: baseline_common_remove_packages | length > 0 + +- name: Appliquer les mises à jour de distribution + ansible.builtin.apt: + upgrade: dist + when: baseline_common_upgrade | bool + +- name: Régler le fuseau horaire + community.general.timezone: + name: "{{ ab_timezone }}" + when: baseline_common_manage_timezone | bool + +- name: Désactiver systemd-timesyncd si présent + ansible.builtin.systemd: + name: systemd-timesyncd + enabled: false + state: stopped + failed_when: false + when: baseline_common_manage_chrony | bool + +- name: Déployer la configuration chrony + ansible.builtin.template: + src: chrony.conf.j2 + dest: /etc/chrony/chrony.conf + mode: '0644' + when: baseline_common_manage_chrony | bool + notify: Restart chrony + +- name: Activer chrony + ansible.builtin.systemd: + name: chrony + enabled: true + state: started + when: baseline_common_manage_chrony | bool + +- name: Activer qemu-guest-agent si présent + ansible.builtin.systemd: + name: "{{ baseline_common_qemu_guest_agent_service }}" + enabled: true + state: started + failed_when: false diff --git a/infrastructure/ansible/roles/baseline_common/templates/chrony.conf.j2 b/infrastructure/ansible/roles/baseline_common/templates/chrony.conf.j2 new file mode 100644 index 0000000..402db33 --- /dev/null +++ b/infrastructure/ansible/roles/baseline_common/templates/chrony.conf.j2 @@ -0,0 +1,12 @@ +# Ansible managed: baseline_common + +pool 2.debian.pool.ntp.org iburst maxsources 2 +server {{ ab_ntp_server }} iburst prefer +{% for dns in ab_dns_servers | default([]) %} +# DNS interne disponible: {{ dns }} +{% endfor %} + +driftfile /var/lib/chrony/chrony.drift +rtcsync +makestep 1 3 +logdir /var/log/chrony diff --git a/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml b/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml new file mode 100644 index 0000000..b9d1050 --- /dev/null +++ b/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml @@ -0,0 +1,9 @@ +--- +firewall_nftables_enabled: true +firewall_nftables_allow_ipv6: true +firewall_nftables_allow_icmp: true +firewall_nftables_policy_input: drop +firewall_nftables_policy_forward: drop +firewall_nftables_policy_output: accept +firewall_nftables_allowed_tcp_in: [22,443] +firewall_nftables_allowed_udp_in: [] diff --git a/infrastructure/ansible/roles/firewall_nftables/handlers/main.yml b/infrastructure/ansible/roles/firewall_nftables/handlers/main.yml new file mode 100644 index 0000000..69e8f72 --- /dev/null +++ b/infrastructure/ansible/roles/firewall_nftables/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/infrastructure/ansible/roles/firewall_nftables/tasks/main.yml b/infrastructure/ansible/roles/firewall_nftables/tasks/main.yml new file mode 100644 index 0000000..3e36cce --- /dev/null +++ b/infrastructure/ansible/roles/firewall_nftables/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Gate + ansible.builtin.meta: end_host + when: not firewall_nftables_enabled | bool + +- name: Installer nftables + ansible.builtin.apt: + name: nftables + state: present + update_cache: true + +- name: Déployer nftables + ansible.builtin.template: + src: nftables.conf.j2 + dest: /etc/nftables.conf + mode: '0644' + notify: Reload nftables + +- name: Activer nftables + ansible.builtin.systemd: + name: nftables + enabled: true + state: started diff --git a/infrastructure/ansible/roles/firewall_nftables/templates/nftables.conf.j2 b/infrastructure/ansible/roles/firewall_nftables/templates/nftables.conf.j2 new file mode 100644 index 0000000..e2edcdd --- /dev/null +++ b/infrastructure/ansible/roles/firewall_nftables/templates/nftables.conf.j2 @@ -0,0 +1,34 @@ +#!/usr/sbin/nft -f +flush ruleset + +table inet filter { + chain input { + type filter hook input priority 0; + policy {{ firewall_nftables_policy_input }}; + + iif lo accept + ct state established,related accept +{% if firewall_nftables_allow_icmp %} + ip protocol icmp accept +{% if firewall_nftables_allow_ipv6 %} + ip6 nexthdr icmpv6 accept +{% endif %} +{% endif %} +{% for rule in firewall_nftables_allowed_tcp_in %} + tcp dport {{ rule.dport }} ip saddr {{ rule.src }} accept +{% endfor %} +{% for rule in firewall_nftables_allowed_udp_in %} + udp dport {{ rule.dport }} ip saddr {{ rule.src }} accept +{% endfor %} + } + + chain forward { + type filter hook forward priority 0; + policy {{ firewall_nftables_policy_forward }}; + } + + chain output { + type filter hook output priority 0; + policy {{ firewall_nftables_policy_output }}; + } +} diff --git a/ansible/roles/forgejo/handlers/main.yml b/infrastructure/ansible/roles/forgejo/handlers/main.yml similarity index 100% rename from ansible/roles/forgejo/handlers/main.yml rename to infrastructure/ansible/roles/forgejo/handlers/main.yml diff --git a/ansible/roles/forgejo/tasks/database.yml b/infrastructure/ansible/roles/forgejo/tasks/database.yml similarity index 100% rename from ansible/roles/forgejo/tasks/database.yml rename to infrastructure/ansible/roles/forgejo/tasks/database.yml diff --git a/ansible/roles/forgejo/tasks/main.yml b/infrastructure/ansible/roles/forgejo/tasks/main.yml similarity index 100% rename from ansible/roles/forgejo/tasks/main.yml rename to infrastructure/ansible/roles/forgejo/tasks/main.yml diff --git a/ansible/roles/forgejo/templates/app.ini.j2 b/infrastructure/ansible/roles/forgejo/templates/app.ini.j2 similarity index 100% rename from ansible/roles/forgejo/templates/app.ini.j2 rename to infrastructure/ansible/roles/forgejo/templates/app.ini.j2 diff --git a/ansible/roles/forgejo/templates/forgejo.service.j2 b/infrastructure/ansible/roles/forgejo/templates/forgejo.service.j2 similarity index 100% rename from ansible/roles/forgejo/templates/forgejo.service.j2 rename to infrastructure/ansible/roles/forgejo/templates/forgejo.service.j2 diff --git a/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 b/infrastructure/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 similarity index 100% rename from ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 rename to infrastructure/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 diff --git a/infrastructure/ansible/roles/hardening_common/defaults/main.yml b/infrastructure/ansible/roles/hardening_common/defaults/main.yml new file mode 100644 index 0000000..f3765b2 --- /dev/null +++ b/infrastructure/ansible/roles/hardening_common/defaults/main.yml @@ -0,0 +1,58 @@ +--- +hardening_common_enabled: true +hardening_common_fail2ban_enabled: true +hardening_common_fail2ban_sshd_enabled: true +hardening_common_unattended_upgrades_enabled: true +hardening_common_debsecan_enabled: true +hardening_common_apparmor_enabled: true +hardening_common_journald_enabled: true +hardening_common_journald_system_max_use: 512M +hardening_common_journald_runtime_max_use: 128M +hardening_common_ssh_port: 22 +hardening_common_ssh_allow_groups: [] +hardening_common_ssh_listen_addresses: [] +hardening_common_sysctl: + fs.protected_fifos: 2 + fs.protected_hardlinks: 1 + fs.protected_regular: 2 + fs.protected_symlinks: 1 + kernel.dmesg_restrict: 1 + kernel.kptr_restrict: 2 + kernel.randomize_va_space: 2 + kernel.sysrq: 0 + net.ipv4.conf.all.accept_redirects: 0 + net.ipv4.conf.default.accept_redirects: 0 + net.ipv4.conf.all.accept_source_route: 0 + net.ipv4.conf.default.accept_source_route: 0 + net.ipv4.conf.all.log_martians: 1 + net.ipv4.conf.default.log_martians: 1 + net.ipv4.conf.all.rp_filter: 1 + net.ipv4.conf.default.rp_filter: 1 + net.ipv4.conf.all.send_redirects: 0 + net.ipv4.conf.default.send_redirects: 0 + net.ipv4.icmp_echo_ignore_broadcasts: 1 + net.ipv4.ip_forward: 0 + net.ipv4.tcp_syncookies: 1 + net.ipv6.conf.all.accept_redirects: 0 + net.ipv6.conf.default.accept_redirects: 0 + net.ipv6.conf.all.accept_source_route: 0 + net.ipv6.conf.default.accept_source_route: 0 +hardening_common_sshd_settings: + PasswordAuthentication: 'no' + PermitRootLogin: 'no' + KbdInteractiveAuthentication: 'no' + ChallengeResponseAuthentication: 'no' + PubkeyAuthentication: 'yes' + X11Forwarding: 'no' + MaxAuthTries: '3' + LoginGraceTime: '30' + AllowAgentForwarding: 'no' + AllowTcpForwarding: 'no' + TCPKeepAlive: 'no' + ClientAliveInterval: '300' + ClientAliveCountMax: '2' + MaxSessions: '10' + MaxStartups: '10:30:60' + Protocol: '2' + Port: '22' + UsePAM: 'yes' diff --git a/infrastructure/ansible/roles/hardening_common/handlers/main.yml b/infrastructure/ansible/roles/hardening_common/handlers/main.yml new file mode 100644 index 0000000..b36f530 --- /dev/null +++ b/infrastructure/ansible/roles/hardening_common/handlers/main.yml @@ -0,0 +1,15 @@ +--- +- name: Reload ssh + ansible.builtin.systemd: + name: ssh + state: reloaded + +- name: Restart journald + ansible.builtin.systemd: + name: systemd-journald + state: restarted + +- name: Restart fail2ban + ansible.builtin.systemd: + name: fail2ban + state: restarted diff --git a/infrastructure/ansible/roles/hardening_common/tasks/main.yml b/infrastructure/ansible/roles/hardening_common/tasks/main.yml new file mode 100644 index 0000000..365ec0a --- /dev/null +++ b/infrastructure/ansible/roles/hardening_common/tasks/main.yml @@ -0,0 +1,95 @@ +--- +- name: Gate + ansible.builtin.meta: end_host + when: not hardening_common_enabled | bool + +- name: Appliquer le socle sysctl + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + state: present + sysctl_set: true + reload: true + loop: "{{ hardening_common_sysctl | dict2items }}" + +- name: Déployer le drop-in SSH durci + ansible.builtin.template: + src: sshd_boreale_hardening.conf.j2 + dest: /etc/ssh/sshd_config.d/99-boreale-hardening.conf + mode: '0644' + validate: '/usr/sbin/sshd -T -f %s' + notify: Reload ssh + +- name: Créer le répertoire des drop-ins journald + ansible.builtin.file: + path: /etc/systemd/journald.conf.d + state: directory + owner: root + group: root + mode: "0755" + +- name: Déployer le drop-in journald + ansible.builtin.template: + src: journald-boreale.conf.j2 + dest: /etc/systemd/journald.conf.d/99-boreale.conf + owner: root + group: root + mode: "0644" + notify: Restart journald + +- name: Installer fail2ban + ansible.builtin.apt: + name: fail2ban + state: present + when: hardening_common_fail2ban_enabled | bool + +- name: Déployer la jail sshd fail2ban + ansible.builtin.template: + src: jail-sshd.local.j2 + dest: /etc/fail2ban/jail.d/sshd.local + mode: '0644' + when: + - hardening_common_fail2ban_enabled | bool + - hardening_common_fail2ban_sshd_enabled | bool + notify: Restart fail2ban + +- name: Activer fail2ban + ansible.builtin.systemd: + name: fail2ban + enabled: true + state: started + when: hardening_common_fail2ban_enabled | bool + +- name: Déployer unattended-upgrades auto + ansible.builtin.copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + mode: '0644' + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; + APT::Periodic::AutocleanInterval "7"; + when: hardening_common_unattended_upgrades_enabled | bool + +- name: Déployer unattended-upgrades de base + ansible.builtin.copy: + dest: /etc/apt/apt.conf.d/52unattended-upgrades-local + mode: '0644' + content: | + Unattended-Upgrade::Remove-Unused-Dependencies "true"; + Unattended-Upgrade::Automatic-Reboot "false"; + Unattended-Upgrade::Automatic-Reboot-WithUsers "false"; + when: hardening_common_unattended_upgrades_enabled | bool + +- name: Installer debsecan + ansible.builtin.apt: + name: debsecan + state: present + when: hardening_common_debsecan_enabled | bool + +- name: Activer AppArmor + ansible.builtin.systemd: + name: apparmor + enabled: true + state: started + failed_when: false + when: hardening_common_apparmor_enabled | bool diff --git a/infrastructure/ansible/roles/hardening_common/templates/jail-sshd.local.j2 b/infrastructure/ansible/roles/hardening_common/templates/jail-sshd.local.j2 new file mode 100644 index 0000000..81eec90 --- /dev/null +++ b/infrastructure/ansible/roles/hardening_common/templates/jail-sshd.local.j2 @@ -0,0 +1,7 @@ +[sshd] +enabled = true +port = {{ hardening_common_ssh_port }} +backend = systemd +maxretry = 4 +findtime = 10m +bantime = 1h diff --git a/infrastructure/ansible/roles/hardening_common/templates/journald-boreale.conf.j2 b/infrastructure/ansible/roles/hardening_common/templates/journald-boreale.conf.j2 new file mode 100644 index 0000000..e3276cf --- /dev/null +++ b/infrastructure/ansible/roles/hardening_common/templates/journald-boreale.conf.j2 @@ -0,0 +1,6 @@ +[Journal] +Storage=persistent +Compress=yes +SystemMaxUse={{ hardening_common_journald_system_max_use }} +RuntimeMaxUse={{ hardening_common_journald_runtime_max_use }} +ForwardToSyslog=no diff --git a/infrastructure/ansible/roles/hardening_common/templates/sshd_boreale_hardening.conf.j2 b/infrastructure/ansible/roles/hardening_common/templates/sshd_boreale_hardening.conf.j2 new file mode 100644 index 0000000..d71be7c --- /dev/null +++ b/infrastructure/ansible/roles/hardening_common/templates/sshd_boreale_hardening.conf.j2 @@ -0,0 +1,14 @@ +# Ansible managed: hardening_common +{% for address in hardening_common_ssh_listen_addresses %} +ListenAddress {{ address }} +{% endfor %} +{% for key, value in hardening_common_sshd_settings.items() %} +{% if key == 'Port' %} +Port {{ hardening_common_ssh_port }} +{% else %} +{{ key }} {{ value }} +{% endif %} +{% endfor %} +{% if hardening_common_ssh_allow_groups | length > 0 %} +AllowGroups {{ hardening_common_ssh_allow_groups | join(' ') }} +{% endif %} diff --git a/ansible/roles/keycloak/handlers/main.yml b/infrastructure/ansible/roles/keycloak/handlers/main.yml similarity index 100% rename from ansible/roles/keycloak/handlers/main.yml rename to infrastructure/ansible/roles/keycloak/handlers/main.yml diff --git a/ansible/roles/keycloak/tasks/database.yml b/infrastructure/ansible/roles/keycloak/tasks/database.yml similarity index 100% rename from ansible/roles/keycloak/tasks/database.yml rename to infrastructure/ansible/roles/keycloak/tasks/database.yml diff --git a/ansible/roles/keycloak/tasks/main.yml b/infrastructure/ansible/roles/keycloak/tasks/main.yml similarity index 100% rename from ansible/roles/keycloak/tasks/main.yml rename to infrastructure/ansible/roles/keycloak/tasks/main.yml diff --git a/ansible/roles/keycloak/templates/keycloak.conf.j2 b/infrastructure/ansible/roles/keycloak/templates/keycloak.conf.j2 similarity index 100% rename from ansible/roles/keycloak/templates/keycloak.conf.j2 rename to infrastructure/ansible/roles/keycloak/templates/keycloak.conf.j2 diff --git a/ansible/roles/keycloak/templates/keycloak.service.j2 b/infrastructure/ansible/roles/keycloak/templates/keycloak.service.j2 similarity index 100% rename from ansible/roles/keycloak/templates/keycloak.service.j2 rename to infrastructure/ansible/roles/keycloak/templates/keycloak.service.j2 diff --git a/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 b/infrastructure/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 similarity index 100% rename from ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 rename to infrastructure/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 diff --git a/ansible/roles/keycloak/templates/realm-config.json.j2 b/infrastructure/ansible/roles/keycloak/templates/realm-config.json.j2 similarity index 100% rename from ansible/roles/keycloak/templates/realm-config.json.j2 rename to infrastructure/ansible/roles/keycloak/templates/realm-config.json.j2 diff --git a/infrastructure/ansible/roles/monitoring_icinga_agent/defaults/main.yml b/infrastructure/ansible/roles/monitoring_icinga_agent/defaults/main.yml new file mode 100644 index 0000000..9108763 --- /dev/null +++ b/infrastructure/ansible/roles/monitoring_icinga_agent/defaults/main.yml @@ -0,0 +1,7 @@ +--- +monitoring_icinga_agent_enabled: true +monitoring_icinga_agent_pkg: icinga2 +monitoring_icinga_agent_service: icinga2 +monitoring_icinga_agent_master: '' +monitoring_icinga_agent_parent_zone: master +monitoring_icinga_agent_zone: '{{ inventory_hostname }}' diff --git a/infrastructure/ansible/roles/monitoring_icinga_agent/handlers/main.yml b/infrastructure/ansible/roles/monitoring_icinga_agent/handlers/main.yml new file mode 100644 index 0000000..bdd5df3 --- /dev/null +++ b/infrastructure/ansible/roles/monitoring_icinga_agent/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart icinga2 + ansible.builtin.systemd: + name: icinga2 + state: restarted diff --git a/infrastructure/ansible/roles/monitoring_icinga_agent/tasks/main.yml b/infrastructure/ansible/roles/monitoring_icinga_agent/tasks/main.yml new file mode 100644 index 0000000..09f7d32 --- /dev/null +++ b/infrastructure/ansible/roles/monitoring_icinga_agent/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Gate + ansible.builtin.meta: end_host + when: not monitoring_icinga_agent_enabled | bool + +- name: Installer l'agent Icinga2 + ansible.builtin.apt: + name: '{{ monitoring_icinga_agent_pkg }}' + state: present + update_cache: true + +- name: Déployer zones.conf minimal + ansible.builtin.template: + src: zones.conf.j2 + dest: /etc/icinga2/zones.conf + mode: '0644' + when: monitoring_icinga_agent_master | length > 0 + notify: Restart icinga2 + +- name: Activer l'agent Icinga2 + ansible.builtin.systemd: + name: '{{ monitoring_icinga_agent_service }}' + enabled: true + state: started diff --git a/infrastructure/ansible/roles/monitoring_icinga_agent/templates/zones.conf.j2 b/infrastructure/ansible/roles/monitoring_icinga_agent/templates/zones.conf.j2 new file mode 100644 index 0000000..ebdedaf --- /dev/null +++ b/infrastructure/ansible/roles/monitoring_icinga_agent/templates/zones.conf.j2 @@ -0,0 +1,15 @@ +// Ansible managed: monitoring_icinga_agent +object Endpoint "{{ monitoring_icinga_agent_master }}" { + host = "{{ monitoring_icinga_agent_master }}" +} + +object Zone "{{ monitoring_icinga_agent_parent_zone }}" { + endpoints = [ "{{ monitoring_icinga_agent_master }}" ] +} + +object Endpoint "{{ monitoring_icinga_agent_zone }}" {} + +object Zone "{{ monitoring_icinga_agent_zone }}" { + endpoints = [ "{{ monitoring_icinga_agent_zone }}" ] + parent = "{{ monitoring_icinga_agent_parent_zone }}" +} diff --git a/ansible/roles/postgresql/defaults/main.yml b/infrastructure/ansible/roles/postgresql/defaults/main.yml similarity index 100% rename from ansible/roles/postgresql/defaults/main.yml rename to infrastructure/ansible/roles/postgresql/defaults/main.yml diff --git a/ansible/roles/postgresql/handlers/main.yml b/infrastructure/ansible/roles/postgresql/handlers/main.yml similarity index 100% rename from ansible/roles/postgresql/handlers/main.yml rename to infrastructure/ansible/roles/postgresql/handlers/main.yml diff --git a/ansible/roles/postgresql/tasks/main.yml b/infrastructure/ansible/roles/postgresql/tasks/main.yml similarity index 100% rename from ansible/roles/postgresql/tasks/main.yml rename to infrastructure/ansible/roles/postgresql/tasks/main.yml diff --git a/ansible/roles/postgresql/templates/pg_hba.conf.j2 b/infrastructure/ansible/roles/postgresql/templates/pg_hba.conf.j2 similarity index 100% rename from ansible/roles/postgresql/templates/pg_hba.conf.j2 rename to infrastructure/ansible/roles/postgresql/templates/pg_hba.conf.j2 diff --git a/ansible/roles/postgresql/templates/postgresql.conf.j2 b/infrastructure/ansible/roles/postgresql/templates/postgresql.conf.j2 similarity index 100% rename from ansible/roles/postgresql/templates/postgresql.conf.j2 rename to infrastructure/ansible/roles/postgresql/templates/postgresql.conf.j2 diff --git a/ansible/roles/powerdns-authoritative/handlers/main.yml b/infrastructure/ansible/roles/powerdns_authoritative/handlers/main.yml similarity index 100% rename from ansible/roles/powerdns-authoritative/handlers/main.yml rename to infrastructure/ansible/roles/powerdns_authoritative/handlers/main.yml diff --git a/ansible/roles/powerdns-authoritative/tasks/firewall.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/firewall.yml similarity index 100% rename from ansible/roles/powerdns-authoritative/tasks/firewall.yml rename to infrastructure/ansible/roles/powerdns_authoritative/tasks/firewall.yml diff --git a/ansible/roles/powerdns-authoritative/tasks/main.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/main.yml similarity index 100% rename from ansible/roles/powerdns-authoritative/tasks/main.yml rename to infrastructure/ansible/roles/powerdns_authoritative/tasks/main.yml diff --git a/ansible/roles/powerdns-authoritative/tasks/repo.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/repo.yml similarity index 100% rename from ansible/roles/powerdns-authoritative/tasks/repo.yml rename to infrastructure/ansible/roles/powerdns_authoritative/tasks/repo.yml diff --git a/ansible/roles/powerdns-authoritative/tasks/schema.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/schema.yml similarity index 100% rename from ansible/roles/powerdns-authoritative/tasks/schema.yml rename to infrastructure/ansible/roles/powerdns_authoritative/tasks/schema.yml diff --git a/ansible/roles/powerdns-authoritative/templates/pdns.conf.j2 b/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.conf.j2 similarity index 100% rename from ansible/roles/powerdns-authoritative/templates/pdns.conf.j2 rename to infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.conf.j2 diff --git a/ansible/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 b/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.local.gpgsql.conf.j2 similarity index 100% rename from ansible/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 rename to infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.local.gpgsql.conf.j2 diff --git a/infrastructure/ansible/roles/security_wazuh_agent/defaults/main.yml b/infrastructure/ansible/roles/security_wazuh_agent/defaults/main.yml new file mode 100644 index 0000000..d6dc35f --- /dev/null +++ b/infrastructure/ansible/roles/security_wazuh_agent/defaults/main.yml @@ -0,0 +1,4 @@ +--- +security_wazuh_agent_enabled: false +security_wazuh_agent_manager: '' +security_wazuh_agent_registration_password: '' diff --git a/infrastructure/ansible/roles/security_wazuh_agent/handlers/main.yml b/infrastructure/ansible/roles/security_wazuh_agent/handlers/main.yml new file mode 100644 index 0000000..6ff2284 --- /dev/null +++ b/infrastructure/ansible/roles/security_wazuh_agent/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart wazuh-agent + ansible.builtin.systemd: + name: wazuh-agent + state: restarted diff --git a/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml b/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml new file mode 100644 index 0000000..9f2b03b --- /dev/null +++ b/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Gate + ansible.builtin.meta: end_host + when: not (security_wazuh_agent_enabled | bool) + +- name: Tenter l'installation de l'agent Wazuh + ansible.builtin.apt: + name: wazuh-agent + state: present + update_cache: true + failed_when: false + +- name: Vérifier la présence du répertoire Wazuh + ansible.builtin.stat: + path: /var/ossec/etc + register: wazuh_ossec_dir + +- name: Wazuh absent sur cet hôte, on saute la configuration + ansible.builtin.debug: + msg: "Wazuh n'est pas installé; configuration ignorée sur cet hôte." + when: not wazuh_ossec_dir.stat.exists + +- name: Stopper le rôle Wazuh sur cet hôte si l'agent est absent + ansible.builtin.meta: end_host + when: not wazuh_ossec_dir.stat.exists + +- name: Déployer ossec.conf minimal + ansible.builtin.template: + src: ossec.conf.j2 + dest: /var/ossec/etc/ossec.conf + mode: "0640" + when: security_wazuh_agent_manager | length > 0 + notify: Restart wazuh-agent + +- name: Activer l'agent Wazuh + ansible.builtin.systemd: + name: wazuh-agent + enabled: true + state: started + failed_when: false diff --git a/infrastructure/ansible/roles/security_wazuh_agent/templates/ossec.conf.j2 b/infrastructure/ansible/roles/security_wazuh_agent/templates/ossec.conf.j2 new file mode 100644 index 0000000..a6ba33d --- /dev/null +++ b/infrastructure/ansible/roles/security_wazuh_agent/templates/ossec.conf.j2 @@ -0,0 +1,13 @@ + + + +
{{ security_wazuh_agent_manager }}
+ 1514 + tcp +
+ 10 + 60 + yes + aes +
+
diff --git a/infrastructure/ansible/vault/README.md b/infrastructure/ansible/vault/README.md new file mode 100644 index 0000000..985809a --- /dev/null +++ b/infrastructure/ansible/vault/README.md @@ -0,0 +1,94 @@ +# Vault Ansible + +Ce répertoire contient les **secrets chiffrés** utilisés par Ansible pour l’écosystème de l’Alliance Boréale. + +## Rôle de ce répertoire + +On y place les variables sensibles, par exemple : + +- mots de passe applicatifs +- secrets PostgreSQL +- clés API +- jetons d’intégration +- secrets Keycloak / Forgejo / PowerDNS +- mots de passe de comptes techniques + +Ces fichiers sont destinés à être utilisés avec **Ansible Vault**. + +## Fichiers typiques + +Exemples : + +- `production.yml` +- `production-phase2.yml` + +Des fichiers d’exemple non sensibles peuvent aussi exister : + +- `production.yml.example` +- `production-phase2.yml.example` + +Les fichiers `*.example` servent de gabarits et **ne doivent contenir aucun vrai secret**. + +## Commandes utiles + +### Créer un nouveau fichier chiffré + +```bash +ansible-vault create vault/production.yml +``` + +### Modifier un fichier chiffré + +```bash +ansible-vault edit vault/production.yml +``` + +### Voir un fichier chiffré + +```bash +ansible-vault view vault/production.yml +``` + +### Chiffrer un fichier existant + +```bash +ansible-vault encrypt vault/production.yml +``` + +## Utilisation dans les playbooks + +Exemple : + +```bash +ansible-playbook playbooks/site.yml --ask-vault-pass +``` + +Ou avec un fichier de mot de passe : + +```bash +ansible-playbook playbooks/site.yml --vault-password-file ~/.ansible/vault-pass.txt +``` + +## Discipline minimale + +- Ne jamais committer de secret en clair. +- Ne jamais renommer un fichier `.example` en fichier réel sans le chiffrer. +- Garder les secrets regroupés par environnement ou par phase logique. +- Préférer des noms explicites. +- Éviter de mélanger secrets de prod et secrets de labo dans le même fichier. + +## Convention recommandée + +- `production.yml` : secrets communs de production +- `production-phase2.yml` : secrets propres aux services phase 2 +- autres fichiers : seulement si un découpage clair est utile + +## Rappel important + +Le dépôt peut contenir : + +- la structure, +- les exemples, +- les références de variables, + +mais **jamais les secrets en clair**. \ No newline at end of file diff --git a/ansible/vault/production-phase2.yml.example b/infrastructure/ansible/vault/production-phase2.yml.example similarity index 100% rename from ansible/vault/production-phase2.yml.example rename to infrastructure/ansible/vault/production-phase2.yml.example diff --git a/ansible/vault/production.yml.example b/infrastructure/ansible/vault/production.yml.example similarity index 100% rename from ansible/vault/production.yml.example rename to infrastructure/ansible/vault/production.yml.example diff --git a/ansible/README-PHASE2.md b/infrastructure/ansible2/README-PHASE2.md similarity index 100% rename from ansible/README-PHASE2.md rename to infrastructure/ansible2/README-PHASE2.md diff --git a/ansible/README.md b/infrastructure/ansible2/README.md similarity index 100% rename from ansible/README.md rename to infrastructure/ansible2/README.md diff --git a/ansible/ansible.cfg b/infrastructure/ansible2/ansible.cfg similarity index 100% rename from ansible/ansible.cfg rename to infrastructure/ansible2/ansible.cfg diff --git a/ansible/devis phases 2.5 et 3.md b/infrastructure/ansible2/devis phases 2.5 et 3.md similarity index 100% rename from ansible/devis phases 2.5 et 3.md rename to infrastructure/ansible2/devis phases 2.5 et 3.md diff --git a/ansible/inventories/production/group_vars/all.yml b/infrastructure/ansible2/inventories/production/group_vars/all.yml similarity index 100% rename from ansible/inventories/production/group_vars/all.yml rename to infrastructure/ansible2/inventories/production/group_vars/all.yml diff --git a/ansible/inventories/production/group_vars/dns_servers.yml b/infrastructure/ansible2/inventories/production/group_vars/dns_servers.yml similarity index 100% rename from ansible/inventories/production/group_vars/dns_servers.yml rename to infrastructure/ansible2/inventories/production/group_vars/dns_servers.yml diff --git a/ansible/inventories/production/group_vars/forge_servers.yml b/infrastructure/ansible2/inventories/production/group_vars/forge_servers.yml similarity index 100% rename from ansible/inventories/production/group_vars/forge_servers.yml rename to infrastructure/ansible2/inventories/production/group_vars/forge_servers.yml diff --git a/ansible/inventories/production/group_vars/idp_servers.yml b/infrastructure/ansible2/inventories/production/group_vars/idp_servers.yml similarity index 100% rename from ansible/inventories/production/group_vars/idp_servers.yml rename to infrastructure/ansible2/inventories/production/group_vars/idp_servers.yml diff --git a/ansible/inventories/production/hosts.yml b/infrastructure/ansible2/inventories/production/hosts.yml similarity index 100% rename from ansible/inventories/production/hosts.yml rename to infrastructure/ansible2/inventories/production/hosts.yml diff --git a/ansible/playbooks/phase1-dns-deploy.yml b/infrastructure/ansible2/playbooks/phase1-dns-deploy.yml similarity index 100% rename from ansible/playbooks/phase1-dns-deploy.yml rename to infrastructure/ansible2/playbooks/phase1-dns-deploy.yml diff --git a/ansible/playbooks/phase2-deploy.yml b/infrastructure/ansible2/playbooks/phase2-deploy.yml similarity index 100% rename from ansible/playbooks/phase2-deploy.yml rename to infrastructure/ansible2/playbooks/phase2-deploy.yml diff --git a/ansible/roles/common/defaults/main.yml b/infrastructure/ansible2/roles/common/defaults/main.yml similarity index 100% rename from ansible/roles/common/defaults/main.yml rename to infrastructure/ansible2/roles/common/defaults/main.yml diff --git a/ansible/roles/common/handlers/main.yml b/infrastructure/ansible2/roles/common/handlers/main.yml similarity index 100% rename from ansible/roles/common/handlers/main.yml rename to infrastructure/ansible2/roles/common/handlers/main.yml diff --git a/ansible/roles/common/tasks/logging.yml b/infrastructure/ansible2/roles/common/tasks/logging.yml similarity index 100% rename from ansible/roles/common/tasks/logging.yml rename to infrastructure/ansible2/roles/common/tasks/logging.yml diff --git a/ansible/roles/common/tasks/main.yml b/infrastructure/ansible2/roles/common/tasks/main.yml similarity index 100% rename from ansible/roles/common/tasks/main.yml rename to infrastructure/ansible2/roles/common/tasks/main.yml diff --git a/ansible/roles/common/tasks/nftables.yml b/infrastructure/ansible2/roles/common/tasks/nftables.yml similarity index 100% rename from ansible/roles/common/tasks/nftables.yml rename to infrastructure/ansible2/roles/common/tasks/nftables.yml diff --git a/ansible/roles/common/tasks/ntp.yml b/infrastructure/ansible2/roles/common/tasks/ntp.yml similarity index 100% rename from ansible/roles/common/tasks/ntp.yml rename to infrastructure/ansible2/roles/common/tasks/ntp.yml diff --git a/ansible/roles/common/tasks/security.yml b/infrastructure/ansible2/roles/common/tasks/security.yml similarity index 100% rename from ansible/roles/common/tasks/security.yml rename to infrastructure/ansible2/roles/common/tasks/security.yml diff --git a/ansible/roles/common/tasks/ssh.yml b/infrastructure/ansible2/roles/common/tasks/ssh.yml similarity index 100% rename from ansible/roles/common/tasks/ssh.yml rename to infrastructure/ansible2/roles/common/tasks/ssh.yml diff --git a/ansible/roles/common/templates/chrony.conf.j2 b/infrastructure/ansible2/roles/common/templates/chrony.conf.j2 similarity index 100% rename from ansible/roles/common/templates/chrony.conf.j2 rename to infrastructure/ansible2/roles/common/templates/chrony.conf.j2 diff --git a/ansible/roles/common/templates/nftables.conf.j2 b/infrastructure/ansible2/roles/common/templates/nftables.conf.j2 similarity index 100% rename from ansible/roles/common/templates/nftables.conf.j2 rename to infrastructure/ansible2/roles/common/templates/nftables.conf.j2 diff --git a/ansible/roles/common/templates/sshd.conf.j2 b/infrastructure/ansible2/roles/common/templates/sshd.conf.j2 similarity index 100% rename from ansible/roles/common/templates/sshd.conf.j2 rename to infrastructure/ansible2/roles/common/templates/sshd.conf.j2 diff --git a/infrastructure/ansible2/roles/forgejo/handlers/main.yml b/infrastructure/ansible2/roles/forgejo/handlers/main.yml new file mode 100644 index 0000000..c7ae8d1 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/forgejo/tasks/database.yml b/infrastructure/ansible2/roles/forgejo/tasks/database.yml new file mode 100644 index 0000000..bd5e6b7 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/forgejo/tasks/main.yml b/infrastructure/ansible2/roles/forgejo/tasks/main.yml new file mode 100644 index 0000000..3a0a347 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/forgejo/templates/app.ini.j2 b/infrastructure/ansible2/roles/forgejo/templates/app.ini.j2 new file mode 100644 index 0000000..c266d64 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/forgejo/templates/forgejo.service.j2 b/infrastructure/ansible2/roles/forgejo/templates/forgejo.service.j2 new file mode 100644 index 0000000..62619f3 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/forgejo/templates/nginx-forgejo.conf.j2 b/infrastructure/ansible2/roles/forgejo/templates/nginx-forgejo.conf.j2 new file mode 100644 index 0000000..ed2f328 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/handlers/main.yml b/infrastructure/ansible2/roles/keycloak/handlers/main.yml new file mode 100644 index 0000000..bbdac89 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/tasks/database.yml b/infrastructure/ansible2/roles/keycloak/tasks/database.yml new file mode 100644 index 0000000..f7bc000 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/tasks/main.yml b/infrastructure/ansible2/roles/keycloak/tasks/main.yml new file mode 100644 index 0000000..3c21b71 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/templates/keycloak.conf.j2 b/infrastructure/ansible2/roles/keycloak/templates/keycloak.conf.j2 new file mode 100644 index 0000000..cddab01 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/templates/keycloak.service.j2 b/infrastructure/ansible2/roles/keycloak/templates/keycloak.service.j2 new file mode 100644 index 0000000..4172619 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/templates/nginx-keycloak.conf.j2 b/infrastructure/ansible2/roles/keycloak/templates/nginx-keycloak.conf.j2 new file mode 100644 index 0000000..7876f37 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/keycloak/templates/realm-config.json.j2 b/infrastructure/ansible2/roles/keycloak/templates/realm-config.json.j2 new file mode 100644 index 0000000..601b999 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/postgresql/defaults/main.yml b/infrastructure/ansible2/roles/postgresql/defaults/main.yml new file mode 100644 index 0000000..67829ac --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/postgresql/handlers/main.yml b/infrastructure/ansible2/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..1a1ec8c --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/postgresql/tasks/main.yml b/infrastructure/ansible2/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..546303e --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/postgresql/templates/pg_hba.conf.j2 b/infrastructure/ansible2/roles/postgresql/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..e5bacfc --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/postgresql/templates/postgresql.conf.j2 b/infrastructure/ansible2/roles/postgresql/templates/postgresql.conf.j2 new file mode 100644 index 0000000..925aa21 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/handlers/main.yml b/infrastructure/ansible2/roles/powerdns-authoritative/handlers/main.yml new file mode 100644 index 0000000..f85227c --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/tasks/firewall.yml b/infrastructure/ansible2/roles/powerdns-authoritative/tasks/firewall.yml new file mode 100644 index 0000000..832a940 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/tasks/main.yml b/infrastructure/ansible2/roles/powerdns-authoritative/tasks/main.yml new file mode 100644 index 0000000..c105eca --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/tasks/repo.yml b/infrastructure/ansible2/roles/powerdns-authoritative/tasks/repo.yml new file mode 100644 index 0000000..c601a3f --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/tasks/schema.yml b/infrastructure/ansible2/roles/powerdns-authoritative/tasks/schema.yml new file mode 100644 index 0000000..52af50f --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/templates/pdns.conf.j2 b/infrastructure/ansible2/roles/powerdns-authoritative/templates/pdns.conf.j2 new file mode 100644 index 0000000..a3169e4 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/ansible2/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 b/infrastructure/ansible2/roles/powerdns-authoritative/templates/pdns.local.gpgsql.conf.j2 new file mode 100644 index 0000000..18d8736 --- /dev/null +++ b/infrastructure/ansible2/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/infrastructure/icinga2-ansible-noc/Makefile b/infrastructure/icinga2-ansible-noc/Makefile new file mode 100644 index 0000000..b60eb00 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/Makefile @@ -0,0 +1,31 @@ +ANSIBLE ?= ansible-playbook +INVENTORY ?= ansible/inventory +PLAYBOOK ?= ansible/site.yml +LIMIT ?= +TAGS ?= + +.PHONY: help ping bootstrap deploy check validate-icinga syntax + +help: + @echo "Targets:" + @echo " make ping - tester SSH/Ansible" + @echo " make syntax - validation syntaxique Ansible" + @echo " make bootstrap - installation complète" + @echo " make deploy - rejouer le déploiement complet" + @echo " make check - checks locaux post-déploiement" + @echo " make validate-icinga - icinga2 daemon -C sur la cible" + +ping: + ansible -i $(INVENTORY) all -m ping + +syntax: + $(ANSIBLE) -i $(INVENTORY) $(PLAYBOOK) --syntax-check + +bootstrap deploy: + $(ANSIBLE) -i $(INVENTORY) $(PLAYBOOK) $(if $(LIMIT),--limit $(LIMIT),) $(if $(TAGS),--tags $(TAGS),) + +check: + ansible -i $(INVENTORY) icinga_servers -m shell -a 'systemctl is-active icinga2 icingadb icingadb-redis mariadb apache2 && icinga2 daemon -C' + +validate-icinga: + ansible -i $(INVENTORY) icinga_servers -m shell -a 'icinga2 daemon -C' diff --git a/infrastructure/icinga2-ansible-noc/README.md b/infrastructure/icinga2-ansible-noc/README.md new file mode 100644 index 0000000..d015137 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/README.md @@ -0,0 +1,84 @@ +# icinga2-ansible-noc + +Dépôt Ansible minimal et reproductible pour déployer un serveur Icinga 2 moderne sur Debian 12, avec : + +- dépôt officiel Icinga ; +- Icinga 2 ; +- Icinga DB + Redis ; +- MariaDB ; +- Icinga Web 2 ; +- module Icinga DB Web ; +- module Business Process Monitoring (BPM) ; +- génération de configuration Icinga via rôles Ansible ; +- commandes Makefile pour bootstrap, validation et déploiement. + +## Principe + +Icinga reste l'autorité de supervision technique. Le dépôt ne recode pas ce qu'Icinga sait déjà faire : checks, états, notifications, objets, groupes, templates et vues restent natifs. + +Ansible sert à produire une installation reproductible et à générer les objets de supervision depuis des variables versionnées. + +## Préparation + +```bash +cp ansible/inventory.example ansible/inventory +cp ansible/group_vars/all.yml.example ansible/group_vars/all.yml +$EDITOR ansible/inventory +$EDITOR ansible/group_vars/all.yml +``` + +## Commandes + +```bash +make ping +make bootstrap +make check +make deploy +make validate-icinga +``` + +## URL + +```text +http:///icingaweb2 +``` + +## Identifiants initiaux + +Définis dans : + +```text +ansible/group_vars/all.yml +``` + +Variables principales : + +- `icingaweb_admin_user` +- `icingaweb_admin_password` + +## Structure + +```text +ansible/ + site.yml + inventory.example + group_vars/all.yml.example + roles/ + common/ + icinga_repo/ + mariadb/ + icinga2/ + icingadb/ + icingaweb2/ + bpm/ + monitoring_config/ +Makefile +``` + +## Philosophie + +- Debian 12 vanille comme cible de départ. +- Official Icinga packages pour rester latest and greatest. +- Icinga DB au lieu de l'ancien IDO. +- Configuration générée par Ansible, déposée dans `/etc/icinga2/zones.d/global-templates/chezlepro/`. +- Validation systématique avec `icinga2 daemon -C`. diff --git a/infrastructure/icinga2-ansible-noc/ansible/ansible.cfg b/infrastructure/icinga2-ansible-noc/ansible/ansible.cfg new file mode 100644 index 0000000..80cfc4f --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/ansible.cfg @@ -0,0 +1,11 @@ +[defaults] +inventory = inventory +roles_path = roles +host_key_checking = False +retry_files_enabled = False +stdout_callback = yaml +interpreter_python = auto_silent + +[privilege_escalation] +become = True +become_method = sudo diff --git a/infrastructure/icinga2-ansible-noc/ansible/group_vars/all.yml.example b/infrastructure/icinga2-ansible-noc/ansible/group_vars/all.yml.example new file mode 100644 index 0000000..d824f4b --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/group_vars/all.yml.example @@ -0,0 +1,44 @@ +--- +timezone: America/Montreal + +icingaweb_admin_user: icingaadmin +icingaweb_admin_password: ChangeMeNow123! + +mysql_root_password: ChangeMeRoot123! +icingadb_database: icingadb +icingadb_user: icingadb +icingadb_password: ChangeMeIcingaDb123! +icingaweb_database: icingaweb2 +icingaweb_db_user: icingaweb2 +icingaweb_db_password: ChangeMeIcingaWeb123! + +icinga_api_root_password: ChangeMeApiRoot123! + +monitoring_zone_dir: /etc/icinga2/zones.d/global-templates/chezlepro + +notification_mail_to: daniel@example.test +notification_mail_from: icinga@example.test + +# Exemple volontairement simple. À remplacer par ton inventaire réel. +infrastructure_hosts: + - name: proxmox-01 + address: 192.168.12.11 + groups: [proxmox, linux] + checks: + - name: ping4 + command: hostalive + - name: ssh + command: ssh + - name: pbs-01 + address: 192.168.12.21 + groups: [backup, linux] + checks: + - name: ping4 + command: hostalive + - name: ssh + command: ssh + +hostgroups: + - proxmox + - linux + - backup diff --git a/infrastructure/icinga2-ansible-noc/ansible/inventory.example b/infrastructure/icinga2-ansible-noc/ansible/inventory.example new file mode 100644 index 0000000..1e626a6 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/inventory.example @@ -0,0 +1,2 @@ +[icinga_servers] +icinga-noc ansible_host=192.168.12.50 ansible_user=ansible diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/tasks/main.yml new file mode 100644 index 0000000..17c1736 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- name: Install Icinga Web 2 Business Process module + ansible.builtin.apt: + name: icingaweb2-module-businessprocess + state: latest + +- name: Ensure BPM process directory exists + ansible.builtin.file: + path: /etc/icingaweb2/modules/businessprocess/processes + state: directory + owner: www-data + group: icingaweb2 + mode: '2770' + +- name: Enable Business Process module + ansible.builtin.command: icingacli module enable businessprocess + args: + creates: /etc/icingaweb2/enabledModules/businessprocess + +- name: Deploy starter BPM process + ansible.builtin.template: + src: chezlepro-infra.conf.j2 + dest: /etc/icingaweb2/modules/businessprocess/processes/chezlepro-infra.conf + owner: www-data + group: icingaweb2 + mode: '0660' diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/templates/chezlepro-infra.conf.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/templates/chezlepro-infra.conf.j2 new file mode 100644 index 0000000..4601d13 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/bpm/templates/chezlepro-infra.conf.j2 @@ -0,0 +1,5 @@ +# Business Process starter file generated by Ansible. +# Ajuste ensuite dans l'interface BPM si nécessaire. + +chezlepro-infrastructure = {% for host in infrastructure_hosts %}{{ host.name }};ping4{% if not loop.last %} & {% endif %}{% endfor %} +chezlepro-infrastructure.display_name = Infrastructure Chezlepro diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/common/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/common/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/common/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..c0b9ba1 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/common/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Set timezone + ansible.builtin.timezone: + name: "{{ timezone }}" + +- name: Install base packages + ansible.builtin.apt: + name: + - ca-certificates + - curl + - gnupg + - lsb-release + - apt-transport-https + - wget + - python3-pymysql + - python3-passlib + - sudo + state: present + update_cache: true diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/handlers/main.yml new file mode 100644 index 0000000..f77f733 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart icinga2 + ansible.builtin.systemd: + name: icinga2 + state: restarted diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/tasks/main.yml new file mode 100644 index 0000000..3791180 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Install Icinga 2 and monitoring plugins + ansible.builtin.apt: + name: + - icinga2 + - monitoring-plugins + state: latest + +- name: Set up Icinga 2 API if not already configured + ansible.builtin.command: icinga2 api setup + args: + creates: /etc/icinga2/features-enabled/api.conf + notify: restart icinga2 + +- name: Configure API root user + ansible.builtin.template: + src: api-users.conf.j2 + dest: /etc/icinga2/conf.d/api-users.conf + owner: nagios + group: nagios + mode: '0640' + notify: restart icinga2 + +- name: Enable Icinga DB feature + ansible.builtin.command: icinga2 feature enable icingadb + args: + creates: /etc/icinga2/features-enabled/icingadb.conf + notify: restart icinga2 + +- name: Enable and start Icinga 2 + ansible.builtin.systemd: + name: icinga2 + enabled: true + state: started diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/templates/api-users.conf.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/templates/api-users.conf.j2 new file mode 100644 index 0000000..7fbe7b9 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga2/templates/api-users.conf.j2 @@ -0,0 +1,4 @@ +object ApiUser "root" { + password = "{{ icinga_api_root_password }}" + permissions = [ "*" ] +} diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icinga_repo/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga_repo/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icinga_repo/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga_repo/tasks/main.yml new file mode 100644 index 0000000..99c00c9 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icinga_repo/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: Read Debian version id + ansible.builtin.command: . /etc/os-release && echo $VERSION_ID + register: debian_version_id + changed_when: false + +- name: Read Debian codename + ansible.builtin.command: awk -F'[)(]+' '/VERSION=/ {print $2}' /etc/os-release + register: debian_codename + changed_when: false + +- name: Download Icinga archive keyring package + ansible.builtin.get_url: + url: "https://packages.icinga.com/icinga-archive-keyring_latest+debian{{ debian_version_id.stdout }}.deb" + dest: /tmp/icinga-archive-keyring.deb + mode: '0644' + +- name: Install Icinga archive keyring + ansible.builtin.apt: + deb: /tmp/icinga-archive-keyring.deb + +- name: Configure official Icinga repository + ansible.builtin.copy: + dest: "/etc/apt/sources.list.d/{{ debian_codename.stdout }}-icinga.list" + mode: '0644' + content: | + deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/debian icinga-{{ debian_codename.stdout }} main + deb-src [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/debian icinga-{{ debian_codename.stdout }} main + +- name: Refresh apt cache after Icinga repository setup + ansible.builtin.apt: + update_cache: true diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/handlers/main.yml new file mode 100644 index 0000000..224bee9 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart icingadb + ansible.builtin.systemd: + name: icingadb + state: restarted diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/tasks/main.yml new file mode 100644 index 0000000..c599700 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Install Icinga DB components + ansible.builtin.apt: + name: + - icingadb + - icingadb-redis + - icingaweb2-module-icingadb + state: latest + +- name: Enable and start Icinga DB Redis + ansible.builtin.systemd: + name: icingadb-redis + enabled: true + state: started + +- name: Check whether Icinga DB schema is already imported + ansible.builtin.shell: "mysql --batch --skip-column-names {{ icingadb_database }} -e 'SHOW TABLES LIKE \"host\";'" + register: icingadb_schema_check + changed_when: false + failed_when: false + +- name: Import Icinga DB schema + ansible.builtin.shell: "mysql {{ icingadb_database }} < /usr/share/icingadb/schema/mysql/schema.sql" + when: icingadb_schema_check.stdout | length == 0 + +- name: Configure Icinga DB daemon + ansible.builtin.template: + src: config.yml.j2 + dest: /etc/icingadb/config.yml + owner: root + group: icingadb + mode: '0640' + notify: restart icingadb + +- name: Enable and start Icinga DB + ansible.builtin.systemd: + name: icingadb + enabled: true + state: started diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/templates/config.yml.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/templates/config.yml.j2 new file mode 100644 index 0000000..7742203 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingadb/templates/config.yml.j2 @@ -0,0 +1,11 @@ +database: + type: mysql + host: localhost + port: 3306 + database: {{ icingadb_database }} + user: {{ icingadb_user }} + password: {{ icingadb_password }} + +redis: + host: localhost + port: 6380 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/tasks/main.yml new file mode 100644 index 0000000..8ffbfc4 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/tasks/main.yml @@ -0,0 +1,96 @@ +--- +- name: Install Apache, PHP and Icinga Web 2 + ansible.builtin.apt: + name: + - apache2 + - libapache2-mod-php + - php + - php-cli + - php-intl + - php-mysql + - php-gd + - php-curl + - php-mbstring + - php-xml + - icingaweb2 + - icingacli + state: latest + +- name: Ensure Icinga Web 2 config directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: www-data + group: icingaweb2 + mode: '2770' + loop: + - /etc/icingaweb2 + - /etc/icingaweb2/modules + - /etc/icingaweb2/modules/icingadb + - /etc/icingaweb2/modules/icingadb/config + +- name: Configure Icinga Web resources + ansible.builtin.template: + src: resources.ini.j2 + dest: /etc/icingaweb2/resources.ini + owner: www-data + group: icingaweb2 + mode: '0660' + +- name: Configure Icinga Web authentication + ansible.builtin.template: + src: authentication.ini.j2 + dest: /etc/icingaweb2/authentication.ini + owner: www-data + group: icingaweb2 + mode: '0660' + +- name: Configure Icinga Web roles + ansible.builtin.template: + src: roles.ini.j2 + dest: /etc/icingaweb2/roles.ini + owner: www-data + group: icingaweb2 + mode: '0660' + +- name: Create admin user password hash + ansible.builtin.command: "openssl passwd -1 {{ icingaweb_admin_password }}" + register: icingaweb_admin_hash + changed_when: false + no_log: true + +- name: Configure local Icinga Web users + ansible.builtin.template: + src: users.ini.j2 + dest: /etc/icingaweb2/users.ini + owner: www-data + group: icingaweb2 + mode: '0660' + no_log: true + +- name: Configure Icinga DB Web module database + ansible.builtin.template: + src: icingadb-config.ini.j2 + dest: /etc/icingaweb2/modules/icingadb/config.ini + owner: www-data + group: icingaweb2 + mode: '0660' + +- name: Configure Icinga DB Web command transport + ansible.builtin.template: + src: icingadb-commandtransports.ini.j2 + dest: /etc/icingaweb2/modules/icingadb/commandtransports.ini + owner: www-data + group: icingaweb2 + mode: '0660' + +- name: Enable Icinga DB Web module + ansible.builtin.command: icingacli module enable icingadb + args: + creates: /etc/icingaweb2/enabledModules/icingadb + +- name: Enable and start Apache + ansible.builtin.systemd: + name: apache2 + enabled: true + state: started diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/authentication.ini.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/authentication.ini.j2 new file mode 100644 index 0000000..d0089f8 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/authentication.ini.j2 @@ -0,0 +1,2 @@ +[icingaweb2] +backend = "ini" diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/icingadb-commandtransports.ini.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/icingadb-commandtransports.ini.j2 new file mode 100644 index 0000000..5987d34 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/icingadb-commandtransports.ini.j2 @@ -0,0 +1,6 @@ +[icinga2] +transport = "api" +host = "localhost" +port = "5665" +username = "root" +password = "{{ icinga_api_root_password }}" diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/icingadb-config.ini.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/icingadb-config.ini.j2 new file mode 100644 index 0000000..a9d2814 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/icingadb-config.ini.j2 @@ -0,0 +1,2 @@ +[icingadb] +resource = "icingadb" diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/resources.ini.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/resources.ini.j2 new file mode 100644 index 0000000..6b98a5d --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/resources.ini.j2 @@ -0,0 +1,19 @@ +[icingaweb_db] +type = "db" +db = "mysql" +host = "localhost" +port = "3306" +dbname = "{{ icingaweb_database }}" +username = "{{ icingaweb_db_user }}" +password = "{{ icingaweb_db_password }}" +charset = "utf8mb4" + +[icingadb] +type = "db" +db = "mysql" +host = "localhost" +port = "3306" +dbname = "{{ icingadb_database }}" +username = "{{ icingadb_user }}" +password = "{{ icingadb_password }}" +charset = "utf8mb4" diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/roles.ini.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/roles.ini.j2 new file mode 100644 index 0000000..63fd316 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/roles.ini.j2 @@ -0,0 +1,3 @@ +[Administrators] +users = "{{ icingaweb_admin_user }}" +permissions = "*" diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/users.ini.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/users.ini.j2 new file mode 100644 index 0000000..ea0eece --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/icingaweb2/templates/users.ini.j2 @@ -0,0 +1,3 @@ +[{{ icingaweb_admin_user }}] +password = "{{ icingaweb_admin_hash.stdout }}" +active = "1" diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/mariadb/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/mariadb/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/mariadb/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/mariadb/tasks/main.yml new file mode 100644 index 0000000..8be25f0 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/mariadb/tasks/main.yml @@ -0,0 +1,43 @@ +--- +- name: Install MariaDB + ansible.builtin.apt: + name: + - mariadb-server + - mariadb-client + state: present + +- name: Enable and start MariaDB + ansible.builtin.systemd: + name: mariadb + enabled: true + state: started + +- name: Create Icinga DB database + community.mysql.mysql_db: + name: "{{ icingadb_database }}" + state: present + login_unix_socket: /run/mysqld/mysqld.sock + +- name: Create Icinga DB user + community.mysql.mysql_user: + name: "{{ icingadb_user }}" + password: "{{ icingadb_password }}" + priv: "{{ icingadb_database }}.*:ALL" + host: localhost + state: present + login_unix_socket: /run/mysqld/mysqld.sock + +- name: Create Icinga Web database + community.mysql.mysql_db: + name: "{{ icingaweb_database }}" + state: present + login_unix_socket: /run/mysqld/mysqld.sock + +- name: Create Icinga Web database user + community.mysql.mysql_user: + name: "{{ icingaweb_db_user }}" + password: "{{ icingaweb_db_password }}" + priv: "{{ icingaweb_database }}.*:ALL" + host: localhost + state: present + login_unix_socket: /run/mysqld/mysqld.sock diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/handlers/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/handlers/main.yml new file mode 100644 index 0000000..2116acf --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: validate and restart icinga2 + ansible.builtin.shell: icinga2 daemon -C && systemctl restart icinga2 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/tasks/main.yml b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/tasks/main.yml new file mode 100644 index 0000000..db51b26 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: Ensure generated Icinga config directory exists + ansible.builtin.file: + path: "{{ monitoring_zone_dir }}" + state: directory + owner: nagios + group: nagios + mode: '0750' + +- name: Generate hostgroups + ansible.builtin.template: + src: hostgroups.conf.j2 + dest: "{{ monitoring_zone_dir }}/hostgroups.conf" + owner: nagios + group: nagios + mode: '0640' + notify: validate and restart icinga2 + +- name: Generate hosts + ansible.builtin.template: + src: hosts.conf.j2 + dest: "{{ monitoring_zone_dir }}/hosts.conf" + owner: nagios + group: nagios + mode: '0640' + notify: validate and restart icinga2 + +- name: Generate services + ansible.builtin.template: + src: services.conf.j2 + dest: "{{ monitoring_zone_dir }}/services.conf" + owner: nagios + group: nagios + mode: '0640' + notify: validate and restart icinga2 diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/hostgroups.conf.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/hostgroups.conf.j2 new file mode 100644 index 0000000..aca0f6a --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/hostgroups.conf.j2 @@ -0,0 +1,5 @@ +{% for group in hostgroups %} +object HostGroup "{{ group }}" { + display_name = "{{ group }}" +} +{% endfor %} diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/hosts.conf.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/hosts.conf.j2 new file mode 100644 index 0000000..f956c37 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/hosts.conf.j2 @@ -0,0 +1,8 @@ +{% for host in infrastructure_hosts %} +object Host "{{ host.name }}" { + import "generic-host" + address = "{{ host.address }}" + vars.os = "Linux" + groups = [ {% for group in host.groups | default([]) %}"{{ group }}"{% if not loop.last %}, {% endif %}{% endfor %} ] +} +{% endfor %} diff --git a/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/services.conf.j2 b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/services.conf.j2 new file mode 100644 index 0000000..fdb0ca6 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/roles/monitoring_config/templates/services.conf.j2 @@ -0,0 +1,9 @@ +{% for host in infrastructure_hosts %} +{% for check in host.checks | default([]) %} +apply Service "{{ check.name }}" { + import "generic-service" + check_command = "{{ check.command }}" + assign where host.name == "{{ host.name }}" +} +{% endfor %} +{% endfor %} diff --git a/infrastructure/icinga2-ansible-noc/ansible/site.yml b/infrastructure/icinga2-ansible-noc/ansible/site.yml new file mode 100644 index 0000000..eb0a9e2 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/ansible/site.yml @@ -0,0 +1,13 @@ +--- +- name: Deploy Icinga 2 NOC server + hosts: icinga_servers + become: true + roles: + - common + - icinga_repo + - mariadb + - icinga2 + - icingadb + - icingaweb2 + - bpm + - monitoring_config diff --git a/infrastructure/icinga2-ansible-noc/docs/ARCHITECTURE.md b/infrastructure/icinga2-ansible-noc/docs/ARCHITECTURE.md new file mode 100644 index 0000000..6305050 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/docs/ARCHITECTURE.md @@ -0,0 +1,50 @@ +# Architecture + +## Cible + +Ce dépôt installe une pile Icinga moderne sur Debian 12 : + +```text +Icinga 2 -> Icinga DB feature -> Redis -> Icinga DB daemon -> MariaDB -> Icinga Web 2 + -> BPM module +``` + +## Choix structurants + +- Icinga 2 demeure le moteur de supervision. +- Icinga DB remplace l'ancien backend IDO pour rester aligné avec la pile moderne. +- Icinga Web 2 fournit l'interface opérateur. +- BPM sert à représenter des regroupements métier ou opérationnels. +- Ansible génère les objets Icinga depuis `group_vars/all.yml`. + +## Emplacement des configurations générées + +```text +/etc/icinga2/zones.d/global-templates/chezlepro/ +``` + +Fichiers générés : + +- `hostgroups.conf` +- `hosts.conf` +- `services.conf` + +## Cycle opératoire + +```bash +make deploy +make validate-icinga +make check +``` + +## Extension prévue + +Ajouter progressivement : + +- templates SNMP ; +- checks Proxmox ; +- checks Ceph ; +- checks PBS ; +- checks HTTP/TLS ; +- notifications mail ; +- intégration agent Icinga sur les hôtes Linux. diff --git a/infrastructure/icinga2-ansible-noc/docs/RUNBOOK.md b/infrastructure/icinga2-ansible-noc/docs/RUNBOOK.md new file mode 100644 index 0000000..0079a91 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/docs/RUNBOOK.md @@ -0,0 +1,50 @@ +# Runbook + +## Premier déploiement + +```bash +ansible-galaxy collection install -r requirements.yml +cp ansible/inventory.example ansible/inventory +cp ansible/group_vars/all.yml.example ansible/group_vars/all.yml +$EDITOR ansible/inventory +$EDITOR ansible/group_vars/all.yml +make bootstrap +``` + +## Validation + +```bash +make validate-icinga +make check +``` + +## Ajouter un hôte + +Modifier `ansible/group_vars/all.yml` : + +```yaml +infrastructure_hosts: + - name: nouveau-serveur + address: 192.168.12.99 + groups: [linux] + checks: + - name: ping4 + command: hostalive + - name: ssh + command: ssh +``` + +Puis : + +```bash +make deploy +``` + +## Dépannage rapide + +```bash +sudo systemctl status icinga2 icingadb icingadb-redis mariadb apache2 --no-pager +sudo icinga2 daemon -C +sudo journalctl -u icinga2 -n 100 --no-pager +sudo journalctl -u icingadb -n 100 --no-pager +``` diff --git a/infrastructure/icinga2-ansible-noc/requirements.yml b/infrastructure/icinga2-ansible-noc/requirements.yml new file mode 100644 index 0000000..6a0d242 --- /dev/null +++ b/infrastructure/icinga2-ansible-noc/requirements.yml @@ -0,0 +1,3 @@ +--- +collections: + - name: community.mysql diff --git a/infrastructure/opentofu b/infrastructure/opentofu new file mode 160000 index 0000000..079b07a --- /dev/null +++ b/infrastructure/opentofu @@ -0,0 +1 @@ +Subproject commit 079b07a49a8afb325c89e6f40b189fa90c29f416 diff --git a/templates/.gitmessage.txt b/infrastructure/templates/.gitmessage.txt similarity index 100% rename from templates/.gitmessage.txt rename to infrastructure/templates/.gitmessage.txt diff --git a/migrate-alliance-boreale-v2.sh b/migrate-alliance-boreale-v2.sh new file mode 100755 index 0000000..164aa38 --- /dev/null +++ b/migrate-alliance-boreale-v2.sh @@ -0,0 +1,358 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# ============================================================================ +# migrate-alliance-boreale-v2.sh +# +# Migration prudente vers une structure canonique du dépôt Alliance Boréale. +# +# Principes : +# - DRY_RUN=1 par défaut +# - ne supprime rien +# - n'écrase rien silencieusement +# - journalise tout +# - évite les auto-déplacements de dossier +# +# Usage : +# bash migrate-alliance-boreale-v2.sh +# DRY_RUN=0 bash migrate-alliance-boreale-v2.sh +# ============================================================================ + +ROOT="${1:-.}" +cd "$ROOT" + +DRY_RUN="${DRY_RUN:-1}" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG_DIR=".migration" +LOG_FILE="${LOG_DIR}/migration-v2-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +log() { + echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE" +} + +run() { + if [[ "$DRY_RUN" == "1" ]]; then + log "DRY-RUN: $*" + else + log "RUN: $*" + eval "$@" | tee -a "$LOG_FILE" + fi +} + +exists() { + [[ -e "$1" ]] +} + +ensure_dir() { + local d="$1" + [[ -d "$d" ]] || run "mkdir -p \"$d\"" +} + +safe_mv() { + local src="$1" + local dst="$2" + + if [[ ! -e "$src" ]]; then + log "SKIP: source absente: $src" + return 0 + fi + + if [[ "$src" == "$dst" ]]; then + log "SKIP: source et destination identiques: $src" + return 0 + fi + + ensure_dir "$(dirname "$dst")" + + if [[ -e "$dst" ]]; then + local backup="${dst}.pre-migration-${STAMP}" + log "CONFLIT: destination existe: $dst" + run "mv \"$dst\" \"$backup\"" + fi + + run "mv \"$src\" \"$dst\"" +} + +safe_mkdir_tree() { + for d in "$@"; do + ensure_dir "$d" + done +} + +move_if_exists() { + local src="$1" + local dst="$2" + exists "$src" && safe_mv "$src" "$dst" || log "SKIP: absent: $src" +} + +quarantine_if_exists() { + local src="$1" + local bucket="$2" + if exists "$src"; then + ensure_dir "$bucket" + safe_mv "$src" "$bucket/$(basename "$src")" + fi +} + +create_readme_if_absent() { + local path="$1" + local content="$2" + + if [[ ! -e "$path" ]]; then + ensure_dir "$(dirname "$path")" + if [[ "$DRY_RUN" == "1" ]]; then + log "DRY-RUN: création de $path" + else + printf "%s\n" "$content" > "$path" + log "RUN: création de $path" + fi + fi +} + +log "Début migration v2" +log "ROOT=$ROOT" +log "DRY_RUN=$DRY_RUN" + +# ---------------------------------------------------------------------------- +# 1) Créer uniquement les branches structurelles sûres +# ---------------------------------------------------------------------------- + +log "Création de l’ossature cible" + +safe_mkdir_tree \ + docs \ + infrastructure \ + operations \ + archives \ + tooling \ + security \ + docs/decisions \ + docs/diffusion \ + archives/pre-migration \ + archives/legacy-docs \ + archives/obsolete + +# Répertoires feuilles qui ne reçoivent pas un mv de dossier entier existant +safe_mkdir_tree \ + docs/decisions/adr \ + docs/rfc \ + docs/diffusion/conferences \ + docs/diffusion/site \ + docs/diffusion/visuels \ + docs/diffusion/recrutement \ + operations/runbooks \ + operations/evidence \ + operations/procedures \ + operations/checklists \ + infrastructure/opentofu \ + infrastructure/cloud-init \ + infrastructure/netbox \ + infrastructure/templates/proxmox + +# ---------------------------------------------------------------------------- +# 2) Déplacer les blocs racine évidents +# ---------------------------------------------------------------------------- + +log "Déplacement des blocs racine évidents" + +move_if_exists "ansible" "infrastructure/ansible" +move_if_exists "templates" "infrastructure/templates" +move_if_exists "evidence" "operations/evidence" +move_if_exists "configure" "tooling/configure" + +# ---------------------------------------------------------------------------- +# 3) Réorganiser docs/ par familles +# ---------------------------------------------------------------------------- + +if exists "docs"; then + log "Réorganisation de docs/" + + # Dossiers historiques -> familles cibles + move_if_exists "docs/00-fondements" "docs/fondements" + move_if_exists "docs/10-architecture" "docs/architecture" + move_if_exists "docs/40-ecosystemes" "docs/ecosystemes" + move_if_exists "docs/constitution" "docs/gouvernance" + move_if_exists "docs/20-rag" "docs/corpus-a-integrer" + move_if_exists "docs/vieustoq" "archives/legacy-docs/vieustoq" + + # engineering / guides / opération + move_if_exists "docs/engineering" "docs/guides-engineering" + move_if_exists "docs/guides" "docs/guides" + move_if_exists "docs/opération" "operations/procedures" + + # pile opérateur -> recentrage + move_if_exists "docs/pile opérateur/adr" "docs/decisions/adr" + move_if_exists "docs/pile opérateur/pol" "docs/politiques" + move_if_exists "docs/pile opérateur/runbooks" "operations/runbooks" + move_if_exists "docs/pile opérateur/evidence" "operations/evidence/pile-operateur" + move_if_exists "docs/pile opérateur/configs" "operations/procedures/pile-operateur-configs" + + # Ce qui reste du dossier "pile opérateur" part en quarantaine s'il existe encore + quarantine_if_exists "docs/pile opérateur/README.md" "archives/pre-migration/docs-pile-operateur" + quarantine_if_exists "docs/pile opérateur" "archives/pre-migration" + + # annexes -> corpus à intégrer + move_if_exists "docs/50-annexes" "docs/corpus-a-integrer-annexes" + + # assets / diffusion + move_if_exists "docs/assets" "docs/diffusion/visuels/assets" + + # fichiers de diffusion + move_if_exists "docs/Alliance_Boreale_Conferences.pptx" "docs/diffusion/conferences/Alliance_Boreale_Conferences.pptx" + move_if_exists "docs/Conference_01_Rideau_ou_Coffre_Fort.pptx" "docs/diffusion/conferences/Conference_01_Rideau_ou_Coffre_Fort.pptx" + move_if_exists "docs/Conference_02_Colonisation_Numerique.pptx" "docs/diffusion/conferences/Conference_02_Colonisation_Numerique.pptx" + move_if_exists "docs/2026-02-03 - Présentation d'Alliance Boréale - Rencontres Linux.odp" "docs/diffusion/conferences/2026-02-03-Rencontres-Linux.odp" + move_if_exists "docs/alliance_boreale_site.html" "docs/diffusion/site/alliance_boreale_site.html" + move_if_exists "docs/alliance_boreale_recrutement.docx" "docs/diffusion/recrutement/alliance_boreale_recrutement.docx" + + # fichiers racine documentaires à classer proprement + move_if_exists "docs/00_Etat_Depot_Vivant_v1.md" "docs/gouvernance/00_Etat_Depot_Vivant_v1.md" + move_if_exists "docs/00_Glossaire_biomimetique_autopoietique_v3.md" "docs/modele-glossaire.md" + move_if_exists "docs/00_Modele_8_couches_biomimetique_autopoietique_v3.md" "docs/modele-8-couches.md" + move_if_exists "docs/00_Nomenclature_v4.md" "docs/nomenclature-v4.md" + move_if_exists "docs/06 - Controles_Conformite_Label_Prestige_par_Couche_v1.0.md" "docs/politiques/06-Controles_Conformite_Label_Prestige_par_Couche_v1.0.md" + + # divers à trier plus tard + for f in \ + "docs/index.md" \ + "docs/linkedin_post.md" \ + "docs/TODO.md" \ + "docs/TRANSMISSION.md" + do + if exists "$f"; then + move_if_exists "$f" "docs/corpus-a-integrer/$(basename "$f")" + fi + done +fi + +# ---------------------------------------------------------------------------- +# 4) Corriger certaines branches créées implicitement +# ---------------------------------------------------------------------------- + +log "Consolidation des branches documentaires" + +# Si gouvernance n'existe pas encore après déplacement depuis constitution +ensure_dir "docs/gouvernance" +ensure_dir "docs/corpus-a-integrer" +ensure_dir "docs/politiques" + +# Si les fichiers modèle ont été déplacés en racine docs, on leur donne une maison +ensure_dir "docs/modele" +move_if_exists "docs/modele-glossaire.md" "docs/modele/00_Glossaire_biomimetique_autopoietique_v3.md" +move_if_exists "docs/modele-8-couches.md" "docs/modele/00_Modele_8_couches_biomimetique_autopoietique_v3.md" +move_if_exists "docs/nomenclature-v4.md" "docs/modele/00_Nomenclature_v4.md" + +# Si on a guides-engineering, le ranger sous guides +if exists "docs/guides-engineering"; then + ensure_dir "docs/guides" + move_if_exists "docs/guides-engineering" "docs/guides/engineering" +fi + +# Si corpus-a-integrer-annexes existe, le ranger sous corpus-a-integrer +if exists "docs/corpus-a-integrer-annexes"; then + ensure_dir "docs/corpus-a-integrer" + move_if_exists "docs/corpus-a-integrer-annexes" "docs/corpus-a-integrer/annexes" +fi + +# ---------------------------------------------------------------------------- +# 5) Doubles probables -> quarantaine prudente +# ---------------------------------------------------------------------------- + +log "Mise en quarantaine des doublons probables" + +ensure_dir "archives/pre-migration/duplicates" + +for f in \ + "docs/fondements/00-manifeste.md" \ + "docs/fondements/00 - Manifeste.md" \ + "docs/corpus-a-integrer/03_Cadre_Conformite_Label_Prestige(1).md" \ + "docs/corpus-a-integrer/devis(2).md" +do + if exists "$f"; then + move_if_exists "$f" "archives/pre-migration/duplicates/$(basename "$f")" + fi +done + +# ---------------------------------------------------------------------------- +# 6) README d’orientation +# ---------------------------------------------------------------------------- + +create_readme_if_absent "docs/README.md" \ +"# Documentation canonique + +Ce dossier contient la documentation de référence de l'Alliance Boréale. + +Branches principales : +- fondements +- modele +- architecture +- gouvernance +- decisions +- politiques +- ecosystemes +- guides +- corpus-a-integrer +- diffusion +" + +create_readme_if_absent "infrastructure/README.md" \ +"# Infrastructure + +Contient l'implémentation opératoire : +- ansible +- opentofu +- cloud-init +- templates +- netbox +" + +create_readme_if_absent "operations/README.md" \ +"# Operations + +Contient : +- runbooks +- evidence +- procedures +- checklists +" + +create_readme_if_absent "archives/README.md" \ +"# Archives + +Contient : +- legacy +- variantes remplacées +- artefacts déplacés pendant migration +" + +# ---------------------------------------------------------------------------- +# 7) Rapport final +# ---------------------------------------------------------------------------- + +log "Migration v2 terminée" +log "Journal: $LOG_FILE" + +cat < /etc/sysctl.d/99-debug-freeze.conf </dev/null +ls -la /boot/efi/EFI/ + +# Boot du dernier crash +journalctl -k -b -1 | tail -100 +journalctl -b -1 -p err +``` + +## Références utiles + +- Proxmox Wiki — PCI(e) Passthrough : https://pve.proxmox.com/wiki/PCI(e)_Passthrough +- Proxmox Wiki — Host Bootloader : https://pve.proxmox.com/wiki/Host_Bootloader +- AMD AGESA changelog (par modèle de carte mère, sur le site du fabricant) — vérifier régulièrement les mentions "stability" + +--- + +*Post-mortem rédigé suite à un diagnostic du 29 avril 2026. Hardware : ASUS TUF Gaming X670E-Plus WiFi, BIOS 3602. Stack : Proxmox VE 8 / kernel 6.8.12-20-pve / Ceph Quincy.* diff --git a/site-alliance-boreale/README.md b/site-alliance-boreale/README.md new file mode 100644 index 0000000..7de0c84 --- /dev/null +++ b/site-alliance-boreale/README.md @@ -0,0 +1,91 @@ +# Site — Alliance Boréale + +Site web statique de l'**Alliance Boréale — souveraineté numérique**. +Thème : aurore boréale, étoiles, constellation. Aucune dépendance, aucun build, +aucun pistage, aucune ressource tierce (police système, JS et CSS locaux). + +> Ce site **remplace** l'ancien `alliance-boreale.ca`, déclaré obsolète. +> Il abandonne volontairement toute imagerie **forestière** (sapin, « forêt +> numérique ») pour éviter la confusion avec l'OBNL *Alliance Forêt Boréale*, +> et adopte le registre **aurore / constellation**. + +## Arborescence + +``` +site-alliance-boreale/ +├── public/ # racine web (ce que nginx sert) +│ ├── index.html # Accueil +│ ├── vision.html # Vision, valeurs, infrastructure mutualisée, « tout est libre » +│ ├── label.html # Le Label : 3 niveaux (Souverain / Résilient / Exemplaire) +│ ├── constellation.html # Les artisans = étoiles ; réciprocité +│ ├── atelier.html # Set-OPS (moteur libre) + la forge de l'Alliance +│ ├── faq.html # Questions & réponses (par public + le Label) +│ ├── contact.html # Contact +│ ├── 404.html +│ └── assets/ +│ ├── css/styles.css +│ └── js/constellation.js # champ d'étoiles + lignes de constellation +└── deploy/ + ├── alliance-boreale.nginx.conf # exemple de vhost + └── deployer.sh # synchronisation rsync + reload nginx +``` + +## Prévisualiser en local + +Aucun outil requis ; un simple serveur statique suffit : + +```bash +cd public +python3 -m http.server 8080 +# puis http://localhost:8080 +``` + +## Déployer sur web-frontal-01 + +1. **Créer la racine web** sur le serveur (une fois) : + + ```bash + ssh ansible@web-frontal-01 'sudo mkdir -p /var/www/alliance-boreale \ + && sudo chown ansible:ansible /var/www/alliance-boreale' + ``` + +2. **Installer le vhost** (une fois) : + + ```bash + scp deploy/alliance-boreale.nginx.conf \ + ansible@web-frontal-01:/tmp/alliance-boreale.conf + ssh ansible@web-frontal-01 ' + sudo mv /tmp/alliance-boreale.conf /etc/nginx/sites-available/ && + sudo ln -sf /etc/nginx/sites-available/alliance-boreale.conf \ + /etc/nginx/sites-enabled/alliance-boreale.conf && + sudo nginx -t && sudo systemctl reload nginx' + ``` + + > Adapter `server_name` et les chemins TLS dans le vhost. Le bloc HTTPS + > attend un certificat (step-ca interne, Let's Encrypt, etc.). + +3. **Déployer le contenu** (à chaque mise à jour) : + + ```bash + ./deploy/deployer.sh # valeurs par défaut + RECHARGER_NGINX=1 ./deploy/deployer.sh # + reload nginx + DRY_RUN=1 ./deploy/deployer.sh # simulation + ``` + + Variables : `HOTE`, `UTILISATEUR`, `RACINE`, `PORT_SSH`, `RECHARGER_NGINX`, `DRY_RUN`. + +## À personnaliser avant publication + +- **Courriel de contact** dans `public/contact.html` (`info@alliance-boreale.ca` + est un repère à remplacer). +- **`server_name`** et **chemins des certificats** dans le vhost. +- Logo si vous en avez un (le « brand-mark » de l'en-tête est dessiné en CSS). + Le **favicon** (`public/favicon.svg`, étoile + aurore) est déjà fourni. + +## Choix techniques + +- **Statique pur** : portable, auditable, longévité maximale, souverain. +- **Zéro tiers** : pas de CDN, pas de Google Fonts, pas de tracker. CSP stricte + `default-src 'self'`. +- **Accessibilité** : navigation au clavier, `skip-link`, contrastes, + `prefers-reduced-motion` respecté (l'animation s'arrête). diff --git a/site-alliance-boreale/deploy/alliance-boreale.nginx.conf b/site-alliance-boreale/deploy/alliance-boreale.nginx.conf new file mode 100644 index 0000000..8662b42 --- /dev/null +++ b/site-alliance-boreale/deploy/alliance-boreale.nginx.conf @@ -0,0 +1,78 @@ +# ============================================================================= +# Alliance Boréale — vhost nginx (site statique) +# À déposer sur web-frontal-01 dans /etc/nginx/sites-available/, puis lier +# dans sites-enabled/. Sert le contenu statique de /var/www/alliance-boreale. +# +# sudo ln -s /etc/nginx/sites-available/alliance-boreale.conf \ +# /etc/nginx/sites-enabled/alliance-boreale.conf +# sudo nginx -t && sudo systemctl reload nginx +# +# Adapter : server_name, chemins des certificats TLS, racine si besoin. +# ============================================================================= + +# --- Redirection HTTP -> HTTPS --------------------------------------------- +server { + listen 80; + listen [::]:80; + server_name alliance-boreale.ca www.alliance-boreale.ca; + + # Laisser passer les défis ACME si vous en utilisez un : + location /.well-known/acme-challenge/ { + root /var/www/alliance-boreale; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# --- Site HTTPS ------------------------------------------------------------- +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name alliance-boreale.ca www.alliance-boreale.ca; + + root /var/www/alliance-boreale; + index index.html; + + # --- TLS (adapter les chemins : step-ca interne, Let's Encrypt, etc.) --- + ssl_certificate /etc/ssl/alliance-boreale/fullchain.pem; + ssl_certificate_key /etc/ssl/alliance-boreale/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_session_timeout 1d; + ssl_session_cache shared:AB_TLS:10m; + + # --- En-têtes de sécurité (site statique, sans pistage) ---------------- + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + # Tout le contenu est local : pas de CDN, pas de tiers. + add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always; + # Décommenter une fois le HTTPS stabilisé sur le domaine : + # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # --- Compression -------------------------------------------------------- + gzip on; + gzip_types text/css application/javascript text/html image/svg+xml; + gzip_min_length 1024; + + # --- Cache des actifs statiques ---------------------------------------- + location /assets/ { + expires 7d; + add_header Cache-Control "public"; + } + + # --- Routage ------------------------------------------------------------ + location / { + try_files $uri $uri/ $uri.html =404; + } + + error_page 404 /404.html; + + # Pas de logs d'accès nominatifs au-delà du nécessaire (sobriété/vie privée). + access_log /var/log/nginx/alliance-boreale.access.log; + error_log /var/log/nginx/alliance-boreale.error.log; +} diff --git a/site-alliance-boreale/deploy/deployer.sh b/site-alliance-boreale/deploy/deployer.sh new file mode 100755 index 0000000..233ff8f --- /dev/null +++ b/site-alliance-boreale/deploy/deployer.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# ============================================================================= +# Déploiement du site Alliance Boréale vers web-frontal-01 (ou tout hôte nginx). +# +# Synchronise public/ vers la racine web distante, puis (optionnel) recharge +# nginx. N'embarque aucun secret. À lancer depuis votre poste. +# +# ./deploy/deployer.sh # déploie avec les valeurs par défaut +# HOTE=web-frontal-01 ./deploy/deployer.sh +# RACINE=/var/www/alliance-boreale UTILISATEUR=ansible ./deploy/deployer.sh +# RECHARGER_NGINX=1 ./deploy/deployer.sh # recharge nginx après copie +# DRY_RUN=1 ./deploy/deployer.sh # simulation, ne copie rien +# ============================================================================= +set -euo pipefail + +# --- Paramètres (surcharger par variables d'environnement) ------------------ +HOTE="${HOTE:-web-frontal-01}" +UTILISATEUR="${UTILISATEUR:-ansible}" +RACINE="${RACINE:-/var/www/alliance-boreale}" +PORT_SSH="${PORT_SSH:-22}" +RECHARGER_NGINX="${RECHARGER_NGINX:-0}" +DRY_RUN="${DRY_RUN:-0}" + +# --- Emplacements ----------------------------------------------------------- +ICI="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SOURCE="${ICI}/public/" + +if [[ ! -d "${SOURCE}" ]]; then + echo "ERREUR : dossier source introuvable : ${SOURCE}" >&2 + exit 1 +fi + +CIBLE="${UTILISATEUR}@${HOTE}:${RACINE}/" + +OPTS_RSYNC=(-az --delete --human-readable --itemize-changes + --exclude ".DS_Store" --exclude "*.swp") +[[ "${DRY_RUN}" == "1" ]] && OPTS_RSYNC+=(--dry-run) + +echo "==> Source : ${SOURCE}" +echo "==> Cible : ${CIBLE} (port SSH ${PORT_SSH})" +[[ "${DRY_RUN}" == "1" ]] && echo "==> MODE SIMULATION (aucune écriture)" +echo + +rsync "${OPTS_RSYNC[@]}" -e "ssh -p ${PORT_SSH}" "${SOURCE}" "${CIBLE}" + +if [[ "${RECHARGER_NGINX}" == "1" && "${DRY_RUN}" != "1" ]]; then + echo + echo "==> Vérification et rechargement de nginx sur ${HOTE}" + ssh -p "${PORT_SSH}" "${UTILISATEUR}@${HOTE}" 'sudo nginx -t && sudo systemctl reload nginx' +fi + +echo +echo "==> Terminé." +[[ "${DRY_RUN}" == "1" ]] && echo " (simulation — relancer sans DRY_RUN=1 pour déployer pour de vrai)" diff --git a/site-alliance-boreale/promo.piz b/site-alliance-boreale/promo.piz new file mode 100644 index 0000000..795b08c Binary files /dev/null and b/site-alliance-boreale/promo.piz differ diff --git a/site-alliance-boreale/public/404.html b/site-alliance-boreale/public/404.html new file mode 100644 index 0000000..50175e2 --- /dev/null +++ b/site-alliance-boreale/public/404.html @@ -0,0 +1,30 @@ + + + + + + Page introuvable — Alliance Boréale + + + + + + + +
+
+

Erreur 404

+

Cette étoile n'existe pas

+

La page demandée s'est éteinte ou n'a jamais brillé. Revenons vers la constellation.

+ +
+
+ + + + diff --git a/site-alliance-boreale/public/assets/css/styles.css b/site-alliance-boreale/public/assets/css/styles.css new file mode 100644 index 0000000..3931374 --- /dev/null +++ b/site-alliance-boreale/public/assets/css/styles.css @@ -0,0 +1,350 @@ +/* ========================================================================= + Alliance Boréale — souveraineté numérique + Thème : aurore boréale · étoiles · constellation + 100 % statique, sans dépendance, sans pistage. Police système (souveraineté). + ========================================================================= */ + +:root { + --bg: #05060f; + --bg-2: #0a0d24; + --panel: rgba(255, 255, 255, 0.045); + --panel-2: rgba(255, 255, 255, 0.07); + --border: rgba(255, 255, 255, 0.10); + --ink: #e9ecff; + --ink-dim: #99a1c9; + --ink-faint: #6b739b; + + --aurora-teal: #4fe3c1; + --aurora-cyan: #56c2ff; + --aurora-violet: #a98bff; + --aurora-green: #2ec18f; + --star: #ffffff; + --gold: #ffd58a; + + --maxw: 1080px; + --radius: 16px; + --shadow: 0 18px 50px rgba(0, 0, 0, 0.45); + + --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Noto Sans", "Helvetica Neue", Arial, sans-serif; +} + +*, +*::before, +*::after { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + font-family: var(--font); + color: var(--ink); + background: var(--bg); + line-height: 1.65; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + overflow-x: hidden; +} + +/* ---------- Ciel : aurore + constellation (fixe, derrière tout) ---------- */ +.sky { + position: fixed; + inset: 0; + z-index: -1; + background: + radial-gradient(1200px 700px at 80% -10%, #131a44 0%, transparent 60%), + radial-gradient(900px 600px at 10% 0%, #0e1640 0%, transparent 55%), + linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 70%); +} + +.sky canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + display: block; +} + +.aurora { + position: absolute; + inset: -20% -10% auto -10%; + height: 70vh; + filter: blur(60px) saturate(140%); + opacity: 0.55; + background: + radial-gradient(40% 60% at 20% 30%, var(--aurora-teal) 0%, transparent 70%), + radial-gradient(45% 70% at 55% 20%, var(--aurora-cyan) 0%, transparent 70%), + radial-gradient(40% 65% at 80% 35%, var(--aurora-violet) 0%, transparent 70%); + animation: aurora-drift 22s ease-in-out infinite alternate; +} + +@keyframes aurora-drift { + 0% { transform: translate3d(-4%, -2%, 0) scale(1.05); opacity: 0.45; } + 50% { transform: translate3d(3%, 2%, 0) scale(1.15); opacity: 0.60; } + 100% { transform: translate3d(6%, -1%, 0) scale(1.08); opacity: 0.50; } +} + +/* ---------- Mise en page ---------- */ +.wrap { width: min(100% - 2.5rem, var(--maxw)); margin-inline: auto; } + +section { padding: clamp(3rem, 7vw, 6rem) 0; } + +.eyebrow { + display: inline-block; + font-size: 0.78rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--aurora-teal); + margin: 0 0 0.6rem; +} + +h1, h2, h3 { line-height: 1.15; letter-spacing: -0.015em; font-weight: 700; } +h1 { font-size: clamp(2.3rem, 6vw, 4rem); margin: 0 0 1rem; } +h2 { font-size: clamp(1.7rem, 3.6vw, 2.5rem); margin: 0 0 1rem; } +h3 { font-size: 1.2rem; margin: 0 0 0.5rem; } + +p { color: var(--ink-dim); margin: 0 0 1rem; } +.lead { font-size: clamp(1.05rem, 2vw, 1.3rem); color: var(--ink); max-width: 60ch; } + +a { color: var(--aurora-cyan); text-decoration: none; } +a:hover { text-decoration: underline; } + +.gradient-text { + background: linear-gradient(100deg, var(--aurora-teal), var(--aurora-cyan) 45%, var(--aurora-violet)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +/* ---------- En-tête / navigation ---------- */ +.site-header { + position: sticky; + top: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.9rem clamp(1rem, 4vw, 2.5rem); + background: rgba(5, 6, 15, 0.55); + backdrop-filter: blur(14px); + border-bottom: 1px solid var(--border); +} + +.brand { display: inline-flex; align-items: center; gap: 0.7rem; color: var(--ink); } +.brand:hover { text-decoration: none; } + +.brand-mark { + width: 30px; height: 30px; + border-radius: 50%; + background: + radial-gradient(circle at 35% 30%, var(--star) 0 2px, transparent 3px), + conic-gradient(from 200deg, var(--aurora-violet), var(--aurora-cyan), var(--aurora-teal), var(--aurora-violet)); + box-shadow: 0 0 18px rgba(86, 194, 255, 0.55); + flex: none; +} + +.brand-text { display: flex; flex-direction: column; line-height: 1.1; } +.brand-name { font-weight: 700; letter-spacing: 0.01em; } +.brand-sub { font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-faint); } + +.site-nav ul { + list-style: none; + display: flex; + gap: 0.4rem; + margin: 0; padding: 0; +} +.site-nav a { + display: block; + padding: 0.5rem 0.85rem; + border-radius: 999px; + color: var(--ink-dim); + font-size: 0.95rem; +} +.site-nav a:hover { color: var(--ink); background: var(--panel); text-decoration: none; } +.site-nav a[aria-current="page"] { color: var(--ink); background: var(--panel-2); } + +.nav-toggle { + display: none; + background: var(--panel); + border: 1px solid var(--border); + color: var(--ink); + border-radius: 10px; + padding: 0.5rem 0.7rem; + font-size: 0.95rem; + cursor: pointer; +} + +/* ---------- Héro ---------- */ +.hero { padding-top: clamp(3rem, 8vw, 6.5rem); text-align: center; } +.hero .lead { margin-inline: auto; } +.hero-cta { display: flex; gap: 0.8rem; justify-content: center; flex-wrap: wrap; margin-top: 1.8rem; } + +/* ---------- Boutons ---------- */ +.btn { + display: inline-block; + padding: 0.8rem 1.4rem; + border-radius: 999px; + font-weight: 600; + border: 1px solid var(--border); + color: var(--ink); + transition: transform 0.15s ease, box-shadow 0.15s ease; +} +.btn:hover { text-decoration: none; transform: translateY(-2px); } +.btn-primary { + background: linear-gradient(100deg, var(--aurora-teal), var(--aurora-cyan)); + color: #04121a; + border-color: transparent; + box-shadow: 0 10px 30px rgba(79, 227, 193, 0.25); +} +.btn-primary:hover { box-shadow: 0 14px 38px rgba(86, 194, 255, 0.35); } +.btn-ghost { background: var(--panel); } + +/* ---------- Cartes / grilles ---------- */ +.grid { display: grid; gap: 1.2rem; } +.grid-3 { grid-template-columns: repeat(3, 1fr); } +.grid-2 { grid-template-columns: repeat(2, 1fr); } + +.card { + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.6rem; + backdrop-filter: blur(6px); +} +.card h3 { color: var(--ink); } +.card p:last-child { margin-bottom: 0; } + +.star-bullet { color: var(--aurora-teal); margin-right: 0.4rem; } + +/* ---------- Le Label : paliers ---------- */ +.tiers { display: grid; gap: 1.2rem; grid-template-columns: repeat(3, 1fr); align-items: stretch; } +.tier { + position: relative; + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.8rem 1.6rem; + overflow: hidden; +} +.tier::before { + content: ""; + position: absolute; + inset: 0 0 auto 0; + height: 4px; +} +.tier-1::before { background: var(--aurora-teal); } +.tier-2::before { background: var(--aurora-cyan); } +.tier-3::before { background: var(--aurora-violet); } +.tier .tier-rank { font-size: 0.8rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-faint); } +.tier h3 { font-size: 1.5rem; margin-top: 0.2rem; } +.tier ul { padding-left: 0; list-style: none; margin: 1rem 0 0; } +.tier li { color: var(--ink-dim); padding: 0.35rem 0; border-top: 1px solid var(--border); } +.tier li:first-child { border-top: none; } +.tier .privilege { color: var(--ink); font-weight: 600; } + +/* ---------- Citation ---------- */ +.quote { + border-left: 3px solid var(--aurora-cyan); + padding: 0.4rem 0 0.4rem 1.4rem; + font-size: clamp(1.15rem, 2.4vw, 1.6rem); + color: var(--ink); + font-style: italic; + max-width: 60ch; +} +.quote cite { display: block; font-style: normal; font-size: 0.95rem; color: var(--ink-faint); margin-top: 0.8rem; } + +/* ---------- Bandeau ---------- */ +.panel-feature { + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: clamp(1.8rem, 4vw, 3rem); +} + +/* ---------- Pied de page ---------- */ +.site-footer { + border-top: 1px solid var(--border); + background: rgba(5, 6, 15, 0.6); + padding: clamp(2.5rem, 5vw, 4rem) 0 2rem; + margin-top: 2rem; +} +.footer-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 2rem; } +.site-footer nav ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.4rem; } +.site-footer nav a { color: var(--ink-dim); } +.site-footer .legal { + margin-top: 2rem; + padding-top: 1.4rem; + border-top: 1px solid var(--border); + font-size: 0.85rem; + color: var(--ink-faint); +} + +/* ---------- Accessibilité ---------- */ +:focus-visible { outline: 2px solid var(--aurora-cyan); outline-offset: 3px; border-radius: 6px; } +.skip-link { + position: absolute; left: -9999px; top: 0; + background: var(--aurora-cyan); color: #04121a; padding: 0.6rem 1rem; border-radius: 0 0 10px 0; z-index: 100; +} +.skip-link:focus { left: 0; } + +/* ---------- FAQ (accordéons natifs) ---------- */ +.faq-group { margin-top: 1rem; } +details.qa { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 14px; + margin: 0.7rem 0; + padding: 0 1.3rem; +} +details.qa[open] { background: var(--panel-2); } +details.qa summary { + list-style: none; + cursor: pointer; + position: relative; + padding: 1.1rem 2.2rem 1.1rem 0; + font-weight: 600; + color: var(--ink); + font-size: 1.05rem; +} +details.qa summary::-webkit-details-marker { display: none; } +details.qa summary::after { + content: "+"; + position: absolute; right: 0; top: 50%; + transform: translateY(-50%); + color: var(--aurora-teal); + font-size: 1.5rem; line-height: 1; font-weight: 400; + transition: transform 0.2s ease; +} +details.qa[open] summary::after { content: "\2212"; } +details.qa .answer { + color: var(--ink-dim); + padding: 0 0 1.3rem; + max-width: 68ch; +} +details.qa .answer strong { color: var(--ink); } +details.qa .answer em { color: var(--aurora-teal); font-style: italic; } + +/* ---------- Responsive ---------- */ +@media (max-width: 820px) { + .grid-3, .grid-2, .tiers, .footer-grid { grid-template-columns: 1fr; } + + .nav-toggle { display: inline-block; } + .site-nav ul { + position: absolute; + top: 100%; left: 0; right: 0; + flex-direction: column; + gap: 0; + background: rgba(5, 6, 15, 0.96); + border-bottom: 1px solid var(--border); + padding: 0.5rem; + display: none; + } + .site-nav.open ul { display: flex; } + .site-nav a { border-radius: 10px; } +} + +@media (prefers-reduced-motion: reduce) { + .aurora { animation: none; } + html { scroll-behavior: auto; } +} diff --git a/site-alliance-boreale/public/assets/js/constellation.js b/site-alliance-boreale/public/assets/js/constellation.js new file mode 100644 index 0000000..3e7e825 --- /dev/null +++ b/site-alliance-boreale/public/assets/js/constellation.js @@ -0,0 +1,102 @@ +/* Alliance Boréale — champ d'étoiles + constellation. + Vanilla JS, aucune dépendance. Désactivé si l'utilisateur préfère moins + d'animation. La métaphore : chaque artisan est une étoile ; reliés, ils + forment la constellation. */ + +(function () { + "use strict"; + + /* ---- Menu mobile ---- */ + var nav = document.querySelector(".site-nav"); + var toggle = document.querySelector(".nav-toggle"); + if (nav && toggle) { + toggle.addEventListener("click", function () { + var open = nav.classList.toggle("open"); + toggle.setAttribute("aria-expanded", open ? "true" : "false"); + }); + } + + /* ---- Constellation ---- */ + var canvas = document.getElementById("constellation"); + if (!canvas) return; + + var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + var ctx = canvas.getContext("2d"); + var stars = []; + var w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2); + var LINK_DIST = 130; + + function resize() { + w = canvas.clientWidth; + h = canvas.clientHeight; + canvas.width = w * dpr; + canvas.height = h * dpr; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + seed(); + } + + function seed() { + var count = Math.round((w * h) / 14000); + count = Math.max(40, Math.min(160, count)); + stars = []; + for (var i = 0; i < count; i++) { + stars.push({ + x: Math.random() * w, + y: Math.random() * h, + r: Math.random() * 1.3 + 0.4, + vx: (Math.random() - 0.5) * 0.12, + vy: (Math.random() - 0.5) * 0.12, + tw: Math.random() * Math.PI * 2 + }); + } + } + + function draw() { + ctx.clearRect(0, 0, w, h); + + // liens de constellation + for (var i = 0; i < stars.length; i++) { + for (var j = i + 1; j < stars.length; j++) { + var dx = stars[i].x - stars[j].x; + var dy = stars[i].y - stars[j].y; + var d = Math.sqrt(dx * dx + dy * dy); + if (d < LINK_DIST) { + var a = (1 - d / LINK_DIST) * 0.22; + ctx.strokeStyle = "rgba(120, 200, 255," + a + ")"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(stars[i].x, stars[i].y); + ctx.lineTo(stars[j].x, stars[j].y); + ctx.stroke(); + } + } + } + + // étoiles + for (var k = 0; k < stars.length; k++) { + var s = stars[k]; + s.tw += 0.02; + var glow = 0.6 + Math.sin(s.tw) * 0.4; + ctx.beginPath(); + ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); + ctx.fillStyle = "rgba(255,255,255," + glow + ")"; + ctx.shadowColor = "rgba(160,220,255,0.9)"; + ctx.shadowBlur = 6; + ctx.fill(); + ctx.shadowBlur = 0; + + if (!reduce) { + s.x += s.vx; + s.y += s.vy; + if (s.x < 0 || s.x > w) s.vx *= -1; + if (s.y < 0 || s.y > h) s.vy *= -1; + } + } + + if (!reduce) requestAnimationFrame(draw); + } + + window.addEventListener("resize", resize); + resize(); + draw(); // un rendu statique si reduce, sinon animé +})(); diff --git a/site-alliance-boreale/public/atelier.html b/site-alliance-boreale/public/atelier.html new file mode 100644 index 0000000..21a9de8 --- /dev/null +++ b/site-alliance-boreale/public/atelier.html @@ -0,0 +1,141 @@ + + + + + + L'atelier — Alliance Boréale + + + + + + + + + + + + +
+
+

L'atelier ouvert

+

Le moteur derrière la promesse : Set-OPS

+

La souveraineté ne se décrète pas, elle s'outille. Set-OPS est le moteur Ansible libre de l'Alliance Boréale : on décrit l'écosystème dans un plan, et la flotte de serveurs se déploie, durcie et reproductible, sur du logiciel libre de bout en bout.

+ +
+ +
+

Comment ça marche

+

Un plan déclaratif, une flotte cohérente

+
+
+

On édite le plan

+

Serveurs, applications, bases de données, domaines : tout est décrit dans des fichiers lisibles. La vérité tient dans le plan, pas dans la tête d'un administrateur.

+
+
+

L'inventaire se génère

+

Set-OPS dérive l'inventaire Ansible depuis le plan : adressage, groupes et rôles découlent de la nomenclature, sans saisie en double.

+
+
+

Les VM se clonent

+

Chaque serveur naît d'un golden template Debian 13 durci sur Proxmox : socle commun, identité par cloud-init, puis configuration réelle par Ansible.

+
+
+

Les rôles s'appliquent

+

Par groupes, de façon idempotente. Le même plan rejoué redonne le même état : traçable, auditable, reproductible.

+
+
+
+ +
+
+

Un écosystème complet

+

Des piliers, tous libres

+

Set-OPS assemble les briques d'un écosystème souverain — chacune un logiciel libre éprouvé :

+
+

Socle durci

Debian 13, AppArmor, auditd, fail2ban, mises à jour automatiques, nftables préparé.

+

PKI & DNS

Autorité de certification interne et résolution de noms souveraine.

+

Identité & SSO

Annuaire et authentification unique pour tout l'écosystème.

+

Bases de données

Stockage relationnel et cache, opérés selon les mêmes règles.

+

Observabilité

Métriques, journaux, tableaux de bord et supervision.

+

Forge

Hébergement du code et de l'automatisation — la forge de l'Alliance.

+
+
+
+ +
+

La forge

+

Là où vit le code commun

+

La forge de l'Alliance Boréale héberge Set-OPS et l'automatisation de l'écosystème, sur du logiciel libre. C'est l'un des services mutualisés de l'infrastructure — et la preuve concrète que « tout est libre » n'est pas qu'une devise.

+
+ +
+

Cloner Set-OPS

+

Le moteur est offert à la communauté québécoise :

+

git clone https://forge.alliance-boreale.ca/Alliance-Boreale/Set-OPS-Public.git

+
+
+
+ +
+
+
+ « Set-OPS s'exploite entièrement à la main, sans aucune IA. La souveraineté, c'est aussi ne dépendre d'aucune boîte noire. » +
+
+
+
+ +
+ + +
+ + + + diff --git a/site-alliance-boreale/public/constellation.html b/site-alliance-boreale/public/constellation.html new file mode 100644 index 0000000..038161f --- /dev/null +++ b/site-alliance-boreale/public/constellation.html @@ -0,0 +1,107 @@ + + + + + + La Constellation — Alliance Boréale + + + + + + + + + + + + +
+
+

La Constellation

+

Chaque artisan est une étoile

+

Pris séparément, un artisan du numérique reste fragile : une panne, une absence, un sinistre. Reliés, les artisans forment une constellation — un dessin stable que l'on reconnaît dans le ciel, même si une étoile vacille.

+
+ « Tu ne dépends pas d'une étoile, mais de la constellation. » +
+
+ +
+

Comment ça tient

+

La réciprocité, pas la hiérarchie

+
+
+

Entraide opérée

+

L'infrastructure commune — DNS, forges, supervision, sauvegardes — est portée par le réseau, pas par chacun dans son coin.

+
+
+

Reprise mutuelle

+

Si une étoile tombe, une autre reprend ses clients. La portabilité prouvée rend cette promesse réelle, pas théorique.

+
+
+

Audit entre pairs

+

On se vérifie mutuellement, avec bienveillance et rigueur. La confiance circule de pair à pair, gratuitement.

+
+
+
+ +
+
+

Qui peut rejoindre

+

Une place pour chaque étoile

+

L'Alliance Boréale s'adresse aux artisans et organisations du numérique au Québec — petits ou grands — qui veulent offrir des services souverains, libres et dignes de confiance. On entre par la transparence : on déclare ce que l'on fait, on accepte le regard des pairs, on progresse à travers le Label.

+ +
+
+
+ +
+ + +
+ + + + diff --git a/site-alliance-boreale/public/contact.html b/site-alliance-boreale/public/contact.html new file mode 100644 index 0000000..6853fe5 --- /dev/null +++ b/site-alliance-boreale/public/contact.html @@ -0,0 +1,100 @@ + + + + + + Contact — Alliance Boréale + + + + + + + + + + + + +
+
+

Contact

+

Allumons une nouvelle étoile

+

Vous portez un projet numérique souverain, ou vous voulez rejoindre la constellation ? Écrivez-nous. L'Alliance Boréale est un projet collectif et polyphonique — votre voix y a sa place.

+
+ +
+
+ +
+

Président-fondateur

+

Daniel Allaire

+

Porteur d'une vision : une façon différente, responsable et durable, de concevoir nos écosystèmes numériques.

+
+
+
+ +
+
+
+ « L'Alliance Boréale, c'est avant tout une vision : une façon différente de concevoir nos écosystèmes numériques. » + — Daniel Allaire, président-fondateur +
+
+
+
+ +
+ + +
+ + + + diff --git a/site-alliance-boreale/public/faq.html b/site-alliance-boreale/public/faq.html new file mode 100644 index 0000000..a80f7f1 --- /dev/null +++ b/site-alliance-boreale/public/faq.html @@ -0,0 +1,236 @@ + + + + + + FAQ — Alliance Boréale + + + + + + + + + + + + +
+
+

Questions & réponses

+

Des réponses franches

+

Voici les questions qu'on nous pose le plus souvent — des professionnels du milieu aux décideurs publics. Quand quelque chose est encore en chantier, on le dit : la franchise distingue l'artisan sérieux du vendeur de rêve.

+
+ +
+

Aux professionnels du milieu

+

MSP, intégrateurs, revendeurs

+
+
+ Confier des données de PME à des structures moins outillées, n'est-ce pas risqué ? +
L'outil encode les bonnes pratiques — durcissement, PKI interne, validations à chaque étape, golden template commun. Il élève le plancher, il ne l'abaisse pas. Un sysadmin compétent n'est pas un amateur ; il lui manquait surtout un levier. Et la continuité ne repose plus sur un individu, mais sur le réseau.
+
+
+ Vous cassez le marché avec du gratuit. +
On ne casse rien : on rouvre un marché que les géants étrangers ont capté. L'outil est libre, mais l'expertise et le service, eux, se paient — c'est le modèle Red Hat. Et très franchement, un professionnel aguerri serait un excellent candidat : l'outil le rend plus efficace, il ne le remplace pas.
+
+
+ Vous n'aurez jamais notre échelle ni nos SLA. +
Exact, et c'est volontaire. On ne cherche pas à battre le géant sur son terrain — le capital — on change de terrain : la proximité et la souveraineté. Pour une PME, un hébergeur local qu'on peut appeler vaut souvent mieux qu'un SLA papier d'un géant injoignable.
+
+
+ Pourquoi ne devrais-je pas vous voir comme un concurrent ? +
Parce que le vrai concurrent, celui qui aspire vos clients année après année, c'est l'hyperscaler — pas nous. Nous, on vous donne le levier pour les garder ici, au Québec. On joue dans la même équipe.
+
+
+
+ +
+

Aux prudents et exigeants

+

Techniciens et investisseurs prudents

+
+
+ Un hébergeur seul peut-il offrir la fiabilité d'un hyperscaler ? +
Seul, non — et on ne le prétend pas. En constellation, oui, et c'est tout le design. La clé, c'est la portabilité : l'écosystème d'un client est un plan déclaratif reproductible. Si un hébergeur tombe, un autre le ré-instancie ailleurs. Le client ne fait pas confiance à un individu, mais au standard et au réseau.
+
+
+ Qui est responsable quand ça plante ? Sauvegardes, reprise ? +
L'hébergeur, contractuellement, comme tout hébergeur. La différence : plus de point unique de défaillance humain. Honnêtement, la sauvegarde et la reprise sont des volets encore à muscler — c'est sur la feuille de route.
+
+
+ Le Québec ne peut pas rivaliser avec des milliards de R&D. +
On ne rivalise pas en R&D — on récupère ce qui fuit déjà. Des dizaines de milliers de PME paient des géants étrangers ; en rapatrier une fraction localement, c'est énorme. Ça ne demande pas de battre AWS, juste d'offrir une alternative souveraine et joignable.
+
+
+ Open source, n'est-ce pas synonyme de mal maintenu ? +
Maintenu par un artisan et par l'Alliance, avec des validations automatiques à chaque changement. Et un point fort sous-estimé : l'outil s'exploite entièrement à la main, sans aucune IA, sans magie ni opacité — un humain lit et comprend tout. La transparence, c'est l'inverse du trou de sécurité.
+
+
+ Vous réinventez NetBox ou YunoHost. +
On en recoupe des morceaux, on l'assume — c'est écrit noir sur blanc dans le dépôt. Mais l'assemblage — Proxmox + Ansible + un modèle souverain léger, possédé en entier — n'existe nulle part. Et la règle est claire : si on se surprend à réimplémenter un NetBox, on l'adopte — on ne le réécrit pas.
+
+
+ N'est-ce pas déjà ce que font les CHATONS ? +
Un cousin qu'on admire — et il en existe déjà au Québec, donc on s'inscrit dans leur lignée. Mais deux pièces leur manquent par choix : un moteur technique commun (chaque chaton bricole sa propre stack) et un modèle viable pour servir des entreprises. On ajoute exactement ça : la portabilité et l'outillage. Pas un rival — le cousin d'ici, outillé.
+
+
+ C'est déployé en production, ou c'est de la vapeur ? +
Le moteur et la modélisation sont prouvés et validés ; le déploiement à grande échelle sur de vraies machines est l'étape en cours. On ne vend pas du « déployé partout » — on montre une fondation solide et un chemin clair.
+
+
+ C'est construit avec une IA — est-ce sérieux et maintenable ? +
L'IA a aidé à construire, mais l'outil ne dépend d'aucune IA pour s'exécuter ni se maintenir — c'est un impératif gravé dans le dépôt. Tout est commité, documenté, validé, lisible par un humain. La souveraineté va jusque-là : pas de laisse, ni géant, ni IA.
+
+
+ Êtes-vous certifiés SOC 2 ou ISO ? +
Pas aujourd'hui, et on ne le prétend pas. SOC 2 est un audit lourd, coûteux et surtout américain — un peu dissonant dans un récit de souveraineté. Ce que l'outil montre déjà : il encode les principes que ces normes auditent (sécurité, disponibilité, traçabilité), avec une trajectoire vers ISO 27001 à mesure que la constellation mûrit.
+
+
+
+ +
+

Aux curieux et futurs partenaires

+

Clients, hébergeurs candidats, alliés

+
+
+ En quoi est-ce mieux que Google ou AWS ? +
Tes données restent au Québec, chez un humain que tu peux appeler, sans verrouillage propriétaire. C'est souvent moins cher à ton échelle — et surtout réversible : tu repars avec ton écosystème quand tu veux. Tu n'es jamais prisonnier.
+
+
+ Et si mon hébergeur disparaît ? +
Tu n'es pas coincé : ton écosystème est portable, et un autre artisan de la constellation le reprend. Tu ne dépends pas d'un homme, mais d'un réseau et d'un standard. C'est ça, la vraie sécurité.
+
+
+ Est-ce aussi simple que le cloud ? +
Pour toi, oui — l'artisan s'occupe de toute la technique. La différence, c'est qu'il est à toi, local, joignable, et que tu n'es jamais enfermé. Le confort du cloud, sans la laisse.
+
+
+ Qu'est-ce qu'il me faut pour me lancer comme hébergeur ? +
Des bases solides en Linux, Ansible et Proxmox, une grappe Proxmox, et l'envie. L'outil est libre et ouvert ; tu pars d'un modèle prêt à déployer, tu le mets à tes couleurs, et un guide pas-à-pas te tient la main.
+
+
+ Est-ce que je peux en vivre ? +
Le modèle est celui de la boutique : peu de clients, à haute valeur, bien servis. Le levier de l'outil et le Label de l'Alliance te rendent crédible face à des PME qui veulent du local. Tu ne vises pas le volume — tu vises la relation.
+
+
+ Comment fonctionne la gouvernance ? +
Une alliance sans subordination : chaque membre reste pleinement souverain, et on mutualise ce qui a du sens — DNS, forge, supervision, label. La gouvernance est distribuée, et prendra une forme coopérative ou OBNL quand la constellation aura sa masse critique.
+
+
+
+ +
+

Aux décideurs publics

+

Décideurs, médias, bailleurs

+
+
+ En quoi est-ce un enjeu de souveraineté pour le Québec ? +
Aujourd'hui, les données et les dollars des PME québécoises partent chez des géants étrangers, soumis à des lois étrangères. Set-OPS et l'Alliance gardent les deux ici : sous le droit d'ici, contrôlés par des gens d'ici. La souveraineté numérique, concrètement.
+
+
+ Et le Cloud Act, les lois étrangères ? +
Des données chez un géant américain restent soumises au Cloud Act, même hébergées ici. Un hébergement souverain local, lui, relève uniquement du droit canadien et québécois. La localisation ne suffit pas — il faut la propriété.
+
+
+ Qu'en est-il de la Loi 25 ? +
La Loi 25 exige une évaluation avant toute communication de renseignements personnels hors Québec — une vraie friction. L'hébergement local l'efface : pas de transfert à justifier, pas d'exposition à un droit étranger. La conformité reste toutefois organisationnelle — l'outil la facilite, il ne la garantit pas.
+
+
+ Quelles retombées économiques concrètes ? +
On rapatrie une part des dépenses cloud de milliers de PME, et on crée des emplois d'artisans numériques répartis sur le territoire — régions comprises — plutôt que concentrés dans une tour. La valeur circule localement.
+
+
+ Est-ce pertinent pour les données publiques et le parapublic ? +
Oui, dès aujourd'hui : hébergement souverain pour OBNL, collectivités et organismes, avec une localisation et un contrôle réels, hors juridiction étrangère. C'est exactement le terrain où la souveraineté compte le plus.
+
+
+ Est-ce sérieux, ou un rêve militant ? +
La fondation technique est réelle et validée, et le modèle économique est éprouvé : du libre + des services, façon Red Hat. On ne parie pas des milliards — on réplique une petite cellule qui fonctionne. Une croissance fractale, pas un pari d'hyperscaler.
+
+
+ Et la sobriété numérique, l'environnement ? +
Le modèle est décentralisé et dimensionné juste — pas la surcapacité permanente d'un hyperscaler. C'est une valeur fondatrice de l'Alliance, pas un argument de façade.
+
+
+ Comment l'État ou un bailleur peut-il aider ? +
Trois leviers : orienter ses propres besoins vers du souverain ; financer la structuration (coopérative ou OBNL) et la formation des hébergeurs ; et reconnaître le Label comme gage de confiance. Pas subventionner un géant — outiller une constellation.
+
+
+
+ +
+

Le Label de l'Alliance

+

Une garantie vérifiable, jamais une hiérarchie de prestige

+
+
+ Qu'est-ce que le Label, concrètement ? +
Trois niveaux accumulatifs, qui certifient la garantie offerte au client — pas le « prestige » de l'artisan. Souverain : libre, local, transparent (le socle). Résilient : portable et couvert — si une étoile s'éteint, une autre te reprend. Exemplaire : porte le commun (contribution, transparence). Chaque niveau ajoute autant d'obligations que de privilèges. Voir le détail du Label.
+
+
+ Un audit par les pairs va-t-il exposer mes méthodes ? +
Non. Comme le standard est ouvert et déclaratif, le pair vérifie la conformité au barème via des preuves publiques (le plan, les CGU, la pile libre) + une démo de reprise — pas ta recette. Tu montres que ta stack atteint une barre publique ; tu n'ouvres pas tes livres. L'Alliance ne se mêle jamais de ta régie interne — ni salaires, ni prix.
+
+
+
+ +
+
+

Une autre question ?

+

Écrivez-nous

+

On répond avec la même franchise. Et si on ne sait pas, on le dit.

+ +
+
+
+ +
+ + +
+ + + + diff --git a/site-alliance-boreale/public/favicon.svg b/site-alliance-boreale/public/favicon.svg new file mode 100644 index 0000000..057b7e0 --- /dev/null +++ b/site-alliance-boreale/public/favicon.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site-alliance-boreale/public/index.html b/site-alliance-boreale/public/index.html new file mode 100644 index 0000000..fc8fa7a --- /dev/null +++ b/site-alliance-boreale/public/index.html @@ -0,0 +1,124 @@ + + + + + + Alliance Boréale — souveraineté numérique + + + + + + + + + + + + +
+
+

Fédération québécoise du numérique éthique

+

Bâtir ensemble une infrastructure numérique souveraine

+

L'Alliance Boréale réunit des artisans du numérique qui partagent une même exigence : responsabilité, traçabilité, portabilité et sobriété. Chacun est une étoile ; ensemble, nous formons une constellation.

+ +
+ +
+
+
+

Infrastructure mutualisée

+

DNS, forges, supervision et sauvegardes opérés en commun, sur du logiciel libre. La robustesse d'un grand opérateur, à l'échelle d'un artisan.

+
+
+

Un Label de confiance

+

Trois niveaux — Souverain, Résilient, Exemplaire — qui certifient la garantie offerte au client, pas la taille de l'entreprise.

+
+
+

Tout est libre

+

L'écosystème entier se construit exclusivement avec du logiciel libre. Pas de boîte noire, pas de dépendance, pas de pistage.

+
+
+
+ +
+
+
+ « Tu ne dépends pas d'une étoile, mais de la constellation. » + — le principe de réciprocité de l'Alliance Boréale +
+
+
+ +
+
+

L'atelier ouvert

+

Le moteur derrière la promesse : Set-OPS

+

Notre souveraineté est outillée. Set-OPS, notre moteur Ansible libre, déploie des écosystèmes numériques durcis et reproductibles — et tout le code vit sur notre forge, ouvert à qui veut le lire.

+ +
+
+ +
+

Pourquoi maintenant

+

Le numérique souverain n'est pas un slogan

+

C'est une façon différente de concevoir nos écosystèmes : des données qui restent au Québec, des outils que l'on comprend de bout en bout, une résilience qui ne tient pas à un seul acteur. L'Alliance Boréale en fait un bien commun, documenté et partagé.

+ +
+
+ +
+ + +
+ + + + diff --git a/site-alliance-boreale/public/label.html b/site-alliance-boreale/public/label.html new file mode 100644 index 0000000..37e7276 --- /dev/null +++ b/site-alliance-boreale/public/label.html @@ -0,0 +1,152 @@ + + + + + + Le Label — Alliance Boréale + + + + + + + + + + + + +
+
+

Le Label de confiance

+

Trois niveaux, une même promesse au client

+

Le Label certifie la garantie offerte au client — jamais la « valeur » de l'artisan ni ses méthodes internes. Les niveaux sont accumulatifs : chacun ajoute autant d'obligations que de privilèges. Noblesse oblige.

+
+ +
+
+
+

Niveau 1 · Socle

+

Souverain

+

Le socle de confiance que tous respectent.

+
    +
  • 100 % logiciel libre
  • +
  • Données hébergées au Québec
  • +
  • Durcissement appliqué (Set-OPS)
  • +
  • CGU claires — zéro revente, zéro pub
  • +
  • Sauvegardes en place
  • +
+
+ +
+

Niveau 2 · + Souverain

+

Résilient

+

Le client est libre, et couvert s'il tombe.

+
    +
  • Portabilité prouvée (pas seulement promise)
  • +
  • Reprise après sinistre testée
  • +
  • Supervision active
  • +
  • Résilience par géo-redondance ou par réciprocité de la constellation
  • +
  • ✦ « La constellation te reprend » si tu tombes
  • +
+
+ +
+

Niveau 3 · + Résilient

+

Exemplaire

+

Celui qui porte le commun.

+
    +
  • Contribution vérifiable au commun (code, doc, formation, mentorat)
  • +
  • Transparence renforcée envers les clients
  • +
  • Audit pair-à-pair approfondi
  • +
  • Aucune ingérence dans la régie interne : ni salaires, ni prix, ni méthodes
  • +
  • ✦ Voix accrue dans la gouvernance de l'Alliance
  • +
+
+
+
+ +
+
+

Un garde-fou social

+

Le mérite, pas le capital

+
+
+

La résilience peut être collective

+

Un petit artisan atteint « Résilient » par la réciprocité de la constellation, sans s'imposer une coûteuse géo-redondance. La force du réseau remplace la taille.

+
+
+

« Exemplaire » se gagne par la générosité

+

On y accède par la contribution et l'éthique, pas par le chiffre d'affaires. Un petit artisan généreux y accède ; un gros sans contribution, non.

+
+
+
+
+ +
+

Comment on certifie

+

Par la transparence, entre pairs

+
+
+

Audits pair-à-pair

+

À tous les niveaux, rigueur croissante. Pas de tiers externe payant : gratuit, réciproque, et ça tisse la constellation.

+
+
+

Preuves publiques

+

Le pair vérifie la conformité à un barème public via des preuves déclaratives — plan Set-OPS, CGU, pile libre — et une démo de reprise réciproque. On ne fouille pas tes méthodes.

+
+
+

La porte d'entrée

+

La transparence est l'entrée, pas une contrainte. Le profil totalement fermé, qui refuse toute vérification, s'auto-exclut — c'est tout.

+
+
+
+
+ +
+ + +
+ + + + diff --git a/site-alliance-boreale/public/vision.html b/site-alliance-boreale/public/vision.html new file mode 100644 index 0000000..6405c29 --- /dev/null +++ b/site-alliance-boreale/public/vision.html @@ -0,0 +1,119 @@ + + + + + + Vision — Alliance Boréale + + + + + + + + + + + + +
+
+

Notre vision

+

La gouvernance numérique souveraine comme bien commun

+

L'Alliance Boréale n'est pas qu'une infrastructure : c'est une façon différente de concevoir nos écosystèmes numériques. Une infrastructure que l'on comprend, que l'on possède, et dont on répond.

+
+ +
+

Quatre exigences

+

Ce qui nous tient ensemble

+
+
+

Responsabilité

+

Chaque acteur répond de ses services devant ses clients et devant ses pairs. La confiance se mérite et se documente.

+
+
+

Traçabilité

+

Ce qui est déployé est décrit, versionné, auditable. Pas de magie : des configurations explicites, comprises de bout en bout.

+
+
+

Portabilité

+

Le client n'est jamais prisonnier. Ses données et ses services peuvent migrer d'une étoile à l'autre : c'est prouvé, pas promis.

+
+
+

Sobriété

+

Des outils dimensionnés au besoin réel, durables, économes. La sobriété numérique est aussi une responsabilité environnementale.

+
+
+
+ +
+
+

Concrètement

+

Une infrastructure mutualisée

+

Seuls, les artisans réinventent chacun la même plomberie. Ensemble, nous l'opérons une fois, bien, en commun :

+
+

DNS interne

Résolution de noms souveraine pour tout l'écosystème.

+

Forges

Hébergement du code et de l'automatisation, chez nous.

+

Supervision

Métriques, journaux et alertes pour veiller sur la flotte.

+

Sauvegardes

Des copies testées : la reprise se vérifie, elle ne se suppose pas.

+
+
+
+ +
+

Le socle

+

Tout est libre

+

L'écosystème de l'Alliance Boréale se construit exclusivement avec du logiciel libre. Ce n'est pas un choix d'outillage : c'est la condition de la souveraineté. Du logiciel que l'on peut lire, auditer, corriger et transmettre — sans demander la permission de personne.

+ +
+
+ +
+ + +
+ + + + diff --git a/configure/configure.yaml b/tooling/configure/configure.yaml similarity index 100% rename from configure/configure.yaml rename to tooling/configure/configure.yaml diff --git a/configure/export.mjs b/tooling/configure/export.mjs similarity index 100% rename from configure/export.mjs rename to tooling/configure/export.mjs