Collecter les journaux de contexte Jamf Pro
Ce document explique comment ingérer les journaux de contexte Jamf Pro (contexte de l'appareil et de l'utilisateur) dans Google Security Operations à l'aide d'AWS S3 avec la planification Lambda et EventBridge.
Avant de commencer
- Instance Google SecOps
- Accès privilégié au locataire Jamf Pro
- Accès privilégié à AWS (S3, IAM, Lambda, EventBridge)
Configurer le rôle de l'API Jamf
- Connectez-vous à l'interface utilisateur Web Jamf.
- Accédez à Paramètres > section "Système" > Rôles et clients de l'API.
- Sélectionnez l'onglet Rôles de l'API.
- Cliquez sur New (Nouveau).
- Saisissez un nom à afficher pour le rôle d'API (par exemple,
context_role). Dans Droits du rôle d'API Jamf Pro, saisissez le nom d'un droit, puis sélectionnez-le dans le menu.
- Inventaire des ordinateurs
- Inventaire des appareils mobiles
Cliquez sur Enregistrer.
Configurer le client API Jamf
- Dans Jamf Pro, accédez à Paramètres > section "Système" > Rôles et clients de l'API.
- Sélectionnez l'onglet Clients de l'API.
- Cliquez sur New (Nouveau).
- Saisissez un nom à afficher pour le client API (par exemple,
context_client). - Dans le champ Rôles d'API, ajoutez le rôle
context_roleque vous avez créé précédemment. - Sous Durée de vie du jeton d'accès, saisissez la durée de validité des jetons d'accès en secondes.
- Cliquez sur Enregistrer.
- Cliquez sur Modifier.
- Cliquez sur Activer le client API.
- Cliquez sur Enregistrer.
Configurer le code secret du client Jamf
- Dans Jamf Pro, accédez au client API que vous venez de créer.
- Cliquez sur Générer le code secret du client.
- Sur l'écran de confirmation, cliquez sur Créer un secret.
- Enregistrez les paramètres suivants dans un emplacement sécurisé :
- URL de base :
https://<your>.jamfcloud.com - ID client : UUID.
- Code secret du client : la valeur n'est affichée qu'une seule fois.
- URL de base :
Configurer un bucket AWS S3 et IAM pour Google SecOps
- Créez un bucket Amazon S3 en suivant ce guide de l'utilisateur : Créer un bucket.
- Enregistrez le nom et la région du bucket pour référence ultérieure (par exemple,
jamfpro). - Créez un utilisateur en suivant ce guide de l'utilisateur : Créer un utilisateur IAM.
- Sélectionnez l'utilisateur créé.
- Sélectionnez l'onglet Informations d'identification de sécurité.
- Cliquez sur Créer une clé d'accès dans la section Clés d'accès.
- Sélectionnez Service tiers comme Cas d'utilisation.
- Cliquez sur Suivant.
- Facultatif : Ajoutez une balise de description.
- Cliquez sur Créer une clé d'accès.
- Cliquez sur Télécharger le fichier CSV pour enregistrer la clé d'accès et la clé d'accès secrète pour référence ultérieure.
- Cliquez sur OK.
- Sélectionnez l'onglet Autorisations.
- Cliquez sur Ajouter des autorisations dans la section Règles relatives aux autorisations.
- Sélectionnez Ajouter des autorisations.
- Sélectionnez Joindre directement des règles.
- Recherchez et sélectionnez la règle AmazonS3FullAccess.
- Cliquez sur Suivant.
- Cliquez sur Ajouter des autorisations.
Configurer la stratégie et le rôle IAM pour les importations S3
JSON de la règle (remplacez
jamfprosi vous avez saisi un autre nom de bucket) :{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutJamfObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::jamfpro/*" } ] }Accédez à la console AWS> IAM> Policies> Create policy> onglet JSON.
Copiez et collez le règlement.
Cliquez sur Suivant > Créer une règle.
Accédez à IAM > Rôles > Créer un rôle > Service AWS > Lambda.
Associez la règle nouvellement créée.
Nommez le rôle
WriteJamfToS3Role, puis cliquez sur Créer un rôle.
Créer la fonction Lambda
- Dans la console AWS, accédez à Lambda > Fonctions > Créer une fonction.
- Cliquez sur Créer à partir de zéro.
- Fournissez les informations de configuration suivantes :
| Paramètre | Valeur |
|---|---|
| Nom | jamf_pro_to_s3 |
| Durée d'exécution | Python 3.13 |
| Architecture | x86_64 |
| Autorisations | WriteJamfToS3Role |
Une fois la fonction créée, ouvrez l'onglet Code, supprimez le stub et saisissez le code suivant (
jamf_pro_to_s3.py) :import os import io import json import gzip import time import logging from datetime import datetime, timezone import boto3 import requests log = logging.getLogger() log.setLevel(logging.INFO) BASE_URL = os.environ.get("JAMF_BASE_URL", "").rstrip("/") CLIENT_ID = os.environ.get("JAMF_CLIENT_ID") CLIENT_SECRET = os.environ.get("JAMF_CLIENT_SECRET") S3_BUCKET = os.environ.get("S3_BUCKET") S3_PREFIX = os.environ.get("S3_PREFIX", "jamf-pro/context/") PAGE_SIZE = int(os.environ.get("PAGE_SIZE", "200")) SECTIONS = [ "GENERAL", "HARDWARE", "OPERATING_SYSTEM", "USER_AND_LOCATION", "DISK_ENCRYPTION", "SECURITY", "EXTENSION_ATTRIBUTES", "APPLICATIONS", "CONFIGURATION_PROFILES", "LOCAL_USER_ACCOUNTS", "CERTIFICATES", "SERVICES", "PRINTERS", "SOFTWARE_UPDATES", "GROUP_MEMBERSHIPS", "CONTENT_CACHING", "STORAGE", "FONTS", "PACKAGE_RECEIPTS", "PLUGINS", "ATTACHMENTS", "LICENSED_SOFTWARE", "IBEACONS", "PURCHASING", ] s3 = boto3.client("s3") def _now_iso(): return datetime.now(timezone.utc).isoformat() def get_token(): """OAuth2 client credentials > access_token""" url = f"{BASE_URL}/api/oauth/token" data = { "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, } headers = {"Content-Type": "application/x-www-form-urlencoded"} r = requests.post(url, data=data, headers=headers, timeout=30) r.raise_for_status() j = r.json() return j["access_token"], int(j.get("expires_in", 1200)) def fetch_page(token: str, page: int): """GET /api/v1/computers-inventory with sections & pagination""" url = f"{BASE_URL}/api/v1/computers-inventory" params = [("page", page), ("page-size", PAGE_SIZE)] + [("section", s) for s in SECTIONS] hdrs = {"Authorization": f"Bearer {token}", "Accept": "application/json"} r = requests.get(url, params=params, headers=hdrs, timeout=60) r.raise_for_status() return r.json() def to_context_event(item: dict) -> dict: inv = item.get("inventory", {}) or {} general = inv.get("general", {}) or {} hardware = inv.get("hardware", {}) or {} osinfo = inv.get("operatingSystem", {}) or {} loc = inv.get("location", {}) or inv.get("userAndLocation", {}) or {} computer = { "udid": general.get("udid") or hardware.get("udid"), "deviceName": general.get("name") or general.get("deviceName"), "serialNumber": hardware.get("serialNumber") or general.get("serialNumber"), "model": hardware.get("model") or general.get("model"), "osVersion": osinfo.get("version") or general.get("osVersion"), "osBuild": osinfo.get("build") or general.get("osBuild"), "macAddress": hardware.get("macAddress"), "alternateMacAddress": hardware.get("wifiMacAddress"), "ipAddress": general.get("ipAddress"), "reportedIpV4Address": general.get("reportedIpV4Address"), "reportedIpV6Address": general.get("reportedIpV6Address"), "modelIdentifier": hardware.get("modelIdentifier"), "assetTag": general.get("assetTag"), } user_block = { "userDirectoryID": loc.get("username") or loc.get("userDirectoryId"), "emailAddress": loc.get("emailAddress"), "realName": loc.get("realName"), "phone": loc.get("phone") or loc.get("phoneNumber"), "position": loc.get("position"), "department": loc.get("department"), "building": loc.get("building"), "room": loc.get("room"), } return { "webhook": {"name": "api.inventory"}, "event_type": "ComputerInventory", "event_action": "snapshot", "event_timestamp": _now_iso(), "event_data": { "computer": {k: v for k, v in computer.items() if v not in (None, "")}, **{k: v for k, v in user_block.items() if v not in (None, "")}, }, "_jamf": { "id": item.get("id"), "inventory": inv, }, } def write_ndjson_gz(objs, when: datetime): buf = io.BytesIO() with gzip.GzipFile(filename="-", mode="wb", fileobj=buf, mtime=int(time.time())) as gz: for obj in objs: line = json.dumps(obj, separators=(",", ":")) + "\n" gz.write(line.encode("utf-8")) buf.seek(0) prefix = S3_PREFIX.strip("/") + "/" if S3_PREFIX else "" key = f"{prefix}{when:%Y/%m/%d}/jamf_pro_context_{int(when.timestamp())}.ndjson.gz" s3.put_object(Bucket=S3_BUCKET, Key=key, Body=buf.getvalue()) return key def lambda_handler(event, context): assert BASE_URL and CLIENT_ID and CLIENT_SECRET and S3_BUCKET, "Missing required env vars" token, _ttl = get_token() page = 0 total = 0 batch = [] now = datetime.now(timezone.utc) while True: payload = fetch_page(token, page) results = payload.get("results") or payload.get("computerInventoryList") or [] if not results: break for item in results: batch.append(to_context_event(item)) total += 1 if len(batch) >= 5000: key = write_ndjson_gz(batch, now) log.info("wrote %s records to s3://%s/%s", len(batch), S3_BUCKET, key) batch = [] if len(results) < PAGE_SIZE: break page += 1 if batch: key = write_ndjson_gz(batch, now) log.info("wrote %s records to s3://%s/%s", len(batch), S3_BUCKET, key) return {"ok": True, "count": total}Accédez à Configuration > Variables d'environnement > Modifier > Ajouter une variable d'environnement.
Saisissez les variables d'environnement suivantes en remplaçant par vos valeurs.
Variables d'environnement
Clé Exemple S3_BUCKETjamfproS3_PREFIXjamf-pro/context/AWS_REGIONSélectionnez votre région JAMF_CLIENT_IDSaisissez l'ID client Jamf JAMF_CLIENT_SECRETSaisir le code secret du client Jamf JAMF_BASE_URLSaisissez l'URL Jamf, puis remplacez <your>danshttps://<your>.jamfcloud.com.PAGE_SIZE200Une fois la fonction créée, restez sur sa page (ou ouvrez Lambda > Fonctions > votre-fonction).
Accédez à l'onglet Configuration.
Dans le panneau Configuration générale, cliquez sur Modifier.
Définissez le délai avant expiration sur 5 minutes (300 secondes), puis cliquez sur Enregistrer.
Créer une programmation EventBridge
- Accédez à Amazon EventBridge> Scheduler> Create schedule.
- Fournissez les informations de configuration suivantes :
- Programmation périodique : tarif (
1 hour). - Cible : votre fonction Lambda.
- Nom :
jamfpro-context-schedule-1h.
- Programmation périodique : tarif (
- Cliquez sur Créer la programmation.
Configurer un flux dans Google SecOps pour ingérer les journaux de contexte Jamf Pro
- Accédez à Paramètres SIEM> Flux.
- Cliquez sur + Ajouter un flux.
- Dans le champ Nom du flux, saisissez un nom pour le flux (par exemple,
Jamf Pro Context logs). - Sélectionnez Amazon S3 V2 comme type de source.
- Sélectionnez Contexte Jamf Pro comme Type de journal.
- Cliquez sur Suivant.
- Spécifiez les valeurs des paramètres d'entrée suivants :
- URI S3 : URI du bucket
s3://jamfpro/jamf-pro/context/- Remplacez
jamfpropar le nom réel du bucket.
- Remplacez
- Options de suppression de la source : sélectionnez l'option de suppression de votre choix.
- Âge maximal des fichiers : incluez les fichiers modifiés au cours des derniers jours. La valeur par défaut est de 180 jours.
- ID de clé d'accès : clé d'accès utilisateur ayant accès au bucket S3.
- Clé d'accès secrète : clé secrète de l'utilisateur ayant accès au bucket S3.
- Espace de noms de l'élément : espace de noms de l'élément.
- Libellés d'ingestion : libellé à appliquer aux événements de ce flux.
- URI S3 : URI du bucket
- Cliquez sur Suivant.
- Vérifiez la configuration de votre nouveau flux sur l'écran Finaliser, puis cliquez sur Envoyer.
Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.