Mengumpulkan log SailPoint IAM

Didukung di:

Dokumen ini menjelaskan cara Anda dapat menyerap log Identity and Access Management (IAM) SailPoint ke Google Security Operations menggunakan Amazon S3. Parser menangani log dalam format JSON dan XML, lalu mengubahnya menjadi Model Data Terpadu (UDM). UDM membedakan antara peristiwa UDM tunggal (ProvisioningPlan, AccountRequest, SOAP-ENV), peristiwa multi-UDM (ProvisioningProject), dan entitas UDM (Identity), dengan menerapkan logika parsing dan pemetaan kolom tertentu untuk setiap peristiwa, termasuk penanganan peristiwa generik untuk data non-XML.

Sebelum memulai

Pastikan Anda memenuhi prasyarat berikut:

  • Instance Google SecOps.
  • Akses istimewa ke SailPoint Identity Security Cloud.
  • Akses istimewa ke AWS (S3, IAM, Lambda, EventBridge).

Kumpulkan prasyarat SailPoint IAM (ID, kunci API, ID organisasi, token)

  1. Login ke Konsol Admin SailPoint Identity Security Cloud sebagai administrator.
  2. Buka Global > Setelan Keamanan > Pengelolaan API.
  3. Klik Create API Client.
  4. Pilih Client Credentials sebagai jenis pemberian.
  5. Berikan detail konfigurasi berikut:
    • Nama: Masukkan nama deskriptif (misalnya, Google SecOps Export API).
    • Deskripsi: Masukkan deskripsi untuk klien API.
    • Cakupan: Pilih sp:scopes:all.
  6. Klik Buat dan simpan kredensial API yang dibuat di lokasi yang aman.
  7. Catat URL dasar tenant SailPoint Anda (misalnya, https://tenant.api.identitynow.com).
  8. Salin dan simpan detail berikut di lokasi yang aman:
    • IDN_CLIENT_ID.
    • IDN_CLIENT_SECRET.
    • IDN_BASE.

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, sailpoint-iam-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 referensi di masa mendatang.
  12. Klik Selesai.
  13. Pilih tab Permissions.
  14. Klik Tambahkan izin di bagian Kebijakan izin.
  15. Pilih Tambahkan izin.
  16. Pilih Lampirkan kebijakan secara langsung.
  17. Cari kebijakan AmazonS3FullAccess.
  18. Pilih kebijakan.
  19. Klik Berikutnya.
  20. Klik Add permissions.

Mengonfigurasi kebijakan dan peran IAM untuk upload S3

  1. Di konsol AWS, buka IAM > Policies.
  2. Klik Buat kebijakan > tab JSON.
  3. Salin dan tempel kebijakan berikut.
  4. Policy JSON (ganti sailpoint-iam-logs jika Anda memasukkan nama bucket yang berbeda):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::sailpoint-iam-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::sailpoint-iam-logs/sailpoint/iam/state.json"
        }
      ]
    }
    
  5. Klik Berikutnya > Buat kebijakan.

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

  7. Lampirkan kebijakan yang baru dibuat.

  8. Beri nama peran SailPointIamToS3Role, 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 sailpoint_iam_to_s3
    Runtime Python 3.13
    Arsitektur x86_64
    Peran eksekusi SailPointIamToS3Role
  4. Setelah fungsi dibuat, buka tab Code, hapus stub, dan tempelkan kode berikut (sailpoint_iam_to_s3.py).

    #!/usr/bin/env python3
    # Lambda: Pull SailPoint Identity Security Cloud audit events and store raw JSON payloads to S3
    # - Uses /v3/search API with pagination for audit events.
    # - Preserves vendor-native JSON format for identity events.
    # - Retries with exponential backoff; unique S3 keys to avoid overwrites.
    
    import os, json, time, uuid, urllib.parse
    from urllib.request import Request, urlopen
    from urllib.error import URLError, HTTPError
    
    import boto3
    
    S3_BUCKET   = os.environ["S3_BUCKET"]
    S3_PREFIX   = os.environ.get("S3_PREFIX", "sailpoint/iam/")
    STATE_KEY   = os.environ.get("STATE_KEY", "sailpoint/iam/state.json")
    WINDOW_SEC  = int(os.environ.get("WINDOW_SECONDS", "3600"))  # default 1h
    HTTP_TIMEOUT= int(os.environ.get("HTTP_TIMEOUT", "60"))
    IDN_BASE    = os.environ["IDN_BASE"]  # e.g. https://tenant.api.identitynow.com
    CLIENT_ID   = os.environ["IDN_CLIENT_ID"]
    CLIENT_SECRET = os.environ["IDN_CLIENT_SECRET"]
    SCOPE       = os.environ.get("IDN_SCOPE", "sp:scopes:all")
    PAGE_SIZE   = int(os.environ.get("PAGE_SIZE", "250"))
    MAX_PAGES   = int(os.environ.get("MAX_PAGES", "20"))
    MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3"))
    USER_AGENT  = os.environ.get("USER_AGENT", "sailpoint-iam-to-s3/1.0")
    
    s3 = boto3.client("s3")
    
    def _load_state():
        try:
            obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            return json.loads(obj["Body"].read())
        except Exception:
            return {}
    
    def _save_state(st):
        s3.put_object(
            Bucket=S3_BUCKET,
            Key=STATE_KEY,
            Body=json.dumps(st, separators=(",", ":")).encode("utf-8"),
            ContentType="application/json",
        )
    
    def _iso(ts: float) -> str:
        return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts))
    
    def _get_oauth_token() -> str:
        """Get OAuth2 access token using Client Credentials flow"""
        token_url = f"{IDN_BASE.rstrip('/')}/oauth/token"
    
        data = urllib.parse.urlencode({
            'grant_type': 'client_credentials',
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'scope': SCOPE
        }).encode('utf-8')
    
        req = Request(token_url, data=data, method="POST")
        req.add_header("Content-Type", "application/x-www-form-urlencoded")
        req.add_header("User-Agent", USER_AGENT)
    
        with urlopen(req, timeout=HTTP_TIMEOUT) as r:
            response = json.loads(r.read())
            return response["access_token"]
    
    def _search_events(access_token: str, created_from: str, search_after: list = None) -> list:
        """Search for audit events using SailPoint's /v3/search API"""
        search_url = f"{IDN_BASE.rstrip('/')}/v3/search"
    
        # Build search query for events created after specified time
        query_str = f'created:">={created_from}"'
    
        payload = {
            "indices": ["events"],
            "query": {"query": query_str},
            "sort": ["created", "+id"],
            "limit": PAGE_SIZE
        }
    
        if search_after:
            payload["searchAfter"] = search_after
    
        attempt = 0
        while True:
            req = Request(search_url, data=json.dumps(payload).encode('utf-8'), method="POST")
            req.add_header("Content-Type", "application/json")
            req.add_header("Accept", "application/json")
            req.add_header("Authorization", f"Bearer {access_token}")
            req.add_header("User-Agent", USER_AGENT)
    
            try:
                with urlopen(req, timeout=HTTP_TIMEOUT) as r:
                    response = json.loads(r.read())
                    # Handle different response formats
                    if isinstance(response, list):
                        return response
                    return response.get("results", response.get("data", []))
            except (HTTPError, URLError) as e:
                attempt += 1
                print(f"HTTP error on attempt {attempt}: {e}")
                if attempt > MAX_RETRIES:
                    raise
                # exponential backoff with jitter
                time.sleep(min(60, 2 ** attempt) + (time.time() % 1))
    
    def _put_events_data(events: list, from_ts: float, to_ts: float, page_num: int) -> str:
        # Create unique S3 key for events data
        ts_path = time.strftime("%Y/%m/%d", time.gmtime(to_ts))
        uniq = f"{int(time.time()*1e6)}_{uuid.uuid4().hex[:8]}"
        key = f"{S3_PREFIX}{ts_path}/sailpoint_iam_{int(from_ts)}_{int(to_ts)}_p{page_num:03d}_{uniq}.json"
    
        s3.put_object(
            Bucket=S3_BUCKET, 
            Key=key, 
            Body=json.dumps(events, separators=(",", ":")).encode("utf-8"), 
            ContentType="application/json",
            Metadata={
                'source': 'sailpoint-iam',
                'from_timestamp': str(int(from_ts)),
                'to_timestamp': str(int(to_ts)),
                'page_number': str(page_num),
                'events_count': str(len(events))
            }
        )
        return key
    
    def _get_item_id(item: dict) -> str:
        """Extract ID from event item, trying multiple possible fields"""
        for field in ("id", "uuid", "eventId", "_id"):
            if field in item and item[field]:
                return str(item[field])
        return ""
    
    def lambda_handler(event=None, context=None):
        st = _load_state()
        now = time.time()
        from_ts = float(st.get("last_to_ts") or (now - WINDOW_SEC))
        to_ts = now
    
        # Get OAuth token
        access_token = _get_oauth_token()
    
        created_from = _iso(from_ts)
        print(f"Fetching SailPoint IAM events from: {created_from}")
    
        # Handle pagination state
        last_created = st.get("last_created")
        last_id = st.get("last_id")
        search_after = [last_created, last_id] if (last_created and last_id) else None
    
        pages = 0
        total_events = 0
        written_keys = []
        newest_created = last_created or created_from
        newest_id = last_id or ""
    
        while pages < MAX_PAGES:
            events = _search_events(access_token, created_from, search_after)
    
            if not events:
                break
    
            # Write page to S3
            key = _put_events_data(events, from_ts, to_ts, pages + 1)
            written_keys.append(key)
            total_events += len(events)
    
            # Update pagination state from last item
            last_event = events[-1]
            last_event_created = last_event.get("created") or last_event.get("metadata", {}).get("created")
            last_event_id = _get_item_id(last_event)
    
            if last_event_created:
                newest_created = last_event_created
            if last_event_id:
                newest_id = last_event_id
    
            search_after = [newest_created, newest_id]
            pages += 1
    
            # If we got less than page size, we're done
            if len(events) < PAGE_SIZE:
                break
    
        print(f"Successfully retrieved {total_events} events across {pages} pages")
    
        # Save state for next run
        st["last_to_ts"] = to_ts
        st["last_created"] = newest_created
        st["last_id"] = newest_id
        st["last_successful_run"] = now
        _save_state(st)
    
        return {
            "statusCode": 200,
            "body": {
                "success": True,
                "pages": pages,
                "total_events": total_events,
                "s3_keys": written_keys,
                "from_timestamp": from_ts,
                "to_timestamp": to_ts,
                "last_created": newest_created,
                "last_id": newest_id
            }
        }
    
    if __name__ == "__main__":
        print(lambda_handler())
    
  5. Buka Configuration > Environment variables.

  6. Klik Edit > Tambahkan variabel lingkungan baru.

  7. Masukkan variabel lingkungan yang diberikan dalam tabel berikut, dengan mengganti nilai contoh dengan nilai Anda.

    Variabel lingkungan

    Kunci Nilai contoh
    S3_BUCKET sailpoint-iam-logs
    S3_PREFIX sailpoint/iam/
    STATE_KEY sailpoint/iam/state.json
    WINDOW_SECONDS 3600
    HTTP_TIMEOUT 60
    MAX_RETRIES 3
    USER_AGENT sailpoint-iam-to-s3/1.0
    IDN_BASE https://tenant.api.identitynow.com
    IDN_CLIENT_ID your-client-id (dari langkah 2)
    IDN_CLIENT_SECRET your-client-secret (dari langkah 2)
    IDN_SCOPE sp:scopes:all
    PAGE_SIZE 250
    MAX_PAGES 20
  8. Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).

  9. Pilih tab Configuration

  10. Di panel Konfigurasi umum, klik Edit.

  11. 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 sailpoint_iam_to_s3.
    • Name: sailpoint-iam-1h.
  3. Klik Buat jadwal.

