Coletar conjuntos de dados principais do Code42 Incydr

Versão do analisador:3.0

Compatível com:

Neste documento, explicamos como ingerir conjuntos de dados principais do Code42 Incydr (usuários, auditoria, casos e, opcionalmente, eventos de arquivo) no Google Security Operations usando o Google Cloud Storage.

O Code42 Incydr é uma solução de gerenciamento de riscos internos que detecta, investiga e responde à exfiltração de dados em dispositivos, monitorando a atividade de arquivos em tempo real em endpoints, serviços de nuvem e e-mails.

Antes de começar

Verifique se você atende os seguintes pré-requisitos:

  • Uma instância do Google SecOps
  • Um projeto do GCP com a API Storage do Cloud ativada
  • Permissões para criar e gerenciar intervalos do GCS
  • Permissões para gerenciar políticas do IAM em buckets do GCS
  • Permissões para criar serviços do Cloud Run, tópicos do Pub/Sub e jobs do Cloud Scheduler
  • Acesso privilegiado à API Incydr da Code42 ou ao Admin Console com a função de administrador de risco interno

Criar um bucket do Google Cloud Storage

  1. Acesse o Console do Google Cloud.
  2. Selecione ou crie um projeto.
  3. No menu de navegação, acesse Cloud Storage > Buckets.
  4. Clique em Criar bucket.
  5. Informe os seguintes detalhes de configuração:

    Configuração Valor
    Nomeie seu bucket Insira um nome globalmente exclusivo (por exemplo, code42-incydr-logs)
    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
  6. Clique em Criar.

Coletar credenciais da API Code42 Incydr

Criar cliente de API

  1. Faça login no console da Web do Code42 Incydr.
  2. Acesse Administration > Integrations > API Clients.
  3. Clique em Criar novo cliente de API.
  4. Na caixa de diálogo Criar novo cliente da API, insira um nome para o cliente (por exemplo, Google Security Operations Integration).
  5. Copie e salve os seguintes detalhes em um local seguro:
    • ID do cliente: o identificador do cliente da API.
    • Secret: a chave secreta do cliente da API.
  6. Clique em Criar.

Determinar o URL de base da API

O URL de base da API depende do URL do console do Code42 Incydr. Verifique o URL do gateway de API no portal do desenvolvedor do Incydr ou na documentação do ambiente do seu locatário.

  • Padrões comuns:

    URL do console URL de base da API
    https://console.us.code42.com https://api.us.code42.com
    https://console.us2.code42.com https://api.us2.code42.com
    https://console.ie.code42.com https://api.ie.code42.com
    https://console.gov.code42.com https://api.gov.code42.com

Verificar as permissões do cliente de API

O cliente da API precisa ter as permissões adequadas para acessar os endpoints necessários:

  1. No console do Code42 Incydr, acesse Administração > Integrações > Clientes da API.
  2. Clique no nome do cliente de API que você criou.
  3. Verifique se o cliente da API tem acesso aos seguintes escopos:

    • Usuários: acesso de leitura aos dados do usuário
    • Registro de auditoria: acesso de leitura aos registros de auditoria
    • Casos: acesso de leitura aos dados do caso
    • Eventos de arquivo (opcional): acesso de leitura aos dados de eventos de arquivo.

