Mengumpulkan log audit Slack

Didukung di:

Dokumen ini menjelaskan cara menyerap Log Audit Slack ke Google Security Operations menggunakan Amazon S3. Parser terlebih dahulu menormalisasi nilai boolean dan menghapus kolom yang telah ditentukan sebelumnya. Kemudian, kolom "message" diuraikan sebagai JSON, dan pesan non-JSON akan diabaikan. Bergantung pada keberadaan kolom tertentu (date_create dan user_id), parser menerapkan logika yang berbeda untuk memetakan kolom log mentah ke UDM, termasuk metadata, principal, jaringan, target, dan informasi tentang, serta membuat hasil keamanan.

Sebelum memulai

Pastikan Anda memenuhi prasyarat berikut:

  • Instance Google SecOps
  • Akses istimewa ke tenant Slack Enterprise Grid dan Konsol Admin
  • Akses istimewa ke AWS (S3, IAM, Lambda, EventBridge)

Kumpulkan prasyarat Slack (ID Aplikasi, Token OAuth, ID Organisasi)

  1. Login ke Konsol Admin Slack.
  2. Buka https://api.slack.com/apps, lalu klik Create New App > From scratch.
  3. Masukkan Nama Aplikasi yang unik dan pilih Ruang Kerja Slack Anda.
  4. Klik Create App.
  5. Buka OAuth & Izin di sidebar kiri.
  6. Buka bagian Cakupan dan tambahkan Cakupan Token Pengguna berikut - auditlogs:read
  7. Klik Instal ke Workspace > Izinkan.
  8. Setelah diinstal, buka Aplikasi Tingkat Org.
  9. Klik Instal ke Organisasi.
  10. Beri otorisasi aplikasi dengan akun Pemilik/Admin Organisasi.
  11. Salin dan simpan Token OAuth Pengguna yang dimulai dengan xoxp- (ini adalah SLACK_AUDIT_TOKEN Anda) dengan aman.
  12. Catat ID Organisasi yang dapat ditemukan di Konsol Admin Slack di bagian Setelan & Izin > Setelan organisasi.

Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps

  1. Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket
  2. Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya, slack-audit-logs).
  3. Buat pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
  4. Pilih Pengguna yang dibuat.
  5. Pilih tab Kredensial keamanan.
  6. Klik Create Access Key di bagian Access Keys.
  7. Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
  8. Klik Berikutnya.
  9. Opsional: tambahkan tag deskripsi.
  10. Klik Create access key.
  11. Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk digunakan nanti.
  12. Klik Selesai.
  13. Pilih tab Izin.
  14. Klik Tambahkan izin di bagian Kebijakan izin.
  15. Pilih Tambahkan izin.
  16. Pilih Lampirkan kebijakan secara langsung
  17. Telusuri dan pilih kebijakan AmazonS3FullAccess.
  18. Klik Berikutnya.
  19. Klik Add permissions.

Mengonfigurasi kebijakan dan peran IAM untuk upload S3

  1. Di konsol AWS, buka IAM > Policies > Create policy > JSON tab.
  2. Masukkan kebijakan berikut:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::slack-audit-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::slack-audit-logs/slack/audit/state.json"
        }
      ]
    }
    
    • Ganti slack-audit-logs jika Anda memasukkan nama bucket yang berbeda.
  3. Klik Berikutnya > Buat kebijakan.

  4. Buka IAM > Roles > Create role > AWS service > Lambda.

  5. Lampirkan kebijakan yang baru dibuat.

  6. Beri nama peran SlackAuditToS3Role, lalu klik Buat peran.

Buat fungsi Lambda

  1. Di Konsol AWS, buka Lambda > Functions > Create function.
  2. Klik Buat dari awal.
  3. Berikan detail konfigurasi berikut:
