Mengumpulkan log audit Workday
Dokumen ini menjelaskan cara menyerap log audit Workday ke Google Security Operations menggunakan AWS S3. Parser terlebih dahulu mengidentifikasi jenis peristiwa tertentu dari log berdasarkan analisis pola data CSV. Kemudian, kolom tersebut mengekstrak dan menyusun kolom yang relevan sesuai dengan jenis yang diidentifikasi, memetakannya ke model data terpadu (UDM) untuk analisis keamanan yang konsisten.
Sebelum memulai
Pastikan Anda memenuhi prasyarat berikut:
- Instance Google SecOps
- Akses istimewa ke AWS
- Akses istimewa ke Workday
Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps
- Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket.
- Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya,
workday-audit-logs
). - Buat Pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
- Pilih Pengguna yang dibuat.
- Pilih tab Kredensial keamanan.
- Klik Create Access Key di bagian Access Keys.
- Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
- Klik Berikutnya.
- Opsional: Tambahkan tag deskripsi.
- Klik Create access key.
- Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk referensi di masa mendatang.
- Klik Selesai.
- Pilih tab Permissions.
- Klik Tambahkan izin di bagian Kebijakan izin.
- Pilih Tambahkan izin.
- Pilih Lampirkan kebijakan secara langsung.
- Telusuri dan pilih kebijakan AmazonS3FullAccess.
- Klik Berikutnya.
- Klik Add permissions.
Membuat Pengguna Sistem Integrasi (ISU) Workday
- Di Workday, cari Create Integration System User > OK.
- Isi User Name (misalnya,
audit_s3_user
). - Klik Oke.
- Setel ulang sandi dengan membuka Tindakan Terkait > Keamanan > Setel Ulang Sandi.
- Pilih Pertahankan Aturan Sandi untuk mencegah masa berlaku sandi berakhir.
- Telusuri Create Security Group > Integration System Security Group (Unconstrained).
- Berikan nama (misalnya,
ISU_Audit_S3
) dan tambahkan ISU ke Pengguna Sistem Integrasi. - Cari Kebijakan Keamanan Domain untuk Area Fungsional > Sistem.
- Untuk Audit Trail, pilih Tindakan > Edit Izin.
- Di bagian Get Only, tambahkan grup
ISU_Audit_S3
. - Klik OKE > Aktifkan Perubahan Kebijakan Keamanan yang Tertunda.
Mengonfigurasi Laporan Kustom Workday
- Di Workday, telusuri Create Custom Report.
- Berikan detail konfigurasi berikut:
- Nama: Masukkan nama unik (misalnya,
Audit_Trail_BP_JSON
). - Jenis: Pilih Lanjutan.
- Sumber Data: Pilih Audit Trail โ Business Process.
- Klik Oke.
- Opsional: Tambahkan filter pada Jenis Proses Bisnis atau Tanggal Mulai Berlaku.
- Nama: Masukkan nama unik (misalnya,
- Buka tab Output.
- Pilih Aktifkan sebagai Layanan Web, Dioptimalkan untuk Performa, lalu pilih Format JSON.
- Klik OK > Selesai.
- Buka laporan, lalu klik Bagikan > tambahkan
ISU_Audit_S3
dengan izin Lihat > OK. - Buka Related Actions > Web Service > View URLs.
- Salin URL JSON (misalnya,
https://wd-services1.workday.com/ccx/service/customreport2/<tenant>/<user>/Audit_Trail_BP_JSON?format=json
).
Mengonfigurasi kebijakan dan peran IAM untuk upload S3
Policy JSON (ganti
workday-audit-logs
jika Anda memasukkan nama bucket yang berbeda):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutWorkdayObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::workday-audit-logs/*" } ] }
Buka konsol AWS > IAM > Policies > Create policy > tab JSON.
Salin dan tempel kebijakan.
Klik Berikutnya > Buat kebijakan.
Buka IAM > Roles > Create role > AWS service > Lambda.
Lampirkan kebijakan yang baru dibuat.
Beri nama peran
WriteWorkdayToS3Role
, lalu klik Buat peran.
Buat fungsi Lambda
Setelan | Nilai |
---|---|
Nama | workday_audit_to_s3 |
Runtime | Python 3.13 |
Arsitektur | x86_64 |
Peran eksekusi | WriteWorkdayToS3Role |
Setelah fungsi dibuat, buka tab Code, hapus stub, dan tempelkan kode di bawah (
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()
Buka Configuration > Environment variables > Edit > Add new environment variable.
Masukkan variabel lingkungan berikut, lalu ganti dengan nilai Anda.
Variabel lingkungan
Kunci Nilai Contoh WD_USER
audit_s3_user
WD_PASS
Wrokday-Password
WD_URL
https://.../Audit_Trail_BP_JSON?format=json
S3_BUCKET_NAME
workday-audit-logs
Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > yourโfunction).
Pilih tab Configuration
Di panel General configuration, klik Edit.
Ubah Waktu tunggu menjadi 5 menit (300 detik), lalu klik Simpan.
Jadwalkan fungsi Lambda (EventBridge Scheduler)
- Buka Configuration > Triggers > Add trigger > EventBridge Scheduler > Create rule.
- Berikan detail konfigurasi berikut:
- Name:
daily-workday-audit export
. - Pola jadwal: Ekspresi cron.
- Ekspresi:
20 2 * * ? *
(berjalan setiap hari pada pukul 02.20 UTC).
- Name:
- Biarkan setelan lainnya dalam nilai default, lalu klik Create.
Mengonfigurasi feed di Google SecOps untuk menyerap log Audit Workday
- Buka Setelan SIEM > Feed.
- Klik + Tambahkan Feed Baru.
- Di kolom Nama feed, masukkan nama untuk feed (misalnya,
Workday Audit Logs
). - Pilih Amazon S3 V2 sebagai Jenis sumber.
- Pilih Audit Workday sebagai Jenis log.
- Klik Dapatkan Akun Layanan.
- Klik Berikutnya.
- Tentukan nilai untuk parameter input berikut:
- URI S3: URI bucket
s3://workday-audit-logs/
.- Ganti
workday-audit-logs
dengan nama bucket yang sebenarnya.
- Ganti
- Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
- Usia File Maksimum: Menyertakan file yang diubah dalam beberapa hari terakhir. Defaultnya adalah 180 Hari.
- Access Key ID: Kunci akses pengguna dengan akses ke bucket s3.
- Secret Access Key: Kunci rahasia pengguna dengan akses ke bucket s3.
- Namespace aset: Namespace aset.
- Label penyerapan: Label yang akan diterapkan ke peristiwa dari feed ini.
- URI S3: URI bucket
- Klik Berikutnya.
- Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.
Tabel Pemetaan UDM
Kolom Log | Pemetaan UDM | Logika |
---|---|---|
Account |
metadata.event_type | Jika kolom "Akun" tidak kosong, kolom "metadata.event_type" disetel ke "USER_RESOURCE_UPDATE_CONTENT". |
Account |
principal.user.primaryId | ID pengguna diekstrak dari kolom "Account" menggunakan pola grok dan dipetakan ke principal.user.primaryId . |
Account |
principal.user.primaryName | Nama tampilan pengguna diekstrak dari kolom "Akun" menggunakan pola grok dan dipetakan ke "principal.user.primaryName". |
ActivityCategory |
metadata.event_type | Jika kolom "ActivityCategory" adalah "READ", kolom "metadata.event_type" ditetapkan ke "RESOURCE_READ". Jika "WRITE", ditetapkan ke "RESOURCE_WRITTEN". |
ActivityCategory |
metadata.product_event_type | Dipetakan langsung dari kolom "ActivityCategory". |
AffectedGroups |
target.user.group_identifiers | Dipetakan langsung dari kolom "AffectedGroups". |
Area |
target.resource.attribute.labels.area.value | Dipetakan langsung dari kolom "Area". |
AuthType |
extensions.auth.auth_details | Dipetakan langsung dari kolom "AuthType". |
AuthType |
extensions.auth.type | Dipetakan dari kolom "AuthType" ke berbagai jenis autentikasi yang ditentukan dalam UDM berdasarkan nilai tertentu. |
CFIPdeConexion |
src.domain.name | Jika kolom "CFIPdeConexion" bukan alamat IP yang valid, kolom tersebut dipetakan ke "src.domain.name". |
CFIPdeConexion |
target.ip | Jika kolom "CFIPdeConexion" adalah alamat IP yang valid, kolom tersebut dipetakan ke "target.ip". |
ChangedRelationship |
metadata.description | Dipetakan langsung dari kolom "ChangedRelationship". |
ClassOfInstance |
target.resource.attribute.labels.class_instance.value | Dipetakan langsung dari kolom "ClassOfInstance". |
column18 |
about.labels.utub.value | Dipetakan langsung dari kolom "column18". |
CreatedBy |
principal.user.userid | ID pengguna diekstrak dari kolom "CreatedBy" menggunakan pola grok dan dipetakan ke "principal.user.userid". |
CreatedBy |
principal.user.user_display_name | Nama tampilan pengguna diekstrak dari kolom "CreatedBy" menggunakan pola grok dan dipetakan ke "principal.user.user_display_name". |
Domain |
about.domain.name | Dipetakan langsung dari kolom "Domain". |
EffectiveDate |
@timestamp | Diuraikan ke "@timestamp" setelah dikonversi ke format "yyyy-MM-dd HH:mm:ss.SSSZ". |
EntryMoment |
@timestamp | Diuraikan ke "@timestamp" setelah dikonversi ke format "ISO8601". |
EventType |
security_result.description | Dipetakan langsung dari kolom "EventType". |
Form |
target.resource.name | Dipetakan langsung dari kolom "Formulir". |
InstancesAdded |
about.resource.attribute.labels.instances_added.value | Dipetakan langsung dari kolom "InstancesAdded". |
InstancesAdded |
target.user.attribute.roles.instances_added.name | Dipetakan langsung dari kolom "InstancesAdded". |
InstancesRemoved |
about.resource.attribute.labels.instances_removed.value | Dipetakan langsung dari kolom "InstancesRemoved". |
InstancesRemoved |
target.user.attribute.roles.instances_removed.name | Dipetakan langsung dari kolom "InstancesRemoved". |
IntegrationEvent |
target.resource.attribute.labels.integration_event.value | Dipetakan langsung dari kolom "IntegrationEvent". |
IntegrationStatus |
security_result.action_details | Dipetakan langsung dari kolom "IntegrationStatus". |
IntegrationSystem |
target.resource.name | Dipetakan langsung dari kolom "IntegrationSystem". |
IP |
src.domain.name | Jika kolom "IP" bukan alamat IP yang valid, kolom tersebut dipetakan ke "src.domain.name". |
IP |
src.ip | Jika kolom "IP" adalah alamat IP yang valid, kolom tersebut dipetakan ke "src.ip". |
IsDeviceManaged |
additional.fields.additional1.value.string_value | Jika kolom "IsDeviceManaged" adalah "N", nilai akan ditetapkan ke "Successful". Jika tidak, nilai ini akan ditetapkan ke "Terjadi login gagal". |
IsDeviceManaged |
additional.fields.additional2.value.string_value | Jika kolom "IsDeviceManaged" adalah "N", nilai akan ditetapkan ke "Successful". Jika tidak, nilai ini akan ditetapkan ke "Kredensial Tidak Valid". |
IsDeviceManaged |
additional.fields.additional3.value.string_value | Jika kolom "IsDeviceManaged" adalah "N", nilai akan ditetapkan ke "Successful". Jika tidak, statusnya akan ditetapkan ke "Akun Terkunci". |
IsDeviceManaged |
security_result.action_details | Dipetakan langsung dari kolom "IsDeviceManaged". |
OutputFiles |
about.file.full_path | Dipetakan langsung dari kolom "OutputFiles". |
Person |
principal.user.primaryId | Jika kolom "Person" dimulai dengan "INT", userid diekstrak menggunakan pola grok dan dipetakan ke "principal.user.primaryId". |
Person |
principal.user.primaryName | Jika kolom "Person" dimulai dengan "INT", nama tampilan pengguna diekstrak menggunakan pola grok dan dipetakan ke "principal.user.primaryName". |
Person |
principal.user.user_display_name | Jika kolom "Person" tidak diawali dengan "INT", kolom tersebut dipetakan langsung ke "principal.user.user_display_name". |
Person |
metadata.event_type | Jika kolom "Person" tidak kosong, kolom "metadata.event_type" disetel ke "USER_RESOURCE_UPDATE_CONTENT". |
ProcessedTransaction |
target.resource.attribute.creation_time | Diuraikan ke "target.resource.attribute.creation_time" setelah dikonversi ke format "dd/MM/yyyy HH:mm:ss,SSS (ZZZ)", "dd/MM/yyyy, HH:mm:ss,SSS (ZZZ)", atau "MM/dd/yyyy, HH:mm:ss.SSS A ZZZ". |
ProgramBy |
principal.user.userid | Dipetakan langsung dari kolom "ProgramBy". |
RecurrenceEndDate |
principal.resource.attribute.last_update_time | Diuraikan ke "principal.resource.attribute.last_update_time" setelah dikonversi ke format "yyyy-MM-dd". |
RecurrenceStartDate |
principal.resource.attribute.creation_time | Diuraikan ke "principal.resource.attribute.creation_time" setelah dikonversi ke format "yyyy-MM-dd". |
RequestName |
metadata.description | Dipetakan langsung dari kolom "RequestName". |
ResponseMessage |
security_result.summary | Dipetakan langsung dari kolom "ResponseMessage". |
RestrictedToEnvironment |
security_result.about.hostname | Dipetakan langsung dari kolom "RestrictedToEnvironment". |
RevokedSecurity |
security_result.outcomes.outcomes.value | Dipetakan langsung dari kolom "RevokedSecurity". |
RunFrequency |
principal.resource.attribute.labels.run_frequency.value | Dipetakan langsung dari kolom "RunFrequency". |
ScheduledProcess |
principal.resource.name | Dipetakan langsung dari kolom "ScheduledProcess". |
SecuredTaskExecuted |
target.resource.name | Dipetakan langsung dari kolom "SecuredTaskExecuted". |
SecureTaskExecuted |
metadata.event_type | Jika kolom "SecureTaskExecuted" berisi "Create", kolom "metadata.event_type" disetel ke "USER_RESOURCE_CREATION". |
SecureTaskExecuted |
target.resource.name | Dipetakan langsung dari kolom "SecureTaskExecuted". |
SentTime |
@timestamp | Diuraikan ke "@timestamp" setelah dikonversi ke format "ISO8601". |
SessionId |
network.session_id | Dipetakan langsung dari kolom "SessionId". |
ShareBy |
target.user.userid | Dipetakan langsung dari kolom "ShareBy". |
SignOffTime |
additional.fields.additional4.value.string_value | Nilai kolom "AuthFailMessage" ditempatkan dalam array "additional.fields" dengan kunci "Enterprise Interface Builder". |
SignOffTime |
metadata.description | Dipetakan langsung dari kolom "AuthFailMessage". |
SignOffTime |
metadata.event_type | Jika kolom "SignOffTime" kosong, kolom "metadata.event_type" disetel ke "USER_LOGIN". Jika tidak, nilai ini akan ditetapkan ke "USER_LOGOUT". |
SignOffTime |
principal.user.attribute.last_update_time | Diuraikan ke "principal.user.attribute.last_update_time" setelah dikonversi ke format "ISO8601". |
SignOnIp |
src.domain.name | Jika kolom "SignOnIp" bukan alamat IP yang valid, kolom tersebut dipetakan ke "src.domain.name". |
SignOnIp |
src.ip | Jika kolom "SignOnIp" adalah alamat IP yang valid, kolom tersebut dipetakan ke "src.ip". |
Status |
metadata.product_event_type | Dipetakan langsung dari kolom "Status". |
SystemAccount |
principal.user.email_addresses | Alamat email diekstrak dari kolom "SystemAccount" menggunakan pola grok dan dipetakan ke "principal.user.email_addresses". |
SystemAccount |
principal.user.primaryId | ID pengguna diekstrak dari kolom "SystemAccount" menggunakan pola grok dan dipetakan ke "principal.user.primaryId". |
SystemAccount |
principal.user.primaryName | Nama tampilan pengguna diekstrak dari kolom "SystemAccount" menggunakan pola grok dan dipetakan ke "principal.user.primaryName". |
SystemAccount |
src.user.userid | ID pengguna sekunder diekstrak dari kolom "SystemAccount" menggunakan pola grok dan dipetakan ke "src.user.userid". |
SystemAccount |
src.user.user_display_name | Nama tampilan pengguna sekunder diekstrak dari kolom "SystemAccount" menggunakan pola grok dan dipetakan ke "src.user.user_display_name". |
SystemAccount |
target.user.userid | Target userid diekstrak dari kolom "SystemAccount" menggunakan pola grok dan dipetakan ke "target.user.userid". |
Target |
target.user.user_display_name | Dipetakan langsung dari kolom "Target". |
Template |
about.resource.name | Dipetakan langsung dari kolom "Template". |
Tenant |
target.asset.hostname | Dipetakan langsung dari kolom "Tenant". |
TlsVersion |
network.tls.version | Dipetakan langsung dari kolom "TlsVersion". |
Transaction |
security_result.action_details | Dipetakan langsung dari kolom "Transaksi". |
TransactionType |
security_result.summary | Dipetakan langsung dari kolom "TransactionType". |
TypeForm |
target.resource.resource_subtype | Dipetakan langsung dari kolom "TypeForm". |
UserAgent |
network.http.parsed_user_agent | Diuraikan dari kolom "UserAgent" menggunakan filter "useragent". |
UserAgent |
network.http.user_agent | Dipetakan langsung dari kolom "UserAgent". |
WorkdayAccount |
target.user.user_display_name | Nama tampilan pengguna diekstrak dari kolom "WorkdayAccount" menggunakan pola grok dan dipetakan ke "target.user.user_display_name". |
WorkdayAccount |
target.user.userid | ID pengguna diekstrak dari kolom "WorkdayAccount" menggunakan pola grok dan dipetakan ke "target.user.userid". |
additional.fields.additional1.key | Ditetapkan ke "FailedSignOn". | |
additional.fields.additional2.key | Tetapkan ke "InvalidCredentials". | |
additional.fields.additional3.key | Ditetapkan ke "AccountLocked". | |
additional.fields.additional4.key | Tetapkan ke "Enterprise Interface Builder". | |
metadata.event_type | Awalnya disetel ke "GENERIC_EVENT", lalu diperbarui berdasarkan logika yang melibatkan kolom lain. | |
metadata.event_type | Disetel ke "USER_CHANGE_PERMISSIONS" untuk jenis peristiwa tertentu. | |
metadata.event_type | Disetel ke "RESOURCE_WRITTEN" untuk jenis peristiwa tertentu. | |
metadata.log_type | Dikodekan secara permanen ke "WORKDAY_AUDIT". | |
metadata.product_name | Dikodekan secara permanen ke "Enterprise Interface Builder". | |
metadata.vendor_name | Dikodekan secara permanen ke "Workday". | |
principal.asset.category | Ditetapkan ke "Ponsel" jika kolom "DeviceType" adalah "Ponsel". | |
principal.resource.resource_type | Dikodekan secara permanen ke "TASK" jika kolom "ScheduledProcess" tidak kosong. | |
security_result.action | Ditetapkan ke "ALLOW" atau "FAIL" berdasarkan nilai kolom "FailedSignOn", "IsDeviceManaged", "InvalidCredentials", dan "AccountLocked". | |
security_result.summary | Disetel ke "Berhasil" atau pesan error tertentu berdasarkan nilai kolom "FailedSignOn", "IsDeviceManaged", "InvalidCredentials", dan "AccountLocked". | |
target.resource.resource_type | Dikodekan secara permanen ke "TASK" untuk jenis peristiwa tertentu. | |
target.resource.resource_type | Dikodekan secara permanen ke "DATASET" jika kolom "TypeForm" tidak kosong. | |
message |
principal.user.email_addresses | Mengekstrak alamat email dari kolom "message" menggunakan pola grok dan menggabungkannya ke "principal.user.email_addresses" jika pola tertentu cocok. |
message |
src.user.userid | Menghapus kolom jika kolom "event.idm.read_only_udm.principal.user.userid" cocok dengan "user_target" yang diekstrak dari kolom "message". |
message |
src.user.user_display_name | Menghapus kolom jika kolom "event.idm.read_only_udm.principal.user.userid" cocok dengan "user_target" yang diekstrak dari kolom "message". |
message |
target.user.userid | Mengekstrak userid dari kolom "message" menggunakan pola grok dan memetakannya ke "target.user.userid" jika pola tertentu cocok. |
Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.