Recolha registos JSON de colaboração do Box

Compatível com:

Este documento explica como carregar registos JSON de colaboração do Box para o Google Security Operations através do AWS S3 com o agendamento do Lambda e do EventBridge. O analisador processa registos de eventos do Box no formato JSON, mapeando-os para um modelo de dados unificado (UDM). Extrai campos relevantes dos registos não processados, executa transformações de dados, como a mudança de nome e a união, e enriquece os dados com informações intermédias antes de gerar os dados de eventos estruturados.

Antes de começar

  • Instância do Google SecOps
  • Acesso privilegiado ao Box (consola do administrador + programador)
  • Acesso privilegiado à AWS (S3, IAM, Lambda, EventBridge) na mesma região onde planeia armazenar os registos

Configure a Box Developer Console (credenciais de cliente)

  1. Inicie sessão na Box Developer Console.
  2. Crie uma app personalizada com autenticação do servidor (concessão de credenciais do cliente).
  3. Defina Acesso à aplicação = App + acesso empresarial.
  4. Em Âmbitos da aplicação, ative a opção Gerir propriedades empresariais.
  5. Na consola do administrador > Apps > Gestor de apps personalizadas, autorize a app através do ID do cliente.
  6. Copie e guarde o ID de cliente e o * segredo do cliente num local seguro.
  7. Aceda à consola do administrador > Conta e faturação > Informações da conta.
  8. Copie e guarde o ID empresarial num local seguro.

Configure o contentor do AWS S3 e o IAM para o Google SecOps

  1. Crie um contentor do Amazon S3 seguindo este manual do utilizador: Criar um contentor
  2. Guarde o nome e a região do contentor para referência futura (por exemplo, box-collaboration-logs).
  3. Crie um utilizador seguindo este guia do utilizador: Criar um utilizador do IAM.
  4. Selecione o utilizador criado.
  5. Selecione o separador Credenciais de segurança.
  6. Clique em Criar chave de acesso na secção Chaves de acesso.
  7. Selecione Serviço de terceiros como o Exemplo de utilização.
  8. Clicar em Seguinte.
  9. Opcional: adicione uma etiqueta de descrição.
  10. Clique em Criar chave de acesso.
  11. Clique em Transferir ficheiro CSV para guardar a chave de acesso e a chave de acesso secreta para utilização posterior.
  12. Clique em Concluído.
  13. Selecione o separador Autorizações.
  14. Clique em Adicionar autorizações na secção Políticas de autorizações.
  15. Selecione Adicionar autorizações.
  16. Selecione Anexar políticas diretamente
  17. Pesquise e selecione a política AmazonS3FullAccess.
  18. Clicar em Seguinte.
  19. Clique em Adicionar autorizações.

Configure a política e a função de IAM para carregamentos do S3

  1. Na consola da AWS, aceda a IAM > Políticas > Criar política > separador JSON.
  2. Introduza 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 tiver introduzido um nome de contentor diferente.
  3. Clique em Seguinte > Criar política.

  4. Aceda a IAM > Funções > Criar função > Serviço AWS > Lambda.

  5. Anexe a política criada recentemente.

  6. Dê o nome WriteBoxToS3Role à função e clique em Criar função.

Crie a função Lambda

  1. Na consola da AWS, aceda a Lambda > Functions > Create function.
  2. Clique em Criar do zero.
  3. Faculte os seguintes detalhes de configuração:

    Definição Valor
    Nome box_collaboration_to_s3
    Runtime Python 3.13
    Arquitetura x86_64
    Função de execução WriteBoxToS3Role
  4. Depois de criar a função, abra o separador Código, elimine o fragmento e introduza 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. Aceda a Configuração > Variáveis de ambiente > Editar > Adicionar nova variável de ambiente.

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

    Chave Exemplo
    S3_BUCKET box-collaboration-logs
    S3_PREFIX box/collaboration/
    STATE_KEY box/collaboration/state.json
    BOX_CLIENT_ID Introduza o ID de cliente do Box
    BOX_CLIENT_SECRET Introduza o segredo do cliente do Box
    BOX_ENTERPRISE_ID Introduza o ID do Box Enterprise
    STREAM_TYPE admin_logs_streaming
    LIMIT 500
  7. Depois de criar a função, permaneça na respetiva página (ou abra Lambda > Functions > your-function).

  8. Selecione o separador Configuração.

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

  10. Altere Tempo limite para 10 minutos (600 segundos) e clique em Guardar.