Setelan Nilai
Nama slack_audit_to_s3
Runtime Python 3.13
Arsitektur x86_64
Peran eksekusi SlackAuditToS3Role
  1. Setelah fungsi dibuat, buka tab Code, hapus stub, dan masukkan kode berikut (slack_audit_to_s3.py):

    #!/usr/bin/env python3
    # Lambda: Pull Slack Audit Logs (Enterprise Grid) to S3 (no transform)
    
    import os, json, time, urllib.parse
    from urllib.request import Request, urlopen
    from urllib.error import HTTPError, URLError
    import boto3
    
    BASE_URL = "https://api.slack.com/audit/v1/logs"
    
    TOKEN        = os.environ["SLACK_AUDIT_TOKEN"]  # org-level user token with auditlogs:read
    BUCKET       = os.environ["S3_BUCKET"]
    PREFIX       = os.environ.get("S3_PREFIX", "slack/audit/")
    STATE_KEY    = os.environ.get("STATE_KEY", "slack/audit/state.json")
    LIMIT        = int(os.environ.get("LIMIT", "200"))             # Slack recommends <= 200
    MAX_PAGES    = int(os.environ.get("MAX_PAGES", "20"))
    LOOKBACK_SEC = int(os.environ.get("LOOKBACK_SECONDS", "3600")) # First-run window
    HTTP_TIMEOUT = int(os.environ.get("HTTP_TIMEOUT", "60"))
    HTTP_RETRIES = int(os.environ.get("HTTP_RETRIES", "3"))
    RETRY_AFTER_DEFAULT = int(os.environ.get("RETRY_AFTER_DEFAULT", "2"))
    # Optional server-side filters (comma-separated "action" values), empty means no filter
    ACTIONS      = os.environ.get("ACTIONS", "").strip()
    
    s3 = boto3.client("s3")
    
    def _get_state() -> dict:
        try:
            obj = s3.get_object(Bucket=BUCKET, Key=STATE_KEY)
            st = json.loads(obj["Body"].read() or b"{}")
            return {"cursor": st.get("cursor")}
        except Exception:
            return {"cursor": None}
    
    def _put_state(state: dict) -> None:
        body = json.dumps(state, separators=(",", ":")).encode("utf-8")
        s3.put_object(Bucket=BUCKET, Key=STATE_KEY, Body=body, ContentType="application/json")
    
    def _http_get(params: dict) -> dict:
        qs  = urllib.parse.urlencode(params, doseq=True)
        url = f"{BASE_URL}?{qs}" if qs else BASE_URL
        req = Request(url, method="GET")
        req.add_header("Authorization", f"Bearer {TOKEN}")
        req.add_header("Accept", "application/json")
    
        attempt = 0
        while True:
            try:
                with urlopen(req, timeout=HTTP_TIMEOUT) as r:
                    return json.loads(r.read().decode("utf-8"))
            except HTTPError as e:
                # Respect Retry-After on 429/5xx
                if e.code in (429, 500, 502, 503, 504) and attempt < HTTP_RETRIES:
                    retry_after = 0
                    try:
                        retry_after = int(e.headers.get("Retry-After", RETRY_AFTER_DEFAULT))
                    except Exception:
                        retry_after = RETRY_AFTER_DEFAULT
                    time.sleep(max(1, retry_after))
                    attempt += 1
                    continue
                # Re-raise other HTTP errors
                raise
            except URLError:
                if attempt < HTTP_RETRIES:
                    time.sleep(RETRY_AFTER_DEFAULT)
                    attempt += 1
                    continue
                raise
    
    def _write_page(payload: dict, page_idx: int) -> str:
        ts  = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime())
        key = f"{PREFIX}/{ts}-slack-audit-p{page_idx:05d}.json"
        body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
        s3.put_object(Bucket=BUCKET, Key=key, Body=body, ContentType="application/json")
        return key
    
    def lambda_handler(event=None, context=None):
        state  = _get_state()
        cursor = state.get("cursor")
    
        params = {"limit": LIMIT}
        if ACTIONS:
            params["action"] = [a.strip() for a in ACTIONS.split(",") if a.strip()]
        if cursor:
            params["cursor"] = cursor
        else:
            # First run (or reset): fetch a recent window by time
            params["oldest"] = int(time.time()) - LOOKBACK_SEC
    
        pages = 0
        total = 0
        last_cursor = None
    
        while pages < MAX_PAGES:
            data = _http_get(params)
            _write_page(data, pages)
    
            entries = data.get("entries") or []
            total += len(entries)
    
            # Cursor for next page
            meta = data.get("response_metadata") or {}
            next_cursor = meta.get("next_cursor") or data.get("next_cursor")
            if next_cursor:
                params = {"limit": LIMIT, "cursor": next_cursor}
                if ACTIONS:
                    params["action"] = [a.strip() for a in ACTIONS.split(",") if a.strip()]
                last_cursor = next_cursor
                pages += 1
                continue
            break
    
        if last_cursor:
            _put_state({"cursor": last_cursor})
    
        return {"ok": True, "pages": pages + (1 if total or last_cursor else 0), "entries": total, "cursor": last_cursor}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
  2. Buka Configuration > Environment variables > Edit > Add new environment variable.

  3. Masukkan variabel lingkungan berikut, ganti dengan nilai Anda:

    Kunci Nilai contoh
    S3_BUCKET slack-audit-logs
    S3_PREFIX slack/audit/
    STATE_KEY slack/audit/state.json
    SLACK_AUDIT_TOKEN xoxp-*** (token pengguna tingkat organisasi dengan auditlogs:read)
    LIMIT 200
    MAX_PAGES 20
    LOOKBACK_SECONDS 3600
    HTTP_TIMEOUT 60
    HTTP_RETRIES 3
    RETRY_AFTER_DEFAULT 2
    ACTIONS (opsional, CSV) user_login,app_installed
  4. Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).

  5. Pilih tab Configuration

  6. Di panel General configuration, klik Edit.

  7. Ubah Waktu tunggu menjadi 5 menit (300 detik), lalu klik Simpan.

