Oracle NetSuite – NetSuite Applications Suite-Logs erfassen

Unterstützt in:

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

  1. Melden Sie sich als Administrator in Ihrem NetSuite-Konto an.
  2. Gehen Sie zu Einrichtung> Unternehmen > Funktionen aktivieren.
  3. Klicken Sie auf den Tab SuiteCloud.
  4. Klicken Sie im Bereich SuiteScript auf die folgenden Kästchen:
    • Client-SuiteScript
    • Server SuiteScript
  5. Setzen Sie im Bereich SuiteTalk (Web Services) ein Häkchen bei:
    • REST-Webdienste
  6. Klicken Sie im Abschnitt Authentifizierung verwalten das Kästchen Tokenbasierte Authentifizierung an.
  7. Klicken Sie auf der Seite mit den SuiteCloud-Nutzungsbedingungen auf Ich stimme zu.
  8. Klicken Sie auf Speichern.

Integrationsdatensatz erstellen

  1. Gehen Sie in NetSuite zu Setup > Integration > Manage Integrations > New.
  2. Geben Sie im Feld Name Google SecOps Integration ein.
  3. Geben Sie im Feld Beschreibung den Text Integration for Google Security Operations log collection ein.
  4. Lassen Sie im Feld Status die Standardoption Aktiviert ausgewählt.
  5. Klicken Sie auf den Tab Authentication (Authentifizierung).
  6. Setzen Sie im Abschnitt Token-based Authentication (Tokenbasierte Authentifizierung) ein Häkchen bei Token-based Authentication (Tokenbasierte Authentifizierung).
  7. Lassen Sie das Kästchen TBA: Authorization Flow deaktiviert.
  8. Setzen Sie ein Häkchen in das Kästchen TBA: ISSUETOKEN ENDPOINT.
  9. Klicken Sie auf Speichern.
  10. 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

  1. Klicken Sie auf Einrichtung> Nutzer/Rollen> Rollen verwalten> Neu.
  2. Geben Sie im Feld Name Google SecOps API Role ein.
  3. Geben Sie im Feld ID den Wert google_secops_api_role ein.
  4. Klicken Sie auf den Untertab Berechtigungen.
  5. Klicken Sie auf den Untertab Einrichtung.
  6. Fügen Sie die folgenden Berechtigungen mit der Zugriffsebene Vollständig hinzu:
    • Mit Zugriffstokens anmelden
    • Nutzerzugriffstokens
    • REST-Webdienste
  7. Klicken Sie auf den Untertab Berichte.
  8. Fügen Sie die folgenden Berechtigungen mit der Zugriffsebene Ansehen hinzu:
    • SuiteAnalytics Workbook
  9. Klicken Sie auf den Untertab Listen.
  10. Fügen Sie die folgenden Berechtigungen mit der Zugriffsebene Ansehen hinzu:
    • Audit-Trail für Anmeldungen
  11. Klicken Sie auf Speichern.

Nutzer eine Rolle zuweisen

  1. Klicken Sie auf Listen > „Mitarbeiter“ > „Mitarbeiter“.
  2. Wählen Sie das Nutzerkonto aus, das für die Integration verwendet werden soll, oder erstellen Sie einen neuen dedizierten Integrationsnutzer.
  3. Klicken Sie auf Bearbeiten.
  4. Klicken Sie auf den Tab Zugriff.
  5. Klicken Sie im Bereich Rollen auf Hinzufügen.
  6. Wählen Sie in der Drop-down-Liste die Option Google SecOps API Role aus.
  7. Klicken Sie auf Speichern.

Zugriffstoken erstellen

  1. Melden Sie sich in NetSuite als Nutzer an, dem die Google SecOps API-Rolle zugewiesen ist.
  2. Gehen Sie zu Setup > Users/Roles > Access Tokens > New.
  3. 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.
  4. Klicken Sie auf Speichern.
  5. Kopieren und speichern Sie auf der Bestätigungsseite die Token-ID und das Token-Secret.

NetSuite-Konto-ID ermitteln

  1. Gehen Sie in NetSuite zu Setup> Company> Company Information.
  2. Suchen Sie das Feld Konto-ID (unter der Einstellung Zeitzone).
  3. Kopieren und speichern Sie Ihre Konto-ID (z. B. 1234567 oder 1234567_SB1 für Sandbox-Konten).

