From 51f19b678fa0041aa207755c0620ef814838e22e Mon Sep 17 00:00:00 2001 From: Fabien ZARIFIAN Date: Mon, 13 Apr 2026 23:25:51 +0200 Subject: [PATCH] feat(ansible-role-remote_users_fact) first commit --- README.md | 104 ++++++++++++++++ inventories/hosts.yml | 39 ++++++ roles/remote_users_fact/defaults/main.yml | 27 ++++ .../remote_users_fact/files/remote_users.fact | 116 ++++++++++++++++++ roles/remote_users_fact/handlers/main.yml | 8 ++ roles/remote_users_fact/meta/main.yml | 40 ++++++ roles/remote_users_fact/tasks/deploy.yml | 23 ++++ roles/remote_users_fact/tasks/main.yml | 24 ++++ roles/remote_users_fact/tasks/summary.yml | 34 +++++ roles/remote_users_fact/tasks/validate.yml | 37 ++++++ site.yml | 26 ++++ 11 files changed, 478 insertions(+) create mode 100644 README.md create mode 100644 inventories/hosts.yml create mode 100644 roles/remote_users_fact/defaults/main.yml create mode 100644 roles/remote_users_fact/files/remote_users.fact create mode 100644 roles/remote_users_fact/handlers/main.yml create mode 100644 roles/remote_users_fact/meta/main.yml create mode 100644 roles/remote_users_fact/tasks/deploy.yml create mode 100644 roles/remote_users_fact/tasks/main.yml create mode 100644 roles/remote_users_fact/tasks/summary.yml create mode 100644 roles/remote_users_fact/tasks/validate.yml create mode 100644 site.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..46dff29 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# remote_users_fact + +Rôle Ansible qui déploie un **local fact** comptant les sessions distantes par protocole (SSH, Citrix, Horizon) et évaluant la fiabilité en comparant avec `who`. + +## Arborescence + +``` +├── site.yml # Playbook principal +├── inventories/ +│ └── hosts.yml # Inventaire exemple +└── roles/ + └── remote_users_fact/ + ├── defaults/main.yml # Variables par défaut + ├── files/remote_users.fact # Script fact déployé + ├── handlers/main.yml # Handler rechargement facts + ├── meta/main.yml # Métadonnées Galaxy + └── tasks/ + ├── main.yml # Orchestration + ├── deploy.yml # Création répertoire + copie + ├── validate.yml # Exécution + parsing JSON + └── summary.yml # Affichage résumé + alertes +``` + +## Usage + +```bash +# Déploiement complet +ansible-playbook -i inventories/hosts.yml site.yml + +# Déploiement seul +ansible-playbook -i inventories/hosts.yml site.yml --tags deploy + +# Vérification seule (fact déjà déployé) +ansible-playbook -i inventories/hosts.yml site.yml --tags validate,summary + +# Sur un groupe spécifique +ansible-playbook -i inventories/hosts.yml site.yml -l citrix_servers +``` + +## Variables + +| Variable | Défaut | Description | +|---|---|---| +| `remote_users_fact_dir` | `/etc/ansible/facts.d` | Répertoire de destination | +| `remote_users_fact_name` | `remote_users.fact` | Nom du script | +| `remote_users_fact_owner` | `root` | Propriétaire | +| `remote_users_fact_group` | `root` | Groupe | +| `remote_users_fact_validate` | `true` | Activer la validation post-deploy | +| `remote_users_fact_display_summary` | `true` | Afficher le résumé | +| `remote_users_fact_warn_verdicts` | voir defaults | Verdicts déclenchant un warning | + +## Fact déployé + +Accessible via `ansible_local.remote_users` : + +```json +{ + "timestamp": "2026-04-13T10:30:00Z", + "sessions": { + "ssh": 3, + "citrix": 12, + "horizon": 0, + "total_by_protocol": 15, + "who_remote": 14 + }, + "users_remote": "alice,bob,charlie", + "reliability": { + "ratio_who_over_total": 0.93, + "verdict": "WHO_INF_TOTAL", + "detail": "who manque 1 session(s) sans TTY" + }, + "detection": { + "citrix_vda_installed": true, + "horizon_agent_installed": false, + "ssh_method": "sshd_process_and_ss", + "citrix_method": "ctxquery", + "horizon_method": "fallback_ports" + } +} +``` + +## Verdicts + +| Verdict | Signification | +|---|---| +| `FIABLE` | who == total → compteurs alignés | +| `OK` | Écart ≤ 1 → tolérable | +| `WHO_SUP_TOTAL` | who > total → protocole non surveillé | +| `WHO_INF_TOTAL` | who < total → sessions headless sans TTY | +| `WHO_SEUL` | total == 0 → protocoles non détectés | +| `PROTO_SEUL` | who == 0 → sessions sans allocation TTY | +| `NEUTRE` | 0 == 0 → aucune session | + +## Utilisation dans d'autres playbooks + +```yaml +- hosts: all + gather_facts: true + tasks: + - name: Refuser un déploiement si trop de sessions actives + ansible.builtin.fail: + msg: "{{ ansible_local.remote_users.sessions.total_by_protocol }} sessions actives, déploiement annulé" + when: ansible_local.remote_users.sessions.total_by_protocol | int > 10 +``` diff --git a/inventories/hosts.yml b/inventories/hosts.yml new file mode 100644 index 0000000..5be9917 --- /dev/null +++ b/inventories/hosts.yml @@ -0,0 +1,39 @@ +--- +# ============================================================================= +# inventories/hosts.yml — Exemple d'inventaire +# ============================================================================= + +all: + children: + + ssh_servers: + hosts: + srv-linux-01: + ansible_host: 192.168.1.10 + srv-linux-02: + ansible_host: 192.168.1.11 + + citrix_servers: + hosts: + ctx-vda-01: + ansible_host: 192.168.2.10 + ctx-vda-02: + ansible_host: 192.168.2.11 + vars: + # Optionnel : surcharger les verdicts d'alerte pour Citrix + # car WHO_INF_TOTAL est fréquent (apps publiées sans TTY) + remote_users_fact_warn_verdicts: + - WHO_SUP_TOTAL + - WHO_SEUL + + horizon_servers: + hosts: + hrz-agent-01: + ansible_host: 192.168.3.10 + hrz-agent-02: + ansible_host: 192.168.3.11 + + vars: + ansible_user: ansible + ansible_become: true + ansible_become_method: sudo diff --git a/roles/remote_users_fact/defaults/main.yml b/roles/remote_users_fact/defaults/main.yml new file mode 100644 index 0000000..14ba51e --- /dev/null +++ b/roles/remote_users_fact/defaults/main.yml @@ -0,0 +1,27 @@ +--- +# ============================================================================= +# defaults/main.yml — Variables par défaut du rôle remote_users_fact +# ============================================================================= + +# Répertoire de destination des local facts +remote_users_fact_dir: /etc/ansible/facts.d + +# Nom du script fact +remote_users_fact_name: remote_users.fact + +# Propriétaire et groupe du fichier +remote_users_fact_owner: root +remote_users_fact_group: root + +# Activer la validation post-déploiement (exécution + parsing JSON) +remote_users_fact_validate: true + +# Activer l'affichage du résumé après déploiement +remote_users_fact_display_summary: true + +# Verdicts considérés comme anormaux (déclenchent un warning) +remote_users_fact_warn_verdicts: + - WHO_SUP_TOTAL + - WHO_INF_TOTAL + - WHO_SEUL + - PROTO_SEUL diff --git a/roles/remote_users_fact/files/remote_users.fact b/roles/remote_users_fact/files/remote_users.fact new file mode 100644 index 0000000..ca480d6 --- /dev/null +++ b/roles/remote_users_fact/files/remote_users.fact @@ -0,0 +1,116 @@ +#!/bin/bash +# ============================================================================= +# remote_users.fact +# Ansible local fact — /etc/ansible/facts.d/remote_users.fact +# Retourne un JSON avec le comptage des sessions distantes par protocole +# et l'analyse de fiabilité who vs total +# ============================================================================= + +set -euo pipefail + +# --- Comptage SSH --- +ssh_by_ss=0 +if command -v ss &>/dev/null; then + ssh_by_ss=$(ss -tnp state established 2>/dev/null | grep -c "sshd" || echo 0) +fi +ssh_by_proc=$(pgrep -c -f "sshd:.*@" 2>/dev/null || echo 0) +ssh_count=$(( ssh_by_ss > ssh_by_proc ? ssh_by_ss : ssh_by_proc )) + +# --- Comptage Citrix --- +citrix_count=0 +ctxquery="/opt/Citrix/VDA/bin/ctxquery" +if [[ -x "$ctxquery" ]]; then + citrix_count=$("$ctxquery" -f all 2>/dev/null | grep -ci "active" || echo 0) +else + citrix_count=$(pgrep -c -f "ctxhdx|ctxgfx|wfica" 2>/dev/null || echo 0) + if [[ $citrix_count -eq 0 ]] && command -v ss &>/dev/null; then + citrix_count=$(ss -tnp state established 2>/dev/null \ + | grep -cE ":(1494|2598)\b" || echo 0) + fi +fi + +# --- Comptage Horizon --- +blast=$(pgrep -c -f "vmware-blast" 2>/dev/null || echo 0) +pcoip=$(pgrep -c -f "pcoip-server" 2>/dev/null || echo 0) +horizon_count=$(( blast + pcoip )) +if [[ $horizon_count -eq 0 ]] && command -v ss &>/dev/null; then + horizon_count=$(ss -tnp state established 2>/dev/null \ + | grep -cE ":(8443|22443)\b" || echo 0) +fi + +# --- Comptage who (distant) --- +who_count=$(who 2>/dev/null | grep -v '(:0' | grep -c '(.*[a-zA-Z0-9])' || echo 0) + +# --- Liste utilisateurs distants (who) --- +who_users=$(who 2>/dev/null | grep -v '(:0' | grep '(.*[a-zA-Z0-9])' \ + | awk '{print $1}' | sort -u | paste -sd ',' || echo "") + +# --- Total protocoles --- +total=$(( ssh_count + citrix_count + horizon_count )) + +# --- Analyse fiabilité --- +if [[ $total -eq 0 && $who_count -eq 0 ]]; then + ratio="null" + verdict="NEUTRE" + detail="Aucune session detectee" +elif [[ $total -eq 0 ]]; then + ratio="null" + verdict="WHO_SEUL" + detail="who detecte ${who_count} session(s) non classifiee(s) par protocole" +elif [[ $who_count -eq 0 ]]; then + ratio="0" + verdict="PROTO_SEUL" + detail="Sessions detectees par protocole mais invisibles dans who" +else + ratio=$(awk "BEGIN { printf \"%.2f\", ($who_count / $total) }") + diff=$(( who_count - total )) + abs_diff=${diff#-} + + if [[ $abs_diff -eq 0 ]]; then + verdict="FIABLE" + detail="who == total protocoles, compteurs alignes" + elif [[ $abs_diff -le 1 ]]; then + verdict="OK" + detail="Ecart de ${abs_diff} session(s), tolerable" + elif [[ $who_count -gt $total ]]; then + verdict="WHO_SUP_TOTAL" + detail="who voit +${abs_diff} session(s) non classifiee(s)" + else + verdict="WHO_INF_TOTAL" + detail="who manque ${abs_diff} session(s) sans TTY" + fi +fi + +# --- Détection des composants installés --- +has_citrix_vda=false +[[ -x "$ctxquery" ]] && has_citrix_vda=true + +has_horizon_agent=false +[[ -f /usr/lib/vmware/viewagent/bin/vmware-viewagent ]] && has_horizon_agent=true + +# --- Sortie JSON --- +cat < + Déploie un local fact Ansible qui compte les sessions distantes + par protocole (SSH, Citrix, Horizon) et compare avec who + pour évaluer la fiabilité des compteurs. + license: MIT + min_ansible_version: "2.12" + + platforms: + - name: EL + versions: + - "8" + - "9" + - name: Debian + versions: + - bullseye + - bookworm + - name: Ubuntu + versions: + - focal + - jammy + - noble + + galaxy_tags: + - monitoring + - facts + - security + - ssh + - citrix + - horizon + - audit + +dependencies: [] diff --git a/roles/remote_users_fact/tasks/deploy.yml b/roles/remote_users_fact/tasks/deploy.yml new file mode 100644 index 0000000..51e07c5 --- /dev/null +++ b/roles/remote_users_fact/tasks/deploy.yml @@ -0,0 +1,23 @@ +--- +# ============================================================================= +# tasks/deploy.yml — Création du répertoire et copie du fact +# ============================================================================= + +- name: Créer le répertoire facts.d + ansible.builtin.file: + path: "{{ remote_users_fact_dir }}" + state: directory + owner: "{{ remote_users_fact_owner }}" + group: "{{ remote_users_fact_group }}" + mode: "0755" + +- name: Déployer le script remote_users.fact + ansible.builtin.copy: + src: "{{ remote_users_fact_name }}" + dest: "{{ remote_users_fact_dir }}/{{ remote_users_fact_name }}" + owner: "{{ remote_users_fact_owner }}" + group: "{{ remote_users_fact_group }}" + mode: "0755" + backup: true + register: _remote_users_fact_deployed + notify: Recharger les local facts diff --git a/roles/remote_users_fact/tasks/main.yml b/roles/remote_users_fact/tasks/main.yml new file mode 100644 index 0000000..672fd13 --- /dev/null +++ b/roles/remote_users_fact/tasks/main.yml @@ -0,0 +1,24 @@ +--- +# ============================================================================= +# tasks/main.yml — Déploiement et validation du local fact remote_users +# ============================================================================= + +- name: Inclure les tâches de déploiement + ansible.builtin.include_tasks: deploy.yml + tags: + - remote_users_fact + - deploy + +- name: Inclure les tâches de validation + ansible.builtin.include_tasks: validate.yml + when: remote_users_fact_validate | bool + tags: + - remote_users_fact + - validate + +- name: Inclure les tâches de résumé + ansible.builtin.include_tasks: summary.yml + when: remote_users_fact_display_summary | bool + tags: + - remote_users_fact + - summary diff --git a/roles/remote_users_fact/tasks/summary.yml b/roles/remote_users_fact/tasks/summary.yml new file mode 100644 index 0000000..46df44c --- /dev/null +++ b/roles/remote_users_fact/tasks/summary.yml @@ -0,0 +1,34 @@ +--- +# ============================================================================= +# tasks/summary.yml — Affichage du résumé et alertes éventuelles +# ============================================================================= + +- name: Afficher le résumé des sessions distantes + ansible.builtin.debug: + msg: + - "══════════════════════════════════════════" + - "Host : {{ inventory_hostname }}" + - "Timestamp : {{ ansible_local.remote_users.timestamp }}" + - "──────────────────────────────────────────" + - "SSH : {{ ansible_local.remote_users.sessions.ssh }}" + - "Citrix : {{ ansible_local.remote_users.sessions.citrix }}" + - "Horizon : {{ ansible_local.remote_users.sessions.horizon }}" + - "Total proto : {{ ansible_local.remote_users.sessions.total_by_protocol }}" + - "Who remote : {{ ansible_local.remote_users.sessions.who_remote }}" + - "──────────────────────────────────────────" + - "Verdict : {{ ansible_local.remote_users.reliability.verdict }}" + - "Ratio : {{ ansible_local.remote_users.reliability.ratio_who_over_total }}" + - "Détail : {{ ansible_local.remote_users.reliability.detail }}" + - "──────────────────────────────────────────" + - "Utilisateurs: {{ ansible_local.remote_users.users_remote | default('aucun') }}" + - "Citrix VDA : {{ ansible_local.remote_users.detection.citrix_vda_installed }}" + - "Horizon Agt : {{ ansible_local.remote_users.detection.horizon_agent_installed }}" + - "══════════════════════════════════════════" + +- name: Alerter si le verdict est anormal + ansible.builtin.debug: + msg: >- + ⚠ ATTENTION sur {{ inventory_hostname }} — + Verdict: {{ ansible_local.remote_users.reliability.verdict }} — + {{ ansible_local.remote_users.reliability.detail }} + when: ansible_local.remote_users.reliability.verdict in remote_users_fact_warn_verdicts diff --git a/roles/remote_users_fact/tasks/validate.yml b/roles/remote_users_fact/tasks/validate.yml new file mode 100644 index 0000000..19ae2ab --- /dev/null +++ b/roles/remote_users_fact/tasks/validate.yml @@ -0,0 +1,37 @@ +--- +# ============================================================================= +# tasks/validate.yml — Validation du fact (exécution + parsing JSON) +# ============================================================================= + +- name: Forcer le rechargement des facts si déploiement effectué + ansible.builtin.meta: flush_handlers + +- name: Exécuter le script fact manuellement pour validation + ansible.builtin.command: + cmd: "{{ remote_users_fact_dir }}/{{ remote_users_fact_name }}" + register: _remote_users_fact_output + changed_when: false + failed_when: _remote_users_fact_output.rc != 0 + +- name: Valider que la sortie est du JSON parsable + ansible.builtin.set_fact: + _remote_users_fact_parsed: "{{ _remote_users_fact_output.stdout | from_json }}" + +- name: Vérifier la présence des clés obligatoires + ansible.builtin.assert: + that: + - _remote_users_fact_parsed.sessions is defined + - _remote_users_fact_parsed.sessions.ssh is defined + - _remote_users_fact_parsed.sessions.citrix is defined + - _remote_users_fact_parsed.sessions.horizon is defined + - _remote_users_fact_parsed.sessions.total_by_protocol is defined + - _remote_users_fact_parsed.sessions.who_remote is defined + - _remote_users_fact_parsed.reliability is defined + - _remote_users_fact_parsed.reliability.verdict is defined + - _remote_users_fact_parsed.detection is defined + fail_msg: "Le fact remote_users ne retourne pas la structure JSON attendue" + success_msg: "Structure JSON validée avec succès" + +- name: Recharger les ansible_local facts + ansible.builtin.setup: + filter: ansible_local diff --git a/site.yml b/site.yml new file mode 100644 index 0000000..c73af91 --- /dev/null +++ b/site.yml @@ -0,0 +1,26 @@ +--- +# ============================================================================= +# site.yml — Playbook de déploiement du local fact remote_users +# ============================================================================= +# +# Usage : +# ansible-playbook -i inventories/hosts.yml site.yml +# +# # Déploiement seul (sans validation ni résumé) : +# ansible-playbook -i inventories/hosts.yml site.yml --tags deploy +# +# # Validation + résumé seul (fact déjà déployé) : +# ansible-playbook -i inventories/hosts.yml site.yml --tags validate,summary +# +# # Limiter à un groupe : +# ansible-playbook -i inventories/hosts.yml site.yml -l citrix_servers +# +# ============================================================================= + +- name: Déployer et valider le local fact remote_users + hosts: all + become: true + gather_facts: true + + roles: + - role: remote_users_fact