Membuat jadwal EventBridge

  1. Buka Amazon EventBridge > Scheduler > Create schedule.
  2. Berikan detail konfigurasi berikut:
    • Jadwal berulang: Tarif (1 hour).
    • Target: Fungsi Lambda Anda slack_audit_to_s3.
    • Name: slack-audit-1h.
  3. Klik Buat jadwal.

Opsional: Buat pengguna & kunci IAM hanya baca untuk Google SecOps

  1. Di Konsol AWS, buka IAM > Pengguna > Tambahkan pengguna.
  2. Klik Add users.
  3. Berikan detail konfigurasi berikut:
    • Pengguna: secops-reader.
    • Jenis akses: Kunci akses — Akses terprogram.
  4. Klik Buat pengguna.
  5. Lampirkan kebijakan baca minimal (kustom): Pengguna > secops-reader > Izin > Tambahkan izin > Lampirkan kebijakan secara langsung > Buat kebijakan.
  6. Di editor JSON, masukkan kebijakan berikut:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::slack-audit-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::slack-audit-logs"
        }
      ]
    }
    
  7. Tetapkan nama ke secops-reader-policy.

  8. Buka Buat kebijakan > cari/pilih > Berikutnya > Tambahkan izin.

  9. Buka Kredensial keamanan > Kunci akses > Buat kunci akses.

  10. Download CSV (nilai ini dimasukkan ke dalam feed).

Mengonfigurasi feed di Google SecOps untuk menyerap Log Audit Slack

  1. Buka Setelan SIEM > Feed.
  2. Klik + Tambahkan Feed Baru.
  3. Di kolom Nama feed, masukkan nama untuk feed (misalnya, Slack Audit Logs).
  4. Pilih Amazon S3 V2 sebagai Jenis sumber.
  5. Pilih Slack Audit sebagai Jenis log.
  6. Klik Berikutnya.
  7. Tentukan nilai untuk parameter input berikut:
    • URI S3: s3://slack-audit-logs/slack/audit/
    • Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
    • Usia File Maksimum: Menyertakan file yang diubah dalam jumlah hari terakhir. Defaultnya adalah 180 hari.
    • ID Kunci Akses: Kunci akses pengguna dengan akses ke bucket S3.
    • Kunci Akses Rahasia: Kunci rahasia pengguna dengan akses ke bucket S3.
    • Namespace aset: Namespace aset.
    • Label penyerapan: Label yang diterapkan ke peristiwa dari feed ini.
  8. Klik Berikutnya.
  9. Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.

Tabel Pemetaan UDM

