Coletar registros JSON de colaboração do Box

Compatível com:

Este documento explica como ingerir registros JSON de colaboração do Box no Google Security Operations usando o AWS S3 com uma programação do Lambda e do EventBridge. O analisador processa registros de eventos do Box no formato JSON, mapeando-os para um modelo de dados unificado (UDM). Ele extrai campos relevantes dos registros brutos, realiza transformações de dados, como renomeação e fusão, e enriquece os dados com informações intermediárias antes de gerar os dados de eventos estruturados.

Antes de começar

  • Instância do Google SecOps
  • Acesso privilegiado ao Box (Admin Console e Developer Console)
  • Acesso privilegiado à AWS (S3, IAM, Lambda, EventBridge) na mesma região em que você planeja armazenar os registros

Configurar o Box Developer Console (credenciais do cliente)

  1. Faça login no Box Developer Console.
  2. Crie um app personalizado com autenticação de servidor (concessão de credenciais do cliente).
  3. Defina Acesso ao aplicativo = Acesso ao app + Enterprise.
  4. Em Escopos do aplicativo, ative Gerenciar propriedades empresariais.
  5. No Admin Console > Apps > Gerenciador de apps personalizados, autorize o app usando o ID do cliente.
  6. Copie e salve o ID do cliente e a chave secreta do cliente em um local seguro.
  7. Acesse Admin Console > Conta e faturamento > Informações da conta.
  8. Copie e salve o ID da empresa em um local seguro.

Configurar o bucket do AWS S3 e o IAM para o Google SecOps

  1. Crie um bucket do Amazon S3 seguindo este guia do usuário: Como criar um bucket
  2. Salve o Nome e a Região do bucket para referência futura (por exemplo, box-collaboration-logs).
  3. Crie um usuário seguindo este guia: Como criar um usuário do IAM.
  4. Selecione o usuário criado.
  5. Selecione a guia Credenciais de segurança.
  6. Clique em Criar chave de acesso na seção Chaves de acesso.
  7. Selecione Serviço de terceiros como o Caso de uso.
  8. Clique em Próxima.
  9. Opcional: adicione uma tag de descrição.
  10. Clique em Criar chave de acesso.
  11. Clique em Fazer o download do arquivo CSV para salvar a chave de acesso e a chave de acesso secreta para uso posterior.
  12. Clique em Concluído.
  13. Selecione a guia Permissões.
  14. Clique em Adicionar permissões na seção Políticas de permissões.
  15. Selecione Adicionar permissões.
  16. Selecione Anexar políticas diretamente.
  17. Pesquise e selecione a política AmazonS3FullAccess.
  18. Clique em Próxima.
  19. Clique em Adicionar permissões

Configurar a política e o papel do IAM para uploads do S3

  1. No console da AWS, acesse IAM > Políticas > Criar política > guia JSON.
  2. Insira a seguinte política:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutBoxObjects",
          "Effect": "Allow",
          "Action": ["s3:PutObject"],
          "Resource": "arn:aws:s3:::box-collaboration-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::box-collaboration-logs/box/collaboration/state.json"
        }
      ]
    }
    
    
    • Substitua box-collaboration-logs se você tiver inserido um nome de bucket diferente.
  3. Clique em Próxima > Criar política.

  4. Acesse IAM > Funções > Criar função > Serviço da AWS > Lambda.

  5. Anexe a política recém-criada.

  6. Nomeie a função como WriteBoxToS3Role e clique em Criar função.

