guest@6admin.io:~$

Piloter Windows depuis Ansible sans fuiter d'identifiants

La question dont on débat est SSH contre WinRM et c’est la mauvaise, parce que les deux sont désormais légitimes: Win32-OpenSSH est un transport Ansible de premier ordre et WinRM est le transport natif. La question qui décide vraiment de votre exposition est plus étroite et plus tranchante: quand une tâche s’authentifie sur un hôte joint au domaine, un identifiant réutilisable traverse-t-il le fil et la cible peut-elle ensuite agir en votre nom contre le reste du domaine. Réglez bien le protocole d’authentification et le modèle de délégation et l’un ou l’autre transport convient. Réglez-les mal et WinRM livrera votre domaine à un attaquant exactement comme le ferait un SSH mal monté.

Le vrai axe: le protocole d’authentification

WinRM est un transport qui porte l’un de plusieurs protocoles d’authentification et ils ne sont pas interchangeables du point de vue sécurité.

Basic envoie le mot de passe, encodé en base64, c’est-à-dire en clair dans tous les sens qui comptent, donc acceptable seulement enveloppé dans HTTPS et encore reste-t-il le mauvais défaut. NTLM n’envoie pas le mot de passe mais répond à un défi, rejouable et relayable et c’est ce vers quoi on retombe précisément quand on n’a rien monté de mieux. Kerberos est la cible: on remet un ticket à la machine, aucun secret réutilisable ne transite et il s’insère dans l’AD que vous exploitez déjà. CredSSP existe pour résoudre le problème du double saut et le fait en déléguant l’identifiant complet à la cible, ce qui signifie qu’une cible compromise détient désormais un identifiant qu’elle peut rejouer partout, donc c’est l’option qu’on garde pour la fin et qu’on cantonne étroitement quand on y est forcé.

La hiérarchie est donc simple: Kerberos par défaut, NTLM seulement en repli contraint, Basic et CredSSP évités sauf besoin précis et rayon d’impact compris.

HTTPS, pas HTTP

L’écouteur WinRM HTTP sur 5985 et l’écouteur HTTPS sur 5986 ne sont pas deux variantes de la même chose. En HTTP, la protection du contenu dépend entièrement du chiffrement au niveau message, que Kerberos et NTLM fournissent mais pas Basic, de sorte qu’un écouteur HTTP en Basic est un identifiant sur le fil. La posture propre consiste à faire tourner HTTPS sur 5986 avec un vrai certificat et à supprimer carrément l’écouteur HTTP, afin que la question du chiffrement message n’ait jamais à se poser dans l’urgence:

winrm delete winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/service @{AllowUnencrypted="false"}
winrm set winrm/config/service/auth @{Basic="false"; Kerberos="true"; CredSSP="false"}

Le côté contrôleur

Kerberos depuis un contrôleur Linux réclame un client krb5 fonctionnel pointé sur le KDC du domaine et les bindings Python:

apt-get install krb5-user
pip install "pywinrm[kerberos]"
# /etc/krb5.conf
[realms]
    COMPANY.COM = {
        kdc = dc1.company.com
        admin_server = dc1.company.com
    }
# group_vars/windows.yml
ansible_connection: winrm
ansible_winrm_transport: kerberos
ansible_winrm_scheme: https
ansible_port: 5986
ansible_winrm_server_cert_validation: validate

server_cert_validation: validate n’est pas optionnel sur un montage durci. La valeur de confort ignore coupe le seul contrôle qui donne un sens à HTTPS, donc une autorité interne dont le contrôleur approuve la racine est le peu de PKI que cela coûte et cela vaut la dépense.

Le piège de la délégation

Le problème du double saut est réel: une tâche qui s’authentifie sur l’hôte A puis cherche depuis A à atteindre un partage sur l’hôte B découvre que A n’a aucun identifiant à présenter, parce que Kerberos lui a remis un ticket pour A et rien d’autre. La correction tentante est CredSSP ou la délégation non contrainte et les deux sont la façon dont les domaines tombent. Déléguer l’identifiant complet à A signifie que quiconque possède A, maintenant ou plus tard, possède tout ce que A avait le droit d’atteindre.

Les réponses défendables, par ordre de préférence: éviter le second saut en exécutant la tâche sur l’hôte qui possède la ressource, utiliser la délégation contrainte Kerberos cantonnée au service précis quand un saut est inévitable et traiter CredSSP comme l’option de dernier recours, activée par tâche plutôt qu’à l’échelle du parc et jamais laissée en place. Une note de transport qui dit “activez juste CredSSP, ça règle le double saut” décrit comment perdre le domaine.