Kolom Log Pemetaan UDM Logika
action metadata.product_event_type Dipetakan langsung dari kolom action dalam log mentah.
actor.type principal.labels.value Dipetakan langsung dari kolom actor.type, dengan kunci actor.type ditambahkan.
actor.user.email principal.user.email_addresses Dipetakan langsung dari kolom actor.user.email.
actor.user.id principal.user.product_object_id Dipetakan langsung dari kolom actor.user.id.
actor.user.id principal.user.userid Dipetakan langsung dari kolom actor.user.id.
actor.user.name principal.user.user_display_name Dipetakan langsung dari kolom actor.user.name.
actor.user.team principal.user.group_identifiers Dipetakan langsung dari kolom actor.user.team.
context.ip_address principal.ip Dipetakan langsung dari kolom context.ip_address.
context.location.domain about.resource.attribute.labels.value Dipetakan langsung dari kolom context.location.domain, dengan kunci context.location.domain ditambahkan.
context.location.id about.resource.id Dipetakan langsung dari kolom context.location.id.
context.location.name about.resource.name Dipetakan langsung dari kolom context.location.name.
context.location.name about.resource.attribute.labels.value Dipetakan langsung dari kolom context.location.name, dengan kunci context.location.name ditambahkan.
context.location.type about.resource.resource_subtype Dipetakan langsung dari kolom context.location.type.
context.session_id network.session_id Dipetakan langsung dari kolom context.session_id.
context.ua network.http.user_agent Dipetakan langsung dari kolom context.ua.
context.ua network.http.parsed_user_agent Informasi agen pengguna yang diuraikan yang berasal dari kolom context.ua menggunakan filter parseduseragent.
country principal.location.country_or_region Dipetakan langsung dari kolom country.
date_create metadata.event_timestamp.seconds Stempel waktu epoch dari kolom date_create dikonversi menjadi objek stempel waktu.
details.inviter.email target.user.email_addresses Dipetakan langsung dari kolom details.inviter.email.
details.inviter.id target.user.product_object_id Dipetakan langsung dari kolom details.inviter.id.
details.inviter.name target.user.user_display_name Dipetakan langsung dari kolom details.inviter.name.
details.inviter.team target.user.group_identifiers Dipetakan langsung dari kolom details.inviter.team.
details.reason security_result.description Dipetakan langsung dari kolom details.reason, atau jika berupa array, digabungkan dengan koma.
details.type about.resource.attribute.labels.value Dipetakan langsung dari kolom details.type, dengan kunci details.type ditambahkan.
details.type security_result.summary Dipetakan langsung dari kolom details.type.
entity.app.id target.resource.id Dipetakan langsung dari kolom entity.app.id.
entity.app.name target.resource.name Dipetakan langsung dari kolom entity.app.name.
entity.channel.id target.resource.id Dipetakan langsung dari kolom entity.channel.id.
entity.channel.name target.resource.name Dipetakan langsung dari kolom entity.channel.name.
entity.channel.privacy target.resource.attribute.labels.value Dipetakan langsung dari kolom entity.channel.privacy, dengan kunci entity.channel.privacy ditambahkan.
entity.file.filetype target.resource.attribute.labels.value Dipetakan langsung dari kolom entity.file.filetype, dengan kunci entity.file.filetype ditambahkan.
entity.file.id target.resource.id Dipetakan langsung dari kolom entity.file.id.
entity.file.name target.resource.name Dipetakan langsung dari kolom entity.file.name.
entity.file.title target.resource.attribute.labels.value Dipetakan langsung dari kolom entity.file.title, dengan kunci entity.file.title ditambahkan.
entity.huddle.date_end about.resource.attribute.labels.value Dipetakan langsung dari kolom entity.huddle.date_end, dengan kunci entity.huddle.date_end ditambahkan.
entity.huddle.date_start about.resource.attribute.labels.value Dipetakan langsung dari kolom entity.huddle.date_start, dengan kunci entity.huddle.date_start ditambahkan.
entity.huddle.id about.resource.attribute.labels.value Dipetakan langsung dari kolom entity.huddle.id, dengan kunci entity.huddle.id ditambahkan.
entity.huddle.participants.0 about.resource.attribute.labels.value Dipetakan langsung dari kolom entity.huddle.participants.0, dengan kunci entity.huddle.participants.0 ditambahkan.
entity.huddle.participants.1 about.resource.attribute.labels.value Dipetakan langsung dari kolom entity.huddle.participants.1, dengan kunci entity.huddle.participants.1 ditambahkan.
entity.type target.resource.resource_subtype Dipetakan langsung dari kolom entity.type.
entity.user.email target.user.email_addresses Dipetakan langsung dari kolom entity.user.email.
entity.user.id target.user.product_object_id Dipetakan langsung dari kolom entity.user.id.
entity.user.name target.user.user_display_name Dipetakan langsung dari kolom entity.user.name.
entity.user.team target.user.group_identifiers Dipetakan langsung dari kolom entity.user.team.
entity.workflow.id target.resource.id Dipetakan langsung dari kolom entity.workflow.id.
entity.workflow.name target.resource.name Dipetakan langsung dari kolom entity.workflow.name.
id metadata.product_log_id Dipetakan langsung dari kolom id.
ip principal.ip Dipetakan langsung dari kolom ip. Ditentukan oleh logika berdasarkan kolom action. Nilai defaultnya adalah USER_COMMUNICATION, tetapi berubah menjadi nilai lain seperti USER_CREATION, USER_LOGIN, USER_LOGOUT, USER_RESOURCE_ACCESS, USER_RESOURCE_UPDATE_PERMISSIONS, atau USER_CHANGE_PERMISSIONS berdasarkan nilai action. Dikodekan secara permanen ke "SLACK_AUDIT". Ditetapkan ke "Enterprise Grid" jika date_create ada, atau ditetapkan ke "Audit Logs" jika user_id ada. Dikodekan secara permanen ke "Slack". Dikodekan secara permanen ke "REMOTE". Setel ke "SSO" jika action berisi "user_login" atau "user_logout". Jika tidak, tetapkan ke "MESIN". Tidak dipetakan dalam contoh yang diberikan. Default-nya adalah "ALLOW", tetapi disetel ke "BLOCK" jika action adalah "user_login_failed". Disetel ke "Slack" jika date_create ada, atau disetel ke "SLACK" jika user_id ada.
user_agent network.http.user_agent Dipetakan langsung dari kolom user_agent.
user_id principal.user.product_object_id Dipetakan langsung dari kolom user_id.
username principal.user.product_object_id Dipetakan langsung dari kolom username.

Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.