Collecter les journaux TeamViewer
Ce document explique comment ingérer des journaux TeamViewer dans Google Security Operations à l'aide d'Amazon S3. L'analyseur extrait les événements d'audit des journaux au format JSON. Il parcourt les détails des événements, mappe des propriétés spécifiques aux champs du modèle de données unifié (UDM), gère les informations sur les participants et les présentateurs, et catégorise les événements en fonction de l'activité des utilisateurs. L'analyseur effectue également des transformations de données, comme la fusion des libellés et la conversion des codes temporels dans un format standardisé.
Avant de commencer
Assurez-vous de remplir les conditions suivantes :
- Une instance Google SecOps.
- Accès privilégié à TeamViewer.
- Accès privilégié à AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge).
Obtenir les conditions préalables de TeamViewer
- Connectez-vous à la console de gestion TeamViewer en tant qu'administrateur.
- Accédez à Mon profil > Applications.
- Cliquez sur Créer l'application.
- Fournissez les informations de configuration suivantes :
- Nom de l'application : saisissez un nom descriptif (par exemple,
Google SecOps Integration
). - Description : saisissez une description de l'application.
- Autorisations : sélectionnez les autorisations d'accès aux journaux d'audit.
- Nom de l'application : saisissez un nom descriptif (par exemple,
- Cliquez sur Créer et enregistrez les identifiants API générés dans un emplacement sécurisé.
- Notez l'URL de base de l'API TeamViewer (par exemple,
https://webapi.teamviewer.com/api/v1
). - Copiez et enregistrez les informations suivantes dans un emplacement sécurisé :
- CLIENT_ID
- CLIENT_SECRET
- API_BASE_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,
teamviewer-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 une balise 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
teamviewer-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:::teamviewer-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::teamviewer-logs/teamviewer/audit/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
TeamViewerToS3Role
, 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 teamviewer_to_s3
Durée d'exécution Python 3.13 Architecture x86_64 Rôle d'exécution TeamViewerToS3Role
Une fois la fonction créée, ouvrez l'onglet Code, supprimez le stub et collez le code suivant (
teamviewer_to_s3.py
).#!/usr/bin/env python3 # Lambda: Pull TeamViewer audit logs and store raw JSON payloads to S3 # - Time window via {FROM}/{TO} placeholders (UTC ISO8601), URL-encoded. # - Preserves vendor-native JSON format for audit and session data. # - Retries with exponential backoff; unique S3 keys to avoid overwrites. import os, json, time, uuid, urllib.parse 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", "teamviewer/audit/") STATE_KEY = os.environ.get("STATE_KEY", "teamviewer/audit/state.json") WINDOW_SEC = int(os.environ.get("WINDOW_SECONDS", "3600")) # default 1h HTTP_TIMEOUT= int(os.environ.get("HTTP_TIMEOUT", "60")) API_BASE_URL = os.environ["API_BASE_URL"] CLIENT_ID = os.environ["CLIENT_ID"] CLIENT_SECRET = os.environ["CLIENT_SECRET"] MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3")) USER_AGENT = os.environ.get("USER_AGENT", "teamviewer-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 _iso(ts: float) -> str: return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts)) def _get_access_token() -> str: # OAuth2 Client Credentials flow for TeamViewer API token_url = f"{API_BASE_URL.rstrip('/')}/oauth2/token" data = urllib.parse.urlencode({ 'grant_type': 'client_credentials', 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET }).encode('utf-8') req = Request(token_url, data=data, method="POST") req.add_header("Content-Type", "application/x-www-form-urlencoded") req.add_header("User-Agent", USER_AGENT) with urlopen(req, timeout=HTTP_TIMEOUT) as r: response = json.loads(r.read()) return response["access_token"] def _build_audit_url(from_ts: float, to_ts: float, access_token: str) -> str: # Build URL for TeamViewer audit API endpoint base_endpoint = f"{API_BASE_URL.rstrip('/')}/reports/connections" params = { "from_date": _iso(from_ts), "to_date": _iso(to_ts) } query_string = urllib.parse.urlencode(params) return f"{base_endpoint}?{query_string}" def _fetch_audit_data(url: str, access_token: str) -> tuple[bytes, str]: attempt = 0 while True: req = Request(url, method="GET") req.add_header("User-Agent", USER_AGENT) req.add_header("Authorization", f"Bearer {access_token}") req.add_header("Accept", "application/json") try: with urlopen(req, timeout=HTTP_TIMEOUT) as r: return r.read(), (r.headers.get("Content-Type") or "application/json") 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 _put_audit_data(blob: bytes, content_type: str, from_ts: float, to_ts: float) -> str: # Create unique S3 key for audit data ts_path = time.strftime("%Y/%m/%d", time.gmtime(to_ts)) uniq = f"{int(time.time()*1e6)}_{uuid.uuid4().hex[:8]}" key = f"{S3_PREFIX}{ts_path}/teamviewer_audit_{int(from_ts)}_{int(to_ts)}_{uniq}.json" s3.put_object( Bucket=S3_BUCKET, Key=key, Body=blob, ContentType=content_type, Metadata={ 'source': 'teamviewer-audit', 'from_timestamp': str(int(from_ts)), 'to_timestamp': str(int(to_ts)) } ) 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 # Get OAuth2 access token access_token = _get_access_token() url = _build_audit_url(from_ts, to_ts, access_token) print(f"Fetching TeamViewer audit data from: {url}") blob, ctype = _fetch_audit_data(url, access_token) # Validate that we received valid JSON data try: audit_data = json.loads(blob) print(f"Successfully retrieved {len(audit_data.get('records', []))} audit records") except json.JSONDecodeError as e: print(f"Warning: Invalid JSON received: {e}") key = _put_audit_data(blob, ctype, 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": ctype, "from_timestamp": from_ts, "to_timestamp": to_ts } } 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 valeurs d'exemple par les vôtres.
Variables d'environnement
Clé Exemple de valeur S3_BUCKET
teamviewer-logs
S3_PREFIX
teamviewer/audit/
STATE_KEY
teamviewer/audit/state.json
WINDOW_SECONDS
3600
HTTP_TIMEOUT
60
MAX_RETRIES
3
USER_AGENT
teamviewer-to-s3/1.0
API_BASE_URL
https://webapi.teamviewer.com/api/v1
CLIENT_ID
your-client-id
(à partir de l'étape 2)CLIENT_SECRET
your-client-secret
(à partir de l'étape 2)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
teamviewer_to_s3
. - Nom :
teamviewer-audit-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
- Accédez à la console AWS> IAM> Utilisateurs> Ajouter des 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:::teamviewer-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::teamviewer-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 TeamViewer
- 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,
TeamViewer logs
). - Sélectionnez Amazon S3 V2 comme type de source.
- Sélectionnez TeamViewer comme Type de journal.
- Cliquez sur Suivant.
- Spécifiez les valeurs des paramètres d'entrée suivants :
- URI S3 :
s3://teamviewer-logs/teamviewer/audit/
- 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 |
---|---|---|
AffectedItem |
metadata.product_log_id |
La valeur de AffectedItem du journal brut est directement mappée sur ce champ UDM. |
EventDetails.NewValue |
principal.resource.attribute.labels.value |
Si PropertyName contient (server) , NewValue est utilisé comme valeur d'un libellé dans principal.resource.attribute.labels . |
EventDetails.NewValue |
principal.user.user_display_name |
Si PropertyName est défini sur Name of participant , NewValue est utilisé comme nom à afficher de l'utilisateur pour le principal. |
EventDetails.NewValue |
principal.user.userid |
Si PropertyName est défini sur ID of participant , NewValue est utilisé comme ID utilisateur pour le principal. |
EventDetails.NewValue |
security_result.about.labels.value |
Pour toutes les autres valeurs PropertyName (à l'exception de celles gérées par des conditions spécifiques), NewValue est utilisé comme valeur d'un libellé dans le tableau security_result.about.labels . |
EventDetails.NewValue |
target.file.full_path |
Si PropertyName est défini sur Source file , NewValue est utilisé comme chemin d'accès complet au fichier cible. |
EventDetails.NewValue |
target.resource.attribute.labels.value |
Si PropertyName contient (client) , NewValue est utilisé comme valeur d'un libellé dans target.resource.attribute.labels . |
EventDetails.NewValue |
target.user.user_display_name |
Si PropertyName est Name of presenter , NewValue est analysé. Si la valeur est un entier, elle est ignorée. Sinon, il est utilisé comme nom à afficher de l'utilisateur pour la cible. |
EventDetails.NewValue |
target.user.userid |
Si PropertyName est défini sur ID of presenter , NewValue est utilisé comme ID utilisateur pour la cible. |
EventDetails.PropertyName |
principal.resource.attribute.labels.key |
Si PropertyName contient (server) , PropertyName est utilisé comme clé d'un libellé dans principal.resource.attribute.labels . |
EventDetails.PropertyName |
security_result.about.labels.key |
Pour toutes les autres valeurs PropertyName (à l'exception de celles gérées par des conditions spécifiques), PropertyName est utilisé comme clé d'un libellé dans le tableau security_result.about.labels . |
EventDetails.PropertyName |
target.resource.attribute.labels.key |
Si PropertyName contient (client) , PropertyName est utilisé comme clé d'un libellé dans target.resource.attribute.labels . |
EventName |
metadata.product_event_type |
La valeur de EventName du journal brut est directement mappée sur ce champ UDM. |
Timestamp |
metadata.event_timestamp |
La valeur de Timestamp du journal brut est analysée et utilisée comme code temporel de l'événement dans les métadonnées. Définie sur USER_UNCATEGORIZED si src_user (dérivé de ID of participant ) n'est pas vide, sinon définie sur USER_RESOURCE_ACCESS . Codé en dur sur TEAMVIEWER . Codé en dur sur TEAMVIEWER . Codé en dur sur TEAMVIEWER . |
Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.