Harness IO 감사 로그 수집
이 문서에서는 Amazon S3를 사용하여 Harness IO 감사 로그를 Google Security Operations에 수집하는 방법을 설명합니다.
시작하기 전에
- Google SecOps 인스턴스
- Harness에 대한 권한 있는 액세스 (API 키 및 계정 ID)
- AWS (S3, IAM, Lambda, EventBridge)에 대한 권한 있는 액세스
개인 계정의 Harness API 키 및 계정 ID 가져오기
- Harness 웹 UI에 로그인합니다.
- 사용자 프로필> 내 API 키로 이동합니다.
- API 키를 선택합니다.
- API 키의 이름을 입력합니다.
- 저장을 클릭합니다.
- 새 API 키 아래에서 토큰을 선택합니다.
- 토큰 이름을 입력합니다.
- 토큰 생성을 클릭합니다.
- 토큰을 복사하여 안전한 위치에 저장합니다.
- 계정 ID (Harness URL 및 계정 설정에 표시됨)를 복사하여 저장합니다.
선택사항: 서비스 계정의 Harness API 키 및 계정 ID 가져오기
- Harness 웹 UI에 로그인합니다.
- 서비스 계정 만들기
- 계정 설정 > 액세스 제어로 이동합니다.
- 서비스 계정> API 키를 만들 서비스 계정을 선택합니다.
- API 키에서 API 키를 선택합니다.
- API 키의 이름을 입력합니다.
- 저장을 클릭합니다.
- 새 API 키에서 토큰을 선택합니다.
- 토큰 이름을 입력합니다.
- 토큰 생성을 클릭합니다.
- 토큰을 복사하여 안전한 위치에 저장합니다.
- 계정 ID (Harness URL 및 계정 설정에 표시됨)를 복사하여 저장합니다.
Google SecOps용 AWS S3 버킷 및 IAM 구성
- 이 사용자 가이드(버킷 만들기)에 따라 Amazon S3 버킷을 만듭니다.
- 나중에 참조할 수 있도록 버킷 이름과 리전을 저장합니다 (예:
harness-io
). - 이 사용자 가이드(IAM 사용자 만들기)에 따라 사용자를 만듭니다.
- 생성된 사용자를 선택합니다.
- 보안 사용자 인증 정보 탭을 선택합니다.
- 액세스 키 섹션에서 액세스 키 만들기를 클릭합니다.
- 사용 사례로 서드 파티 서비스를 선택합니다.
- 다음을 클릭합니다.
- 선택사항: 설명 태그를 추가합니다.
- 액세스 키 만들기를 클릭합니다.
- CSV 파일 다운로드를 클릭하여 나중에 사용할 수 있도록 액세스 키와 비밀 액세스 키를 저장합니다.
- 완료를 클릭합니다.
- 권한 탭을 선택합니다.
- 권한 정책 섹션에서 권한 추가를 클릭합니다.
- 권한 추가를 선택합니다.
- 정책 직접 연결을 선택합니다.
- AmazonS3FullAccess 정책을 검색하여 선택합니다.
- 다음을 클릭합니다.
- 권한 추가를 클릭합니다.
S3 업로드용 IAM 정책 및 역할 구성
- AWS 콘솔에서 IAM > 정책 > 정책 만들기 > JSON 탭으로 이동합니다.
다음 정책을 입력합니다.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutHarnessObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::harness-io/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::harness-io/harness/audit/state.json" } ] }
- 다른 버킷 이름을 입력한 경우
harness-io
을 바꿉니다.
- 다른 버킷 이름을 입력한 경우
다음 > 정책 만들기를 클릭합니다.
IAM > 역할 > 역할 생성 > AWS 서비스 > Lambda로 이동합니다.
새로 만든 정책을 연결합니다.
역할 이름을
WriteHarnessToS3Role
로 지정하고 역할 만들기를 클릭합니다.
Lambda 함수 만들기
- AWS 콘솔에서 Lambda > 함수 > 함수 만들기로 이동합니다.
- 처음부터 작성을 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 이름 harness_io_to_s3
런타임 Python 3.13 아키텍처 x86_64 실행 역할 WriteHarnessToS3Role
함수가 생성되면 코드 탭을 열고 스텁을 삭제한 후 다음 코드를 입력합니다 (
harness_io_to_s3.py
).#!/usr/bin/env python3 import os, json, time, urllib.parse from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError import boto3 API_BASE = os.environ.get("HARNESS_API_BASE", "https://app.harness.io").rstrip("/") ACCOUNT_ID = os.environ["HARNESS_ACCOUNT_ID"] API_KEY = os.environ["HARNESS_API_KEY"] # x-api-key token BUCKET = os.environ["S3_BUCKET"] PREFIX = os.environ.get("S3_PREFIX", "harness/audit/").strip("/") STATE_KEY = os.environ.get("STATE_KEY", "harness/audit/state.json") PAGE_SIZE = min(int(os.environ.get("PAGE_SIZE", "100")), 100) # <=100 START_MINUTES_BACK = int(os.environ.get("START_MINUTES_BACK", "60")) s3 = boto3.client("s3") HDRS = {"x-api-key": API_KEY, "Content-Type": "application/json", "Accept": "application/json"} def _read_state(): try: obj = s3.get_object(Bucket=BUCKET, Key=STATE_KEY) j = json.loads(obj["Body"].read()) return j.get("since"), j.get("pageToken") except Exception: return None, None def _write_state(since_ms: int, page_token: str | None): body = json.dumps({"since": since_ms, "pageToken": page_token}).encode("utf-8") s3.put_object(Bucket=BUCKET, Key=STATE_KEY, Body=body, ContentType="application/json") def _http_post(path: str, body: dict, query: dict, timeout: int = 60, max_retries: int = 5) -> dict: qs = urllib.parse.urlencode(query) url = f"{API_BASE}{path}?{qs}" data = json.dumps(body).encode("utf-8") attempt, backoff = 0, 1.0 while True: req = Request(url, data=data, method="POST") for k, v in HDRS.items(): req.add_header(k, v) try: with urlopen(req, timeout=timeout) as r: return json.loads(r.read().decode("utf-8")) except HTTPError as e: if (e.code == 429 or 500 <= e.code <= 599) and attempt < max_retries: time.sleep(backoff) attempt += 1 backoff *= 2 continue raise except URLError: if attempt < max_retries: time.sleep(backoff) attempt += 1 backoff *= 2 continue raise def _write_page(obj: dict, now: float, page_index: int) -> str: ts = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime(now)) key = f"{PREFIX}/{ts}-page{page_index:05d}.json" s3.put_object( Bucket=BUCKET, Key=key, Body=json.dumps(obj, separators=(",", ":")).encode("utf-8"), ContentType="application/json", ) return key def fetch_and_store(): now_s = time.time() since_ms, page_token = _read_state() if since_ms is None: since_ms = int((now_s - START_MINUTES_BACK * 60) * 1000) until_ms = int(now_s * 1000) page_index = 0 total = 0 while True: body = {"startTime": since_ms, "endTime": until_ms} query = {"accountIdentifier": ACCOUNT_ID, "pageSize": PAGE_SIZE} if page_token: query["pageToken"] = page_token else: query["pageIndex"] = page_index data = _http_post("/audit/api/audits/listV2", body, query) _write_page(data, now_s, page_index) entries = [] for key in ("data", "content", "response", "resource", "resources", "items"): if isinstance(data.get(key), list): entries = data[key] break total += len(entries) if isinstance(entries, list) else 0 next_token = ( data.get("pageToken") or (isinstance(data.get("meta"), dict) and data["meta"].get("pageToken")) or (isinstance(data.get("metadata"), dict) and data["metadata"].get("pageToken")) ) if next_token: page_token = next_token page_index += 1 continue if len(entries) < PAGE_SIZE: break page_index += 1 _write_state(until_ms, None) return {"pages": page_index + 1, "objects_estimate": total} def lambda_handler(event=None, context=None): return fetch_and_store() if __name__ == "__main__": print(lambda_handler())
구성 > 환경 변수 > 수정 > 새 환경 변수 추가로 이동합니다.
다음 환경 변수를 입력하고 값으로 바꿉니다.
키 예 S3_BUCKET
harness-io
S3_PREFIX
harness/audit/
STATE_KEY
harness/audit/state.json
HARNESS_ACCOUNT_ID
123456789
HARNESS_API_KEY
harness_xxx_token
HARNESS_API_BASE
https://app.harness.io
PAGE_SIZE
100
START_MINUTES_BACK
60
함수가 생성된 후 해당 페이지에 머무르거나 Lambda > 함수 > your‑function을 엽니다.
구성 탭을 선택합니다.
일반 구성 패널에서 수정을 클릭합니다.
제한 시간을 5분 (300초)으로 변경하고 저장을 클릭합니다.
EventBridge 일정 만들기
- Amazon EventBridge > 스케줄러 > 일정 만들기로 이동합니다.
다음 구성 세부정보를 제공합니다.
- 반복 일정: 요율 (
1 hour
) - 타겟: Lambda 함수
- 이름:
harness-io-1h
.
- 반복 일정: 요율 (
일정 만들기를 클릭합니다.
Google SecOps용 읽기 전용 IAM 사용자 및 키 만들기
- AWS 콘솔에서 IAM > Users로 이동한 다음 Add users를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 사용자: 고유한 이름 (예:
secops-reader
)을 입력합니다. - 액세스 유형: 액세스 키 - 프로그래매틱 액세스를 선택합니다.
- 사용자 만들기를 클릭합니다.
- 사용자: 고유한 이름 (예:
- 최소 읽기 정책 (맞춤) 연결: 사용자 >
secops-reader
선택 > 권한 > 권한 추가 > 정책 직접 연결 > 정책 만들기 JSON 편집기에서 다음 정책을 입력합니다.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::<your-bucket>/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::<your-bucket>" } ] }
이름을
secops-reader-policy
로 설정합니다.정책 만들기 > 검색/선택 > 다음 > 권한 추가로 이동합니다.
보안 사용자 인증 정보> 액세스 키> 액세스 키 만들기로 이동합니다.
CSV를 다운로드합니다 (이러한 값은 피드에 입력됨).
Harness IO 로그를 수집하도록 Google SecOps에서 피드 구성
- SIEM 설정> 피드로 이동합니다.
- 새 피드 추가를 클릭합니다.
- 피드 이름 필드에 피드 이름을 입력합니다 (예:
Harness IO
). - 소스 유형으로 Amazon S3 V2를 선택합니다.
- 로그 유형으로 Harness IO를 선택합니다.
- 다음을 클릭합니다.
- 다음 입력 파라미터의 값을 지정합니다.
- S3 URI:
s3://harness-io/harness/audit/
- 소스 삭제 옵션: 환경설정에 따라 삭제 옵션을 선택합니다.
- 최대 파일 기간: 기본값은 180일입니다.
- 액세스 키 ID: S3 버킷에 액세스할 수 있는 사용자 액세스 키입니다.
- 보안 비밀 액세스 키: S3 버킷에 액세스할 수 있는 사용자 보안 비밀 키입니다.
- 애셋 네임스페이스: 애셋 네임스페이스입니다.
- 수집 라벨: 이 피드의 이벤트에 적용할 라벨입니다.
- S3 URI:
- 다음을 클릭합니다.
- 확정 화면에서 새 피드 구성을 검토한 다음 제출을 클릭합니다.
도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가로부터 답변을 받으세요.