Oracle NetSuite – NetSuite Applications Suite-Logs erfassen
In diesem Dokument wird beschrieben, wie Sie Oracle NetSuite-Logs aus der NetSuite Applications Suite mit Google Cloud Storage V2 in Google Security Operations aufnehmen.
Oracle NetSuite ist eine cloudbasierte ERP-Plattform (Enterprise Resource Planning), die umfassende Funktionen für die Unternehmensverwaltung bietet, darunter Finanzmanagement, Customer-Relationship-Management (CRM), E-Commerce und Inventarverwaltung. NetSuite generiert umfangreiche Audit-Logs, darunter Nutzeranmeldungen, Systemanmerkungen zur Nachverfolgung von Datensatzänderungen und API-Zugriffslogs, die über die SuiteQL REST API für Sicherheitsmonitoring und Compliance abgerufen werden können.
Hinweis
Folgende Voraussetzungen müssen erfüllt sein:
- Eine Google SecOps-Instanz
- Ein GCP-Projekt mit aktivierten Cloud Storage, Cloud Run, Pub/Sub und Cloud Scheduler APIs
- Berechtigungen zum Erstellen und Verwalten von GCS-Buckets
- Berechtigungen zum Verwalten von IAM-Richtlinien für GCS-Buckets
- Berechtigungen zum Erstellen von Cloud Run-Diensten, Pub/Sub-Themen und Cloud Scheduler-Jobs
- Ein NetSuite-Konto mit Administratorzugriff
- NetSuite REST-Webdienste aktiviert
- Berechtigungen zum Erstellen von Integrationsdatensätzen und Zugriffstokens in NetSuite
NetSuite-API-Zugriff konfigurieren
Wenn Sie die Logerfassung über die SuiteQL REST API aktivieren möchten, müssen Sie REST-Webdienste aktivieren, einen Integrationseintrag erstellen und in NetSuite Anmeldedaten für die tokenbasierte Authentifizierung (TBA) generieren.
Erforderliche NetSuite-Funktionen aktivieren
- Melden Sie sich als Administrator in Ihrem NetSuite-Konto an.
- Gehen Sie zu Einrichtung> Unternehmen > Funktionen aktivieren.
- Klicken Sie auf den Tab SuiteCloud.
- Klicken Sie im Bereich SuiteScript auf die folgenden Kästchen:
- Client-SuiteScript
- Server SuiteScript
- Setzen Sie im Bereich SuiteTalk (Web Services) ein Häkchen bei:
- REST-Webdienste
- Klicken Sie im Abschnitt Authentifizierung verwalten das Kästchen Tokenbasierte Authentifizierung an.
- Klicken Sie auf der Seite mit den SuiteCloud-Nutzungsbedingungen auf Ich stimme zu.
- Klicken Sie auf Speichern.
Integrationsdatensatz erstellen
- Gehen Sie in NetSuite zu Setup > Integration > Manage Integrations > New.
- Geben Sie im Feld Name
Google SecOps Integrationein. - Geben Sie im Feld Beschreibung den Text
Integration for Google Security Operations log collectionein. - Lassen Sie im Feld Status die Standardoption Aktiviert ausgewählt.
- Klicken Sie auf den Tab Authentication (Authentifizierung).
- Setzen Sie im Abschnitt Token-based Authentication (Tokenbasierte Authentifizierung) ein Häkchen bei Token-based Authentication (Tokenbasierte Authentifizierung).
- Lassen Sie das Kästchen TBA: Authorization Flow deaktiviert.
- Setzen Sie ein Häkchen in das Kästchen TBA: ISSUETOKEN ENDPOINT.
- Klicken Sie auf Speichern.
Kopieren und speichern Sie auf der Bestätigungsseite den Consumer-Key/Client-ID und den Clientschlüssel.
Benutzerdefinierte Rolle für den API-Zugriff erstellen
- Klicken Sie auf Einrichtung> Nutzer/Rollen> Rollen verwalten> Neu.
- Geben Sie im Feld Name
Google SecOps API Roleein. - Geben Sie im Feld ID den Wert
google_secops_api_roleein. - Klicken Sie auf den Untertab Berechtigungen.
- Klicken Sie auf den Untertab Einrichtung.
- Fügen Sie die folgenden Berechtigungen mit der Zugriffsebene Vollständig hinzu:
- Mit Zugriffstokens anmelden
- Nutzerzugriffstokens
- REST-Webdienste
- Klicken Sie auf den Untertab Berichte.
- Fügen Sie die folgenden Berechtigungen mit der Zugriffsebene Ansehen hinzu:
- SuiteAnalytics Workbook
- Klicken Sie auf den Untertab Listen.
- Fügen Sie die folgenden Berechtigungen mit der Zugriffsebene Ansehen hinzu:
- Audit-Trail für Anmeldungen
- Klicken Sie auf Speichern.
Nutzer eine Rolle zuweisen
- Klicken Sie auf Listen > „Mitarbeiter“ > „Mitarbeiter“.
- Wählen Sie das Nutzerkonto aus, das für die Integration verwendet werden soll, oder erstellen Sie einen neuen dedizierten Integrationsnutzer.
- Klicken Sie auf Bearbeiten.
- Klicken Sie auf den Tab Zugriff.
- Klicken Sie im Bereich Rollen auf Hinzufügen.
- Wählen Sie in der Drop-down-Liste die Option Google SecOps API Role aus.
- Klicken Sie auf Speichern.
Zugriffstoken erstellen
- Melden Sie sich in NetSuite als Nutzer an, dem die Google SecOps API-Rolle zugewiesen ist.
- Gehen Sie zu Setup > Users/Roles > Access Tokens > New.
- Auf der Seite Access Token (Zugriffstoken):
- Wählen Sie in der Drop-down-Liste Application Name (Anwendungsname) die Option Google SecOps Integration aus.
- Wählen Sie in der Drop-down-Liste Nutzer das Nutzerkonto aus, dem die Google SecOps API-Rolle zugewiesen ist.
- Wählen Sie in der Drop-down-Liste Rolle die Option Google SecOps API-Rolle aus.
- Das Feld Tokenname wird automatisch ausgefüllt. Sie können sie nach Bedarf ändern.
- Klicken Sie auf Speichern.
Kopieren und speichern Sie auf der Bestätigungsseite die Token-ID und das Token-Secret.
NetSuite-Konto-ID ermitteln
- Gehen Sie in NetSuite zu Setup> Company> Company Information.
- Suchen Sie das Feld Konto-ID (unter der Einstellung Zeitzone).
- Kopieren und speichern Sie Ihre Konto-ID (z. B.
1234567oder1234567_SB1für Sandbox-Konten).
Berechtigungen prüfen
So prüfen Sie, ob das Konto die erforderlichen Berechtigungen hat:
- Melden Sie sich in NetSuite mit dem Nutzer an, dem die Google SecOps API-Rolle zugewiesen ist.
- Gehen Sie zu Einrichtung > Nutzer/Rollen > Anmelde-Audit-Trail ansehen.
- Wenn Sie die Seite „Anmelde-Audit-Trail“ sehen können, hat das Konto die erforderlichen Berechtigungen.
- Wenn Sie diese Option nicht sehen, wenden Sie sich an Ihren NetSuite-Administrator, um die Berechtigung Login Audit Trail auf dem Untertab Lists zu erhalten.
API-Zugriff testen
Testen Sie Ihre Anmeldedaten, bevor Sie mit der Integration fortfahren:
# Replace with your actual values ACCOUNT_ID="your-account-id" # Replace underscores with hyphens in the account ID for the URL # For example: 1234567_SB1 becomes 1234567-sb1 URL_ACCOUNT_ID=$(echo "$ACCOUNT_ID" | tr '_' '-' | tr '[:upper:]' '[:lower:]') CONSUMER_KEY="your-consumer-key" CONSUMER_SECRET="your-consumer-secret" TOKEN_ID="your-token-id" TOKEN_SECRET="your-token-secret" # Test SuiteQL API access using OAuth 1.0 TBA # Note: This example requires the oauth1 CLI tool or Postman. # Use the following endpoint and headers to verify connectivity: # POST https://${URL_ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql?limit=5 # Headers: # Authorization: OAuth (with consumer key, token, HMAC-SHA256 signature) # Content-Type: application/json # Prefer: transient # Body: {"q": "SELECT id, date, status, user, emailaddress FROM LoginAudit ORDER BY date DESC"}
Eine erfolgreiche Antwort gibt ein JSON-Objekt mit LoginAudit-Datensätzen zurück.
Google Cloud Storage-Bucket erstellen
- Gehen Sie zur Google Cloud Console.
- Wählen Sie Ihr Projekt aus oder erstellen Sie ein neues.
- Rufen Sie im Navigationsmenü Cloud Storage > Buckets auf.
- Klicken Sie auf Bucket erstellen.
Geben Sie die folgenden Konfigurationsdetails an:
Einstellung Wert Bucket benennen Geben Sie einen global eindeutigen Namen ein, z. B. netsuite-audit-logs.Standorttyp Wählen Sie die Option, die am besten zu Ihren Anforderungen passt (Region, Dual-Region, Multi-Region). Standort Wählen Sie den Standort aus (z. B. us-central1).Speicherklasse Standard (empfohlen für Logs, auf die häufig zugegriffen wird) Zugriffskontrolle Einheitlich (empfohlen) Schutztools Optional: Objektversionsverwaltung oder Aufbewahrungsrichtlinie aktivieren Klicken Sie auf Erstellen.
Dienstkonto für Cloud Run-Funktion erstellen
Für die Cloud Run-Funktion ist ein Dienstkonto mit Berechtigungen zum Schreiben in den GCS-Bucket und zum Aufrufen durch Pub/Sub erforderlich.
Dienstkonto erstellen
- Rufen Sie in der GCP Console die Seite IAM und Verwaltung > Dienstkonten auf.
- Klicken Sie auf Dienstkonto erstellen.
- Geben Sie die folgenden Konfigurationsdetails an:
- Dienstkontoname: Geben Sie
netsuite-audit-collector-saein. - Beschreibung des Dienstkontos: Geben Sie
Service account for Cloud Run function to collect NetSuite audit logsein.
- Dienstkontoname: Geben Sie
- Klicken Sie auf Erstellen und fortfahren.
- Fügen Sie im Abschnitt Diesem Dienstkonto Zugriff auf das Projekt erteilen die folgenden Rollen hinzu:
- Klicken Sie auf Rolle auswählen.
- Suchen Sie nach Storage Object Admin und wählen Sie die Rolle aus.
- Klicken Sie auf + Weitere Rolle hinzufügen.
- Suchen Sie nach Cloud Run Invoker und wählen Sie die Rolle aus.
- Klicken Sie auf + Weitere Rolle hinzufügen.
- Suchen Sie nach Cloud Functions-Aufrufer und wählen Sie diese Option aus.
- Klicken Sie auf Weiter.
- Klicken Sie auf Fertig.
Diese Rollen sind für Folgendes erforderlich:
- Storage-Objekt-Administrator: Protokolle in GCS-Bucket schreiben und Statusdateien verwalten
- Cloud Run Invoker: Pub/Sub darf die Funktion aufrufen.
- Cloud Functions-Invoker: Funktionsaufruf zulassen
IAM-Berechtigungen für GCS-Bucket erteilen
- Rufen Sie Cloud Storage > Buckets auf.
- Klicken Sie auf den Namen Ihres Buckets (
netsuite-audit-logs). - Wechseln Sie zum Tab Berechtigungen.
- Klicken Sie auf Zugriff erlauben.
- Geben Sie die folgenden Konfigurationsdetails an:
- Hauptkonten hinzufügen: Geben Sie die E‑Mail-Adresse des Dienstkontos (
netsuite-audit-collector-sa@PROJECT_ID.iam.gserviceaccount.com) ein. - Rollen zuweisen: Wählen Sie Storage Object Admin aus.
- Hauptkonten hinzufügen: Geben Sie die E‑Mail-Adresse des Dienstkontos (
- Klicken Sie auf Speichern.
Pub/Sub-Thema erstellen
- Rufen Sie in der GCP Console Pub/Sub > Themen auf.
- Klicken Sie auf Thema erstellen.
- Geben Sie die folgenden Konfigurationsdetails an:
- Themen-ID: Geben Sie
netsuite-audit-triggerein. - Übernehmen Sie die anderen Einstellungen.
- Themen-ID: Geben Sie
- Klicken Sie auf Erstellen.
Cloud Run-Funktion zum Erfassen von Logs erstellen
Die Cloud Run-Funktion wird durch Pub/Sub-Nachrichten von Cloud Scheduler ausgelöst, um Logs von der NetSuite SuiteQL REST API abzurufen und in GCS zu schreiben.
- Rufen Sie in der GCP Console die Option Cloud Run auf.
- Klicken Sie auf Dienst erstellen.
- Wählen Sie Funktion aus, um eine Funktion mit einem Inline-Editor zu erstellen.
Geben Sie im Abschnitt Konfigurieren die folgenden Konfigurationsdetails an:
Einstellung Wert Dienstname netsuite-audit-collectorRegion Wählen Sie die Region aus, die Ihrem GCS-Bucket entspricht (z. B. us-central1).Laufzeit Wählen Sie Python 3.12 oder höher aus. Im Abschnitt Trigger (optional):
- Klicken Sie auf + Trigger hinzufügen.
- Wählen Sie Cloud Pub/Sub aus.
- Wählen Sie unter Cloud Pub/Sub-Thema auswählen die Option
netsuite-audit-triggeraus. - Klicken Sie auf Speichern.
Im Abschnitt Authentifizierung:
- Wählen Sie Authentifizierung erforderlich aus.
- Weitere Informationen finden Sie unter Identity and Access Management (IAM).
Scrollen Sie nach unten und maximieren Sie Container, Netzwerk, Sicherheit.
Rufen Sie den Tab Sicherheit auf:
- Dienstkonto: Wählen Sie
netsuite-audit-collector-saaus.
- Dienstkonto: Wählen Sie
Rufen Sie den Tab Container auf:
- Klicken Sie auf Variablen und Secrets.
- Klicken Sie für jede Umgebungsvariable auf + Variable hinzufügen:
Variablenname Beispielwert Beschreibung GCS_BUCKETnetsuite-audit-logsName des GCS-Buckets GCS_PREFIXnetsuite-auditPräfix für Logdateien STATE_KEYnetsuite-audit/state.jsonPfad der Statusdatei NS_ACCOUNT_ID1234567NetSuite-Konto-ID NS_CONSUMER_KEYyour-consumer-keyConsumer-Key aus dem Integrationseintrag NS_CONSUMER_SECRETyour-consumer-secretConsumer-Secret aus dem Integrationsdatensatz NS_TOKEN_IDyour-token-idToken-ID aus Zugriffstoken NS_TOKEN_SECRETyour-token-secretToken-Secret aus Zugriffstoken MAX_RECORDS10000Maximale Anzahl von Datensätzen pro Ausführung PAGE_SIZE1000Einträge pro SuiteQL-Seite LOOKBACK_HOURS24Anfänglicher Rückschauzeitraum Scrollen Sie im Bereich Variablen und Secrets nach unten zu Anfragen:
- Zeitlimit für die Anfrage: Geben Sie
600Sekunden (10 Minuten) ein.
- Zeitlimit für die Anfrage: Geben Sie
Gehen Sie zum Tab Einstellungen:
- Im Abschnitt Ressourcen:
- Arbeitsspeicher: Wählen Sie 512 MiB oder höher aus.
- CPU: Wählen Sie 1 aus.
- Im Abschnitt Ressourcen:
Im Abschnitt Versionsskalierung:
- Mindestanzahl von Instanzen: Geben Sie
0ein. - Maximale Anzahl von Instanzen: Geben Sie
100ein.
- Mindestanzahl von Instanzen: Geben Sie
Klicken Sie auf Erstellen.
Warten Sie, bis der Dienst erstellt wurde (1–2 Minuten).
Nachdem der Dienst erstellt wurde, wird automatisch der Inline-Code-Editor geöffnet.
Funktionscode hinzufügen
- Geben Sie main in das Feld Einstiegspunkt ein.
Erstellen Sie im Inline-Code-Editor zwei Dateien:
- main.py:
import functions_framework from google.cloud import storage import json import os import urllib3 import hashlib import hmac import base64 import time import secrets import urllib.parse from datetime import datetime, timezone, timedelta # Initialize HTTP client with timeouts http = urllib3.PoolManager( timeout=urllib3.Timeout(connect=10.0, read=60.0), retries=False, ) # Initialize Storage client storage_client = storage.Client() # Environment variables GCS_BUCKET = os.environ.get('GCS_BUCKET') GCS_PREFIX = os.environ.get('GCS_PREFIX', 'netsuite-audit') STATE_KEY = os.environ.get('STATE_KEY', 'netsuite-audit/state.json') NS_ACCOUNT_ID = os.environ.get('NS_ACCOUNT_ID') NS_CONSUMER_KEY = os.environ.get('NS_CONSUMER_KEY') NS_CONSUMER_SECRET = os.environ.get('NS_CONSUMER_SECRET') NS_TOKEN_ID = os.environ.get('NS_TOKEN_ID') NS_TOKEN_SECRET = os.environ.get('NS_TOKEN_SECRET') MAX_RECORDS = int(os.environ.get('MAX_RECORDS', '10000')) PAGE_SIZE = int(os.environ.get('PAGE_SIZE', '1000')) LOOKBACK_HOURS = int(os.environ.get('LOOKBACK_HOURS', '24')) def get_netsuite_base_url(account_id): """Build the NetSuite REST API base URL from the account ID.""" url_account = account_id.replace('_', '-').lower() return f"https://{url_account}.suitetalk.api.netsuite.com" def generate_oauth_header(method, url, account_id, consumer_key, consumer_secret, token_id, token_secret): """Generate OAuth 1.0 Authorization header using HMAC-SHA256.""" timestamp = str(int(time.time())) nonce = secrets.token_hex(16) # Parse URL to separate base URL and query parameters parsed = urllib.parse.urlparse(url) base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" query_params = urllib.parse.parse_qs(parsed.query, keep_blank_values=True) # Build parameter string (OAuth params + query params) params = { 'oauth_consumer_key': consumer_key, 'oauth_token': token_id, 'oauth_nonce': nonce, 'oauth_timestamp': timestamp, 'oauth_signature_method': 'HMAC-SHA256', 'oauth_version': '1.0', } for key, values in query_params.items(): params[key] = values[0] # Sort parameters and create parameter string sorted_params = sorted(params.items()) param_string = '&'.join( f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(v, safe='')}" for k, v in sorted_params ) # Create signature base string signature_base = ( f"{method.upper()}&" f"{urllib.parse.quote(base_url, safe='')}&" f"{urllib.parse.quote(param_string, safe='')}" ) # Create signing key signing_key = ( f"{urllib.parse.quote(consumer_secret, safe='')}&" f"{urllib.parse.quote(token_secret, safe='')}" ) # Generate HMAC-SHA256 signature signature = base64.b64encode( hmac.new( signing_key.encode('utf-8'), signature_base.encode('utf-8'), hashlib.sha256 ).digest() ).decode('utf-8') # Build realm from account ID realm = account_id.replace('-', '_').upper() # Build Authorization header auth_header = ( f'OAuth realm="{realm}",' f'oauth_consumer_key="{consumer_key}",' f'oauth_token="{token_id}",' f'oauth_nonce="{nonce}",' f'oauth_timestamp="{timestamp}",' f'oauth_signature_method="HMAC-SHA256",' f'oauth_version="1.0",' f'oauth_signature="{urllib.parse.quote(signature, safe="")}"' ) return auth_header @functions_framework.cloud_event def main(cloud_event): """ Cloud Run function triggered by Pub/Sub to fetch NetSuite LoginAudit logs via SuiteQL and write to GCS. """ if not all([GCS_BUCKET, NS_ACCOUNT_ID, NS_CONSUMER_KEY, NS_CONSUMER_SECRET, NS_TOKEN_ID, NS_TOKEN_SECRET]): print('Error: Missing required environment variables') return try: bucket = storage_client.bucket(GCS_BUCKET) state = load_state(bucket) now = datetime.now(timezone.utc) if isinstance(state, dict) and state.get('last_event_time'): try: last_val = state['last_event_time'] if last_val.endswith('Z'): last_val = last_val[:-1] + '+00:00' last_time = datetime.fromisoformat(last_val) last_time = last_time - timedelta(minutes=2) except Exception as e: print(f"Warning: Could not parse last_event_time: {e}") last_time = now - timedelta(hours=LOOKBACK_HOURS) else: last_time = now - timedelta(hours=LOOKBACK_HOURS) print(f"Fetching logs from {last_time.isoformat()} to {now.isoformat()}") records, newest_time = fetch_login_audit(last_time, now) if not records: print("No new log records found.") save_state(bucket, now.isoformat()) return timestamp = now.strftime('%Y%m%d_%H%M%S') object_key = f"{GCS_PREFIX}/netsuite_audit_{timestamp}.ndjson" blob = bucket.blob(object_key) ndjson = '\n'.join( [json.dumps(r, ensure_ascii=False, default=str) for r in records] ) + '\n' blob.upload_from_string(ndjson, content_type='application/x-ndjson') print(f"Wrote {len(records)} records to gs://{GCS_BUCKET}/{object_key}") if newest_time: save_state(bucket, newest_time) else: save_state(bucket, now.isoformat()) print(f"Successfully processed {len(records)} records") except Exception as e: print(f'Error processing logs: {str(e)}') raise def fetch_login_audit(start_time, end_time): """ Fetch LoginAudit records from NetSuite SuiteQL API with pagination. Returns: Tuple of (records list, newest_event_time ISO string) """ base_url = get_netsuite_base_url(NS_ACCOUNT_ID) suiteql_endpoint = f"{base_url}/services/rest/query/v1/suiteql" start_str = start_time.strftime('%Y-%m-%d %H:%M:%S') end_str = end_time.strftime('%Y-%m-%d %H:%M:%S') query = ( "SELECT id, date, status, user, role, emailaddress, " "ipaddress, useragent, detail, requesturi, " "oauthaccesstokenname, oauthappname " "FROM LoginAudit " f"WHERE date BETWEEN TO_DATE('{start_str}', 'YYYY-MM-DD HH24:MI:SS') " f"AND TO_DATE('{end_str}', 'YYYY-MM-DD HH24:MI:SS') " "ORDER BY date ASC" ) records = [] newest_time = None offset = 0 backoff = 1.0 while True: if len(records) >= MAX_RECORDS: print(f"Reached max_records limit ({MAX_RECORDS})") break remaining = min(PAGE_SIZE, MAX_RECORDS - len(records)) url = f"{suiteql_endpoint}?limit={remaining}&offset={offset}" auth_header = generate_oauth_header( 'POST', url, NS_ACCOUNT_ID, NS_CONSUMER_KEY, NS_CONSUMER_SECRET, NS_TOKEN_ID, NS_TOKEN_SECRET ) headers = { 'Authorization': auth_header, 'Content-Type': 'application/json', 'Prefer': 'transient', 'User-Agent': 'GoogleSecOps-NetSuiteCollector/1.0', } body = json.dumps({'q': query}).encode('utf-8') try: response = http.request('POST', url, headers=headers, body=body) if response.status == 429: retry_after = int( response.headers.get('Retry-After', str(int(backoff))) ) print(f"Rate limited (429). Retrying after {retry_after}s...") time.sleep(retry_after) backoff = min(backoff * 2, 60.0) continue backoff = 1.0 if response.status != 200: print(f"HTTP Error: {response.status}") response_text = response.data.decode('utf-8') print(f"Response body: {response_text}") return records, newest_time data = json.loads(response.data.decode('utf-8')) page_results = data.get('items', []) if not page_results: print("No more results (empty page)") break page_num = (offset // PAGE_SIZE) + 1 print(f"Page {page_num}: Retrieved {len(page_results)} records") records.extend(page_results) for event in page_results: try: event_time = event.get('date') if event_time: if newest_time is None or event_time > newest_time: newest_time = event_time except Exception as e: print(f"Warning: Could not parse event time: {e}") has_more = data.get('hasMore', False) if not has_more: print("Reached last page (hasMore=false)") break offset += len(page_results) except Exception as e: print(f"Error fetching logs: {e}") return records, newest_time print(f"Retrieved {len(records)} total records") return records, newest_time def load_state(bucket): """Load state from GCS.""" try: blob = bucket.blob(STATE_KEY) if blob.exists(): return json.loads(blob.download_as_text()) except Exception as e: print(f"Warning: Could not load state: {e}") return {} def save_state(bucket, last_event_time_iso): """Save the last event timestamp to GCS state file.""" try: state = { 'last_event_time': last_event_time_iso, 'last_run': datetime.now(timezone.utc).isoformat() } blob = bucket.blob(STATE_KEY) blob.upload_from_string( json.dumps(state, indent=2), content_type='application/json' ) print(f"Saved state: last_event_time={last_event_time_iso}") except Exception as e: print(f"Warning: Could not save state: {e}")- requirements.txt:
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0Klicken Sie auf Bereitstellen, um die Funktion zu speichern und bereitzustellen.
Warten Sie, bis die Bereitstellung abgeschlossen ist (2–3 Minuten).
Cloud Scheduler-Job erstellen
Cloud Scheduler veröffentlicht in regelmäßigen Abständen Nachrichten im Pub/Sub-Thema und löst so die Cloud Run-Funktion aus.
- Rufen Sie in der GCP Console Cloud Scheduler auf.
- Klicken Sie auf Job erstellen.
Geben Sie die folgenden Konfigurationsdetails an:
Einstellung Wert Name netsuite-audit-collector-hourlyRegion Dieselbe Region wie die Cloud Run-Funktion auswählen Frequenz 0 * * * *(stündlich zur vollen Stunde)Zeitzone Zeitzone auswählen (UTC empfohlen) Zieltyp Pub/Sub Thema netsuite-audit-triggerauswählenInhalt der Nachricht {}(leeres JSON-Objekt)Klicken Sie auf Erstellen.
Optionen für die Häufigkeit des Zeitplans
Wählen Sie die Häufigkeit basierend auf dem Logvolumen und den Latenzanforderungen aus:
| Häufigkeit | Cron-Ausdruck | Anwendungsfall |
|---|---|---|
| Alle 5 Minuten | */5 * * * * |
Hohes Volumen, niedrige Latenz |
| Alle 15 Minuten | */15 * * * * |
Mittleres Suchvolumen |
| Stündlich | 0 * * * * |
Standard (empfohlen) |
| Alle 6 Stunden | 0 */6 * * * |
Geringes Volumen, Batchverarbeitung |
| Täglich | 0 0 * * * |
Erhebung von Verlaufsdaten |
Integration testen
- Suchen Sie in der Cloud Scheduler-Konsole nach Ihrem Job (
netsuite-audit-collector-hourly). - Klicken Sie auf Ausführung erzwingen, um den Job manuell auszulösen.
- Warten Sie einige Sekunden.
- Rufen Sie Cloud Run > Dienste auf.
- Klicken Sie auf
netsuite-audit-collector. - Klicken Sie auf den Tab Logs.
Prüfen Sie, ob die Funktion erfolgreich ausgeführt wurde. Darauf sollten Sie achten:
Fetching logs from YYYY-MM-DDTHH:MM:SS+00:00 to YYYY-MM-DDTHH:MM:SS+00:00 Page 1: Retrieved X records Wrote X records to gs://netsuite-audit-logs/netsuite-audit/netsuite_audit_YYYYMMDD_HHMMSS.ndjson Successfully processed X recordsRufen Sie Cloud Storage > Buckets auf.
Klicken Sie auf
netsuite-audit-logs.Rufen Sie den Ordner
netsuite-audit/auf.Prüfen Sie, ob eine neue
.ndjson-Datei mit dem aktuellen Zeitstempel erstellt wurde.
Wenn Sie Fehler in den Logs sehen:
- HTTP 401: Prüfen Sie, ob die OAuth-Anmeldedaten (
NS_CONSUMER_KEY,NS_CONSUMER_SECRET,NS_TOKEN_ID,NS_TOKEN_SECRET) und die UmgebungsvariablenNS_ACCOUNT_IDkorrekt sind. - HTTP 403: Prüfen Sie, ob die NetSuite-Rolle die erforderlichen Berechtigungen hat (REST-Webdienste, Login Audit Trail, SuiteAnalytics Workbook).
- HTTP 429: Ratenbegrenzung – die Funktion wird automatisch mit exponentiellem Backoff wiederholt.
- Fehlende Umgebungsvariablen: Prüfen Sie, ob alle erforderlichen Variablen in der Konfiguration der Cloud Run-Funktion festgelegt sind.
Google SecOps-Dienstkonto abrufen
- Rufen Sie die SIEM-Einstellungen > Feeds auf.
- Klicken Sie auf Neuen Feed hinzufügen.
- Klicken Sie auf Einzelnen Feed konfigurieren.
- Geben Sie im Feld Feedname einen Namen für den Feed ein, z. B.
NetSuite Audit Logs. - Wählen Sie Google Cloud Storage V2 als Quelltyp aus.
- Wählen Sie Oracle NetSuite – NetSuite Applications Suite als Log type (Protokolltyp) aus.
Klicken Sie auf Dienstkonto abrufen. Eine eindeutige E‑Mail-Adresse für das Dienstkonto wird angezeigt. Beispiel:
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.comKopieren Sie diese E-Mail-Adresse, um sie im nächsten Schritt zu verwenden.
Klicken Sie auf Weiter.
Geben Sie Werte für die folgenden Eingabeparameter an:
Storage-Bucket-URL: Geben Sie den GCS-Bucket-URI mit dem Präfixpfad ein:
gs://netsuite-audit-logs/netsuite-audit/
- Option zum Löschen der Quelle: Wählen Sie die gewünschte Option zum Löschen aus:
- Nie: Löscht nach Übertragungen nie Dateien (empfohlen für Tests).
- Übertragene Dateien löschen: Dateien werden nach der erfolgreichen Übertragung gelöscht.
- Übertragene Dateien und leere Verzeichnisse löschen: Löscht Dateien und leere Verzeichnisse nach erfolgreicher Übertragung.
- Höchstalter der Datei: Dateien einbeziehen, die in den letzten Tagen geändert wurden (Standardwert: 180 Tage)
- Asset-Namespace: Der Asset-Namespace
- Labels für Datenaufnahme: Das Label, das auf die Ereignisse aus diesem Feed angewendet werden soll.
Klicken Sie auf Weiter.
Prüfen Sie die neue Nachrichten-Feed-Konfiguration auf dem Bildschirm Abschließen und klicken Sie dann auf Senden.
Dem Google SecOps-Dienstkonto IAM-Berechtigungen gewähren
Das Google SecOps-Dienstkonto benötigt die Rolle Storage Object Viewer für Ihren GCS-Bucket.
- Rufen Sie Cloud Storage > Buckets auf.
- Klicken Sie auf
netsuite-audit-logs. - Wechseln Sie zum Tab Berechtigungen.
- Klicken Sie auf Zugriff erlauben.
- Geben Sie die folgenden Konfigurationsdetails an:
- Hauptkonten hinzufügen: Fügen Sie die E‑Mail-Adresse des Google SecOps-Dienstkontos ein.
- Rollen zuweisen: Wählen Sie Storage Object Viewer aus.
Klicken Sie auf Speichern.
NetSuite API-Ratenlimits
Für die NetSuite API gelten die folgenden Limits für Parallelität und Ratenbegrenzung:
| Limit-Typ | Standardwert | Hinweise |
|---|---|---|
| Gleichzeitige Anfragen | 15 Anfragen | Gilt für kombinierte SOAP- und REST API-Anfragen pro Konto |
| SuiteQL-Abfrageergebnisse | Maximal 100.000 Zeilen | Maximale Anzahl von Zeilen, die pro SuiteQL-Abfrage zurückgegeben werden |
| Objekte pro Anfrage | Maximal 1.000 Objekte | Maximale Anzahl von Objekten, die pro API-Anfrage zurückgegeben werden |
Die Cloud Run-Funktion verarbeitet die Ratenbegrenzung automatisch mit exponentiellem Backoff. Wenn Sie weiterhin Ratenbegrenzungsfehler (HTTP 429) erhalten, wenden Sie sich an den NetSuite-Support, um Ihre API-Limits zu erhöhen oder die Cloud Scheduler-Häufigkeit anzupassen.
UDM-Zuordnungstabelle
| Logfeld | UDM-Zuordnung | Logik |
|---|---|---|
| Nutzer | extensions.auth.type | Art des verwendeten Authentifizierungsmechanismus |
| date | metadata.event_timestamp | Zeitstempel des Ereignisses |
| event_type | metadata.event_type | Ereignistyp (z.B. USER_LOGIN, NETWORK_CONNECTION) |
| security_result | security_result | Details zum Sicherheitsergebnis des Ereignisses |
| oauthaccesstokenname | security_result.about.resource.attribute.labels | Schlüssel/Wert-Labels, die Attribute der Ressource beschreiben, die am Sicherheitsergebnis beteiligt ist |
| secchallenge | security_result.about.resource.attribute.labels | |
| Status | security_result.summary | Zusammenfassende Beschreibung des Sicherheitsergebnisses |
| oauthappname | target.application | Name der Zielanwendung |
| useragent | target.application | |
| ipaddress | target.ip | IP-Adresse des Ziels |
| requesturi | target.url | URL der Zielressource |
| emailaddress | target.user.email_addresses | Mit dem Zielnutzer verknüpfte E‑Mail-Adressen |
| Rolle | target.user.role_name | Name der Rolle, die dem Zielnutzer zugewiesen ist |
| Nutzer | target.user.userid | Eindeutige Kennung für den Zielnutzer |
Änderungsprotokoll
Änderungsprotokoll für diesen Parser ansehen
Benötigen Sie weitere Hilfe? Antworten von Community-Mitgliedern und Google SecOps-Experten erhalten