Windows n’est pas le reste du parc

Pour le reste du parc, le réflexe Ansible est simple: Ansible se connecte aux nœuds POSIX par OpenSSH par défaut, donc Linux, BSD et même macOS se pilotent avec la même clé, le même inventaire et le même réflexe de gestion de parc. C’est ce réflexe qui rend le SSH tentant pour Windows: une clé privée au lieu des certificats, des transferts de fichiers plus rapides et un montage plus simple hors domaine, le tout officiellement supporté par Ansible seulement depuis la 2.18, sur l’OpenSSH embarqué dans Windows et rien d’autre (le paquet amont Win32-OpenSSH n’est pas supporté), en version 7.9.0.0 minimum, soit du Server 2022 et plus.

Là où le mirage s’arrête, c’est que la tâche Ansible n’atterrit pas dans une session interactive mais dans un logon réseau: le shell tourne sous la session du service sshd, sans bureau ni station de fenêtre interactive. Les programmes qui réclament une session interactive ne tournent pas, les APIs réservées aux logons interactifs se ferment, le store DPAPI est typiquement inaccessible et les mappings réseau par lecteur du profil utilisateur n’existent pas dans la session. La correction documentée est become: il bascule le logon réseau en logon interactif et, avec des credentials explicites, rend les ressources réseau et le store DPAPI accessibles.

Et le double saut de la section précédente revient avec une tuile de plus sous SSH: une clé ne délègue aucun identifiant, donc la session ne peut pas atteindre une ressource réseau en votre nom. Trois sorties, toutes moins propres que le Kerberos WinRM du chapitre au-dessus: become avec les credentials du compte qui doit voir la ressource, GSSAPI avec un ticket forwardable et GSSAPIDelegateCredentials=yes ou le mot de passe par sshpass, qui délègue sans contrainte comme le ferait CredSSP, avec les mêmes réserves en plus d’être un secret réutilisable qui transite. Le plugin SSH ne peut pas non plus obtenir un TGT Kerberos depuis un couple utilisateur/mot de passe: le kinit se fait à la main et un ticket court expire au milieu du playbook. Subtilité d’usage qui mord vite: la clé publique d’un compte administrateur ne va pas dans le .ssh du profil mais dans C:\ProgramData\ssh\administrators_authorized_keys, règle par le bloc Match Group administrators du sshd_config.

C’est là que la décision du parc se prend: ce qui est pure config et paquet passe très bien en SSH à clé; ce qui a besoin d’une identité déléguée, d’un store DPAPI ou d’une vraie session interactive finit nécessairement par WinRM ou par du become qui change de contexte à chaque tâche. Dans un parc joint au domaine, où la posture Kerberos est déjà montée pour WinRM, ce second groupe n’est pas une exception, c’est la majorité.

Cantonnez l’écouteur et surveillez-le

Le point d’accès WinRM ne devrait répondre qu’au contrôleur, ce qui est une règle de pare-feu, pas un espoir:

New-NetFirewallRule -DisplayName "WinRM-HTTPS-Ansible" -Direction Inbound `
    -LocalPort 5986 -Protocol TCP -Action Allow -RemoteAddress 10.0.1.0/24

Et comme WinRM journalise dans le système d’événements Windows, le canal opérationnel est l’endroit où un écouteur en échec ou une authentification rejetée se voit d’abord:

Get-WinEvent -LogName "Microsoft-Windows-WinRM/Operational" |
    Where-Object LevelDisplayName -eq Error

Où cela atterrit

L’un ou l’autre transport fonctionne. La sécurité vit en dessous de ce choix: Kerberos pour qu’aucun secret réutilisable ne traverse le fil, HTTPS sur 5986 avec la validation de certificat active pour que le canal veuille dire quelque chose, Basic et l’écouteur HTTP supprimés et CredSSP ou la délégation non contrainte laissés éteints jusqu’à ce qu’un besoin précis justifie le rayon d’impact, cantonnés quand on ne peut l’éviter. WinRM n’est pas plus sûr que SSH par nature; il l’est quand c’est du Kerberos sur TLS validé et un passif quand c’est du Basic sur HTTP. La configuration est toute l’histoire, pas l’acronyme.