TeamViewer のログを収集する
このドキュメントでは、Amazon S3 を使用して TeamViewer ログを Google Security Operations に取り込む方法について説明します。パーサーは、JSON 形式のログから監査イベントを抽出します。イベントの詳細を反復処理し、特定のプロパティを統合データモデル(UDM)フィールドにマッピングし、参加者と発表者の情報を処理し、ユーザー アクティビティに基づいてイベントを分類します。また、パーサーは、ラベルの統合やタイムスタンプの標準形式への変換などのデータ変換も行います。
始める前に
次の前提条件を満たしていることを確認してください。
- Google SecOps インスタンス。
- TeamViewer への特権アクセス。
- AWS(S3、Identity and Access Management(IAM)、Lambda、EventBridge)への特権アクセス。
TeamViewer の前提条件を取得する
- 管理者として TeamViewer Management Console にログインします。
- [マイ プロフィール> アプリ] に移動します。
- [アプリを作成] をクリックします。
- 次の構成の詳細を入力します。
- アプリ名: わかりやすい名前を入力します(例: Google SecOps Integration)。
- 説明: アプリの説明を入力します。
- 権限: 監査ログへのアクセス権限を選択します。
 
- アプリ名: わかりやすい名前を入力します(例: 
- [作成] をクリックし、生成された API 認証情報を安全な場所に保存します。
- TeamViewer API ベース URL(例: https://webapi.teamviewer.com/api/v1)を記録します。
- 次の詳細をコピーして安全な場所に保存します。
- CLIENT_ID
- CLIENT_SECRET
- API_BASE_URL
 
Google SecOps 用に AWS S3 バケットと IAM を構成する
- バケットの作成のユーザーガイドに沿って、Amazon S3 バケットを作成します。
- 後で参照できるように、バケットの名前とリージョンを保存します(例: teamviewer-logs)。
- IAM ユーザーの作成のユーザーガイドに沿って、ユーザーを作成します。
- 作成したユーザーを選択します。
- [セキュリティ認証情報] タブを選択します。
- [アクセスキー] セクションで [アクセスキーを作成] をクリックします。
- [ユースケース] として [サードパーティ サービス] を選択します。
- [次へ] をクリックします。
- 省略可: 説明タグを追加します。
- [アクセスキーを作成] をクリックします。
- [CSV ファイルをダウンロード] をクリックして、[アクセスキー] と [シークレット アクセスキー] を保存し、今後の参照に備えます。
- [完了] をクリックします。
- [権限] タブを選択します。
- [権限ポリシー] セクションの [権限を追加] をクリックします。
- [権限を追加] を選択します。
- [ポリシーを直接アタッチする] を選択します。
- AmazonS3FullAccess ポリシーを検索します。
- ポリシーを選択します。
- [次へ] をクリックします。
- [権限を追加] をクリックします。
S3 アップロードの IAM ポリシーとロールを構成する
- AWS コンソールで、[IAM] > [ポリシー] に移動します。
- [ポリシーを作成> [JSON] タブ] をクリックします。
- 次のポリシーをコピーして貼り付けます。
- ポリシー JSON(別のバケット名を入力した場合は - teamviewer-logsを置き換えます):- { "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" } ] }
- [次へ] > [ポリシーを作成] をクリックします。 
- [IAM] > [ロール] > [ロールの作成] > [AWS サービス] > [Lambda] に移動します。 
- 新しく作成したポリシーを関連付けます。 
- ロールに「 - TeamViewerToS3Role」という名前を付けて、[ロールを作成] をクリックします。
Lambda 関数を作成する
- AWS コンソールで、[Lambda] > [Functions] > [Create function] に移動します。
- [Author from scratch] をクリックします。
- 次の構成情報を提供してください。 - 設定 - 値 - 名前 - teamviewer_to_s3- ランタイム - Python 3.13 - アーキテクチャ - x86_64 - 実行ロール - TeamViewerToS3Role
- 関数を作成したら、[コード] タブを開き、スタブを削除して次のコード( - 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())
- [構成] > [環境変数] に移動します。 
- [編集>新しい環境変数を追加] をクリックします。 
- 次の表に示す環境変数を入力します。例の値を実際の値に置き換えてください。 - 環境変数 - キー - 値の例 - 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(ステップ 2 から)- CLIENT_SECRET- your-client-secret(ステップ 2 から)
- 関数が作成されたら、そのページにとどまるか、[Lambda] > [関数] > [your-function] を開きます。 
- [CONFIGURATION] タブを選択します。 
- [全般設定] パネルで、[編集] をクリックします。 
- [Timeout] を [5 minutes (300 seconds)] に変更し、[Save] をクリックします。 
EventBridge スケジュールを作成する
- [Amazon EventBridge] > [Scheduler] > [スケジュールの作成] に移動します。
- 次の構成の詳細を入力します。
- 定期的なスケジュール: レート(1 hour)。
- ターゲット: Lambda 関数 teamviewer_to_s3。
- 名前: teamviewer-audit-1h
 
