收集 Tines 审核日志
本文档介绍了如何使用 Amazon S3 将 Tines 审核日志注入到 Google Security Operations。
准备工作
请确保满足以下前提条件:
- Google SecOps 实例。
- 对 Tines 的特权访问权限。
- 对 AWS(S3、Identity and Access Management (IAM)、Lambda、EventBridge)的特权访问权限。
获取 Tines 网址
- 在浏览器中,打开您租户的 Tines 界面。
- 从地址栏中复制网域,您将使用该网域作为
TINES_BASE_URL
。- 格式:
https://<tenant-domain>
(例如https://<tenant-domain>.tines.com
)。
- 格式:
创建 Tines 服务 API 密钥(推荐)或个人 API 密钥
要保存的值,以供后续步骤使用:
TINES_BASE_URL
- 例如,https://<domain>.tines.com
TINES_API_KEY
- 您将在后续步骤中创建的令牌
方式 1 - 服务 API 密钥(推荐)
- 依次前往导航菜单 > API 密钥。
- 点击 + 新密钥。
- 选择 Service API key。
- 输入一个描述性名称(例如
SecOps Audit Logs
)。 - 点击创建。
- 立即复制生成的令牌并妥善保存,您将使用它作为
TINES_API_KEY
。
方法 2 - 个人 API 密钥(如果服务密钥不可用)
- 依次前往导航菜单 > API 密钥。
- 点击 + 新密钥。
- 选择个人 API 密钥。
- 输入一个描述性名称。
- 点击创建。
复制生成的令牌并妥善保存。
授予“读取审核日志”权限
- 以租户所有者的身份登录(或请租户所有者执行此操作)。
- 依次前往设置 > 管理员 > 用户管理(或点击左上角菜单中的团队名称,然后选择用户)。
- 找到与您的服务 API 密钥关联的服务账号用户(其名称与您的 API 密钥相同)。
- 如果使用的是个人 API 密钥,请改为查找自己的用户账号。
- 点击相应用户以打开其个人资料。
- 在租户权限部分,启用 AUDIT_LOG_READ。
- 点击保存。
(可选)验证 API 访问权限
使用 curl 或任何 HTTP 客户端测试端点:
curl -X GET "https://<tenant-domain>/api/v1/audit_logs?per_page=1" \ -H "Authorization: Bearer <TINES_API_KEY>" \ -H "Content-Type: application/json"
您应该会收到包含审核日志条目的 JSON 响应。
您还可以在界面中前往设置 > 监控 > 审核日志(需要 AUDIT_LOG_READ 权限),以验证审核日志是否存在。
配置 AWS S3 存储桶
- 按照以下用户指南创建 Amazon S3 存储桶:创建存储桶
- 保存存储桶名称和区域以供日后参考(例如
tines-audit-logs
)。
为 Lambda S3 上传配置 IAM 政策和角色
- 在 AWS 控制台中,依次前往 IAM > 政策 > 创建政策 > JSON 标签页。
- 复制并粘贴以下政策。
政策 JSON(如果您输入了其他存储桶名称,请替换
tines-audit-logs
):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::tines-audit-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::tines-audit-logs/tines/audit/state.json" } ] }
依次点击下一步 > 创建政策。
将政策命名为
TinesLambdaS3Policy
。依次前往 IAM > 角色 > 创建角色 > AWS 服务 > Lambda。
附加您刚刚创建的
TinesLambdaS3Policy
。将角色命名为
TinesAuditToS3Role
,然后点击创建角色。
创建 Lambda 函数
- 在 AWS 控制台中,依次前往 Lambda > 函数 > 创建函数。
- 点击从头开始创作。
提供以下配置详细信息:
设置 值 名称 tines_audit_to_s3
运行时 Python 3.13 架构 x86_64 执行角色 TinesAuditToS3Role
创建函数后,打开 Code 标签页,删除桩代码并粘贴以下代码 (
tines_audit_to_s3.py
)。#!/usr/bin/env python3 # Lambda: Pull Tines Audit Logs to S3 (no transform) import os, json, time, urllib.parse from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError import boto3 S3_BUCKET = os.environ["S3_BUCKET"] S3_PREFIX = os.environ.get("S3_PREFIX", "tines/audit/") STATE_KEY = os.environ.get("STATE_KEY", "tines/audit/state.json") LOOKBACK_SEC = int(os.environ.get("LOOKBACK_SECONDS", "3600")) # default 1h PAGE_SIZE = int(os.environ.get("PAGE_SIZE", "500")) # Max is 500 for Tines MAX_PAGES = int(os.environ.get("MAX_PAGES", "20")) TIMEOUT = int(os.environ.get("HTTP_TIMEOUT", "60")) HTTP_RETRIES = int(os.environ.get("HTTP_RETRIES", "3")) TINES_BASE_URL = os.environ["TINES_BASE_URL"] TINES_API_KEY = os.environ["TINES_API_KEY"] s3 = boto3.client("s3") def _iso(ts: float) -> str: return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts)) def _load_state() -> dict: try: obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY) b = obj["Body"].read() return json.loads(b) if b else {} except Exception: return {} def _save_state(st: dict) -> None: s3.put_object( Bucket=S3_BUCKET, Key=STATE_KEY, Body=json.dumps(st, separators=(",", ":")).encode("utf-8"), ContentType="application/json", ) def _req(url: str) -> dict: attempt = 0 while True: try: req = Request(url, method="GET") req.add_header("Authorization", f"Bearer {TINES_API_KEY}") req.add_header("Accept", "application/json") req.add_header("Content-Type", "application/json") with urlopen(req, timeout=TIMEOUT) as r: data = r.read() return json.loads(data.decode("utf-8")) except HTTPError as e: if e.code in (429, 500, 502, 503, 504) and attempt < HTTP_RETRIES: retry_after = 1 + attempt try: retry_after = int(e.headers.get("Retry-After", retry_after)) except Exception: pass time.sleep(max(1, retry_after)) attempt += 1 continue raise except URLError: if attempt < HTTP_RETRIES: time.sleep(1 + attempt) attempt += 1 continue raise def _write(payload, page: int) -> str: ts = time.gmtime() key = f"{S3_PREFIX}{time.strftime('%Y/%m/%d/%H%M%S', ts)}-tines-audit-{page:05d}.json" s3.put_object( Bucket=S3_BUCKET, Key=key, Body=json.dumps(payload, separators=(",", ":")).encode("utf-8"), ContentType="application/json", ) return key def _extract_items(payload) -> list: if isinstance(payload, list): return payload if isinstance(payload, dict): audit_logs = payload.get("audit_logs") if isinstance(audit_logs, list): return audit_logs return [] def _extract_newest_ts(items: list, current: str | None) -> str | None: newest = current for it in items: # Use created_at as the timestamp field t = it.get("created_at") if isinstance(t, str) and (newest is None or t > newest): newest = t return newest def lambda_handler(event=None, context=None): st = _load_state() since = st.get("since") or _iso(time.time() - LOOKBACK_SEC) page = 1 pages = 0 total = 0 newest_ts = since while pages < MAX_PAGES: # Build URL with query parameters # Note: Tines audit logs API uses 'after' parameter for filtering base_url = f"{TINES_BASE_URL.rstrip('/')}/api/v1/audit_logs" params = { "after": since, # Filter for logs created after this timestamp "page": page, "per_page": PAGE_SIZE } url = f"{base_url}?{urllib.parse.urlencode(params)}" payload = _req(url) _write(payload, page) items = _extract_items(payload) total += len(items) newest_ts = _extract_newest_ts(items, newest_ts) pages += 1 # Check if there's a next page using meta.next_page_number meta = payload.get("meta") or {} next_page = meta.get("next_page_number") if not next_page: break page = next_page if newest_ts and newest_ts != since: st["since"] = newest_ts _save_state(st) return {"ok": True, "pages": pages, "items": total, "since": st.get("since")} if __name__ == "__main__": print(lambda_handler())
依次前往配置 > 环境变量。
依次点击修改 > 添加新的环境变量。
输入下表中提供的环境变量,并将示例值替换为您的值。
环境变量
键 示例值 S3_BUCKET
tines-audit-logs
S3_PREFIX
tines/audit/
STATE_KEY
tines/audit/state.json
TINES_BASE_URL
https://your-tenant.tines.com
TINES_API_KEY
your-tines-api-key
LOOKBACK_SECONDS
3600
PAGE_SIZE
500
MAX_PAGES
20
HTTP_TIMEOUT
60
HTTP_RETRIES
3
创建函数后,请停留在其页面上(或依次打开 Lambda > 函数 > 您的函数)。
选择配置标签页。
在常规配置面板中,点击修改。
将超时更改为 5 分钟(300 秒),然后点击保存。
创建 EventBridge 计划
- 依次前往 Amazon EventBridge > 调度器 > 创建调度。
- 提供以下配置详细信息:
- 周期性安排:费率 (
1 hour
)。 - 目标:您的 Lambda 函数
tines_audit_to_s3
。 - 名称:
tines-audit-1h
。
- 周期性安排:费率 (
- 点击创建时间表。
为 Google SecOps 创建只读 IAM 用户和密钥
- 在 AWS 控制台中,依次前往 IAM > 用户。
- 点击 Add users(添加用户)。
- 提供以下配置详细信息:
- 用户:输入
secops-reader
。 - 访问类型:选择访问密钥 - 以程序化方式访问。
- 用户:输入
- 点击创建用户。
- 附加最低限度的读取政策(自定义):依次选择用户 > secops-reader > 权限 > 添加权限 > 直接附加政策 > 创建政策。
JSON:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::tines-audit-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::tines-audit-logs" } ] }
名称 =
secops-reader-policy
。依次点击创建政策 > 搜索/选择 > 下一步 > 添加权限。
为
secops-reader
创建访问密钥:安全凭据 > 访问密钥。点击创建访问密钥。
下载
.CSV
。(您需要将这些值粘贴到 Feed 中)。
在 Google SecOps 中配置 Feed 以注入 Tines 审核日志
- 依次前往 SIEM 设置> Feed。
- 点击 + 添加新 Feed。
- 在Feed 名称字段中,输入 Feed 的名称(例如
Tines Audit Logs
)。 - 选择 Amazon S3 V2 作为来源类型。
- 选择 Tines 作为日志类型。
- 点击下一步。
- 为以下输入参数指定值:
- S3 URI:
s3://tines-audit-logs/tines/audit/
- 来源删除选项:根据您的偏好选择删除选项。
- 文件存在时间上限:包含在过去指定天数内修改的文件。默认值为 180 天。
- 访问密钥 ID:有权访问 S3 存储桶的用户访问密钥。
- 私有访问密钥:具有 S3 存储桶访问权限的用户私有密钥。
- 资产命名空间:资产命名空间。
- 注入标签:应用于此 Feed 中事件的标签。
- S3 URI:
- 点击下一步。
- 在最终确定界面中查看新的 Feed 配置,然后点击提交。
需要更多帮助?从社区成员和 Google SecOps 专业人士那里获得解答。