Enrichit l'audit GPResult utilisateur et les groupes de sécurité
This commit is contained in:
1 parent
010f45b8cf
commit
be8cd468f6
1 file changed
+648
-186
@@ -1,272 +1,734 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Audit des GPO de l'utilisateur actif : audit-gpresult.user-gpo.
|
||||
|
||||
L'installation ne realise aucune action. La collecte est executee dans
|
||||
audit(), sous le compte de l'agent WAPT, en ciblant explicitement les
|
||||
donnees RSoP de l'utilisateur avec l'option /USER de gpresult.exe.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from setuphelpers import *
|
||||
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Identifiants de l'audit WAPT
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
AUDIT_SECTION = "audit-gpresult"
|
||||
AUDIT_NAME = "user-gpo"
|
||||
|
||||
|
||||
def install():
|
||||
"""Point d'entree obligatoire ; aucune configuration a appliquer."""
|
||||
"""
|
||||
Ce paquet est uniquement destiné à l'audit.
|
||||
|
||||
Aucune modification persistante n'est effectuée sur le poste.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fonctions utilitaires XML
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _clean_tag(tag):
|
||||
"""
|
||||
Supprime le namespace XML d'un tag ElementTree.
|
||||
"""
|
||||
return tag.split("}")[-1]
|
||||
|
||||
|
||||
def _text(element, tag, default=""):
|
||||
"""
|
||||
Retourne récursivement la première valeur XML correspondant au tag
|
||||
demandé, en ignorant les namespaces.
|
||||
"""
|
||||
|
||||
if element is None:
|
||||
return default
|
||||
|
||||
for child in element.iter():
|
||||
|
||||
if _clean_tag(child.tag).lower() == tag.lower():
|
||||
|
||||
if child.text:
|
||||
return child.text.strip()
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def _to_bool(value):
|
||||
"""
|
||||
Convertit une valeur booléenne textuelle issue de GPResult
|
||||
en booléen Python.
|
||||
"""
|
||||
return str(value).strip().lower() in (
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
"oui"
|
||||
)
|
||||
|
||||
|
||||
def _split_account(account):
|
||||
"""
|
||||
Sépare un compte Windows entre son préfixe et son nom de compte.
|
||||
|
||||
Exemples :
|
||||
FORMATION\\sebastien
|
||||
-> ("FORMATION", "sebastien")
|
||||
|
||||
WSMANAGE30\\formation
|
||||
-> ("WSMANAGE30", "formation")
|
||||
"""
|
||||
|
||||
if not account:
|
||||
return "", ""
|
||||
|
||||
if "\\" in account:
|
||||
return account.split("\\", 1)
|
||||
|
||||
return "", account
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Détection de l'utilisateur interactif
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _get_logged_on_user():
|
||||
"""Retourne DOMAINE\\utilisateur pour l'unique utilisateur actif.
|
||||
|
||||
Les sessions deconnectees sont ignorees. Plusieurs comptes actifs
|
||||
provoquent une erreur pour eviter de choisir une cible arbitraire.
|
||||
Sans utilisateur actif, retourne None.
|
||||
"""
|
||||
import win32ts
|
||||
Retourne le nom de l'utilisateur associé à la session interactive active.
|
||||
|
||||
server = win32ts.WTS_CURRENT_SERVER_HANDLE
|
||||
users = []
|
||||
Les audits WAPT s'exécutent dans le contexte du service WAPT.
|
||||
Il n'est donc pas possible de se fier simplement à l'identité
|
||||
du processus courant.
|
||||
|
||||
for session in win32ts.WTSEnumerateSessions(server, 1, 0):
|
||||
if session["State"] != win32ts.WTSActive:
|
||||
continue
|
||||
`quser` est utilisé pour inspecter les sessions Windows et récupérer
|
||||
l'utilisateur de la session interactive active.
|
||||
|
||||
session_id = session["SessionId"]
|
||||
Exemples retournés par cette fonction :
|
||||
formation
|
||||
sebastien
|
||||
|
||||
username = win32ts.WTSQuerySessionInformation(
|
||||
server, session_id, win32ts.WTSUserName
|
||||
)
|
||||
domain = win32ts.WTSQuerySessionInformation(
|
||||
server, session_id, win32ts.WTSDomainName
|
||||
)
|
||||
L'identité complète du compte est ensuite résolue par GPResult :
|
||||
WSMANAGE30\\formation
|
||||
FORMATION\\sebastien
|
||||
|
||||
username = (username or "").strip()
|
||||
domain = (domain or "").strip()
|
||||
|
||||
if not username:
|
||||
continue
|
||||
|
||||
account = (
|
||||
"%s\\%s" % (domain, username)
|
||||
if domain else username
|
||||
)
|
||||
|
||||
if account not in users:
|
||||
users.append(account)
|
||||
|
||||
if len(users) > 1:
|
||||
raise Exception(
|
||||
"Plusieurs utilisateurs ont une session active : %s"
|
||||
% ", ".join(users)
|
||||
)
|
||||
|
||||
return users[0] if users else None
|
||||
|
||||
|
||||
def _write_user_audit(data):
|
||||
"""Enregistre un objet structure, sans conversion en chaine JSON.
|
||||
|
||||
La meme cle est utilisee pour les succes, avertissements et erreurs,
|
||||
afin de ne pas laisser un ancien resultat presente comme actuel.
|
||||
Returns:
|
||||
str ou None
|
||||
"""
|
||||
WAPT.write_audit_data_if_changed(
|
||||
"audit-gpresult",
|
||||
"user-gpo",
|
||||
data,
|
||||
keep_days=365,
|
||||
max_count=1
|
||||
|
||||
try:
|
||||
|
||||
output = run(
|
||||
r'quser.exe',
|
||||
timeout=30,
|
||||
return_stderr=False
|
||||
)
|
||||
|
||||
|
||||
def _xml_flag(element, tag):
|
||||
"""Lit un booleen XML ; None signifie que le champ est absent."""
|
||||
value = element.findtext(tag)
|
||||
|
||||
if value is None:
|
||||
if not output:
|
||||
return None
|
||||
|
||||
value = value.strip().lower()
|
||||
lines = output.splitlines()
|
||||
|
||||
if value in ("true", "1"):
|
||||
return True
|
||||
# La première ligne correspond aux en-têtes de colonnes.
|
||||
for line in lines[1:]:
|
||||
|
||||
if value in ("false", "0"):
|
||||
return False
|
||||
line = line.strip()
|
||||
|
||||
raise Exception(
|
||||
"Valeur XML inattendue pour %s : %s" % (tag, value)
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# quser peut préfixer la session courante avec ">".
|
||||
line = line.lstrip(">").strip()
|
||||
|
||||
# Prend en charge les sorties Windows en anglais et en français.
|
||||
if (
|
||||
" Active " not in " %s " % line
|
||||
and
|
||||
" Actif " not in " %s " % line
|
||||
):
|
||||
continue
|
||||
|
||||
parts = re.split(
|
||||
r"\s+",
|
||||
line
|
||||
)
|
||||
|
||||
if not parts:
|
||||
continue
|
||||
|
||||
def _parse_user_gpos(xml_path):
|
||||
"""Retourne les listes triees des GPO appliquees et non appliquees.
|
||||
# USERNAME correspond à la première colonne.
|
||||
username = parts[0].strip()
|
||||
|
||||
denied_gpos regroupe toutes les GPO non appliquees selon les indicateurs
|
||||
XML : desactivation, invalidite, filtrage ou refus d'acces.
|
||||
if username:
|
||||
return username
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print(
|
||||
"Unable to detect interactive user: %s"
|
||||
% e
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Recherche des données GPResult dans le XML
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _find_user_results(root):
|
||||
"""
|
||||
Recherche la section UserResults dans le document XML GPResult.
|
||||
"""
|
||||
root = ET.parse(xml_path).getroot()
|
||||
|
||||
# Neutraliser les espaces de noms pour lire les balises par leur nom.
|
||||
for element in root.iter():
|
||||
if isinstance(element.tag, str):
|
||||
element.tag = element.tag.rsplit("}", 1)[-1]
|
||||
|
||||
user_results = root.find(".//UserResults")
|
||||
if _clean_tag(element.tag) == "UserResults":
|
||||
return element
|
||||
|
||||
if user_results is None:
|
||||
raise Exception(
|
||||
"Le rapport XML ne contient aucune section UserResults."
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Analyse des GPO
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _get_gpos(scope_results):
|
||||
"""
|
||||
Extrait les noms des GPO appliquées et refusées depuis le RSoP USER.
|
||||
|
||||
Les structures GPO internes sans nom sont ignorées
|
||||
et les doublons sont supprimés.
|
||||
"""
|
||||
|
||||
applied = []
|
||||
denied = []
|
||||
|
||||
# Les references imbriquees ne sont pas recomptees comme des GPO.
|
||||
for gpo in user_results.findall("GPO"):
|
||||
name = (
|
||||
gpo.findtext("Name")
|
||||
or gpo.findtext("Path/Identifier")
|
||||
or ""
|
||||
).strip()
|
||||
seen = set()
|
||||
|
||||
if not name:
|
||||
raise Exception(
|
||||
"GPO sans nom ni identifiant dans le rapport XML."
|
||||
if scope_results is None:
|
||||
return applied, denied
|
||||
|
||||
for element in scope_results.iter():
|
||||
|
||||
if _clean_tag(element.tag) != "GPO":
|
||||
continue
|
||||
|
||||
name = _text(
|
||||
element,
|
||||
"Name"
|
||||
)
|
||||
|
||||
enabled = _xml_flag(gpo, "Enabled")
|
||||
valid = _xml_flag(gpo, "IsValid")
|
||||
allowed = _xml_flag(gpo, "FilterAllowed")
|
||||
access_denied = _xml_flag(gpo, "AccessDenied")
|
||||
if not name:
|
||||
continue
|
||||
|
||||
enabled = _to_bool(
|
||||
_text(element, "Enabled")
|
||||
)
|
||||
|
||||
valid = _to_bool(
|
||||
_text(element, "IsValid")
|
||||
)
|
||||
|
||||
filter_allowed = _to_bool(
|
||||
_text(element, "FilterAllowed")
|
||||
)
|
||||
|
||||
access_denied = _to_bool(
|
||||
_text(element, "AccessDenied")
|
||||
)
|
||||
|
||||
version_directory = _text(
|
||||
element,
|
||||
"VersionDirectory"
|
||||
)
|
||||
|
||||
version_sysvol = _text(
|
||||
element,
|
||||
"VersionSysvol"
|
||||
)
|
||||
|
||||
identity = (
|
||||
name,
|
||||
version_directory,
|
||||
version_sysvol
|
||||
)
|
||||
|
||||
if identity in seen:
|
||||
continue
|
||||
|
||||
seen.add(identity)
|
||||
|
||||
if (
|
||||
enabled is False
|
||||
or valid is False
|
||||
or allowed is False
|
||||
or access_denied is True
|
||||
enabled
|
||||
and valid
|
||||
and filter_allowed
|
||||
and not access_denied
|
||||
):
|
||||
denied.append(name)
|
||||
|
||||
elif valid is True and allowed is True:
|
||||
applied.append(name)
|
||||
|
||||
else:
|
||||
# Un etat inconnu ne doit pas devenir un faux succes.
|
||||
raise Exception(
|
||||
"Etat de la GPO indeterminable : %s" % name
|
||||
|
||||
denied.append(name)
|
||||
|
||||
return applied, denied
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Analyse des groupes de sécurité
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _get_security_groups(scope_results, principal_name):
|
||||
"""
|
||||
Extrait et classe les groupes de sécurité utilisés dans le RSoP USER.
|
||||
|
||||
Catégories :
|
||||
|
||||
domain:
|
||||
Groupes dont le préfixe correspond au préfixe de domaine
|
||||
du compte utilisateur.
|
||||
|
||||
Exemple :
|
||||
FORMATION\\Domain Users
|
||||
|
||||
local:
|
||||
Groupes BUILTIN ou groupes appartenant au poste local.
|
||||
|
||||
Exemples :
|
||||
BUILTIN\\Users
|
||||
WSMANAGE30\\Administrators
|
||||
|
||||
system:
|
||||
Identités NT AUTHORITY.
|
||||
|
||||
Exemples :
|
||||
NT AUTHORITY\\Authenticated Users
|
||||
NT AUTHORITY\\INTERACTIVE
|
||||
|
||||
other:
|
||||
Identités de sécurité qui ne peuvent pas être classées
|
||||
de manière fiable.
|
||||
|
||||
Cette distinction évite notamment qu'un compte local tel que :
|
||||
|
||||
WSMANAGE30\\formation
|
||||
|
||||
soit considéré à tort comme un compte Active Directory.
|
||||
"""
|
||||
|
||||
groups = {
|
||||
"domain": [],
|
||||
"local": [],
|
||||
"system": [],
|
||||
"other": [],
|
||||
}
|
||||
|
||||
seen = set()
|
||||
|
||||
if scope_results is None:
|
||||
return groups
|
||||
|
||||
principal_domain, _ = _split_account(
|
||||
principal_name
|
||||
)
|
||||
|
||||
return sorted(applied), sorted(denied)
|
||||
principal_domain = principal_domain.upper()
|
||||
|
||||
computer_name = os.environ.get(
|
||||
"COMPUTERNAME",
|
||||
""
|
||||
).upper()
|
||||
|
||||
# Le préfixe du compte n'est considéré comme un domaine AD
|
||||
# que s'il est différent du nom du poste local.
|
||||
ad_domain = ""
|
||||
|
||||
if (
|
||||
principal_domain
|
||||
and principal_domain != computer_name
|
||||
):
|
||||
ad_domain = principal_domain
|
||||
|
||||
for element in scope_results.iter():
|
||||
|
||||
if _clean_tag(element.tag) != "SecurityGroup":
|
||||
continue
|
||||
|
||||
name = _text(
|
||||
element,
|
||||
"Name"
|
||||
)
|
||||
|
||||
if not name:
|
||||
continue
|
||||
|
||||
name = name.strip()
|
||||
|
||||
key = name.lower()
|
||||
|
||||
if key in seen:
|
||||
continue
|
||||
|
||||
seen.add(key)
|
||||
|
||||
upper_name = name.upper()
|
||||
|
||||
if upper_name.startswith(
|
||||
"NT AUTHORITY\\"
|
||||
):
|
||||
|
||||
groups["system"].append(name)
|
||||
|
||||
elif upper_name.startswith(
|
||||
"BUILTIN\\"
|
||||
):
|
||||
|
||||
groups["local"].append(name)
|
||||
|
||||
elif (
|
||||
computer_name
|
||||
and upper_name.startswith(
|
||||
computer_name + "\\"
|
||||
)
|
||||
):
|
||||
|
||||
groups["local"].append(name)
|
||||
|
||||
elif (
|
||||
ad_domain
|
||||
and upper_name.startswith(
|
||||
ad_domain + "\\"
|
||||
)
|
||||
):
|
||||
|
||||
groups["domain"].append(name)
|
||||
|
||||
else:
|
||||
|
||||
groups["other"].append(name)
|
||||
|
||||
# Garantit un ordre stable dans les résultats.
|
||||
for category in groups:
|
||||
|
||||
groups[category] = sorted(
|
||||
groups[category],
|
||||
key=lambda x: x.lower()
|
||||
)
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Audit WAPT
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def audit():
|
||||
"""Collecte le GPResult utilisateur et retourne OK, WARNING ou ERROR."""
|
||||
user = None
|
||||
computer = {
|
||||
"name": os.environ.get("COMPUTERNAME", "")
|
||||
}
|
||||
"""
|
||||
Exécute GPResult pour le contexte USER de l'utilisateur interactif actif.
|
||||
|
||||
try:
|
||||
user = _get_logged_on_user()
|
||||
Déroulement :
|
||||
1. Détecte la session utilisateur Windows active.
|
||||
2. Exécute GPResult pour le RSoP USER de ce compte.
|
||||
3. Analyse les GPO appliquées et refusées.
|
||||
4. Extrait et classe les groupes de sécurité.
|
||||
5. Publie le résultat structuré dans les données d'audit WAPT.
|
||||
|
||||
if not user:
|
||||
data = {
|
||||
"status": "WARNING",
|
||||
"message": "Aucun utilisateur avec une session active.",
|
||||
"computer": computer,
|
||||
"user": None
|
||||
}
|
||||
L'absence d'utilisateur interactif est considérée comme une situation
|
||||
normale. Dans ce cas, l'audit WAPT retourne OK avec un statut
|
||||
fonctionnel NO_USER.
|
||||
"""
|
||||
|
||||
_write_user_audit(data)
|
||||
print(data["message"])
|
||||
return "WARNING"
|
||||
|
||||
print("Audit GPResult utilisateur : %s" % user)
|
||||
|
||||
# Dossier unique, automatiquement supprime a la sortie du bloc,
|
||||
# y compris si l'export ou l'analyse XML echoue.
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="wapt_gpresult_user_"
|
||||
) as temp_dir:
|
||||
xml_path = os.path.join(temp_dir, "gpresult.xml")
|
||||
|
||||
# Conserver l'appel direct valide sur l'environnement cible.
|
||||
# /USER cible les donnees de l'utilisateur ; /SCOPE USER
|
||||
# exclut les GPO machine ; /X exporte les donnees en XML.
|
||||
command = (
|
||||
'gpresult.exe /USER "%s" /SCOPE USER /X "%s" /F'
|
||||
% (user, xml_path)
|
||||
xml_file = os.path.join(
|
||||
tempfile.gettempdir(),
|
||||
"wapt-gpresult-user.xml"
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
# WAPT s'exécute en tant que service.
|
||||
# Il faut donc identifier explicitement l'utilisateur interactif.
|
||||
logged_on_user = _get_logged_on_user()
|
||||
|
||||
if not logged_on_user:
|
||||
|
||||
audit_data = {
|
||||
|
||||
"status": "NO_USER",
|
||||
|
||||
"message":
|
||||
"No active interactive user session",
|
||||
|
||||
"user": None,
|
||||
|
||||
"applied_gpos": [],
|
||||
|
||||
"denied_gpos": [],
|
||||
|
||||
"domain_groups": [],
|
||||
|
||||
"security_groups": {
|
||||
"domain": [],
|
||||
"local": [],
|
||||
"system": [],
|
||||
"other": [],
|
||||
}
|
||||
}
|
||||
|
||||
WAPT.write_audit_data_if_changed(
|
||||
AUDIT_SECTION,
|
||||
AUDIT_NAME,
|
||||
audit_data,
|
||||
keep_days=365,
|
||||
max_count=1
|
||||
)
|
||||
|
||||
print("Commande : %s" % command)
|
||||
print(
|
||||
"Dossier temporaire present : %s"
|
||||
% os.path.isdir(temp_dir)
|
||||
"No active interactive user session"
|
||||
)
|
||||
|
||||
# L'absence d'utilisateur connecté n'est pas considérée
|
||||
# comme un échec de l'audit.
|
||||
return "OK"
|
||||
|
||||
print(
|
||||
"Active interactive user: %s"
|
||||
% logged_on_user
|
||||
)
|
||||
|
||||
# Supprime un éventuel rapport restant d'une exécution précédente.
|
||||
if os.path.isfile(xml_file):
|
||||
os.remove(xml_file)
|
||||
|
||||
gpresult_path = makepath(
|
||||
system32(),
|
||||
"gpresult.exe"
|
||||
)
|
||||
|
||||
# Le nom utilisateur retourné par quser suffit à GPResult
|
||||
# pour résoudre le compte local ou de domaine réellement associé
|
||||
# au RSoP.
|
||||
command = (
|
||||
'"%s" '
|
||||
'/USER "%s" '
|
||||
'/SCOPE USER '
|
||||
'/X "%s" '
|
||||
'/F'
|
||||
% (
|
||||
gpresult_path,
|
||||
logged_on_user,
|
||||
xml_file
|
||||
)
|
||||
)
|
||||
|
||||
print(
|
||||
"Running GPResult USER for %s..."
|
||||
% logged_on_user
|
||||
)
|
||||
|
||||
result = run(
|
||||
command,
|
||||
timeout=120,
|
||||
return_stderr=True
|
||||
return_stderr=False
|
||||
)
|
||||
|
||||
if result:
|
||||
print(result)
|
||||
if not os.path.isfile(xml_file):
|
||||
|
||||
if not os.path.isfile(xml_path):
|
||||
raise Exception(
|
||||
"GPResult n'a pas genere de rapport XML : %s"
|
||||
% xml_path
|
||||
"GPResult did not generate XML report. "
|
||||
"Output: %s"
|
||||
% result
|
||||
)
|
||||
|
||||
applied, denied = _parse_user_gpos(xml_path)
|
||||
tree = ET.parse(
|
||||
xml_file
|
||||
)
|
||||
|
||||
# Le domaine provient du compte WTS (souvent au format NetBIOS).
|
||||
domain, separator, username = user.partition("\\")
|
||||
root = tree.getroot()
|
||||
|
||||
if not separator:
|
||||
username = user
|
||||
domain = ""
|
||||
user_results = _find_user_results(
|
||||
root
|
||||
)
|
||||
|
||||
if user_results is None:
|
||||
|
||||
raise Exception(
|
||||
"UserResults section not found"
|
||||
)
|
||||
|
||||
applied_gpos, denied_gpos = _get_gpos(
|
||||
user_results
|
||||
)
|
||||
|
||||
# On laisse GPResult fournir l'identité de compte faisant foi.
|
||||
#
|
||||
# Exemples :
|
||||
# FORMATION\\sebastien
|
||||
# WSMANAGE30\\formation
|
||||
rsop_user = _text(
|
||||
user_results,
|
||||
"Name"
|
||||
)
|
||||
|
||||
if not rsop_user:
|
||||
rsop_user = logged_on_user
|
||||
|
||||
account_domain, username = _split_account(
|
||||
rsop_user
|
||||
)
|
||||
|
||||
security_groups = _get_security_groups(
|
||||
user_results,
|
||||
rsop_user
|
||||
)
|
||||
|
||||
computer_name = os.environ.get(
|
||||
"COMPUTERNAME",
|
||||
""
|
||||
)
|
||||
|
||||
# Détermine si GPResult a résolu le compte
|
||||
# comme compte local ou compte de domaine.
|
||||
if (
|
||||
not account_domain
|
||||
or account_domain.upper()
|
||||
== computer_name.upper()
|
||||
):
|
||||
account_type = "local"
|
||||
|
||||
else:
|
||||
account_type = "domain"
|
||||
|
||||
audit_data = {
|
||||
|
||||
data = {
|
||||
"status": "OK",
|
||||
"summary": {
|
||||
"total_gpo_count": len(applied) + len(denied),
|
||||
"denied_gpo_count": len(denied),
|
||||
"applied_gpo_count": len(applied)
|
||||
},
|
||||
"computer": computer,
|
||||
|
||||
"user": {
|
||||
"account": user,
|
||||
"name": username,
|
||||
"domain": domain
|
||||
|
||||
# Identité complète du compte telle que résolue par GPResult.
|
||||
"account": rsop_user,
|
||||
|
||||
"username": username,
|
||||
|
||||
# Préfixe au format NetBIOS :
|
||||
# nom de domaine pour un compte AD,
|
||||
# nom du poste pour un compte local.
|
||||
"account_domain": account_domain,
|
||||
|
||||
"account_type": account_type,
|
||||
},
|
||||
"denied_gpos": denied,
|
||||
"applied_gpos": applied
|
||||
|
||||
"summary": {
|
||||
|
||||
"applied_gpo_count": len(
|
||||
applied_gpos
|
||||
),
|
||||
|
||||
"denied_gpo_count": len(
|
||||
denied_gpos
|
||||
),
|
||||
|
||||
"total_gpo_count": (
|
||||
len(applied_gpos)
|
||||
+ len(denied_gpos)
|
||||
),
|
||||
|
||||
"domain_group_count": len(
|
||||
security_groups["domain"]
|
||||
),
|
||||
|
||||
"local_group_count": len(
|
||||
security_groups["local"]
|
||||
),
|
||||
|
||||
"system_group_count": len(
|
||||
security_groups["system"]
|
||||
),
|
||||
|
||||
"other_group_count": len(
|
||||
security_groups["other"]
|
||||
),
|
||||
},
|
||||
|
||||
# Des listes simples sont volontairement conservées
|
||||
# afin de faciliter l'exploitation en SQL / reporting.
|
||||
"applied_gpos": applied_gpos,
|
||||
|
||||
"denied_gpos": denied_gpos,
|
||||
|
||||
# Liste pratique contenant uniquement les groupes AD,
|
||||
# généralement les plus intéressants dans un audit GPO.
|
||||
"domain_groups":
|
||||
security_groups["domain"],
|
||||
|
||||
# Le contexte complet est conservé pour faciliter le diagnostic.
|
||||
"security_groups":
|
||||
security_groups,
|
||||
}
|
||||
|
||||
_write_user_audit(data)
|
||||
WAPT.write_audit_data_if_changed(
|
||||
AUDIT_SECTION,
|
||||
AUDIT_NAME,
|
||||
audit_data,
|
||||
keep_days=365,
|
||||
max_count=1
|
||||
)
|
||||
|
||||
print(
|
||||
"Audit disponible : audit-gpresult.user-gpo "
|
||||
"(%s appliquees, %s non appliquees)"
|
||||
% (len(applied), len(denied))
|
||||
"GPResult USER %s: "
|
||||
"%s applied / %s denied"
|
||||
% (
|
||||
rsop_user,
|
||||
len(applied_gpos),
|
||||
len(denied_gpos)
|
||||
)
|
||||
)
|
||||
|
||||
print(
|
||||
"Account type: %s"
|
||||
% account_type
|
||||
)
|
||||
|
||||
print(
|
||||
"AD groups: %s"
|
||||
% len(
|
||||
security_groups["domain"]
|
||||
)
|
||||
)
|
||||
|
||||
return "OK"
|
||||
|
||||
except Exception as e:
|
||||
# Remplacer le resultat precedent par l'erreur de cette collecte.
|
||||
data = {
|
||||
"status": "ERROR",
|
||||
"message": str(e),
|
||||
"computer": computer,
|
||||
"user": user
|
||||
}
|
||||
|
||||
_write_user_audit(data)
|
||||
print("Erreur audit GPResult utilisateur : %s" % e)
|
||||
# Conserve également l'erreur dans les données d'audit WAPT
|
||||
# afin qu'elle reste visible depuis la console.
|
||||
WAPT.write_audit_data_if_changed(
|
||||
AUDIT_SECTION,
|
||||
AUDIT_NAME,
|
||||
{
|
||||
"status": "ERROR",
|
||||
"message": str(e)
|
||||
},
|
||||
keep_days=365,
|
||||
max_count=1
|
||||
)
|
||||
|
||||
print(
|
||||
"GPResult USER audit error: %s"
|
||||
% str(e)
|
||||
)
|
||||
|
||||
return "ERROR"
|
||||
|
||||
finally:
|
||||
|
||||
# Le XML GPResult est uniquement utilisé comme fichier temporaire.
|
||||
try:
|
||||
|
||||
if os.path.isfile(xml_file):
|
||||
os.remove(xml_file)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
Reference in new issue
Block a user