Collecter les journaux d'audit Workday

Compatible avec :

Ce document explique comment ingérer des journaux d'audit Workday dans Google Security Operations à l'aide d'AWS S3. L'analyseur identifie d'abord le type d'événement spécifique à partir des journaux en se basant sur l'analyse des modèles des données CSV. Il extrait et structure ensuite les champs pertinents en fonction du type identifié, en les mappant à un modèle de données unifié (UDM) pour une analyse de sécurité cohérente.

Avant de commencer

Assurez-vous de remplir les conditions suivantes :

  • Instance Google SecOps
  • Accès privilégié à AWS
  • Accès privilégié à Workday

Configurer un bucket AWS S3 et IAM pour Google SecOps

  1. Créez un bucket Amazon S3 en suivant ce guide de l'utilisateur : Créer un bucket.
  2. Enregistrez le nom et la région du bucket pour référence ultérieure (par exemple, workday-audit-logs).
  3. Créez un utilisateur en suivant ce guide de l'utilisateur : Créer un utilisateur IAM.
  4. Sélectionnez l'utilisateur créé.
  5. Sélectionnez l'onglet Informations d'identification de sécurité.
  6. Cliquez sur Créer une clé d'accès dans la section Clés d'accès.
  7. Sélectionnez Service tiers comme Cas d'utilisation.
  8. Cliquez sur Suivant.
  9. Facultatif : Ajoutez une balise de description.
  10. Cliquez sur Créer une clé d'accès.
  11. Cliquez sur Télécharger le fichier CSV pour enregistrer la clé d'accès et la clé d'accès secrète pour référence ultérieure.
  12. Cliquez sur OK.
  13. Sélectionnez l'onglet Autorisations.
  14. Cliquez sur Ajouter des autorisations dans la section Règles relatives aux autorisations.
  15. Sélectionnez Ajouter des autorisations.
  16. Sélectionnez Joindre directement des règles.
  17. Recherchez et sélectionnez la règle AmazonS3FullAccess.
  18. Cliquez sur Suivant.
  19. Cliquez sur Ajouter des autorisations.

