Collecter les journaux Oracle NetSuite – NetSuite Applications Suite

Compatible avec :

Ce document explique comment ingérer les journaux Oracle NetSuite – NetSuite Applications Suite dans Google Security Operations à l'aide de Google Cloud Storage V2.

Oracle NetSuite est une plate-forme de planification des ressources d'entreprise (ERP) basée dans le cloud. Elle offre des fonctionnalités complètes de gestion d'entreprise, y compris la gestion financière, la gestion de la relation client (CRM), l'e-commerce et la gestion de l'inventaire. NetSuite génère des journaux d'audit complets, y compris les pistes de connexion des utilisateurs, les notes système qui suivent les modifications des enregistrements et les journaux d'accès à l'API, qui peuvent être récupérés via l'API REST SuiteQL à des fins de surveillance de la sécurité et de conformité.

Avant de commencer

Assurez-vous de remplir les conditions préalables suivantes :

  • Une instance Google SecOps
  • Un projet GCP avec les API Cloud Storage, Cloud Run, Pub/Sub et Cloud Scheduler activées
  • Autorisations permettant de créer et de gérer des buckets GCS
  • Autorisations permettant de gérer les stratégies IAM sur les buckets GCS
  • Autorisations permettant de créer des services Cloud Run, des sujets Pub/Sub et des jobs Cloud Scheduler
  • Un compte NetSuite avec accès administrateur
  • Services Web REST NetSuite activés
  • Autorisations permettant de créer des enregistrements d'intégration et des jetons d'accès dans NetSuite

Configurer l'accès à l'API NetSuite

Pour activer la collecte de journaux via l'API REST SuiteQL, vous devez activer les services Web REST, créer un enregistrement d'intégration et générer des identifiants d'authentification basée sur un jeton (TBA) dans NetSuite.

Activer les fonctionnalités NetSuite requises

  1. Connectez-vous à votre compte NetSuite en tant qu'administrateur.
  2. Accédez à Configuration> Société> Activer les fonctionnalités.
  3. Cliquez sur l'onglet SuiteCloud.
  4. Dans la section SuiteScript, cochez les cases suivantes :
    • Client SuiteScript
    • SuiteScript côté serveur
  5. Dans la section SuiteTalk (Web Services), cochez la case suivante :
    • Services Web REST
  6. Dans la section Gérer l'authentification, cochez la case Authentification basée sur un jeton.
  7. Cliquez sur J'accepte sur la page des conditions d'utilisation de SuiteCloud.
  8. Cliquez sur Enregistrer.

Créer un enregistrement d'intégration

  1. Dans NetSuite, accédez à Setup> Integration> Manage Integrations> New (Configuration > Intégration > Gérer les intégrations > Nouveau).
  2. Dans le champ Nom, saisissez Google SecOps Integration.
  3. Dans le champ Description, saisissez Integration for Google Security Operations log collection.
  4. Dans le champ État, laissez l'option par défaut Activé sélectionnée.
  5. Cliquez sur l'onglet Authentification.
  6. Dans la section Authentification basée sur un jeton, cochez la case Authentification basée sur un jeton.
  7. Ne cochez pas la case TBA: Authorization Flow (À déterminer : flux d'autorisation).
  8. Cochez la case TBA: ISSUETOKEN ENDPOINT (À DÉTERMINER : POINT DE TERMINAISON ISSUETOKEN).
  9. Cliquez sur Enregistrer.
  10. Sur la page de confirmation, copiez et enregistrez la clé client/ID client et le code secret du client.

Créer un rôle personnalisé pour l'accès à l'API

  1. Accédez à Configuration > Utilisateurs/Rôles > Gérer les rôles > Nouveau.
  2. Dans le champ Nom, saisissez Google SecOps API Role.
  3. Dans le champ ID, saisissez google_secops_api_role.
  4. Cliquez sur le sous-onglet Autorisations.
  5. Cliquez sur le sous-onglet Configuration.
  6. Ajoutez les autorisations suivantes avec le niveau d'accès Complet :
    • Se connecter à l'aide de jetons d'accès
    • Jetons d'accès utilisateur
    • Services Web REST
  7. Cliquez sur le sous-onglet Rapports.
  8. Ajoutez les autorisations suivantes avec le niveau d'accès Afficher :
    • SuiteAnalytics Workbook
  9. Cliquez sur le sous-onglet Listes.
  10. Ajoutez les autorisations suivantes avec le niveau d'accès Afficher :
    • Piste d'audit des connexions
  11. Cliquez sur Enregistrer.

Attribuer le rôle à un utilisateur

  1. Accédez à Listes > Employés > Employés.
  2. Sélectionnez le compte utilisateur qui sera utilisé pour l'intégration (ou créez un compte utilisateur dédié à l'intégration).
  3. Cliquez sur Modifier.
  4. Cliquez sur l'onglet Accès.
  5. Dans la section Rôles, cliquez sur Ajouter.
  6. Sélectionnez Rôle d'API Google SecOps dans la liste déroulante.
  7. Cliquez sur Enregistrer.

