Trellix Endpoint Security HX(이전의 FireEye HX) 로그 수집
파서 버전: 30.0
이 가이드에서는 Google Cloud Storage V2를 사용하여 Trellix Endpoint Security HX(이전 명칭: FireEye HX) 로그를 Google Security Operations에 수집하는 방법을 설명합니다. Trellix Endpoint Security HX는 지능형 위협 탐지, 조사, 격리 기능을 제공하는 엔드포인트 탐지 및 대응(EDR) 플랫폼입니다. 인텔리전스 기반 보호 및 실시간 침해 지표(IOC)를 사용하여 엔드포인트에 대한 정교한 공격을 감지하고 대응합니다.
시작하기 전에
다음 기본 요건이 충족되었는지 확인합니다.
- Google SecOps 인스턴스
- Cloud Storage API가 사용 설정된 GCP 프로젝트
- GCS 버킷 및 IAM 정책을 만들고 관리할 수 있는 권한입니다.
- Cloud Run 서비스, Pub/Sub 주제, Cloud Scheduler 작업을 만들 수 있는 권한
- API 액세스가 사용 설정된 Trellix Endpoint Security HX 콘솔에 대한 권한 있는 액세스
- 최소한 분석가 역할이 있는 Trellix HX API 사용자 계정
Google Cloud Storage 버킷 만들기
- Google Cloud 콘솔로 이동하세요.
- 프로젝트를 선택합니다.
- 탐색 메뉴에서 Cloud Storage > 버킷으로 이동합니다.
- 버킷 만들기를 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 버킷 이름 지정 전역적으로 고유한 이름(예: fireeye-hx-logs)을 입력합니다.위치 유형 필요에 따라 선택(리전, 이중 리전, 멀티 리전) 위치 Google SecOps 인스턴스에 가장 가까운 위치를 선택합니다. 스토리지 클래스 표준(자주 액세스하는 로그에 권장) 액세스 제어 균일(권장) 보호 도구 선택사항: 객체 버전 관리 또는 보관 정책 사용 설정 만들기를 클릭합니다.
Trellix Endpoint Security HX API 사용자 인증 정보 수집
HX 콘솔 URL 가져오기
- Trellix Endpoint Security HX 웹 콘솔에 로그인합니다.
- 브라우저 주소 표시줄에서 콘솔 URL을 확인합니다(형식:
https://<hx-hostname>:<port>).
API 사용자 계정 만들기 또는 확인
- 관리자 계정으로 Trellix HX 콘솔에 로그인합니다.
- 관리 > 사용자 계정으로 이동합니다.
- API 액세스를 위해 새 사용자를 만들거나 기존 사용자를 선택합니다.
- 사용자에게 api_analyst 또는 api_admin 역할이 있는지 확인합니다.
- API 액세스를 위한 사용자 이름과 비밀번호를 기록해 둡니다.
API 액세스 확인
통합을 진행하기 전에 사용자 인증 정보를 테스트합니다.
HX_HOST="[https://hx-console.example.com:3000](https://hx-console.example.com:3000)" HX_USER="api_user" HX_PASS="api_password" # Authenticate and get token curl -k -X POST "$HX_HOST/hx/api/v3/token" \ -u "$HX_USER:$HX_PASS"
Cloud Run Functions의 서비스 계정 만들기
Cloud Run 함수에는 GCS 버킷에 쓰고 Pub/Sub에서 호출할 수 있는 권한이 있는 서비스 계정이 필요합니다.
서비스 계정 만들기
- GCP 콘솔에서 IAM 및 관리자 > 서비스 계정으로 이동합니다.
- 서비스 계정 만들기를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 서비스 계정 이름:
fireeye-hx-collector-sa - 서비스 계정 설명:
Service account for Cloud Run function to collect Trellix HX logs
- 서비스 계정 이름:
- 만들고 계속하기를 클릭합니다.
- 이 서비스 계정에 프로젝트에 대한 액세스 권한 부여 섹션에서 다음 역할을 추가합니다.
- 스토리지 객체 관리자
- Cloud Run 호출자
- Cloud Functions 호출자
- 계속을 클릭한 다음 완료를 클릭합니다.
GCS 버킷에 IAM 권한 부여
- Cloud Storage > 버킷으로 이동합니다.
- 버킷 이름(
fireeye-hx-logs)을 클릭합니다. - 권한 탭으로 이동합니다.
- 액세스 권한 부여를 클릭합니다.
- 주 구성원 추가: 서비스 계정 이메일을 입력합니다.
- 역할 할당: 스토리지 객체 관리자를 선택합니다.
- 저장을 클릭합니다.
Pub/Sub 주제 만들기
Cloud Scheduler가 게시하고 Cloud Run 함수가 구독할 주제를 만듭니다.
- GCP 콘솔에서 Pub/Sub > 주제로 이동합니다.
- 주제 만들기를 클릭합니다.
- 주제 ID:
fireeye-hx-logs-trigger. - 만들기를 클릭합니다.
로그를 수집하는 Cloud Run 함수 만들기
- GCP Console에서 Cloud Run으로 이동합니다.
- 서비스 만들기를 클릭합니다.
- 함수를 선택합니다.
구성 섹션에서 다음 세부정보를 입력합니다.
설정 값 서비스 이름 fireeye-hx-collector리전 GCS 버킷과 일치하는 리전 선택 런타임 Python 3.12 이상 트리거 섹션에서 다음을 수행합니다.
- + 트리거 추가를 클릭합니다.
- Cloud Pub/Sub를 선택합니다.
- Cloud Pub/Sub 주제 선택에서
fireeye-hx-logs-trigger를 선택합니다. - 저장을 클릭합니다.
인증 섹션에서 인증 필요를 선택하고 Identity and Access Management(IAM)를 선택합니다.
컨테이너, 네트워킹, 보안까지 아래로 스크롤합니다.
보안 탭에서 서비스 계정
fireeye-hx-collector-sa를 선택합니다.컨테이너 탭에서 변수 및 보안 비밀을 클릭하고 다음을 추가합니다.
변수 이름 예시 값 설명 GCS_BUCKETfireeye-hx-logsGCS 버킷 이름 GCS_PREFIXhx-alerts로그 파일의 프리픽스 STATE_KEYhx-alerts/state.json상태 파일 경로 HX_HOSThttps://hx-console.example.com:3000HX 콘솔 URL HX_USERapi_userAPI 사용자 이름 HX_PASSapi_passwordAPI 비밀번호 MAX_RECORDS1000실행당 최대 레코드 수 PAGE_SIZE100페이지당 레코드 수 LOOKBACK_HOURS24초기 대상 기간 요청에서 요청 제한 시간을
600초로 설정합니다.만들기를 클릭합니다. 서비스가 생성되면 인라인 코드 편집기가 열립니다.
함수 코드 추가
- 진입점 필드에 main을 입력하세요.
인라인 편집기에서 다음 파일을 만듭니다.
- main.py:
import functions_framework from google.cloud import storage import json import os import urllib3 from datetime import datetime, timezone, timedelta import time # Disable SSL warnings for self-signed certs urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Initialize HTTP client with timeouts http = urllib3.PoolManager( timeout=urllib3.Timeout(connect=5.0, read=30.0), retries=False, cert_reqs='CERT_NONE', ) # Initialize Storage client storage_client = storage.Client() # Environment variables GCS_BUCKET = os.environ.get('GCS_BUCKET') GCS_PREFIX = os.environ.get('GCS_PREFIX', 'hx-alerts') STATE_KEY = os.environ.get('STATE_KEY', 'hx-alerts/state.json') HX_HOST = os.environ.get('HX_HOST') HX_USER = os.environ.get('HX_USER') HX_PASS = os.environ.get('HX_PASS') MAX_RECORDS = int(os.environ.get('MAX_RECORDS', '1000')) PAGE_SIZE = int(os.environ.get('PAGE_SIZE', '100')) LOOKBACK_HOURS = int(os.environ.get('LOOKBACK_HOURS', '24')) def get_api_token(host, username, password): url = f"{host}/hx/api/v3/token" auth_string = f"{username}:{password}" import base64 auth_b64 = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8') headers = {'Authorization': f'Basic {auth_b64}'} response = http.request('POST', url, headers=headers) if response.status == 204: return response.headers.get('X-FeApi-Token') else: raise Exception(f"Authentication failed: HTTP {response.status}") @functions_framework.cloud_event def main(cloud_event): if not all([GCS_BUCKET, HX_HOST, HX_USER, HX_PASS]): print('Error: Missing required environment variables') return try: bucket = storage_client.bucket(GCS_BUCKET) state = load_state(bucket, STATE_KEY) now = datetime.now(timezone.utc) last_time = None if isinstance(state, dict) and state.get("last_event_time"): try: last_time = parse_datetime(state["last_event_time"]) - timedelta(minutes=2) except Exception as e: print(f"Warning: Could not parse last_event_time: {e}") if last_time is None: last_time = now - timedelta(hours=LOOKBACK_HOURS) print(f"Fetching alerts from {last_time.isoformat()} to {now.isoformat()}") token = get_api_token(HX_HOST, HX_USER, HX_PASS) alerts, newest_time = fetch_alerts(HX_HOST, token, last_time, now, PAGE_SIZE, MAX_RECORDS) hosts = fetch_hosts(HX_HOST, token) all_records = alerts + hosts if not all_records: print("No new records found.") save_state(bucket, STATE_KEY, now.isoformat()) return timestamp = now.strftime('%Y%m%d_%H%M%S') object_key = f"{GCS_PREFIX}/logs_{timestamp}.ndjson" ndjson = '\n'.join([json.dumps(record, ensure_ascii=False) for record in all_records]) + '\n' bucket.blob(object_key).upload_from_string(ndjson, content_type='application/x-ndjson') print(f"Wrote {len(all_records)} records to gs://{GCS_BUCKET}/{object_key}") save_state(bucket, STATE_KEY, newest_time if newest_time else now.isoformat()) release_token(HX_HOST, token) print(f"Successfully processed {len(all_records)} records") except Exception as e: print(f'Error processing logs: {str(e)}') raise def parse_datetime(value): if value.endswith("Z"): value = value[:-1] + "+00:00" return datetime.fromisoformat(value) def load_state(bucket, key): try: blob = bucket.blob(key) if blob.exists(): return json.loads(blob.download_as_text()) except Exception as e: print(f"Warning: Could not load state: {e}") return {} def save_state(bucket, key, last_event_time_iso): try: state = {'last_event_time': last_event_time_iso} bucket.blob(key).upload_from_string(json.dumps(state, indent=2), content_type='application/json') except Exception as e: print(f"Warning: Could not save state: {e}") def fetch_alerts(host, token, start_time, end_time, page_size, max_records): endpoint = f"{host}/hx/api/v3/alerts" headers = {'X-FeApi-Token': token, 'Accept': 'application/json'} records = []; newest_time = None; offset = 0; backoff = 1.0 while True: if len(records) >= max_records: break start_iso = start_time.strftime('%Y-%m-%dT%H:%M:%S.000Z') end_iso = end_time.strftime('%Y-%m-%dT%H:%M:%S.000Z') url = f"{endpoint}?sort=reported_at+asc&min_reported_at={start_iso}&max_reported_at={end_iso}&offset={offset}&limit={min(page_size, max_records - len(records))}" try: response = http.request('GET', url, headers=headers) if response.status == 429: time.sleep(int(response.headers.get('Retry-After', str(int(backoff))))); backoff = min(backoff * 2, 30.0); continue backoff = 1.0 if response.status != 200: break data = json.loads(response.data.decode('utf-8')) page_results = data.get('data', {}).get('entries', []) if not page_results: break records.extend(page_results) for alert in page_results: event_time = alert.get('reported_at') if event_time and (newest_time is None or event_time > newest_time): newest_time = event_time offset += len(page_results) if offset >= data.get('data', {}).get('total', 0): break except Exception: break return records, newest_time def fetch_hosts(host, token): endpoint = f"{host}/hx/api/v3/hosts" headers = {'X-FeApi-Token': token, 'Accept': 'application/json'} try: response = http.request('GET', f"{endpoint}?limit=100&offset=0", headers=headers) if response.status != 200: return [] return json.loads(response.data.decode('utf-8')).get('data', {}).get('entries', []) except Exception: return [] def release_token(host, token): try: http.request('DELETE', f"{host}/hx/api/v3/token", headers={'X-FeApi-Token': token}) except Exception: pass- requirements.txt:
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0배포를 클릭하여 함수를 저장하고 배포합니다.
Cloud Scheduler 작업 만들기
- GCP Console에서 Cloud Scheduler로 이동합니다.
- 작업 만들기를 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 이름 fireeye-hx-collector-hourly리전 Cloud Run 함수와 동일한 리전 선택 주파수 0 * * * *(매시간 정각)시간대 시간대 선택(UTC 권장) 타겟 유형 Pub/Sub 주제 fireeye-hx-logs-trigger메일 본문 {}만들기를 클릭합니다.
통합 테스트
- Cloud Scheduler에서 작업을 찾아 강제 실행을 클릭합니다.
- Cloud Run > 서비스 > 로그로 이동하여 성공 여부를 확인합니다.
- Cloud Storage를 확인하여
hx-alerts/폴더에 새.ndjson파일이 있는지 확인합니다.
Google SecOps에서 피드 구성
- 설정 > 데이터 관리 > 피드로 이동합니다. SIEM 전용 플랫폼을 사용하는 경우 설정 > 피드로 이동합니다.
- 새 피드 추가 > 단일 피드 구성을 클릭합니다.
- 피드 이름:
Trellix HX Logs - 소스 유형: Google Cloud Storage V2
- 로그 유형: FireEye HX
- 서비스 계정 가져오기를 클릭하고 이메일(예:
chronicle-12345678@...)을 복사합니다. - 다음을 클릭합니다.
- 스토리지 버킷 URL:
gs://fireeye-hx-logs/hx-alerts/(후행 슬래시 포함) - 소스 삭제 옵션: 원하는 대로 선택합니다.
- 다음을 클릭하고 검토한 후 제출을 클릭합니다.
서비스 계정에 IAM 권한 부여
Google SecOps 서비스 계정에는 버킷에 대한 스토리지 객체 뷰어 역할이 필요합니다.
- Cloud Storage > 버킷으로 이동합니다.
- 버킷을 선택하고 권한 탭으로 이동합니다.
- 액세스 권한 부여를 클릭합니다.
- 주 구성원 추가: Google SecOps 서비스 계정 이메일을 붙여넣습니다.
- 역할 할당: 스토리지 객체 뷰어를 선택합니다.
- 저장을 클릭합니다.
UDM 매핑 테이블
| 로그 필드 | UDM 매핑 | 로직 |
|---|---|---|
alert.event_type |
metadata.product_event_type |
직접 매핑 |
alert.reported_at |
metadata.event_timestamp |
타임스탬프로 파싱됩니다. |
alert.source |
principal.hostname |
직접 매핑 |
alert.agent._id |
principal.asset_id |
직접 매핑 |
alert.indicator.display_name |
security_result.threat_name |
직접 매핑 |
alert.md5values |
target.file.md5 |
직접 매핑 |
| 해당 사항 없음 | metadata.vendor_name |
Trellix로 설정합니다. |
| 해당 사항 없음 | metadata.product_name |
Endpoint Security HX로 설정합니다. |
| 해당 사항 없음 | metadata.log_type |
FIREEYE_HX로 설정합니다. |
cs12 |
additional.fields |
변경 로그에서 매핑됨 |
cs9 |
target.process.file.md5 |
변경 로그에서 매핑됨 |
account_name", "UUID", "Mitre", "host_details.data.sysinfo.url", "host_details.route", "host_details.data.reported_clone", and "host_details.data.timezone |
security_result.detection_fields |
변경 로그에서 매핑됨 |
Desc |
metadata.description |
변경 로그에서 매핑됨 |
Confidence |
security_result.confidence |
변경 로그에서 매핑됨 |
alert.appliance._id |
additional.fields |
변경 로그에서 매핑됨 |
host_details.data.stats.acqs", "host_details.data.stats.alerting_conditions", "host_details.data.stats.alerts", "host_details.data.stats.exploit_alerts", "host_details.data.stats.exploit_blocks", and "host_details.data.stats.false_positive_alerts |
security_result.detection_fields |
변경 로그에서 매핑됨 |
categoryOutcome", "cs13 |
additional.fields |
변경 로그에서 매핑됨 |
cs6 |
target.process.file.sha1 |
변경 로그에서 매핑됨 |
host_details.message |
security_result.action_details |
변경 로그에서 매핑됨 |
alert.md5values", "alert.resolution", "alert.is_false_positive", and "alert.alert_type |
additional.fields |
변경 로그에서 매핑됨 |
type.threat_type |
security_result.threat_name |
변경 로그에서 매핑됨 |
ent.lms_event_id |
metadata.product_log_id |
변경 로그에서 매핑됨 |
email.smtp.mail_from |
network.email.from |
변경 로그에서 매핑됨 |
email.headers.subject |
network.email.subject |
변경 로그에서 매핑됨 |
email.headers.to |
network.email.to |
변경 로그에서 매핑됨 |
ent.type", "ent.id", "ent.name", "ent.object_source", "ent.binary", and "ent.attributes.scan_id |
security_result.detection_fields |
변경 로그에서 매핑됨 |
cs11Label |
additional_cs11Label.key |
변경 로그에서 매핑됨 |
cs11 |
additional_cs11.value |
변경 로그에서 매핑됨 |
alert.sysinfo.mac_address |
principal.mac |
변경 로그에서 매핑됨 |
host_details.data.agent_version |
metadata.product_version |
변경 로그에서 매핑됨 |
alert.url |
metadata.url_back_to_product |
변경 로그에서 매핑됨 |
description |
metadata.description |
변경 로그에서 매핑됨 |
alert.agent._id |
principal.asset.asset_id |
변경 로그에서 매핑됨 |
alert.event_id |
metadata.product_log_id |
변경 로그에서 매핑됨 |
deviceCustomDate1Label |
additional_deviceCustomDate1.key |
변경 로그에서 매핑됨 |
deviceCustomDate1 |
additional_deviceCustomDate1.value |
변경 로그에서 매핑됨 |
deviceCustomDate2 |
additional_deviceCustomDate2.value |
변경 로그에서 매핑됨 |
client |
principal.ip |
변경 로그에서 매핑됨 |
principal_ip |
principal.ip |
변경 로그에서 매핑됨 |
remoteaddress |
principal.ip |
변경 로그에서 매핑됨 |
host_ |
principal.hostname |
변경 로그에서 매핑됨 |
line |
principal.application |
변경 로그에서 매핑됨 |
username |
principal.user.userid |
변경 로그에서 매핑됨 |
client_app_type |
principal.resource.attribute.labels |
변경 로그에서 매핑됨 |
upstream |
target.url |
변경 로그에서 매핑됨 |
role |
target.user.role_name |
변경 로그에서 매핑됨 |
server |
target.resource.attribute.labels |
변경 로그에서 매핑됨 |
localusername |
target.user.user_display_name |
변경 로그에서 매핑됨 |
request |
additional.fields |
변경 로그에서 매핑됨 |
mlocked |
additional.fields |
변경 로그에서 매핑됨 |
kernel_stack |
additional.fields |
변경 로그에서 매핑됨 |
sessionID |
network.session_id |
변경 로그에서 매핑됨 |
auth_mechanism |
extensions.auth.mechanism |
변경 로그에서 매핑됨 |
authsubmethod |
extensions.auth.auth_details |
변경 로그에서 매핑됨 |
변경 로그
도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가에게 문의하여 답변을 받으세요.