(Opsional) Buat pengguna & kunci IAM hanya baca untuk Google SecOps

  1. Buka Konsol AWS > IAM > Pengguna.
  2. Klik Add users.
  3. Berikan detail konfigurasi berikut:
    • Pengguna: Masukkan secops-reader.
    • Jenis akses: Pilih 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. JSON:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::sailpoint-iam-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::sailpoint-iam-logs"
        }
      ]
    }
    
  7. Nama = secops-reader-policy.

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

  9. Buat kunci akses untuk secops-reader: Kredensial keamanan > Kunci akses.

  10. Klik Create access key.

  11. Download .CSV. (Anda akan menempelkan nilai ini ke feed).

Mengonfigurasi feed di Google SecOps untuk memproses log IAM SailPoint

  1. Buka Setelan SIEM > Feed.
  2. Klik + Tambahkan Feed Baru.
  3. Di kolom Nama feed, masukkan nama untuk feed (misalnya, SailPoint IAM logs).
  4. Pilih Amazon S3 V2 sebagai Jenis sumber.
  5. Pilih SailPoint IAM sebagai Jenis log.
  6. Klik Berikutnya.
  7. Tentukan nilai untuk parameter input berikut:
    • URI S3: s3://sailpoint-iam-logs/sailpoint/iam/
    • 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.description Nilai kolom action dari log mentah.
