serveur_oauth2_proxy : passerelle SSO OIDC générique + Icinga Web 2 au SSO
oauth2-proxy (v7.15.3) place Keycloak devant toute app sans OIDC natif (auth external, utilisateur via en-tête). Rôle paramétrable, réutilisable. Éprouvé devant icingaweb2 (backend=external, X-Forwarded-Preferred-Username). Prouvé : testmail → oauth2-proxy → Keycloak → icingaweb2 /dashboard, connecté. Ferme le gap LDAP-direct d'icingaweb2. Réglages appris : insecure_oidc_allow_unverified_email (IdP interne) ; reverse-proxy passe X-Forwarded-* pas X-Auth-Request-* ; handler nginx en restart (pas reload) — un changement d'adresse d'écoute n'est pas pris par un reload gracieux. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2ed919e914
commit
b60c5dc963
13 changed files with 212 additions and 6 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -3,6 +3,19 @@
|
|||
## 2026-07-03
|
||||
|
||||
### Ajouté
|
||||
- **Rôle `serveur_oauth2_proxy` — passerelle SSO OIDC générique (Keycloak) + Icinga Web 2 au SSO.**
|
||||
oauth2-proxy (v7.15.3, binaire GitHub) place Keycloak **devant** n'importe quelle app sans OIDC
|
||||
natif : elle reçoit l'utilisateur authentifié via en-tête, en auth `external`. Rôle paramétrable
|
||||
(client, secret voûte, redirect, upstream, cookie voûte) — **réutilisable** pour toute app
|
||||
OIDC-less. Éprouvé sur `sup-01` **devant Icinga Web 2** : client Keycloak `icingaweb2`,
|
||||
oauth2-proxy `:4180` (exposé par l'edge) → upstream nginx local `:8080` → icingaweb2
|
||||
`backend = external` (REMOTE_USER depuis `X-Forwarded-Preferred-Username`). **Prouvé** (flux
|
||||
authorization code headless) : `testmail` → oauth2-proxy → Keycloak → **icingaweb2 `/dashboard`,
|
||||
connecté** (« Se connecter avec Chezlepro », comme Grafana/Forgejo). Ferme le gap LDAP-direct
|
||||
d'icingaweb2. **Réglages appris** : `insecure_oidc_allow_unverified_email` (les users LDAP n'ont
|
||||
pas `email_verified` ; IdP interne de confiance) ; en reverse-proxy oauth2-proxy passe
|
||||
`X-Forwarded-*` (pas `X-Auth-Request-*`) ; **handler nginx en `restart` (pas `reload`)** car un
|
||||
changement d'adresse d'écoute n'est pas pris par un reload gracieux.
|
||||
- **Rôle `serveur_icingaweb2` — Icinga Web 2 (UI native) + module IcingaDB : éprouvé.** App PHP
|
||||
(php8.4-fpm) servie par un **nginx local**, exposée par l'edge (`icinga.lab.chezlepro.internal`,
|
||||
auto-dérivé : vhost + cert SAN + A PowerDNS + alias plancher). Config **par fichiers `.ini`**
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@ sur `forge-01`, PostgreSQL via registre, **branché au SSO OIDC** — 2e app : `
|
|||
compte auto-créé). Et **`serveur_redis`** (sur `data-sql-01`, cache réseau, auth obligatoire —
|
||||
SET/GET prouvé, accès sans mot de passe refusé). Et le **cœur `serveur_icinga`** (sur `sup-01` :
|
||||
icinga2 + icingadb + redis dédié, base PostgreSQL via registre — supervise, IcingaDB peuplée) +
|
||||
**`serveur_icingaweb2`** (UI native, PHP/nginx local, module IcingaDB + **module BPM**, **auth LDAP** —
|
||||
`testmail` se connecte, voit la supervision, et un processus métier BPM rend un état).
|
||||
**Pile Icinga complète : moteur + Web 2 + BPM.** Restent « code non éprouvé » : `serveur_sendmail`.
|
||||
**`serveur_icingaweb2`** (UI native, PHP/nginx local, module IcingaDB + **module BPM**, **SSO Keycloak**
|
||||
via `serveur_oauth2_proxy` — `testmail` se connecte par le SSO, voit la supervision, et un processus
|
||||
métier BPM rend un état). **Pile Icinga complète : moteur + Web 2 + BPM, au SSO.** Plus la passerelle
|
||||
**`serveur_oauth2_proxy`** (oauth2-proxy) : SSO OIDC générique réutilisable pour toute app sans OIDC
|
||||
natif. Restent « code non éprouvé » : `serveur_sendmail`.
|
||||
|
||||
> **Lacunes connues sur Keycloak** (déployé mais pas complet) : la **fédération LDAP** (Keycloak
|
||||
> → OpenLDAP, modèle d'identité A) n'est **pas encore automatisée** dans le rôle ; l'**edge nginx**
|
||||
|
|
|
|||
13
playbooks/groupes/serveur_oauth2_proxy.yml
Normal file
13
playbooks/groupes/serveur_oauth2_proxy.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
- name: Appliquer le groupe serveur_oauth2_proxy
|
||||
hosts: serveur_oauth2_proxy
|
||||
become: true
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: Vérifier que la cible est Debian
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.distribution == "Debian"
|
||||
fail_msg: "Ce playbook est prévu pour Debian."
|
||||
roles:
|
||||
- serveur_oauth2_proxy
|
||||
|
|
@ -45,6 +45,14 @@ serveur_icingaweb2_ldap_user_attr: "uid"
|
|||
# Qui reçoit le rôle Administrateur (liste d'uid séparés par des virgules).
|
||||
serveur_icingaweb2_admins: "testmail"
|
||||
|
||||
# Backend d'authentification : "ldap" (direct) ou "external" (utilisateur fourni par une
|
||||
# passerelle SSO devant, ex. oauth2-proxy → Keycloak). En "external", nginx doit poser REMOTE_USER.
|
||||
serveur_icingaweb2_auth: "ldap"
|
||||
# Variable nginx d'où vient REMOTE_USER. En SSO : l'en-tête d'oauth2-proxy.
|
||||
serveur_icingaweb2_remote_user: "$remote_user"
|
||||
# Préfixe d'écoute nginx local ("" = toutes interfaces ; "127.0.0.1:" = local, derrière la passerelle).
|
||||
serveur_icingaweb2_nginx_bind: ""
|
||||
|
||||
# Modules icingaweb2 à activer.
|
||||
serveur_icingaweb2_modules:
|
||||
- icingadb
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
|
||||
# restart (pas reload) : un changement d'adresse d'écoute (ex. 0.0.0.0 -> 127.0.0.1)
|
||||
# n'est pas pris par un reload gracieux (il conserve les sockets d'écoute existants).
|
||||
- name: Recharger nginx
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ serveur_icingaweb2_nginx_service }}"
|
||||
state: reloaded
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
; Géré par Set-OPS (rôle serveur_icingaweb2). Ne pas éditer à la main.
|
||||
{% if serveur_icingaweb2_auth == 'external' %}
|
||||
; SSO : l'utilisateur est fourni par la passerelle (oauth2-proxy → Keycloak) via REMOTE_USER.
|
||||
[icingaweb2]
|
||||
backend = "external"
|
||||
{% else %}
|
||||
[icingaweb2]
|
||||
backend = "ldap"
|
||||
resource = "icingaweb_ldap"
|
||||
user_class = "{{ serveur_icingaweb2_ldap_user_class }}"
|
||||
user_name_attribute = "{{ serveur_icingaweb2_ldap_user_attr }}"
|
||||
base_dn = "{{ serveur_icingaweb2_ldap_users_dn }}"
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# Géré par Set-OPS (rôle serveur_icingaweb2). Ne pas éditer à la main.
|
||||
# nginx LOCAL : sert Icinga Web 2 (PHP-FPM). L'edge nginx proxifie par nom vers ce port.
|
||||
server {
|
||||
listen {{ serveur_icingaweb2_http_port }};
|
||||
listen {{ serveur_icingaweb2_nginx_bind }}{{ serveur_icingaweb2_http_port }};
|
||||
{% if not serveur_icingaweb2_nginx_bind %}
|
||||
listen [::]:{{ serveur_icingaweb2_http_port }};
|
||||
{% endif %}
|
||||
server_name {{ serveur_icingaweb2_hostname }};
|
||||
|
||||
root {{ serveur_icingaweb2_docroot }};
|
||||
|
|
@ -18,7 +20,7 @@ server {
|
|||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME {{ serveur_icingaweb2_docroot }}/index.php;
|
||||
fastcgi_param ICINGAWEB_CONFIGDIR {{ serveur_icingaweb2_config_dir }};
|
||||
fastcgi_param REMOTE_USER $remote_user;
|
||||
fastcgi_param REMOTE_USER {{ serveur_icingaweb2_remote_user }};
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
|
|
|
|||
36
roles/serveur_oauth2_proxy/defaults/main.yml
Normal file
36
roles/serveur_oauth2_proxy/defaults/main.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
# Passerelle SSO OIDC générique (oauth2-proxy) : place Keycloak DEVANT n'importe quelle
|
||||
# application sans OIDC natif. L'app reçoit l'utilisateur via en-têtes (auth « external »).
|
||||
# Binaire Go statique depuis les releases GitHub (pas d'apt). Requiert client_pki (confiance
|
||||
# step_ca pour joindre Keycloak) + résolution de l'hôte Keycloak.
|
||||
serveur_oauth2_proxy_version: "v7.15.3"
|
||||
serveur_oauth2_proxy_archive: "oauth2-proxy-{{ serveur_oauth2_proxy_version }}.linux-amd64.tar.gz"
|
||||
serveur_oauth2_proxy_url: >-
|
||||
https://github.com/oauth2-proxy/oauth2-proxy/releases/download/{{
|
||||
serveur_oauth2_proxy_version }}/{{ serveur_oauth2_proxy_archive }}
|
||||
serveur_oauth2_proxy_binaire: "/usr/local/bin/oauth2-proxy"
|
||||
serveur_oauth2_proxy_utilisateur: "oauth2-proxy"
|
||||
serveur_oauth2_proxy_config: "/etc/oauth2-proxy/oauth2-proxy.cfg"
|
||||
serveur_oauth2_proxy_service: "oauth2-proxy"
|
||||
|
||||
# Écoute (l'edge nginx proxifie ici).
|
||||
serveur_oauth2_proxy_ecoute: "0.0.0.0:4180"
|
||||
|
||||
# OIDC (Keycloak) — realm applicatif.
|
||||
serveur_oauth2_proxy_realm: "chezlepro"
|
||||
serveur_oauth2_proxy_issuer: "https://keycloak.{{ domaine_interne }}/realms/{{ serveur_oauth2_proxy_realm }}"
|
||||
serveur_oauth2_proxy_client_id: "" # OBLIGATOIRE (par instance)
|
||||
serveur_oauth2_proxy_client_secret: "" # OBLIGATOIRE (voûte)
|
||||
serveur_oauth2_proxy_redirect_url: "" # OBLIGATOIRE (https://<app>/oauth2/callback)
|
||||
|
||||
# Application protégée (upstream local).
|
||||
serveur_oauth2_proxy_upstream: "" # OBLIGATOIRE (ex. http://127.0.0.1:8080)
|
||||
|
||||
# Secret de cookie (voûte) — 32 octets base64.
|
||||
serveur_oauth2_proxy_cookie_secret: "{{ vault_oauth2_cookie | default('') }}"
|
||||
# * = tout utilisateur authentifié par Keycloak.
|
||||
serveur_oauth2_proxy_email_domains: "*"
|
||||
# IdP interne de confiance : les utilisateurs LDAP n'ont pas le drapeau email_verified.
|
||||
serveur_oauth2_proxy_allow_unverified_email: true
|
||||
# Bundle d'AC système (racine step_ca via client_pki) pour joindre Keycloak en HTTPS.
|
||||
serveur_oauth2_proxy_ca_bundle: "/etc/ssl/certs/ca-certificates.crt"
|
||||
7
roles/serveur_oauth2_proxy/handlers/main.yml
Normal file
7
roles/serveur_oauth2_proxy/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
- name: Redémarrer oauth2-proxy
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ serveur_oauth2_proxy_service }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
when: not ansible_check_mode
|
||||
6
roles/serveur_oauth2_proxy/meta/empreinte.yml
Normal file
6
roles/serveur_oauth2_proxy/meta/empreinte.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
# Empreinte ressources — oauth2-proxy (binaire Go léger).
|
||||
setops_empreinte:
|
||||
coeurs: 1
|
||||
memoire_mo: 128
|
||||
disque_go: 1
|
||||
70
roles/serveur_oauth2_proxy/tasks/main.yml
Normal file
70
roles/serveur_oauth2_proxy/tasks/main.yml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
- name: Exiger la configuration OIDC (client, secret, redirect, upstream, cookie)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- serveur_oauth2_proxy_client_id | length > 0
|
||||
- serveur_oauth2_proxy_client_secret | length > 0
|
||||
- serveur_oauth2_proxy_redirect_url | length > 0
|
||||
- serveur_oauth2_proxy_upstream | length > 0
|
||||
- serveur_oauth2_proxy_cookie_secret | length > 0
|
||||
fail_msg: "client_id, client_secret (voûte), redirect_url, upstream et cookie_secret (voûte) requis."
|
||||
|
||||
- name: Créer l'utilisateur système oauth2-proxy
|
||||
ansible.builtin.user:
|
||||
name: "{{ serveur_oauth2_proxy_utilisateur }}"
|
||||
system: true
|
||||
shell: /usr/sbin/nologin
|
||||
create_home: false
|
||||
|
||||
- name: Télécharger oauth2-proxy
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ serveur_oauth2_proxy_url }}"
|
||||
dest: "/tmp/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz"
|
||||
mode: "0644"
|
||||
|
||||
- name: Extraire le binaire oauth2-proxy
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz"
|
||||
dest: /usr/local/bin
|
||||
remote_src: true
|
||||
extra_opts: ["--strip-components=1", "--wildcards", "*/oauth2-proxy"]
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
creates: "{{ serveur_oauth2_proxy_binaire }}"
|
||||
notify: Redémarrer oauth2-proxy
|
||||
|
||||
- name: Créer le répertoire de configuration
|
||||
ansible.builtin.file:
|
||||
path: "{{ serveur_oauth2_proxy_config | dirname }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ serveur_oauth2_proxy_utilisateur }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: Déployer la configuration oauth2-proxy
|
||||
ansible.builtin.template:
|
||||
src: oauth2-proxy.cfg.j2
|
||||
dest: "{{ serveur_oauth2_proxy_config }}"
|
||||
owner: root
|
||||
group: "{{ serveur_oauth2_proxy_utilisateur }}"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
notify: Redémarrer oauth2-proxy
|
||||
|
||||
- name: Déployer l'unité systemd oauth2-proxy
|
||||
ansible.builtin.template:
|
||||
src: oauth2-proxy.service.j2
|
||||
dest: /etc/systemd/system/oauth2-proxy.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Redémarrer oauth2-proxy
|
||||
|
||||
- name: Activer et démarrer oauth2-proxy
|
||||
when: not ansible_check_mode
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ serveur_oauth2_proxy_service }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
22
roles/serveur_oauth2_proxy/templates/oauth2-proxy.cfg.j2
Normal file
22
roles/serveur_oauth2_proxy/templates/oauth2-proxy.cfg.j2
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Géré par Set-OPS (rôle serveur_oauth2_proxy). Ne pas éditer à la main.
|
||||
http_address = "{{ serveur_oauth2_proxy_ecoute }}"
|
||||
reverse_proxy = true
|
||||
|
||||
provider = "oidc"
|
||||
oidc_issuer_url = "{{ serveur_oauth2_proxy_issuer }}"
|
||||
client_id = "{{ serveur_oauth2_proxy_client_id }}"
|
||||
client_secret = "{{ serveur_oauth2_proxy_client_secret }}"
|
||||
redirect_url = "{{ serveur_oauth2_proxy_redirect_url }}"
|
||||
provider_ca_files = ["{{ serveur_oauth2_proxy_ca_bundle }}"]
|
||||
|
||||
upstreams = ["{{ serveur_oauth2_proxy_upstream }}"]
|
||||
|
||||
email_domains = ["{{ serveur_oauth2_proxy_email_domains }}"]
|
||||
insecure_oidc_allow_unverified_email = {{ serveur_oauth2_proxy_allow_unverified_email | lower }}
|
||||
cookie_secret = "{{ serveur_oauth2_proxy_cookie_secret }}"
|
||||
cookie_secure = true
|
||||
|
||||
# TLS terminé à l'edge ; oauth2-proxy voit HTTP mais reverse_proxy=true honore X-Forwarded-Proto.
|
||||
skip_provider_button = true
|
||||
set_xauthrequest = true
|
||||
pass_user_headers = true
|
||||
19
roles/serveur_oauth2_proxy/templates/oauth2-proxy.service.j2
Normal file
19
roles/serveur_oauth2_proxy/templates/oauth2-proxy.service.j2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Géré par Set-OPS (rôle serveur_oauth2_proxy). Ne pas éditer à la main.
|
||||
[Unit]
|
||||
Description=OAuth2 Proxy (passerelle SSO OIDC Keycloak)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User={{ serveur_oauth2_proxy_utilisateur }}
|
||||
Group={{ serveur_oauth2_proxy_utilisateur }}
|
||||
ExecStart={{ serveur_oauth2_proxy_binaire }} --config={{ serveur_oauth2_proxy_config }}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in a new issue