--- # L'ISSUER SE DERIVE DE L'EXPOSITION DECLAREE, il ne s'invente pas. Le defaut fabriquait # `https://keycloak.` par convention de nommage — un nom que rien ne publie : le # plan expose Keycloak sous `auth.`, et c'est ce nom-la que PowerDNS resout et # que nginx sert. Resultat le 2026-08-07 : `lookup keycloak.chezlepro.internal : no such # host`, et oauth2-proxy redemarrait en boucle. # # Le champ `expose` est deja la source pour PowerDNS, nginx et /etc/hosts. C'est la # quatrieme fois qu'on le lit : une convention de nommage se contredit, une declaration # se corrige. - name: Charger les registres applications et domaines (expositions) ansible.builtin.include_vars: file: "{{ item }}" loop: - "{{ setops_plan_dir }}/applications.yml" - "{{ setops_plan_dir }}/domaines.yml" when: serveur_oauth2_proxy_issuer_derive | bool - name: Dériver l'issuer depuis l'exposition de Keycloak ansible.builtin.set_fact: serveur_oauth2_proxy_issuer: >- https://{{ (applications | default({})).get(serveur_oauth2_proxy_app_idp, {}).get('expose', []) | first }}/realms/{{ serveur_oauth2_proxy_realm }} when: - serveur_oauth2_proxy_issuer_derive | bool - ((applications | default({})).get(serveur_oauth2_proxy_app_idp, {}).get('expose', []) | length) > 0 - 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." # Garde d'ENCODAGE, distincte de la garde de présence : un secret présent mais en base64 # standard fait redémarrer oauth2-proxy en boucle, avec un message qui parle de longueur # et jamais d'encodage. Mieux vaut refuser ici, où la cause est nommable. - name: Refuser un cookie_secret qui n'est pas du base64 url-safe ansible.builtin.assert: that: - "'+' not in serveur_oauth2_proxy_cookie_secret" - "'/' not in serveur_oauth2_proxy_cookie_secret" fail_msg: >- `vault_oauth2_cookie` contient `+` ou `/` : c'est du base64 STANDARD. oauth2-proxy décode en base64 URL-SAFE et retombera sur la chaîne brute, dont la longueur sera refusée. Régénérer : openssl rand -base64 32 | tr '+/' '-_' - 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 # Le chemin de destination PORTE la version : cet artefact est immuable une fois pose. # Sans garde, `get_url` recontacte le serveur distant a CHAQUE deploiement — et un # serveur tiers lent suffit alors a faire tomber un deploiement de flotte. Constate le # 2026-08-09 sur le binaire Forgejo, meme motif ici. # # LE REPERE EST LE BINAIRE INSTALLE, PAS L'ARCHIVE (2026-09-28). On regardait l'archive # dans /tmp : /tmp se vide, et elle repartait du controleur a chaque deploiement (`changed` # sans raison). Le binaire est exactement ce que l'extraction attend (`creates`) : present, # il n'y a rien a apporter. Meme correction que `serveur_keycloak`. # # LA VERSION, PAS LA SEULE PRESENCE (2026-10-03). Le repere etait l'existence du binaire : # changer `serveur_oauth2_proxy_version` sur une machine deja installee ne faisait RIEN — # ni telechargement, ni extraction. On demande donc au binaire sa version. - name: Quelle version d'oauth2-proxy est en place ? ansible.builtin.command: argv: ["{{ serveur_oauth2_proxy_binaire }}", "--version"] register: serveur_oauth2_proxy_version_posee changed_when: false failed_when: false check_mode: false - name: L artefact de cette version est-il deja pose ? ansible.builtin.set_fact: serveur_oauth2_proxy_a_jour: >- {{ (serveur_oauth2_proxy_version_posee.rc | default(1) == 0) and ((serveur_oauth2_proxy_version ~ ' ') in (serveur_oauth2_proxy_version_posee.stdout | default('') ~ ' ')) }} # Le controleur telecharge, puis pousse par SSH — la cible ne tire jamais d'Internet. # Motif explique en detail dans `roles/serveur_nextcloud/tasks/installer.yml`. - name: Cache d artefacts du contrôleur ansible.builtin.file: path: "{{ serveur_oauth2_proxy_cache_local }}" state: directory mode: "0700" delegate_to: localhost become: false when: not (serveur_oauth2_proxy_a_jour | bool) # --- LE DEPOT DU SITE D'ABORD, L'INTERNET ENSUITE (2026-09-12) ---------------- # # Le site tient ces artefacts dans son cache (`serveur_artefacts`, LocalDirs). Un runner # qui les y trouve ne sort pas du tout : c'est le meme principe que le cache apt, applique # aux publications qui ne vivent dans aucun depot apt. # # PLACEE AVANT LE `stat` DU CACHE DU CONTROLEUR, ET C'EST TOUT LE MONTAGE : si le depot # sert le fichier, le `stat` qui suit le voit, et la tache de telechargement amont se # saute d'elle-meme. Aucune tache existante n'a change. # # `failed_when: false` — le depot est une COMMODITE. Site sans depot, depot qui n'a pas # encore cette version, cache du site tout neuf : on retombe sur l'amont, comme avant. - name: Prendre l'archive oauth2-proxy au dépôt du site, avant de sortir sur Internet ansible.builtin.get_url: url: "{{ setops_depot_binaires }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" dest: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" mode: "0644" timeout: 30 delegate_to: localhost become: false failed_when: false when: - setops_depot_binaires | default('') | length > 0 - not (serveur_oauth2_proxy_a_jour | bool) - name: L artefact est-il déjà dans le cache du contrôleur ? ansible.builtin.stat: path: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" register: serveur_oauth2_proxy_cache_present delegate_to: localhost become: false when: not (serveur_oauth2_proxy_a_jour | bool) - name: Télécharger oauth2-proxy dans le cache du contrôleur (une seule fois) ansible.builtin.get_url: url: "{{ serveur_oauth2_proxy_url }}" dest: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" mode: "0644" delegate_to: localhost become: false when: - not (serveur_oauth2_proxy_a_jour | bool) - not (serveur_oauth2_proxy_cache_present.stat.exists | default(false)) # QUELLE QUE SOIT LA PROVENANCE — depot du site ou Internet — l'archive du cache doit avoir # l'empreinte relue (defaults/main.yml). Une archive qui ne l'a pas est retiree du cache, # pour ne pas la reproposer au passage suivant. - name: Mesurer l'archive oauth2-proxy du cache ansible.builtin.stat: path: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" checksum_algorithm: sha256 delegate_to: localhost become: false register: serveur_oauth2_proxy_archive_mesuree when: not (serveur_oauth2_proxy_a_jour | bool) - name: Retirer du cache une archive dont l'empreinte ne correspond pas ansible.builtin.file: path: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" state: absent delegate_to: localhost become: false when: - not (serveur_oauth2_proxy_a_jour | bool) - serveur_oauth2_proxy_archive_mesuree.stat.exists | default(false) - serveur_oauth2_proxy_archive_mesuree.stat.checksum | default('') != serveur_oauth2_proxy_sha256 - name: Refuser une archive oauth2-proxy dont l'empreinte ne correspond pas ansible.builtin.assert: that: - serveur_oauth2_proxy_archive_mesuree.stat.checksum | default('') == serveur_oauth2_proxy_sha256 fail_msg: >- L'archive oauth2-proxy {{ serveur_oauth2_proxy_version }} n'a pas l'empreinte relue ({{ serveur_oauth2_proxy_sha256[:12] }}…) : obtenue {{ (serveur_oauth2_proxy_archive_mesuree.stat.checksum | default('absente'))[:12] }}. Elle a ete retiree du cache. Ne pas contourner : relire la publication avant de changer l'empreinte. when: not (serveur_oauth2_proxy_a_jour | bool) - name: Déposer oauth2-proxy sur l hôte depuis le cache ansible.builtin.copy: src: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" dest: "/tmp/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" mode: "0644" when: not (serveur_oauth2_proxy_a_jour | bool) - 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" # En simulation, l'archive n'a pas ete copiee dans /tmp : rien a extraire. when: - not (serveur_oauth2_proxy_a_jour | bool) - not ansible_check_mode 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 # LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et # pousse le resultat ; les greffons standard, eux, sont poses par le porteur. - name: Assurer le repertoire des sondes de supervision ansible.builtin.file: path: /usr/local/lib/setops/sondes state: directory owner: root group: root mode: "0755" - name: Deposer la sonde « passerelle » ansible.builtin.template: src: sonde-passerelle.sh.j2 dest: /usr/local/lib/setops/sondes/passerelle.sh owner: root group: root mode: "0750"