Recopila registros de FortiCNAPP (anteriormente Lacework)

Se admite en los siguientes sistemas operativos:

En este documento, se explica cómo transferir registros de FortiCNAPP (anteriormente conocido como Lacework) a Google Security Operations con Google Cloud Storage V2.

FortiCNAPP es una plataforma de protección de aplicaciones nativas de la nube (CNAPP) que proporciona administración de la postura de seguridad en la nube, protección de cargas de trabajo y detección de amenazas en entornos de múltiples nubes. Genera alertas, hallazgos de cumplimiento y registros de auditoría que se pueden recopilar a través de la API de REST de Lacework.

Antes de comenzar

Asegúrate de cumplir con los siguientes requisitos previos:

  • Una instancia de Google SecOps
  • Un proyecto de GCP con la API de Cloud Storage habilitada
  • Permisos para crear y administrar buckets de GCS
  • Permisos para administrar políticas de IAM en buckets de GCS
  • Permisos para crear servicios de Cloud Run, temas de Pub/Sub y trabajos de Cloud Scheduler
  • Acceso con privilegios a la consola de FortiCNAPP (anteriormente Lacework) con permisos de administrador
  • Una cuenta de Lacework con acceso a la clave de API habilitado

Crea un bucket de Google Cloud Storage

  1. Ve a la consola de Google Cloud.
  2. Selecciona tu proyecto o crea uno nuevo.
  3. En el menú de navegación, ve a Cloud Storage > Buckets.
  4. Haz clic en Crear bucket.

  5. Proporciona los siguientes detalles de configuración:

    Parámetro de configuración Valor
    Asigna un nombre a tu bucket Ingresa un nombre global único (por ejemplo, lacework-logs).
    Tipo de ubicación Elige según tus necesidades (regional, birregional o multirregional)
    Ubicación Selecciona la ubicación (por ejemplo, us-central1).
    Clase de almacenamiento Estándar (recomendado para los registros a los que se accede con frecuencia)
    Control de acceso Uniforme (recomendado)
    Herramientas de protección Opcional: Habilita el control de versiones de objetos o la política de retención
  6. Haz clic en Crear.

Recopila credenciales de la API de FortiCNAPP (anteriormente Lacework)

Generar clave de API

  1. Accede a tu consola de Lacework.
  2. Ve a Configuración > Configuración > Claves de API.
  3. Haz clic en + Agregar nuevo.
  4. Ingresa un nombre para la clave de API (por ejemplo, Google SecOps Integration).
  5. De manera opcional, ingresa una Descripción.
  6. Haz clic en Guardar.

  7. Copia y guarda los siguientes detalles en una ubicación segura:

    • ID de clave: Es el ID de la clave de API generada.
    • Secreto: Es el secreto de API generado (se muestra solo una vez).
  8. Toma nota de la URL de tu cuenta de Lacework en la barra de direcciones del navegador.

    • Formato: https://<ACCOUNT>.lacework.net
    • Ejemplo: Si la URL de tu consola de Lacework es https://acme.lacework.net, el nombre de tu cuenta es acme.

Verifica los permisos

Para verificar que la cuenta tenga los permisos necesarios, haz lo siguiente:

  1. Accede a la consola de Lacework.
  2. Ve a Configuración > Configuración > Claves de API.
  3. Si puedes ver la página Claves de API y crear claves, tienes los permisos necesarios.
  4. Si no ves esta opción, comunícate con tu administrador para que te otorgue acceso a nivel de administrador.

Prueba el acceso a la API

  • Prueba tus credenciales antes de continuar con la integración:

    # Replace with your actual credentials
    LW_ACCOUNT="your-account-name"
    LW_KEY_ID="your-api-key-id"
    LW_SECRET="your-api-secret"
    
    # Get a temporary access token
    TOKEN=$(curl -s -X POST "https://${LW_ACCOUNT}.lacework.net/api/v2/access/tokens" \
        -H "X-LW-UAKS: ${LW_SECRET}" \
        -H "Content-Type: application/json" \
        -d "{\"keyId\": \"${LW_KEY_ID}\", \"expiryTime\": 3600}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))")
    
    # Test API access - list alerts
    curl -v -H "Authorization: Bearer ${TOKEN}" \
        "https://${LW_ACCOUNT}.lacework.net/api/v2/Alerts?startTime=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ)&endTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
    

Crea una cuenta de servicio para la función de Cloud Run

La función de Cloud Run necesita una cuenta de servicio con permisos para escribir en el bucket de GCS y para que Pub/Sub la invoque.

Crear cuenta de servicio

  1. En Google Cloud Console, ve a IAM y administración > Cuentas de servicio.
  2. Haz clic en Crear cuenta de servicio.

  3. Proporciona los siguientes detalles de configuración:

    • Nombre de la cuenta de servicio: Ingresa lacework-logs-collector-sa.
    • Descripción de la cuenta de servicio: Ingresa Service account for Cloud Run function to collect FortiCNAPP (formerly Lacework) logs.
  4. Haz clic en Crear y continuar.

  5. En la sección Otorga a esta cuenta de servicio acceso al proyecto, agrega los siguientes roles:

    1. Haz clic en Selecciona un rol.
    2. Busca y selecciona Administrador de objetos de Storage.
    3. Haz clic en + Agregar otro rol.
    4. Busca y selecciona Invocador de Cloud Run.
    5. Haz clic en + Agregar otro rol.
    6. Busca y selecciona Invocador de Cloud Functions.
  6. Haz clic en Continuar.

  7. Haz clic en Listo.