actor.name principal.user.user_display_name Nilai kolom actor.name dari log mentah.
attributes.accountName principal.user.group_identifiers Nilai kolom attributes.accountName dari log mentah.
attributes.appId target.asset_id "ID Aplikasi: " digabungkan dengan nilai kolom attributes.appId dari log mentah.
attributes.attributeName additional.fields[0].value.string_value Nilai kolom attributes.attributeName dari log mentah, ditempatkan dalam objek additional.fields. Kunci ditetapkan ke "Nama Atribut".
attributes.attributeValue additional.fields[1].value.string_value Nilai kolom attributes.attributeValue dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "Nilai Atribut".
attributes.cloudAppName target.application Nilai kolom attributes.cloudAppName dari log mentah.
attributes.hostName target.hostname, target.asset.hostname Nilai kolom attributes.hostName dari log mentah.
attributes.interface additional.fields[2].value.string_value Nilai kolom attributes.interface dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "Interface".
attributes.operation security_result.action_details Nilai kolom attributes.operation dari log mentah.
attributes.previousValue additional.fields[3].value.string_value Nilai kolom attributes.previousValue dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "Nilai Sebelumnya".
attributes.provisioningResult security_result.detection_fields.value Nilai kolom attributes.provisioningResult dari log mentah, ditempatkan dalam objek security_result.detection_fields. Kuncinya ditetapkan ke "Provisioning Result".
attributes.sourceId principal.labels[0].value Nilai kolom attributes.sourceId dari log mentah, ditempatkan dalam objek principal.labels. Kuncinya ditetapkan ke "ID Sumber".
attributes.sourceName principal.labels[1].value Nilai kolom attributes.sourceName dari log mentah, ditempatkan dalam objek principal.labels. Kuncinya ditetapkan ke "Nama Sumber".
auditClassName metadata.product_event_type Nilai kolom auditClassName dari log mentah.
created metadata.event_timestamp.seconds, metadata.event_timestamp.nanos Nilai kolom created dari log mentah, dikonversi menjadi stempel waktu jika instant.epochSecond tidak ada.
id metadata.product_log_id Nilai kolom id dari log mentah.
instant.epochSecond metadata.event_timestamp.seconds Nilai kolom instant.epochSecond dari log mentah, yang digunakan untuk stempel waktu.
ipAddress principal.asset.ip, principal.ip Nilai kolom ipAddress dari log mentah.
interface additional.fields[0].value.string_value Nilai kolom interface dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "interface".
loggerName intermediary.application Nilai kolom loggerName dari log mentah.
message metadata.description, security_result.description Digunakan untuk berbagai tujuan, termasuk menyetel deskripsi dalam metadata dan security_result, serta mengekstrak konten XML.
name security_result.description Nilai kolom name dari log mentah.
operation target.resource.attribute.labels[0].value, metadata.product_event_type Nilai kolom operation dari log mentah, ditempatkan dalam objek target.resource.attribute.labels. Kuncinya ditetapkan ke "operation". Juga digunakan untuk metadata.product_event_type.
org principal.administrative_domain Nilai kolom org dari log mentah.
pod principal.location.name Nilai kolom pod dari log mentah.
referenceClass additional.fields[1].value.string_value Nilai kolom referenceClass dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "referenceClass".
referenceId additional.fields[2].value.string_value Nilai kolom referenceId dari log mentah, ditempatkan dalam objek additional.fields. Kunci ditetapkan ke "referenceId".
sailPointObjectName additional.fields[3].value.string_value Nilai kolom sailPointObjectName dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "sailPointObjectName".
serverHost principal.hostname, principal.asset.hostname Nilai kolom serverHost dari log mentah.
stack additional.fields[4].value.string_value Nilai kolom stack dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "Stack".
status security_result.severity_details Nilai kolom status dari log mentah.
target additional.fields[4].value.string_value Nilai kolom target dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "target".
target.name principal.user.userid Nilai kolom target.name dari log mentah.
technicalName security_result.summary Nilai kolom technicalName dari log mentah.
thrown.cause.message xml_body, detailed_message Nilai kolom thrown.cause.message dari log mentah, yang digunakan untuk mengekstrak konten XML.
thrown.message xml_body, detailed_message Nilai kolom thrown.message dari log mentah, yang digunakan untuk mengekstrak konten XML.
trackingNumber additional.fields[5].value.string_value Nilai kolom trackingNumber dari log mentah, ditempatkan dalam objek additional.fields. Kuncinya ditetapkan ke "Nomor Pelacakan".
type metadata.product_event_type Nilai kolom type dari log mentah.
_version metadata.product_version Nilai kolom _version dari log mentah.
T/A metadata.event_timestamp Diperoleh dari kolom instant.epochSecond atau created.
T/A metadata.event_type Ditentukan oleh logika parser berdasarkan berbagai kolom, termasuk has_principal_user, has_target_application, technicalName, dan action. Nilai defaultnya adalah "GENERIC_EVENT".
T/A metadata.log_type Tetapkan ke "SAILPOINT_IAM".
T/A metadata.product_name Tetapkan ke IAM.
T/A metadata.vendor_name Tetapkan ke "SAILPOINT".
T/A extensions.auth.type Disetel ke "AUTHTYPE_UNSPECIFIED" dalam kondisi tertentu.
T/A target.resource.attribute.labels[0].key Tetapkan ke "operation".

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