Créer un utilisateur du système d'intégration Workday

  1. Dans Workday, recherchez Create Integration System User > OK.
  2. Renseignez le champ Nom d'utilisateur (par exemple, audit_s3_user).
  3. Cliquez sur OK.
  4. Pour réinitialiser le mot de passe, accédez à Actions associées > Sécurité > Réinitialiser le mot de passe.
  5. Sélectionnez Conserver les règles relatives aux mots de passe pour empêcher l'expiration du mot de passe.
  6. Recherchez Create Security Group> Integration System Security Group (Unconstrained) (Créer un groupe de sécurité > Groupe de sécurité du système d'intégration (sans restriction)).
  7. Attribuez un nom (par exemple, ISU_Audit_S3) et ajoutez l'utilisateur du système d'intégration à Utilisateurs du système d'intégration.
  8. Recherchez Domain Security Policies for Functional Area > System (Règles de sécurité du domaine pour la zone fonctionnelle > Système).
  9. Pour Audit Trail (Journal d'audit), sélectionnez Actions > Modifier les autorisations.
  10. Sous Get Only, ajoutez le groupe ISU_Audit_S3.
  11. Cliquez sur OK> Activer les modifications en attente de la stratégie de sécurité.

Configurer un rapport personnalisé Workday

  1. Dans Workday, recherchez Create Custom Report (Créer un rapport personnalisé).
  2. Fournissez les informations de configuration suivantes :
    • Nom : saisissez un nom unique (par exemple, Audit_Trail_BP_JSON).
    • Type : sélectionnez Avancé.
    • Source de données : sélectionnez Piste d'audit – Processus métier.
    • Cliquez sur OK.
    • Facultatif : Ajoutez des filtres sur Type de processus métier ou Date d'entrée en vigueur.
  3. Accédez à l'onglet Sortie.
  4. Sélectionnez Activer en tant que service Web, Optimisé pour les performances, puis Format JSON.
  5. Cliquez sur OK > OK.
  6. Ouvrez le rapport, puis cliquez sur Partager > ajoutez ISU_Audit_S3 avec l'autorisation Afficher > OK.
  7. Accédez à Actions associées > Service Web > Afficher les URL.
  8. Copiez l'URL JSON (par exemple, https://wd-services1.workday.com/ccx/service/customreport2/<tenant>/<user>/Audit_Trail_BP_JSON?format=json).

Configurer la stratégie et le rôle IAM pour les importations S3

  1. JSON de la règle (remplacez workday-audit-logs si vous avez saisi un autre nom de bucket) :

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutWorkdayObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::workday-audit-logs/*"
        }
      ]
    }
    
  2. Accédez à la console AWS> IAM> Policies> Create policy> onglet JSON.

  3. Copiez et collez le règlement.

  4. Cliquez sur Suivant > Créer une règle.

  5. Accédez à IAM > Rôles > Créer un rôle > Service AWS > Lambda.

  6. Associez la règle nouvellement créée.

  7. Nommez le rôle WriteWorkdayToS3Role, puis cliquez sur Créer un rôle.

Créer la fonction Lambda

Paramètre Valeur
Nom workday_audit_to_s3
Durée d'exécution Python 3.13
Architecture x86_64
Rôle d'exécution WriteWorkdayToS3Role
  1. Une fois la fonction créée, ouvrez l'onglet Code, supprimez le stub et collez le code ci-dessous (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()
    
  2. Accédez à Configuration > Variables d'environnement > Modifier > Ajouter une variable d'environnement.

  3. Saisissez les variables d'environnement suivantes en remplaçant par votre valeur.

    Variables d'environnement

    Clé Exemples de valeurs
    WD_USER audit_s3_user
    WD_PASS Wrokday-Password
    WD_URL https://.../Audit_Trail_BP_JSON?format=json
    S3_BUCKET_NAME workday-audit-logs
  4. Une fois la fonction créée, restez sur sa page (ou ouvrez Lambda > Fonctions > votre‑fonction).

  5. Accédez à l'onglet Configuration.

  6. Dans le panneau Configuration générale, cliquez sur Modifier.

  7. Définissez le délai avant expiration sur 5 minutes (300 secondes), puis cliquez sur Enregistrer.

Planifier la fonction Lambda (EventBridge Scheduler)

  1. Accédez à Configuration> Déclencheurs> Ajouter un déclencheur> EventBridge Scheduler> Créer une règle.
  2. Fournissez les informations de configuration suivantes :
    • Nom : daily-workday-audit export.
    • Modèle de programmation : expression Cron.
    • Expression : 20 2 * * ? * (s'exécute tous les jours à 02h20 UTC).
  3. Conservez les autres valeurs par défaut, puis cliquez sur Créer.

Configurer un flux dans Google SecOps pour ingérer les journaux d'audit Workday

  1. Accédez à Paramètres SIEM> Flux.
  2. Cliquez sur + Ajouter un flux.
  3. Dans le champ Nom du flux, saisissez un nom pour le flux (par exemple, Workday Audit Logs).
  4. Sélectionnez Amazon S3 V2 comme type de source.
  5. Sélectionnez Audit Workday comme Type de journal.
  6. Cliquez sur Créer un compte de service.
  7. Cliquez sur Suivant.
  8. Spécifiez les valeurs des paramètres d'entrée suivants :
    • URI S3 : URI du bucket
      • s3://workday-audit-logs/.
        • Remplacez workday-audit-logs par le nom réel du bucket.
    • Options de suppression de la source : sélectionnez l'option de suppression de votre choix.
    • Âge maximal des fichiers : incluez les fichiers modifiés au cours des derniers jours. La valeur par défaut est de 180 jours.
    • ID de clé d'accès : clé d'accès utilisateur ayant accès au bucket S3.
    • Clé d'accès secrète : clé secrète de l'utilisateur ayant accès au bucket S3.
    • Espace de noms de l'élément : espace de noms de l'élément.
    • Libellés d'ingestion : libellé à appliquer aux événements de ce flux.
  9. Cliquez sur Suivant.
  10. Vérifiez la configuration de votre nouveau flux sur l'écran Finaliser, puis cliquez sur Envoyer.

Table de mappage UDM

Champ de journal Mappage UDM Logique
Account metadata.event_type Si le champ "Compte" n'est pas vide, le champ "metadata.event_type" est défini sur "USER_RESOURCE_UPDATE_CONTENT".
Account principal.user.primaryId L'ID utilisateur est extrait du champ "Compte" à l'aide d'un modèle Grok et mappé à principal.user.primaryId.
Account principal.user.primaryName Le nom à afficher de l'utilisateur est extrait du champ "Compte" à l'aide d'un modèle Grok et mappé sur "principal.user.primaryName".
ActivityCategory metadata.event_type Si le champ "ActivityCategory" est défini sur "READ", le champ "metadata.event_type" est défini sur "RESOURCE_READ". Si la valeur est "WRITE", elle est définie sur "RESOURCE_WRITTEN".
ActivityCategory metadata.product_event_type Directement mappé à partir du champ "ActivityCategory".
AffectedGroups target.user.group_identifiers Mappé directement à partir du champ "AffectedGroups".
Area target.resource.attribute.labels.area.value Mappé directement à partir du champ "Zone".
AuthType extensions.auth.auth_details Mappé directement à partir du champ "AuthType".
AuthType extensions.auth.type Mappé à partir du champ "AuthType" vers différents types d'authentification définis dans l'UDM en fonction de valeurs spécifiques.
CFIPdeConexion src.domain.name Si le champ "CFIPdeConexion" n'est pas une adresse IP valide, il est mappé sur "src.domain.name".
CFIPdeConexion target.ip Si le champ "CFIPdeConexion" est une adresse IP valide, il est mappé à "target.ip".
ChangedRelationship metadata.description Mappé directement à partir du champ "ChangedRelationship".
ClassOfInstance target.resource.attribute.labels.class_instance.value Mappé directement à partir du champ "ClassOfInstance".
column18 about.labels.utub.value Mappé directement à partir du champ "column18".
CreatedBy principal.user.userid L'ID utilisateur est extrait du champ "CreatedBy" (Créé par) à l'aide d'un modèle Grok et mappé sur "principal.user.userid".
CreatedBy principal.user.user_display_name Le nom à afficher de l'utilisateur est extrait du champ "CreatedBy" à l'aide d'un modèle Grok et mappé à "principal.user.user_display_name".
Domain about.domain.name Mappé directement à partir du champ "Domaine".
EffectiveDate @timestamp Analysé dans "@timestamp" après conversion au format "aaaa-MM-jj HH:mm:ss.SSSZ".
EntryMoment @timestamp Analysé dans "@timestamp" après conversion au format "ISO8601".
EventType security_result.description Mappé directement à partir du champ "EventType".
Form target.resource.name Mappé directement à partir du champ "Formulaire".
InstancesAdded about.resource.attribute.labels.instances_added.value Mappé directement à partir du champ "InstancesAdded".
InstancesAdded target.user.attribute.roles.instances_added.name Mappé directement à partir du champ "InstancesAdded".
InstancesRemoved about.resource.attribute.labels.instances_removed.value Mappé directement à partir du champ "InstancesRemoved".
InstancesRemoved target.user.attribute.roles.instances_removed.name Mappé directement à partir du champ "InstancesRemoved".
IntegrationEvent target.resource.attribute.labels.integration_event.value Mappé directement à partir du champ "IntegrationEvent".
IntegrationStatus security_result.action_details Mappé directement à partir du champ "IntegrationStatus".
IntegrationSystem target.resource.name Mappé directement à partir du champ "IntegrationSystem".
IP src.domain.name Si le champ "IP" n'est pas une adresse IP valide, il est mappé sur "src.domain.name".
IP src.ip Si le champ "IP" est une adresse IP valide, il est mappé sur "src.ip".
IsDeviceManaged additional.fields.additional1.value.string_value Si le champ "IsDeviceManaged" est défini sur "N", la valeur est définie sur "Successful" (Réussie). Sinon, la valeur est définie sur "Échec de la connexion".
IsDeviceManaged additional.fields.additional2.value.string_value Si le champ "IsDeviceManaged" est défini sur "N", la valeur est définie sur "Successful" (Réussie). Sinon, elle est définie sur "Invalid Credentials" (Identifiants non valides).
IsDeviceManaged additional.fields.additional3.value.string_value Si le champ "IsDeviceManaged" est défini sur "N", la valeur est définie sur "Successful" (Réussie). Sinon, il est défini sur "Compte verrouillé".
IsDeviceManaged security_result.action_details Mappé directement à partir du champ "IsDeviceManaged".
OutputFiles about.file.full_path Mappé directement à partir du champ "OutputFiles".
Person principal.user.primaryId Si le champ "Personne" commence par "INT", l'ID utilisateur est extrait à l'aide d'un modèle Grok et mappé sur "principal.user.primaryId".
Person principal.user.primaryName Si le champ "Personne" commence par "INT", le nom à afficher de l'utilisateur est extrait à l'aide d'un modèle Grok et mappé sur "principal.user.primaryName".
Person principal.user.user_display_name Si le champ "Personne" ne commence pas par "INT", il est directement mappé à "principal.user.user_display_name".
Person metadata.event_type Si le champ "Personne" n'est pas vide, le champ "metadata.event_type" est défini sur "USER_RESOURCE_UPDATE_CONTENT".
ProcessedTransaction target.resource.attribute.creation_time Analysé en "target.resource.attribute.creation_time" après conversion au format "jj/MM/aaaa HH:mm:ss,SSS (ZZZ)", "jj/MM/aaaa, HH:mm:ss,SSS (ZZZ)" ou "MM/jj/aaaa, HH:mm:ss.SSS A ZZZ".
ProgramBy principal.user.userid Mappé directement à partir du champ "ProgramBy".
RecurrenceEndDate principal.resource.attribute.last_update_time Analysé en "principal.resource.attribute.last_update_time" après conversion au format "yyyy-MM-dd".
RecurrenceStartDate principal.resource.attribute.creation_time Analysé en "principal.resource.attribute.creation_time" après conversion au format "aaaa-MM-jj".
RequestName metadata.description Mappé directement à partir du champ "RequestName".
ResponseMessage security_result.summary Mappé directement à partir du champ "ResponseMessage".
RestrictedToEnvironment security_result.about.hostname Mappé directement à partir du champ "RestrictedToEnvironment".
RevokedSecurity security_result.outcomes.outcomes.value Directement mappé à partir du champ "RevokedSecurity".
RunFrequency principal.resource.attribute.labels.run_frequency.value Mappé directement à partir du champ "RunFrequency".
ScheduledProcess principal.resource.name Mappé directement à partir du champ "ScheduledProcess".
SecuredTaskExecuted target.resource.name Mappé directement à partir du champ "SecuredTaskExecuted".
SecureTaskExecuted metadata.event_type Si le champ "SecureTaskExecuted" contient "Create", le champ "metadata.event_type" est défini sur "USER_RESOURCE_CREATION".
SecureTaskExecuted target.resource.name Mappé directement à partir du champ "SecureTaskExecuted".
SentTime @timestamp Analysé dans "@timestamp" après conversion au format "ISO8601".
SessionId network.session_id Mappé directement à partir du champ "SessionId".
ShareBy target.user.userid Mappé directement à partir du champ "ShareBy".
SignOffTime additional.fields.additional4.value.string_value La valeur du champ "AuthFailMessage" est placée dans le tableau "additional.fields" avec la clé "Enterprise Interface Builder".
SignOffTime metadata.description Mappé directement à partir du champ "AuthFailMessage".
SignOffTime metadata.event_type Si le champ "SignOffTime" est vide, le champ "metadata.event_type" est défini sur "USER_LOGIN". Sinon, la valeur est définie sur "USER_LOGOUT".
SignOffTime principal.user.attribute.last_update_time Analysé dans "principal.user.attribute.last_update_time" après conversion au format "ISO8601".
SignOnIp src.domain.name Si le champ "SignOnIp" n'est pas une adresse IP valide, il est mappé sur "src.domain.name".
SignOnIp src.ip Si le champ "SignOnIp" est une adresse IP valide, il est mappé sur "src.ip".
Status metadata.product_event_type Mappé directement à partir du champ "État".
SystemAccount principal.user.email_addresses L'adresse e-mail est extraite du champ "SystemAccount" à l'aide d'un modèle grok et mappée sur "principal.user.email_addresses".
SystemAccount principal.user.primaryId L'ID utilisateur est extrait du champ "SystemAccount" à l'aide d'un modèle Grok et mappé sur "principal.user.primaryId".
SystemAccount principal.user.primaryName Le nom à afficher de l'utilisateur est extrait du champ "SystemAccount" à l'aide d'un modèle Grok et mappé sur "principal.user.primaryName".
SystemAccount src.user.userid L'ID utilisateur secondaire est extrait du champ "SystemAccount" à l'aide d'un modèle Grok et mappé à "src.user.userid".
SystemAccount src.user.user_display_name Le nom à afficher de l'utilisateur secondaire est extrait du champ "SystemAccount" à l'aide d'un modèle Grok et mappé sur "src.user.user_display_name".
SystemAccount target.user.userid L'ID utilisateur cible est extrait du champ "SystemAccount" à l'aide d'un modèle Grok et mappé sur "target.user.userid".
Target target.user.user_display_name Mappé directement à partir du champ "Cible".
Template about.resource.name Mappé directement à partir du champ "Modèle".
Tenant target.asset.hostname Mappé directement à partir du champ "Locataire".
TlsVersion network.tls.version Mappé directement à partir du champ "TlsVersion".
Transaction security_result.action_details Directement mappé à partir du champ "Transaction".
TransactionType security_result.summary Directement mappé à partir du champ "TransactionType".
TypeForm target.resource.resource_subtype Mappé directement à partir du champ "TypeForm".
UserAgent network.http.parsed_user_agent Analysé à partir du champ "UserAgent" à l'aide du filtre "useragent".
UserAgent network.http.user_agent Mappé directement à partir du champ "UserAgent".
WorkdayAccount target.user.user_display_name Le nom à afficher de l'utilisateur est extrait du champ "WorkdayAccount" à l'aide d'un modèle Grok et mappé sur "target.user.user_display_name".
WorkdayAccount target.user.userid L'ID utilisateur est extrait du champ "WorkdayAccount" à l'aide d'un modèle Grok et mappé sur "target.user.userid".
additional.fields.additional1.key Défini sur "FailedSignOn".
additional.fields.additional2.key Défini sur "InvalidCredentials".
additional.fields.additional3.key Défini sur "AccountLocked".
additional.fields.additional4.key Définissez-le sur "Enterprise Interface Builder".
metadata.event_type Défini sur "GENERIC_EVENT" au départ, puis mis à jour en fonction de la logique impliquant d'autres champs.
metadata.event_type Définissez la valeur sur "USER_CHANGE_PERMISSIONS" pour des types d'événements spécifiques.
metadata.event_type Définissez la valeur sur "RESOURCE_WRITTEN" pour des types d'événements spécifiques.
metadata.log_type Codé en dur sur "WORKDAY_AUDIT".
metadata.product_name Codé en dur sur "Enterprise Interface Builder".
metadata.vendor_name Codé en dur sur "Workday".
principal.asset.category Définissez la valeur sur "Téléphone" si le champ "DeviceType" est défini sur "Téléphone".
principal.resource.resource_type Codé en dur sur "TASK" si le champ "ScheduledProcess" n'est pas vide.
security_result.action Définissez la valeur sur "ALLOW" ou "FAIL" en fonction des valeurs des champs "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" et "AccountLocked".
security_result.summary Définissez la valeur sur "Traitement effectué" ou sur des messages d'erreur spécifiques en fonction des valeurs des champs "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" et "AccountLocked".
target.resource.resource_type Codé en dur sur "TASK" pour certains types d'événements.
target.resource.resource_type Codé en dur sur "DATASET" si le champ "TypeForm" n'est pas vide.
message principal.user.email_addresses Extrait l'adresse e-mail du champ "message" à l'aide d'un modèle Grok et la fusionne dans "principal.user.email_addresses" si un modèle spécifique est trouvé.
message src.user.userid Efface le champ si le champ "event.idm.read_only_udm.principal.user.userid" correspond à "user_target" extrait du champ "message".
message src.user.user_display_name Efface le champ si le champ "event.idm.read_only_udm.principal.user.userid" correspond à "user_target" extrait du champ "message".
message target.user.userid Extrait l'ID utilisateur du champ "message" à l'aide d'un modèle Grok et le mappe à "target.user.userid" si un modèle spécifique est trouvé.

Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.