- 定期的なスケジュール: レート(
- [スケジュールを作成] をクリックします。
(省略可)Google SecOps の読み取り専用 IAM ユーザーと鍵を作成する
- AWS コンソール > IAM > ユーザー > ユーザーを追加 に移動します。
- [ユーザーを追加] をクリックします。
- 次の構成の詳細を入力します。
- ユーザー: 「secops-reader」と入力します。
- アクセスの種類: [アクセスキー - プログラムによるアクセス] を選択します。
 
- ユーザー: 「
- [ユーザーを作成] をクリックします。
- 最小限の読み取りポリシー(カスタム)を関連付ける: [ユーザー] > [secops-reader] > [権限] > [権限を追加] > [ポリシーを直接関連付ける] > [ポリシーを作成]。
- 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" } ] }
- 名前 = - secops-reader-policy。
- [ポリシーを作成> 検索/選択> 次へ> 権限を追加] をクリックします。 
- secops-readerのアクセスキーを作成します。[セキュリティ認証情報] > [アクセスキー] に移動します。
- [アクセスキーを作成] をクリックします。 
- CSVをダウンロードします。(これらの値はフィードに貼り付けます)。
TeamViewer のログを取り込むように Google SecOps でフィードを構成する
- [SIEM 設定] > [フィード] に移動します。
- [+ 新しいフィードを追加] をクリックします。
- [フィード名] フィールドに、フィードの名前を入力します(例: TeamViewer logs)。
- [ソースタイプ] として [Amazon S3 V2] を選択します。
- [ログタイプ] として [TeamViewer] を選択します。
- [次へ] をクリックします。
- 次の入力パラメータの値を指定します。
- S3 URI: s3://teamviewer-logs/teamviewer/audit/
- Source deletion options: 必要に応じて削除オプションを選択します。
- ファイルの最大経過日数: 指定した日数以内に変更されたファイルを含めます。デフォルトは 180 日です。
- アクセスキー ID: S3 バケットにアクセスできるユーザー アクセスキー。
- シークレット アクセスキー: S3 バケットにアクセスできるユーザーのシークレット キー。
- アセットの名前空間: アセットの名前空間。
- Ingestion labels: このフィードのイベントに適用されるラベル。
 
- S3 URI: 
- [次へ] をクリックします。
- [Finalize] 画面で新しいフィードの設定を確認し、[送信] をクリックします。
UDM マッピング テーブル
| ログフィールド | UDM マッピング | ロジック | 
|---|---|---|
| AffectedItem | metadata.product_log_id | 未加工ログの AffectedItemの値は、この UDM フィールドに直接マッピングされます。 | 
| EventDetails.NewValue | principal.resource.attribute.labels.value | PropertyNameに(server)が含まれている場合、NewValueはprincipal.resource.attribute.labelsのラベルの値として使用されます。 | 
| EventDetails.NewValue | principal.user.user_display_name | PropertyNameがName of participantの場合、NewValueはプリンシパルのユーザー表示名として使用されます。 | 
| EventDetails.NewValue | principal.user.userid | PropertyNameがID of participantの場合、NewValueはプリンシパルのユーザー ID として使用されます。 | 
| EventDetails.NewValue | security_result.about.labels.value | 他のすべての PropertyName値(特定の条件で処理される値を除く)の場合、NewValueはsecurity_result.about.labels配列内のラベルの値として使用されます。 | 
| EventDetails.NewValue | target.file.full_path | PropertyNameがSource fileの場合、NewValueはターゲット ファイルのフルパスとして使用されます。 | 
| EventDetails.NewValue | target.resource.attribute.labels.value | PropertyNameに(client)が含まれている場合、NewValueはtarget.resource.attribute.labelsのラベルの値として使用されます。 | 
| EventDetails.NewValue | target.user.user_display_name | PropertyNameがName of presenterの場合、NewValueが解析されます。整数である場合は破棄されます。それ以外の場合は、ターゲットのユーザー表示名として使用されます。 | 
| EventDetails.NewValue | target.user.userid | PropertyNameがID of presenterの場合、NewValueはターゲットのユーザー ID として使用されます。 | 
| EventDetails.PropertyName | principal.resource.attribute.labels.key | PropertyNameに(server)が含まれている場合、PropertyNameはprincipal.resource.attribute.labelsのラベルのキーとして使用されます。 | 
| EventDetails.PropertyName | security_result.about.labels.key | 他のすべての PropertyName値(特定の条件で処理されるものを除く)では、PropertyNameはsecurity_result.about.labels配列内のラベルのキーとして使用されます。 | 
| EventDetails.PropertyName | target.resource.attribute.labels.key | PropertyNameに(client)が含まれている場合、PropertyNameはtarget.resource.attribute.labelsのラベルのキーとして使用されます。 | 
| EventName | metadata.product_event_type | 未加工ログの EventNameの値は、この UDM フィールドに直接マッピングされます。 | 
| Timestamp | metadata.event_timestamp | 未加工ログの Timestampの値が解析され、メタデータのイベント タイムスタンプとして使用されます。src_user(ID of participantから派生)が空でない場合はUSER_UNCATEGORIZEDに設定し、それ以外の場合はUSER_RESOURCE_ACCESSに設定します。TEAMVIEWERにハードコードされています。TEAMVIEWERにハードコードされています。TEAMVIEWERにハードコードされています。 | 
さらにサポートが必要な場合 コミュニティ メンバーや Google SecOps のプロフェッショナルから回答を得ることができます。