Estos roles son necesarios para lo siguiente:

  • Administrador de objetos de Storage: Escribe registros en el bucket de GCS y administra archivos de estado
  • Invocador de Cloud Run: Permite que Pub/Sub invoque la función
  • Invocador de Cloud Functions: Permite la invocación de funciones

Otorga permisos de IAM en el bucket de GCS

Otorga permisos de escritura a la cuenta de servicio en el bucket de GCS:

  1. Ve a Cloud Storage > Buckets.
  2. Haz clic en el nombre de tu bucket (por ejemplo, lacework-logs).
  3. Ve a la pestaña Permisos.
  4. Haz clic en Otorgar acceso.

  5. Proporciona los siguientes detalles de configuración:

    • Agregar entidades: Ingresa el correo electrónico de la cuenta de servicio (por ejemplo, lacework-logs-collector-sa@PROJECT_ID.iam.gserviceaccount.com).
    • Asignar roles: Selecciona Administrador de objetos de Storage.
  6. Haz clic en Guardar.

Crea un tema de Pub/Sub

Crea un tema de Pub/Sub en el que Cloud Scheduler publicará y al que se suscribirá la función de Cloud Run.

  1. En Google Cloud Console, ve a Pub/Sub > Temas.
  2. Haz clic en Crear un tema.

  3. Proporciona los siguientes detalles de configuración:

    • ID del tema: Ingresa lacework-logs-trigger.
    • Deja el resto de la configuración con sus valores predeterminados.
  4. Haz clic en Crear.

Crea una función de Cloud Run para recopilar registros

La función de Cloud Run se activará con mensajes de Pub/Sub de Cloud Scheduler para recuperar registros de la API de FortiCNAPP (anteriormente Lacework) y escribirlos en GCS.

  1. En Google Cloud Console, ve a Cloud Run.
  2. Haz clic en Crear servicio.
  3. Selecciona Función (usa un editor intercalado para crear una función).

  4. En la sección Configurar, proporciona los siguientes detalles de configuración:

    Parámetro de configuración Valor
    Nombre del servicio lacework-logs-collector
    Región Selecciona la región que coincida con tu bucket de GCS (por ejemplo, us-central1).
    Tiempo de ejecución Selecciona Python 3.12 o una versión posterior.
  5. En la sección Activador (opcional), haz lo siguiente:

    1. Haz clic en + Agregar activador.
    2. Selecciona Cloud Pub/Sub.
    3. En Selecciona un tema de Cloud Pub/Sub, elige el tema lacework-logs-trigger.
    4. Haz clic en Guardar.
  6. En la sección Autenticación, haz lo siguiente:

    1. Selecciona Necesita autenticación.
    2. Verifica Identity and Access Management (IAM).
  7. Desplázate hacia abajo y expande Contenedores, redes y seguridad.

  8. Ve a la pestaña Seguridad:

    • Cuenta de servicio: Selecciona la cuenta de servicio lacework-logs-collector-sa.
  9. Ve a la pestaña Contenedores:

    1. Haz clic en Variables y secretos.
    2. Haz clic en + Agregar variable para cada variable de entorno:
    Nombre de la variable Valor de ejemplo Descripción
    GCS_BUCKET lacework-logs Nombre del bucket de GCS
    GCS_PREFIX lacework Prefijo para los archivos de registro
    STATE_KEY lacework/state.json Ruta de acceso al archivo de estado
    LW_ACCOUNT acme Nombre de la cuenta de Lacework
    LW_KEY_ID your-api-key-id ID de la clave de API de Lacework
    LW_SECRET your-api-secret Secreto de la API de Lacework
    MAX_RECORDS 5000 Cantidad máxima de registros por ejecución
    PAGE_SIZE 500 Registros por página
    LOOKBACK_HOURS 24 Período de visualización inicial
  10. En la sección Variables y Secrets, desplázate hacia abajo hasta Solicitudes:

    • Tiempo de espera de la solicitud: Ingresa 600 segundos (10 minutos).
  11. Ve a la pestaña Configuración:

    • En la sección Recursos, haz lo siguiente:
      • Memoria: Selecciona 512 MiB o más.
      • CPU: Selecciona 1.
  12. En la sección Escalamiento de revisión, haz lo siguiente:

    • Cantidad mínima de instancias: Ingresa 0.
    • Cantidad máxima de instancias: Ingresa 100 (o ajusta según la carga esperada).
  13. Haz clic en Crear.

  14. Espera a que se cree el servicio (de 1 a 2 minutos).

  15. Después de crear el servicio, se abrirá automáticamente el editor de código intercalado.

