Recolha registos de auditoria do Workday
Este documento explica como carregar registos de auditoria do Workday para o Google Security Operations através do AWS S3. O analisador identifica primeiro o tipo de evento específico nos registos com base na análise de padrões dos dados CSV. Em seguida, 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
Certifique-se de que tem os seguintes pré-requisitos:
- Instância do Google SecOps
- Acesso privilegiado ao AWS
- Acesso privilegiado ao Workday
Configure o contentor do AWS S3 e o IAM para o Google SecOps
- Crie um contentor do Amazon S3 seguindo este manual do utilizador: Criar um contentor.
- Guarde o nome e a região do contentor para referência futura (por exemplo,
workday-audit-logs
). - Crie um utilizador seguindo este guia do utilizador: criar um utilizador do IAM.
- Selecione o utilizador criado.
- Selecione o separador Credenciais de segurança.
- Clique em Criar chave de acesso na secção Chaves de acesso.
- Selecione Serviço de terceiros como Exemplo de utilização.
- Clicar em Seguinte.
- Opcional: adicione a etiqueta de descrição.
- Clique em Criar chave de acesso.
- Clique em Transferir ficheiro CSV para guardar a chave de acesso e a chave de acesso secreta para referência futura.
- Clique em Concluído.
- Selecione o separador Autorizações.
- Clique em Adicionar autorizações na secção Políticas de autorizações.
- Selecione Adicionar autorizações.
- Selecione Anexar políticas diretamente.
- Pesquise e selecione a política AmazonS3FullAccess.
- Clicar em Seguinte.
- Clique em Adicionar autorizações.
Crie um utilizador do sistema de integração (ISU) do Workday
- No Workday, pesquise Create Integration System User > OK.
- Preencha o campo Nome de utilizador (por exemplo,
audit_s3_user
). - Clique em OK.
- Reponha a palavra-passe acedendo a Ações relacionadas > Segurança > Repor palavra-passe.
- Selecione Manter regras de palavras-passe para evitar que a palavra-passe expire.
- Pesquise Criar grupo de segurança > Grupo de segurança do sistema de integração (sem restrições).
- Indique um nome (por exemplo,
ISU_Audit_S3
) e adicione o ISU a Integration System Users. - Pesquise Políticas de segurança de domínio para a área funcional > Sistema.
- Para Rasto de auditoria, selecione Ações > Editar autorizações.
- Em Obter apenas, adicione o grupo
ISU_Audit_S3
. - Clique em OK > Ativar alterações pendentes da política de segurança.
Configure o relatório personalizado do Workday
- No Workday, pesquise Create Custom Report.
- Indique os seguintes detalhes de configuração:
- Nome: introduza um nome exclusivo (por exemplo,
Audit_Trail_BP_JSON
). - Tipo: selecione Avançadas.
- Origem de dados: selecione Rasto de auditoria – Processo empresarial.
- Clique em OK.
- Opcional: adicione filtros em Tipo de processo empresarial ou Data de entrada em vigor.
- Nome: introduza um nome exclusivo (por exemplo,
- Aceda ao separador Saída.
- Selecione Ativar como serviço Web, Otimizado para o desempenho e selecione Formato JSON.
- Clique em OK > Concluído.
- Abra o relatório e clique em Partilhar > adicione
ISU_Audit_S3
com autorização de visualização > OK. - Aceda a Ações relacionadas > Serviço 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
).
Configure a política e a função de IAM para carregamentos do S3
JSON da política (substitua
workday-audit-logs
se tiver introduzido um nome de contentor diferente):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutWorkdayObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::workday-audit-logs/*" } ] }
Aceda a AWS console > IAM > Policies > Create policy > separador JSON.
Copie e cole a política.
Clique em Seguinte > Criar política.
Aceda a IAM > Funções > Criar função > Serviço AWS > Lambda.
Anexe a política criada recentemente.
Dê o nome
WriteWorkdayToS3Role
à função e clique em Criar função.
Crie a função Lambda
Definição | Valor |
---|---|
Nome | workday_audit_to_s3 |
Runtime | Python 3.13 |
Arquitetura | x86_64 |
Função de execução | WriteWorkdayToS3Role |
Depois de criar a função, abra o separador Código, elimine o fragmento de código 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()
Aceda a Configuração > Variáveis de ambiente > Editar > Adicionar nova variável de ambiente.
Introduza as seguintes variáveis de ambiente, substituindo-as 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 de criar a função, permaneça na respetiva página (ou abra Lambda > Functions > your‑function).
Selecione o separador Configuração.
No painel Configuração geral, clique em Editar.
Altere Tempo limite para 5 minutos (300 segundos) e clique em Guardar.
Agende a função Lambda (EventBridge Scheduler)
- Aceda a Configuration > Triggers > Add trigger > EventBridge Scheduler > Create rule.
- Indique os seguintes detalhes de configuração:
- Nome:
daily-workday-audit export
. - Padrão de agendamento: expressão cron.
- Expressão:
20 2 * * ? *
(executado diariamente às 02:20 UTC).
- Nome:
- Deixe o resto como predefinição e clique em Criar.
Configure um feed no Google SecOps para carregar registos de auditoria do Workday
- Aceda a Definições do SIEM > Feeds.
- Clique em + Adicionar novo feed.
- No campo Nome do feed, introduza um nome para o feed (por exemplo,
Workday Audit Logs
). - Selecione Amazon S3 V2 como o Tipo de origem.
- Selecione Workday Audit como o Tipo de registo.
- Clique em Obter uma conta de serviço.
- Clicar em Seguinte.
- Especifique valores para os seguintes parâmetros de entrada:
- URI do S3: o URI do contentor
s3://workday-audit-logs/
.- Substitua
workday-audit-logs
pelo nome real do contentor.
- Substitua
- 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.
- URI do S3: o URI do contentor
- Clicar em Seguinte.
- 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 de UDM | Lógica |
---|---|---|
Account |
metadata.event_type | Se o campo "Account" não estiver vazio, o campo "metadata.event_type" é definido como "USER_RESOURCE_UPDATE_CONTENT". |
Account |
principal.user.primaryId | O ID do utilizador é extraído do campo "Conta" através de um padrão grok e mapeado para principal.user.primaryId . |
Account |
principal.user.primaryName | O nome a apresentar do utilizador é extraído do campo "Conta" através de 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" é definido como "RESOURCE_READ". Se for "WRITE", é definido como "RESOURCE_WRITTEN". |
ActivityCategory |
metadata.product_event_type | Mapeado diretamente a partir do campo "ActivityCategory". |
AffectedGroups |
target.user.group_identifiers | Mapeado diretamente a partir do campo "AffectedGroups". |
Area |
target.resource.attribute.labels.area.value | Mapeado diretamente a partir do campo "Área". |
AuthType |
extensions.auth.auth_details | Mapeado diretamente a partir do campo "AuthType". |
AuthType |
extensions.auth.type | Mapeado do campo "AuthType" para diferentes tipos de autenticação definidos no UDM com base em valores específicos. |
CFIPdeConexion |
src.domain.name | Se o campo "CFIPdeConexion" não for um endereço IP válido, é mapeado para "src.domain.name". |
CFIPdeConexion |
target.ip | Se o campo "CFIPdeConexion" for um endereço IP válido, é mapeado para "target.ip". |
ChangedRelationship |
metadata.description | Mapeado diretamente a partir do campo "ChangedRelationship". |
ClassOfInstance |
target.resource.attribute.labels.class_instance.value | Mapeado diretamente a partir do campo "ClassOfInstance". |
column18 |
about.labels.utub.value | Mapeado diretamente a partir do campo "column18". |
CreatedBy |
principal.user.userid | O userid é extraído do campo "CreatedBy" através de um padrão grok e mapeado para "principal.user.userid". |
CreatedBy |
principal.user.user_display_name | O nome a apresentar do utilizador é extraído do campo "CreatedBy" através de um padrão grok e mapeado para "principal.user.user_display_name". |
Domain |
about.domain.name | Mapeado diretamente a partir do campo "Domínio". |
EffectiveDate |
@timestamp | Analisado para "@timestamp" após a conversão para o formato "aaaa-MM-dd HH:mm:ss.SSSZ". |
EntryMoment |
@timestamp | Analisado para "@timestamp" após a conversão para o formato "ISO8601". |
EventType |
security_result.description | Mapeado diretamente a partir do campo "EventType". |
Form |
target.resource.name | Mapeado diretamente a partir do campo "Formulário". |
InstancesAdded |
about.resource.attribute.labels.instances_added.value | Mapeado diretamente a partir do campo "InstancesAdded". |
InstancesAdded |
target.user.attribute.roles.instances_added.name | Mapeado diretamente a partir do campo "InstancesAdded". |
InstancesRemoved |
about.resource.attribute.labels.instances_removed.value | Mapeado diretamente a partir do campo "InstancesRemoved". |
InstancesRemoved |
target.user.attribute.roles.instances_removed.name | Mapeado diretamente a partir do campo "InstancesRemoved". |
IntegrationEvent |
target.resource.attribute.labels.integration_event.value | Mapeado diretamente a partir do campo "IntegrationEvent". |
IntegrationStatus |
security_result.action_details | Mapeado diretamente a partir do campo "IntegrationStatus". |
IntegrationSystem |
target.resource.name | Mapeado diretamente a partir do campo "IntegrationSystem". |
IP |
src.domain.name | Se o campo "IP" não for um endereço IP válido, é mapeado para "src.domain.name". |
IP |
src.ip | Se o campo "IP" for um endereço IP válido, é mapeado para "src.ip". |
IsDeviceManaged |
additional.fields.additional1.value.string_value | Se o campo "IsDeviceManaged" for "N", o valor é definido como "Successful". Caso contrário, é definido como "Ocorreu uma tentativa de início de sessão falhada". |
IsDeviceManaged |
additional.fields.additional2.value.string_value | Se o campo "IsDeviceManaged" for "N", o valor é definido como "Successful". Caso contrário, é definido como "Credenciais inválidas". |
IsDeviceManaged |
additional.fields.additional3.value.string_value | Se o campo "IsDeviceManaged" for "N", o valor é definido como "Successful". Caso contrário, é definido como "Conta bloqueada". |
IsDeviceManaged |
security_result.action_details | Mapeado diretamente a partir do campo "IsDeviceManaged". |
OutputFiles |
about.file.full_path | Mapeado diretamente a partir do campo "OutputFiles". |
Person |
principal.user.primaryId | Se o campo "Person" começar por "INT", o userid é extraído através de um padrão grok e mapeado para "principal.user.primaryId". |
Person |
principal.user.primaryName | Se o campo "Person" começar por "INT", o nome a apresentar do utilizador é extraído através de um padrão grok e mapeado para "principal.user.primaryName". |
Person |
principal.user.user_display_name | Se o campo "Person" não começar por "INT", é 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" é definido como "USER_RESOURCE_UPDATE_CONTENT". |
ProcessedTransaction |
target.resource.attribute.creation_time | Analisado para "target.resource.attribute.creation_time" após a conversão para o formato "dd/MM/aaaa HH:mm:ss,SSS (ZZZ)", "dd/MM/aaaa, HH:mm:ss,SSS (ZZZ)" ou "MM/dd/aaaa, HH:mm:ss.SSS A ZZZ". |
ProgramBy |
principal.user.userid | Mapeado diretamente a partir do campo "ProgramBy". |
RecurrenceEndDate |
principal.resource.attribute.last_update_time | Analisado para "principal.resource.attribute.last_update_time" após a conversão para o formato "aaaa-MM-dd". |
RecurrenceStartDate |
principal.resource.attribute.creation_time | Analisado para "principal.resource.attribute.creation_time" após a conversão para o formato "aaaa-MM-dd". |
RequestName |
metadata.description | Mapeado diretamente a partir do campo "RequestName". |
ResponseMessage |
security_result.summary | Mapeado diretamente a partir do campo "ResponseMessage". |
RestrictedToEnvironment |
security_result.about.hostname | Mapeado diretamente a partir do campo "RestrictedToEnvironment". |
RevokedSecurity |
security_result.outcomes.outcomes.value | Mapeado diretamente a partir do campo "RevokedSecurity". |
RunFrequency |
principal.resource.attribute.labels.run_frequency.value | Mapeado diretamente a partir do campo "RunFrequency". |
ScheduledProcess |
principal.resource.name | Mapeado diretamente a partir do campo "ScheduledProcess". |
SecuredTaskExecuted |
target.resource.name | Mapeado diretamente a partir do campo "SecuredTaskExecuted". |
SecureTaskExecuted |
metadata.event_type | Se o campo "SecureTaskExecuted" contiver "Create", o campo "metadata.event_type" é definido como "USER_RESOURCE_CREATION". |
SecureTaskExecuted |
target.resource.name | Mapeado diretamente a partir do campo "SecureTaskExecuted". |
SentTime |
@timestamp | Analisado para "@timestamp" após a conversão para o formato "ISO8601". |
SessionId |
network.session_id | Mapeado diretamente a partir do campo "SessionId". |
ShareBy |
target.user.userid | Mapeado diretamente a partir 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 a partir do campo "AuthFailMessage". |
SignOffTime |
metadata.event_type | Se o campo "SignOffTime" estiver vazio, o campo "metadata.event_type" é definido como "USER_LOGIN". Caso contrário, é definido como "USER_LOGOUT". |
SignOffTime |
principal.user.attribute.last_update_time | Analisado para "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, é mapeado para "src.domain.name". |
SignOnIp |
src.ip | Se o campo "SignOnIp" for um endereço IP válido, é mapeado para "src.ip". |
Status |
metadata.product_event_type | Mapeado diretamente a partir do campo "Estado". |
SystemAccount |
principal.user.email_addresses | O endereço de email é extraído do campo "SystemAccount" através de um padrão grok e mapeado para "principal.user.email_addresses". |
SystemAccount |
principal.user.primaryId | O ID do utilizador é extraído do campo "SystemAccount" através de um padrão grok e mapeado para "principal.user.primaryId". |
SystemAccount |
principal.user.primaryName | O nome a apresentar do utilizador é extraído do campo "SystemAccount" através de um padrão grok e mapeado para "principal.user.primaryName". |
SystemAccount |
src.user.userid | O ID do utilizador secundário é extraído do campo "SystemAccount" através de um padrão grok e mapeado para "src.user.userid". |
SystemAccount |
src.user.user_display_name | O nome a apresentar do utilizador secundário é extraído do campo "SystemAccount" através de um padrão grok e mapeado para "src.user.user_display_name". |
SystemAccount |
target.user.userid | O ID do utilizador de destino é extraído do campo "SystemAccount" através de um padrão grok e mapeado para "target.user.userid". |
Target |
target.user.user_display_name | Mapeado diretamente a partir do campo "Destino". |
Template |
about.resource.name | Mapeado diretamente a partir do campo "Modelo". |
Tenant |
target.asset.hostname | Mapeado diretamente a partir do campo "Inquilino". |
TlsVersion |
network.tls.version | Mapeado diretamente a partir do campo "TlsVersion". |
Transaction |
security_result.action_details | Mapeado diretamente a partir do campo "Transação". |
TransactionType |
security_result.summary | Mapeado diretamente a partir do campo "TransactionType". |
TypeForm |
target.resource.resource_subtype | Mapeado diretamente a partir do campo "TypeForm". |
UserAgent |
network.http.parsed_user_agent | Analisado a partir do campo "UserAgent" através do filtro "useragent". |
UserAgent |
network.http.user_agent | Mapeado diretamente a partir do campo "UserAgent". |
WorkdayAccount |
target.user.user_display_name | O nome a apresentar do utilizador é extraído do campo "WorkdayAccount" através de um padrão grok e mapeado para "target.user.user_display_name". |
WorkdayAccount |
target.user.userid | O ID do utilizador é extraído do campo "WorkdayAccount" através de 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 | Definido como "Enterprise Interface Builder". | |
metadata.event_type | Inicialmente, definido como "GENERIC_EVENT" e, em seguida, 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 de forma rígida para "WORKDAY_AUDIT". | |
metadata.product_name | Codificado de forma rígida para "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 "Successful" ou mensagens de erro específicas com base nos valores dos campos "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" e "AccountLocked". | |
target.resource.resource_type | Codificado de forma rígida 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 email do campo "message" através de um padrão grok e junta-o a "principal.user.email_addresses" se for encontrada uma correspondência com um padrão específico. |
message |
src.user.userid | Limpa o campo se o campo "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 o campo "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" através de um padrão grok e mapeia-o para "target.user.userid" se for encontrada uma correspondência com um padrão específico. |
Precisa de mais ajuda? Receba respostas de membros da comunidade e profissionais da Google SecOps.