Ajoute le script d'audit GPResult machine et sa documentation
This commit is contained in:
1 parent
fcd1a19cd9
commit
0fb81f2a9c
3 files changed
+386
-3
No files matched your search
@@ -0,0 +1,286 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Audit GPResult machine : resultat structure dans audit-gpresult.computer-gpo.
|
||||
|
||||
L'installation est vide. La collecte, l'analyse XML et l'enregistrement
|
||||
des donnees sont realises uniquement lors de l'execution de audit().
|
||||
"""
|
||||
|
||||
from setuphelpers import *
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
# Categorie et cle des donnees d'audit WAPT.
|
||||
AUDIT_SECTION = "audit-gpresult"
|
||||
AUDIT_NAME = "computer-gpo"
|
||||
|
||||
|
||||
def install():
|
||||
"""Point d'entree d'installation ; aucune configuration a appliquer."""
|
||||
pass
|
||||
|
||||
|
||||
def _clean_tag(tag):
|
||||
"""Retire l'espace de noms XML eventuel d'une balise."""
|
||||
return tag.split("}")[-1]
|
||||
|
||||
|
||||
def _text(element, tag, default=""):
|
||||
"""Lit le premier texte non vide d'une balise dans tout le sous-arbre.
|
||||
|
||||
La recherche est recursive et insensible a la casse. Si aucune valeur
|
||||
n'est trouvee, retourne la valeur par defaut.
|
||||
"""
|
||||
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 les valeurs reconnues en True ; toute autre valeur est False."""
|
||||
return str(value).strip().lower() in (
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
"oui"
|
||||
)
|
||||
|
||||
|
||||
def _find_computer_results(root):
|
||||
"""Recherche la section machine sans dependre des espaces de noms XML."""
|
||||
for element in root.iter():
|
||||
if _clean_tag(element.tag) == "ComputerResults":
|
||||
return element
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_gpos(computer_results):
|
||||
"""Classe les GPO machine en appliquees et non appliquees.
|
||||
|
||||
Les doublons sont elimines selon le triplet nom/version annuaire/
|
||||
version SYSVOL. Les GPO sans nom sont ignorees.
|
||||
"""
|
||||
applied = []
|
||||
denied = []
|
||||
|
||||
seen = set()
|
||||
|
||||
if computer_results is None:
|
||||
return applied, denied
|
||||
|
||||
# Conserver la recherche recursive de la version de reference.
|
||||
for element in computer_results.iter():
|
||||
|
||||
if _clean_tag(element.tag) != "GPO":
|
||||
continue
|
||||
|
||||
name = _text(element, "Name")
|
||||
|
||||
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)
|
||||
|
||||
# Une GPO est appliquee uniquement si les trois indicateurs
|
||||
# positifs sont vrais et si aucun refus d'acces n'est indique.
|
||||
# denied regroupe toutes les autres entrees retenues.
|
||||
if (
|
||||
enabled
|
||||
and valid
|
||||
and filter_allowed
|
||||
and not access_denied
|
||||
):
|
||||
applied.append(name)
|
||||
|
||||
else:
|
||||
denied.append(name)
|
||||
|
||||
return applied, denied
|
||||
|
||||
|
||||
def audit():
|
||||
"""Exporte le GPResult machine, enregistre les donnees et retourne un statut."""
|
||||
|
||||
# Nom temporaire fixe conserve de la version fournie.
|
||||
xml_file = os.path.join(
|
||||
tempfile.gettempdir(),
|
||||
"wapt-gpresult-computer.xml"
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
# Eviter de lire le rapport d'une execution precedente.
|
||||
if os.path.isfile(xml_file):
|
||||
os.remove(xml_file)
|
||||
|
||||
# /SCOPE COMPUTER cible les GPO machine ; /X produit le XML ;
|
||||
# /F autorise le remplacement du fichier de destination.
|
||||
command = '"%s" /SCOPE COMPUTER /X "%s" /F' % (
|
||||
makepath(system32(), "gpresult.exe"),
|
||||
xml_file
|
||||
)
|
||||
|
||||
print("Running GPResult COMPUTER...")
|
||||
|
||||
run(
|
||||
command,
|
||||
timeout=120,
|
||||
return_stderr=False
|
||||
)
|
||||
|
||||
if not os.path.isfile(xml_file):
|
||||
raise Exception(
|
||||
"GPResult did not generate XML report"
|
||||
)
|
||||
|
||||
tree = ET.parse(xml_file)
|
||||
root = tree.getroot()
|
||||
|
||||
computer_results = _find_computer_results(root)
|
||||
|
||||
if computer_results is None:
|
||||
raise Exception(
|
||||
"ComputerResults section not found"
|
||||
)
|
||||
|
||||
applied_gpos, denied_gpos = _get_gpos(
|
||||
computer_results
|
||||
)
|
||||
|
||||
# Essayer DomainName, puis Domain si aucune valeur n'est trouvee.
|
||||
domain = _text(
|
||||
computer_results,
|
||||
"DomainName"
|
||||
)
|
||||
|
||||
if not domain:
|
||||
domain = _text(
|
||||
computer_results,
|
||||
"Domain"
|
||||
)
|
||||
|
||||
audit_data = {
|
||||
|
||||
"status": "OK",
|
||||
|
||||
"computer": {
|
||||
"name": os.environ.get(
|
||||
"COMPUTERNAME",
|
||||
""
|
||||
),
|
||||
"domain": domain,
|
||||
"site": _text(
|
||||
computer_results,
|
||||
"Site"
|
||||
),
|
||||
},
|
||||
|
||||
"summary": {
|
||||
"applied_gpo_count": len(applied_gpos),
|
||||
"denied_gpo_count": len(denied_gpos),
|
||||
"total_gpo_count": (
|
||||
len(applied_gpos)
|
||||
+ len(denied_gpos)
|
||||
),
|
||||
},
|
||||
|
||||
"applied_gpos": applied_gpos,
|
||||
|
||||
"denied_gpos": denied_gpos,
|
||||
}
|
||||
|
||||
# Transmettre le dictionnaire directement pour conserver une
|
||||
# donnee structuree, plutot qu'une chaine de texte JSON.
|
||||
WAPT.write_audit_data_if_changed(
|
||||
AUDIT_SECTION,
|
||||
AUDIT_NAME,
|
||||
audit_data,
|
||||
keep_days=365,
|
||||
max_count=1
|
||||
)
|
||||
|
||||
print(
|
||||
"GPResult COMPUTER: %s applied / %s denied"
|
||||
% (
|
||||
len(applied_gpos),
|
||||
len(denied_gpos)
|
||||
)
|
||||
)
|
||||
|
||||
return "OK"
|
||||
|
||||
except Exception as e:
|
||||
|
||||
# La meme cle est mise a jour avec l'erreur : les anciennes
|
||||
# listes ne sont pas recopiees comme resultat de cet audit.
|
||||
WAPT.write_audit_data_if_changed(
|
||||
AUDIT_SECTION,
|
||||
AUDIT_NAME,
|
||||
{
|
||||
"status": "ERROR",
|
||||
"message": str(e)
|
||||
},
|
||||
keep_days=365,
|
||||
max_count=1
|
||||
)
|
||||
|
||||
print(
|
||||
"GPResult COMPUTER audit error: %s"
|
||||
% str(e)
|
||||
)
|
||||
|
||||
return "ERROR"
|
||||
|
||||
finally:
|
||||
|
||||
# Nettoyage tente apres succes comme apres erreur.
|
||||
# Un echec de suppression est ignore dans la version conservee.
|
||||
try:
|
||||
if os.path.isfile(xml_file):
|
||||
os.remove(xml_file)
|
||||
except:
|
||||
pass
|
||||
Reference in new issue
Block a user