Coletar registros de auditoria do Workday
Este documento explica como ingerir registros de auditoria do Workday no Google Security Operations usando o AWS S3. Primeiro, o analisador identifica o tipo de evento específico nos registros com base na análise de padrões dos dados CSV. Em seguida, ele extrai e estrutura os campos relevantes de acordo com o tipo identificado, mapeando-os para um modelo de dados unificado (UDM) para uma análise de segurança consistente.
Antes de começar
Verifique se você tem os pré-requisitos a seguir:
- Instância do Google SecOps
- Acesso privilegiado à AWS
- Acesso privilegiado ao Workday
Configurar o bucket do AWS S3 e o IAM para o Google SecOps
- Crie um bucket do Amazon S3 seguindo este guia do usuário: Como criar um bucket.
- Salve o Nome e a Região do bucket para referência futura (por exemplo,
workday-audit-logs
). - Crie um usuário seguindo este guia: Como criar um usuário do IAM.
- Selecione o usuário criado.
- Selecione a guia Credenciais de segurança.
- Clique em Criar chave de acesso na seção Chaves de acesso.
- Selecione Serviço de terceiros como Caso de uso.
- Clique em Próxima.
- Opcional: adicione uma tag de descrição.
- Clique em Criar chave de acesso.
- Clique em Fazer o download do arquivo CSV para salvar a chave de acesso e a chave de acesso secreta para referência futura.
- Clique em Concluído.
- Selecione a guia Permissões.
- Clique em Adicionar permissões na seção Políticas de permissões.
- Selecione Adicionar permissões.
- Selecione Anexar políticas diretamente.
- Pesquise e selecione a política AmazonS3FullAccess.
- Clique em Próxima.
- Clique em Adicionar permissões
Criar um usuário do sistema de integração do Workday (ISU, na sigla em inglês)
- No Workday, pesquise Create Integration System User > OK.
- Preencha o campo Nome de usuário (por exemplo,
audit_s3_user
). - Clique em OK.
- Para redefinir a senha, acesse Ações relacionadas > Segurança > Redefinir senha.
- Selecione Manter regras de senha para evitar que a senha expire.
- Pesquise Criar grupo de segurança > Grupo de segurança do sistema de integração (sem restrições).
- Forneça um nome (por exemplo,
ISU_Audit_S3
) e adicione o ISU a Usuários do sistema de integração. - Pesquise Políticas de segurança de domínio para área funcional > Sistema.
- Em Registro de auditoria, selecione Ações > Editar permissões.
- Em Get Only, adicione o grupo
ISU_Audit_S3
. - Clique em OK > Ativar mudanças pendentes na política de segurança.
Configurar um relatório personalizado do Workday
- No Workday, pesquise Criar relatório personalizado.
- Informe os seguintes detalhes de configuração:
- Nome: insira um nome exclusivo, por exemplo,
Audit_Trail_BP_JSON
. - Tipo: selecione Avançado.
- Origem de dados: selecione Rastreamento de auditoria – Processo comercial.
- Clique em OK.
- Opcional: adicione filtros em Tipo de processo de negócios ou Data de vigência.
- Nome: insira um nome exclusivo, por exemplo,
- Acesse a guia Saída.
- Selecione Ativar como serviço da Web, Otimizado para desempenho e Formato JSON.
- Clique em OK > Concluído.
- Abra o relatório e clique em Compartilhar > adicionar
ISU_Audit_S3
com permissão de visualização > OK. - Acesse Ações relacionadas > Serviço da Web > Ver URLs.
- Copie o URL JSON (por exemplo,
https://wd-services1.workday.com/ccx/service/customreport2/<tenant>/<user>/Audit_Trail_BP_JSON?format=json
).
Configurar a política e o papel do IAM para uploads do S3
JSON da política (substitua
workday-audit-logs
se você tiver inserido um nome de bucket diferente):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutWorkdayObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::workday-audit-logs/*" } ] }
Acesse Console da AWS > IAM > Políticas > Criar política > guia JSON.
Copie e cole a política.
Clique em Próxima > Criar política.
Acesse IAM > Funções > Criar função > Serviço da AWS > Lambda.
Anexe a política recém-criada.
Nomeie a função como
WriteWorkdayToS3Role
e clique em Criar função.
Criar a função Lambda
Configuração | Valor |
---|---|
Nome | workday_audit_to_s3 |
Ambiente de execução | Python 3.13 |
Arquitetura | x86_64 |
Função de execução | WriteWorkdayToS3Role |
Depois que a função for criada, abra a guia Código, exclua o stub e cole o código abaixo (
workday_audit_to_s3.py
).#!/usr/bin/env python3 import os, json, gzip, io, uuid, base64, datetime as dt, urllib.request, urllib.error import boto3 WD_USER = os.environ["WD_USER"] WD_PASS = os.environ["WD_PASS"] WD_URL = os.environ["WD_URL"] S3_BUCKET = os.environ["S3_BUCKET_NAME"] def fetch_report() -> bytes: credentials = f"{WD_USER}:{WD_PASS}".encode() auth_header = b"Basic " + base64.b64encode(credentials) req = urllib.request.Request(WD_URL, headers={"Authorization": auth_header.decode()}) with urllib.request.urlopen(req, timeout=30) as r: return r.read() # raw JSON bytes def upload(payload: bytes, ts: dt.datetime) -> None: key = f"{ts:%Y/%m/%d}/workday-audit-{uuid.uuid4()}.json.gz" buf = io.BytesIO() with gzip.GzipFile(fileobj=buf, mode="w") as gz: gz.write(payload) buf.seek(0) boto3.client("s3").upload_fileobj(buf, S3_BUCKET, key) def lambda_handler(event=None, context=None): now = dt.datetime.utcnow().replace(microsecond=0) data = fetch_report() upload(data, now) print(f"Uploaded Workday audit report ({len(data)} bytes raw)") if __name__ == "__main__": lambda_handler()
Acesse Configuração > Variáveis de ambiente > Editar > Adicionar nova variável de ambiente.
Insira as seguintes variáveis de ambiente, substituindo pelo seu valor.
Variáveis de ambiente
Chave Exemplos de valores WD_USER
audit_s3_user
WD_PASS
Wrokday-Password
WD_URL
https://.../Audit_Trail_BP_JSON?format=json
S3_BUCKET_NAME
workday-audit-logs
Depois que a função for criada, permaneça na página dela ou abra Lambda > Functions > sua‑função.
Selecione a guia Configuração.
No painel Configuração geral, clique em Editar.
Mude Tempo limite para 5 minutos (300 segundos) e clique em Salvar.
Programar a função do Lambda (EventBridge Scheduler)
- Acesse Configuração > Gatilhos > Adicionar gatilho > EventBridge Scheduler > Criar regra.
- Informe os seguintes detalhes de configuração:
- Nome:
daily-workday-audit export
. - Padrão de programação: expressão cron.
- Expressão:
20 2 * * ? *
(executado diariamente às 02h20 UTC).
- Nome:
- Deixe o restante como padrão e clique em Criar.
Configurar um feed no Google SecOps para ingerir registros de auditoria do Workday
- Acesse Configurações do SIEM > Feeds.
- Clique em + Adicionar novo feed.
- No campo Nome do feed, insira um nome para o feed (por exemplo,
Workday Audit Logs
). - Selecione Amazon S3 V2 como o Tipo de origem.
- Selecione Auditoria do Workday como o Tipo de registro.
- Clique em Gerar uma conta de serviço.
- Clique em Próxima.
- Especifique valores para os seguintes parâmetros de entrada:
- URI do S3: o URI do bucket
s3://workday-audit-logs/
.- Substitua
workday-audit-logs
pelo nome real do bucket.
- Substitua
- 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.
- URI do S3: o URI do bucket
- Clique em Próxima.
- 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 |
---|---|---|
Account |
metadata.event_type | Se o campo "Account" não estiver vazio, o campo "metadata.event_type" será definido como "USER_RESOURCE_UPDATE_CONTENT". |
Account |
principal.user.primaryId | O userid é extraído do campo "Conta" usando um padrão grok e mapeado para principal.user.primaryId . |
Account |
principal.user.primaryName | O nome de exibição do usuário é extraído do campo "Conta" usando um padrão grok e mapeado para "principal.user.primaryName". |
ActivityCategory |
metadata.event_type | Se o campo "ActivityCategory" for "READ", o campo "metadata.event_type" será definido como "RESOURCE_READ". Se for "WRITE", será definido como "RESOURCE_WRITTEN". |
ActivityCategory |
metadata.product_event_type | Mapeado diretamente do campo "ActivityCategory". |
AffectedGroups |
target.user.group_identifiers | Mapeado diretamente do campo "AffectedGroups". |
Area |
target.resource.attribute.labels.area.value | Mapeado diretamente do campo "Área". |
AuthType |
extensions.auth.auth_details | Mapeado diretamente do campo "AuthType". |
AuthType |
extensions.auth.type | Mapeado do campo "AuthType" para diferentes tipos de autenticação definidos na UDM com base em valores específicos. |
CFIPdeConexion |
src.domain.name | Se o campo "CFIPdeConexion" não for um endereço IP válido, ele será mapeado para "src.domain.name". |
CFIPdeConexion |
target.ip | Se o campo "CFIPdeConexion" for um endereço IP válido, ele será mapeado para "target.ip". |
ChangedRelationship |
metadata.description | Mapeado diretamente do campo "ChangedRelationship". |
ClassOfInstance |
target.resource.attribute.labels.class_instance.value | Mapeado diretamente do campo "ClassOfInstance". |
column18 |
about.labels.utub.value | Mapeado diretamente do campo "column18". |
CreatedBy |
principal.user.userid | O userid é extraído do campo "CreatedBy" usando um padrão grok e mapeado para "principal.user.userid". |
CreatedBy |
principal.user.user_display_name | O nome de exibição do usuário é extraído do campo "CreatedBy" usando um padrão grok e mapeado para "principal.user.user_display_name". |
Domain |
about.domain.name | Mapeado diretamente do campo "Domínio". |
EffectiveDate |
@timestamp | Analisado como "@timestamp" após a conversão para o formato "aaaa-MM-dd HH:mm:ss.SSSZ". |
EntryMoment |
@timestamp | Analisado como "@timestamp" após a conversão para o formato "ISO8601". |
EventType |
security_result.description | Mapeado diretamente do campo "EventType". |
Form |
target.resource.name | Mapeado diretamente do campo "Formulário". |
InstancesAdded |
about.resource.attribute.labels.instances_added.value | Mapeado diretamente do campo "InstancesAdded". |
InstancesAdded |
target.user.attribute.roles.instances_added.name | Mapeado diretamente do campo "InstancesAdded". |
InstancesRemoved |
about.resource.attribute.labels.instances_removed.value | Mapeado diretamente do campo "InstancesRemoved". |
InstancesRemoved |
target.user.attribute.roles.instances_removed.name | Mapeado diretamente do campo "InstancesRemoved". |
IntegrationEvent |
target.resource.attribute.labels.integration_event.value | Mapeado diretamente do campo "IntegrationEvent". |
IntegrationStatus |
security_result.action_details | Mapeado diretamente do campo "IntegrationStatus". |
IntegrationSystem |
target.resource.name | Mapeado diretamente do campo "IntegrationSystem". |
IP |
src.domain.name | Se o campo "IP" não for um endereço IP válido, ele será mapeado para "src.domain.name". |
IP |
src.ip | Se o campo "IP" for um endereço IP válido, ele será mapeado para "src.ip". |
IsDeviceManaged |
additional.fields.additional1.value.string_value | Se o campo "IsDeviceManaged" for "N", o valor será definido como "Successful". Caso contrário, ela será definida como "Falha no login". |
IsDeviceManaged |
additional.fields.additional2.value.string_value | Se o campo "IsDeviceManaged" for "N", o valor será definido como "Successful". Caso contrário, será definido como "Credenciais inválidas". |
IsDeviceManaged |
additional.fields.additional3.value.string_value | Se o campo "IsDeviceManaged" for "N", o valor será definido como "Successful". Caso contrário, será definido como "Conta bloqueada". |
IsDeviceManaged |
security_result.action_details | Mapeado diretamente do campo "IsDeviceManaged". |
OutputFiles |
about.file.full_path | Mapeado diretamente do campo "OutputFiles". |
Person |
principal.user.primaryId | Se o campo "Person" começar com "INT", o userid será extraído usando um padrão grok e mapeado para "principal.user.primaryId". |
Person |
principal.user.primaryName | Se o campo "Person" começar com "INT", o nome de exibição do usuário será extraído usando um padrão grok e mapeado para "principal.user.primaryName". |
Person |
principal.user.user_display_name | Se o campo "Person" não começar com "INT", ele será mapeado diretamente para "principal.user.user_display_name". |
Person |
metadata.event_type | Se o campo "Person" não estiver vazio, o campo "metadata.event_type" será definido como "USER_RESOURCE_UPDATE_CONTENT". |
ProcessedTransaction |
target.resource.attribute.creation_time | Analisado como "target.resource.attribute.creation_time" após a conversão para o formato "dd/MM/yyyy HH:mm:ss,SSS (ZZZ)", "dd/MM/yyyy, HH:mm:ss,SSS (ZZZ)" ou "MM/dd/yyyy, HH:mm:ss.SSS A ZZZ". |
ProgramBy |
principal.user.userid | Mapeado diretamente do campo "ProgramBy". |
RecurrenceEndDate |
principal.resource.attribute.last_update_time | Analisado como "principal.resource.attribute.last_update_time" após a conversão para o formato "aaaa-MM-dd". |
RecurrenceStartDate |
principal.resource.attribute.creation_time | Analisado como "principal.resource.attribute.creation_time" após a conversão para o formato "aaaa-MM-dd". |
RequestName |
metadata.description | Mapeado diretamente do campo "RequestName". |
ResponseMessage |
security_result.summary | Mapeado diretamente do campo "ResponseMessage". |
RestrictedToEnvironment |
security_result.about.hostname | Mapeado diretamente do campo "RestrictedToEnvironment". |
RevokedSecurity |
security_result.outcomes.outcomes.value | Mapeado diretamente do campo "RevokedSecurity". |
RunFrequency |
principal.resource.attribute.labels.run_frequency.value | Mapeado diretamente do campo "RunFrequency". |
ScheduledProcess |
principal.resource.name | Mapeado diretamente do campo "ScheduledProcess". |
SecuredTaskExecuted |
target.resource.name | Mapeado diretamente do campo "SecuredTaskExecuted". |
SecureTaskExecuted |
metadata.event_type | Se o campo "SecureTaskExecuted" contiver "Create", o campo "metadata.event_type" será definido como "USER_RESOURCE_CREATION". |
SecureTaskExecuted |
target.resource.name | Mapeado diretamente do campo "SecureTaskExecuted". |
SentTime |
@timestamp | Analisado como "@timestamp" após a conversão para o formato "ISO8601". |
SessionId |
network.session_id | Mapeado diretamente do campo "SessionId". |
ShareBy |
target.user.userid | Mapeado diretamente do campo "ShareBy". |
SignOffTime |
additional.fields.additional4.value.string_value | O valor do campo "AuthFailMessage" é colocado na matriz "additional.fields" com a chave "Enterprise Interface Builder". |
SignOffTime |
metadata.description | Mapeado diretamente do campo "AuthFailMessage". |
SignOffTime |
metadata.event_type | Se o campo "SignOffTime" estiver vazio, o campo "metadata.event_type" será definido como "USER_LOGIN". Caso contrário, ele será definido como "USER_LOGOUT". |
SignOffTime |
principal.user.attribute.last_update_time | Analisado como "principal.user.attribute.last_update_time" após a conversão para o formato "ISO8601". |
SignOnIp |
src.domain.name | Se o campo "SignOnIp" não for um endereço IP válido, ele será mapeado para "src.domain.name". |
SignOnIp |
src.ip | Se o campo "SignOnIp" for um endereço IP válido, ele será mapeado para "src.ip". |
Status |
metadata.product_event_type | Mapeado diretamente do campo "Status". |
SystemAccount |
principal.user.email_addresses | O endereço de e-mail é extraído do campo "SystemAccount" usando um padrão grok e mapeado para "principal.user.email_addresses". |
SystemAccount |
principal.user.primaryId | O userid é extraído do campo "SystemAccount" usando um padrão grok e mapeado para "principal.user.primaryId". |
SystemAccount |
principal.user.primaryName | O nome de exibição do usuário é extraído do campo "SystemAccount" usando um padrão grok e mapeado para "principal.user.primaryName". |
SystemAccount |
src.user.userid | O userid secundário é extraído do campo "SystemAccount" usando um padrão grok e mapeado para "src.user.userid". |
SystemAccount |
src.user.user_display_name | O nome de exibição do usuário secundário é extraído do campo "SystemAccount" usando um padrão grok e mapeado para "src.user.user_display_name". |
SystemAccount |
target.user.userid | O userid de destino é extraído do campo "SystemAccount" usando um padrão grok e mapeado para "target.user.userid". |
Target |
target.user.user_display_name | Mapeado diretamente do campo "Segmentação". |
Template |
about.resource.name | Mapeado diretamente do campo "Modelo". |
Tenant |
target.asset.hostname | Mapeado diretamente do campo "Locatário". |
TlsVersion |
network.tls.version | Mapeado diretamente do campo "TlsVersion". |
Transaction |
security_result.action_details | Mapeado diretamente do campo "Transação". |
TransactionType |
security_result.summary | Mapeado diretamente do campo "TransactionType". |
TypeForm |
target.resource.resource_subtype | Mapeado diretamente do campo "TypeForm". |
UserAgent |
network.http.parsed_user_agent | Analisado do campo "UserAgent" usando o filtro "useragent". |
UserAgent |
network.http.user_agent | Mapeado diretamente do campo "UserAgent". |
WorkdayAccount |
target.user.user_display_name | O nome de exibição do usuário é extraído do campo "WorkdayAccount" usando um padrão grok e mapeado para "target.user.user_display_name". |
WorkdayAccount |
target.user.userid | O userid é extraído do campo "WorkdayAccount" usando um padrão grok e mapeado para "target.user.userid". |
additional.fields.additional1.key | Definido como "FailedSignOn". | |
additional.fields.additional2.key | Definido como "InvalidCredentials". | |
additional.fields.additional3.key | Definido como "AccountLocked". | |
additional.fields.additional4.key | Defina como "Enterprise Interface Builder". | |
metadata.event_type | Definido como "GENERIC_EVENT" inicialmente e atualizado com base na lógica que envolve outros campos. | |
metadata.event_type | Definido como "USER_CHANGE_PERMISSIONS" para tipos de eventos específicos. | |
metadata.event_type | Definido como "RESOURCE_WRITTEN" para tipos de eventos específicos. | |
metadata.log_type | Codificado como "WORKDAY_AUDIT". | |
metadata.product_name | Codificado como "Enterprise Interface Builder". | |
metadata.vendor_name | Codificado como "Workday". | |
principal.asset.category | Definido como "Phone" se o campo "DeviceType" for "Phone". | |
principal.resource.resource_type | Codificado como "TASK" se o campo "ScheduledProcess" não estiver vazio. | |
security_result.action | Definido como "ALLOW" ou "FAIL" com base nos valores dos campos "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" e "AccountLocked". | |
security_result.summary | Definido como "Concluído" ou mensagens de erro específicas com base nos valores dos campos "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" e "AccountLocked". | |
target.resource.resource_type | Codificado como "TASK" para tipos de eventos específicos. | |
target.resource.resource_type | Codificado como "DATASET" se o campo "TypeForm" não estiver vazio. | |
message |
principal.user.email_addresses | Extrai o endereço de e-mail do campo "message" usando um padrão grok e o mescla em "principal.user.email_addresses" se um padrão específico for correspondente. |
message |
src.user.userid | Limpa o campo se "event.idm.read_only_udm.principal.user.userid" corresponder ao "user_target" extraído do campo "message". |
message |
src.user.user_display_name | Limpa o campo se "event.idm.read_only_udm.principal.user.userid" corresponder ao "user_target" extraído do campo "message". |
message |
target.user.userid | Extrai o userid do campo "message" usando um padrão grok e o mapeia para "target.user.userid" se um padrão específico for correspondente. |
Precisa de mais ajuda? Receba respostas de membros da comunidade e profissionais do Google SecOps.