Coletar registros de contexto da entidade do Duo

Compatível com:

Este documento explica como ingerir dados de contexto de entidade do Duo no Google Security Operations usando o Amazon S3. O analisador transforma os registros JSON em um modelo de dados unificado (UDM) extraindo primeiro os campos do JSON bruto e mapeando esses campos para atributos do UDM. Ele processa vários cenários de dados, incluindo informações de usuários e recursos, detalhes de software e rótulos de segurança, garantindo uma representação abrangente no esquema da UDM.

Antes de começar

  • Instância do Google SecOps
  • Acesso privilegiado ao locatário do Duo (aplicativo da API Admin)
  • Acesso privilegiado à AWS (S3, IAM, Lambda, EventBridge)

Configurar o aplicativo da API Admin do Duo

  1. Faça login no painel de administração do Duo.
  2. Acesse Aplicativos > Catálogo de aplicativos.
  3. Adicione o aplicativo API Admin.
  4. Anote os seguintes valores:
    • Chave de integração (ikey)
    • Chave secreta (skey)
    • Nome do host da API (por exemplo, api-XXXXXXXX.duosecurity.com)
  5. Em Permissões, ative Conceder recurso – leitura (para ler usuários, grupos, dispositivos/endpoints).
  6. Salve o aplicativo.

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, duo-context).
  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. Acesse Console da AWS > IAM > Políticas > Criar política > guia JSON.
  2. Insira a seguinte política:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutDuoObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::duo-context/*"
        }
      ]
    }
    
    • Substitua duo-context se você inseriu 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 WriteDuoToS3Role 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 duo_entity_context_to_s3
    Ambiente de execução Python 3.13
    Arquitetura x86_64
    Função de execução WriteDuoToS3Role
  4. Depois que a função for criada, abra a guia Código, exclua o stub e insira o seguinte código (duo_entity_context_to_s3.py):

    #!/usr/bin/env python3
    
    import os, json, time, hmac, hashlib, base64, email.utils, urllib.parse
    from urllib.request import Request, urlopen
    import boto3
    
    # Env
    DUO_IKEY = os.environ["DUO_IKEY"]
    DUO_SKEY = os.environ["DUO_SKEY"]
    DUO_API_HOSTNAME = os.environ["DUO_API_HOSTNAME"].strip()
    S3_BUCKET = os.environ["S3_BUCKET"]
    S3_PREFIX = os.environ.get("S3_PREFIX", "duo/context/")
    # Default set can be adjusted via ENV
    RESOURCES = [r.strip() for r in os.environ.get(
        "RESOURCES",
        "users,groups,phones,endpoints,tokens,webauthncredentials,desktop_authenticators"
    ).split(",") if r.strip()]
    # Duo paging: default 100; max 500 for these endpoints
    LIMIT = int(os.environ.get("LIMIT", "500"))
    
    s3 = boto3.client("s3")
    
    def _canon_params(params: dict) -> str:
        """RFC3986 encoding with '~' unescaped, keys sorted lexicographically."""
        if not params:
            return ""
        parts = []
        for k in sorted(params.keys()):
            v = params[k]
            if v is None:
                continue
            ks = urllib.parse.quote(str(k), safe="~")
            vs = urllib.parse.quote(str(v), safe="~")
            parts.append(f"{ks}={vs}")
        return "&".join(parts)
    
    def _sign(method: str, host: str, path: str, params: dict) -> dict:
        """Construct Duo Admin API Authorization + Date headers (HMAC-SHA1)."""
        now = email.utils.formatdate()
        canon = "\n".join([now, method.upper(), host.lower(), path, _canon_params(params)])
        sig = hmac.new(DUO_SKEY.encode("utf-8"), canon.encode("utf-8"), hashlib.sha1).hexdigest()
        auth = base64.b64encode(f"{DUO_IKEY}:{sig}".encode("utf-8")).decode("utf-8")
        return {"Date": now, "Authorization": f"Basic {auth}"}
    
    def _call(method: str, path: str, params: dict) -> dict:
        host = DUO_API_HOSTNAME
        assert host.startswith("api-") and host.endswith(".duosecurity.com"), \
            "DUO_API_HOSTNAME must be e.g. api-XXXXXXXX.duosecurity.com"
        qs = _canon_params(params)
        url = f"https://{host}{path}" + (f"?{qs}" if method.upper() == "GET" and qs else "")
        req = Request(url, method=method.upper())
        for k, v in _sign(method, host, path, params).items():
            req.add_header(k, v)
        with urlopen(req, timeout=60) as r:
            return json.loads(r.read().decode("utf-8"))
    
    def _write_json(obj: dict, when: float, resource: str, page: int) -> str:
        prefix = S3_PREFIX.strip("/") + "/" if S3_PREFIX else ""
        key = f"{prefix}{time.strftime('%Y/%m/%d', time.gmtime(when))}/duo-{resource}-{page:05d}.json"
        s3.put_object(Bucket=S3_BUCKET, Key=key, Body=json.dumps(obj, separators=(",", ":")).encode("utf-8"))
        return key
    
    def _fetch_resource(resource: str) -> dict:
        """Fetch all pages for a list endpoint using limit/offset + metadata.next_offset."""
        path = f"/admin/v1/{resource}"
        offset = 0
        page = 0
        now = time.time()
        total_items = 0
    
        while True:
            params = {"limit": LIMIT, "offset": offset}
            data = _call("GET", path, params)
            _write_json(data, now, resource, page)
            page += 1
    
            resp = data.get("response")
            # most endpoints return a list; if not a list, count as 1 object page
            if isinstance(resp, list):
                total_items += len(resp)
            elif resp is not None:
                total_items += 1
    
            meta = data.get("metadata") or {}
            next_offset = meta.get("next_offset")
            if next_offset is None:
                break
    
            # Duo returns next_offset as int
            try:
                offset = int(next_offset)
            except Exception:
                break
    
        return {"resource": resource, "pages": page, "objects": total_items}
    
    def lambda_handler(event=None, context=None):
        results = []
        for res in RESOURCES:
            results.append(_fetch_resource(res))
        return {"ok": True, "results": results}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
    
  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 duo-context
    S3_PREFIX duo/context/
    DUO_IKEY DIXYZ...
    DUO_SKEY ****************
    DUO_API_HOSTNAME api-XXXXXXXX.duosecurity.com
    LIMIT 200
    RESOURCES users,groups,phones,endpoints,tokens,webauthncredentials
  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 5 minutos (300 segundos) e clique em Salvar.

Criar uma programação do EventBridge

  1. Acesse Amazon EventBridge > Scheduler > Criar programação.
  2. Informe os seguintes detalhes de configuração:
    • Programação recorrente: Taxa (1 hour).
    • Destino: sua função Lambda.
    • Nome: duo-entity-context-1h.
  3. Clique em Criar programação.

Opcional: criar um usuário e chaves do IAM somente leitura para o Google SecOps

  1. No console da AWS, acesse IAM > Usuários e clique em Adicionar usuários.
  2. Informe os seguintes detalhes de configuração:
    • Usuário: insira um nome exclusivo (por exemplo, secops-reader)
    • Tipo de acesso: selecione Chave de acesso - Acesso programático.
    • Clique em Criar usuário.
  3. Anexe a política de leitura mínima (personalizada): Usuários > selecione secops-reader > Permissões > Adicionar permissões > Anexar políticas diretamente > Criar política
  4. No editor JSON, insira a seguinte política:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::<your-bucket>/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::<your-bucket>"
        }
      ]
    }
    
  5. Defina o nome como secops-reader-policy.

  6. Acesse Criar política > pesquise/selecione > Próxima > Adicionar permissões.

  7. Acesse Credenciais de segurança > Chaves de acesso > Criar chave de acesso.

  8. Faça o download do CSV (esses valores são inseridos no feed).

Configurar um feed no Google SecOps para ingerir dados de contexto da entidade do Duo

  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, Duo Entity Context).
  4. Selecione Amazon S3 V2 como o Tipo de origem.
  5. Selecione Dados de contexto da entidade do Duo como o Tipo de registro.
  6. Clique em Próxima.
  7. Especifique valores para os seguintes parâmetros de entrada:
    • URI do S3: s3://duo-context/duo/context/
    • 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: padrão de 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 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
ativado entity.asset.deployment_status Se "activated" for falso, defina como "DECOMISSIONED". Caso contrário, defina como "ACTIVE".
browsers.browser_family entity.asset.software.name Extraído da matriz "browsers" no registro bruto.
browsers.browser_version entity.asset.software.version Extraído da matriz "browsers" no registro bruto.
device_name entity.asset.hostname Mapeado diretamente do registro bruto.
disk_encryption_status entity.asset.attribute.labels.key: "disk_encryption_status", entity.asset.attribute.labels.value: Mapeado diretamente do registro bruto e convertido em letras minúsculas.
e-mail entity.user.email_addresses Mapeado diretamente do registro bruto se ele contiver "@". Caso contrário, usa "username" ou "username1" se eles contiverem "@".
criptografado entity.asset.attribute.labels.key: "Encrypted", entity.asset.attribute.labels.value: Mapeado diretamente do registro bruto e convertido em letras minúsculas.
epkey entity.asset.product_object_id Usado como "product_object_id" se estiver presente. Caso contrário, usa "phone_id" ou "token_id".
Impressão digital entity.asset.attribute.labels.key: "Finger Print", entity.asset.attribute.labels.value: Mapeado diretamente do registro bruto e convertido em letras minúsculas.
firewall_status entity.asset.attribute.labels.key: "firewall_status", entity.asset.attribute.labels.value: Mapeado diretamente do registro bruto e convertido em letras minúsculas.
hardware_uuid entity.asset.asset_id Usado como "asset_id" se estiver presente. Caso contrário, usa "user_id".
last_seen entity.asset.last_discover_time Analisado como um carimbo de data/hora ISO8601 e mapeado.
modelo entity.asset.hardware.model Mapeado diretamente do registro bruto.
número entity.user.phone_numbers Mapeado diretamente do registro bruto.
os_family entity.asset.platform_software.platform Mapeado para "WINDOWS", "LINUX" ou "MAC" com base no valor, sem diferenciação de maiúsculas e minúsculas.
os_version entity.asset.platform_software.platform_version Mapeado diretamente do registro bruto.
password_status entity.asset.attribute.labels.key: "password_status", entity.asset.attribute.labels.value: Mapeado diretamente do registro bruto e convertido em letras minúsculas.
phone_id entity.asset.product_object_id Usado como "product_object_id" se "epkey" não estiver presente. Caso contrário, usa "token_id".
security_agents.security_agent entity.asset.software.name Extraído da matriz "security_agents" no registro bruto.
security_agents.version entity.asset.software.version Extraído da matriz "security_agents" no registro bruto.
timestamp entity.metadata.collected_timestamp Preenche o campo "collected_timestamp" no objeto "metadata".
token_id entity.asset.product_object_id Usado como "product_object_id" se "epkey" e "phone_id" não estiverem presentes.
trusted_endpoint entity.asset.attribute.labels.key: "trusted_endpoint", entity.asset.attribute.labels.value: Mapeado diretamente do registro bruto e convertido em letras minúsculas.
tipo entity.asset.type Se o "type" do registro bruto contiver "mobile" (sem diferenciar maiúsculas de minúsculas), defina como "MOBILE". Caso contrário, defina como "LAPTOP".
user_id entity.asset.asset_id Usado como "asset_id" se "hardware_uuid" não estiver presente.
users.email entity.user.email_addresses Usado como "email_addresses" se for o primeiro usuário na matriz "users" e contiver "@".
users.username entity.user.userid Nome de usuário extraído antes de "@" e usado como "userid" se for o primeiro usuário na matriz "users".
entity.metadata.vendor_name "Duo"
entity.metadata.product_name "Dados de contexto da entidade do Duo"
entity.metadata.entity_type RECURSO
entity.relations.entity_type USUÁRIO
entity.relations.relationship OWNS

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