Tines 감사 로그 수집

다음에서 지원:

이 문서에서는 Amazon S3를 사용하여 Tines 감사 로그를 Google Security Operations에 수집하는 방법을 설명합니다.

시작하기 전에

다음 기본 요건이 충족되었는지 확인합니다.

  • Google SecOps 인스턴스입니다.
  • Tines에 대한 액세스 권한 관리
  • AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge)에 대한 권한 있는 액세스

Tines URL 가져오기

  1. 브라우저에서 테넌트의 Tines UI를 엽니다.
  2. 주소 표시줄에서 도메인을 복사합니다. 이 도메인은 TINES_BASE_URL로 사용됩니다.
    • 형식: https://<tenant-domain> (예: https://<tenant-domain>.tines.com)

나중에 사용할 값을 저장합니다.

  • TINES_BASE_URL(예: https://<domain>.tines.com)
  • TINES_API_KEY - 다음 단계에서 만드는 토큰
  1. 탐색 메뉴 > API 키로 이동합니다.
  2. + 새 키를 클릭합니다.
  3. 서비스 API 키를 선택합니다.
  4. 설명이 포함된 이름 (예: SecOps Audit Logs)을 입력합니다.
  5. 만들기를 클릭합니다.
  6. 생성된 토큰을 즉시 복사하고 안전하게 저장합니다. 이 토큰은 TINES_API_KEY로 사용됩니다.

옵션 2 - 개인 API 키 (서비스 키를 사용할 수 없는 경우)

  1. 탐색 메뉴 > API 키로 이동합니다.
  2. + 새 키를 클릭합니다.
  3. 개인 API 키를 선택합니다.
  4. 설명을 포함한 이름을 입력합니다.
  5. 만들기를 클릭합니다.
  6. 생성된 토큰을 복사하여 안전하게 저장합니다.

감사 로그 읽기 권한 부여

  1. 테넌트 소유자로 로그인합니다 (또는 테넌트 소유자에게 로그인하도록 요청).
  2. 설정 > 관리 > 사용자 관리로 이동합니다(또는 왼쪽 상단 메뉴에서 팀 이름을 클릭하고 사용자를 선택합니다).
  3. 서비스 API 키와 연결된 서비스 계정 사용자를 찾습니다 (API 키와 이름이 동일함).
    • 개인 API 키를 사용하는 경우 대신 사용자 계정을 찾으세요.
  4. 사용자를 클릭하여 프로필을 엽니다.
  5. 테넌트 권한 섹션에서 AUDIT_LOG_READ를 사용 설정합니다.
  6. 저장을 클릭합니다.

(선택사항) API 액세스 확인

  1. 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"
    
  2. 감사 로그 항목이 포함된 JSON 응답이 표시됩니다.

  3. UI에서 설정> 모니터링> 감사 로그로 이동하여 감사 로그가 있는지 확인할 수도 있습니다 (AUDIT_LOG_READ 권한 필요).

AWS S3 버킷 구성

  1. 이 사용자 가이드(버킷 만들기)에 따라 Amazon S3 버킷을 만듭니다.
  2. 나중에 참조할 수 있도록 버킷 이름리전을 저장합니다 (예: tines-audit-logs).

Lambda S3 업로드의 IAM 정책 및 역할 구성

  1. AWS 콘솔에서 IAM > 정책 > 정책 만들기 > JSON 탭으로 이동합니다.
  2. 다음 정책을 복사하여 붙여넣습니다.
  3. 정책 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"
        }
      ]
    }
    
  4. 다음 > 정책 만들기를 클릭합니다.

  5. 정책 이름을 TinesLambdaS3Policy로 지정합니다.

  6. IAM > 역할 > 역할 생성 > AWS 서비스 > Lambda로 이동합니다.

  7. 방금 만든 TinesLambdaS3Policy를 첨부합니다.

  8. 역할 이름을 TinesAuditToS3Role로 지정하고 역할 만들기를 클릭합니다.

