Recoger registros de auditoría de Workday

Disponible en:

En este documento se explica cómo ingerir registros de auditoría de Workday en Google Security Operations mediante AWS S3. En primer lugar, el analizador identifica el tipo de evento específico de los registros en función del análisis de patrones de los datos CSV. A continuación, extrae y estructura los campos relevantes según el tipo identificado, asignándolos a un modelo de datos unificado (UDM) para realizar un análisis de seguridad coherente.

Antes de empezar

Asegúrate de que cumples los siguientes requisitos previos:

  • Instancia de Google SecOps
  • Acceso privilegiado a AWS
  • Acceso privilegiado a Workday

Configurar un segmento de AWS S3 y IAM para Google SecOps

  1. Crea un segmento de Amazon S3 siguiendo esta guía de usuario: Crear un segmento.
  2. Guarda el nombre y la región del segmento para consultarlos más adelante (por ejemplo, workday-audit-logs).
  3. Crea un usuario siguiendo esta guía: Crear un usuario de gestión de identidades y accesos.
  4. Selecciona el usuario creado.
  5. Selecciona la pestaña Credenciales de seguridad.
  6. En la sección Claves de acceso, haz clic en Crear clave de acceso.
  7. Selecciona Servicio de terceros como Caso práctico.
  8. Haz clic en Siguiente.
  9. Opcional: añade una etiqueta de descripción.
  10. Haz clic en Crear clave de acceso.
  11. Haz clic en Descargar archivo CSV para guardar la clave de acceso y la clave de acceso secreta para futuras consultas.
  12. Haz clic en Listo.
  13. Selecciona la pestaña Permisos.
  14. Haz clic en Añadir permisos en la sección Políticas de permisos.
  15. Selecciona Añadir permisos.
  16. Seleccione Adjuntar políticas directamente.
  17. Busca y selecciona la política AmazonS3FullAccess.
  18. Haz clic en Siguiente.
  19. Haz clic en Añadir permisos.

Crear un usuario del sistema de integración de Workday

  1. En Workday, busca Create Integration System User (Crear usuario del sistema de integración) > OK (Aceptar).
  2. Rellena el campo Nombre de usuario (por ejemplo, audit_s3_user).
  3. Haz clic en Aceptar.
  4. Para restablecer la contraseña, ve a Acciones relacionadas > Seguridad > Restablecer contraseña.
  5. Selecciona Mantener reglas de contraseñas para evitar que la contraseña caduque.
  6. Busca Create Security Group > Integration System Security Group (Unconstrained) (Crear grupo de seguridad > Grupo de seguridad del sistema de integración [sin restricciones]).
  7. Proporciona un nombre (por ejemplo, ISU_Audit_S3) y añade el usuario del sistema de integración a Integration System Users (Usuarios del sistema de integración).
  8. Busca Políticas de seguridad de dominio para el área funcional > Sistema.
  9. En Registro de auditoría, selecciona Acciones > Editar permisos.
  10. En Obtener solo, añade el grupo ISU_Audit_S3.
  11. Haz clic en Aceptar > Activar cambios pendientes en la política de seguridad.