Criar a função Lambda

  1. No console da AWS, acesse Lambda > Functions > Create function.
  2. Clique em Criar do zero.
  3. Informe os seguintes detalhes de configuração:

    Configuração Valor
    Nome box_collaboration_to_s3
    Ambiente de execução Python 3.13
    Arquitetura x86_64
    Função de execução WriteBoxToS3Role
  4. Depois que a função for criada, abra a guia Código, exclua o stub e insira o seguinte código (box_collaboration_to_s3.py):

    #!/usr/bin/env python3
    # Lambda: Pull Box Enterprise Events to S3 (no transform)
    
    import os, json, time, urllib.parse
    from urllib.request import Request, urlopen
    from urllib.error import HTTPError, URLError
    import boto3
    
    TOKEN_URL = "https://api.box.com/oauth2/token"
    EVENTS_URL = "https://api.box.com/2.0/events"
    
    CID         = os.environ["BOX_CLIENT_ID"]
    CSECRET     = os.environ["BOX_CLIENT_SECRET"]
    ENT_ID      = os.environ["BOX_ENTERPRISE_ID"]
    STREAM_TYPE = os.environ.get("STREAM_TYPE", "admin_logs_streaming")
    LIMIT       = int(os.environ.get("LIMIT", "500"))
    BUCKET      = os.environ["S3_BUCKET"]
    PREFIX      = os.environ.get("S3_PREFIX", "box/collaboration/")
    STATE_KEY   = os.environ.get("STATE_KEY", "box/collaboration/state.json")
    
    s3 = boto3.client("s3")
    
    def get_state():
        try:
            obj = s3.get_object(Bucket=BUCKET, Key=STATE_KEY)
            data = json.loads(obj["Body"].read())
            return data.get("stream_position")
        except Exception:
            return None
    
    def put_state(pos):
        body = json.dumps({"stream_position": pos}, separators=(",", ":")).encode("utf-8")
        s3.put_object(Bucket=BUCKET, Key=STATE_KEY, Body=body, ContentType="application/json")
    
    def get_token():
        body = urllib.parse.urlencode({
            "grant_type": "client_credentials",
            "client_id": CID,
            "client_secret": CSECRET,
            "box_subject_type": "enterprise",
            "box_subject_id": ENT_ID,
        }).encode()
        req = Request(TOKEN_URL, data=body, method="POST")
        req.add_header("Content-Type", "application/x-www-form-urlencoded")
        with urlopen(req, timeout=30) as r:
            tok = json.loads(r.read().decode())
        return tok["access_token"]
    
    def fetch_events(token, stream_position=None, timeout=60, max_retries=5):
        params = {"stream_type": STREAM_TYPE, "limit": LIMIT, "stream_position": stream_position or "now"}
        qs = urllib.parse.urlencode(params)
        attempt, backoff = 0, 1.0
        while True:
            try:
                req = Request(f"{EVENTS_URL}?{qs}", method="GET")
                req.add_header("Authorization", f"Bearer {token}")
                with urlopen(req, timeout=timeout) as r:
                    return json.loads(r.read().decode())
            except HTTPError as e:
                if e.code == 429 and attempt < max_retries:
                    ra = e.headers.get("Retry-After")
                    delay = int(ra) if (ra and ra.isdigit()) else int(backoff)
                    time.sleep(max(1, delay)); attempt += 1; backoff *= 2; continue
                if 500 <= e.code <= 599 and attempt < max_retries:
                    time.sleep(backoff); attempt += 1; backoff *= 2; continue
                raise
            except URLError:
                if attempt < max_retries:
                    time.sleep(backoff); attempt += 1; backoff *= 2; continue
                raise
    
    def write_chunk(data):
        ts = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime())
        key = f"{PREFIX}/{ts}-box-events.json"  
        s3.put_object(Bucket=BUCKET, Key=key,
                      Body=json.dumps(data, separators=(",", ":")).encode("utf-8"),
                      ContentType="application/json")  
        return key
    
    def lambda_handler(event=None, context=None):
        token = get_token()
        pos = get_state()
        total, idx = 0, 0
        while True:
            page = fetch_events(token, pos)
            entries = page.get("entries") or []
            if not entries:
                next_pos = page.get("next_stream_position") or pos
                if next_pos and next_pos != pos:
                    put_state(next_pos)
                break
    
            # уникальный ключ
            ts = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime())
            key = f"{PREFIX}/{ts}-box-events-{idx:03d}.json"
            s3.put_object(Bucket=BUCKET, Key=key,
                          Body=json.dumps(page, separators=(",", ":")).encode("utf-8"),
                          ContentType="application/json")
            idx += 1
            total += len(entries)
    
            pos = page.get("next_stream_position") or pos
            if pos:
                put_state(pos)
    
            if len(entries) < LIMIT:
                break
    
        return {"ok": True, "written": total, "next_stream_position": pos}
    
    
  5. Acesse Configuração > Variáveis de ambiente > Editar > Adicionar nova variável de ambiente.

  6. Insira as seguintes variáveis de ambiente, substituindo pelos seus valores:

    Chave Exemplo
    S3_BUCKET box-collaboration-logs
    S3_PREFIX box/collaboration/
    STATE_KEY box/collaboration/state.json
    BOX_CLIENT_ID Insira o ID do cliente do Box
    BOX_CLIENT_SECRET Insira a chave secreta do cliente do Box
    BOX_ENTERPRISE_ID Insira o ID corporativo do Box
    STREAM_TYPE admin_logs_streaming
    LIMIT 500
  7. Depois que a função for criada, permaneça na página dela ou abra Lambda > Functions > sua-função.

  8. Selecione a guia Configuração.

  9. No painel Configuração geral, clique em Editar.

  10. Mude Tempo limite para 10 minutos (600 segundos) e clique em Salvar.

