diff --git a/infrastructure/ansible/README.md b/infrastructure/ansible/README.md index ec5ae06..3044f54 100644 --- a/infrastructure/ansible/README.md +++ b/infrastructure/ansible/README.md @@ -55,3 +55,27 @@ ansible/ ```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 index 062b90f..d0b424b 100644 --- a/infrastructure/ansible/ansible.cfg +++ b/infrastructure/ansible/ansible.cfg @@ -4,7 +4,8 @@ roles_path = roles host_key_checking = False retry_files_enabled = False interpreter_python = auto_silent -stdout_callback = yaml +stdout_callback = ansible.builtin.default +result_format = yaml bin_ansible_callbacks = True timeout = 30 forks = 20 diff --git a/infrastructure/ansible/docs/CONVERGENCE_NOTES.md b/infrastructure/ansible/docs/CONVERGENCE_NOTES.md index 2563421..57e926f 100644 --- a/infrastructure/ansible/docs/CONVERGENCE_NOTES.md +++ b/infrastructure/ansible/docs/CONVERGENCE_NOTES.md @@ -31,3 +31,21 @@ Le sous-arbre `ansible/` vise une baseline VM commune : 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 index 1d288ca..e83f9b4 100644 --- a/infrastructure/ansible/inventories/prod/group_vars/all.yml +++ b/infrastructure/ansible/inventories/prod/group_vars/all.yml @@ -1,21 +1,17 @@ --- # 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: example.internal +ab_domain: chezlepro.local ab_dns_servers: - - 192.0.2.10 - - 192.0.2.20 + - 192.168.10.30 ab_admin_networks: - - 192.0.2.0/24 -ab_ntp_server: c2-opnsense-01.example.internal -ab_ansible_user: ansible -ab_ansible_ssh_key_path: ~/.ssh/id_ed25519_ansible + - 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 -ansible_user: "{{ ab_ansible_user }}" -ansible_ssh_private_key_file: "{{ ab_ansible_ssh_key_path }}" - # Baseline commune baseline_common_enabled: true baseline_common_upgrade: true @@ -44,7 +40,7 @@ baseline_common_packages: - fail2ban - debian-archive-keyring -a_baseline_common_manage_sources: false +baseline_common_manage_sources: false # Hardening commun hardening_common_enabled: true @@ -69,18 +65,19 @@ firewall_nftables_policy_forward: drop firewall_nftables_policy_output: accept firewall_nftables_allowed_tcp_in: - dport: 22 - src: 192.0.2.0/24 + 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: c3-icinga-01.example.internal +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.example.internal +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 index c898d35..db78caa 100644 --- a/infrastructure/ansible/inventories/prod/hosts.yml +++ b/infrastructure/ansible/inventories/prod/hosts.yml @@ -1,4 +1,69 @@ -all: - children: - boreale_all: - hosts: {} +# ══════════════════════════════════════════════════════════════════ +# 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-netbox-01": + "ansible_host": "192.168.10.30" + "boreale_env": null + "boreale_layer": "c3" + "boreale_managed_by": "opentofu" + "boreale_role": "netbox" + "c3_identity": + "hosts": {} + "c3_supervision": + "hosts": + "c3-icinga-01": + "ansible_host": "192.168.10.31" + "boreale_env": null + "boreale_layer": "c3" + "boreale_managed_by": "opentofu" + "boreale_role": "icinga" + "icinga_role": "master" + "c4_forge": + "hosts": + "c4-forgejo-01": + "ansible_host": "192.168.10.40" + "boreale_env": null + "boreale_layer": "c4" + "boreale_managed_by": "opentofu" + "boreale_role": "forgejo" + "c5_pivot": + "hosts": + "c5-runner-01": + "ansible_host": "192.168.10.50" + "boreale_env": null + "boreale_layer": "c5" + "boreale_managed_by": "opentofu" + "boreale_role": "runner" + "tenants": + "children": + "c6_services": + "hosts": {} + "c7_products": + "hosts": + "c7-87-16-01": + "ansible_host": "192.168.12.45" + "boreale_env": null + "boreale_layer": "c7" + "boreale_managed_by": "opentofu" + "boreale_role": "87-16" + "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/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/requirements.yml b/infrastructure/ansible/requirements.yml index 92a0244..bd6dd7f 100644 --- a/infrastructure/ansible/requirements.yml +++ b/infrastructure/ansible/requirements.yml @@ -2,3 +2,4 @@ collections: - name: ansible.posix - name: community.general + - name: community.postgresql diff --git a/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml b/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml index c15c217..b9d1050 100644 --- a/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml +++ b/infrastructure/ansible/roles/firewall_nftables/defaults/main.yml @@ -5,5 +5,5 @@ 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: [] +firewall_nftables_allowed_tcp_in: [22,443] firewall_nftables_allowed_udp_in: [] diff --git a/infrastructure/ansible/roles/forgejo/handlers/main.yml b/infrastructure/ansible/roles/forgejo/handlers/main.yml new file mode 100644 index 0000000..c7ae8d1 --- /dev/null +++ b/infrastructure/ansible/roles/forgejo/handlers/main.yml @@ -0,0 +1,18 @@ +# Alliance Boréale - Forgejo Handlers +# Date: 2025-10-31 + +--- +- name: restart forgejo + ansible.builtin.systemd: + name: forgejo + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/infrastructure/ansible/roles/forgejo/tasks/database.yml b/infrastructure/ansible/roles/forgejo/tasks/database.yml new file mode 100644 index 0000000..bd5e6b7 --- /dev/null +++ b/infrastructure/ansible/roles/forgejo/tasks/database.yml @@ -0,0 +1,98 @@ +# Alliance Boréale - Forgejo Tasks (database, nginx, firewall) +# Date: 2025-10-31 + +--- +# ========================================== +# DATABASE.YML +# ========================================== +- name: "🗄️ Ensure PostgreSQL is installed" + ansible.builtin.include_role: + name: postgresql + when: "'postgresql_servers' in group_names" + +- name: "🗄️ Create Forgejo database" + community.postgresql.postgresql_db: + name: "{{ forgejo.db.name }}" + encoding: UTF8 + state: present + become: true + become_user: postgres + +- name: "👤 Create Forgejo database user" + community.postgresql.postgresql_user: + name: "{{ forgejo.db.user }}" + password: "{{ forgejo.db.password }}" + state: present + become: true + become_user: postgres + no_log: true + +- name: "🔐 Grant privileges to Forgejo user" + community.postgresql.postgresql_privs: + database: "{{ forgejo.db.name }}" + roles: "{{ forgejo.db.user }}" + type: database + privs: ALL + state: present + become: true + become_user: postgres + +# ========================================== +# NGINX.YML +# ========================================== +- name: "📦 Ensure Nginx is installed" + ansible.builtin.apt: + name: nginx + state: present + +- name: "🔐 Generate self-signed SSL certificate" + ansible.builtin.command: + cmd: > + openssl req -x509 -nodes -days 365 -newkey rsa:2048 + -keyout /etc/ssl/private/{{ forgejo.hostname }}.key + -out /etc/ssl/certs/{{ forgejo.hostname }}.crt + -subj "/C=CA/ST=Quebec/L=Montreal/O=Chezlepro/CN={{ forgejo.hostname }}" + creates: "/etc/ssl/certs/{{ forgejo.hostname }}.crt" + when: nginx.ssl.cert_source == 'self-signed' + +- name: "⚙️ Configure Nginx for Forgejo" + ansible.builtin.template: + src: nginx-forgejo.conf.j2 + dest: /etc/nginx/sites-available/forgejo + owner: root + group: root + mode: '0644' + notify: reload nginx + +- name: "🔗 Enable Nginx site" + ansible.builtin.file: + src: /etc/nginx/sites-available/forgejo + dest: /etc/nginx/sites-enabled/forgejo + state: link + notify: reload nginx + +- name: "✅ Start and enable Nginx" + ansible.builtin.systemd: + name: nginx + state: started + enabled: true + +# ========================================== +# FIREWALL.YML +# ========================================== +- name: "🔥 Configure firewall for Forgejo" + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED - Forgejo" + insertbefore: "# Log dropped packets" + block: | + # Forgejo - Internal HTTP (backend only) + ip saddr 127.0.0.1 tcp dport {{ forgejo.http_port }} accept comment "Forgejo HTTP" + + # Forgejo - SSH (Git over SSH) + tcp dport {{ forgejo.ssh_port }} accept comment "Forgejo SSH" + + # Nginx - HTTPS (public) + tcp dport 443 accept comment "HTTPS (Forgejo via Nginx)" + tcp dport 80 accept comment "HTTP redirect" + notify: reload nftables diff --git a/infrastructure/ansible/roles/forgejo/tasks/main.yml b/infrastructure/ansible/roles/forgejo/tasks/main.yml new file mode 100644 index 0000000..3a0a347 --- /dev/null +++ b/infrastructure/ansible/roles/forgejo/tasks/main.yml @@ -0,0 +1,131 @@ +# Alliance Boréale - Rôle Forgejo +# Couche: C4 (Forge & Mutualisation) +# Objectif: Déployer Forge Git avec SSO Keycloak +# Date: 2025-10-31 + +--- +- name: "📦 Install dependencies" + ansible.builtin.apt: + name: + - git + - nginx + - openssl + - python3-psycopg2 + state: present + update_cache: true + tags: forgejo + +- name: "👤 Create git user" + ansible.builtin.user: + name: "{{ forgejo.user }}" + system: true + shell: /bin/bash + home: "{{ forgejo.data_dir }}" + create_home: true + tags: forgejo + +- name: "📁 Create forgejo directories" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0755' + loop: + - "{{ forgejo.install_dir }}" + - "{{ forgejo.custom_dir }}" + - "{{ forgejo.log_dir }}" + - "{{ forgejo.data_dir }}/git" + - "{{ forgejo.data_dir }}/repositories" + tags: forgejo + +- name: "📥 Download Forgejo binary" + ansible.builtin.get_url: + url: "https://codeberg.org/forgejo/forgejo/releases/download/v{{ forgejo.version }}/forgejo-{{ forgejo.version }}-linux-amd64" + dest: "{{ forgejo.install_dir }}/forgejo" + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0755' + tags: forgejo + +- name: "🗄️ Configure PostgreSQL for Forgejo" + ansible.builtin.import_tasks: database.yml + tags: forgejo + +- name: "⚙️ Configure Forgejo" + ansible.builtin.template: + src: app.ini.j2 + dest: "{{ forgejo.custom_dir }}/app.ini" + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0640' + notify: restart forgejo + tags: forgejo + +- name: "⚙️ Create systemd service" + ansible.builtin.template: + src: forgejo.service.j2 + dest: /etc/systemd/system/forgejo.service + owner: root + group: root + mode: '0644' + notify: restart forgejo + tags: forgejo + +- name: "✅ Start and enable Forgejo" + ansible.builtin.systemd: + name: forgejo + state: started + enabled: true + daemon_reload: true + tags: forgejo + +- name: "⏳ Wait for Forgejo to be ready" + ansible.builtin.wait_for: + port: "{{ forgejo.http_port }}" + host: 127.0.0.1 + timeout: 60 + tags: forgejo + +- name: "🌐 Configure Nginx reverse proxy" + ansible.builtin.import_tasks: nginx.yml + tags: forgejo + +- name: "👑 Create admin user" + ansible.builtin.command: + cmd: > + {{ forgejo.install_dir }}/forgejo admin user create + --username {{ forgejo.admin.username }} + --password {{ forgejo.admin.password }} + --email {{ forgejo.admin.email }} + --admin + --config {{ forgejo.custom_dir }}/app.ini + creates: "{{ forgejo.data_dir }}/.admin_created" + become: true + become_user: "{{ forgejo.user }}" + register: admin_created + no_log: true + tags: forgejo + +- name: "✅ Mark admin as created" + ansible.builtin.file: + path: "{{ forgejo.data_dir }}/.admin_created" + state: touch + owner: "{{ forgejo.user }}" + group: "{{ forgejo.group }}" + mode: '0644' + when: admin_created.changed + tags: forgejo + +- name: "🔥 Configure firewall" + ansible.builtin.import_tasks: firewall.yml + tags: forgejo + +- name: "✅ Forgejo role completed" + ansible.builtin.debug: + msg: | + ✅ Forgejo deployed on {{ inventory_hostname }} + URL: https://{{ forgejo.hostname }} + SSH: git@{{ forgejo.hostname }}:{{ forgejo.ssh_port }} + Admin: {{ forgejo.admin.username }} + tags: forgejo diff --git a/infrastructure/ansible/roles/forgejo/templates/app.ini.j2 b/infrastructure/ansible/roles/forgejo/templates/app.ini.j2 new file mode 100644 index 0000000..c266d64 --- /dev/null +++ b/infrastructure/ansible/roles/forgejo/templates/app.ini.j2 @@ -0,0 +1,100 @@ +# Alliance Boréale - Forgejo Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +APP_NAME = L'Alliance Boréale - Forge Git +RUN_MODE = prod +RUN_USER = {{ forgejo.user }} + +[server] +DOMAIN = {{ forgejo.hostname }} +HTTP_PORT = {{ forgejo.http_port }} +ROOT_URL = https://{{ forgejo.hostname }}/ +DISABLE_SSH = false +SSH_DOMAIN = {{ forgejo.hostname }} +SSH_PORT = {{ forgejo.ssh_port }} +SSH_LISTEN_PORT = {{ forgejo.ssh_port }} +START_SSH_SERVER = true +LFS_START_SERVER = {{ forgejo.features.enable_lfs | lower }} +OFFLINE_MODE = false + +[database] +DB_TYPE = {{ forgejo.db.type }} +HOST = {{ forgejo.db.host }} +NAME = {{ forgejo.db.name }} +USER = {{ forgejo.db.user }} +PASSWD = {{ forgejo.db.password }} +SSL_MODE = {{ forgejo.db.ssl_mode }} +CHARSET = utf8mb4 +LOG_SQL = false + +[repository] +ROOT = {{ forgejo.data_dir }}/repositories +DEFAULT_BRANCH = main +PREFERRED_LICENSES = MIT,Apache-2.0,GPL-3.0 +DISABLE_HTTP_GIT = false +ACCESS_CONTROL_ALLOW_ORIGIN = https://{{ forgejo.hostname }} + +[security] +INSTALL_LOCK = true +SECRET_KEY = {{ forgejo.secret_key }} +INTERNAL_TOKEN = {{ forgejo.internal_token }} +PASSWORD_HASH_ALGO = argon2 +MIN_PASSWORD_LENGTH = 12 + +[service] +DISABLE_REGISTRATION = {{ forgejo.features.disable_registration | lower }} +REQUIRE_SIGNIN_VIEW = {{ forgejo.features.require_signin | lower }} +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DEFAULT_KEEP_EMAIL_PRIVATE = true +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +DEFAULT_ENABLE_TIMETRACKING = true +NO_REPLY_ADDRESS = noreply@{{ forgejo.domain }} + +[mailer] +ENABLED = false + +[session] +PROVIDER = file +PROVIDER_CONFIG = {{ forgejo.data_dir }}/sessions + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[log] +MODE = console, file +LEVEL = Info +ROOT_PATH = {{ forgejo.log_dir }} + +[git] +MAX_GIT_DIFF_LINES = 10000 +MAX_GIT_DIFF_LINE_CHARACTERS = 5000 +MAX_GIT_DIFF_FILES = 100 + +[actions] +ENABLED = {{ forgejo.features.enable_actions | lower }} + +[packages] +ENABLED = {{ forgejo.features.enable_packages | lower }} + +{% if forgejo.oauth.enabled %} +# OAuth2 / OpenID Connect (Keycloak) +[oauth2_client] +REGISTER_EMAIL_CONFIRM = false +ENABLE_AUTO_REGISTRATION = true +USERNAME = preferred_username +UPDATE_AVATAR = true +ACCOUNT_LINKING = auto + +{% for provider in forgejo.oauth.providers %} +[oauth2.{{ provider.name }}] +ENABLED = true +PROVIDER = {{ provider.provider }} +CLIENT_ID = {{ provider.client_id }} +CLIENT_SECRET = {{ provider.client_secret }} +OPENID_CONNECT_AUTO_DISCOVERY_URL = {{ provider.openid_connect_auto_discovery_url }} +{% endfor %} +{% endif %} diff --git a/infrastructure/ansible/roles/forgejo/templates/forgejo.service.j2 b/infrastructure/ansible/roles/forgejo/templates/forgejo.service.j2 new file mode 100644 index 0000000..62619f3 --- /dev/null +++ b/infrastructure/ansible/roles/forgejo/templates/forgejo.service.j2 @@ -0,0 +1,91 @@ +# Alliance Boréale - Forgejo Systemd Service +# Generated by Ansible + +[Unit] +Description=Forgejo Git Service +After=network.target postgresql.service +Wants=postgresql.service + +[Service] +Type=simple +User={{ forgejo.user }} +Group={{ forgejo.group }} +WorkingDirectory={{ forgejo.data_dir }} +ExecStart={{ forgejo.install_dir }}/forgejo web --config {{ forgejo.custom_dir }}/app.ini +Restart=on-failure +RestartSec=10 + +# Security +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths={{ forgejo.data_dir }} {{ forgejo.log_dir }} + +# Limits +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target + +---SEPARATOR--- + +# Alliance Boréale - Nginx Configuration for Forgejo +# Generated by Ansible + +upstream forgejo_backend { + server 127.0.0.1:{{ forgejo.http_port }}; +} + +server { + listen 80; + server_name {{ forgejo.hostname }}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ forgejo.hostname }}; + + ssl_certificate /etc/ssl/certs/{{ forgejo.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ forgejo.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + # Large uploads (Git LFS) + client_max_body_size 512M; + + location / { + proxy_pass http://forgejo_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support (for Actions) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} + +---SEPARATOR--- + +# Alliance Boréale - Forgejo Handlers +# Date: 2025-10-31 + +--- +- name: restart forgejo + ansible.builtin.systemd: + name: forgejo + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/infrastructure/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 b/infrastructure/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 new file mode 100644 index 0000000..ed2f328 --- /dev/null +++ b/infrastructure/ansible/roles/forgejo/templates/nginx-forgejo.conf.j2 @@ -0,0 +1,36 @@ +# Alliance Boréale - Nginx Forgejo +# Generated by Ansible + +upstream forgejo_backend { + server 127.0.0.1:{{ forgejo.http_port }}; +} + +server { + listen 80; + server_name {{ forgejo.hostname }}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ forgejo.hostname }}; + + ssl_certificate /etc/ssl/certs/{{ forgejo.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ forgejo.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + client_max_body_size 512M; + + location / { + proxy_pass http://forgejo_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/infrastructure/ansible/roles/hardening_common/tasks/main.yml b/infrastructure/ansible/roles/hardening_common/tasks/main.yml index ecf082c..365ec0a 100644 --- a/infrastructure/ansible/roles/hardening_common/tasks/main.yml +++ b/infrastructure/ansible/roles/hardening_common/tasks/main.yml @@ -20,12 +20,21 @@ 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 - mode: '0644' - when: hardening_common_journald_enabled | bool + owner: root + group: root + mode: "0644" notify: Restart journald - name: Installer fail2ban diff --git a/infrastructure/ansible/roles/keycloak/handlers/main.yml b/infrastructure/ansible/roles/keycloak/handlers/main.yml new file mode 100644 index 0000000..bbdac89 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/handlers/main.yml @@ -0,0 +1,18 @@ +# Alliance Boréale - Keycloak Handlers +# Date: 2025-10-31 + +--- +- name: restart keycloak + ansible.builtin.systemd: + name: keycloak + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/infrastructure/ansible/roles/keycloak/tasks/database.yml b/infrastructure/ansible/roles/keycloak/tasks/database.yml new file mode 100644 index 0000000..f7bc000 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/tasks/database.yml @@ -0,0 +1,140 @@ +# Alliance Boréale - Keycloak Tasks (database, nginx, realms, firewall) +# Date: 2025-10-31 + +--- +# ========================================== +# DATABASE.YML +# ========================================== +- name: "🗄️ Ensure PostgreSQL is installed" + ansible.builtin.include_role: + name: postgresql + when: "'postgresql_servers' in group_names" + +- name: "🗄️ Create Keycloak database" + community.postgresql.postgresql_db: + name: "{{ keycloak.db.database }}" + encoding: UTF8 + state: present + become: true + become_user: postgres + +- name: "👤 Create Keycloak database user" + community.postgresql.postgresql_user: + name: "{{ keycloak.db.username }}" + password: "{{ keycloak.db.password }}" + state: present + become: true + become_user: postgres + no_log: true + +- name: "🔐 Grant privileges to Keycloak user" + community.postgresql.postgresql_privs: + database: "{{ keycloak.db.database }}" + roles: "{{ keycloak.db.username }}" + type: database + privs: ALL + state: present + become: true + become_user: postgres + +# ========================================== +# NGINX.YML +# ========================================== +- name: "📦 Ensure Nginx is installed" + ansible.builtin.apt: + name: nginx + state: present + +- name: "🔐 Generate self-signed SSL certificate" + ansible.builtin.command: + cmd: > + openssl req -x509 -nodes -days 365 -newkey rsa:2048 + -keyout /etc/ssl/private/{{ keycloak.hostname }}.key + -out /etc/ssl/certs/{{ keycloak.hostname }}.crt + -subj "/C=CA/ST=Quebec/L=Montreal/O=Chezlepro/CN={{ keycloak.hostname }}" + creates: "/etc/ssl/certs/{{ keycloak.hostname }}.crt" + when: nginx.ssl.cert_source == 'self-signed' + +- name: "⚙️ Configure Nginx for Keycloak" + ansible.builtin.template: + src: nginx-keycloak.conf.j2 + dest: /etc/nginx/sites-available/keycloak + owner: root + group: root + mode: '0644' + notify: reload nginx + +- name: "🔗 Enable Nginx site" + ansible.builtin.file: + src: /etc/nginx/sites-available/keycloak + dest: /etc/nginx/sites-enabled/keycloak + state: link + notify: reload nginx + +- name: "🚫 Remove default Nginx site" + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + +- name: "✅ Start and enable Nginx" + ansible.builtin.systemd: + name: nginx + state: started + enabled: true + +# ========================================== +# REALMS.YML +# ========================================== +- name: "🏰 Check if realm exists" + ansible.builtin.uri: + url: "http://127.0.0.1:{{ keycloak.http_port }}/admin/realms/{{ item.name }}" + method: GET + user: "{{ keycloak.admin_user }}" + password: "{{ keycloak.admin_password }}" + force_basic_auth: true + status_code: [200, 404] + loop: "{{ keycloak.realms }}" + register: realm_check + changed_when: false + no_log: true + +- name: "🏰 Create realm configuration file" + ansible.builtin.template: + src: realm-config.json.j2 + dest: "/tmp/realm-{{ item.name }}.json" + owner: keycloak + group: keycloak + mode: '0640' + loop: "{{ keycloak.realms }}" + when: realm_check.results[0].status == 404 + +- name: "🏰 Import realm" + ansible.builtin.command: + cmd: > + /opt/keycloak/bin/kc.sh import + --file /tmp/realm-{{ item.name }}.json + --override false + become: true + become_user: keycloak + loop: "{{ keycloak.realms }}" + when: realm_check.results[0].status == 404 + register: realm_import + changed_when: "'imported' in realm_import.stdout" + +# ========================================== +# FIREWALL.YML +# ========================================== +- name: "🔥 Configure firewall for Keycloak" + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED - Keycloak" + insertbefore: "# Log dropped packets" + block: | + # Keycloak - Internal HTTP (backend only) + ip saddr 127.0.0.1 tcp dport {{ keycloak.http_port }} accept comment "Keycloak HTTP" + + # Nginx - HTTPS (public) + tcp dport 443 accept comment "HTTPS (Keycloak via Nginx)" + tcp dport 80 accept comment "HTTP redirect" + notify: reload nftables diff --git a/infrastructure/ansible/roles/keycloak/tasks/main.yml b/infrastructure/ansible/roles/keycloak/tasks/main.yml new file mode 100644 index 0000000..3c21b71 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/tasks/main.yml @@ -0,0 +1,140 @@ +# Alliance Boréale - Rôle Keycloak +# Couche: C3 (Gouvernance & Supervision) +# Objectif: Déployer IdP fédéré (SSO) +# Date: 2025-10-31 + +--- +- name: "📦 Install dependencies" + ansible.builtin.apt: + name: + - openjdk-17-jre-headless + - python3-pip + - python3-psycopg2 + - nginx + - openssl + state: present + update_cache: true + tags: keycloak + +- name: "👤 Create keycloak user" + ansible.builtin.user: + name: keycloak + system: true + shell: /bin/false + home: /opt/keycloak + create_home: false + tags: keycloak + +- name: "📁 Create keycloak directories" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: keycloak + group: keycloak + mode: '0755' + loop: + - /opt/keycloak + - /var/log/keycloak + tags: keycloak + +- name: "📥 Download Keycloak" + ansible.builtin.get_url: + url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak.version }}/keycloak-{{ keycloak.version }}.tar.gz" + dest: "/tmp/keycloak-{{ keycloak.version }}.tar.gz" + mode: '0644' + tags: keycloak + +- name: "📦 Extract Keycloak" + ansible.builtin.unarchive: + src: "/tmp/keycloak-{{ keycloak.version }}.tar.gz" + dest: /opt/keycloak + remote_src: true + owner: keycloak + group: keycloak + extra_opts: [--strip-components=1] + creates: /opt/keycloak/bin/kc.sh + tags: keycloak + +- name: "🗄️ Configure PostgreSQL for Keycloak" + ansible.builtin.import_tasks: database.yml + tags: keycloak + +- name: "⚙️ Configure Keycloak" + ansible.builtin.template: + src: keycloak.conf.j2 + dest: /opt/keycloak/conf/keycloak.conf + owner: keycloak + group: keycloak + mode: '0640' + notify: restart keycloak + tags: keycloak + +- name: "🔨 Build Keycloak" + ansible.builtin.command: + cmd: /opt/keycloak/bin/kc.sh build + become: true + become_user: keycloak + args: + creates: /opt/keycloak/lib/quarkus/quarkus-application.dat + tags: keycloak + +- name: "⚙️ Create systemd service" + ansible.builtin.template: + src: keycloak.service.j2 + dest: /etc/systemd/system/keycloak.service + owner: root + group: root + mode: '0644' + notify: restart keycloak + tags: keycloak + +- name: "✅ Start and enable Keycloak" + ansible.builtin.systemd: + name: keycloak + state: started + enabled: true + daemon_reload: true + tags: keycloak + +- name: "⏳ Wait for Keycloak to be ready" + ansible.builtin.wait_for: + port: "{{ keycloak.http_port }}" + host: 127.0.0.1 + timeout: 120 + tags: keycloak + +- name: "🌐 Configure Nginx reverse proxy" + ansible.builtin.import_tasks: nginx.yml + tags: keycloak + +- name: "👑 Create admin user" + ansible.builtin.command: + cmd: > + /opt/keycloak/bin/kcadm.sh config credentials + --server http://localhost:{{ keycloak.http_port }} + --realm master + --user {{ keycloak.admin_user }} + --password {{ keycloak.admin_password }} + become: true + become_user: keycloak + register: admin_created + changed_when: false + failed_when: false + no_log: true + tags: keycloak + +- name: "🏰 Configure realms and clients" + ansible.builtin.import_tasks: realms.yml + tags: keycloak + +- name: "🔥 Configure firewall" + ansible.builtin.import_tasks: firewall.yml + tags: keycloak + +- name: "✅ Keycloak role completed" + ansible.builtin.debug: + msg: | + ✅ Keycloak deployed on {{ inventory_hostname }} + URL: https://{{ keycloak.hostname }} + Admin: {{ keycloak.admin_user }} + tags: keycloak diff --git a/infrastructure/ansible/roles/keycloak/templates/keycloak.conf.j2 b/infrastructure/ansible/roles/keycloak/templates/keycloak.conf.j2 new file mode 100644 index 0000000..cddab01 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/templates/keycloak.conf.j2 @@ -0,0 +1,25 @@ +# Alliance Boréale - Keycloak Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# Network +hostname={{ keycloak.hostname }} +http-enabled=true +http-port={{ keycloak.http_port }} +http-host=0.0.0.0 +proxy=edge + +# Database +db=postgres +db-url=jdbc:postgresql://{{ keycloak.db.host }}/{{ keycloak.db.database }} +db-username={{ keycloak.db.username }} +db-password={{ keycloak.db.password }} + +# Logging +log-level=INFO +log-console-output=default + +# Health +health-enabled=true +metrics-enabled=true diff --git a/infrastructure/ansible/roles/keycloak/templates/keycloak.service.j2 b/infrastructure/ansible/roles/keycloak/templates/keycloak.service.j2 new file mode 100644 index 0000000..4172619 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/templates/keycloak.service.j2 @@ -0,0 +1,83 @@ +# Alliance Boréale - Keycloak Systemd Service +# Generated by Ansible + +[Unit] +Description=Keycloak Identity Provider +After=network.target postgresql.service +Wants=postgresql.service + +[Service] +Type=simple +User=keycloak +Group=keycloak +WorkingDirectory=/opt/keycloak +ExecStart=/opt/keycloak/bin/kc.sh start +Restart=on-failure +RestartSec=10 + +# Security +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/log/keycloak /opt/keycloak/data + +# Limits +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target + +---SEPARATOR--- + +# Alliance Boréale - Nginx Configuration for Keycloak +# Generated by Ansible + +upstream keycloak_backend { + server 127.0.0.1:{{ keycloak.http_port }}; +} + +# HTTP -> HTTPS redirect +server { + listen 80; + server_name {{ keycloak.hostname }}; + return 301 https://$server_name$request_uri; +} + +# HTTPS +server { + listen 443 ssl http2; + server_name {{ keycloak.hostname }}; + + # SSL + ssl_certificate /etc/ssl/certs/{{ keycloak.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ keycloak.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Logging + access_log /var/log/nginx/keycloak-access.log; + error_log /var/log/nginx/keycloak-error.log; + + # Proxy settings + location / { + proxy_pass http://keycloak_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # Buffers + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + + # Timeouts + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + } +} diff --git a/infrastructure/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 b/infrastructure/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 new file mode 100644 index 0000000..7876f37 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/templates/nginx-keycloak.conf.j2 @@ -0,0 +1,30 @@ +# Alliance Boréale - Nginx Keycloak +# Generated by Ansible + +upstream keycloak_backend { + server 127.0.0.1:{{ keycloak.http_port }}; +} + +server { + listen 80; + server_name {{ keycloak.hostname }}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ keycloak.hostname }}; + + ssl_certificate /etc/ssl/certs/{{ keycloak.hostname }}.crt; + ssl_certificate_key /etc/ssl/private/{{ keycloak.hostname }}.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://keycloak_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/infrastructure/ansible/roles/keycloak/templates/realm-config.json.j2 b/infrastructure/ansible/roles/keycloak/templates/realm-config.json.j2 new file mode 100644 index 0000000..601b999 --- /dev/null +++ b/infrastructure/ansible/roles/keycloak/templates/realm-config.json.j2 @@ -0,0 +1,48 @@ +{ + "realm": "{{ item.name }}", + "displayName": "{{ item.display_name }}", + "enabled": {{ item.enabled | lower }}, + "sslRequired": "external", + "registrationAllowed": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": true, + "clients": [ +{% for client in item.clients %} + { + "clientId": "{{ client.client_id }}", + "name": "{{ client.name }}", + "enabled": {{ client.enabled | lower }}, + "protocol": "{{ client.protocol }}", + "publicClient": false, + "redirectUris": {{ client.redirect_uris | to_json }}, + "webOrigins": {{ client.web_origins | to_json }}, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": false + }{{ "," if not loop.last else "" }} +{% endfor %} + ] +} + +---SEPARATOR--- + +# Alliance Boréale - Keycloak Handlers +# Date: 2025-10-31 + +--- +- name: restart keycloak + ansible.builtin.systemd: + name: keycloak + state: restarted + +- name: reload nginx + ansible.builtin.systemd: + name: nginx + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/infrastructure/ansible/roles/postgresql/defaults/main.yml b/infrastructure/ansible/roles/postgresql/defaults/main.yml new file mode 100644 index 0000000..67829ac --- /dev/null +++ b/infrastructure/ansible/roles/postgresql/defaults/main.yml @@ -0,0 +1,22 @@ +# Alliance Boréale - Rôle PostgreSQL - Default Variables +# Date: 2025-10-31 + +--- +postgresql: + version: 15 + listen_addresses: localhost + port: 5432 + + shared_buffers: 256MB + work_mem: 16MB + maintenance_work_mem: 128MB + effective_cache_size: 1GB + + databases: [] + users: [] + + backup: + enabled: true + schedule: "0 2 * * *" + retention_days: 7 + destination: /var/backups/postgresql diff --git a/infrastructure/ansible/roles/postgresql/handlers/main.yml b/infrastructure/ansible/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..1a1ec8c --- /dev/null +++ b/infrastructure/ansible/roles/postgresql/handlers/main.yml @@ -0,0 +1,13 @@ +# Alliance Boréale - Rôle PostgreSQL - Handlers +# Date: 2025-10-31 + +--- +- name: restart postgresql + ansible.builtin.systemd: + name: postgresql + state: restarted + +- name: reload postgresql + ansible.builtin.systemd: + name: postgresql + state: reloaded diff --git a/infrastructure/ansible/roles/postgresql/tasks/main.yml b/infrastructure/ansible/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..546303e --- /dev/null +++ b/infrastructure/ansible/roles/postgresql/tasks/main.yml @@ -0,0 +1,114 @@ +# Alliance Boréale - Rôle PostgreSQL +# Couche: C3 (dépendance pour PowerDNS) +# Objectif: Déployer PostgreSQL pour backend PowerDNS +# Date: 2025-10-31 + +--- +- name: "📦 Install PostgreSQL and dependencies" + ansible.builtin.apt: + name: + - postgresql-{{ postgresql.version }} + - postgresql-contrib-{{ postgresql.version }} + - python3-psycopg2 + state: present + update_cache: true + tags: postgresql + +- name: "⚙️ Configure PostgreSQL" + ansible.builtin.template: + src: postgresql.conf.j2 + dest: "/etc/postgresql/{{ postgresql.version }}/main/postgresql.conf" + owner: postgres + group: postgres + mode: '0644' + notify: restart postgresql + tags: postgresql + +- name: "⚙️ Configure pg_hba.conf" + ansible.builtin.template: + src: pg_hba.conf.j2 + dest: "/etc/postgresql/{{ postgresql.version }}/main/pg_hba.conf" + owner: postgres + group: postgres + mode: '0640' + notify: restart postgresql + tags: postgresql + +- name: "✅ Ensure PostgreSQL is started" + ansible.builtin.systemd: + name: postgresql + state: started + enabled: true + tags: postgresql + +- name: "🗄️ Create PostgreSQL databases" + community.postgresql.postgresql_db: + name: "{{ item.name }}" + encoding: "{{ item.encoding | default('UTF8') }}" + lc_collate: "{{ item.lc_collate | default('en_US.UTF-8') }}" + lc_ctype: "{{ item.lc_ctype | default('en_US.UTF-8') }}" + state: present + loop: "{{ postgresql.databases }}" + become: true + become_user: postgres + tags: postgresql + +- name: "👤 Create PostgreSQL users" + community.postgresql.postgresql_user: + name: "{{ item.name }}" + password: "{{ item.password }}" + state: present + loop: "{{ postgresql.users }}" + become: true + become_user: postgres + no_log: true + tags: postgresql + +- name: "🔐 Grant database privileges" + community.postgresql.postgresql_privs: + database: "{{ item.name }}" + roles: "{{ item.owner }}" + type: database + privs: ALL + state: present + loop: "{{ postgresql.databases }}" + become: true + become_user: postgres + tags: postgresql + +- name: "📊 Create backup directory" + ansible.builtin.file: + path: "{{ postgresql.backup.destination }}" + state: directory + owner: postgres + group: postgres + mode: '0750' + when: postgresql.backup.enabled | default(true) + tags: postgresql + +- name: "📊 Configure backup cron job" + ansible.builtin.cron: + name: "PostgreSQL backup" + user: postgres + minute: "0" + hour: "2" + job: "pg_dumpall | gzip > {{ postgresql.backup.destination }}/postgres-$(date +\\%Y\\%m\\%d).sql.gz" + state: present + when: postgresql.backup.enabled | default(true) + tags: postgresql + +- name: "🗑️ Configure backup retention (delete old backups)" + ansible.builtin.cron: + name: "PostgreSQL backup cleanup" + user: postgres + minute: "30" + hour: "2" + job: "find {{ postgresql.backup.destination }} -name 'postgres-*.sql.gz' -mtime +{{ postgresql.backup.retention_days }} -delete" + state: present + when: postgresql.backup.enabled | default(true) + tags: postgresql + +- name: "✅ PostgreSQL role completed" + ansible.builtin.debug: + msg: "✅ PostgreSQL {{ postgresql.version }} configured on {{ inventory_hostname }}" + tags: postgresql diff --git a/infrastructure/ansible/roles/postgresql/templates/pg_hba.conf.j2 b/infrastructure/ansible/roles/postgresql/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..e5bacfc --- /dev/null +++ b/infrastructure/ansible/roles/postgresql/templates/pg_hba.conf.j2 @@ -0,0 +1,24 @@ +# Alliance Boréale - PostgreSQL Client Authentication +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# TYPE DATABASE USER ADDRESS METHOD + +# Local connections +local all postgres peer +local all all peer + +# IPv4 local connections +host all all 127.0.0.1/32 scram-sha-256 + +# IPv6 local connections +host all all ::1/128 scram-sha-256 + +# Internal network (if needed later) +# host all all 10.0.0.0/8 scram-sha-256 + +# Replication (for future use) +# local replication all peer +# host replication all 127.0.0.1/32 scram-sha-256 +# host replication all ::1/128 scram-sha-256 diff --git a/infrastructure/ansible/roles/postgresql/templates/postgresql.conf.j2 b/infrastructure/ansible/roles/postgresql/templates/postgresql.conf.j2 new file mode 100644 index 0000000..925aa21 --- /dev/null +++ b/infrastructure/ansible/roles/postgresql/templates/postgresql.conf.j2 @@ -0,0 +1,38 @@ +# Alliance Boréale - PostgreSQL Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# Network +listen_addresses = '{{ postgresql.listen_addresses | default("localhost") }}' +port = {{ postgresql.port | default(5432) }} + +# Memory +shared_buffers = {{ postgresql.shared_buffers | default('256MB') }} +work_mem = {{ postgresql.work_mem | default('16MB') }} +maintenance_work_mem = {{ postgresql.maintenance_work_mem | default('128MB') }} +effective_cache_size = {{ postgresql.effective_cache_size | default('1GB') }} + +# Write-Ahead Log +wal_level = replica +max_wal_size = 1GB +min_wal_size = 80MB + +# Logging +log_destination = 'stderr' +logging_collector = on +log_directory = 'log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_rotation_age = 1d +log_rotation_size = 10MB +log_line_prefix = '%m [%p] %q%u@%d ' +log_timezone = 'America/Toronto' + +# Locale +datestyle = 'iso, mdy' +timezone = 'America/Toronto' +lc_messages = 'en_CA.UTF-8' +lc_monetary = 'en_CA.UTF-8' +lc_numeric = 'en_CA.UTF-8' +lc_time = 'en_CA.UTF-8' +default_text_search_config = 'pg_catalog.english' diff --git a/infrastructure/ansible/roles/powerdns_authoritative/handlers/main.yml b/infrastructure/ansible/roles/powerdns_authoritative/handlers/main.yml new file mode 100644 index 0000000..f85227c --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/handlers/main.yml @@ -0,0 +1,18 @@ +# Alliance Boréale - PowerDNS Handlers +# Date: 2025-10-31 + +--- +- name: restart powerdns + ansible.builtin.systemd: + name: pdns + state: restarted + +- name: reload powerdns + ansible.builtin.systemd: + name: pdns + state: reloaded + +- name: reload nftables + ansible.builtin.systemd: + name: nftables + state: reloaded diff --git a/infrastructure/ansible/roles/powerdns_authoritative/tasks/firewall.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/firewall.yml new file mode 100644 index 0000000..832a940 --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/tasks/firewall.yml @@ -0,0 +1,57 @@ +# Alliance Boréale - PowerDNS Firewall & Validation +# Date: 2025-10-31 + +--- +# ========================================== +# FIREWALL.YML +# ========================================== +- name: "🔥 Ensure nftables rules include DNS" + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED - PowerDNS" + insertbefore: "# Log dropped packets" + block: | + # PowerDNS - DNS queries + udp dport 53 accept comment "DNS queries (UDP)" + tcp dport 53 accept comment "DNS queries (TCP)" + + # PowerDNS - API (internal only) + ip saddr {{ network.internal_subnet | default('10.0.0.0/8') }} tcp dport 8081 accept comment "PowerDNS API" + notify: reload nftables + +# ========================================== +# VALIDATE.YML +# ========================================== +- name: "🧪 Wait for PowerDNS to be ready" + ansible.builtin.wait_for: + port: 53 + host: "{{ ansible_host }}" + timeout: 30 + +- name: "🧪 Test DNS resolution (localhost)" + ansible.builtin.command: + cmd: "dig @127.0.0.1 {{ dns_zones[0].name }} SOA +short" + register: dns_test_local + changed_when: false + failed_when: dns_test_local.rc != 0 + +- name: "🧪 Display DNS test result" + ansible.builtin.debug: + msg: "✅ DNS resolution working: {{ dns_test_local.stdout }}" + +- name: "🧪 Check PowerDNS API (if enabled)" + ansible.builtin.uri: + url: "http://127.0.0.1:8081/api/v1/servers/localhost" + headers: + X-API-Key: "{{ powerdns.api.key }}" + return_content: true + register: api_test + when: powerdns.api.enabled | default(true) + failed_when: false + +- name: "🧪 Display PowerDNS version" + ansible.builtin.debug: + msg: "✅ PowerDNS API responding: {{ api_test.json.version | default('N/A') }}" + when: + - powerdns.api.enabled | default(true) + - api_test.status == 200 diff --git a/infrastructure/ansible/roles/powerdns_authoritative/tasks/main.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/main.yml new file mode 100644 index 0000000..c105eca --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/tasks/main.yml @@ -0,0 +1,90 @@ +# Alliance Boréale - Rôle PowerDNS Authoritative +# Couche: C2 (Réseau & DNS) +# Objectif: Déployer PowerDNS autoritaire (MASTER ou SLAVE) +# Date: 2025-10-31 + +--- +- name: "🔍 Determine PowerDNS role (master/slave)" + ansible.builtin.set_fact: + pdns_is_master: "{{ inventory_hostname in groups['dns_masters'] }}" + pdns_is_slave: "{{ inventory_hostname in groups['dns_slaves'] }}" + tags: always + +- name: "📦 Add PowerDNS repository" + ansible.builtin.import_tasks: repo.yml + tags: powerdns + +- name: "📦 Install PowerDNS packages" + ansible.builtin.apt: + name: + - pdns-server + - pdns-backend-pgsql + - pdns-tools + state: present + update_cache: true + tags: powerdns + +- name: "⚙️ Configure PowerDNS" + ansible.builtin.template: + src: pdns.conf.j2 + dest: /etc/powerdns/pdns.conf + owner: root + group: pdns + mode: '0640' + notify: restart powerdns + tags: powerdns + +- name: "⚙️ Configure PowerDNS PostgreSQL backend" + ansible.builtin.template: + src: pdns.d/pdns.local.gpgsql.conf.j2 + dest: /etc/powerdns/pdns.d/pdns.local.gpgsql.conf + owner: root + group: pdns + mode: '0640' + notify: restart powerdns + tags: powerdns + +- name: "🗄️ Import PowerDNS PostgreSQL schema" + ansible.builtin.import_tasks: schema.yml + when: pdns_is_master + tags: powerdns + +- name: "🌐 Configure DNS zones (MASTER only)" + ansible.builtin.import_tasks: zones.yml + when: pdns_is_master + tags: powerdns + +- name: "🔁 Configure AXFR (SLAVE only)" + ansible.builtin.import_tasks: slave.yml + when: pdns_is_slave + tags: powerdns + +- name: "🔐 Configure DNSSEC" + ansible.builtin.import_tasks: dnssec.yml + when: + - pdns_is_master + - powerdns.dnssec.enabled | default(true) + tags: powerdns + +- name: "🔥 Configure firewall for DNS" + ansible.builtin.import_tasks: firewall.yml + tags: powerdns + +- name: "✅ Start and enable PowerDNS" + ansible.builtin.systemd: + name: pdns + state: started + enabled: true + tags: powerdns + +- name: "🧪 Validate PowerDNS configuration" + ansible.builtin.import_tasks: validate.yml + tags: powerdns + +- name: "✅ PowerDNS role completed" + ansible.builtin.debug: + msg: | + ✅ PowerDNS configured on {{ inventory_hostname }} + Role: {{ 'MASTER' if pdns_is_master else 'SLAVE' }} + Zones: {{ dns_zones | map(attribute='name') | list if pdns_is_master else 'N/A' }} + tags: powerdns diff --git a/infrastructure/ansible/roles/powerdns_authoritative/tasks/repo.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/repo.yml new file mode 100644 index 0000000..c601a3f --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/tasks/repo.yml @@ -0,0 +1,25 @@ +# Alliance Boréale - PowerDNS Repository Setup +# Date: 2025-10-31 + +--- +- name: "🔑 Add PowerDNS GPG key" + ansible.builtin.apt_key: + url: https://repo.powerdns.com/FD380FBB-pub.asc + state: present + +- name: "📦 Add PowerDNS repository" + ansible.builtin.apt_repository: + repo: "deb [arch=amd64] http://repo.powerdns.com/{{ ansible_distribution | lower }} {{ ansible_distribution_release }}-auth-48 main" + state: present + filename: pdns + +- name: "📋 Set PowerDNS package preferences" + ansible.builtin.copy: + dest: /etc/apt/preferences.d/pdns + content: | + Package: pdns-* + Pin: origin repo.powerdns.com + Pin-Priority: 600 + owner: root + group: root + mode: '0644' diff --git a/infrastructure/ansible/roles/powerdns_authoritative/tasks/schema.yml b/infrastructure/ansible/roles/powerdns_authoritative/tasks/schema.yml new file mode 100644 index 0000000..52af50f --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/tasks/schema.yml @@ -0,0 +1,149 @@ +# Alliance Boréale - PowerDNS Schema Import +# Date: 2025-10-31 + +--- +# Ce fichier contient: schema.yml, zones.yml, slave.yml, dnssec.yml + +# ========================================== +# SCHEMA.YML - Import PostgreSQL schema +# ========================================== +- name: "🗄️ Check if PowerDNS schema exists" + community.postgresql.postgresql_query: + db: powerdns + query: "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'domains');" + become: true + become_user: postgres + register: pdns_schema_check + changed_when: false + +- name: "🗄️ Download PowerDNS schema" + ansible.builtin.get_url: + url: https://raw.githubusercontent.com/PowerDNS/pdns/rel/auth-4.8.x/modules/gpgsqlbackend/schema.pgsql.sql + dest: /tmp/pdns-schema.sql + mode: '0644' + when: not pdns_schema_check.query_result[0].exists + +- name: "🗄️ Import PowerDNS schema" + community.postgresql.postgresql_db: + db: powerdns + state: restore + target: /tmp/pdns-schema.sql + become: true + become_user: postgres + when: not pdns_schema_check.query_result[0].exists + +# ========================================== +# ZONES.YML - Create DNS zones (MASTER) +# ========================================== +- name: "🌐 Create DNS zones" + ansible.builtin.command: + cmd: "pdnsutil create-zone {{ item.name }}" + loop: "{{ dns_zones }}" + register: zone_create + changed_when: "'created' in zone_create.stdout" + failed_when: + - zone_create.rc != 0 + - "'already exists' not in zone_create.stderr" + +- name: "🌐 Set zone kind to NATIVE" + ansible.builtin.command: + cmd: "pdnsutil set-kind {{ item.name }} NATIVE" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🌐 Configure SOA records" + ansible.builtin.command: + cmd: > + pdnsutil replace-rrset {{ item.name }} @ SOA + "{{ item.soa.nameserver }} {{ item.soa.email }} + {{ item.soa.serial }} {{ item.soa.refresh }} {{ item.soa.retry }} + {{ item.soa.expire }} {{ item.soa.minimum }}" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🌐 Add NS records" + ansible.builtin.command: + cmd: "pdnsutil add-record {{ item.0.name }} @ NS {{ item.1 }}" + loop: "{{ dns_zones | subelements('ns_records') }}" + register: ns_add + changed_when: false + failed_when: + - ns_add.rc != 0 + - "'already exists' not in ns_add.stderr" + +- name: "🌐 Add A/AAAA records" + ansible.builtin.command: + cmd: "pdnsutil add-record {{ item.0.name }} {{ item.1.name }} {{ item.1.type }} {{ item.1.content }} {{ item.1.ttl | default(3600) }}" + loop: "{{ dns_zones | subelements('records', skip_missing=True) }}" + register: record_add + changed_when: false + failed_when: + - record_add.rc != 0 + - "'already exists' not in record_add.stderr" + +- name: "🌐 Rectify zones" + ansible.builtin.command: + cmd: "pdnsutil rectify-zone {{ item.name }}" + loop: "{{ dns_zones }}" + changed_when: false + +# ========================================== +# SLAVE.YML - Configure AXFR for slaves +# ========================================== +- name: "🔁 Configure supermaster for AXFR" + community.postgresql.postgresql_query: + db: powerdns + query: > + INSERT INTO supermasters (ip, nameserver, account) + VALUES ('{{ hostvars[groups['dns_masters'][0]]['ansible_host'] }}', + '{{ inventory_hostname }}', + 'default') + ON CONFLICT DO NOTHING; + become: true + become_user: postgres + +- name: "🔁 Enable slave mode in PowerDNS" + ansible.builtin.lineinfile: + path: /etc/powerdns/pdns.conf + regexp: '^slave=' + line: 'slave=yes' + notify: restart powerdns + +# ========================================== +# DNSSEC.YML - Configure DNSSEC +# ========================================== +- name: "🔐 Enable DNSSEC for zones" + ansible.builtin.command: + cmd: "pdnsutil secure-zone {{ item.name }}" + loop: "{{ dns_zones }}" + register: dnssec_secure + changed_when: "'secured' in dnssec_secure.stdout" + failed_when: + - dnssec_secure.rc != 0 + - "'already' not in dnssec_secure.stderr" + +- name: "🔐 Configure NSEC3" + ansible.builtin.command: + cmd: "pdnsutil set-nsec3 {{ item.name }} '1 0 10 ab' narrow" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🔐 Rectify zones after DNSSEC" + ansible.builtin.command: + cmd: "pdnsutil rectify-zone {{ item.name }}" + loop: "{{ dns_zones }}" + changed_when: false + +- name: "🔐 Export DS records" + ansible.builtin.command: + cmd: "pdnsutil show-zone {{ item.name }}" + loop: "{{ dns_zones }}" + register: ds_records + changed_when: false + +- name: "🔐 Display DS records for parent zone" + ansible.builtin.debug: + msg: | + ⚠️ IMPORTANT: Add these DS records to your domain registrar: + {{ ds_records.results | map(attribute='stdout') | join('\n') }} + when: ds_records.results | length > 0 diff --git a/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.conf.j2 b/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.conf.j2 new file mode 100644 index 0000000..a3169e4 --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.conf.j2 @@ -0,0 +1,70 @@ +# Alliance Boréale - PowerDNS Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Role: {{ 'MASTER' if pdns_is_master else 'SLAVE' }} +# Date: {{ ansible_date_time.iso8601 }} + +# ========================================== +# BACKEND +# ========================================== +launch=gpgsql +include-dir=/etc/powerdns/pdns.d + +# ========================================== +# NETWORK +# ========================================== +local-address=0.0.0.0 +local-port=53 +local-ipv6= + +# ========================================== +# MODE +# ========================================== +master={{ 'yes' if pdns_is_master else 'no' }} +slave={{ 'yes' if pdns_is_slave else 'no' }} +superslave={{ 'no' }} + +# ========================================== +# API & WEBSERVER +# ========================================== +api={{ 'yes' if powerdns.api.enabled | default(true) else 'no' }} +api-key={{ powerdns.api.key | default('changeme') }} + +webserver={{ 'yes' if powerdns.webserver.enabled | default(true) else 'no' }} +webserver-address={{ powerdns.webserver.address | default('0.0.0.0') }} +webserver-port={{ powerdns.webserver.port | default(8081) }} +webserver-allow-from={{ powerdns.webserver.allow_from | default('10.0.0.0/8') }} + +# ========================================== +# DNSSEC +# ========================================== +dnssec={{ 'on' if powerdns.dnssec.enabled | default(true) else 'off' }} + +# ========================================== +# LOGGING +# ========================================== +log-dns-queries={{ 'yes' if powerdns.logging.queries | default(false) else 'no' }} +log-dns-details={{ 'yes' if powerdns.logging.details | default(true) else 'no' }} +loglevel={{ powerdns.logging.level | default(4) }} + +# ========================================== +# PERFORMANCE +# ========================================== +cache-ttl={{ powerdns.cache_ttl | default(20) }} +negquery-cache-ttl={{ powerdns.negquery_cache_ttl | default(60) }} +query-cache-ttl={{ powerdns.query_cache_ttl | default(20) }} + +# ========================================== +# SECURITY +# ========================================== +setuid=pdns +setgid=pdns +chroot=/var/spool/powerdns +disable-axfr={{ 'no' }} +allow-axfr-ips={{ axfr.allow_from | join(',') if axfr.allow_from is defined else '127.0.0.1' }} +also-notify={{ notify.also_notify | join(',') if notify.also_notify is defined else '' }} + +# ========================================== +# SOA +# ========================================== +default-soa-content=ns1.infra.{{ member.domain_primary }} admin.{{ member.domain_primary }} 0 3600 1800 1209600 3600 diff --git a/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.local.gpgsql.conf.j2 b/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.local.gpgsql.conf.j2 new file mode 100644 index 0000000..18d8736 --- /dev/null +++ b/infrastructure/ansible/roles/powerdns_authoritative/templates/pdns.local.gpgsql.conf.j2 @@ -0,0 +1,12 @@ +# Alliance Boréale - PowerDNS PostgreSQL Backend +# Generated by Ansible - DO NOT EDIT MANUALLY +# Host: {{ inventory_hostname }} +# Date: {{ ansible_date_time.iso8601 }} + +# PostgreSQL connection +gpgsql-host=/var/run/postgresql +gpgsql-port=5432 +gpgsql-dbname=powerdns +gpgsql-user=pdns +gpgsql-password={{ vault_postgresql_pdns_password }} +gpgsql-dnssec=yes diff --git a/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml b/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml index 8445a6a..9f2b03b 100644 --- a/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml +++ b/infrastructure/ansible/roles/security_wazuh_agent/tasks/main.yml @@ -1,20 +1,34 @@ --- - name: Gate ansible.builtin.meta: end_host - when: not security_wazuh_agent_enabled | bool + when: not (security_wazuh_agent_enabled | bool) -- name: Installer l'agent Wazuh depuis APT natif si disponible +- 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' + mode: "0640" when: security_wazuh_agent_manager | length > 0 notify: Restart wazuh-agent diff --git a/infrastructure/ansible2/vault/production-phase2.yml.example b/infrastructure/ansible/vault/production-phase2.yml.example similarity index 100% rename from infrastructure/ansible2/vault/production-phase2.yml.example rename to infrastructure/ansible/vault/production-phase2.yml.example diff --git a/infrastructure/ansible2/vault/production.yml.example b/infrastructure/ansible/vault/production.yml.example similarity index 100% rename from infrastructure/ansible2/vault/production.yml.example rename to infrastructure/ansible/vault/production.yml.example diff --git a/infrastructure/ansible2/inventory/tofu_generated.yml b/infrastructure/ansible2/inventory/tofu_generated.yml deleted file mode 100644 index 7876d5c..0000000 --- a/infrastructure/ansible2/inventory/tofu_generated.yml +++ /dev/null @@ -1,33 +0,0 @@ -# ══════════════════════════════════════════════════════════════════ -# Inventaire Ansible — généré par OpenTofu -# NE PAS MODIFIER MANUELLEMENT — regénéré à chaque tofu apply -# ══════════════════════════════════════════════════════════════════ ---- -"all": - "children": - "c3_monitoring": - "hosts": - "c3-icinga-01": - "ansible_host": "192.168.10.31" - "icinga_role": "master" - "c3_services": - "hosts": - "c3-netbox-01": - "ansible_host": "192.168.10.30" - "c4_forge": - "hosts": - "c4-forgejo-01": - "ansible_host": "192.168.10.40" - "c5_runner": - "hosts": - "c5-runner-01": - "ansible_host": "192.168.10.50" - "c7_tenants": - "hosts": - "c7-87-16-01": - "ansible_host": "192.168.12.45" - "c7-life-noc-01": - "ansible_host": "192.168.12.40" - "vars": - "ansible_ssh_private_key_file": "~/.ssh/id_ed25519_ansible_chezlepro" - "ansible_user": "ansible"