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
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# 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: ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
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"
|
||||
|
|
|
|||
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:
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
- name: community.postgresql
|
||||
|
|
|
|||
|
|
@ -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: []
|
||||
|
|
|
|||
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'
|
||||
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
|
||||
|
|
|
|||
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
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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