testé jusqu'au hardening
This commit is contained in:
parent
ef98fd8a3f
commit
0ef19077f2
48 changed files with 1933 additions and 59 deletions
|
|
@ -55,3 +55,27 @@ ansible/
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbooks/site.yml
|
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`.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ roles_path = roles
|
||||||
host_key_checking = False
|
host_key_checking = False
|
||||||
retry_files_enabled = False
|
retry_files_enabled = False
|
||||||
interpreter_python = auto_silent
|
interpreter_python = auto_silent
|
||||||
stdout_callback = yaml
|
stdout_callback = ansible.builtin.default
|
||||||
|
result_format = yaml
|
||||||
bin_ansible_callbacks = True
|
bin_ansible_callbacks = True
|
||||||
timeout = 30
|
timeout = 30
|
||||||
forks = 20
|
forks = 20
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,21 @@ Le sous-arbre `ansible/` vise une baseline VM commune :
|
||||||
3. fermée par défaut ;
|
3. fermée par défaut ;
|
||||||
4. compatible avec l'inventaire généré par OpenTofu ;
|
4. compatible avec l'inventaire généré par OpenTofu ;
|
||||||
5. prête à recevoir ensuite les rôles applicatifs C2/C3/C4/C5.
|
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`.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
---
|
---
|
||||||
# Paramètres globaux de la baseline commune Alliance Boréale
|
# 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:
|
ab_dns_servers:
|
||||||
- 192.0.2.10
|
- 192.168.10.30
|
||||||
- 192.0.2.20
|
|
||||||
ab_admin_networks:
|
ab_admin_networks:
|
||||||
- 192.0.2.0/24
|
- 192.168.10.0/24
|
||||||
ab_ntp_server: c2-opnsense-01.example.internal
|
- 192.168.12.0/24
|
||||||
ab_ansible_user: ansible
|
ab_ntp_server: 192.168.10.1
|
||||||
ab_ansible_ssh_key_path: ~/.ssh/id_ed25519_ansible
|
|
||||||
ab_timezone: America/Montreal
|
ab_timezone: America/Montreal
|
||||||
ab_enable_wazuh_agent: false
|
ab_enable_wazuh_agent: false
|
||||||
|
|
||||||
ansible_user: "{{ ab_ansible_user }}"
|
|
||||||
ansible_ssh_private_key_file: "{{ ab_ansible_ssh_key_path }}"
|
|
||||||
|
|
||||||
# Baseline commune
|
# Baseline commune
|
||||||
baseline_common_enabled: true
|
baseline_common_enabled: true
|
||||||
baseline_common_upgrade: true
|
baseline_common_upgrade: true
|
||||||
|
|
@ -44,7 +40,7 @@ baseline_common_packages:
|
||||||
- fail2ban
|
- fail2ban
|
||||||
- debian-archive-keyring
|
- debian-archive-keyring
|
||||||
|
|
||||||
a_baseline_common_manage_sources: false
|
baseline_common_manage_sources: false
|
||||||
|
|
||||||
# Hardening commun
|
# Hardening commun
|
||||||
hardening_common_enabled: true
|
hardening_common_enabled: true
|
||||||
|
|
@ -69,18 +65,19 @@ firewall_nftables_policy_forward: drop
|
||||||
firewall_nftables_policy_output: accept
|
firewall_nftables_policy_output: accept
|
||||||
firewall_nftables_allowed_tcp_in:
|
firewall_nftables_allowed_tcp_in:
|
||||||
- dport: 22
|
- 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_allowed_udp_in: []
|
||||||
firewall_nftables_allow_icmp: true
|
firewall_nftables_allow_icmp: true
|
||||||
|
|
||||||
# Monitoring commun
|
# Monitoring commun
|
||||||
monitoring_icinga_agent_enabled: true
|
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_parent_zone: master
|
||||||
monitoring_icinga_agent_zone: "{{ inventory_hostname }}"
|
monitoring_icinga_agent_zone: "{{ inventory_hostname }}"
|
||||||
|
|
||||||
# Wazuh optionnel
|
# Wazuh optionnel
|
||||||
security_wazuh_agent_enabled: "{{ ab_enable_wazuh_agent }}"
|
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: ""
|
security_wazuh_agent_registration_password: ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
monitoring_icinga_agent_enabled: false
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
ab_enable_wazuh_agent: true
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
# Override example for product-facing tenant nodes
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
@ -1,4 +1,69 @@
|
||||||
all:
|
# ══════════════════════════════════════════════════════════════════
|
||||||
children:
|
# Inventaire Ansible — généré par OpenTofu
|
||||||
boreale_all:
|
# NE PAS MODIFIER MANUELLEMENT — regénéré à chaque tofu apply
|
||||||
hosts: {}
|
# ══════════════════════════════════════════════════════════════════
|
||||||
|
---
|
||||||
|
"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"
|
||||||
|
|
|
||||||
11
infrastructure/ansible/playbooks/services/phase1_dns.yml
Normal file
11
infrastructure/ansible/playbooks/services/phase1_dns.yml
Normal file
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
10
infrastructure/ansible/playbooks/services/phase3_runner.yml
Normal file
10
infrastructure/ansible/playbooks/services/phase3_runner.yml
Normal file
|
|
@ -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
|
||||||
10
infrastructure/ansible/playbooks/services/phase7_tenants.yml
Normal file
10
infrastructure/ansible/playbooks/services/phase7_tenants.yml
Normal file
|
|
@ -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
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
collections:
|
collections:
|
||||||
- name: ansible.posix
|
- name: ansible.posix
|
||||||
- name: community.general
|
- name: community.general
|
||||||
|
- name: community.postgresql
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ firewall_nftables_allow_icmp: true
|
||||||
firewall_nftables_policy_input: drop
|
firewall_nftables_policy_input: drop
|
||||||
firewall_nftables_policy_forward: drop
|
firewall_nftables_policy_forward: drop
|
||||||
firewall_nftables_policy_output: accept
|
firewall_nftables_policy_output: accept
|
||||||
firewall_nftables_allowed_tcp_in: []
|
firewall_nftables_allowed_tcp_in: [22,443]
|
||||||
firewall_nftables_allowed_udp_in: []
|
firewall_nftables_allowed_udp_in: []
|
||||||
|
|
|
||||||
18
infrastructure/ansible/roles/forgejo/handlers/main.yml
Normal file
18
infrastructure/ansible/roles/forgejo/handlers/main.yml
Normal file
|
|
@ -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
|
||||||
98
infrastructure/ansible/roles/forgejo/tasks/database.yml
Normal file
98
infrastructure/ansible/roles/forgejo/tasks/database.yml
Normal file
|
|
@ -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
|
||||||
131
infrastructure/ansible/roles/forgejo/tasks/main.yml
Normal file
131
infrastructure/ansible/roles/forgejo/tasks/main.yml
Normal file
|
|
@ -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
|
||||||
100
infrastructure/ansible/roles/forgejo/templates/app.ini.j2
Normal file
100
infrastructure/ansible/roles/forgejo/templates/app.ini.j2
Normal file
|
|
@ -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 %}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,12 +20,21 @@
|
||||||
validate: '/usr/sbin/sshd -T -f %s'
|
validate: '/usr/sbin/sshd -T -f %s'
|
||||||
notify: Reload ssh
|
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
|
- name: Déployer le drop-in journald
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: journald-boreale.conf.j2
|
src: journald-boreale.conf.j2
|
||||||
dest: /etc/systemd/journald.conf.d/99-boreale.conf
|
dest: /etc/systemd/journald.conf.d/99-boreale.conf
|
||||||
mode: '0644'
|
owner: root
|
||||||
when: hardening_common_journald_enabled | bool
|
group: root
|
||||||
|
mode: "0644"
|
||||||
notify: Restart journald
|
notify: Restart journald
|
||||||
|
|
||||||
- name: Installer fail2ban
|
- name: Installer fail2ban
|
||||||
|
|
|
||||||
18
infrastructure/ansible/roles/keycloak/handlers/main.yml
Normal file
18
infrastructure/ansible/roles/keycloak/handlers/main.yml
Normal file
|
|
@ -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
|
||||||
140
infrastructure/ansible/roles/keycloak/tasks/database.yml
Normal file
140
infrastructure/ansible/roles/keycloak/tasks/database.yml
Normal file
|
|
@ -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
|
||||||
140
infrastructure/ansible/roles/keycloak/tasks/main.yml
Normal file
140
infrastructure/ansible/roles/keycloak/tasks/main.yml
Normal file
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
22
infrastructure/ansible/roles/postgresql/defaults/main.yml
Normal file
22
infrastructure/ansible/roles/postgresql/defaults/main.yml
Normal file
|
|
@ -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
|
||||||
13
infrastructure/ansible/roles/postgresql/handlers/main.yml
Normal file
13
infrastructure/ansible/roles/postgresql/handlers/main.yml
Normal file
|
|
@ -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
|
||||||
114
infrastructure/ansible/roles/postgresql/tasks/main.yml
Normal file
114
infrastructure/ansible/roles/postgresql/tasks/main.yml
Normal file
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,20 +1,34 @@
|
||||||
---
|
---
|
||||||
- name: Gate
|
- name: Gate
|
||||||
ansible.builtin.meta: end_host
|
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:
|
ansible.builtin.apt:
|
||||||
name: wazuh-agent
|
name: wazuh-agent
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
failed_when: false
|
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
|
- name: Déployer ossec.conf minimal
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: ossec.conf.j2
|
src: ossec.conf.j2
|
||||||
dest: /var/ossec/etc/ossec.conf
|
dest: /var/ossec/etc/ossec.conf
|
||||||
mode: '0640'
|
mode: "0640"
|
||||||
when: security_wazuh_agent_manager | length > 0
|
when: security_wazuh_agent_manager | length > 0
|
||||||
notify: Restart wazuh-agent
|
notify: Restart wazuh-agent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
Loading…
Reference in a new issue