Testar o acesso à API

  • Teste suas credenciais antes de prosseguir com a integração:

    # Replace with your actual credentials
    CLIENT_ID="your-client-id"
    CLIENT_SECRET="your-client-secret"
    API_BASE="https://api.us.code42.com"
    
    # Get OAuth token
    TOKEN=$(curl -s -X POST "${API_BASE}/v1/oauth/token" \
      -u "${CLIENT_ID}:${CLIENT_SECRET}" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=client_credentials" | jq -r '.access_token')
    
    # Test API access
    curl -v -H "Authorization: Bearer ${TOKEN}" "${API_BASE}/v1/users?pageSize=1"
    

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 GCS e ser invocada pelo Pub/Sub.

Criar conta de serviço

  1. No console do GCP, acesse IAM e administrador > Contas de serviço.
  2. Clique em Criar conta de serviço.
  3. Informe os seguintes detalhes de configuração:
    • Nome da conta de serviço: insira code42-incydr-collector-sa.
    • Descrição da conta de serviço: insira Service account for Cloud Run function to collect Code42 Incydr logs.
  4. Clique em Criar e continuar.
  5. Na seção Conceder acesso a essa conta de serviço ao projeto, adicione os seguintes papéis:
    1. Clique em Selecionar papel.
    2. Pesquise e selecione Administrador de objetos do Storage.
    3. Clique em + Adicionar outro papel.
    4. Pesquise e selecione Invocador do Cloud Run.
    5. Clique em + Adicionar outro papel.
    6. Pesquise e selecione Invocador de funções do Cloud.
  6. Clique em Continuar.
  7. Clique em Concluído.

Essas funções são necessárias para:

  • Administrador de objetos do Storage: gravar registros no bucket do GCS e gerenciar 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 GCS

Conceda à conta de serviço permissões de gravação no bucket do GCS:

  1. Acesse Cloud Storage > Buckets.
  2. Clique no nome do bucket (por exemplo, code42-incydr-logs).
  3. Acesse a guia Permissões.
  4. Clique em Conceder acesso.
  5. Informe os seguintes detalhes de configuração:
    • Adicionar principais: insira o e-mail da conta de serviço (por exemplo, code42-incydr-collector-sa@your-project.iam.gserviceaccount.com).
    • Atribuir papéis: selecione Administrador de objetos do Storage.
  6. 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.

  1. No Console do GCP, acesse Pub/Sub > Tópicos.
  2. Selecione Criar tópico.
  3. Informe os seguintes detalhes de configuração:
    • ID do tópico: insira code42-incydr-trigger.
    • Não mude as outras configurações.
  4. Clique em Criar.

Criar uma função do Cloud Run para coletar registros

A função do Cloud Run é acionada por mensagens do Pub/Sub do Cloud Scheduler para buscar registros da API Code42 Incydr e gravá-los no GCS.

  1. No Console do GCP, acesse o Cloud Run.
  2. Clique em Criar serviço.
  3. Selecione Função (use um editor in-line para criar uma função).
  4. Na seção Configurar, forneça os seguintes detalhes de configuração:

    Configuração Valor
    Nome do serviço code42-incydr-collector
    Região Selecione a região correspondente ao seu bucket do GCS (por exemplo, us-central1).
    Ambiente de execução Selecione Python 3.12 ou uma versão mais recente.
  5. Na seção Acionador (opcional):

    1. Clique em + Adicionar gatilho.
    2. Selecione Cloud Pub/Sub.
    3. Em Selecionar um tópico do Cloud Pub/Sub, escolha o tópico code42-incydr-trigger.
    4. Clique em Salvar.
  6. Na seção Autenticação:

    1. Selecione Exigir autenticação.
    2. Confira o Identity and Access Management (IAM).
  7. Role para baixo e expanda Contêineres, rede, segurança.

  8. Acesse a guia Segurança:

    • Conta de serviço: selecione a conta de serviço code42-incydr-collector-sa.
  9. Acesse a guia Contêineres:

    1. Clique em Variáveis e secrets.
    2. Clique em + Adicionar variável para cada variável de ambiente:
    Nome da variável Valor de exemplo Descrição
    INCYDR_BASE_URL https://api.us.code42.com URL de base da API do seu locatário
    INCYDR_CLIENT_ID your-client-id ID do cliente da API
    INCYDR_CLIENT_SECRET your-client-secret Chave secreta do cliente da API
    GCS_BUCKET code42-incydr-logs Nome do bucket do GCS
    GCS_PREFIX code42/ Prefixo para arquivos de registro
    PAGE_SIZE 500 Registros por página
    LOOKBACK_MINUTES 60 Período de lookback inicial
    STREAMS users,audit,cases Fluxos de dados separados por vírgulas
    FE_QUERY_JSON `` Opcional: JSON de consulta de eventos de arquivo
    FE_PAGE_SIZE 1000 Opcional: tamanho da página de eventos de arquivo
  10. Role para baixo na guia Variáveis e secrets até Solicitações:

    • Tempo limite da solicitação: insira 600 segundos (10 minutos).
  11. Acesse a guia Configurações em Contêineres:

    • Na seção Recursos:
      • Memória: selecione 1024 MiB ou mais.
      • CPU: selecione 1.
    • Clique em Concluído.
  12. Role até Ambiente de execução:

    • Selecione Padrão (recomendado).
  13. Na seção Escalonamento de revisão:

    • Número mínimo de instâncias: insira 0.
    • Número máximo de instâncias: insira 100 ou ajuste com base na carga esperada.
  14. Clique em Criar.

  15. Aguarde a criação do serviço (1 a 2 minutos).

  16. Depois que o serviço for criado, o editor de código inline será aberto automaticamente.

Adicionar código da função

  1. Insira main em Ponto de entrada da função
  2. 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, timedelta, timezone
    import time
    
    # Initialize HTTP client
    http = urllib3.PoolManager(
        timeout=urllib3.Timeout(connect=5.0, read=30.0),
        retries=False,
    )
    
    # Initialize Storage client
    storage_client = storage.Client()
    
    # Environment variables
    BASE = os.environ.get("INCYDR_BASE_URL", "").rstrip("/")
    CID = os.environ.get("INCYDR_CLIENT_ID", "")
    CSECRET = os.environ.get("INCYDR_CLIENT_SECRET", "")
    BUCKET = os.environ.get("GCS_BUCKET", "")
    PREFIX_BASE = os.environ.get("GCS_PREFIX", "code42/")
    PAGE_SIZE = int(os.environ.get("PAGE_SIZE", "500"))
    LOOKBACK_MINUTES = int(os.environ.get("LOOKBACK_MINUTES", "60"))
    STREAMS = [s.strip() for s in os.environ.get("STREAMS", "users").split(",") if s.strip()]
    FE_QUERY_JSON = os.environ.get("FE_QUERY_JSON", "").strip()
    FE_PAGE_SIZE = int(os.environ.get("FE_PAGE_SIZE", "1000"))
    
    def now_utc():
        return datetime.now(timezone.utc)
    
    def iso_minus(minutes: int):
        return (now_utc() - timedelta(minutes=minutes)).strftime("%Y-%m-%dT%H:%M:%SZ")
    
    def put_json(bucket, prefix: str, page_label: str, data):
        ts = now_utc().strftime("%Y/%m/%d/%H%M%S")
        key = f"{PREFIX_BASE}{prefix}{ts}-{page_label}.json"
        blob = bucket.blob(key)
        blob.upload_from_string(json.dumps(data), content_type='application/json')
        return key
    
    def get_token():
        """Get OAuth 2.0 access token using client credentials flow."""
        token_url = f"{BASE}/v1/oauth/token"
    
        # Encode credentials
        import base64
        credentials = f"{CID}:{CSECRET}"
        encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
    
        headers = {
            'Authorization': f'Basic {encoded_credentials}',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'application/json'
        }
    
        body = 'grant_type=client_credentials'
    
        backoff = 1.0
        max_retries = 3
    
        for attempt in range(max_retries):
            response = http.request('POST', token_url, body=body, headers=headers)
    
            if response.status == 429:
                retry_after = int(response.headers.get('Retry-After', str(int(backoff))))
                print(f"Rate limited (429) on token request. Retrying after {retry_after}s...")
                time.sleep(retry_after)
                backoff = min(backoff * 2, 30.0)
                continue
    
            if response.status != 200:
                raise RuntimeError(f"Failed to get access token: {response.status} - {response.data.decode('utf-8')}")
    
            data = json.loads(response.data.decode('utf-8'))
            return data['access_token']
    
        raise RuntimeError(f"Failed to get token after {max_retries} retries due to rate limiting")
    
    def auth_header():
        token = get_token()
        return {
            "Authorization": f"Bearer {token}",
            "Accept": "application/json"
        }
    
    def http_get(path: str, params: dict = None, headers: dict = None):
        url = f"{BASE}{path}"
        if params:
            from urllib.parse import urlencode
            url += "?" + urlencode(params)
    
        backoff = 1.0
        max_retries = 3
    
        for attempt in range(max_retries):
            response = http.request('GET', url, headers=headers)
    
            if response.status == 429:
                retry_after = int(response.headers.get('Retry-After', str(int(backoff))))
                print(f"Rate limited (429). Retrying after {retry_after}s...")
                time.sleep(retry_after)
                backoff = min(backoff * 2, 30.0)
                continue
    
            return response.data
    
        raise RuntimeError(f"Failed after {max_retries} retries due to rate limiting")
    
    def http_post_json(path: str, body: dict, headers: dict = None):
        url = f"{BASE}{path}"
    
        backoff = 1.0
        max_retries = 3
    
        for attempt in range(max_retries):
            response = http.request(
                'POST',
                url,
                body=json.dumps(body),
                headers={**headers, 'Content-Type': 'application/json'}
            )
    
            if response.status == 429:
                retry_after = int(response.headers.get('Retry-After', str(int(backoff))))
                print(f"Rate limited (429). Retrying after {retry_after}s...")
                time.sleep(retry_after)
                backoff = min(backoff * 2, 30.0)
                continue
    
            return response.data
    
        raise RuntimeError(f"Failed after {max_retries} retries due to rate limiting")
    
    # USERS (/v1/users)
    def pull_users(bucket, hdrs):
        next_token = None
        pages = 0
        while True:
            params = {"active": "true", "blocked": "false", "pageSize": PAGE_SIZE}
            if next_token:
                params["pageToken"] = next_token
            raw = http_get("/v1/users", params, hdrs)
            data = json.loads(raw.decode('utf-8'))
            put_json(bucket, "users/", f"users-page-{pages}", data)
            pages += 1
            next_token = data.get("nextPageToken") or data.get("next_page_token")
            if not next_token:
                break
        return pages
    
    # AUDIT LOG (/v1/audit/log)
    def pull_audit(bucket, hdrs):
        start_iso = iso_minus(LOOKBACK_MINUTES)
        next_token = None
        pages = 0
        while True:
            params = {"startTime": start_iso, "pageSize": PAGE_SIZE}
            if next_token:
                params["pageToken"] = next_token
            raw = http_get("/v1/audit/log", params, hdrs)
            try:
                data = json.loads(raw.decode('utf-8'))
                put_json(bucket, "audit/", f"audit-page-{pages}", data)
                next_token = data.get("nextPageToken") or data.get("next_page_token")
                pages += 1
                if not next_token:
                    break
            except Exception as e:
                print(f"Error parsing audit log response: {e}")
                # Save raw response
                ts = now_utc().strftime("%Y/%m/%d/%H%M%S")
                key = f"{PREFIX_BASE}audit/{ts}-audit-export.bin"
                blob = bucket.blob(key)
                blob.upload_from_string(raw, content_type='application/octet-stream')
                pages += 1
                break
        return pages
    
    # CASES (/v1/cases)
    def pull_cases(bucket, hdrs):
        next_token = None
        pages = 0
        while True:
            params = {"pageSize": PAGE_SIZE}
            if next_token:
                params["pageToken"] = next_token
            raw = http_get("/v1/cases", params, hdrs)
            data = json.loads(raw.decode('utf-8'))
            put_json(bucket, "cases/", f"cases-page-{pages}", data)
            pages += 1
            next_token = data.get("nextPageToken") or data.get("next_page_token")
            if not next_token:
                break
        return pages
    
    # FILE EVENTS (/v2/file-events/search)
    def pull_file_events(bucket, hdrs):
        if not FE_QUERY_JSON:
            return 0
        try:
            base_query = json.loads(FE_QUERY_JSON)
        except Exception as e:
            print(f"Error: FE_QUERY_JSON is not valid JSON: {e}")
            return 0
    
        pages = 0
        next_token = None
        while True:
            body = dict(base_query)
            body["pageSize"] = FE_PAGE_SIZE
            if next_token:
                body["pageToken"] = next_token
            raw = http_post_json("/v2/file-events/search", body, hdrs)
            data = json.loads(raw.decode('utf-8'))
            put_json(bucket, "file_events/", f"fileevents-page-{pages}", data)
            pages += 1
            next_token = (
                data.get("nextPageToken") or 
                data.get("next_page_token") or 
                (data.get("file_events") or {}).get("nextPageToken")
            )
            if not next_token:
                break
        return pages
    
    @functions_framework.cloud_event
    def main(cloud_event):
        """
        Cloud Run function triggered by Pub/Sub to fetch logs from Code42 Incydr API and write to GCS.
    
        Args:
            cloud_event: CloudEvent object containing Pub/Sub message
        """
    
        if not all([BASE, CID, CSECRET, BUCKET]):
            print('Error: Missing required environment variables')
            return
    
        try:
            bucket = storage_client.bucket(BUCKET)
            hdrs = auth_header()
            report = {}
    
            if "users" in STREAMS:
                print("Fetching users...")
                report["users_pages"] = pull_users(bucket, hdrs)
            if "audit" in STREAMS:
                print("Fetching audit logs...")
                report["audit_pages"] = pull_audit(bucket, hdrs)
            if "cases" in STREAMS:
                print("Fetching cases...")
                report["cases_pages"] = pull_cases(bucket, hdrs)
            if "file_events" in STREAMS:
                print("Fetching file events...")
                report["file_events_pages"] = pull_file_events(bucket, hdrs)
    
            print(f'Successfully processed logs: {json.dumps(report)}')
    
        except Exception as e:
            print(f'Error processing logs: {str(e)}')
            raise
    
    • Segundo arquivo: requirements.txt:
    functions-framework==3.*
    google-cloud-storage==2.*
    urllib3>=2.0.0
    
  3. Clique em Implantar para salvar e implantar a função.

  4. Aguarde a conclusão da implantação (2 a 3 minutos).

Criar o job do Cloud Scheduler

O Cloud Scheduler publica mensagens no tópico do Pub/Sub em intervalos regulares, acionando a função do Cloud Run.

  1. No console do GCP, acesse o Cloud Scheduler.
  2. Clique em Criar job.
  3. Informe os seguintes detalhes de configuração:

    Configuração Valor
    Nome code42-incydr-hourly
    Regiã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 code42-incydr-trigger.
    Corpo da mensagem {} (objeto JSON vazio)
  4. Clique em Criar.

Opções de frequência de programação

  • Escolha a frequência com base no volume de registros e nos requisitos de latência:

    Frequência Expressão Cron Caso de uso
    A cada 5 minutos */5 * * * * Alto volume e baixa latência
    A cada 15 minutos */15 * * * * Volume médio
    A cada hora 0 * * * * Padrão (recomendado)
    A cada 6 horas 0 */6 * * * Baixo volume, processamento em lote
    Diário 0 0 * * * Coleta de dados históricos

Testar o job do programador

  1. No console do Cloud Scheduler, encontre seu job (code42-incydr-hourly).
  2. Clique em Forçar execução para acionar manualmente.
  3. Aguarde alguns segundos e acesse Cloud Run > Serviços > code42-incydr-collector > Registros.
  4. Verifique se a função foi executada com sucesso. Procure:

    Fetching users...
    Fetching audit logs...
    Fetching cases...
    Successfully processed logs: {"users_pages": X, "audit_pages": Y, "cases_pages": Z}
    
  5. Verifique o bucket do GCS (code42-incydr-logs) para confirmar se os registros foram gravados.

Se você encontrar erros nos registros:

  • HTTP 401: verifique as credenciais da API nas variáveis de ambiente
  • HTTP 403: verifique se o cliente da API tem as permissões necessárias no console do Code42 Incydr.
  • HTTP 429: limitação de taxa. A função vai tentar novamente de forma automática com espera.
  • Falha ao receber o token de acesso: verifique se INCYDR_BASE_URL, INCYDR_CLIENT_ID e INCYDR_CLIENT_SECRET estão corretos.

Recuperar a conta de serviço do Google SecOps

O Google SecOps usa uma conta de serviço exclusiva para ler dados do seu bucket do GCS. Você precisa conceder a essa conta de serviço acesso ao seu bucket.

Receber o e-mail da conta de serviço

  1. Acesse Configurações do SIEM > Feeds.
  2. Clique em Adicionar novo feed.
  3. Clique em Configurar um único feed.
  4. No campo Nome do feed, insira um nome para o feed (por exemplo, Code42 Incydr Datasets).
  5. Selecione Google Cloud Storage V2 como o Tipo de origem.
  6. Selecione Code42 Incydr como o Tipo de registro.
  7. Clique em Receber conta de serviço. Um e-mail exclusivo da conta de serviço será exibido, por exemplo:

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  8. Copie esse endereço de e-mail para usar na próxima etapa.

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 GCS.

  1. Acesse Cloud Storage > Buckets.
  2. Clique no nome do bucket (code42-incydr-logs).
  3. Acesse a guia Permissões.
  4. Clique em Conceder acesso.
  5. 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.
  6. Clique em Salvar.

Configurar um feed no Google SecOps para ingerir registros do Code42 Incydr

  1. Acesse Configurações do SIEM > Feeds.
  2. Clique em Adicionar novo feed.
  3. Clique em Configurar um único feed.
  4. No campo Nome do feed, insira um nome para o feed (por exemplo, Code42 Incydr Datasets).
  5. Selecione Google Cloud Storage V2 como o Tipo de origem.
  6. Selecione Code42 Incydr como o Tipo de registro.
  7. Clique em Próxima.
  8. Especifique valores para os seguintes parâmetros de entrada:

    • URL do bucket de armazenamento: insira o URI do bucket do GCS com o caminho do prefixo:

      gs://code42-incydr-logs/code42/
      
      • Substitua:

        • code42-incydr-logs: o nome do bucket do GCS.
        • code42/: prefixo/caminho da pasta opcional onde os registros são armazenados (deixe em branco para a raiz).
      • Exemplos:

        • Bucket raiz: gs://code42-incydr-logs/
        • Com prefixo: gs://code42-incydr-logs/code42/
    • 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 é 180 dias.

    • Namespace do recurso: o namespace do recurso.

    • Rótulos de ingestão: o rótulo a ser aplicado aos eventos desse feed.

  9. Clique em Próxima.

  10. Revise a nova configuração do feed na tela Finalizar e clique em Enviar.

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.