Citrix Analytics 로그 수집
이 문서에서는 Amazon S3를 사용하여 Citrix Analytics 로그를 Google Security Operations에 수집하는 방법을 설명합니다.
시작하기 전에
다음 기본 요건이 충족되었는지 확인합니다.
- Google SecOps 인스턴스
- Citrix Analytics for Performance 테넌트에 대한 액세스 권한
- AWS (S3, IAM, Lambda, EventBridge)에 대한 권한 액세스
Citrix Analytics 필수 구성요소 수집
- Citrix Cloud Console에 로그인합니다.
- Identity and Access Management> API 액세스로 이동합니다.
- 클라이언트 만들기를 클릭합니다.
- 다음 세부정보를 복사하여 안전한 위치에 저장합니다.
- 클라이언트 ID
- 클라이언트 보안 비밀번호
- 고객 ID (Citrix Cloud URL 또는 IAM 페이지에 있음)
- API 기본 URL:
https://api.cloud.com/casodata
Google SecOps용 AWS S3 버킷 및 IAM 구성
- 이 사용자 가이드(버킷 만들기)에 따라 Amazon S3 버킷을 만듭니다.
- 나중에 참조할 수 있도록 버킷 이름과 리전을 저장합니다 (예:
citrix-analytics-logs
). - 이 사용자 가이드(IAM 사용자 만들기)에 따라 사용자를 만듭니다.
- 생성된 사용자를 선택합니다.
- 보안 사용자 인증 정보 탭을 선택합니다.
- 액세스 키 섹션에서 액세스 키 만들기를 클릭합니다.
- 사용 사례로 서드 파티 서비스를 선택합니다.
- 다음을 클릭합니다.
- 선택사항: 설명 태그를 추가합니다.
- 액세스 키 만들기를 클릭합니다.
- CSV 파일 다운로드를 클릭하여 나중에 사용할 수 있도록 액세스 키와 비밀 액세스 키를 저장합니다.
- 완료를 클릭합니다.
- 권한 탭을 선택합니다.
- 권한 정책 섹션에서 권한 추가를 클릭합니다.
- 권한 추가를 선택합니다.
- 정책 직접 연결을 선택합니다.
- AmazonS3FullAccess 정책을 검색하여 선택합니다.
- 다음을 클릭합니다.
- 권한 추가를 클릭합니다.
S3 업로드용 IAM 정책 및 역할 구성
- AWS 콘솔에서 IAM > 정책 > 정책 만들기 > JSON 탭으로 이동합니다.
다음 정책을 입력합니다.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::citrix-analytics-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::citrix-analytics-logs/citrix_analytics/state.json" } ] }
- 다른 버킷 이름을 입력한 경우
citrix-analytics-logs
을 해당 이름으로 바꿉니다.
- 다른 버킷 이름을 입력한 경우
다음 > 정책 만들기를 클릭합니다.
IAM > 역할 > 역할 생성 > AWS 서비스 > Lambda로 이동합니다.
새로 만든 정책을 연결합니다.
역할 이름을
CitrixAnalyticsLambdaRole
로 지정하고 역할 만들기를 클릭합니다.
Lambda 함수 만들기
- AWS 콘솔에서 Lambda > 함수 > 함수 만들기로 이동합니다.
- 처음부터 작성을 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 이름 CitrixAnalyticsCollector
런타임 Python 3.13 아키텍처 x86_64 실행 역할 CitrixAnalyticsLambdaRole
함수가 생성되면 코드 탭을 열고 스텁을 삭제하고 다음 코드를 입력합니다 (
CitrixAnalyticsCollector.py
).import os import json import uuid import datetime import urllib.parse import urllib.request import boto3 import botocore CITRIX_TOKEN_URL_TMPL = "https://api.cloud.com/cctrustoauth2/{customerid}/tokens/clients" DEFAULT_API_BASE = "https://api.cloud.com/casodata" s3 = boto3.client("s3") def _http_post_form(url, data_dict): """POST form data to get authentication token.""" data = urllib.parse.urlencode(data_dict).encode("utf-8") req = urllib.request.Request(url, data=data, headers={ "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded", }) with urllib.request.urlopen(req, timeout=30) as response: return json.loads(response.read().decode("utf-8")) def _http_get_json(url, headers): """GET JSON data from API endpoint.""" req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=60) as response: return json.loads(response.read().decode("utf-8")) def get_citrix_token(customer_id, client_id, client_secret): """Get Citrix Cloud authentication token.""" url = CITRIX_TOKEN_URL_TMPL.format(customerid=customer_id) payload = { "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, } token_response = _http_post_form(url, payload) return token_response["access_token"] def fetch_odata_entity(entity, when_utc, top, headers, api_base): """Fetch data from Citrix Analytics OData API with pagination.""" year = when_utc.year month = when_utc.month day = when_utc.day hour = when_utc.hour base_url = f"{api_base.rstrip('/')}/{entity}?year={year:04d}&month={month:02d}&day={day:02d}&hour={hour:02d}" skip = 0 while True: url = f"{base_url}&$top={top}&$skip={skip}" data = _http_get_json(url, headers) items = data.get("value", []) if not items: break for item in items: yield item if len(items) < top: break skip += top def read_state_file(bucket, state_key): """Read the last processed timestamp from S3 state file.""" try: obj = s3.get_object(Bucket=bucket, Key=state_key) content = obj["Body"].read().decode("utf-8") state = json.loads(content) timestamp_str = state.get("last_hour_utc") if timestamp_str: return datetime.datetime.fromisoformat(timestamp_str.replace("Z", "+00:00")).replace(tzinfo=None) except botocore.exceptions.ClientError as e: if e.response["Error"]["Code"] == "NoSuchKey": return None raise return None def write_state_file(bucket, state_key, dt_utc): """Write the current processed timestamp to S3 state file.""" state_data = {"last_hour_utc": dt_utc.isoformat() + "Z"} s3.put_object( Bucket=bucket, Key=state_key, Body=json.dumps(state_data, separators=(",", ":")), ContentType="application/json" ) def write_ndjson_to_s3(bucket, key, records): """Write records as NDJSON to S3.""" body_lines = [] for record in records: json_line = json.dumps(record, separators=(",", ":"), ensure_ascii=False) body_lines.append(json_line) body = ("n".join(body_lines) + "n").encode("utf-8") s3.put_object( Bucket=bucket, Key=key, Body=body, ContentType="application/x-ndjson" ) def lambda_handler(event, context): """Main Lambda handler function.""" # Environment variables bucket = os.environ["S3_BUCKET"] prefix = os.environ.get("S3_PREFIX", "").strip("/") state_key = os.environ.get("STATE_KEY") or f"{prefix}/state.json" customer_id = os.environ["CITRIX_CUSTOMER_ID"] client_id = os.environ["CITRIX_CLIENT_ID"] client_secret = os.environ["CITRIX_CLIENT_SECRET"] api_base = os.environ.get("API_BASE", DEFAULT_API_BASE) entities = [e.strip() for e in os.environ.get("ENTITIES", "sessions,machines,users").split(",") if e.strip()] top_n = int(os.environ.get("TOP_N", "1000")) lookback_minutes = int(os.environ.get("LOOKBACK_MINUTES", "75")) # Determine target hour to collect now = datetime.datetime.utcnow() fallback_target = (now - datetime.timedelta(minutes=lookback_minutes)).replace(minute=0, second=0, microsecond=0) last_processed = read_state_file(bucket, state_key) if last_processed: target_hour = last_processed + datetime.timedelta(hours=1) else: target_hour = fallback_target # Get authentication token token = get_citrix_token(customer_id, client_id, client_secret) headers = { "Authorization": f"CwsAuth bearer={token}", "Citrix-CustomerId": customer_id, "Accept": "application/json", "Content-Type": "application/json", } total_records = 0 # Process each entity type for entity in entities: records = [] for row in fetch_odata_entity(entity, target_hour, top_n, headers, api_base): enriched_record = { "citrix_entity": entity, "citrix_hour_utc": target_hour.isoformat() + "Z", "collection_timestamp": datetime.datetime.utcnow().isoformat() + "Z", "raw": row } records.append(enriched_record) # Write in batches to avoid memory issues if len(records) >= 1000: s3_key = f"{prefix}/{entity}/year={target_hour.year:04d}/month={target_hour.month:02d}/day={target_hour.day:02d}/hour={target_hour.hour:02d}/part-{uuid.uuid4().hex}.ndjson" write_ndjson_to_s3(bucket, s3_key, records) total_records += len(records) records = [] # Write remaining records if records: s3_key = f"{prefix}/{entity}/year={target_hour.year:04d}/month={target_hour.month:02d}/day={target_hour.day:02d}/hour={target_hour.hour:02d}/part-{uuid.uuid4().hex}.ndjson" write_ndjson_to_s3(bucket, s3_key, records) total_records += len(records) # Update state file write_state_file(bucket, state_key, target_hour) return { "statusCode": 200, "body": json.dumps({ "success": True, "hour_collected": target_hour.isoformat() + "Z", "records_written": total_records, "entities_processed": entities }) }
구성 > 환경 변수 > 수정 > 새 환경 변수 추가로 이동합니다.
다음 환경 변수를 입력하고 값으로 바꿉니다.
키 예시 값 S3_BUCKET
citrix-analytics-logs
S3_PREFIX
citrix_analytics
STATE_KEY
citrix_analytics/state.json
CITRIX_CLIENT_ID
your-client-id
CITRIX_CLIENT_SECRET
your-client-secret
API_BASE
https://api.cloud.com/casodata
CITRIX_CUSTOMER_ID
your-customer-id
ENTITIES
sessions,machines,users
TOP_N
1000
LOOKBACK_MINUTES
75
함수가 생성되면 해당 페이지에 머무르거나 Lambda > 함수 > CitrixAnalyticsCollector를 엽니다.
구성 탭을 선택합니다.
일반 구성 패널에서 수정을 클릭합니다.
제한 시간을 5분 (300초)으로 변경하고 저장을 클릭합니다.
EventBridge 일정 만들기
- Amazon EventBridge > 스케줄러 > 일정 만들기로 이동합니다.
- 다음 구성 세부정보를 제공합니다.
- 반복 일정: 요금 (
1 hour
) - 타겟: Lambda 함수
CitrixAnalyticsCollector
- 이름:
CitrixAnalyticsCollector-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:::citrix-analytics-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::citrix-analytics-logs" } ] }
이름을
secops-reader-policy
로 설정합니다.정책 만들기 > 검색/선택 > 다음 > 권한 추가로 이동합니다.
보안 사용자 인증 정보> 액세스 키> 액세스 키 만들기로 이동합니다.
CSV를 다운로드합니다 (이러한 값은 피드에 입력됨).
Citrix Analytics 로그를 수집하도록 Google SecOps에서 피드 구성
- SIEM 설정> 피드로 이동합니다.
- + 새 피드 추가를 클릭합니다.
- 피드 이름 필드에 피드 이름을 입력합니다 (예:
Citrix Analytics Performance logs
). - 소스 유형으로 Amazon S3 V2를 선택합니다.
- 로그 유형으로 Citrix Analytics를 선택합니다.
- 다음을 클릭합니다.
- 다음 입력 파라미터의 값을 지정합니다.
- S3 URI:
s3://citrix-analytics-logs/citrix_analytics/
- 소스 삭제 옵션: 환경설정에 따라 삭제 옵션을 선택합니다.
- 최대 파일 기간: 지난 일수 동안 수정된 파일을 포함합니다. 기본값은 180일입니다.
- 액세스 키 ID: S3 버킷에 대한 액세스 권한이 있는 사용자 액세스 키입니다.
- 보안 비밀 액세스 키: S3 버킷에 액세스할 수 있는 사용자 보안 비밀 키입니다.
- 애셋 네임스페이스: 애셋 네임스페이스입니다.
- 수집 라벨: 이 피드의 이벤트에 적용된 라벨입니다.
- S3 URI:
- 다음을 클릭합니다.
- 확정 화면에서 새 피드 구성을 검토한 다음 제출을 클릭합니다.
도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가로부터 답변을 받으세요.