Berechtigungen prüfen

So prüfen Sie, ob das Konto die erforderlichen Berechtigungen hat:

  1. Melden Sie sich in NetSuite mit dem Nutzer an, dem die Google SecOps API-Rolle zugewiesen ist.
  2. Gehen Sie zu Einrichtung > Nutzer/Rollen > Anmelde-Audit-Trail ansehen.
  3. Wenn Sie die Seite „Anmelde-Audit-Trail“ sehen können, hat das Konto die erforderlichen Berechtigungen.
  4. 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

  1. Gehen Sie zur Google Cloud Console.
  2. Wählen Sie Ihr Projekt aus oder erstellen Sie ein neues.
  3. Rufen Sie im Navigationsmenü Cloud Storage > Buckets auf.
  4. Klicken Sie auf Bucket erstellen.
  5. 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
  6. 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

  1. Rufen Sie in der GCP Console die Seite IAM und Verwaltung > Dienstkonten auf.
  2. Klicken Sie auf Dienstkonto erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:
    • Dienstkontoname: Geben Sie netsuite-audit-collector-sa ein.
    • Beschreibung des Dienstkontos: Geben Sie Service account for Cloud Run function to collect NetSuite audit logs ein.
  4. Klicken Sie auf Erstellen und fortfahren.
  5. Fügen Sie im Abschnitt Diesem Dienstkonto Zugriff auf das Projekt erteilen die folgenden Rollen hinzu:
    1. Klicken Sie auf Rolle auswählen.
    2. Suchen Sie nach Storage Object Admin und wählen Sie die Rolle aus.
    3. Klicken Sie auf + Weitere Rolle hinzufügen.
    4. Suchen Sie nach Cloud Run Invoker und wählen Sie die Rolle aus.
    5. Klicken Sie auf + Weitere Rolle hinzufügen.
    6. Suchen Sie nach Cloud Functions-Aufrufer und wählen Sie diese Option aus.
  6. Klicken Sie auf Weiter.
  7. 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

  1. Rufen Sie Cloud Storage > Buckets auf.
  2. Klicken Sie auf den Namen Ihres Buckets (netsuite-audit-logs).
  3. Wechseln Sie zum Tab Berechtigungen.
  4. Klicken Sie auf Zugriff erlauben.
  5. 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.
  6. Klicken Sie auf Speichern.

Pub/Sub-Thema erstellen

  1. Rufen Sie in der GCP Console Pub/Sub > Themen auf.
  2. Klicken Sie auf Thema erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:
    • Themen-ID: Geben Sie netsuite-audit-trigger ein.
    • Übernehmen Sie die anderen Einstellungen.
  4. 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.

  1. Rufen Sie in der GCP Console die Option Cloud Run auf.
  2. Klicken Sie auf Dienst erstellen.
  3. Wählen Sie Funktion aus, um eine Funktion mit einem Inline-Editor zu erstellen.
  4. Geben Sie im Abschnitt Konfigurieren die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Dienstname netsuite-audit-collector
    Region 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.
  5. Im Abschnitt Trigger (optional):

    1. Klicken Sie auf + Trigger hinzufügen.
    2. Wählen Sie Cloud Pub/Sub aus.
    3. Wählen Sie unter Cloud Pub/Sub-Thema auswählen die Option netsuite-audit-trigger aus.
    4. Klicken Sie auf Speichern.
  6. Im Abschnitt Authentifizierung:

    1. Wählen Sie Authentifizierung erforderlich aus.
    2. Weitere Informationen finden Sie unter Identity and Access Management (IAM).
  7. Scrollen Sie nach unten und maximieren Sie Container, Netzwerk, Sicherheit.

  8. Rufen Sie den Tab Sicherheit auf:

    • Dienstkonto: Wählen Sie netsuite-audit-collector-sa aus.
  9. Rufen Sie den Tab Container auf:

    1. Klicken Sie auf Variablen und Secrets.
    2. Klicken Sie für jede Umgebungsvariable auf + Variable hinzufügen:
    Variablenname Beispielwert Beschreibung
    GCS_BUCKET netsuite-audit-logs Name des GCS-Buckets
    GCS_PREFIX netsuite-audit Präfix für Logdateien
    STATE_KEY netsuite-audit/state.json Pfad der Statusdatei
    NS_ACCOUNT_ID 1234567 NetSuite-Konto-ID
    NS_CONSUMER_KEY your-consumer-key Consumer-Key aus dem Integrationseintrag
    NS_CONSUMER_SECRET your-consumer-secret Consumer-Secret aus dem Integrationsdatensatz
    NS_TOKEN_ID your-token-id Token-ID aus Zugriffstoken
    NS_TOKEN_SECRET your-token-secret Token-Secret aus Zugriffstoken
    MAX_RECORDS 10000 Maximale Anzahl von Datensätzen pro Ausführung
    PAGE_SIZE 1000 Einträge pro SuiteQL-Seite
    LOOKBACK_HOURS 24 Anfänglicher Rückschauzeitraum
  10. Scrollen Sie im Bereich Variablen und Secrets nach unten zu Anfragen:

    • Zeitlimit für die Anfrage: Geben Sie 600 Sekunden (10 Minuten) ein.
  11. Gehen Sie zum Tab Einstellungen:

    • Im Abschnitt Ressourcen:
      • Arbeitsspeicher: Wählen Sie 512 MiB oder höher aus.
      • CPU: Wählen Sie 1 aus.
  12. Im Abschnitt Versionsskalierung:

    • Mindestanzahl von Instanzen: Geben Sie 0 ein.
    • Maximale Anzahl von Instanzen: Geben Sie 100 ein.
  13. Klicken Sie auf Erstellen.

  14. Warten Sie, bis der Dienst erstellt wurde (1–2 Minuten).

  15. Nachdem der Dienst erstellt wurde, wird automatisch der Inline-Code-Editor geöffnet.

