Mengumpulkan log konteks Jamf Pro
Dokumen ini menjelaskan cara menyerap log konteks Jamf Pro (konteks perangkat & pengguna) ke (Google Security Operations) menggunakan AWS S3 menggunakan jadwal Lambda dan EventBridge.
Sebelum memulai
- Instance Google SecOps
 - Akses istimewa ke tenant Jamf Pro
 - Akses istimewa ke AWS (S3, IAM, Lambda, EventBridge)
 
Mengonfigurasi Peran Jamf API
- Login ke UI web Jamf.
 - Buka Setelan > Bagian Sistem > Peran dan Klien API.
 - Pilih tab Peran API.
 - Klik New.
 - Masukkan nama tampilan untuk peran API (misalnya, 
context_role). Di Hak istimewa peran Jamf Pro API, ketik nama hak istimewa, lalu pilih dari menu.
- Inventaris Komputer
 - Inventaris Perangkat Seluler
 
Klik Simpan.
Mengonfigurasi Klien API Jamf
- Di Jamf Pro, buka Settings > System section > API roles and clients.
 - Pilih tab Klien API.
 - Klik New.
 - Masukkan nama tampilan untuk klien API (misalnya, 
context_client). - Di kolom Peran API, tambahkan peran 
context_roleyang Anda buat sebelumnya. - Di bagian Masa Aktif Token Akses, masukkan waktu dalam detik agar token akses valid.
 - Klik Simpan.
 - Klik Edit.
 - Klik Enable API Client.
 - Klik Simpan.
 
Mengonfigurasi Rahasia Klien Jamf
- Di Jamf Pro, buka klien API yang baru dibuat.
 - Klik Generate Client Secret.
 - Di layar konfirmasi, klik Buat Secret.
 - Simpan parameter berikut di lokasi yang aman:
- URL Dasar: 
https://<your>.jamfcloud.com - Client ID: UUID.
 - Client Secret: Nilai ditampilkan satu kali.
 
 - URL Dasar: 
 
Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps
- Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket.
 - Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya, 
jamfpro). - Buat Pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
 - Pilih Pengguna yang dibuat.
 - Pilih tab Kredensial keamanan.
 - Klik Create Access Key di bagian Access Keys.
 - Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
 - Klik Berikutnya.
 - Opsional: Tambahkan tag deskripsi.
 - Klik Create access key.
 - Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk referensi di masa mendatang.
 - Klik Selesai.
 - Pilih tab Permissions.
 - Klik Tambahkan izin di bagian Kebijakan izin.
 - Pilih Tambahkan izin.
 - Pilih Lampirkan kebijakan secara langsung.
 - Telusuri dan pilih kebijakan AmazonS3FullAccess.
 - Klik Berikutnya.
 - Klik Add permissions.
 
Mengonfigurasi kebijakan dan peran IAM untuk upload S3
Policy JSON (ganti
jamfprojika Anda memasukkan nama bucket yang berbeda):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutJamfObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::jamfpro/*" } ] }Buka konsol AWS > IAM > Policies > Create policy > tab JSON.
Salin dan tempel kebijakan.
Klik Berikutnya > Buat kebijakan.
Buka IAM > Roles > Create role > AWS service > Lambda.
Lampirkan kebijakan yang baru dibuat.
Beri nama peran
WriteJamfToS3Role, lalu klik Buat peran.
Buat fungsi Lambda
- Di Konsol AWS, buka Lambda > Functions > Create function.
 - Klik Buat dari awal.
 - Berikan detail konfigurasi berikut:
 
| Setelan | Nilai | 
|---|---|
| Nama | jamf_pro_to_s3 | 
| Runtime | Python 3.13 | 
| Arsitektur | x86_64 | 
| Izin | WriteJamfToS3Role | 
Setelah fungsi dibuat, buka tab Code, hapus stub, dan masukkan kode berikut (
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}Buka Configuration > Environment variables > Edit > Add new environment variable.
Masukkan variabel lingkungan berikut, lalu ganti dengan nilai Anda.
Variabel lingkungan
Kunci Contoh S3_BUCKETjamfproS3_PREFIXjamf-pro/context/AWS_REGIONPilih Wilayah Anda JAMF_CLIENT_IDMasukkan ID Klien Jamf JAMF_CLIENT_SECRETMasukkan Rahasia Klien Jamf JAMF_BASE_URLMasukkan URL Jamf, ganti <your>dihttps://<your>.jamfcloud.comPAGE_SIZE200Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).
Pilih tab Configuration
Di panel General configuration, klik Edit.
Ubah Waktu tunggu menjadi 5 menit (300 detik), lalu klik Simpan.
Membuat jadwal EventBridge
- Buka Amazon EventBridge > Scheduler > Create schedule.
 - Berikan detail konfigurasi berikut:
- Jadwal berulang: Tarif (
1 hour). - Target: fungsi Lambda Anda.
 - Nama: 
jamfpro-context-schedule-1h. 
 - Jadwal berulang: Tarif (
 - Klik Buat jadwal.
 
Mengonfigurasi feed di Google SecOps untuk memproses log konteks Jamf Pro
- Buka Setelan SIEM > Feed.
 - Klik + Tambahkan Feed Baru.
 - Di kolom Nama feed, masukkan nama untuk feed (misalnya, 
Jamf Pro Context logs). - Pilih Amazon S3 V2 sebagai Jenis sumber.
 - Pilih Jamf pro context sebagai Log type.
 - Klik Berikutnya.
 - Tentukan nilai untuk parameter input berikut:
- URI S3: URI bucket 
s3://jamfpro/jamf-pro/context/- Ganti 
jamfprodengan nama bucket yang sebenarnya. 
- Ganti 
 
 - Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
 - Usia File Maksimum: Menyertakan file yang diubah dalam beberapa hari terakhir. Defaultnya adalah 180 Hari.
 - Access Key ID: Kunci akses pengguna dengan akses ke bucket s3.
 - Secret Access Key: Kunci rahasia pengguna dengan akses ke bucket s3.
 - Namespace aset: Namespace aset.
 - Label penyerapan: Label yang akan diterapkan ke peristiwa dari feed ini.
 
 - URI S3: URI bucket 
 - Klik Berikutnya.
 - Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.
 
Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.