Coletar registros de contexto do Jamf Pro
Este documento explica como ingerir registros de contexto do Jamf Pro (contexto de dispositivo e usuário) no Google Security Operations usando o Cloud Storage com funções do Cloud Run, Pub/Sub e Cloud Scheduler. O Jamf Pro é uma solução de gerenciamento abrangente para dispositivos Apple, que oferece inventário de dispositivos, contexto do usuário e recursos de gerenciamento de configuração.
Antes de começar
Verifique se você atende os seguintes pré-requisitos:
- Uma instância do Google SecOps
- Um projeto do Google Cloud com a API Storage Cloud ativada
- Permissões para criar e gerenciar buckets do Cloud Storage
- Permissões para gerenciar políticas do IAM em buckets do Cloud Storage
- Permissões para criar serviços do Cloud Run, tópicos do Pub/Sub e jobs do Cloud Scheduler
- Acesso privilegiado ao locatário do Jamf Pro
Configurar a função da API Jamf
- Faça login na UI da Web do Jamf.
- Acesse Configurações > seção "Sistema" > Funções e clientes da API.
- Selecione a guia Funções da API.
- Clique em Novo.
- Insira um nome de exibição para a função da API (por exemplo,
context_role). Em Privilégios de função da API Jamf Pro, digite o nome de um privilégio e selecione-o no menu:
- Inventário de computadores
- Inventário de dispositivos móveis
Clique em Salvar.
Configurar o cliente da API Jamf
- No Jamf Pro, acesse Configurações > seção "Sistema" > Funções e clientes da API.
- Selecione a guia Clientes da API.
- Clique em Novo.
- Insira um nome de exibição para o cliente da API (por exemplo,
context_client). - No campo Funções da API, adicione a função
context_roleque você criou anteriormente. - Em Ciclo de vida do token de acesso, insira o tempo em segundos para que os tokens de acesso sejam válidos.
- Clique em Salvar.
- Clique em Editar.
- Clique em Ativar cliente de API.
- Clique em Salvar.
Configurar a chave secreta do cliente Jamf
- No Jamf Pro, acesse o cliente de API recém-criado.
- Clique em Gerar chave secreta do cliente.
- Na tela de confirmação, clique em Criar Secret.
- Salve os seguintes parâmetros em um local seguro:
- URL base:
https://<your>.jamfcloud.com - ID do cliente: UUID.
- Chave secreta do cliente: o valor é mostrado apenas uma vez.
- URL base:
criar o bucket do Cloud Storage
- Acesse o console doGoogle Cloud .
- Selecione ou crie um projeto.
- No menu de navegação, acesse Cloud Storage > Buckets.
- Clique em Criar bucket.
Informe os seguintes detalhes de configuração:
Configuração Valor Nomeie seu bucket Insira um nome globalmente exclusivo, por exemplo, jamfpro.Tipo de local Escolha de acordo com suas necessidades (região, birregional, multirregional) Local Selecione o local (por exemplo, us-central1).Classe de armazenamento Padrão (recomendado para registros acessados com frequência) Controle de acesso Uniforme (recomendado) Ferramentas de proteção Opcional: ativar o controle de versões de objetos ou a política de retenção Clique em Criar.
Criar uma conta de serviço para a função do Cloud Run
A função do Cloud Run precisa de uma conta de serviço com permissões para gravar no bucket do Cloud Storage e ser invocada pelo Pub/Sub.
Criar conta de serviço
- No console, acesse IAM e administrador > Contas de serviço.
- Clique em Criar conta de serviço.
- Informe os seguintes detalhes de configuração:
- Nome da conta de serviço: insira
jamf-pro-collector-sa. - Descrição da conta de serviço: insira
Service account for Cloud Run function to collect Jamf Pro context logs.
- Nome da conta de serviço: insira
- Clique em Criar e continuar.
- Na seção Conceder acesso a essa conta de serviço ao projeto, adicione os seguintes papéis:
- Clique em Selecionar papel.
- Pesquise e selecione Administrador de objetos do Storage.
- Clique em + Adicionar outro papel.
- Pesquise e selecione Invocador do Cloud Run.
- Clique em + Adicionar outro papel.
- Pesquise e selecione Invocador de funções do Cloud.
- Clique em Continuar.
- Clique em Concluído.
Essas funções são necessárias para:
- Administrador de objetos do Storage: grava registros no bucket do Cloud Storage e gerencia arquivos de estado.
- Invocador do Cloud Run: permite que o Pub/Sub invoque a função.
- Invocador do Cloud Functions: permite a invocação de funções.
Conceder permissões do IAM no bucket do Cloud Storage
Conceda permissões de gravação à conta de serviço no bucket do Cloud Storage:
- Acesse Cloud Storage > Buckets.
- Clique no nome do bucket.
- Acesse a guia Permissões.
- Clique em Conceder acesso.
- Informe os seguintes detalhes de configuração:
- Adicionar principais: insira o e-mail da conta de serviço (por exemplo,
jamf-pro-collector-sa@PROJECT_ID.iam.gserviceaccount.com). - Atribuir papéis: selecione Administrador de objetos do Storage.
- Adicionar principais: insira o e-mail da conta de serviço (por exemplo,
- Clique em Salvar.
Criar tópico Pub/Sub
Crie um tópico do Pub/Sub em que o Cloud Scheduler vai publicar e a função do Cloud Run vai se inscrever.
- No console, acesse Pub/Sub > Tópicos.
- Selecione Criar tópico.
- Informe os seguintes detalhes de configuração:
- ID do tópico: insira
jamf-pro-context-trigger. - Não mude as outras configurações.
- ID do tópico: insira
- Clique em Criar.
Criar uma função do Cloud Run para coletar registros
A função do Cloud Run será acionada por mensagens do Pub/Sub do Cloud Scheduler para buscar registros da API Jamf Pro e gravá-los no Cloud Storage.
- No console, acesse o Cloud Run.
- Clique em Criar serviço.
- Selecione Função (use um editor in-line para criar uma função).
Na seção Configurar, forneça os seguintes detalhes de configuração:
Configuração Valor Nome do serviço jamf-pro-context-collectorRegião Selecione a região que corresponde ao seu bucket do Cloud Storage (por exemplo, us-central1).Ambiente de execução Selecione Python 3.12 ou uma versão mais recente. Na seção Gatilho (opcional):
- Clique em + Adicionar gatilho.
- Selecione Pub/Sub.
- Em Selecionar um tópico do Pub/Sub, escolha o tópico do Pub/Sub (
jamf-pro-context-trigger). - Clique em Salvar.
Na seção Autenticação:
- Selecione Exigir autenticação.
- Confira o Identity and Access Management (IAM).
Role a tela para baixo e abra Contêineres, rede, segurança.
Acesse a guia Segurança:
- Conta de serviço: selecione a conta de serviço (
jamf-pro-collector-sa).
- Conta de serviço: selecione a conta de serviço (
Acesse a guia Contêineres:
- Clique em Variáveis e secrets.
- Clique em + Adicionar variável para cada variável de ambiente:
Nome da variável Valor de exemplo GCS_BUCKETjamfproGCS_PREFIXjamf-pro/context/JAMF_CLIENT_IDInsira o ID do cliente do Jamf JAMF_CLIENT_SECRETInsira a chave secreta do cliente da Jamf JAMF_BASE_URLInsira o URL do Jamf e substitua <your>emhttps://<your>.jamfcloud.comPAGE_SIZE200Role a tela para baixo na guia Variáveis e secrets até Solicitações:
- Tempo limite da solicitação: insira
600segundos (10 minutos).
- Tempo limite da solicitação: insira
Acesse a guia Configurações em Contêineres:
- Na seção Recursos:
- Memória: selecione 512 MiB ou mais.
- CPU: selecione 1.
- Clique em Concluído.
- Na seção Recursos:
Role a tela para baixo até Ambiente de execução:
- Selecione Padrão (recomendado).
Na seção Escalonamento de revisão:
- Número mínimo de instâncias: insira
0. - Número máximo de instâncias: insira
100ou ajuste com base na carga esperada.
- Número mínimo de instâncias: insira
Clique em Criar.
Aguarde a criação do serviço (1 a 2 minutos).
Depois que o serviço for criado, o editor de código inline será aberto automaticamente.
Adicionar código da função
- Insira main em Ponto de entrada da função
No editor de código inline, crie dois arquivos:
- Primeiro arquivo: main.py::
import functions_framework from google.cloud import storage import json import os import urllib3 from datetime import datetime, timezone import gzip import io import time # Initialize HTTP client http = urllib3.PoolManager() # Initialize Storage client storage_client = storage.Client() # Configuration 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") GCS_BUCKET = os.environ.get("GCS_BUCKET") GCS_PREFIX = os.environ.get("GCS_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" ] # Cached access token, refreshed shortly before it expires _token_cache = {"value": None, "expires_at": 0.0} def get_token(): """OAuth2 client credentials > access_token""" url = f"{BASE_URL}/api/oauth/token" fields = { "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET } # encode_multipart=False is required. It sends a real # application/x-www-form-urlencoded body, which is what the Jamf token # endpoint expects. Without it, urllib3 sends a multipart/form-data body # and Jamf cannot read grant_type, client_id and client_secret. response = http.request( 'POST', url, fields=fields, encode_multipart=False, timeout=30.0 ) if response.status != 200: raise Exception(f"Failed to get token: {response.status} {response.data.decode('utf-8')}") data = json.loads(response.data.decode('utf-8')) return data["access_token"], int(data.get("expires_in", 1200)) def get_valid_token(): """Return a cached token, refreshing it before it expires.""" now = time.time() if _token_cache["value"] and now < _token_cache["expires_at"] - 60: return _token_cache["value"] token, ttl = get_token() _token_cache["value"] = token _token_cache["expires_at"] = now + ttl return token def fetch_page(token, page): """GET /api/v1/computers-inventory with sections & pagination""" url = f"{BASE_URL}/api/v1/computers-inventory" # Build query parameters params = [("page", str(page)), ("page-size", str(PAGE_SIZE))] params.extend([("section", s) for s in SECTIONS]) # Encode parameters query_string = "&".join([f"{k}={v}" for k, v in params]) full_url = f"{url}?{query_string}" headers = { "Authorization": f"Bearer {token}", "Accept": "application/json" } response = http.request( 'GET', full_url, headers=headers, timeout=60.0 ) if response.status != 200: raise Exception(f"Failed to fetch page {page}: {response.status} {response.data.decode('utf-8')}") return json.loads(response.data.decode('utf-8')) def write_ndjson_gz(objs, when): 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 = GCS_PREFIX.strip("/") + "/" if GCS_PREFIX else "" key = f"{prefix}{when:%Y/%m/%d}/jamf_pro_context_{int(when.timestamp())}.ndjson.gz" bucket = storage_client.bucket(GCS_BUCKET) blob = bucket.blob(key) blob.upload_from_file(buf, content_type="application/gzip") return key @functions_framework.cloud_event def main(cloud_event): """ Cloud Run function triggered by Pub/Sub to fetch Jamf Pro context logs and write to Cloud Storage. Each computer is written exactly as Jamf returns it: the inventory sections (general, hardware, operatingSystem, userAndLocation, and so on) are top-level keys on the record. The default parser reads this shape directly, so the records are not reshaped here. Args: cloud_event: CloudEvent object containing Pub/Sub message """ if not all([BASE_URL, CLIENT_ID, CLIENT_SECRET, GCS_BUCKET]): print("Error: Missing required environment variables") return try: page = 0 total = 0 batch = [] now = datetime.now(timezone.utc) while True: payload = fetch_page(get_valid_token(), page) results = payload.get("results") or [] if not results: break # Write the Jamf records as returned; do not rename or nest fields. batch.extend(results) total += len(results) if len(batch) >= 5000: key = write_ndjson_gz(batch, now) print(f"Wrote {len(batch)} records to gs://{GCS_BUCKET}/{key}") batch = [] if len(results) < PAGE_SIZE: break page += 1 if batch: key = write_ndjson_gz(batch, now) print(f"Wrote {len(batch)} records to gs://{GCS_BUCKET}/{key}") print(f"Successfully processed {total} total records") except Exception as e: print(f"Error processing Jamf Pro context logs: {str(e)}") raise- Segundo arquivo: requirements.txt:
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0Clique em Implantar para salvar e implantar a função.
Aguarde a conclusão da implantação (2 a 3 minutos).
Criar o job do Cloud Scheduler
O Cloud Scheduler vai publicar mensagens no tópico do Pub/Sub em intervalos regulares, acionando a função do Cloud Run.
- No console, acesse o Cloud Scheduler.
- Clique em Criar job.
Informe os seguintes detalhes de configuração:
Configuração Valor Nome jamfpro-context-schedule-1hRegião Selecione a mesma região da função do Cloud Run Frequência 0 * * * *(de hora em hora)Fuso horário Selecionar fuso horário (UTC recomendado) Tipo de destino Pub/Sub Tópico Selecione o tópico do Pub/Sub ( jamf-pro-context-trigger).Corpo da mensagem {}(objeto JSON vazio)Clique em Criar.
Testar o job do programador
- No console do Cloud Scheduler, encontre seu job.
- Clique em Forçar execução para acionar manualmente.
- Aguarde alguns segundos e acesse Cloud Run > Serviços > jamf-pro-context-collector > Registros.
- Verifique se a função foi executada com sucesso.
- Verifique o bucket do Cloud Storage para confirmar se os registros foram gravados.
Configurar um feed no Google SecOps para ingerir registros de contexto do Jamf Pro
- Acesse Configurações do SIEM > Feeds.
- Clique em Adicionar novo feed.
- Clique em Configurar um único feed.
- No campo Nome do feed, insira um nome para o feed (por exemplo,
Jamf Pro Context logs). - Selecione Google Cloud Storage V2 como o Tipo de origem.
- Selecione Contexto do Jamf Pro como o Tipo de registro.
Clique em Receber conta de serviço. Um e-mail exclusivo da conta de serviço é exibido, por exemplo:
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.comCopie o endereço de e-mail. Você vai usá-la na próxima etapa.
Clique em Próxima.
Especifique valores para os seguintes parâmetros de entrada:
URL do bucket de armazenamento: insira o URI do bucket do Cloud Storage com o caminho do prefixo:
gs://jamfpro/jamf-pro/context/Substitua
jamfpropelo nome real do bucket.
Opção de exclusão da fonte: selecione a opção de exclusão de acordo com sua preferência:
- Nunca: nunca exclui arquivos após as transferências (recomendado para testes).
- Excluir arquivos transferidos: exclui os arquivos após a transferência.
- Excluir arquivos transferidos e diretórios vazios: exclui arquivos e diretórios vazios após a transferência.
Idade máxima do arquivo: inclui arquivos modificados nos últimos dias. O padrão é de 180 dias.
Namespace do recurso: o namespace do recurso.
Rótulos de ingestão: o rótulo a ser aplicado aos eventos deste feed.
- Clique em Próxima.
- Revise a nova configuração do feed na tela Finalizar e clique em Enviar.
Conceder permissões do IAM à conta de serviço do Google SecOps
A conta de serviço do Google SecOps precisa do papel Leitor de objetos do Storage no seu bucket do Cloud Storage.
- Acesse Cloud Storage > Buckets.
- Clique no nome do bucket.
- Acesse a guia Permissões.
- Clique em Conceder acesso.
- Informe os seguintes detalhes de configuração:
- Adicionar participantes: cole o e-mail da conta de serviço do Google SecOps.
- Atribuir papéis: selecione Leitor de objetos do Storage.
Clique em Salvar.
Registro de alterações
Ver o registro de alterações deste analisador
Precisa de mais ajuda? Receba respostas de membros da comunidade e profissionais do Google SecOps.