Raccogliere i log di controllo di Workday
Questo documento spiega come importare i log di controllo di Workday in Google Security Operations utilizzando AWS S3. Il parser identifica innanzitutto il tipo di evento specifico dai log in base all'analisi dei pattern dei dati CSV. Poi, estrae e struttura i campi pertinenti in base al tipo identificato, mappandoli a un modello UDM (Unified Data Model) per un'analisi di sicurezza coerente.
Prima di iniziare
Assicurati di soddisfare i seguenti prerequisiti:
- Istanza Google SecOps
- Accesso privilegiato ad AWS
- Accesso con privilegi a Workday
Configura il bucket AWS S3 e IAM per Google SecOps
- Crea un bucket Amazon S3 seguendo questa guida utente: Creazione di un bucket.
- Salva il nome e la regione del bucket per riferimento futuro (ad esempio,
workday-audit-logs
). - Crea un utente seguendo questa guida utente: Creazione di un utente IAM.
- Seleziona l'utente creato.
- Seleziona la scheda Credenziali di sicurezza.
- Fai clic su Crea chiave di accesso nella sezione Chiavi di accesso.
- Seleziona Servizio di terze parti come Caso d'uso.
- Fai clic su Avanti.
- (Facoltativo) Aggiungi il tag della descrizione.
- Fai clic su Crea chiave di accesso.
- Fai clic su Scarica file CSV per salvare la chiave di accesso e la chiave di accesso segreta per riferimento futuro.
- Fai clic su Fine.
- Seleziona la scheda Autorizzazioni.
- Fai clic su Aggiungi autorizzazioni nella sezione Criteri per le autorizzazioni.
- Seleziona Aggiungi autorizzazioni.
- Seleziona Allega direttamente i criteri.
- Cerca e seleziona il criterio AmazonS3FullAccess.
- Fai clic su Avanti.
- Fai clic su Aggiungi autorizzazioni.
Crea l'utente di sistema di integrazione (ISU) di Workday
- In Workday, cerca Create Integration System User > Ok.
- Compila il campo Nome utente (ad esempio,
audit_s3_user
). - Fai clic su OK.
- Reimposta la password andando su Azioni correlate > Sicurezza > Reimposta password.
- Seleziona Mantieni regole password per impedire la scadenza della password.
- Cerca Create Security Group > Integration System Security Group (Unconstrained).
- Fornisci un nome (ad esempio
ISU_Audit_S3
) e aggiungi l'utente di sistema integrato a Integration System Users (Utenti di sistema integrato). - Cerca Domain Security Policies for Functional Area > System.
- Per Audit Trail, seleziona Azioni > Modifica autorizzazioni.
- In Ottieni solo, aggiungi il gruppo
ISU_Audit_S3
. - Fai clic su Ok > Attiva modifiche in attesa del criterio di sicurezza.
Configurare il report personalizzato di Workday
- In Workday, cerca Crea report personalizzato.
- Fornisci i seguenti dettagli di configurazione:
- Nome: inserisci un nome univoco (ad esempio,
Audit_Trail_BP_JSON
). - Tipo: seleziona Avanzate.
- Origine dati: seleziona Audit trail - Business Process.
- Fai clic su OK.
- (Facoltativo) Aggiungi filtri per Tipo di processo aziendale o Data di validitร .
- Nome: inserisci un nome univoco (ad esempio,
- Vai alla scheda Output.
- Seleziona Attiva come servizio web, Ottimizzato per le prestazioni e Formato JSON.
- Fai clic su Ok > Fine.
- Apri il report e fai clic su Condividi > aggiungi
ISU_Audit_S3
con autorizzazione di visualizzazione > Ok. - Vai ad Azioni correlate > Servizio web > Visualizza URL.
- Copia l'URL JSON (ad esempio,
https://wd-services1.workday.com/ccx/service/customreport2/<tenant>/<user>/Audit_Trail_BP_JSON?format=json
).
Configura il ruolo e il criterio IAM per i caricamenti S3
JSON delle policy (sostituisci
workday-audit-logs
se hai inserito un nome del bucket diverso):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutWorkdayObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::workday-audit-logs/*" } ] }
Vai alla console AWS > IAM > Policy > Crea policy > scheda JSON.
Copia e incolla le norme.
Fai clic su Avanti > Crea policy.
Vai a IAM > Ruoli > Crea ruolo > Servizio AWS > Lambda.
Allega il criterio appena creato.
Assegna al ruolo il nome
WriteWorkdayToS3Role
e fai clic su Crea ruolo.
Crea la funzione Lambda
Impostazione | Valore |
---|---|
Nome | workday_audit_to_s3 |
Tempo di esecuzione | Python 3.13 |
Architettura | x86_64 |
Ruolo di esecuzione | WriteWorkdayToS3Role |
Dopo aver creato la funzione, apri la scheda Codice, elimina lo stub e incolla il codice riportato di seguito (
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()
Vai a Configurazione > Variabili di ambiente > Modifica > Aggiungi nuova variabile di ambiente.
Inserisci le seguenti variabili di ambiente, sostituendo il valore con il tuo.
Variabili di ambiente
Chiave Valori di esempio WD_USER
audit_s3_user
WD_PASS
Wrokday-Password
WD_URL
https://.../Audit_Trail_BP_JSON?format=json
S3_BUCKET_NAME
workday-audit-logs
Dopo aver creato la funzione, rimani sulla relativa pagina (o apri Lambda > Funzioni > la tua funzione).
Seleziona la scheda Configurazione.
Nel riquadro Configurazione generale, fai clic su Modifica.
Modifica Timeout impostando 5 minuti (300 secondi) e fai clic su Salva.
Pianifica la funzione Lambda (EventBridge Scheduler)
- Vai a Configurazione > Trigger > Aggiungi trigger > EventBridge Scheduler > Crea regola.
- Fornisci i seguenti dettagli di configurazione:
- Nome:
daily-workday-audit export
- Pattern di pianificazione: espressione cron.
- Espressione:
20 2 * * ? *
(viene eseguita ogni giorno alle 02:20 UTC).
- Nome:
- Lascia invariate le altre impostazioni predefinite e fai clic su Crea.
Configura un feed in Google SecOps per importare gli audit log di Workday
- Vai a Impostazioni SIEM > Feed.
- Fai clic su + Aggiungi nuovo feed.
- Nel campo Nome feed, inserisci un nome per il feed (ad esempio,
Workday Audit Logs
). - Seleziona Amazon S3 V2 come Tipo di origine.
- Seleziona Audit di Workday come Tipo di log.
- Fai clic su Ottieni un service account.
- Fai clic su Avanti.
- Specifica i valori per i seguenti parametri di input:
- URI S3: l'URI del bucket
s3://workday-audit-logs/
.- Sostituisci
workday-audit-logs
con il nome effettivo del bucket.
- Sostituisci
- Opzioni di eliminazione dell'origine: seleziona l'opzione di eliminazione in base alle tue preferenze.
- Etร massima del file: includi i file modificati nell'ultimo numero di giorni. Il valore predefinito รจ 180 giorni.
- ID chiave di accesso: chiave di accesso utente con accesso al bucket S3.
- Chiave di accesso segreta: chiave segreta dell'utente con accesso al bucket S3.
- Spazio dei nomi dell'asset: lo spazio dei nomi dell'asset.
- Etichette di importazione: l'etichetta da applicare agli eventi di questo feed.
- URI S3: l'URI del bucket
- Fai clic su Avanti.
- Controlla la nuova configurazione del feed nella schermata Finalizza e poi fai clic su Invia.
Tabella di mappatura UDM
Campo log | Mappatura UDM | Logic |
---|---|---|
Account |
metadata.event_type | Se il campo "Account" non รจ vuoto, il campo "metadata.event_type" รจ impostato su "USER_RESOURCE_UPDATE_CONTENT". |
Account |
principal.user.primaryId | L'ID utente viene estratto dal campo "Account" utilizzando un pattern grok e mappato a principal.user.primaryId . |
Account |
principal.user.primaryName | Il nome visualizzato dell'utente viene estratto dal campo "Account" utilizzando un pattern grok e mappato a "principal.user.primaryName". |
ActivityCategory |
metadata.event_type | Se il campo "ActivityCategory" รจ "READ", il campo "metadata.event_type" รจ impostato su "RESOURCE_READ". Se "WRITE", รจ impostato su "RESOURCE_WRITTEN". |
ActivityCategory |
metadata.product_event_type | Mappato direttamente dal campo "ActivityCategory". |
AffectedGroups |
target.user.group_identifiers | Mappato direttamente dal campo "AffectedGroups". |
Area |
target.resource.attribute.labels.area.value | Mappato direttamente dal campo "Area". |
AuthType |
extensions.auth.auth_details | Mappato direttamente dal campo "AuthType". |
AuthType |
extensions.auth.type | Mappato dal campo "AuthType" a diversi tipi di autenticazione definiti in UDM in base a valori specifici. |
CFIPdeConexion |
src.domain.name | Se il campo "CFIPdeConexion" non รจ un indirizzo IP valido, viene mappato a "src.domain.name". |
CFIPdeConexion |
target.ip | Se il campo "CFIPdeConexion" รจ un indirizzo IP valido, viene mappato a "target.ip". |
ChangedRelationship |
metadata.description | Mappato direttamente dal campo "ChangedRelationship". |
ClassOfInstance |
target.resource.attribute.labels.class_instance.value | Mappato direttamente dal campo "ClassOfInstance". |
column18 |
about.labels.utub.value | Mappato direttamente dal campo "column18". |
CreatedBy |
principal.user.userid | L'ID utente viene estratto dal campo "CreatedBy" utilizzando un pattern grok e mappato a "principal.user.userid". |
CreatedBy |
principal.user.user_display_name | Il nome visualizzato dell'utente viene estratto dal campo "CreatedBy" utilizzando un pattern grok e mappato a "principal.user.user_display_name". |
Domain |
about.domain.name | Mappato direttamente dal campo "Dominio". |
EffectiveDate |
@timestamp | Analizzato in "@timestamp" dopo la conversione nel formato "aaaa-MM-gg HH:mm:ss.SSSZ". |
EntryMoment |
@timestamp | Analizzato in "@timestamp" dopo la conversione nel formato "ISO8601". |
EventType |
security_result.description | Mappato direttamente dal campo "EventType". |
Form |
target.resource.name | Mappato direttamente dal campo "Modulo". |
InstancesAdded |
about.resource.attribute.labels.instances_added.value | Mappato direttamente dal campo "InstancesAdded". |
InstancesAdded |
target.user.attribute.roles.instances_added.name | Mappato direttamente dal campo "InstancesAdded". |
InstancesRemoved |
about.resource.attribute.labels.instances_removed.value | Mappato direttamente dal campo "InstancesRemoved". |
InstancesRemoved |
target.user.attribute.roles.instances_removed.name | Mappato direttamente dal campo "InstancesRemoved". |
IntegrationEvent |
target.resource.attribute.labels.integration_event.value | Mappato direttamente dal campo "IntegrationEvent". |
IntegrationStatus |
security_result.action_details | Mappato direttamente dal campo "IntegrationStatus". |
IntegrationSystem |
target.resource.name | Mappato direttamente dal campo "IntegrationSystem". |
IP |
src.domain.name | Se il campo "IP" non รจ un indirizzo IP valido, viene mappato a "src.domain.name". |
IP |
src.ip | Se il campo "IP" รจ un indirizzo IP valido, viene mappato a "src.ip". |
IsDeviceManaged |
additional.fields.additional1.value.string_value | Se il campo "IsDeviceManaged" รจ "N", il valore รจ impostato su "Riuscito". In caso contrario, รจ impostato su "Accesso non riuscito". |
IsDeviceManaged |
additional.fields.additional2.value.string_value | Se il campo "IsDeviceManaged" รจ "N", il valore รจ impostato su "Riuscito". In caso contrario, รจ impostato su "Credenziali non valide". |
IsDeviceManaged |
additional.fields.additional3.value.string_value | Se il campo "IsDeviceManaged" รจ "N", il valore รจ impostato su "Riuscito". In caso contrario, รจ impostato su "Account bloccato". |
IsDeviceManaged |
security_result.action_details | Mappato direttamente dal campo "IsDeviceManaged". |
OutputFiles |
about.file.full_path | Mappato direttamente dal campo "OutputFiles". |
Person |
principal.user.primaryId | Se il campo "Person" inizia con "INT", l'ID utente viene estratto utilizzando un pattern grok e mappato a "principal.user.primaryId". |
Person |
principal.user.primaryName | Se il campo "Person" inizia con "INT", il nome visualizzato dell'utente viene estratto utilizzando un pattern grok e mappato a "principal.user.primaryName". |
Person |
principal.user.user_display_name | Se il campo "Persona" non inizia con "INT", viene mappato direttamente a "principal.user.user_display_name". |
Person |
metadata.event_type | Se il campo "Person" non รจ vuoto, il campo "metadata.event_type" รจ impostato su "USER_RESOURCE_UPDATE_CONTENT". |
ProcessedTransaction |
target.resource.attribute.creation_time | Analizzato in "target.resource.attribute.creation_time" dopo la conversione nel formato "gg/MM/aaaa HH:mm:ss,SSS (ZZZ)", "gg/MM/aaaa, HH:mm:ss,SSS (ZZZ)" o "MM/gg/aaaa, HH:mm:ss.SSS A ZZZ". |
ProgramBy |
principal.user.userid | Mappato direttamente dal campo "ProgramBy". |
RecurrenceEndDate |
principal.resource.attribute.last_update_time | Analizzato in "principal.resource.attribute.last_update_time" dopo la conversione nel formato "aaaa-MM-gg". |
RecurrenceStartDate |
principal.resource.attribute.creation_time | Analizzato come "principal.resource.attribute.creation_time" dopo la conversione nel formato "aaaa-MM-gg". |
RequestName |
metadata.description | Mappato direttamente dal campo "RequestName". |
ResponseMessage |
security_result.summary | Mappato direttamente dal campo "ResponseMessage". |
RestrictedToEnvironment |
security_result.about.hostname | Mappato direttamente dal campo "RestrictedToEnvironment". |
RevokedSecurity |
security_result.outcomes.outcomes.value | Mappato direttamente dal campo "RevokedSecurity". |
RunFrequency |
principal.resource.attribute.labels.run_frequency.value | Mappato direttamente dal campo "RunFrequency". |
ScheduledProcess |
principal.resource.name | Mappato direttamente dal campo "ScheduledProcess". |
SecuredTaskExecuted |
target.resource.name | Mappato direttamente dal campo "SecuredTaskExecuted". |
SecureTaskExecuted |
metadata.event_type | Se il campo "SecureTaskExecuted" contiene "Create", il campo "metadata.event_type" รจ impostato su "USER_RESOURCE_CREATION". |
SecureTaskExecuted |
target.resource.name | Mappato direttamente dal campo "SecureTaskExecuted". |
SentTime |
@timestamp | Analizzato in "@timestamp" dopo la conversione nel formato "ISO8601". |
SessionId |
network.session_id | Mappato direttamente dal campo "SessionId". |
ShareBy |
target.user.userid | Mappato direttamente dal campo "ShareBy". |
SignOffTime |
additional.fields.additional4.value.string_value | Il valore del campo "AuthFailMessage" viene inserito nell'array "additional.fields" con la chiave "Enterprise Interface Builder". |
SignOffTime |
metadata.description | Mappato direttamente dal campo "AuthFailMessage". |
SignOffTime |
metadata.event_type | Se il campo "SignOffTime" รจ vuoto, il campo "metadata.event_type" รจ impostato su "USER_LOGIN". In caso contrario, รจ impostato su "USER_LOGOUT". |
SignOffTime |
principal.user.attribute.last_update_time | Analizzato come "principal.user.attribute.last_update_time" dopo la conversione nel formato "ISO8601". |
SignOnIp |
src.domain.name | Se il campo "SignOnIp" non รจ un indirizzo IP valido, viene mappato a "src.domain.name". |
SignOnIp |
src.ip | Se il campo "SignOnIp" รจ un indirizzo IP valido, viene mappato a "src.ip". |
Status |
metadata.product_event_type | Mappato direttamente dal campo "Stato". |
SystemAccount |
principal.user.email_addresses | L'indirizzo email viene estratto dal campo "SystemAccount" utilizzando un pattern grok e mappato su "principal.user.email_addresses". |
SystemAccount |
principal.user.primaryId | L'ID utente viene estratto dal campo "SystemAccount" utilizzando un pattern grok e mappato a "principal.user.primaryId". |
SystemAccount |
principal.user.primaryName | Il nome visualizzato dell'utente viene estratto dal campo "SystemAccount" utilizzando un pattern grok e mappato a "principal.user.primaryName". |
SystemAccount |
src.user.userid | L'ID utente secondario viene estratto dal campo "SystemAccount" utilizzando un pattern grok e mappato a "src.user.userid". |
SystemAccount |
src.user.user_display_name | Il nome visualizzato dell'utente secondario viene estratto dal campo "SystemAccount" utilizzando un pattern grok e mappato a "src.user.user_display_name". |
SystemAccount |
target.user.userid | L'ID utente di destinazione viene estratto dal campo "SystemAccount" utilizzando un pattern grok e mappato a "target.user.userid". |
Target |
target.user.user_display_name | Mappato direttamente dal campo "Target". |
Template |
about.resource.name | Mappato direttamente dal campo "Modello". |
Tenant |
target.asset.hostname | Mappato direttamente dal campo "Tenant". |
TlsVersion |
network.tls.version | Mappato direttamente dal campo "TlsVersion". |
Transaction |
security_result.action_details | Mappato direttamente dal campo "Transazione". |
TransactionType |
security_result.summary | Mappato direttamente dal campo "TransactionType". |
TypeForm |
target.resource.resource_subtype | Mappato direttamente dal campo "TypeForm". |
UserAgent |
network.http.parsed_user_agent | Analizzato dal campo "UserAgent" utilizzando il filtro "useragent". |
UserAgent |
network.http.user_agent | Mappato direttamente dal campo "UserAgent". |
WorkdayAccount |
target.user.user_display_name | Il nome visualizzato dell'utente viene estratto dal campo "WorkdayAccount" utilizzando un pattern grok e mappato a "target.user.user_display_name". |
WorkdayAccount |
target.user.userid | L'ID utente viene estratto dal campo "WorkdayAccount" utilizzando un pattern grok e mappato a "target.user.userid". |
additional.fields.additional1.key | Imposta "FailedSignOn". | |
additional.fields.additional2.key | Imposta il valore su "InvalidCredentials". | |
additional.fields.additional3.key | Impostato su "AccountLocked". | |
additional.fields.additional4.key | Imposta su "Enterprise Interface Builder". | |
metadata.event_type | Imposta inizialmente il valore "GENERIC_EVENT", che verrร poi aggiornato in base alla logica che coinvolge altri campi. | |
metadata.event_type | Imposta "USER_CHANGE_PERMISSIONS" per tipi di eventi specifici. | |
metadata.event_type | Imposta "RESOURCE_WRITTEN" per tipi di eventi specifici. | |
metadata.log_type | Codificato come "WORKDAY_AUDIT". | |
metadata.product_name | Codificato in modo permanente su "Enterprise Interface Builder". | |
metadata.vendor_name | Codificato in modo permanente su "Giorno lavorativo". | |
principal.asset.category | Imposta "Telefono" se il campo "DeviceType" รจ "Telefono". | |
principal.resource.resource_type | Codificato in modo permanente su "TASK" se il campo "ScheduledProcess" non รจ vuoto. | |
security_result.action | Imposta "ALLOW" o "FAIL" in base ai valori dei campi "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" e "AccountLocked". | |
security_result.summary | Imposta "Riuscito" o messaggi di errore specifici in base ai valori dei campi "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" e "AccountLocked". | |
target.resource.resource_type | Codificato come "TASK" per tipi di eventi specifici. | |
target.resource.resource_type | Codificato in modo permanente su "DATASET" se il campo "TypeForm" non รจ vuoto. | |
message |
principal.user.email_addresses | Estrae l'indirizzo email dal campo "message" utilizzando un pattern grok e lo unisce a "principal.user.email_addresses" se viene trovato un pattern specifico. |
message |
src.user.userid | Cancella il campo se il campo "event.idm.read_only_udm.principal.user.userid" corrisponde a "user_target" estratto dal campo "message". |
message |
src.user.user_display_name | Cancella il campo se il campo "event.idm.read_only_udm.principal.user.userid" corrisponde a "user_target" estratto dal campo "message". |
message |
target.user.userid | Estrae l'ID utente dal campo "message" utilizzando un pattern grok e lo mappa a "target.user.userid" se viene trovato un pattern specifico. |
Hai bisogno di ulteriore assistenza? Ricevi risposte dai membri della community e dai professionisti di Google SecOps.