Configurar un informe personalizado de Workday

  1. En Workday, busca Create Custom Report (Crear informe personalizado).
  2. Proporcione los siguientes detalles de configuración:
    • Nombre: introduzca un nombre único (por ejemplo, Audit_Trail_BP_JSON).
    • Tipo: selecciona Avanzado.
    • Fuente de datos: selecciona Registro de auditoría: proceso empresarial.
    • Haz clic en Aceptar.
    • Opcional: Añade filtros en Tipo de proceso de empresa o Fecha de entrada en vigor.
  3. Ve a la pestaña Salida.
  4. Selecciona Habilitar como servicio web y Optimizado para el rendimiento y, a continuación, Formato JSON.
  5. Haz clic en Aceptar > Hecho.
  6. Abre el informe y haz clic en Compartir > añade ISU_Audit_S3 con permiso de lectura > Aceptar.
  7. Vaya a Acciones relacionadas > Servicio web > Ver URLs.
  8. Copia la URL JSON (por ejemplo, https://wd-services1.workday.com/ccx/service/customreport2/<tenant>/<user>/Audit_Trail_BP_JSON?format=json).

Configurar la política y el rol de gestión de identidades y accesos para las subidas de S3

  1. JSON de la política (sustituye workday-audit-logs si has introducido otro nombre de contenedor):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutWorkdayObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::workday-audit-logs/*"
        }
      ]
    }
    
  2. Ve a la consola de AWS > IAM > Políticas > Crear política > pestaña JSON.

  3. Copia y pega la política.

  4. Haz clic en Siguiente > Crear política.

  5. Ve a IAM > Roles > Crear rol > Servicio de AWS > Lambda.

  6. Adjunte la política que acaba de crear.

  7. Dale el nombre WriteWorkdayToS3Role al rol y haz clic en Crear rol.

Crear la función Lambda

Ajuste Valor
Nombre workday_audit_to_s3
Tiempo de ejecución Python 3.13
Arquitectura x86_64
Rol de ejecución WriteWorkdayToS3Role
  1. Una vez creada la función, abre la pestaña Código, elimina el stub y pega el código que aparece más abajo (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. Ve a Configuración > Variables de entorno > Editar > Añadir nueva variable de entorno.

  3. Introduce las siguientes variables de entorno y sustituye los valores por los tuyos.

    Variables de entorno

    Clave Valores de ejemplo
    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. Una vez creada la función, permanece en su página (o abre Lambda > Funciones > tu‑función).

  5. Seleccione la pestaña Configuración.

  6. En el panel Configuración general, haz clic en Editar.

  7. Cambia Tiempo de espera a 5 minutos (300 segundos) y haz clic en Guardar.

Programar la función Lambda (EventBridge Scheduler)

  1. Ve a Configuración > Activadores > Añadir activador > EventBridge Scheduler > Crear regla.
  2. Proporcione los siguientes detalles de configuración:
    • Nombre: daily-workday-audit export.
    • Patrón de programación: expresión cron.
    • Expresión: 20 2 * * ? * (se ejecuta todos los días a las 02:20 UTC).
  3. Deja el resto de los ajustes como están y haz clic en Crear.

Configurar un feed en Google SecOps para ingerir registros de auditoría de Workday

  1. Ve a Configuración de SIEM > Feeds.
  2. Haz clic en + Añadir nuevo feed.
  3. En el campo Nombre del feed, introduce un nombre para el feed (por ejemplo, Workday Audit Logs).
  4. Selecciona Amazon S3 V2 como Tipo de fuente.
  5. Seleccione Auditoría de Workday como Tipo de registro.
  6. Haz clic en Obtener una cuenta de servicio.
  7. Haz clic en Siguiente.
  8. Especifique valores para los siguientes parámetros de entrada:
    • URI de S3: el URI del contenedor
      • s3://workday-audit-logs/.
        • Sustituye workday-audit-logs por el nombre real del segmento.
    • Opciones de eliminación de la fuente: selecciona la opción de eliminación que prefieras.
    • Antigüedad máxima del archivo: incluye los archivos modificados en los últimos días. El valor predeterminado es 180 días.
    • ID de clave de acceso: clave de acceso de usuario con acceso al segmento de S3.
    • Clave de acceso secreta: clave secreta del usuario con acceso al segmento de S3.
    • Espacio de nombres de recursos: el espacio de nombres de recursos.
    • Etiquetas de ingestión: etiqueta que se aplicará a los eventos de este feed.
  9. Haz clic en Siguiente.
  10. Revise la configuración de la nueva fuente en la pantalla Finalizar y, a continuación, haga clic en Enviar.

Tabla de asignación de UDM

Campo de registro Asignación de UDM Lógica
Account metadata.event_type Si el campo "Account" no está vacío, el campo "metadata.event_type" se define como "USER_RESOURCE_UPDATE_CONTENT".
Account principal.user.primaryId El ID de usuario se extrae del campo "Account" (Cuenta) mediante un patrón grok y se asigna a principal.user.primaryId.
Account principal.user.primaryName El nombre visible del usuario se extrae del campo "Account" mediante un patrón grok y se asigna a "principal.user.primaryName".
ActivityCategory metadata.event_type Si el valor del campo "ActivityCategory" es "READ", el valor del campo "metadata.event_type" es "RESOURCE_READ". Si es "WRITE", se asigna el valor "RESOURCE_WRITTEN".
ActivityCategory metadata.product_event_type Se asigna directamente desde el campo "ActivityCategory".
AffectedGroups target.user.group_identifiers Se asigna directamente desde el campo "AffectedGroups".
Area target.resource.attribute.labels.area.value Se asigna directamente desde el campo "Área".
AuthType extensions.auth.auth_details Se asigna directamente desde el campo "AuthType".
AuthType extensions.auth.type Se asigna del campo "AuthType" a diferentes tipos de autenticación definidos en UDM en función de valores específicos.
CFIPdeConexion src.domain.name Si el campo "CFIPdeConexion" no es una dirección IP válida, se asigna a "src.domain.name".
CFIPdeConexion target.ip Si el campo "CFIPdeConexion" es una dirección IP válida, se asigna a "target.ip".
ChangedRelationship metadata.description Se asigna directamente desde el campo "ChangedRelationship".
ClassOfInstance target.resource.attribute.labels.class_instance.value Se asigna directamente desde el campo "ClassOfInstance".
column18 about.labels.utub.value Se asigna directamente desde el campo "column18".
CreatedBy principal.user.userid El ID de usuario se extrae del campo "CreatedBy" mediante un patrón grok y se asigna a "principal.user.userid".
CreatedBy principal.user.user_display_name El nombre visible del usuario se extrae del campo "CreatedBy" mediante un patrón grok y se asigna a "principal.user.user_display_name".
Domain about.domain.name Se asigna directamente desde el campo "Dominio".
EffectiveDate @timestamp Se analiza en "@timestamp" después de convertirlo al formato "aaaa-MM-dd HH:mm:ss.SSSZ".
EntryMoment @timestamp Se analiza en "@timestamp" después de convertirlo al formato "ISO8601".
EventType security_result.description Se asigna directamente desde el campo "EventType".
Form target.resource.name Se asigna directamente desde el campo "Form" (Formulario).
InstancesAdded about.resource.attribute.labels.instances_added.value Se asigna directamente desde el campo "InstancesAdded".
InstancesAdded target.user.attribute.roles.instances_added.name Se asigna directamente desde el campo "InstancesAdded".
InstancesRemoved about.resource.attribute.labels.instances_removed.value Se asigna directamente desde el campo "InstancesRemoved".
InstancesRemoved target.user.attribute.roles.instances_removed.name Se asigna directamente desde el campo "InstancesRemoved".
IntegrationEvent target.resource.attribute.labels.integration_event.value Se asigna directamente desde el campo "IntegrationEvent".
IntegrationStatus security_result.action_details Se asigna directamente desde el campo "IntegrationStatus".
IntegrationSystem target.resource.name Se asigna directamente desde el campo "IntegrationSystem".
IP src.domain.name Si el campo "IP" no es una dirección IP válida, se asigna a "src.domain.name".
IP src.ip Si el campo "IP" es una dirección IP válida, se asigna a "src.ip".
IsDeviceManaged additional.fields.additional1.value.string_value Si el campo "IsDeviceManaged" es "N", el valor se define como "Successful". De lo contrario, se asigna el valor "Se ha producido un error al iniciar sesión".
IsDeviceManaged additional.fields.additional2.value.string_value Si el campo "IsDeviceManaged" es "N", el valor se define como "Successful". De lo contrario, se le asigna el valor "Credenciales no válidas".
IsDeviceManaged additional.fields.additional3.value.string_value Si el campo "IsDeviceManaged" es "N", el valor se define como "Successful". De lo contrario, se le asigna el valor "Cuenta bloqueada".
IsDeviceManaged security_result.action_details Se asigna directamente desde el campo "IsDeviceManaged".
OutputFiles about.file.full_path Se asigna directamente desde el campo "OutputFiles".
Person principal.user.primaryId Si el campo "Person" empieza por "INT", el ID de usuario se extrae mediante un patrón grok y se asigna a "principal.user.primaryId".
Person principal.user.primaryName Si el campo "Person" empieza por "INT", el nombre visible del usuario se extrae mediante un patrón grok y se asigna a "principal.user.primaryName".
Person principal.user.user_display_name Si el campo "Person" no empieza por "INT", se asigna directamente a "principal.user.user_display_name".
Person metadata.event_type Si el campo "Person" no está vacío, el campo "metadata.event_type" se define como "USER_RESOURCE_UPDATE_CONTENT".
ProcessedTransaction target.resource.attribute.creation_time Se analiza como "target.resource.attribute.creation_time" después de convertirlo al formato "dd/MM/yyyy HH:mm:ss,SSS (ZZZ)", "dd/MM/yyyy, HH:mm:ss,SSS (ZZZ)" o "MM/dd/yyyy, HH:mm:ss.SSS A ZZZ".
ProgramBy principal.user.userid Se asigna directamente desde el campo "ProgramBy".
RecurrenceEndDate principal.resource.attribute.last_update_time Se ha analizado como "principal.resource.attribute.last_update_time" después de convertirlo al formato "aaaa-MM-dd".
RecurrenceStartDate principal.resource.attribute.creation_time Se ha analizado como "principal.resource.attribute.creation_time" después de convertirlo al formato "aaaa-MM-dd".
RequestName metadata.description Se asigna directamente desde el campo "RequestName".
ResponseMessage security_result.summary Se asigna directamente desde el campo "ResponseMessage".
RestrictedToEnvironment security_result.about.hostname Se asigna directamente desde el campo "RestrictedToEnvironment".
RevokedSecurity security_result.outcomes.outcomes.value Se asigna directamente desde el campo "RevokedSecurity".
RunFrequency principal.resource.attribute.labels.run_frequency.value Se asigna directamente desde el campo "RunFrequency".
ScheduledProcess principal.resource.name Se asigna directamente desde el campo "ScheduledProcess".
SecuredTaskExecuted target.resource.name Se asigna directamente desde el campo "SecuredTaskExecuted".
SecureTaskExecuted metadata.event_type Si el campo "SecureTaskExecuted" contiene "Create", el campo "metadata.event_type" se define como "USER_RESOURCE_CREATION".
SecureTaskExecuted target.resource.name Se asigna directamente desde el campo "SecureTaskExecuted".
SentTime @timestamp Se analiza en "@timestamp" después de convertirlo al formato "ISO8601".
SessionId network.session_id Se asigna directamente desde el campo "SessionId".
ShareBy target.user.userid Se asigna directamente desde el campo "ShareBy".
SignOffTime additional.fields.additional4.value.string_value El valor del campo "AuthFailMessage" se coloca en la matriz "additional.fields" con la clave "Enterprise Interface Builder".
SignOffTime metadata.description Se asigna directamente desde el campo "AuthFailMessage".
SignOffTime metadata.event_type Si el campo "SignOffTime" está vacío, el campo "metadata.event_type" se define como "USER_LOGIN". De lo contrario, se le asigna el valor "USER_LOGOUT".
SignOffTime principal.user.attribute.last_update_time Se ha analizado como "principal.user.attribute.last_update_time" después de convertirlo al formato "ISO8601".
SignOnIp src.domain.name Si el campo "SignOnIp" no es una dirección IP válida, se asigna a "src.domain.name".
SignOnIp src.ip Si el campo "SignOnIp" es una dirección IP válida, se asigna a "src.ip".
Status metadata.product_event_type Se asigna directamente desde el campo "Estado".
SystemAccount principal.user.email_addresses La dirección de correo se extrae del campo "SystemAccount" mediante un patrón grok y se asigna a "principal.user.email_addresses".
SystemAccount principal.user.primaryId El ID de usuario se extrae del campo "SystemAccount" mediante un patrón grok y se asigna a "principal.user.primaryId".
SystemAccount principal.user.primaryName El nombre visible del usuario se extrae del campo "SystemAccount" mediante un patrón grok y se asigna a "principal.user.primaryName".
SystemAccount src.user.userid El ID de usuario secundario se extrae del campo "SystemAccount" mediante un patrón grok y se asigna a "src.user.userid".
SystemAccount src.user.user_display_name El nombre visible del usuario secundario se extrae del campo "SystemAccount" mediante un patrón grok y se asigna a "src.user.user_display_name".
SystemAccount target.user.userid El ID de usuario de destino se extrae del campo "SystemAccount" mediante un patrón grok y se asigna a "target.user.userid".
Target target.user.user_display_name Se asigna directamente desde el campo "Target" (Objetivo).
Template about.resource.name Se asigna directamente desde el campo "Plantilla".
Tenant target.asset.hostname Se asigna directamente desde el campo "Tenant".
TlsVersion network.tls.version Se asigna directamente desde el campo "TlsVersion".
Transaction security_result.action_details Se asigna directamente desde el campo "Transacción".
TransactionType security_result.summary Se asigna directamente desde el campo "TransactionType".
TypeForm target.resource.resource_subtype Se asigna directamente desde el campo "TypeForm".
UserAgent network.http.parsed_user_agent Se ha analizado a partir del campo "UserAgent" mediante el filtro "useragent".
UserAgent network.http.user_agent Se asigna directamente desde el campo "UserAgent".
WorkdayAccount target.user.user_display_name El nombre visible del usuario se extrae del campo "WorkdayAccount" mediante un patrón grok y se asigna a "target.user.user_display_name".
WorkdayAccount target.user.userid El ID de usuario se extrae del campo "WorkdayAccount" mediante un patrón grok y se asigna a "target.user.userid".
additional.fields.additional1.key Se ha definido como "FailedSignOn".
additional.fields.additional2.key Se establece en "InvalidCredentials".
additional.fields.additional3.key Se ha asignado el valor "AccountLocked".
additional.fields.additional4.key Selecciona "Enterprise Interface Builder".
metadata.event_type Se define como "GENERIC_EVENT" al principio y, después, se actualiza en función de la lógica que implican otros campos.
metadata.event_type Se asigna el valor "USER_CHANGE_PERMISSIONS" a tipos de eventos específicos.
metadata.event_type Se establece en "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 Selecciona "Teléfono" si el campo "DeviceType" es "Teléfono".
principal.resource.resource_type Se codifica como "TASK" si el campo "ScheduledProcess" no está vacío.
security_result.action Se define como "ALLOW" o "FAIL" en función de los valores de los campos "FailedSignOn", "IsDeviceManaged", "InvalidCredentials" y "AccountLocked".
security_result.summary Se define como "Successful" (Completado) o como mensajes de error específicos en función de los valores de los campos "FailedSignOn" (Inicio de sesión fallido), "IsDeviceManaged" (¿El dispositivo está gestionado?), "InvalidCredentials" (Credenciales no válidas) y "AccountLocked" (Cuenta bloqueada).
target.resource.resource_type Se ha codificado como "TASK" para tipos de eventos específicos.
target.resource.resource_type Se codifica como "DATASET" si el campo "TypeForm" no está vacío.
message principal.user.email_addresses Extrae la dirección de correo del campo "message" mediante un patrón grok y la combina en "principal.user.email_addresses" si se encuentra un patrón específico.
message src.user.userid Borra el campo si el campo "event.idm.read_only_udm.principal.user.userid" coincide con el campo "user_target" extraído del campo "message".
message src.user.user_display_name Borra el campo si el campo "event.idm.read_only_udm.principal.user.userid" coincide con el campo "user_target" extraído del campo "message".
message target.user.userid Extrae el ID de usuario del campo "message" mediante un patrón grok y lo asigna a "target.user.userid" si se encuentra un patrón específico.

¿Necesitas más ayuda? Recibe respuestas de los miembros de la comunidad y de los profesionales de Google SecOps.