Lambda 함수 만들기

  1. AWS 콘솔에서 Lambda > 함수 > 함수 만들기로 이동합니다.
  2. 처음부터 작성을 클릭합니다.
  3. 다음 구성 세부정보를 제공합니다.

    설정
    이름 tines_audit_to_s3
    런타임 Python 3.13
    아키텍처 x86_64
    실행 역할 TinesAuditToS3Role
  4. 함수가 생성되면 코드 탭을 열고 스텁을 삭제한 후 다음 코드 (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())
    
  5. 구성 > 환경 변수로 이동합니다.

  6. 수정 > 새 환경 변수 추가를 클릭합니다.

  7. 다음 표에 제공된 환경 변수를 입력하고 예시 값을 실제 값으로 바꿉니다.

    환경 변수

    예시 값
    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
  8. 함수가 생성되면 해당 페이지에 머무르거나 Lambda > 함수 > your-function을 엽니다.

  9. 구성 탭을 선택합니다.

  10. 일반 구성 패널에서 수정을 클릭합니다.

  11. 제한 시간5분 (300초)으로 변경하고 저장을 클릭합니다.

EventBridge 일정 만들기

  1. Amazon EventBridge > 스케줄러 > 일정 만들기로 이동합니다.
  2. 다음 구성 세부정보를 제공합니다.
    • 반복 일정: 요금 (1 hour)
    • 타겟: Lambda 함수 tines_audit_to_s3
    • 이름: tines-audit-1h.
  3. 일정 만들기를 클릭합니다.

Google SecOps용 읽기 전용 IAM 사용자 및 키 만들기

  1. AWS 콘솔에서 IAM > 사용자로 이동합니다.
  2. Add users를 클릭합니다.
  3. 다음 구성 세부정보를 제공합니다.
    • 사용자: secops-reader를 입력합니다.
    • 액세스 유형: 액세스 키 - 프로그래매틱 액세스를 선택합니다.
  4. 사용자 만들기를 클릭합니다.
  5. 최소 읽기 정책 (맞춤) 연결: 사용자 > secops-reader > 권한 > 권한 추가 > 정책 직접 연결 > 정책 만들기
  6. 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"
        }
      ]
    }
    
  7. 이름 = secops-reader-policy

  8. 정책 만들기 > 검색/선택 > 다음 > 권한 추가를 클릭합니다.

  9. secops-reader의 액세스 키를 만듭니다(보안 사용자 인증 정보 > 액세스 키).

  10. 액세스 키 만들기를 클릭합니다.

  11. .CSV을 다운로드합니다. (이 값은 피드에 붙여넣습니다.)

Tines 감사 로그를 수집하도록 Google SecOps에서 피드 구성

  1. SIEM 설정> 피드로 이동합니다.
  2. + 새 피드 추가를 클릭합니다.
  3. 피드 이름 필드에 피드 이름을 입력합니다 (예: Tines Audit Logs).
  4. 소스 유형으로 Amazon S3 V2를 선택합니다.
  5. 로그 유형으로 Tines를 선택합니다.
  6. 다음을 클릭합니다.
  7. 다음 입력 파라미터의 값을 지정합니다.
    • S3 URI: s3://tines-audit-logs/tines/audit/
    • 소스 삭제 옵션: 환경설정에 따라 삭제 옵션을 선택합니다.
    • 최대 파일 기간: 지난 일수 동안 수정된 파일을 포함합니다. 기본값은 180일입니다.
    • 액세스 키 ID: S3 버킷에 대한 액세스 권한이 있는 사용자 액세스 키입니다.
    • 보안 비밀 액세스 키: S3 버킷에 액세스할 수 있는 사용자 보안 비밀 키입니다.
    • 애셋 네임스페이스: 애셋 네임스페이스입니다.
    • 수집 라벨: 이 피드의 이벤트에 적용된 라벨입니다.
  8. 다음을 클릭합니다.
  9. 확정 화면에서 새 피드 구성을 검토한 다음 제출을 클릭합니다.

도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가로부터 답변을 받으세요.