Funktionscode hinzufügen

  1. Geben Sie main in das Feld Einstiegspunkt ein.
  2. 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.0
    
  3. Klicken Sie auf Bereitstellen, um die Funktion zu speichern und bereitzustellen.

  4. 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.

  1. Rufen Sie in der GCP Console Cloud Scheduler auf.
  2. Klicken Sie auf Job erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Name netsuite-audit-collector-hourly
    Region 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-trigger auswählen
    Inhalt der Nachricht {} (leeres JSON-Objekt)
  4. 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

  1. Suchen Sie in der Cloud Scheduler-Konsole nach Ihrem Job (netsuite-audit-collector-hourly).
  2. Klicken Sie auf Ausführung erzwingen, um den Job manuell auszulösen.
  3. Warten Sie einige Sekunden.
  4. Rufen Sie Cloud Run > Dienste auf.
  5. Klicken Sie auf netsuite-audit-collector.
  6. Klicken Sie auf den Tab Logs.
  7. 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 records
    
  8. Rufen Sie Cloud Storage > Buckets auf.

  9. Klicken Sie auf netsuite-audit-logs.

  10. Rufen Sie den Ordner netsuite-audit/ auf.

  11. 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 Umgebungsvariablen NS_ACCOUNT_ID korrekt 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

  1. Rufen Sie die SIEM-Einstellungen > Feeds auf.
  2. Klicken Sie auf Neuen Feed hinzufügen.
  3. Klicken Sie auf Einzelnen Feed konfigurieren.
  4. Geben Sie im Feld Feedname einen Namen für den Feed ein, z. B. NetSuite Audit Logs.
  5. Wählen Sie Google Cloud Storage V2 als Quelltyp aus.
  6. Wählen Sie Oracle NetSuite – NetSuite Applications Suite als Log type (Protokolltyp) aus.
  7. Klicken Sie auf Dienstkonto abrufen. Eine eindeutige E‑Mail-Adresse für das Dienstkonto wird angezeigt. Beispiel:

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  8. Kopieren Sie diese E-Mail-Adresse, um sie im nächsten Schritt zu verwenden.

  9. Klicken Sie auf Weiter.

  10. 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.
  11. Klicken Sie auf Weiter.

  12. 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.

  1. Rufen Sie Cloud Storage > Buckets auf.
  2. Klicken Sie auf netsuite-audit-logs.
  3. Wechseln Sie zum Tab Berechtigungen.
  4. Klicken Sie auf Zugriff erlauben.
  5. 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.
  6. 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