Collecter les journaux Symantec WSS
Ce document explique comment ingérer des journaux Symantec Web Security Service (WSS) dans Google Security Operations à l'aide d'Amazon S3. L'analyseur tente d'abord d'analyser le message de journal en tant que JSON. Si cela échoue, il utilise une série de modèles grok de plus en plus spécifiques pour extraire les champs du texte brut, en mappant finalement les données extraites au modèle de données unifié (UDM).
Avant de commencer
Assurez-vous de remplir les conditions suivantes :
- Une instance Google SecOps.
- Accès privilégié à Symantec Web Security Service.
- Accès privilégié à AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge).
Collecter les conditions préalables de Symantec WSS (ID, clés API, ID d'organisation, jetons)
- Connectez-vous au portail Symantec Web Security Service en tant qu'administrateur.
- Accédez à Compte > Identifiants de l'API.
- Cliquez sur Ajouter.
- Fournissez les informations de configuration suivantes :
- Nom de l'API : saisissez un nom descriptif (par exemple,
Google SecOps Integration
). - Description : saisissez une description pour les identifiants de l'API.
- Nom de l'API : saisissez un nom descriptif (par exemple,
- Cliquez sur Enregistrer et copiez les identifiants API générés de manière sécurisée.
- Notez l'URL du portail WSS et le point de terminaison de l'API de synchronisation.
- Copiez et enregistrez les informations suivantes dans un emplacement sécurisé :
- WSS_API_USERNAME.
- WSS_API_PASSWORD.
- WSS_SYNC_URL.
Configurer un bucket AWS S3 et IAM pour Google SecOps
- Créez un bucket Amazon S3 en suivant ce guide de l'utilisateur : Créer un bucket.
- Enregistrez le Nom et la Région du bucket pour référence ultérieure (par exemple,
symantec-wss-logs
). - Créez un utilisateur en suivant ce guide de l'utilisateur : Créer un utilisateur IAM.
- Sélectionnez l'utilisateur créé.
- Sélectionnez l'onglet Informations d'identification de sécurité.
- Cliquez sur Créer une clé d'accès dans la section Clés d'accès.
- Sélectionnez Service tiers comme Cas d'utilisation.
- Cliquez sur Suivant.
- Facultatif : Ajoutez un tag de description.
- Cliquez sur Créer une clé d'accès.
- Cliquez sur Télécharger le fichier CSV pour enregistrer la clé d'accès et la clé d'accès secrète pour référence ultérieure.
- Cliquez sur OK.
- Sélectionnez l'onglet Autorisations.
- Cliquez sur Ajouter des autorisations dans la section Règles relatives aux autorisations.
- Sélectionnez Ajouter des autorisations.
- Sélectionnez Joindre directement des règles.
- Recherchez la règle AmazonS3FullAccess.
- Sélectionnez la règle.
- Cliquez sur Suivant.
- Cliquez sur Ajouter des autorisations.
Configurer la stratégie et le rôle IAM pour les importations S3
- Dans la console AWS, accédez à IAM > Stratégies.
- Cliquez sur Créer une règle > onglet JSON.
- Copiez et collez le règlement suivant.
JSON de la règle (remplacez
symantec-wss-logs
si vous avez saisi un autre nom de bucket) :{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::symantec-wss-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::symantec-wss-logs/symantec/wss/state.json" } ] }
Cliquez sur Suivant > Créer une règle.
Accédez à IAM > Rôles > Créer un rôle > Service AWS > Lambda.
Associez la règle que vous venez de créer.
Nommez le rôle
SymantecWssToS3Role
, puis cliquez sur Créer un rôle.
Créer la fonction Lambda
- Dans la console AWS, accédez à Lambda > Fonctions > Créer une fonction.
- Cliquez sur Créer à partir de zéro.
Fournissez les informations de configuration suivantes :
Paramètre Valeur Nom symantec_wss_to_s3
Durée d'exécution Python 3.13 Architecture x86_64 Rôle d'exécution SymantecWssToS3Role
Une fois la fonction créée, ouvrez l'onglet Code, supprimez le stub et collez le code suivant (
symantec_wss_to_s3.py
).#!/usr/bin/env python3 # Lambda: Pull Symantec WSS logs and store raw payloads to S3 # - Time window via millisecond timestamps for WSS Sync API. # - Preserves vendor-native format (CSV/JSON/ZIP). # - Retries with exponential backoff; unique S3 keys to avoid overwrites. import os, json, time, uuid from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError import boto3 S3_BUCKET = os.environ["S3_BUCKET"] S3_PREFIX = os.environ.get("S3_PREFIX", "symantec/wss/") STATE_KEY = os.environ.get("STATE_KEY", "symantec/wss/state.json") WINDOW_SEC = int(os.environ.get("WINDOW_SECONDS", "3600")) # default 1h HTTP_TIMEOUT= int(os.environ.get("HTTP_TIMEOUT", "60")) WSS_SYNC_URL = os.environ.get("WSS_SYNC_URL", "https://portal.threatpulse.com/reportpod/logs/sync") API_USERNAME = os.environ["WSS_API_USERNAME"] API_PASSWORD = os.environ["WSS_API_PASSWORD"] TOKEN_PARAM = os.environ.get("WSS_TOKEN_PARAM", "none") MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3")) USER_AGENT = os.environ.get("USER_AGENT", "symantec-wss-to-s3/1.0") s3 = boto3.client("s3") def _load_state(): try: obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY) return json.loads(obj["Body"].read()) except Exception: return {} def _save_state(st): s3.put_object( Bucket=S3_BUCKET, Key=STATE_KEY, Body=json.dumps(st, separators=(",", ":")).encode("utf-8"), ContentType="application/json", ) def _ms_timestamp(ts: float) -> int: """Convert Unix timestamp to milliseconds for WSS API""" return int(ts * 1000) def _fetch_wss_logs(start_ms: int, end_ms: int) -> tuple[bytes, str, str]: # WSS Sync API parameters params = f"startDate={start_ms}&endDate={end_ms}&token={TOKEN_PARAM}" url = f"{WSS_SYNC_URL}?{params}" attempt = 0 while True: req = Request(url, method="GET") req.add_header("User-Agent", USER_AGENT) req.add_header("X-APIUsername", API_USERNAME) req.add_header("X-APIPassword", API_PASSWORD) try: with urlopen(req, timeout=HTTP_TIMEOUT) as r: blob = r.read() content_type = r.headers.get("Content-Type", "application/octet-stream") content_encoding = r.headers.get("Content-Encoding", "") return blob, content_type, content_encoding except (HTTPError, URLError) as e: attempt += 1 print(f"HTTP error on attempt {attempt}: {e}") if attempt > MAX_RETRIES: raise # exponential backoff with jitter time.sleep(min(60, 2 ** attempt) + (time.time() % 1)) def _determine_extension(content_type: str, content_encoding: str) -> str: """Determine file extension based on content type and encoding""" if "zip" in content_type.lower(): return ".zip" if "gzip" in content_type.lower() or content_encoding.lower() == "gzip": return ".gz" if "json" in content_type.lower(): return ".json" if "csv" in content_type.lower(): return ".csv" return ".bin" def _put_wss_data(blob: bytes, content_type: str, content_encoding: str, from_ts: float, to_ts: float) -> str: # Create unique S3 key for WSS data ts_path = time.strftime("%Y/%m/%d", time.gmtime(to_ts)) uniq = f"{int(time.time()*1e6)}_{uuid.uuid4().hex[:8]}" ext = _determine_extension(content_type, content_encoding) key = f"{S3_PREFIX}{ts_path}/symantec_wss_{int(from_ts)}_{int(to_ts)}_{uniq}{ext}" s3.put_object( Bucket=S3_BUCKET, Key=key, Body=blob, ContentType=content_type, Metadata={ 'source': 'symantec-wss', 'from_timestamp': str(int(from_ts)), 'to_timestamp': str(int(to_ts)), 'content_encoding': content_encoding } ) return key def lambda_handler(event=None, context=None): st = _load_state() now = time.time() from_ts = float(st.get("last_to_ts") or (now - WINDOW_SEC)) to_ts = now # Convert to milliseconds for WSS API start_ms = _ms_timestamp(from_ts) end_ms = _ms_timestamp(to_ts) print(f"Fetching Symantec WSS logs from {start_ms} to {end_ms}") blob, content_type, content_encoding = _fetch_wss_logs(start_ms, end_ms) print(f"Retrieved {len(blob)} bytes with content-type: {content_type}") if content_encoding: print(f"Content encoding: {content_encoding}") key = _put_wss_data(blob, content_type, content_encoding, from_ts, to_ts) st["last_to_ts"] = to_ts st["last_successful_run"] = now _save_state(st) return { "statusCode": 200, "body": { "success": True, "s3_key": key, "content_type": content_type, "content_encoding": content_encoding, "from_timestamp": from_ts, "to_timestamp": to_ts, "bytes_retrieved": len(blob) } } if __name__ == "__main__": print(lambda_handler())
Accédez à Configuration > Variables d'environnement.
Cliquez sur Modifier > Ajouter une variable d'environnement.
Saisissez les variables d'environnement fournies dans le tableau suivant, en remplaçant les exemples de valeurs par les vôtres.
Variables d'environnement
Clé Exemple de valeur S3_BUCKET
symantec-wss-logs
S3_PREFIX
symantec/wss/
STATE_KEY
symantec/wss/state.json
WINDOW_SECONDS
3600
HTTP_TIMEOUT
60
MAX_RETRIES
3
USER_AGENT
symantec-wss-to-s3/1.0
WSS_SYNC_URL
https://portal.threatpulse.com/reportpod/logs/sync
WSS_API_USERNAME
your-api-username
(à partir de l'étape 2)WSS_API_PASSWORD
your-api-password
(à partir de l'étape 2)WSS_TOKEN_PARAM
none
Une fois la fonction créée, restez sur sa page (ou ouvrez Lambda > Fonctions > votre-fonction).
Accédez à l'onglet Configuration.
Dans le panneau Configuration générale, cliquez sur Modifier.
Définissez le délai avant expiration sur 5 minutes (300 secondes), puis cliquez sur Enregistrer.
Créer une programmation EventBridge
- Accédez à Amazon EventBridge> Scheduler> Create schedule.
- Fournissez les informations de configuration suivantes :
- Planning récurrent : Tarif (
1 hour
). - Cible : votre fonction Lambda
symantec_wss_to_s3
. - Nom :
symantec-wss-1h
.
- Planning récurrent : Tarif (
- Cliquez sur Créer la programmation.
(Facultatif) Créez un utilisateur et des clés IAM en lecture seule pour Google SecOps
- Dans la console AWS, accédez à IAM > Utilisateurs.
- Cliquez sur Add users (Ajouter des utilisateurs).
- Fournissez les informations de configuration suivantes :
- Utilisateur : saisissez
secops-reader
. - Type d'accès : sélectionnez Clé d'accès – Accès programmatique.
- Utilisateur : saisissez
- Cliquez sur Créer un utilisateur.
- Associez une stratégie de lecture minimale (personnalisée) : Utilisateurs > secops-reader > Autorisations > Ajouter des autorisations > Associer des stratégies directement > Créer une stratégie.
JSON :
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::symantec-wss-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::symantec-wss-logs" } ] }
Nom =
secops-reader-policy
.Cliquez sur Créer une règle> recherchez/sélectionnez > Suivant> Ajouter des autorisations.
Créez une clé d'accès pour
secops-reader
: Identifiants de sécurité > Clés d'accès.Cliquez sur Créer une clé d'accès.
Téléchargez le fichier CSV. (Vous collerez ces valeurs dans le flux.)
Configurer un flux dans Google SecOps pour ingérer les journaux Symantec WSS
- Accédez à Paramètres SIEM> Flux.
- Cliquez sur + Ajouter un flux.
- Dans le champ Nom du flux, saisissez un nom pour le flux (par exemple,
Symantec WSS logs
). - Sélectionnez Amazon S3 V2 comme type de source.
- Sélectionnez Symantec WSS comme Type de journal.
- Cliquez sur Suivant.
- Spécifiez les valeurs des paramètres d'entrée suivants :
- URI S3 :
s3://symantec-wss-logs/symantec/wss/
- Options de suppression de la source : sélectionnez l'option de suppression de votre choix.
- Âge maximal des fichiers : incluez les fichiers modifiés au cours des derniers jours. La valeur par défaut est de 180 jours.
- ID de clé d'accès : clé d'accès utilisateur ayant accès au bucket S3.
- Clé d'accès secrète : clé secrète de l'utilisateur ayant accès au bucket S3.
- Espace de noms de l'élément : espace de noms de l'élément.
- Libellés d'ingestion : libellé appliqué aux événements de ce flux.
- URI S3 :
- Cliquez sur Suivant.
- Vérifiez la configuration de votre nouveau flux sur l'écran Finaliser, puis cliquez sur Envoyer.
Table de mappage UDM
Champ du journal | Mappage UDM | Logique |
---|---|---|
category_id | read_only_udm.metadata.product_event_type | Si category_id est défini sur 1, read_only_udm.metadata.product_event_type est défini sur Security . Si category_id est défini sur 5, read_only_udm.metadata.product_event_type est défini sur Policy . |
collector_device_ip | read_only_udm.principal.ip, read_only_udm.principal.asset.ip | Valeur du champ "collector_device_ip" |
connection.bytes_download | read_only_udm.network.received_bytes | Valeur du champ "connection.bytes_download" convertie en entier |
connection.bytes_upload | read_only_udm.network.sent_bytes | Valeur du champ connection.bytes_upload convertie en entier |
connection.dst_ip | read_only_udm.target.ip | Valeur du champ "connection.dst_ip" |
connection.dst_location.country | read_only_udm.target.location.country_or_region | Valeur du champ connection.dst_location.country |
connection.dst_name | read_only_udm.target.hostname | Valeur du champ "connection.dst_name" |
connection.dst_port | read_only_udm.target.port | Valeur du champ connection.dst_port convertie en entier |
connection.http_status | read_only_udm.network.http.response_code | Valeur du champ connection.http_status convertie en entier |
connection.http_user_agent | read_only_udm.network.http.user_agent | Valeur du champ connection.http_user_agent |
connection.src_ip | read_only_udm.principal.ip, read_only_udm.src.ip | Valeur du champ "connection.src_ip". Si src_ip ou collector_device_ip n'est pas vide, il est mappé sur read_only_udm.src.ip. |
connection.tls.version | read_only_udm.network.tls.version_protocol | Valeur du champ "connection.tls.version" |
connection.url.host | read_only_udm.target.hostname | Valeur du champ connection.url.host |
connection.url.method | read_only_udm.network.http.method | Valeur du champ connection.url.method |
connection.url.path | read_only_udm.target.url | Valeur du champ connection.url.path |
connection.url.text | read_only_udm.target.url | Valeur du champ "connection.url.text" |
cs_connection_negotiated_cipher | read_only_udm.network.tls.cipher | Valeur du champ cs_connection_negotiated_cipher |
cs_icap_status | read_only_udm.security_result.description | Valeur du champ cs_icap_status |
device_id | read_only_udm.target.resource.id, read_only_udm.target.resource.product_object_id | Valeur du champ device_id |
device_ip | read_only_udm.intermediary.ip, read_only_udm.intermediary.asset.ip | Valeur du champ "device_ip" |
device_time | read_only_udm.metadata.collected_timestamp, read_only_udm.metadata.event_timestamp | Valeur du champ device_time convertie en chaîne. Si "when" est vide, il est mappé sur read_only_udm.metadata.event_timestamp. |
nom d'hôte | read_only_udm.principal.hostname, read_only_udm.principal.asset.hostname | Valeur du champ "Nom d'hôte" |
log_time | read_only_udm.metadata.event_timestamp | La valeur du champ "log_time" a été convertie en code temporel. Si les champs "when" et "device_time" sont vides, ils sont mappés sur read_only_udm.metadata.event_timestamp. |
msg_desc | read_only_udm.metadata.description | Valeur du champ msg_desc |
os_details | read_only_udm.target.asset.platform_software.platform, read_only_udm.target.asset.platform_software.platform_version | Valeur du champ "os_details". Si os_details n'est pas vide, il est analysé pour extraire os_name et os_ver. Si os_name contient Windows , read_only_udm.target.asset.platform_software.platform est défini sur WINDOWS . os_ver est mappé sur read_only_udm.target.asset.platform_software.platform_version |
product_data.cs(Referer) | read_only_udm.network.http.referral_url | Valeur du champ product_data.cs(Referer) |
product_data.r-supplier-country | read_only_udm.principal.location.country_or_region | Valeur du champ product_data.r-supplier-country |
product_data.s-supplier-ip | read_only_udm.intermediary.ip, read_only_udm.intermediary.asset.ip | Valeur du champ product_data.s-supplier-ip |
product_data.x-bluecoat-application-name | read_only_udm.target.application | Valeur du champ product_data.x-bluecoat-application-name |
product_data.x-bluecoat-transaction-uuid | read_only_udm.metadata.product_log_id | Valeur du champ product_data.x-bluecoat-transaction-uuid |
product_data.x-client-agent-sw | read_only_udm.observer.platform_version | Valeur du champ product_data.x-client-agent-sw |
product_data.x-client-agent-type | read_only_udm.observer.application | Valeur du champ product_data.x-client-agent-type |
product_data.x-client-device-id | read_only_udm.target.resource.type, read_only_udm.target.resource.id, read_only_udm.target.resource.product_object_id | Si elle n'est pas vide, read_only_udm.target.resource.type est défini sur DEVICE . La valeur du champ product_data.x-client-device-id est mappée sur read_only_udm.target.resource.id et read_only_udm.target.resource.product_object_id. |
product_data.x-client-device-name | read_only_udm.src.hostname, read_only_udm.src.asset.hostname | Valeur du champ product_data.x-client-device-name |
product_data.x-cs-client-ip-country | read_only_udm.target.location.country_or_region | Valeur du champ product_data.x-cs-client-ip-country |
product_data.x-cs-connection-negotiated-cipher | read_only_udm.network.tls.cipher | Valeur du champ product_data.x-cs-connection-negotiated-cipher |
product_data.x-cs-connection-negotiated-ssl-version | read_only_udm.network.tls.version_protocol | Valeur du champ product_data.x-cs-connection-negotiated-ssl-version |
product_data.x-exception-id | read_only_udm.security_result.summary | Valeur du champ product_data.x-exception-id |
product_data.x-rs-certificate-hostname | read_only_udm.network.tls.client.server_name | Valeur du champ product_data.x-rs-certificate-hostname |
product_data.x-rs-certificate-hostname-categories | read_only_udm.security_result.category_details | Valeur du champ product_data.x-rs-certificate-hostname-categories |
product_data.x-rs-certificate-observed-errors | read_only_udm.network.tls.server.certificate.issuer | Valeur du champ product_data.x-rs-certificate-observed-errors |
product_data.x-rs-certificate-validate-status | read_only_udm.network.tls.server.certificate.subject | Valeur du champ product_data.x-rs-certificate-validate-status |
product_name | read_only_udm.metadata.product_name | Valeur du champ product_name |
product_ver | read_only_udm.metadata.product_version | Valeur du champ product_ver |
proxy_connection.src_ip | read_only_udm.intermediary.ip, read_only_udm.intermediary.asset.ip | Valeur du champ "proxy_connection.src_ip" |
received_bytes | read_only_udm.network.received_bytes | Valeur du champ "received_bytes" convertie en entier |
ref_uid | read_only_udm.metadata.product_log_id | Valeur du champ ref_uid |
s_action | read_only_udm.metadata.description | Valeur du champ s_action |
sent_bytes | read_only_udm.network.sent_bytes | Valeur du champ "sent_bytes" convertie en entier |
severity_id | read_only_udm.security_result.severity | Si severity_id est défini sur 1 ou 2, read_only_udm.security_result.severity est défini sur LOW . Si severity_id est défini sur 3 ou 4, read_only_udm.security_result.severity est défini sur MEDIUM . Si severity_id est défini sur 5 ou 6, read_only_udm.security_result.severity est défini sur HIGH . |
supplier_country | read_only_udm.principal.location.country_or_region | Valeur du champ "supplier_country" |
target_ip | read_only_udm.target.ip, read_only_udm.target.asset.ip | Valeur du champ "target_ip" |
user.full_name | read_only_udm.principal.user.user_display_name | Valeur du champ user.full_name |
nom.utilisateur | read_only_udm.principal.user.user_display_name | Valeur du champ "user.name" |
user_name | read_only_udm.principal.user.user_display_name | Valeur du champ user_name |
uuid | read_only_udm.metadata.product_log_id | Valeur du champ "uuid" |
date | read_only_udm.metadata.event_timestamp | Valeur du champ "when" convertie en code temporel |
read_only_udm.metadata.event_type | Défini sur NETWORK_UNCATEGORIZED si le nom d'hôte est vide et que connection.dst_ip n'est pas vide. Défini sur SCAN_NETWORK si le nom d'hôte n'est pas vide. Définie sur NETWORK_CONNECTION si has_principal et has_target sont true . Définie sur STATUS_UPDATE si has_principal est true et has_target est false . Définie sur GENERIC_EVENT si has_principal et has_target sont false |
|
read_only_udm.metadata.log_type | Toujours défini sur SYMANTEC_WSS |
|
read_only_udm.metadata.vendor_name | Toujours défini sur SYMANTEC |
|
read_only_udm.security_result.action | Définissez sur ALLOW si product_data.sc-filter_result est OBSERVED ou PROXIED . Définie sur BLOCK si product_data.sc-filter_result est DENIED |
|
read_only_udm.security_result.action_details | Valeur du champ product_data.sc-filter_result | |
read_only_udm.target.resource.type | Définissez sur DEVICE si product_data.x-client-device-id n'est pas vide. |
Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.