Raccogliere i log IAM di SailPoint
Questo documento spiega come importare i log di SailPoint Identity and Access Management (IAM) in Google Security Operations utilizzando Amazon S3. Il parser gestisce i log in formato JSON e XML, trasformandoli nel modello Unified Data Model (UDM). Fa distinzione tra eventi UDM singoli (ProvisioningPlan, AccountRequest, SOAP-ENV), eventi UDM multipli (ProvisioningProject) ed entità UDM (Identity), applicando logica di analisi e mappature dei campi specifiche per ciascuno, inclusa la gestione generica degli eventi per i dati non XML.
Prima di iniziare
Assicurati di soddisfare i seguenti prerequisiti:
- Un'istanza Google SecOps.
- Accesso con privilegi a SailPoint Identity Security Cloud.
- Accesso privilegiato ad AWS (S3, IAM, Lambda, EventBridge).
Raccogli i prerequisiti di SailPoint IAM (ID, chiavi API, ID organizzazione, token)
- Accedi alla console di amministrazione di SailPoint Identity Security Cloud come amministratore.
- Vai a Impostazioni di sicurezza > globali > Gestione API.
- Fai clic su Crea client API.
- Seleziona Credenziali client come tipo di concessione.
- Fornisci i seguenti dettagli di configurazione:
- Nome: inserisci un nome descrittivo (ad esempio,
Google SecOps Export API
). - Descrizione: inserisci la descrizione del client API.
- Ambiti: seleziona
sp:scopes:all
.
- Nome: inserisci un nome descrittivo (ad esempio,
- Fai clic su Crea e salva le credenziali API generate in una posizione sicura.
- Registra l'URL di base del tenant SailPoint (ad esempio,
https://tenant.api.identitynow.com
). - Copia e salva in una posizione sicura i seguenti dettagli:
- IDN_CLIENT_ID.
- IDN_CLIENT_SECRET.
- IDN_BASE.
Configura il bucket AWS S3 e IAM per Google SecOps
- Crea un bucket Amazon S3 seguendo questa guida utente: Creazione di un bucket
- Salva il nome e la regione del bucket per riferimento futuro (ad esempio,
sailpoint-iam-logs
). - Crea un utente seguendo questa guida utente: Creazione di un utente IAM.
- Seleziona l'utente creato.
- Seleziona la scheda Credenziali di sicurezza.
- Fai clic su Crea chiave di accesso nella sezione Chiavi di accesso.
- Seleziona Servizio di terze parti come Caso d'uso.
- Fai clic su Avanti.
- (Facoltativo) Aggiungi il tag della descrizione.
- Fai clic su Crea chiave di accesso.
- Fai clic su Scarica file CSV per salvare la chiave di accesso e la chiave di accesso segreta per riferimento futuro.
- Fai clic su Fine.
- Seleziona la scheda Autorizzazioni.
- Fai clic su Aggiungi autorizzazioni nella sezione Criteri per le autorizzazioni.
- Seleziona Aggiungi autorizzazioni.
- Seleziona Allega direttamente i criteri.
- Cerca i criteri AmazonS3FullAccess.
- Seleziona la policy.
- Fai clic su Avanti.
- Fai clic su Aggiungi autorizzazioni.
Configura il ruolo e il criterio IAM per i caricamenti S3
- Nella console AWS, vai a IAM > Policy.
- Fai clic su Crea criterio > scheda JSON.
- Copia e incolla i seguenti criteri.
JSON delle policy (sostituisci
sailpoint-iam-logs
se hai inserito un nome del bucket diverso):{ "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" } ] }
Fai clic su Avanti > Crea criterio.
Vai a IAM > Ruoli > Crea ruolo > Servizio AWS > Lambda.
Allega il criterio appena creato.
Assegna al ruolo il nome
SailPointIamToS3Role
e fai clic su Crea ruolo.
Crea la funzione Lambda
- Nella console AWS, vai a Lambda > Funzioni > Crea funzione.
- Fai clic su Crea autore da zero.
Fornisci i seguenti dettagli di configurazione:
Impostazione Valore Nome sailpoint_iam_to_s3
Tempo di esecuzione Python 3.13 Architettura x86_64 Ruolo di esecuzione SailPointIamToS3Role
Dopo aver creato la funzione, apri la scheda Codice, elimina lo stub e incolla il seguente codice (
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())
Vai a Configurazione > Variabili di ambiente.
Fai clic su Modifica > Aggiungi nuova variabile di ambiente.
Inserisci le variabili di ambiente fornite nella tabella seguente, sostituendo i valori di esempio con i tuoi valori.
Variabili di ambiente
Chiave Valore di esempio 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
(dal passaggio 2)IDN_CLIENT_SECRET
your-client-secret
(dal passaggio 2)IDN_SCOPE
sp:scopes:all
PAGE_SIZE
250
MAX_PAGES
20
Dopo aver creato la funzione, rimani sulla relativa pagina (o apri Lambda > Funzioni > la tua funzione).
Seleziona la scheda Configurazione.
Nel riquadro Configurazione generale, fai clic su Modifica.
Modifica Timeout impostando 5 minuti (300 secondi) e fai clic su Salva.
Creare una pianificazione EventBridge
- Vai a Amazon EventBridge > Scheduler > Crea pianificazione.
- Fornisci i seguenti dettagli di configurazione:
- Programma ricorrente: Tariffa (
1 hour
). - Target: la tua funzione Lambda
sailpoint_iam_to_s3
. - Nome:
sailpoint-iam-1h
- Programma ricorrente: Tariffa (
- Fai clic su Crea pianificazione.
(Facoltativo) Crea chiavi e utenti IAM di sola lettura per Google SecOps
- Vai alla console AWS > IAM > Utenti.
- Fai clic su Add users (Aggiungi utenti).
- Fornisci i seguenti dettagli di configurazione:
- Utente: inserisci
secops-reader
. - Tipo di accesso: seleziona Chiave di accesso - Accesso programmatico.
- Utente: inserisci
- Fai clic su Crea utente.
- Collega la criterio per la lettura minima (personalizzata): Utenti > secops-reader > Autorizzazioni > Aggiungi autorizzazioni > Collega le norme direttamente > Crea norma.
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" } ] }
Name =
secops-reader-policy
.Fai clic su Crea criterio > cerca/seleziona > Avanti > Aggiungi autorizzazioni.
Crea la chiave di accesso per
secops-reader
: Credenziali di sicurezza > Chiavi di accesso.Fai clic su Crea chiave di accesso.
Scarica il
.CSV
. Incollerai questi valori nel feed.
Configura un feed in Google SecOps per importare i log IAM di SailPoint
- Vai a Impostazioni SIEM > Feed.
- Fai clic su + Aggiungi nuovo feed.
- Nel campo Nome feed, inserisci un nome per il feed (ad esempio,
SailPoint IAM logs
). - Seleziona Amazon S3 V2 come Tipo di origine.
- Seleziona SailPoint IAM come Tipo di log.
- Fai clic su Avanti.
- Specifica i valori per i seguenti parametri di input:
- URI S3:
s3://sailpoint-iam-logs/sailpoint/iam/
- Opzioni di eliminazione dell'origine: seleziona l'opzione di eliminazione in base alle tue preferenze.
- Età massima del file: includi i file modificati nell'ultimo numero di giorni. Il valore predefinito è 180 giorni.
- ID chiave di accesso: chiave di accesso utente con accesso al bucket S3.
- Chiave di accesso segreta: chiave segreta dell'utente con accesso al bucket S3.
- Spazio dei nomi dell'asset: lo spazio dei nomi dell'asset.
- Etichette di importazione: l'etichetta applicata agli eventi di questo feed.
- URI S3:
- Fai clic su Avanti.
- Controlla la nuova configurazione del feed nella schermata Finalizza e poi fai clic su Invia.
Tabella di mappatura UDM
Campo log | Mappatura UDM | Logic |
---|---|---|
action |
metadata.description |
Il valore del campo action del log non elaborato. |
actor.name |
principal.user.user_display_name |
Il valore del campo actor.name del log non elaborato. |
attributes.accountName |
principal.user.group_identifiers |
Il valore del campo attributes.accountName del log non elaborato. |
attributes.appId |
target.asset_id |
"App ID: " concatenato al valore del campo attributes.appId del log non elaborato. |
attributes.attributeName |
additional.fields[0].value.string_value |
Il valore del campo attributes.attributeName del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "Nome attributo". |
attributes.attributeValue |
additional.fields[1].value.string_value |
Il valore del campo attributes.attributeValue del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "Valore attributo". |
attributes.cloudAppName |
target.application |
Il valore del campo attributes.cloudAppName del log non elaborato. |
attributes.hostName |
target.hostname , target.asset.hostname |
Il valore del campo attributes.hostName del log non elaborato. |
attributes.interface |
additional.fields[2].value.string_value |
Il valore del campo attributes.interface del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "Interface". |
attributes.operation |
security_result.action_details |
Il valore del campo attributes.operation del log non elaborato. |
attributes.previousValue |
additional.fields[3].value.string_value |
Il valore del campo attributes.previousValue del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "Valore precedente". |
attributes.provisioningResult |
security_result.detection_fields.value |
Il valore del campo attributes.provisioningResult del log non elaborato, inserito in un oggetto security_result.detection_fields . La chiave è impostata su "Provisioning Result". |
attributes.sourceId |
principal.labels[0].value |
Il valore del campo attributes.sourceId del log non elaborato, inserito in un oggetto principal.labels . La chiave è impostata su "ID origine". |
attributes.sourceName |
principal.labels[1].value |
Il valore del campo attributes.sourceName del log non elaborato, inserito in un oggetto principal.labels . La chiave è impostata su "Nome origine". |
auditClassName |
metadata.product_event_type |
Il valore del campo auditClassName del log non elaborato. |
created |
metadata.event_timestamp.seconds , metadata.event_timestamp.nanos |
Il valore del campo created del log non elaborato, convertito in timestamp se instant.epochSecond non è presente. |
id |
metadata.product_log_id |
Il valore del campo id del log non elaborato. |
instant.epochSecond |
metadata.event_timestamp.seconds |
Il valore del campo instant.epochSecond del log non elaborato, utilizzato per il timestamp. |
ipAddress |
principal.asset.ip , principal.ip |
Il valore del campo ipAddress del log non elaborato. |
interface |
additional.fields[0].value.string_value |
Il valore del campo interface del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "interface". |
loggerName |
intermediary.application |
Il valore del campo loggerName del log non elaborato. |
message |
metadata.description , security_result.description |
Utilizzato per vari scopi, tra cui l'impostazione della descrizione nei metadati e in security_result e l'estrazione di contenuti XML. |
name |
security_result.description |
Il valore del campo name del log non elaborato. |
operation |
target.resource.attribute.labels[0].value , metadata.product_event_type |
Il valore del campo operation del log non elaborato, inserito in un oggetto target.resource.attribute.labels . La chiave è impostata su "operation". Utilizzato anche per metadata.product_event_type . |
org |
principal.administrative_domain |
Il valore del campo org del log non elaborato. |
pod |
principal.location.name |
Il valore del campo pod del log non elaborato. |
referenceClass |
additional.fields[1].value.string_value |
Il valore del campo referenceClass del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "referenceClass". |
referenceId |
additional.fields[2].value.string_value |
Il valore del campo referenceId del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "referenceId". |
sailPointObjectName |
additional.fields[3].value.string_value |
Il valore del campo sailPointObjectName del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "sailPointObjectName". |
serverHost |
principal.hostname , principal.asset.hostname |
Il valore del campo serverHost del log non elaborato. |
stack |
additional.fields[4].value.string_value |
Il valore del campo stack del log non elaborato, inserito in un oggetto additional.fields . Il tasto è impostato su "Pila". |
status |
security_result.severity_details |
Il valore del campo status del log non elaborato. |
target |
additional.fields[4].value.string_value |
Il valore del campo target del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "target". |
target.name |
principal.user.userid |
Il valore del campo target.name del log non elaborato. |
technicalName |
security_result.summary |
Il valore del campo technicalName del log non elaborato. |
thrown.cause.message |
xml_body , detailed_message |
Il valore del campo thrown.cause.message del log non elaborato, utilizzato per estrarre i contenuti XML. |
thrown.message |
xml_body , detailed_message |
Il valore del campo thrown.message del log non elaborato, utilizzato per estrarre i contenuti XML. |
trackingNumber |
additional.fields[5].value.string_value |
Il valore del campo trackingNumber del log non elaborato, inserito in un oggetto additional.fields . La chiave è impostata su "Numero di tracciamento". |
type |
metadata.product_event_type |
Il valore del campo type del log non elaborato. |
_version |
metadata.product_version |
Il valore del campo _version del log non elaborato. |
N/D | metadata.event_timestamp |
Derivato dai campi instant.epochSecond o created . |
N/D | metadata.event_type |
Determinato dalla logica del parser in base a vari campi, tra cui has_principal_user , has_target_application , technicalName e action . Il valore predefinito è "GENERIC_EVENT". |
N/D | metadata.log_type |
Imposta il valore su "SAILPOINT_IAM". |
N/D | metadata.product_name |
Imposta su IAM . |
N/D | metadata.vendor_name |
Imposta su "SAILPOINT". |
N/D | extensions.auth.type |
Impostato su "AUTHTYPE_UNSPECIFIED" in determinate condizioni. |
N/D | target.resource.attribute.labels[0].key |
Imposta il valore su "operation". |
Hai bisogno di ulteriore assistenza? Ricevi risposte dai membri della community e dai professionisti di Google SecOps.