Rippling 활동 로그 수집
이 문서에서는 Amazon S3를 사용하여 Rippling 활동 로그를 Google Security Operations에 수집하는 방법을 설명합니다.
시작하기 전에
다음 기본 요건이 충족되었는지 확인합니다.
- Google SecOps 인스턴스입니다.
- Rippling에 대한 권한 있는 액세스 (회사 활동에 액세스할 수 있는 API 토큰)
- AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge)에 대한 권한 있는 액세스
Rippling 기본 요건 가져오기
- Rippling Admin에 로그인합니다.
- 검색 > API 토큰을 엽니다.
대체 경로: 설정 > 회사 설정 > API 토큰 - API 토큰 만들기를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 이름: 고유하고 의미 있는 이름을 입력합니다 (예:
Google SecOps S3 Export
). - API 버전: 기본 API (v1)
- 범위/권한:
company:activity:read
를 사용 설정합니다 (회사 활동에 필요).
- 이름: 고유하고 의미 있는 이름을 입력합니다 (예:
- 만들기를 클릭하고 토큰 값을 안전한 위치에 저장합니다. (이를 베어러 토큰으로 사용합니다.)
Google SecOps용 AWS S3 버킷 및 IAM 구성
- 이 사용자 가이드(버킷 만들기)에 따라 Amazon S3 버킷을 만듭니다.
- 나중에 참조할 수 있도록 버킷 이름과 리전을 저장합니다 (예:
rippling-activity-logs
). - 이 사용자 가이드(IAM 사용자 만들기)에 따라 사용자를 만듭니다.
- 생성된 사용자를 선택합니다.
- 보안 사용자 인증 정보 탭을 선택합니다.
- 액세스 키 섹션에서 액세스 키 만들기를 클릭합니다.
- 사용 사례로 서드 파티 서비스를 선택합니다.
- 다음을 클릭합니다.
- 선택사항: 설명 태그를 추가합니다.
- 액세스 키 만들기를 클릭합니다.
- CSV 파일 다운로드를 클릭하여 향후 참조할 수 있도록 액세스 키와 비밀 액세스 키를 저장합니다.
- 완료를 클릭합니다.
- 권한 탭을 선택합니다.
- 권한 정책 섹션에서 권한 추가를 클릭합니다.
- 권한 추가를 선택합니다.
- 정책 직접 연결을 선택합니다.
- AmazonS3FullAccess 정책을 검색합니다.
- 정책을 선택합니다.
- 다음을 클릭합니다.
- 권한 추가를 클릭합니다.
S3 업로드용 IAM 정책 및 역할 구성
- AWS 콘솔에서 IAM > 정책으로 이동합니다.
- 정책 만들기 > JSON 탭을 클릭합니다.
- 다음 정책을 복사하여 붙여넣습니다.
정책 JSON (다른 버킷 또는 접두사를 입력한 경우 값을 바꿈)
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::rippling-activity-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::rippling-activity-logs/rippling/activity/state.json" } ] } ````
다음 > 정책 만들기를 클릭합니다.
IAM > 역할 > 역할 생성 > AWS 서비스 > Lambda로 이동합니다.
새로 만든 정책을 연결합니다.
역할 이름을
WriteRipplingToS3Role
로 지정하고 역할 만들기를 클릭합니다.
Lambda 함수 만들기
- AWS 콘솔에서 Lambda > 함수 > 함수 만들기로 이동합니다.
- 처음부터 작성을 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 이름 rippling_activity_to_s3
런타임 Python 3.13 아키텍처 x86_64 실행 역할 WriteRipplingToS3Role
함수가 생성되면 코드 탭을 열고 스텁을 삭제한 후 다음 코드 (
rippling_activity_to_s3.py
)를 붙여넣습니다.#!/usr/bin/env python3 # Lambda: Pull Rippling Company Activity logs to S3 (raw JSON, no transforms) import os, json, time, urllib.parse from urllib.request import Request, urlopen from datetime import datetime, timezone, timedelta import boto3 API_TOKEN = os.environ["RIPPLING_API_TOKEN"] ACTIVITY_URL = os.environ.get("RIPPLING_ACTIVITY_URL", "https://api.rippling.com/platform/api/company_activity") S3_BUCKET = os.environ["S3_BUCKET"] S3_PREFIX = os.environ.get("S3_PREFIX", "rippling/activity/") STATE_KEY = os.environ.get("STATE_KEY", "rippling/activity/state.json") LIMIT = int(os.environ.get("LIMIT", "1000")) MAX_PAGES = int(os.environ.get("MAX_PAGES", "10")) LOOKBACK_MINUTES = int(os.environ.get("LOOKBACK_MINUTES", "60")) END_LAG_SECONDS = int(os.environ.get("END_LAG_SECONDS", "120")) s3 = boto3.client("s3") def _headers(): return {"Authorization": f"Bearer {API_TOKEN}", "Accept": "application/json"} def _get_state(): try: obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY) j = json.loads(obj["Body"].read()) return {"since": j.get("since"), "next": j.get("next")} except Exception: return {"since": None, "next": None} def _put_state(since_iso, next_cursor): body = json.dumps({"since": since_iso, "next": next_cursor}, separators=(",", ":")).encode("utf-8") s3.put_object(Bucket=S3_BUCKET, Key=STATE_KEY, Body=body) def _get(url): req = Request(url, method="GET") for k, v in _headers().items(): req.add_header(k, v) with urlopen(req, timeout=60) as r: return json.loads(r.read().decode("utf-8")) def _build_url(base, params): qs = urllib.parse.urlencode(params) return f"{base}?{qs}" if qs else base def _parse_iso(ts): if ts.endswith("Z"): ts = ts[:-1] + "+00:00" return datetime.fromisoformat(ts) def _iso_from_epoch(sec): return datetime.fromtimestamp(sec, tz=timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") def _write(payload, run_ts_iso, page_index, source="company_activity"): day_path = _parse_iso(run_ts_iso).strftime("%Y/%m/%d") key = f"{S3_PREFIX.strip('/')}/{day_path}/{run_ts_iso.replace(':','').replace('-','')}-page{page_index:05d}-{source}.json" s3.put_object(Bucket=S3_BUCKET, Key=key, Body=json.dumps(payload, separators=(",", ":")).encode("utf-8")) return key def lambda_handler(event=None, context=None): state = _get_state() run_end = datetime.now(timezone.utc) - timedelta(seconds=END_LAG_SECONDS) end_iso = run_end.replace(microsecond=0).isoformat().replace("+00:00", "Z") since_iso = state["since"] next_cursor = state["next"] if since_iso is None: since_iso = _iso_from_epoch(time.time() - LOOKBACK_MINUTES * 60) else: try: since_iso = (_parse_iso(since_iso) + timedelta(seconds=1)).replace(microsecond=0).isoformat().replace("+00:00", "Z") except Exception: since_iso = _iso_from_epoch(time.time() - LOOKBACK_MINUTES * 60) run_ts_iso = end_iso pages = 0 total = 0 newest_ts = None pending_next = None while pages < MAX_PAGES: params = {"limit": str(LIMIT)} if next_cursor: params["next"] = next_cursor else: params["startDate"] = since_iso params["endDate"] = end_iso url = _build_url(ACTIVITY_URL, params) data = _get(url) _write(data, run_ts_iso, pages) events = data.get("events") or data.get("items") or data.get("data") or [] total += len(events) if isinstance(events, list) else 0 if isinstance(events, list): for ev in events: t = ev.get("timestamp") or ev.get("time") or ev.get("event_time") if isinstance(t, str): try: dt_ts = _parse_iso(t) if newest_ts is None or dt_ts > newest_ts: newest_ts = dt_ts except Exception: pass nxt = data.get("next") or data.get("next_cursor") or None pages += 1 if nxt: next_cursor = nxt pending_next = nxt continue else: pending_next = None break new_since_iso = (newest_ts or run_end).replace(microsecond=0).isoformat().replace("+00:00", "Z") _put_state(new_since_iso, pending_next) return {"ok": True, "pages": pages, "events": total, "since": new_since_iso, "next": pending_next}
구성 > 환경 변수로 이동합니다.
수정 > 새 환경 변수 추가를 클릭합니다.
다음 표에 제공된 환경 변수를 입력하고 예시 값을 실제 값으로 바꿉니다.
환경 변수
키 예시 값 S3_BUCKET
rippling-activity-logs
S3_PREFIX
rippling/activity/
STATE_KEY
rippling/activity/state.json
RIPPLING_API_TOKEN
your-api-token
RIPPLING_ACTIVITY_URL
https://api.rippling.com/platform/api/company_activity
LIMIT
1000
MAX_PAGES
10
LOOKBACK_MINUTES
60
END_LAG_SECONDS
120
함수가 생성되면 해당 페이지에 머무르거나 Lambda > 함수 > your-function을 엽니다.
구성 탭을 선택합니다.
일반 구성 패널에서 수정을 클릭합니다.
제한 시간을 5분 (300초)으로 변경하고 저장을 클릭합니다.
EventBridge 일정 만들기
- Amazon EventBridge > 스케줄러 > 일정 만들기로 이동합니다.
- 다음 구성 세부정보를 제공합니다.
- 반복 일정: 요금 (
1 hour
) - 타겟: Lambda 함수
rippling_activity_to_s3
- 이름:
rippling-activity-logs-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:::rippling-activity-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::rippling-activity-logs" } ] }
이름 =
secops-reader-policy
정책 만들기 > 검색/선택 > 다음 > 권한 추가를 클릭합니다.
secops-reader
의 액세스 키를 만듭니다(보안 사용자 인증 정보 > 액세스 키).액세스 키 만들기를 클릭합니다.
.CSV
을 다운로드합니다. (이 값은 피드에 붙여넣습니다.)
Rippling 활동 로그를 수집하도록 Google SecOps에서 피드 구성
- SIEM 설정> 피드로 이동합니다.
- + 새 피드 추가를 클릭합니다.
- 피드 이름 필드에 피드 이름을 입력합니다 (예:
Rippling Activity Logs
). - 소스 유형으로 Amazon S3 V2를 선택합니다.
- 로그 유형으로 Rippling 활동 로그를 선택합니다.
- 다음을 클릭합니다.
- 다음 입력 파라미터의 값을 지정합니다.
- S3 URI:
s3://rippling-activity-logs/rippling/activity/
- 소스 삭제 옵션: 환경설정에 따라 삭제 옵션을 선택합니다.
- 최대 파일 기간: 지난 일수 동안 수정된 파일을 포함합니다. 기본값은 180일입니다.
- 액세스 키 ID: S3 버킷에 대한 액세스 권한이 있는 사용자 액세스 키입니다.
- 보안 비밀 액세스 키: S3 버킷에 액세스할 수 있는 사용자 보안 비밀 키입니다.
- 애셋 네임스페이스:
rippling.activity
- 선택사항: 수집 라벨: 수집 라벨을 추가합니다.
- S3 URI:
- 다음을 클릭합니다.
- 확정 화면에서 새 피드 구성을 검토한 다음 제출을 클릭합니다.
도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가로부터 답변을 받으세요.