Recoger registros de Signal Sciences WAF
En este documento se explica cómo ingerir registros de WAF de Signal Sciences en Google Security Operations mediante Google Cloud Storage. El analizador transforma los registros de Signal Sciences de su formato JSON al modelo de datos unificado (UDM) de Chronicle. Gestiona dos estructuras de mensajes principales: los mensajes "RPC.PreRequest/PostRequest" se analizan mediante patrones Grok, mientras que otros mensajes se procesan como objetos JSON, extrayendo los campos relevantes y asignándolos al esquema UDM.
Antes de empezar
Asegúrate de que cumples los siguientes requisitos previos:
- Instancia de Google SecOps
- El flujo de VPC está configurado y activo en tu Google Cloud entorno
- Acceso privilegiado a Signal Sciences WAF
Crea un Google Cloud segmento de almacenamiento
- Inicia sesión en la Google Cloud consola.
Ve a la página Segmentos de Cloud Storage.
Haz clic en Crear.
En la página Crear un segmento, introduce la información del segmento. Después de cada uno de los pasos siguientes, haga clic en Continuar para pasar al siguiente:
En la sección Empezar, haz lo siguiente:
- Introduce un nombre único que cumpla los requisitos de nombres de segmentos (por ejemplo, vpcflow-logs).
- Para habilitar el espacio de nombres jerárquico, haz clic en la flecha para desplegar la sección Optimizar para cargas de trabajo orientadas a archivos y con gran cantidad de datos y, a continuación, selecciona Habilitar espacio de nombres jerárquico en este contenedor.
- Para añadir una etiqueta de contenedor, haz clic en la flecha para desplegar la sección Etiquetas.
- Haga clic en Añadir etiqueta y especifique una clave y un valor para la etiqueta.
En la sección Elige dónde quieres almacenar los datos, haz lo siguiente:
- Selecciona un Tipo de ubicación.
- Usa el menú del tipo de ubicación para seleccionar una Ubicación donde se almacenarán de forma permanente los datos de los objetos de tu segmento.
- Para configurar la replicación entre contenedores, despliega la sección Configurar la replicación entre contenedores.
En la sección Elige una clase de almacenamiento para tus datos, selecciona una clase de almacenamiento predeterminada para el segmento o Autoclass para gestionar automáticamente la clase de almacenamiento de los datos del segmento.
En la sección Elige cómo quieres controlar el acceso a los objetos, selecciona no para aplicar la prevención del acceso público y elige un modelo de control de acceso para los objetos del segmento.
En la sección Elige cómo proteger los datos de los objetos, haz lo siguiente:
- Selecciona cualquiera de las opciones de Protección de datos que quieras configurar para tu contenedor.
- Para elegir cómo se cifrarán los datos de los objetos, haga clic en la flecha del desplegable Cifrado de datos y seleccione un Método de cifrado de datos.
Haz clic en Crear.
Configurar una clave de API de Signal Sciences WAF
- Inicia sesión en la interfaz web de Signal Sciences WAF.
- Ve a Mi perfil > Tokens de acceso a la API.
- Haz clic en Añadir token de acceso a la API.
- Proporciona un nombre único y descriptivo (por ejemplo,
Google SecOps
). - Haz clic en Crear token de acceso a la API.
- Copia y guarda el token en un lugar seguro.
- Haz clic en Entendido para terminar de crear el token.
Implementar una secuencia de comandos en un host Linux para extraer registros de Signal Sciences y almacenarlos en Google Cloud
- Inicia sesión en el host de Linux mediante SSH.
Instala la biblioteca de Python para almacenar el JSON del WAF de Signal Sciences en un segmento de Cloud Storage:
pip install google-cloud-storage
Define esta variable de entorno para llamar al archivo JSON que tiene las credenciales de Google Cloud:
export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/service-account-key.json"
Configura las siguientes variables de entorno, ya que esta información no debe estar codificada:
export SIGSCI_EMAIL=<Signal_Sciences_account_email> export SIGSCI_TOKEN=<Signal_Sciences_API_token> export SIGSCI_CORP=<Corporation_name_in_Signal_Sciences>
Ejecuta la siguiente secuencia de comandos:
import sys import requests import os import calendar import json from datetime import datetime, timedelta from google.cloud import storage # Check if all necessary environment variables are set if 'SIGSCI_EMAIL' not in os.environ or 'SIGSCI_TOKEN' not in os.environ or 'SIGSCI_CORP' not in os.environ: print("ERROR: You need to define SIGSCI_EMAIL, SIGSCI_TOKEN, and SIGSCI_CORP environment variables.") print("Please fix and run again. Existing...") sys.exit(1) # Exit if environment variables are not set # Define the Google Cloud Storage bucket name and output file name bucket_name = 'Your_GCS_Bucket' # Replace with your GCS bucket name output_file_name = 'signal_sciences_logs.json' # Initialize Google Cloud Storage client storage_client = storage.Client() # Function to upload data to Google Cloud Storage def upload_to_gcs(bucket_name, data, destination_blob_name): bucket = storage_client.bucket(bucket_name) blob = bucket.blob(destination_blob_name) blob.upload_from_string(data, content_type='application/json') print(f"Data uploaded to {destination_blob_name} in bucket {bucket_name}") # Signal Sciences API information api_host = 'https://dashboard.signalsciences.net' # email = 'user@domain.com' # Signal Sciences account email # token = 'XXXXXXXX-XXXX-XXX-XXXX-XXXXXXXXXXXX' # API token for authentication # corp_name = 'Domain' # Corporation name in Signal Sciences # site_names = ['testenv'] # Replace with your actual site names # List of comma-delimited sites that you want to extract data from site_names = [ 'site123', 'site345' ] # Define all sites to pull logs from email = os.environ.get('SIGSCI_EMAIL') # Signal Sciences account email token = os.environ.get('SIGSCI_TOKEN') # API token for authentication corp_name = os.environ.get('SIGSCI_CORP') # Corporation name in Signal Sciences # Calculate the start and end timestamps for the previous hour in UTC until_time = datetime.utcnow().replace(minute=0, second=0, microsecond=0) from_time = until_time - timedelta(hours=1) until_time = calendar.timegm(until_time.utctimetuple()) from_time = calendar.timegm(from_time.utctimetuple()) # Prepare HTTP headers for the API request headers = { 'Content-Type': 'application/json', 'x-api-user': email, 'x-api-token': token } # Collect logs for each site collected_logs = [] for site_name in site_names: url = f"{api_host}/api/v0/corps/{corp_name}/sites/{site_name}/feed/requests?from={from_time}&until={until_time}" while True: response = requests.get(url, headers=headers) if response.status_code != 200: print(f"Error fetching logs: {response.text}", file=sys.stderr) break # Parse the JSON response data = response.json() collected_logs.extend(data['data']) # Add the log messages to our list # Pagination: check if there is a next page next_url = data.get('next', {}).get('uri') if not next_url: break url = api_host + next_url # Convert the collected logs to a newline-delimited JSON string json_data = '\n'.join(json.dumps(log) for log in collected_logs) # Save the newline-delimited JSON data to a GCS bucket upload_to_gcs(bucket_name, json_data, output_file_name)
Configurar feeds
Para configurar un feed, sigue estos pasos:
- Ve a Configuración de SIEM > Feeds.
- Haz clic en Añadir feed.
- En la página siguiente, haga clic en Configurar un solo feed.
- En el campo Nombre del feed, introduce un nombre para el feed (por ejemplo,
Signal Sciences WAF Logs
). - Seleccione Google Cloud Storage como Tipo de origen.
- Seleccione Signal Sciences WAF como Tipo de registro.
- Haz clic en Obtener cuenta de servicio en Cuenta de servicio de Chronicle.
- Haz clic en Siguiente.
Especifique los valores de los siguientes parámetros de entrada:
- URI de segmento de almacenamiento: URL de segmento de almacenamiento en formato
gs://my-bucket/<value>
. Google Cloud - El URI es un: selecciona Directorio que incluye subdirectorios.
Opciones de eliminación de la fuente: selecciona la opción de eliminación que prefieras.
- URI de segmento de almacenamiento: URL de segmento de almacenamiento en formato
Haz clic en Siguiente.
Revise la configuración de la nueva fuente en la pantalla Finalizar y, a continuación, haga clic en Enviar.
Tabla de asignación de UDM
Campo de registro | Asignación de UDM | Lógica |
---|---|---|
CLIENT-IP | target.ip | Se extrae del campo de encabezado CLIENT-IP . |
CLIENT-IP | target.port | Se extrae del campo de encabezado CLIENT-IP . |
Conexión | security_result.about.labels | El valor se toma del campo Connection del registro sin procesar y se asigna a security_result.about.labels . |
Content-Length | security_result.about.labels | El valor se toma del campo Content-Length del registro sin procesar y se asigna a security_result.about.labels . |
Tipo de contenido | security_result.about.labels | El valor se toma del campo Content-Type del registro sin procesar y se asigna a security_result.about.labels . |
creado | metadata.event_timestamp | El valor se toma del campo created del registro sin procesar y se asigna a metadata.event_timestamp . |
details.headersIn | security_result.about.resource.attribute.labels | El valor se toma del campo details.headersIn del registro sin procesar y se asigna a security_result.about.resource.attribute.labels . |
details.headersOut | security_result.about.resource.attribute.labels | El valor se toma del campo details.headersOut del registro sin procesar y se asigna a security_result.about.resource.attribute.labels . |
details.id | principal.process.pid | El valor se toma del campo details.id del registro sin procesar y se asigna a principal.process.pid . |
details.method | network.http.method | El valor se toma del campo details.method del registro sin procesar y se asigna a network.http.method . |
details.protocol | network.application_protocol | El valor se toma del campo details.protocol del registro sin procesar y se asigna a network.application_protocol . |
details.remoteCountryCode | principal.location.country_or_region | El valor se toma del campo details.remoteCountryCode del registro sin procesar y se asigna a principal.location.country_or_region . |
details.remoteHostname | target.hostname | El valor se toma del campo details.remoteHostname del registro sin procesar y se asigna a target.hostname . |
details.remoteIP | target.ip | El valor se toma del campo details.remoteIP del registro sin procesar y se asigna a target.ip . |
details.responseCode | network.http.response_code | El valor se toma del campo details.responseCode del registro sin procesar y se asigna a network.http.response_code . |
details.responseSize | network.received_bytes | El valor se toma del campo details.responseSize del registro sin procesar y se asigna a network.received_bytes . |
details.serverHostname | principal.hostname | El valor se toma del campo details.serverHostname del registro sin procesar y se asigna a principal.hostname . |
details.serverName | principal.asset.network_domain | El valor se toma del campo details.serverName del registro sin procesar y se asigna a principal.asset.network_domain . |
details.tags | security_result.detection_fields | El valor se toma del campo details.tags del registro sin procesar y se asigna a security_result.detection_fields . |
details.tlsCipher | network.tls.cipher | El valor se toma del campo details.tlsCipher del registro sin procesar y se asigna a network.tls.cipher . |
details.tlsProtocol | network.tls.version | El valor se toma del campo details.tlsProtocol del registro sin procesar y se asigna a network.tls.version . |
details.userAgent | network.http.user_agent | El valor se toma del campo details.userAgent del registro sin procesar y se asigna a network.http.user_agent . |
details.uri | network.http.referral_url | El valor se toma del campo details.uri del registro sin procesar y se asigna a network.http.referral_url . |
eventType | metadata.product_event_type | El valor se toma del campo eventType del registro sin procesar y se asigna a metadata.product_event_type . |
headersIn | security_result.about.labels | El valor se toma del campo headersIn del registro sin procesar y se asigna a security_result.about.labels . |
headersOut | security_result.about.labels | El valor se toma del campo headersOut del registro sin procesar y se asigna a security_result.about.labels . |
id | principal.process.pid | El valor se toma del campo id del registro sin procesar y se asigna a principal.process.pid . |
mensaje | metadata.description | El valor se toma del campo message del registro sin procesar y se asigna a metadata.description . |
método | network.http.method | El valor se toma del campo method del registro sin procesar y se asigna a network.http.method . |
ModuleVersion | metadata.ingestion_labels | El valor se toma del campo ModuleVersion del registro sin procesar y se asigna a metadata.ingestion_labels . |
msgData.actions | security_result.action | El valor se toma del campo msgData.actions del registro sin procesar y se asigna a security_result.action . |
msgData.changes | target.resource.attribute.labels | El valor se toma del campo msgData.changes del registro sin procesar y se asigna a target.resource.attribute.labels . |
msgData.conditions | security_result.description | El valor se toma del campo msgData.conditions del registro sin procesar y se asigna a security_result.description . |
msgData.detailLink | network.http.referral_url | El valor se toma del campo msgData.detailLink del registro sin procesar y se asigna a network.http.referral_url . |
msgData.name | target.resource.name | El valor se toma del campo msgData.name del registro sin procesar y se asigna a target.resource.name . |
msgData.reason | security_result.summary | El valor se toma del campo msgData.reason del registro sin procesar y se asigna a security_result.summary . |
msgData.sites | network.http.user_agent | El valor se toma del campo msgData.sites del registro sin procesar y se asigna a network.http.user_agent . |
protocolo | network.application_protocol | El valor se toma del campo protocol del registro sin procesar y se asigna a network.application_protocol . |
remoteCountryCode | principal.location.country_or_region | El valor se toma del campo remoteCountryCode del registro sin procesar y se asigna a principal.location.country_or_region . |
remoteHostname | target.hostname | El valor se toma del campo remoteHostname del registro sin procesar y se asigna a target.hostname . |
remoteIP | target.ip | El valor se toma del campo remoteIP del registro sin procesar y se asigna a target.ip . |
responseCode | network.http.response_code | El valor se toma del campo responseCode del registro sin procesar y se asigna a network.http.response_code . |
responseSize | network.received_bytes | El valor se toma del campo responseSize del registro sin procesar y se asigna a network.received_bytes . |
serverHostname | principal.hostname | El valor se toma del campo serverHostname del registro sin procesar y se asigna a principal.hostname . |
serverName | principal.asset.network_domain | El valor se toma del campo serverName del registro sin procesar y se asigna a principal.asset.network_domain . |
etiquetas | security_result.detection_fields | El valor se toma del campo tags del registro sin procesar y se asigna a security_result.detection_fields . |
timestamp | metadata.event_timestamp | El valor se toma del campo timestamp del registro sin procesar y se asigna a metadata.event_timestamp . |
tlsCipher | network.tls.cipher | El valor se toma del campo tlsCipher del registro sin procesar y se asigna a network.tls.cipher . |
tlsProtocol | network.tls.version | El valor se toma del campo tlsProtocol del registro sin procesar y se asigna a network.tls.version . |
URI | target.url | El valor se toma del campo URI del registro sin procesar y se asigna a target.url . |
userAgent | network.http.user_agent | El valor se toma del campo userAgent del registro sin procesar y se asigna a network.http.user_agent . |
uri | network.http.referral_url | El valor se toma del campo uri del registro sin procesar y se asigna a network.http.referral_url . |
X-ARR-SSL | network.tls.client.certificate.issuer | El valor se extrae del campo de encabezado X-ARR-SSL mediante los filtros grok y kv. |
metadata.event_type | El tipo de evento lo determina el analizador en función de la presencia de información sobre el principal y el objetivo. Si están presentes tanto el objetivo como la entidad principal, el tipo de evento es NETWORK_HTTP . Si solo está presente el principal, el tipo de evento es STATUS_UPDATE . De lo contrario, el tipo de evento es GENERIC_EVENT . |
|
metadata.log_type | El valor se ha codificado como SIGNAL_SCIENCES_WAF . |
|
metadata.product_name | El valor se ha codificado como Signal Sciences WAF . |
|
metadata.vendor_name | El valor se ha codificado como Signal Sciences . |
|
principal.asset.hostname | El valor se toma del campo principal.hostname . |
|
target.asset.hostname | El valor se toma del campo target.hostname . |
|
target.asset.ip | El valor se toma del campo target.ip . |
|
target.user.user_display_name | El valor se extrae del campo message_data mediante un filtro grok. |
|
target.user.userid | El valor se extrae del campo message_data mediante un filtro grok. |
¿Necesitas más ayuda? Recibe respuestas de los miembros de la comunidad y de los profesionales de Google SecOps.