Agregar el código de función

  1. Ingresa main en el campo Punto de entrada.
  2. En el editor de código intercalado, crea dos archivos:

    • main.py:

      import functions_framework
      from google.cloud import storage
      import json
      import os
      import urllib3
      from datetime import datetime, timezone, timedelta
      import time
      
      # Initialize HTTP client with timeouts
      http = urllib3.PoolManager(
          timeout=urllib3.Timeout(connect=5.0, read=30.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', 'lacework')
      STATE_KEY = os.environ.get('STATE_KEY', 'lacework/state.json')
      LW_ACCOUNT = os.environ.get('LW_ACCOUNT')
      LW_KEY_ID = os.environ.get('LW_KEY_ID')
      LW_SECRET = os.environ.get('LW_SECRET')
      MAX_RECORDS = int(os.environ.get('MAX_RECORDS', '5000'))
      PAGE_SIZE = int(os.environ.get('PAGE_SIZE', '500'))
      LOOKBACK_HOURS = int(os.environ.get('LOOKBACK_HOURS', '24'))
      
      # Lacework API base URL
      API_BASE_TEMPLATE = 'https://{account}.lacework.net/api/v2'
      
      # Log endpoints to fetch
      ENDPOINTS = [
          {'name': 'alerts', 'path': '/Alerts', 'time_field': 'startTime', 'results_key': 'data'},
          {'name': 'audit_logs', 'path': '/AuditLogs', 'time_field': 'createdTime', 'results_key': 'data'},
      ]
      
      def get_access_token(api_base: str, key_id: str, secret: str) -> str:
          """Get a temporary access token from Lacework API."""
          token_url = f"{api_base}/access/tokens"
          body = json.dumps({
              'keyId': key_id,
              'expiryTime': 3600
          }).encode('utf-8')
          headers = {
              'X-LW-UAKS': secret,
              'Content-Type': 'application/json',
          }
          response = http.request('POST', token_url, body=body, headers=headers)
          if response.status != 201:
              raise Exception(f"Failed to get access token: HTTP {response.status} - {response.data.decode('utf-8')}")
          token_data = json.loads(response.data.decode('utf-8'))
          return token_data['token']
      
      @functions_framework.cloud_event
      def main(cloud_event):
          """
          Cloud Run function triggered by Pub/Sub to fetch FortiCNAPP
          (formerly Lacework) logs and write to GCS.
      
          Args:
              cloud_event: CloudEvent object containing Pub/Sub message
          """
      
          if not all([GCS_BUCKET, LW_ACCOUNT, LW_KEY_ID, LW_SECRET]):
              print('Error: Missing required environment variables')
              return
      
          try:
              bucket = storage_client.bucket(GCS_BUCKET)
              api_base = API_BASE_TEMPLATE.format(account=LW_ACCOUNT)
      
              # Get access token
              token = get_access_token(api_base, LW_KEY_ID, LW_SECRET)
              print("Successfully obtained access token")
      
              # Load state
              state = load_state(bucket, STATE_KEY)
      
              # Determine time window
              now = datetime.now(timezone.utc)
              all_records = []
      
              for endpoint in ENDPOINTS:
                  ep_name = endpoint['name']
                  last_time_str = None
      
                  if isinstance(state, dict) and state.get(f"last_{ep_name}_time"):
                      try:
                          last_time = parse_datetime(state[f"last_{ep_name}_time"])
                          # Overlap by 2 minutes to catch any delayed events
                          last_time = last_time - timedelta(minutes=2)
                          last_time_str = last_time.strftime('%Y-%m-%dT%H:%M:%SZ')
                      except Exception as e:
                          print(f"Warning: Could not parse last_{ep_name}_time: {e}")
      
                  if last_time_str is None:
                      last_time = now - timedelta(hours=LOOKBACK_HOURS)
                      last_time_str = last_time.strftime('%Y-%m-%dT%H:%M:%SZ')
      
                  end_time_str = now.strftime('%Y-%m-%dT%H:%M:%SZ')
      
                  print(f"Fetching {ep_name} from {last_time_str} to {end_time_str}")
      
                  records, newest_event_time = fetch_logs(
                      api_base=api_base,
                      token=token,
                      endpoint=endpoint,
                      start_time=last_time_str,
                      end_time=end_time_str,
                      page_size=PAGE_SIZE,
                      max_records=MAX_RECORDS,
                  )
      
                  # Tag records with endpoint type
                  for record in records:
                      record['_lw_log_type'] = ep_name
      
                  all_records.extend(records)
      
                  # Update state for this endpoint
                  if newest_event_time:
                      state[f"last_{ep_name}_time"] = newest_event_time
                  else:
                      state[f"last_{ep_name}_time"] = end_time_str
      
                  print(f"Fetched {len(records)} {ep_name} records")
      
              if not all_records:
                  print("No new log records found.")
                  save_state(bucket, STATE_KEY, state)
                  return
      
              # Write to GCS as NDJSON
              timestamp = now.strftime('%Y%m%d_%H%M%S')
              object_key = f"{GCS_PREFIX}/logs_{timestamp}.ndjson"
              blob = bucket.blob(object_key)
      
              ndjson = '\n'.join([json.dumps(record, ensure_ascii=False) for record in all_records]) + '\n'
              blob.upload_from_string(ndjson, content_type='application/x-ndjson')
      
              print(f"Wrote {len(all_records)} records to gs://{GCS_BUCKET}/{object_key}")
      
              # Save state
              save_state(bucket, STATE_KEY, state)
      
              print(f"Successfully processed {len(all_records)} records")
      
          except Exception as e:
              print(f'Error processing logs: {str(e)}')
              raise
      
      def parse_datetime(value: str) -> datetime:
          """Parse ISO datetime string to datetime object."""
          if value.endswith("Z"):
              value = value[:-1] + "+00:00"
          return datetime.fromisoformat(value)
      
      def load_state(bucket, key):
          """Load state from GCS."""
          try:
              blob = bucket.blob(key)
              if blob.exists():
                  state_data = blob.download_as_text()
                  return json.loads(state_data)
          except Exception as e:
              print(f"Warning: Could not load state: {e}")
      
          return {}
      
      def save_state(bucket, key, state: dict):
          """Save the state to GCS state file."""
          try:
              blob = bucket.blob(key)
              blob.upload_from_string(
                  json.dumps(state, indent=2),
                  content_type='application/json'
              )
              print(f"Saved state: {json.dumps(state)}")
          except Exception as e:
              print(f"Warning: Could not save state: {e}")
      
      def fetch_logs(api_base: str, token: str, endpoint: dict, start_time: str, end_time: str, page_size: int, max_records: int):
          """
          Fetch logs from Lacework API with pagination and rate limiting.
      
          Args:
              api_base: API base URL
              token: Bearer access token
              endpoint: Endpoint configuration dict
              start_time: Start time in ISO format
              end_time: End time in ISO format
              page_size: Number of records per page
              max_records: Maximum total records to fetch
      
          Returns:
              Tuple of (records list, newest_event_time ISO string)
          """
          headers = {
              'Authorization': f'Bearer {token}',
              'Accept': 'application/json',
              'Content-Type': 'application/json',
              'User-Agent': 'GoogleSecOps-LaceworkCollector/1.0'
          }
      
          ep_path = endpoint['path']
          time_field = endpoint['time_field']
          results_key = endpoint['results_key']
      
          records = []
          newest_time = None
          page_num = 0
          backoff = 1.0
          next_page = None
      
          while True:
              page_num += 1
      
              if len(records) >= max_records:
                  print(f"Reached max_records limit ({max_records}) for {endpoint['name']}")
                  break
      
              # Build request URL
              if next_page:
                  url = next_page
              else:
                  url = f"{api_base}{ep_path}?startTime={start_time}&endTime={end_time}"
      
              try:
                  response = http.request('GET', url, headers=headers)
      
                  # Handle rate limiting with exponential backoff
                  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, 30.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 [], None
      
                  data = json.loads(response.data.decode('utf-8'))
      
                  page_results = data.get(results_key, [])
      
                  if not page_results:
                      print(f"No more results (empty page) for {endpoint['name']}")
                      break
      
                  print(f"Page {page_num}: Retrieved {len(page_results)} {endpoint['name']} events")
                  records.extend(page_results)
      
                  # Track newest event time
                  for event in page_results:
                      try:
                          event_time = event.get(time_field)
                          if event_time:
                              if newest_time is None or parse_datetime(event_time) > parse_datetime(newest_time):
                                  newest_time = event_time
                      except Exception as e:
                          print(f"Warning: Could not parse event time: {e}")
      
                  # Check for next page via paging object
                  paging = data.get('paging', {})
                  next_page_url = paging.get('urls', {}).get('nextPage')
                  if not next_page_url:
                      print(f"No more pages for {endpoint['name']}")
                      break
                  next_page = next_page_url
      
              except Exception as e:
                  print(f"Error fetching {endpoint['name']} logs: {e}")
                  return [], None
      
          print(f"Retrieved {len(records)} total {endpoint['name']} records from {page_num} pages")
          return records, newest_time
      
    • requirements.txt:

      functions-framework==3.*
      google-cloud-storage==2.*
      urllib3>=2.0.0
      
  3. Haz clic en Implementar para guardar y, luego, implementar la función.

  4. Espera a que se complete la implementación (de 2 a 3 minutos).

Crea un trabajo de Cloud Scheduler

Cloud Scheduler publicará mensajes en el tema de Pub/Sub a intervalos regulares, lo que activará la función de Cloud Run.

  1. En Google Cloud Console, ve a Cloud Scheduler.
  2. Haz clic en Crear trabajo.

  3. Proporciona los siguientes detalles de configuración:

    Parámetro de configuración Valor
    Nombre lacework-logs-collector-hourly
    Región Selecciona la misma región que la función de Cloud Run
    Frecuencia 0 * * * * (cada hora en punto)
    Zona horaria Selecciona la zona horaria (se recomienda UTC)
    Tipo de objetivo Pub/Sub
    Tema Selecciona el tema lacework-logs-trigger.
    Cuerpo del mensaje {} (objeto JSON vacío)
  4. Haz clic en Crear.

Opciones de frecuencia de programación

Elige la frecuencia según los requisitos de volumen y latencia de los registros:

Frecuencia Expresión cron Caso de uso
Cada 5 minutos */5 * * * * Alto volumen y baja latencia
Cada 15 minutos */15 * * * * Volumen medio
Cada 1 hora 0 * * * * Estándar (recomendado)
Cada 6 horas 0 */6 * * * Procesamiento por lotes de bajo volumen
Diario 0 0 * * * Recopilación de datos históricos

Prueba la integración

  1. En la consola de Cloud Scheduler, busca tu trabajo.
  2. Haz clic en Forzar ejecución para activar el trabajo de forma manual.
  3. Espera unos segundos.
  4. Ve a Cloud Run > Servicios.
  5. Haz clic en lacework-logs-collector:
  6. Haz clic en la pestaña Registros.
  7. Verifica que la función se haya ejecutado correctamente. Busca lo siguiente:

    Successfully obtained access token
    Fetching alerts from YYYY-MM-DDTHH:MM:SSZ to YYYY-MM-DDTHH:MM:SSZ
    Page 1: Retrieved X alerts events
    Fetched X alerts records
    Fetching audit_logs from YYYY-MM-DDTHH:MM:SSZ to YYYY-MM-DDTHH:MM:SSZ
    Page 1: Retrieved X audit_logs events
    Fetched X audit_logs records
    Wrote X records to gs://lacework-logs/lacework/logs_YYYYMMDD_HHMMSS.ndjson
    Successfully processed X records
    
  8. Ve a Cloud Storage > Buckets.

  9. Haz clic en el nombre de tu bucket (lacework-logs).

  10. Navega a la carpeta lacework/:

  11. Verifica que se haya creado un nuevo archivo .ndjson con la marca de tiempo actual.

Si ves errores en los registros, haz lo siguiente:

  • HTTP 401: Verifica las credenciales de la API en las variables de entorno o es posible que el token haya vencido.
  • HTTP 403: Verifica que la clave de API tenga los permisos necesarios en la consola de Lacework
  • HTTP 429: Limitación de frecuencia. La función volverá a intentarlo automáticamente con una espera exponencial.
  • Faltan variables de entorno: Verifica que estén configuradas todas las variables obligatorias.

Configura un feed en Google SecOps para transferir registros de FortiCNAPP (antes Lacework)

  1. Ve a Configuración de SIEM > Feeds.
  2. Haz clic en Add New Feed.
  3. Haz clic en Configura un feed único.
  4. En el campo Nombre del feed, ingresa un nombre para el feed (por ejemplo, Lacework Logs).
  5. Selecciona Google Cloud Storage V2 como el Tipo de fuente.
  6. Selecciona Lacework Cloud Security como el Tipo de registro.
  7. Haz clic en Obtener cuenta de servicio. Se mostrará un correo electrónico único de la cuenta de servicio, por ejemplo:

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  8. Copia esta dirección de correo electrónico.

  9. Haz clic en Siguiente.

  10. Especifica valores para los siguientes parámetros de entrada:

    • URL del bucket de almacenamiento: Ingresa el URI del bucket de GCS con la ruta de acceso del prefijo:

      gs://lacework-logs/lacework/
      
      • Reemplaza:
        • lacework-logs: Es el nombre de tu bucket de GCS.
        • lacework: Es el prefijo o la ruta de carpeta opcional en la que se almacenan los registros (déjalo vacío para la raíz).
    • Opción de eliminación del código fuente: Selecciona la opción de eliminación según tu preferencia:

      • Nunca: Nunca borra ningún archivo después de las transferencias (se recomienda para las pruebas).
      • Borrar archivos transferidos: Borra los archivos después de que se transfirieron correctamente.
      • Borrar los archivos transferidos y los directorios vacíos: Borra los archivos y los directorios vacíos después de que se transfirieron correctamente.

    • Antigüedad máxima del archivo: Incluye los archivos modificados en la cantidad de días especificada (el valor predeterminado es 180 días).

    • Espacio de nombres del recurso: Es el espacio de nombres del recurso.

    • Etiquetas de transferencia: Es la etiqueta que se aplicará a los eventos de este feed.

  11. Haz clic en Siguiente.

  12. Revisa la nueva configuración del feed en la pantalla Finalizar y, luego, haz clic en Enviar.

Otorga permisos de IAM a la cuenta de servicio de Google SecOps

La cuenta de servicio de Google SecOps necesita el rol de visualizador de objetos de Storage en tu bucket de GCS.

  1. Ve a Cloud Storage > Buckets.
  2. Haz clic en el nombre del bucket.
  3. Ve a la pestaña Permisos.
  4. Haz clic en Otorgar acceso.

  5. Proporciona los siguientes detalles de configuración:

    • Agregar entidades: Pega el correo electrónico de la cuenta de servicio de Google SecOps.
    • Asignar roles: Selecciona Visualizador de objetos de Storage.
  6. Haz clic en Guardar.

Registros de muestra de seguridad en la nube de Lacework compatibles

  • Información del agente o de la máquina (inventario de hosts)

    {
      "AGENT_VERSION": "6.7.6-4ce73a7b",
      "CREATED_TIME": "Thu, 03 Nov 2022 02:09:36 -0700",
      "HOSTNAME": "host-agent-1",
      "IP_ADDR": "10.0.0.1",
      "LAST_UPDATE": "Wed, 18 Oct 2023 17:59:09 -0700",
      "MID": 6516601498285932156,
      "MODE": "ebpf",
      "OS": "Linux",
      "STATUS": "ACTIVE",
      "TAGS": {
        "Account": "999999999999",
        "AmiId": "ami-00000000000000000",
        "ExternalIp": "203.0.113.10",
        "Hostname": "internal-host-1.zone.compute.internal",
        "InstanceId": "i-00000000000000000",
        "InternalIp": "172.16.1.10",
        "LwTokenShort": "DUMMYTOKENABCD123456",
        "Name": "proxy-DMZ-app-1",
        "ResourceType": "proxy-machines",
        "SubnetId": "subnet-00000000000000000",
        "VmInstanceType": "t3.small",
        "VmProvider": "AWS",
        "VpcId": "vpc-00000000000000000",
        "Zone": "us-west-2a",
        "arch": "amd64",
        "falconx.io/application": "proxy-machines",
        "falconx.io/environment": "prod",
        "falconx.io/project": "edge",
        "falconx.io/team": "edge",
        "os": "linux"
      }
    }
    
  • Metadatos o integridad del archivo

    {
    "CREATED_TIME": "Wed, 18 Oct 2023 17:02:01 -0700",
    "FILEDATA_HASH": "DUMMYHASH582C741AD91CA817B4718DEAA4E8A83C0B9D92E2",
    "FILE_PATH": "/usr/local/bin/secure_config",
    "MID": 7371220731851617371,
    "MTIME": "Fri, 25 Aug 2023 13:03:09 -0700",
    "SIZE": 8078
    }
    
  • Evaluación de vulnerabilidades del host

    {
    "CVE_PROPS": {
      "description": "DOCUMENTATION: The MITRE CVE dictionary describes this issue as: "
                     "This CVE ID has been rejected or withdrawn by its CVE Numbering "
                     "Authority for the following reason: This CVE ID has been rejected "
                     "or withdrawn by its CVE Numbering Authority.",
      "link": "https://vendor.example.com/security/cve/CVE-2021-47472",
      "metadata": null
    },
    "CVE_RISK_INFO": {
      "HOST_COUNT": 1249,
      "IMAGE_COUNT": 0,
      "PKG_COUNT": 0,
      "SEVERITY_LEVEL": 2,
      "score": 0.5154245281584533
    },
    "CVE_RISK_SCORE": 3.77,
    "END_TIME": "2024-09-04 07:00:00.000",
    "EVAL_CTX": {
      "collector_type": "Agent",
      "exception_props": [],
      "hostname": "vuln-host-1.example.net"
    },
    "EVAL_GUID": "3dc61df780e3b722aa59b0ffcac85683",
    "FEATURE_KEY": {
      "name": "kernel-headers",
      "namespace": "centos:7",
      "package_active": 1,
      "package_path": "",
      "version_installed": "0:3.10.0-1160.119.1.el7.tuxcare.els2"
    },
    "MACHINE_TAGS": {
      "Account": "999999999999",
      "AmiId": "ami-00000000000000000",
      "ExternalIp": "203.0.113.10",
      "Hostname": "ip-172-16-1-10.example-prod.aws.featurespace.net",
      "InternalIp": "10.0.0.1",
      "LwTokenShort": "DUMMYTOKENABCD123456",
      "VmProvider": "AWS",
      "VpcId": "vpc-00000000000000000",
      "os": "linux"
    },
    "MID": 5746003737030963813,
    "PACKAGE_STATUS": "ACTIVE",
    "REGION": "eu-west-2",
    "RISK_SCORE": 10,
    "SEVERITY": "Low",
    "START_TIME": "2024-09-04 06:00:00.000",
    "STATUS": "Exception",
    "VULN_ID": "CVE-2021-47472"
    }
    
  • Cumplimiento de la configuración de Cloud (auditoría)

    {
    "ACCOUNT": {
      "AccountId": "999999999999",
      "Account_Alias": ""
    },
    "EVAL_TYPE": "LW_SA",
    "ID": "lacework-global-87",
    "REASON": "Default security group does not restrict traffic",
    "RECOMMENDATION": "Ensure the default security group of every Virtual Private Cloud (VPC) restricts all traffic",
    "REGION": "eu-north-1",
    "REPORT_TIME": "2024-11-10 18:00:00.000",
    "RESOURCE_ID": "arn:aws:ec2:eu-west-1:999999999999:security-group/sg-00000000000000000",
    "SECTION": "",
    "SEVERITY": "High",
    "STATUS": "NonCompliant"
    }
    
  • Consulta o resolución de DNS

    {
    "CREATED_TIME": "2024-11-06 05:14:44.329",
    "DNS_SERVER_IP": "10.0.0.53",
    "FQDN": "data-service-prod-1234567890.s3.eu-west-2.amazonaws.com",
    "HOST_IP_ADDR": "172.16.1.20",
    "MID": 8843985456817096491,
    "TTL": 5
    }
    
  • Evaluación de vulnerabilidades de imágenes

    {
    "CVE_PROPS": null,
    "EVAL_CTX": {
      "collector_type": "Agentless",
      "image_info": {
        "digest": "sha256:52d5cb782dad7a8a03c8bd1b285bbd32bdbfa8fcc435614bb1e6ceefcf26ae1d",
        "id": "sha256:31427c44cac7ab632d541181073bbd46a964e4ed38d087d8a47f60bb66eef4df",
        "registry": "999999999999.dkr.ecr.eu-west-1.amazonaws.com",
        "repo": "amazon/aws-network-policy-agent"
      }
    },
    "EVAL_GUID": "3a17a74f0a65eed2bddd2d37bb02e6af",
    "FEATURE_KEY": {
      "name": "perl-threads",
      "namespace": "amzn:2",
      "version": "1.87-4.amzn2.0.2"
    },
    "FIX_INFO": {
      "fix_available": 0,
      "fixed_version": ""
    },
    "IMAGE_ID": "sha256:31427c44cac7ab632d541181073bbd46a964e4ed38d087d8a47f60bb66eef4df",
    "IMAGE_RISK_INFO": {
      "factors": [
        "cve",
        "reachability"
      ],
      "factors_breakdown": {
        "cve_counts": {
          "Critical": 0,
          "High": 21,
          "Medium": 73
        },
        "internet_reachability": "Unknown"
      }
    },
    "IMAGE_RISK_SCORE": 6.4,
    "PACKAGE_STATUS": "NO_AGENT_AVAILABLE",
    "RISK_SCORE": 6.4,
    "START_TIME": "2024-11-05 19:05:03.553",
    "STATUS": "GOOD"
    }
    
  • Resumen de la conexión o el tráfico de red

    {
    "DST_ENTITY_ID": {
      "hostname": "service-A.region.amazonaws.com",
      "ip_internal": 0,
      "port": 443,
      "protocol": "TCP"
    },
    "DST_ENTITY_TYPE": "DnsSep",
    "DST_IN_BYTES": 0,
    "DST_OUT_BYTES": 0,
    "ENDPOINT_DETAILS": [
      {
        "dst_ip_addr": "203.0.113.10",
        "dst_port": 443,
        "protocol": "TCP",
        "src_ip_addr": "192.168.1.10"
      },
      {
        "dst_ip_addr": "198.51.100.5",
        "dst_port": 443,
        "protocol": "TCP",
        "src_ip_addr": "192.168.1.10"
      }
    ],
    "END_TIME": "2024-11-05 21:00:00.000",
    "NUM_CONNS": 4,
    "SRC_ENTITY_ID": {
      "mid": 2080882850610892909,
      "pid_hash": 744766973756676842
    },
    "SRC_ENTITY_TYPE": "Process",
    "SRC_IN_BYTES": 25028,
    "SRC_OUT_BYTES": 11962,
    "START_TIME": "2024-11-05 20:00:00.000"
    }
    
  • Información o actualización del paquete

    {
    "ARCH": "x86_64",
    "CREATED_TIME": "2024-11-08 01:28:30.566",
    "MID": 4172267319977985370,
    "PACKAGE_NAME": "grub2",
    "VERSION": "2:2.02-0.87.0.2.el7.el7.centos.14.tuxcare.els2"
    }
    
  • Actividad del proceso del contenedor

    {
    "CONTAINER_ID": "4853339865add970f72213ec5d76ff51d1308c61a7680cc23c8de20c38c0a8e1",
    "END_TIME": "2024-11-08 02:00:00.000",
    "FILE_PATH": "/app/grpc-health-probe",
    "MID": 3708952045169222383,
    "PID": 177267,
    "POD_NAME": "kubernetes-pod-abc",
    "PPID": 177257,
    "PROCESS_START_TIME": "2024-11-08 01:43:29.960",
    "START_TIME": "2024-11-08 01:00:00.000",
    "UID": 0,
    "USERNAME": "serviceuser"
    }
    
  • Alerta o evento general (CloudTrail)

    {
    "EVENT_ID": "413328",
    "EVENT_NAME": "Unauthorized API Call",
    "EVENT_TYPE": "CloudTrailDefaultAlert",
    "SUMMARY": " For account: 999999999999 (and 22 more) : event Unauthorized API Call from a username other "
               "than whitelisted ones. Replaces lacework-global-29 occurred 3772 times by user "
               "UDM-PRINCIPAL-ID:UDM-SERVICE-ROLE (and 167 more) ",
    "START_TIME": "07 Feb 2025 12:00 GMT",
    "EVENT_CATEGORY": "Aws",
    "LINK": "https://security.example.net/ui/alert/12345/details",
    "ACCOUNT": "UDM_ACCOUNT",
    "SOURCE": "CloudTrail",
    "subject": {
      "srcEvent": {
        "event": {
          "errorCode": "AccessDenied",
          "errorMessage": "User: arn:aws:sts::999999999999:assumed-role/UDM-SERVICE-ROLE-IngestionApiRole/UDM-SERVICE-PRINCIPAL "
                          "is not authorized to perform: kinesis:ListShards on resource: "
                          "arn:aws:kinesis:us-east-1:999999999999:stream/ingestion-qa-rel-fraud-review-Stream "
                          "because no identity-based policy allows the kinesis:ListShards action",
          "eventName": "ListShards",
          "eventSource": "kinesis.amazonaws.com",
          "eventTime": "2025-02-07T12:00:24Z",
          "recipientAccountId": "999999999999",
          "sourceIPAddress": "firehose.amazonaws.com",
          "userIdentity": {
            "accessKeyId": "ACCESSKEYIDDUMMY",
            "accountId": "999999999999",
            "arn": "arn:aws:sts::999999999999:assumed-role/UDM-SERVICE-ROLE-IngestionApiRole/UDM-SERVICE-PRINCIPAL",
            "sessionContext": {
              "sessionIssuer": {
                "accountId": "999999999999",
                "arn": "arn:aws:iam::999999999999:role/UDM-SERVICE-ROLE-IngestionApiRole",
                "principalId": "PRINCIPALIDDUMMY",
                "userName": "UDM-SERVICE-ROLE-IngestionApiRole"
              }
            }
          },
          "vpcEndpointId": "vpce-00000000000000000"
        },
        "principalId": "PRINCIPALIDDUMMY:UDM-SERVICE-PRINCIPAL",
        "recipientAccountId": "999999999999",
        "sourceIPAddress": "firehose.amazonaws.com",
        "userIdentityName": "UDM-SERVICE-ROLE-IngestionApiRole"
      }
    }
    }
    

Tabla de asignación de UDM

Campo de registro Asignación de UDM Lógica
alertId metadata.product_log_id El valor se copió directamente.
alertName security_result.rule_name El valor se copió directamente.
gravedad, security_result.severity Se asigna a la gravedad del UDM
estado security_result.summary El valor se copió directamente.
alertType security_result.category_details El valor se copió directamente.
startTime metadata.event_timestamp Se analiza como una marca de tiempo ISO 8601.
endTime additional.fields Se almacena como la etiqueta end_time.
alertInfo.description security_result.description El valor se copió directamente.
alertInfo.subject metadata.description El valor se copió directamente.
entityMap.Machine.hostname principal.hostname El valor se copió directamente.
entityMap.Machine.externalIp principal.ip El valor se copió directamente.
entityMap.User.username principal.user.userid El valor se copió directamente.
entityMap.Region.region principal.location.name El valor se copió directamente.
entityMap.CT_User.accountId principal.user.product_object_id El valor se copió directamente.
event_title event.idm.read_only_udm.security_result.summary Se asignó desde el registro de cambios
event_description event.idm.read_only_udm.security_result.description Se asignó desde el registro de cambios
summary_details event.idm.read_only_udm.security_result.confidence_details Se asignó desde el registro de cambios
target_user_id event.idm.read_only_udm.target.user.userid Se asignó desde el registro de cambios
resource_product_id event.idm.read_only_udm.target.resource.product_object_id Se asignó desde el registro de cambios
user_role event.idm.read_only_udm.target.user.role_name Se asignó desde el registro de cambios
account_name event.idm.read_only_udm.target.resource.name Se asignó desde el registro de cambios
application event.idm.read_only_udm.target.application Se asignó desde el registro de cambios
event_id event.idm.read_only_udm.metadata.product_log_id Se asignó desde el registro de cambios
event_type event.idm.read_only_udm.metadata.product_event_type Se asignó desde el registro de cambios
event_severity event.idm.read_only_udm.security_result.severity_details Se asignó desde el registro de cambios
lacework_account event.idm.read_only_udm.principal.user.userid Se asignó desde el registro de cambios
event_link event.idm.read_only_udm.metadata.url_back_to_product Se asignó desde el registro de cambios
starttimevalue event.idm.read_only_udm.additional.fields Se asignó desde el registro de cambios
endtimevalue event.idm.read_only_udm.additional.fields Se asignó desde el registro de cambios
intgGuid event.idm.read_only_udm.target.resource.attribute.labels Se asignó desde el registro de cambios
rec_id event.idm.read_only_udm.target.resource.attribute.labels Se asignó desde el registro de cambios
event_source event.idm.read_only_udm.metadata.product_name Se asignó desde el registro de cambios
event_timestamp event.idm.read_only_udm.metadata.event_timestamp Se asignó desde el registro de cambios
ACCOUNT", "EVENT_CATEGORY", "subject.srcEvent.recipientAccountAlias", "DERIVED_FIELDS.SOURCE", "subject.srcEvent.event.userIdentity.accessKeyId", "subject.srcEvent.event.userIdentity.arn", "subject.srcEvent.event.errorCode", "subject.srcEvent.event.errorMessage", "subject.srcEvent.event.eventID", "subject.srcEvent.event.eventSource", "subject.srcEvent.event.userIdentity.sessionContext.attributes.mfaAuthenticated", "subject.srcEvent.username", "subject.startTime", "subject.srcEvent.eventName", "DERIVED_FIELDS.CATEGORY", "DERIVED_FIELDS.SUBCATEGORY", "subject.dstEvent.gbm_version", "subject.dstEvent.is_visible", "subject.dstEvent.severity", "subject.dstEvent.recipientAccountAlias", "subject.srcEvent.api", "subject.srcEvent.calltype", "subject.srcEvent.gbm_version", "subject.srcEvent.is_visible", and "subject.srcEvent.severity additional.fields Se asignó desde el registro de cambios
SUMMARY metadata.description Se asignó desde el registro de cambios
EVENT_TYPE metadata.product_event_type Se asignó desde el registro de cambios
EVENT_ID metadata.product_log_id Se asignó desde el registro de cambios
LINK metadata.url_back_to_product Se asignó desde el registro de cambios
subject.srcEvent.event.userAgent", "subject.srcEvent.source network.http.user_agent Se asignó desde el registro de cambios
subject.srcEvent.recipientAccountId principal.user.groupid Se asignó desde el registro de cambios
subject.srcEvent.principalId principal.user.userid Se asignó desde el registro de cambios
subject.srcEvent.event.awsRegion security_result.about.asset.attribute.cloud.availability_zone Se asignó desde el registro de cambios
subject.srcEvent.event.eventCategory security_result.about.asset.category Se asignó desde el registro de cambios
EVENT_NAME security_result.category Se asignó desde el registro de cambios
EVENT_NAME security_result.summary Se asignó desde el registro de cambios
subject.srcType src.resource.resource_subtype Se asignó desde el registro de cambios
subject.srcEvent.event.userIdentity.sessionContext.attributes.creationDate metadata.event_timestamp Se asignó desde el registro de cambios
subject.srcEvent.accountcaller principal.resource.product_object_id Se asignó desde el registro de cambios
subject.dstEvent.region target.asset.location.name Se asignó desde el registro de cambios
subject.dstEvent.accountcaller target.resource.product_object_id Se asignó desde el registro de cambios
subject.dstType target.resource.resource_subtype Se asignó desde el registro de cambios
subject.dstEvent.service target.url Se asignó desde el registro de cambios
subject.dstEvent.username target.user.userid Se asignó desde el registro de cambios

Registro de cambios

Consulta el registro de cambios de este analizador

¿Necesitas más ayuda? Obtén respuestas de miembros de la comunidad y profesionales de Google SecOps.