Mengumpulkan log TeamViewer
Dokumen ini menjelaskan cara menyerap log TeamViewer ke Google Security Operations menggunakan Amazon S3. Parser mengekstrak peristiwa audit dari log berformat JSON. Fungsi ini melakukan iterasi melalui detail acara, memetakan properti tertentu ke kolom Model Data Terpadu (UDM), menangani informasi peserta dan presenter, serta mengategorikan acara berdasarkan aktivitas pengguna. Parser juga melakukan transformasi data, seperti menggabungkan label dan mengonversi stempel waktu ke format standar.
Sebelum memulai
Pastikan Anda memenuhi prasyarat berikut:
- Instance Google SecOps.
- Akses istimewa ke TeamViewer.
- Akses istimewa ke AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge).
Mendapatkan prasyarat TeamViewer
- Login ke TeamViewer Management Console sebagai administrator.
- Buka Profil Saya > Aplikasi.
- Klik Buat aplikasi.
- Berikan detail konfigurasi berikut:
- Nama aplikasi: Masukkan nama deskriptif (misalnya,
Google SecOps Integration
). - Deskripsi: Masukkan deskripsi untuk aplikasi.
- Izin: Pilih izin untuk akses log audit.
- Nama aplikasi: Masukkan nama deskriptif (misalnya,
- Klik Buat dan simpan kredensial API yang dibuat di lokasi yang aman.
- Catat TeamViewer API Base URL Anda (misalnya,
https://webapi.teamviewer.com/api/v1
). - Salin dan simpan detail berikut di lokasi yang aman:
- CLIENT_ID
- CLIENT_SECRET
- API_BASE_URL
Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps
- Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket
- Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya,
teamviewer-logs
). - Buat Pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
- Pilih Pengguna yang dibuat.
- Pilih tab Kredensial keamanan.
- Klik Create Access Key di bagian Access Keys.
- Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
- Klik Berikutnya.
- Opsional: Tambahkan tag deskripsi.
- Klik Create access key.
- Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk referensi di masa mendatang.
- Klik Selesai.
- Pilih tab Permissions.
- Klik Tambahkan izin di bagian Kebijakan izin.
- Pilih Tambahkan izin.
- Pilih Lampirkan kebijakan secara langsung.
- Cari kebijakan AmazonS3FullAccess.
- Pilih kebijakan.
- Klik Berikutnya.
- Klik Add permissions.
Mengonfigurasi kebijakan dan peran IAM untuk upload S3
- Di konsol AWS, buka IAM > Policies.
- Klik Buat kebijakan > tab JSON.
- Salin dan tempel kebijakan berikut.
Policy JSON (ganti
teamviewer-logs
jika Anda memasukkan nama bucket yang berbeda):{ "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" } ] }
Klik Berikutnya > Buat kebijakan.
Buka IAM > Roles > Create role > AWS service > Lambda.
Lampirkan kebijakan yang baru dibuat.
Beri nama peran
TeamViewerToS3Role
, lalu klik Buat peran.
Buat fungsi Lambda
- Di Konsol AWS, buka Lambda > Functions > Create function.
- Klik Buat dari awal.
Berikan detail konfigurasi berikut:
Setelan Nilai Nama teamviewer_to_s3
Runtime Python 3.13 Arsitektur x86_64 Peran eksekusi TeamViewerToS3Role
Setelah fungsi dibuat, buka tab Code, hapus stub, dan tempelkan kode berikut (
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())
Buka Configuration > Environment variables.
Klik Edit > Tambahkan variabel lingkungan baru.
Masukkan variabel lingkungan yang diberikan dalam tabel berikut dengan mengganti nilai contoh dengan nilai Anda.
Variabel lingkungan
Kunci Nilai contoh 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
(dari langkah 2)CLIENT_SECRET
your-client-secret
(dari langkah 2)Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).
Pilih tab Configuration
Di panel General configuration, klik Edit.
Ubah Waktu Tunggu menjadi 5 menit (300 detik), lalu klik Simpan.
Membuat jadwal EventBridge
- Buka Amazon EventBridge > Scheduler > Create schedule.
- Berikan detail konfigurasi berikut:
- Jadwal berulang: Tarif (
1 hour
). - Target: Fungsi Lambda Anda
teamviewer_to_s3
. - Name:
teamviewer-audit-1h
.
- Jadwal berulang: Tarif (
- Klik Buat jadwal.
(Opsional) Buat pengguna dan kunci IAM hanya baca untuk Google SecOps
- Buka Konsol AWS > IAM > Pengguna > Tambahkan pengguna.
- Klik Add users.
- Berikan detail konfigurasi berikut:
- Pengguna: Masukkan
secops-reader
. - Jenis akses: Pilih Kunci akses – Akses terprogram.
- Pengguna: Masukkan
- Klik Buat pengguna.
- Lampirkan kebijakan baca minimal (kustom): Pengguna > secops-reader > Izin > Tambahkan izin > Lampirkan kebijakan secara langsung > Buat kebijakan.
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" } ] }
Nama =
secops-reader-policy
.Klik Buat kebijakan > cari/pilih > Berikutnya > Tambahkan izin.
Buat kunci akses untuk
secops-reader
: Kredensial keamanan > Kunci akses.Klik Create access key.
Download
CSV
. (Anda akan menempelkan nilai ini ke feed).
Mengonfigurasi feed di Google SecOps untuk menyerap log TeamViewer
- Buka Setelan SIEM > Feed.
- Klik + Tambahkan Feed Baru.
- Di kolom Nama feed, masukkan nama untuk feed (misalnya,
TeamViewer logs
). - Pilih Amazon S3 V2 sebagai Jenis sumber.
- Pilih TeamViewer sebagai Jenis log.
- Klik Berikutnya.
- Tentukan nilai untuk parameter input berikut:
- URI S3:
s3://teamviewer-logs/teamviewer/audit/
- Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
- Usia File Maksimum: Menyertakan file yang diubah dalam jumlah hari terakhir. Defaultnya adalah 180 hari.
- ID Kunci Akses: Kunci akses pengguna dengan akses ke bucket S3.
- Kunci Akses Rahasia: Kunci rahasia pengguna dengan akses ke bucket S3.
- Namespace aset: Namespace aset.
- Label penyerapan: Label yang diterapkan ke peristiwa dari feed ini.
- URI S3:
- Klik Berikutnya.
- Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.
Tabel pemetaan UDM
Kolom log | Pemetaan UDM | Logika |
---|---|---|
AffectedItem |
metadata.product_log_id |
Nilai AffectedItem dari log mentah dipetakan langsung ke kolom UDM ini. |
EventDetails.NewValue |
principal.resource.attribute.labels.value |
Jika PropertyName berisi (server) , NewValue digunakan sebagai nilai label di principal.resource.attribute.labels . |
EventDetails.NewValue |
principal.user.user_display_name |
Jika PropertyName adalah Name of participant , NewValue digunakan sebagai nama tampilan pengguna untuk prinsipal. |
EventDetails.NewValue |
principal.user.userid |
Jika PropertyName adalah ID of participant , NewValue digunakan sebagai ID pengguna untuk prinsipal. |
EventDetails.NewValue |
security_result.about.labels.value |
Untuk semua nilai PropertyName lainnya (kecuali yang ditangani oleh kondisi tertentu), NewValue digunakan sebagai nilai label dalam array security_result.about.labels . |
EventDetails.NewValue |
target.file.full_path |
Jika PropertyName adalah Source file , NewValue digunakan sebagai jalur lengkap untuk file target. |
EventDetails.NewValue |
target.resource.attribute.labels.value |
Jika PropertyName berisi (client) , NewValue digunakan sebagai nilai label di target.resource.attribute.labels . |
EventDetails.NewValue |
target.user.user_display_name |
Jika PropertyName adalah Name of presenter , NewValue akan diuraikan. Jika berupa bilangan bulat, nilai tersebut akan diabaikan. Jika tidak, nama tersebut akan digunakan sebagai nama tampilan pengguna untuk target. |
EventDetails.NewValue |
target.user.userid |
Jika PropertyName adalah ID of presenter , NewValue digunakan sebagai ID pengguna untuk target. |
EventDetails.PropertyName |
principal.resource.attribute.labels.key |
Jika PropertyName berisi (server) , PropertyName digunakan sebagai kunci label di principal.resource.attribute.labels . |
EventDetails.PropertyName |
security_result.about.labels.key |
Untuk semua nilai PropertyName lainnya (kecuali yang ditangani oleh kondisi tertentu), PropertyName digunakan sebagai kunci label dalam array security_result.about.labels . |
EventDetails.PropertyName |
target.resource.attribute.labels.key |
Jika PropertyName berisi (client) , PropertyName digunakan sebagai kunci label di target.resource.attribute.labels . |
EventName |
metadata.product_event_type |
Nilai EventName dari log mentah dipetakan langsung ke kolom UDM ini. |
Timestamp |
metadata.event_timestamp |
Nilai Timestamp dari log mentah diuraikan dan digunakan sebagai stempel waktu peristiwa dalam metadata. Ditetapkan ke USER_UNCATEGORIZED jika src_user (berasal dari ID of participant ) tidak kosong, jika tidak, ditetapkan ke USER_RESOURCE_ACCESS . Hardcode ke TEAMVIEWER . Hardcode ke TEAMVIEWER . Hardcode ke TEAMVIEWER . |
Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.