Créer un jeton d'accès

  1. Connectez-vous à NetSuite en tant qu'utilisateur auquel le rôle d'API Google SecOps est attribué.
  2. Accédez à Configuration > Utilisateurs/Rôles > Jetons d'accès > Nouveau.
  3. Sur la page Jeton d'accès :
    • Dans la liste déroulante Nom de l'application, sélectionnez Intégration Google SecOps.
    • Dans la liste déroulante Utilisateur, sélectionnez le compte utilisateur auquel le rôle d'API Google SecOps est attribué.
    • Dans la liste déroulante Rôle, sélectionnez Rôle de l'API Google SecOps.
    • Le champ Nom du jeton est renseigné automatiquement. Vous pouvez le modifier si vous le souhaitez.
  4. Cliquez sur Enregistrer.
  5. Sur la page de confirmation, copiez et enregistrez l'ID de jeton et le code secret du jeton.

Trouver votre ID de compte NetSuite

  1. Dans NetSuite, accédez à Setup> Company > Company Information (Configuration > Entreprise > Informations sur l'entreprise).
  2. Recherchez le champ ID de compte (affiché sous le paramètre Fuseau horaire).
  3. Copiez et enregistrez votre ID de compte (par exemple, 1234567 ou 1234567_SB1 pour les comptes sandbox).

Vérifier les autorisations

Pour vérifier que le compte dispose des autorisations requises :

  1. Connectez-vous à NetSuite avec l'utilisateur auquel le rôle d'API Google SecOps a été attribué.
  2. Accédez à Configuration > Utilisateurs/Rôles > Afficher la piste d'audit des connexions.
  3. Si vous pouvez accéder à la page "Piste d'audit des connexions", cela signifie que le compte dispose des autorisations requises.
  4. Si vous ne voyez pas cette option, contactez votre administrateur NetSuite pour qu'il vous accorde l'autorisation Login Audit Trail (Piste d'audit des connexions) dans le sous-onglet Lists (Listes).

Tester l'accès à l'API

  • Testez vos identifiants avant de procéder à l'intégration :

    # 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"}
    

Une réponse réussie renvoie un objet JSON contenant des enregistrements LoginAudit.

Créer un bucket Google Cloud Storage

  1. Accédez à la console Google Cloud.
  2. Sélectionnez votre projet ou créez-en un.
  3. Dans le menu de navigation, accédez à Cloud Storage > Buckets.
  4. Cliquez sur Créer un bucket.
  5. Fournissez les informations de configuration suivantes :

    Paramètre Valeur
    Attribuez un nom au bucket Saisissez un nom unique (par exemple, netsuite-audit-logs).
    Type d'emplacement Choisissez en fonction de vos besoins (région, birégion, multirégion).
    Emplacement Sélectionnez l'emplacement (par exemple, us-central1).
    Classe de stockage Standard (recommandé pour les journaux auxquels vous accédez fréquemment)
    Access control (Contrôle des accès) Uniforme (recommandé)
    Outils de protection Facultatif : Activez la gestion des versions des objets ou la règle de conservation.
  6. Cliquez sur Créer.

Créer un compte de service pour la fonction Cloud Run

La fonction Cloud Run a besoin d'un compte de service disposant des autorisations nécessaires pour écrire dans le bucket GCS et être appelée par Pub/Sub.

Créer un compte de service

  1. Dans la console GCP, accédez à IAM et administration > Comptes de service.
  2. Cliquez sur Créer un compte de service.
  3. Fournissez les informations de configuration suivantes :
    • Nom du compte de service : saisissez netsuite-audit-collector-sa.
    • Description du compte de service : saisissez Service account for Cloud Run function to collect NetSuite audit logs.
  4. Cliquez sur Créer et continuer.
  5. Dans la section Autoriser ce compte de service à accéder au projet, ajoutez les rôles suivants :
    1. Cliquez sur Sélectionner un rôle.
    2. Recherchez et sélectionnez Administrateur des objets Storage.
    3. Cliquez sur + Ajouter un autre rôle.
    4. Recherchez et sélectionnez Demandeur Cloud Run.
    5. Cliquez sur + Ajouter un autre rôle.
    6. Recherchez et sélectionnez Demandeur Cloud Functions.
  6. Cliquez sur Continuer.
  7. Cliquez sur OK.

Ces rôles sont requis pour :

  • Administrateur des objets Storage : écrire des journaux dans un bucket GCS et gérer les fichiers d'état
  • Demandeur Cloud Run : autorise Pub/Sub à appeler la fonction
  • Demandeur Cloud Functions : autorise l'appel de fonctions

Accorder des autorisations IAM sur le bucket GCS

  1. Accédez à Cloud Storage > Buckets.
  2. Cliquez sur le nom de votre bucket (netsuite-audit-logs).
  3. Accédez à l'onglet Autorisations.
  4. Cliquez sur Accorder l'accès.
  5. Fournissez les informations de configuration suivantes :
    • Ajouter des comptes principaux : saisissez l'adresse e-mail du compte de service (netsuite-audit-collector-sa@PROJECT_ID.iam.gserviceaccount.com).
    • Attribuer des rôles : sélectionnez Administrateur des objets Storage.
  6. Cliquez sur Enregistrer.

Créer un sujet Pub/Sub

  1. Dans la console GCP, accédez à Pub/Sub > Sujets.
  2. Cliquez sur Créer un sujet.
  3. Fournissez les informations de configuration suivantes :
    • ID du sujet : saisissez netsuite-audit-trigger.
    • Conservez les valeurs par défaut des autres paramètres.
  4. Cliquez sur Créer.

Créer une fonction Cloud Run pour collecter les journaux

La fonction Cloud Run sera déclenchée par les messages Pub/Sub de Cloud Scheduler pour extraire les journaux de l'API REST SuiteQL de NetSuite et les écrire dans GCS.

  1. Dans la console GCP, accédez à Cloud Run.
  2. Cliquez sur Créer un service.
  3. Sélectionnez Fonction (utilisez un éditeur de modification directe pour créer une fonction).
  4. Dans la section Configurer, fournissez les informations de configuration suivantes :

    Paramètre Valeur
    Nom du service netsuite-audit-collector
    Région Sélectionnez la région correspondant à votre bucket GCS (par exemple, us-central1).
    Durée d'exécution Sélectionnez Python 3.12 ou version ultérieure.
  5. Dans la section Déclencheur (facultatif) :

    1. Cliquez sur + Ajouter un déclencheur.
    2. Sélectionnez Cloud Pub/Sub.
    3. Dans Sélectionner un sujet Cloud Pub/Sub, choisissez netsuite-audit-trigger.
    4. Cliquez sur Enregistrer.
  6. Dans la section Authentification :

    1. Sélectionnez Exiger l'authentification.
    2. Consultez Identity and Access Management (IAM).
  7. Faites défiler la page vers le bas, puis développez Conteneurs, mise en réseau, sécurité.

  8. Accédez à l'onglet Sécurité :

    • Compte de service : sélectionnez netsuite-audit-collector-sa.
  9. Accédez à l'onglet Conteneurs :

    1. Cliquez sur Variables et secrets.
    2. Cliquez sur + Ajouter une variable pour chaque variable d'environnement :
    Nom de la variable Exemple de valeur Description
    GCS_BUCKET netsuite-audit-logs Nom du bucket GCS
    GCS_PREFIX netsuite-audit Préfixe des fichiers journaux
    STATE_KEY netsuite-audit/state.json Chemin d'accès au fichier d'état
    NS_ACCOUNT_ID 1234567 ID de compte NetSuite
    NS_CONSUMER_KEY your-consumer-key Clé client issue de l'enregistrement de l'intégration
    NS_CONSUMER_SECRET your-consumer-secret Secret client de l'enregistrement d'intégration
    NS_TOKEN_ID your-token-id ID de jeton à partir du jeton d'accès
    NS_TOKEN_SECRET your-token-secret Secret du jeton d'accès
    MAX_RECORDS 10000 Nombre maximal d'enregistrements par exécution
    PAGE_SIZE 1000 Enregistrements par page SuiteQL
    LOOKBACK_HOURS 24 Période d'analyse initiale
  10. Dans la section Variables et secrets, faites défiler la page jusqu'à Requêtes :

    • Délai avant expiration de la requête : saisissez 600 secondes (10 minutes).
  11. Accédez à l'onglet Paramètres :

    • Dans la section Ressources :
      • Mémoire : sélectionnez 512 Mio ou plus.
      • CPU : sélectionnez 1.
  12. Dans la section Scaling de révision :

    • Nombre minimal d'instances : saisissez 0.
    • Nombre maximal d'instances : saisissez 100.
  13. Cliquez sur Créer.

  14. Attendez que le service soit créé (une à deux minutes).

  15. Une fois le service créé, l'éditeur de code intégré s'ouvre automatiquement.

Ajouter un code de fonction

  1. Saisissez main dans le champ Point d'entrée.
  2. Dans l'éditeur de code intégré, créez deux fichiers :

    • 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. Cliquez sur Déployer pour enregistrer et déployer la fonction.

  4. Attendez la fin du déploiement (deux à trois minutes).

Créer une tâche Cloud Scheduler

Cloud Scheduler publiera des messages dans le sujet Pub/Sub à intervalles réguliers, ce qui déclenchera la fonction Cloud Run.

  1. Dans la console GCP, accédez à Cloud Scheduler.
  2. Cliquez sur Créer une tâche.
  3. Fournissez les informations de configuration suivantes :

    Paramètre Valeur
    Nom netsuite-audit-collector-hourly
    Région Sélectionnez la même région que la fonction Cloud Run.
    Fréquence 0 * * * * (toutes les heures, à l'heure pile)
    Fuseau horaire Sélectionnez un fuseau horaire (UTC recommandé)
    Type de cible Pub/Sub
    Sujet Sélectionner netsuite-audit-trigger
    Corps du message {} (objet JSON vide)
  4. Cliquez sur Créer.

Options de fréquence de programmation

Choisissez la fréquence en fonction du volume de journaux et des exigences de latence :

Fréquence Expression Cron Cas d'utilisation
Toutes les 5 minutes */5 * * * * Volume élevé, faible latence
Toutes les 15 minutes */15 * * * * Volume moyen
Toutes les heures 0 * * * * Standard (recommandé)
Toutes les 6 heures 0 */6 * * * Traitement par lot à faible volume
Tous les jours 0 0 * * * Collecte de données historiques

Tester l'intégration

  1. Dans la console Cloud Scheduler, recherchez votre job (netsuite-audit-collector-hourly).
  2. Cliquez sur Exécuter de force pour déclencher le job manuellement.
  3. Patientez quelques secondes.
  4. Accédez à Cloud Run > Services.
  5. Cliquez sur netsuite-audit-collector.
  6. Cliquez sur l'onglet Journaux.
  7. Vérifiez que la fonction s'est exécutée correctement. Par exemple :

    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. Accédez à Cloud Storage > Buckets.

  9. Cliquez sur netsuite-audit-logs.

  10. Accédez au dossier netsuite-audit/.

  11. Vérifiez qu'un nouveau fichier .ndjson a été créé avec le code temporel actuel.

Si des erreurs s'affichent dans les journaux :

  • HTTP 401 : vérifiez que les identifiants OAuth (NS_CONSUMER_KEY, NS_CONSUMER_SECRET, NS_TOKEN_ID, NS_TOKEN_SECRET) et les variables d'environnement NS_ACCOUNT_ID sont corrects.
  • HTTP 403 : vérifiez que le rôle NetSuite dispose des autorisations requises (services Web REST, piste d'audit de connexion, classeur SuiteAnalytics).
  • HTTP 429 : limitation du débit. La fonction effectuera automatiquement une nouvelle tentative avec un intervalle exponentiel entre les tentatives.
  • Variables d'environnement manquantes : vérifiez que toutes les variables requises sont définies dans la configuration de la fonction Cloud Run.

Récupérer le compte de service Google SecOps

  1. Accédez à Paramètres SIEM> Flux.
  2. Cliquez sur Ajouter un flux.
  3. Cliquez sur Configurer un flux unique.
  4. Dans le champ Nom du flux, saisissez un nom pour le flux (par exemple, NetSuite Audit Logs).
  5. Sélectionnez Google Cloud Storage V2 comme Type de source.
  6. Sélectionnez Oracle NetSuite – Suite d'applications NetSuite comme Type de journal.
  7. Cliquez sur Obtenir un compte de service. Une adresse e-mail de compte de service unique s'affiche. Exemple :

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  8. Copiez cette adresse e-mail pour l'utiliser à l'étape suivante.

  9. Cliquez sur Suivant.

  10. Spécifiez les valeurs des paramètres d'entrée suivants :

    • URL du bucket de stockage : saisissez l'URI du bucket GCS avec le chemin d'accès du préfixe :

      gs://netsuite-audit-logs/netsuite-audit/
      
    • Option de suppression de la source : sélectionnez l'option de suppression de votre choix :
      • Jamais : ne supprime jamais aucun fichier après les transferts (recommandé pour les tests).
      • Supprimer les fichiers transférés : supprime les fichiers une fois le transfert réussi.
      • Supprimer les fichiers transférés et les répertoires vides : supprime les fichiers et les répertoires vides une fois le transfert réussi.
    • Âge maximal du fichier : incluez les fichiers modifiés au cours d'un certain nombre de jours (180 jours par défaut).
    • Espace de noms de l'élément : espace de noms de l'élément
    • Étiquettes d'ingestion : étiquette à appliquer aux événements de ce flux
  11. Cliquez sur Suivant.

  12. Vérifiez la configuration de votre nouveau flux sur l'écran Finaliser, puis cliquez sur Envoyer.

Accorder des autorisations IAM au compte de service Google SecOps

Le compte de service Google SecOps a besoin du rôle Lecteur des objets Storage sur votre bucket GCS.

  1. Accédez à Cloud Storage > Buckets.
  2. Cliquez sur netsuite-audit-logs.
  3. Accédez à l'onglet Autorisations.
  4. Cliquez sur Accorder l'accès.
  5. Fournissez les informations de configuration suivantes :
    • Ajouter des comptes principaux : collez l'adresse e-mail du compte de service Google SecOps.
    • Attribuer des rôles : sélectionnez Lecteur d'objets Storage.
  6. Cliquez sur Enregistrer.

Limites de débit de l'API NetSuite

L'API NetSuite présente les limites de simultanéité et de limitation du débit suivantes :

Type de limite Valeur par défaut Remarques
Requêtes simultanées 15 requêtes S'applique aux requêtes API SOAP et REST combinées par compte
Résultats de requête SuiteQL 100 000 lignes maximum Nombre maximal de lignes renvoyées par requête SuiteQL
Objets par requête 1 000 objets maximum Nombre maximal d'objets renvoyés par requête API

La fonction Cloud Run gère automatiquement la limitation du débit avec un intervalle exponentiel entre les tentatives. Si vous rencontrez des erreurs de limite de fréquence persistantes (HTTP 429), contactez l'assistance NetSuite pour augmenter vos limites d'API ou ajuster la fréquence de Cloud Scheduler.

Table de mappage UDM

Champ de journal Mappage UDM Logique
utilisateur extensions.auth.type Type de mécanisme d'authentification utilisé
date metadata.event_timestamp Code temporel de l'événement
event_type metadata.event_type Type d'événement (par exemple, USER_LOGIN, NETWORK_CONNECTION)
security_result security_result Détails sur le résultat de sécurité de l'événement
oauthaccesstokenname security_result.about.resource.attribute.labels Étiquettes clé-valeur décrivant les attributs de la ressource concernée par le résultat de sécurité
secchallenge security_result.about.resource.attribute.labels
état security_result.summary Description récapitulative du résultat de sécurité
oauthappname target.application Nom de l'application cible
User-agent target.application
ipaddress target.ip Adresse IP de la cible
requesturi target.url URL de la ressource cible
emailaddress target.user.email_addresses Adresses e-mail associées à l'utilisateur cible
rôle target.user.role_name Nom du rôle attribué à l'utilisateur cible
utilisateur target.user.userid Identifiant unique de l'utilisateur cible

Journal des modifications

Afficher le journal des modifications de ce parseur

Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.