Programar a função do Lambda (EventBridge Scheduler)

  1. Acesse Amazon EventBridge > Scheduler > Criar programação.
  2. Informe os seguintes detalhes de configuração:
    • Programação recorrente: Taxa (15 min).
    • Destino: sua função Lambda.
    • Nome: box-collaboration-schedule-15min.
  3. Clique em Criar programação.

Configurar um feed no Google SecOps para ingerir registros do Box

  1. Acesse Configurações do SIEM > Feeds.
  2. Clique em Adicionar novo feed.
  3. No campo Nome do feed, insira um nome para o feed (por exemplo, Box Collaboration).
  4. Selecione Amazon S3 V2 como o Tipo de origem.
  5. Selecione Caixa como o Tipo de registro.
  6. Clique em Próxima.
  7. Especifique valores para os seguintes parâmetros de entrada:
    • URI do S3: o URI do bucket (o formato precisa ser: s3://box-collaboration-logs/box/collaboration/). Substitua box-collaboration-logs: use o nome real do bucket.
    • Opções de exclusão de fontes: selecione a opção de exclusão de acordo com sua preferência.
    • Idade máxima do arquivo: inclui arquivos modificados no último número de dias. O padrão é 180 dias.
    • ID da chave de acesso: chave de acesso do usuário com acesso ao bucket do S3.
    • Chave de acesso secreta: chave secreta do usuário com acesso ao bucket do S3.
    • Namespace do recurso: o namespace do recurso.
    • Rótulos de ingestão: o rótulo a ser aplicado aos eventos deste feed.
  8. Clique em Próxima.
  9. Revise a nova configuração do feed na tela Finalizar e clique em Enviar.

Tabela de mapeamento da UDM

Campo de registro Mapeamento do UDM Lógica
additional_details.ekm_id additional.fields Valor extraído de "additional_details.ekm_id"
additional_details.service_id additional.fields Valor extraído de additional_details.service_id
additional_details.service_name additional.fields Valor extraído de additional_details.service_name
additional_details.shared_link_id additional.fields Valor extraído de "additional_details.shared_link_id"
additional_details.size target.file.size Valor extraído de "additional_details.size"
additional_details.version_id additional.fields Valor extraído de "additional_details.version_id"
created_at metadata.event_timestamp Valor extraído de "created_at"
created_by.id principal.user.userid Valor extraído de created_by.id
created_by.login principal.user.email_addresses Valor extraído de created_by.login
created_by.name principal.user.user_display_name Valor extraído de created_by.name
event_id metadata.product_log_id Valor extraído de event_id
event_type metadata.product_event_type Valor extraído de "event_type"
ip_address principal.ip Valor extraído de "ip_address"
source.item_id target.file.product_object_id Valor extraído de "source.item_id"
source.item_name target.file.full_path Valor extraído de source.item_name
source.item_type Não mapeado
source.login target.user.email_addresses Valor extraído de "source.login"
source.name target.user.user_display_name Valor extraído de "source.name"
source.owned_by.id target.user.userid Valor extraído de source.owned_by.id
source.owned_by.login target.user.email_addresses Valor extraído de source.owned_by.login
source.owned_by.name target.user.user_display_name Valor extraído de source.owned_by.name
source.parent.id Não mapeado
source.parent.name Não mapeado
source.parent.type Não mapeado
source.type Não mapeado
tipo metadata.log_type Valor extraído do tipo
metadata.vendor_name Valor fixado no código
metadata.product_name Valor fixado no código
security_result.action Derivado de "event_type". Se event_type for FAILED_LOGIN, será BLOCK. Se for USER_LOGIN, será ALLOW. Caso contrário, será UNSPECIFIED.
extensions.auth.type Derivado de "event_type". Se event_type for USER_LOGIN ou ADMIN_LOGIN, MACHINE. Caso contrário, UNSPECIFIED.
extensions.auth.mechanism Derivado de "event_type". Se event_type for USER_LOGIN ou ADMIN_LOGIN, será USERNAME_PASSWORD. Caso contrário, será UNSPECIFIED.

Precisa de mais ajuda? Receba respostas de membros da comunidade e profissionais do Google SecOps.