Agende a função Lambda (EventBridge Scheduler)

  1. Aceda a Amazon EventBridge > Scheduler > Create schedule.
  2. Indique os seguintes detalhes de configuração:
    • Agenda recorrente: Taxa (15 min).
    • Alvo: a sua função Lambda.
    • Nome: box-collaboration-schedule-15min.
  3. Clique em Criar programação.

Configure um feed no Google SecOps para carregar registos do Box

  1. Aceda a Definições do SIEM > Feeds.
  2. Clique em Adicionar novo feed.
  3. No campo Nome do feed, introduza 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 registo.
  6. Clicar em Seguinte.
  7. Especifique valores para os seguintes parâmetros de entrada:
    • URI do S3: o URI do contentor (o formato deve ser: s3://box-collaboration-logs/box/collaboration/). Substitua box-collaboration-logs: use o nome real do contentor.
    • Opções de eliminação de origens: selecione a opção de eliminação de acordo com a sua preferência.
    • Idade máxima do ficheiro: inclua ficheiros modificados no último número de dias. A predefinição é 180 dias.
    • ID da chave de acesso: chave de acesso do utilizador com acesso ao contentor do S3.
    • Chave de acesso secreta: chave secreta do utilizador com acesso ao contentor do S3.
    • Espaço de nomes do recurso: o espaço de nomes do recurso.
    • Etiquetas de carregamento: a etiqueta a aplicar aos eventos deste feed.
  8. Clicar em Seguinte.
  9. Reveja a nova configuração do feed no ecrã Finalizar e, de seguida, clique em Enviar.

Tabela de mapeamento da UDM

Campo de registo Mapeamento do UDM Lógica
additional_details.ekm_id additional.fields Valor retirado de additional_details.ekm_id
additional_details.service_id additional.fields Valor obtido de additional_details.service_id
additional_details.service_name additional.fields Valor retirado de additional_details.service_name
additional_details.shared_link_id additional.fields Valor retirado de additional_details.shared_link_id
additional_details.size target.file.size Valor retirado de additional_details.size
additional_details.version_id additional.fields Valor retirado de additional_details.version_id
created_at metadata.event_timestamp Valor retirado de created_at
created_by.id principal.user.userid Valor retirado de created_by.id
created_by.login principal.user.email_addresses Valor retirado de created_by.login
created_by.name principal.user.user_display_name Valor retirado de created_by.name
event_id metadata.product_log_id Valor retirado de event_id
event_type metadata.product_event_type Valor retirado de event_type
ip_address principal.ip Valor retirado de ip_address
source.item_id target.file.product_object_id Valor retirado de source.item_id
source.item_name target.file.full_path Valor retirado de source.item_name
source.item_type Não mapeado
source.login target.user.email_addresses Valor retirado de source.login
source.name target.user.user_display_name Valor retirado de source.name
source.owned_by.id target.user.userid Valor retirado de source.owned_by.id
source.owned_by.login target.user.email_addresses Valor retirado de source.owned_by.login
source.owned_by.name target.user.user_display_name Valor retirado 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
escrever metadata.log_type Valor retirado do tipo
metadata.vendor_name Valor codificado
metadata.product_name Valor codificado
security_result.action Derivado de event_type. Se event_type for FAILED_LOGIN, então BLOCK. Se event_type for USER_LOGIN, então ALLOW. Caso contrário, UNSPECIFIED.
extensions.auth.type Derivado de event_type. Se event_type for USER_LOGIN ou ADMIN_LOGIN, então MACHINE; caso contrário, UNSPECIFIED.
extensions.auth.mechanism Derivado de event_type. Se event_type for USER_LOGIN ou ADMIN_LOGIN, então USERNAME_PASSWORD. Caso contrário, UNSPECIFIED.

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