Recopilar registros de contexto de Jamf Pro
En este documento se explica cómo ingerir registros de contexto de Jamf Pro (contexto de dispositivo y de usuario) en Google Security Operations mediante AWS S3 con la programación de Lambda y EventBridge.
Antes de empezar
- Instancia de Google SecOps
- Acceso privilegiado al arrendatario de Jamf Pro
- Acceso privilegiado a AWS (S3, IAM, Lambda y EventBridge)
Configurar el rol de API de Jamf
- Inicia sesión en la interfaz web de Jamf.
- Ve a Configuración > sección Sistema > Roles y clientes de API.
- Selecciona la pestaña Roles de API.
- Haz clic en New (Nuevo).
- Introduce un nombre visible para el rol de la API (por ejemplo,
context_role
). En Privilegios del rol de la API de Jamf Pro, escribe el nombre de un privilegio y, a continuación, selecciónalo en el menú.
- Inventario de ordenadores
- Inventario de dispositivos móviles
Haz clic en Guardar.
Configurar el cliente de la API de Jamf
- En Jamf Pro, ve a Ajustes > sección Sistema > Roles y clientes de API.
- Seleccione la pestaña Clientes de API.
- Haz clic en New (Nuevo).
- Introduce un nombre visible para el cliente de la API (por ejemplo,
context_client
). - En el campo Roles de la API, añade el rol
context_role
que has creado anteriormente. - En Tiempo de vida del token de acceso, introduce el tiempo en segundos durante el que serán válidos los tokens de acceso.
- Haz clic en Guardar.
- Haz clic en Editar.
- Haz clic en Habilitar cliente de API.
- Haz clic en Guardar.
Configurar el secreto de cliente de Jamf
- En Jamf Pro, ve al cliente de API que acabas de crear.
- Haz clic en Generar secreto de cliente.
- En la pantalla de confirmación, haz clic en Crear secreto.
- Guarda los siguientes parámetros en un lugar seguro:
- URL base:
https://<your>.jamfcloud.com
- ID de cliente: UUID.
- Secreto de cliente: el valor se muestra una vez.
- URL base:
Configurar un segmento de AWS S3 y IAM para Google SecOps
- Crea un segmento de Amazon S3 siguiendo esta guía de usuario: Crear un segmento.
- Guarda el nombre y la región del segmento para consultarlos más adelante (por ejemplo,
jamfpro
). - Crea un usuario siguiendo esta guía: Crear un usuario de gestión de identidades y accesos.
- Selecciona el usuario creado.
- Selecciona la pestaña Credenciales de seguridad.
- En la sección Claves de acceso, haz clic en Crear clave de acceso.
- Selecciona Servicio de terceros como Caso práctico.
- Haz clic en Siguiente.
- Opcional: añade una etiqueta de descripción.
- Haz clic en Crear clave de acceso.
- Haz clic en Descargar archivo CSV para guardar la clave de acceso y la clave de acceso secreta para futuras consultas.
- Haz clic en Listo.
- Selecciona la pestaña Permisos.
- Haz clic en Añadir permisos en la sección Políticas de permisos.
- Selecciona Añadir permisos.
- Seleccione Adjuntar políticas directamente.
- Busca y selecciona la política AmazonS3FullAccess.
- Haz clic en Siguiente.
- Haz clic en Añadir permisos.
Configurar la política y el rol de gestión de identidades y accesos para las subidas de S3
JSON de la política (sustituye
jamfpro
si has introducido otro nombre de contenedor):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutJamfObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::jamfpro/*" } ] }
Ve a la consola de AWS > IAM > Políticas > Crear política > pestaña JSON.
Copia y pega la política.
Haz clic en Siguiente > Crear política.
Ve a IAM > Roles > Crear rol > Servicio de AWS > Lambda.
Adjunte la política que acaba de crear.
Dale el nombre
WriteJamfToS3Role
al rol y haz clic en Crear rol.
Crear la función Lambda
- En la consola de AWS, ve a Lambda > Funciones > Crear función.
- Haz clic en Crear desde cero.
- Proporciona los siguientes detalles de configuración:
Ajuste | Valor |
---|---|
Nombre | jamf_pro_to_s3 |
Tiempo de ejecución | Python 3.13 |
Arquitectura | x86_64 |
Permisos | WriteJamfToS3Role |
Una vez creada la función, abra la pestaña Código, elimine el stub e introduzca el siguiente código (
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}
Ve a Configuración > Variables de entorno > Editar > Añadir nueva variable de entorno.
Introduce las siguientes variables de entorno y sustitúyelas por tus valores.
Variables de entorno
Clave Ejemplo S3_BUCKET
jamfpro
S3_PREFIX
jamf-pro/context/
AWS_REGION
Selecciona tu región JAMF_CLIENT_ID
Introduce el ID de cliente de Jamf JAMF_CLIENT_SECRET
Introduce el secreto de cliente de Jamf JAMF_BASE_URL
Introduce la URL de Jamf y sustituye <your>
enhttps://<your>.jamfcloud.com
.PAGE_SIZE
200
Una vez creada la función, permanece en su página (o abre Lambda > Funciones > tu-función).
Seleccione la pestaña Configuración.
En el panel Configuración general, haz clic en Editar.
Cambia Tiempo de espera a 5 minutos (300 segundos) y haz clic en Guardar.
Crear una programación de EventBridge
- Ve a Amazon EventBridge > Scheduler > Create schedule (Amazon EventBridge > Programador > Crear programación).
- Proporcione los siguientes detalles de configuración:
- Programación periódica: tarifa (
1 hour
). - Destino: tu función Lambda.
- Nombre:
jamfpro-context-schedule-1h
.
- Programación periódica: tarifa (
- Haz clic en Crear programación.
Configurar un feed en Google SecOps para ingerir registros de contexto de Jamf Pro
- Ve a Configuración de SIEM > Feeds.
- Haz clic en + Añadir nuevo feed.
- En el campo Nombre del feed, introduce un nombre para el feed (por ejemplo,
Jamf Pro Context logs
). - Selecciona Amazon S3 V2 como Tipo de fuente.
- Selecciona Contexto de Jamf Pro como Tipo de registro.
- Haz clic en Siguiente.
- Especifique valores para los siguientes parámetros de entrada:
- URI de S3: el URI del contenedor
s3://jamfpro/jamf-pro/context/
- Sustituye
jamfpro
por el nombre real del segmento.
- Sustituye
- Opciones de eliminación de la fuente: selecciona la opción de eliminación que prefieras.
- Antigüedad máxima del archivo: incluye los archivos modificados en los últimos días. El valor predeterminado es 180 días.
- ID de clave de acceso: clave de acceso de usuario con acceso al segmento de S3.
- Clave de acceso secreta: clave secreta del usuario con acceso al segmento de S3.
- Espacio de nombres de recursos: el espacio de nombres de recursos.
- Etiquetas de ingestión: etiqueta que se aplicará a los eventos de este feed.
- URI de S3: el URI del contenedor
- Haz clic en Siguiente.
- Revise la nueva configuración del feed en la pantalla Finalizar y haga clic en Enviar.
¿Necesitas más ayuda? Recibe respuestas de los miembros de la comunidad y de los profesionales de Google SecOps.