ServiceNow Security 로그 수집
이 문서에서는 웹훅 피드를 사용하여 ServiceNow 보안 이벤트를 Google Security Operations로 전송하는 방법을 설명합니다. ServiceNow 보안 파서는 로그인 실패, SNC 로그인, 관리자 로그인, 가장, 보안 권한 상승과 같은 ServiceNow 인스턴스 보안 센터 보안 이벤트를 위해 설계되었습니다 (ServiceNow 인스턴스 보안 센터 보안 이벤트 참고). ServiceNow 통합은 각 이벤트를 JSON으로 Google SecOps 웹훅 엔드포인트에 게시하며, 여기서 파서는 이를 통합 데이터 모델 (UDM)로 정규화합니다.
파서는 Failed Login, Admin Login, SNC Login, Impersonation, Security Elevation의 5가지 이벤트 값을 인식합니다. 각 값은 sysevent 대기열에 등록된 ServiceNow 이벤트에 해당합니다. 이 문서에서는 웹훅 피드, 피드에 게시하는 스크립트 포함, 일치하는 sysevent 레코드에서 실행되고 스크립트 포함을 호출하는 4개의 스크립트 작업을 만듭니다.
이 일회성 설정을 완료하면 ServiceNow에서 각 새 보안 이벤트를 Google SecOps로 자동으로 전달합니다. 관리자의 추가 조치는 필요하지 않습니다.
시작하기 전에
다음 기본 요건이 충족되었는지 확인합니다.
- Google SecOps 인스턴스입니다.
admin역할이 있거나 시스템 정의 > 스크립트 포함, 시스템 속성, 시스템 정책 > 이벤트 > 스크립트 작업, 레지스트리에 대한 권한이 있는 사용자 계정이 있는 ServiceNow 인스턴스- 기존 ServiceNow 인스턴스 Security Center 활성화 ISC는 판매가 종료되었으며 새 인스턴스에서 활성화할 수 없습니다.
- Google Google Cloud 콘솔에 대한 권한 있는 액세스 (API 키 생성용)
Google SecOps에서 웹훅 피드 만들기
다음 섹션에서는 Google SecOps에서 웹훅 피드를 만드는 방법을 설명합니다.
피드 만들기
- SIEM 설정> 피드로 이동합니다.
- 새 피드 추가를 클릭합니다.
- 다음 페이지에서 단일 피드 구성을 클릭합니다.
- 피드 이름 필드에 피드 이름을 입력합니다(예:
ServiceNow Security Events). - 소스 유형으로 웹훅을 선택합니다.
- 로그 유형으로 ServiceNow Security를 선택합니다.
- 다음을 클릭합니다.
다음 입력 매개변수의 값을 지정합니다.
- 분할 구분자: 통합에서 요청당 JSON 이벤트를 두 개 이상 전송하는 경우
\n를 입력합니다. 각 요청에 단일 이벤트가 포함된 경우 비워 둡니다. - 애셋 네임스페이스: 애셋 네임스페이스입니다.
- 수집 라벨: 이 피드의 이벤트에 적용된 라벨입니다.
- 분할 구분자: 통합에서 요청당 JSON 이벤트를 두 개 이상 전송하는 경우
다음을 클릭합니다.
확정 화면에서 새 피드 구성을 검토한 다음 제출을 클릭합니다.
보안 비밀 키 생성 및 저장
- 피드 세부정보 페이지에서 보안 비밀 키 생성을 클릭합니다.
보안 비밀 키를 안전한 위치에 복사하여 저장합니다.
피드 엔드포인트 URL 가져오기
- 피드의 세부정보 탭으로 이동합니다.
엔드포인트 정보 섹션에서 피드 엔드포인트 URL을 복사합니다. URL 형식은 다음과 같습니다.
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate또는 리전 엔드포인트의 경우:
https://<REGION>-malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate이후 단계를 위해 이 URL을 저장합니다.
완료를 클릭합니다.
Google Cloud API 키 만들기
- Google Google Cloud 콘솔 사용자 인증 정보 페이지로 이동합니다.
- Google SecOps 인스턴스와 연결된 프로젝트를 선택합니다.
- 사용자 인증 정보 만들기 API 키를 클릭합니다.
- API 키 수정을 클릭하여 키를 제한합니다.
- 이름 필드에 설명이 포함된 이름을 입력합니다 (예:
ServiceNow Webhook API Key). API 제한사항에서 다음을 실행합니다.
- 키 제한을 선택합니다.
- API 선택 목록에서 Google SecOps API를 선택합니다.
저장을 클릭합니다.
API 키를 복사하여 안전한 위치에 저장합니다.
ServiceNow에 Google SecOps 사용자 인증 정보 저장
통합 코드에 보안 비밀이 포함되지 않도록 엔드포인트 URL과 사용자 인증 정보를 ServiceNow 시스템 속성으로 저장합니다.
- ServiceNow에서 System Properties> sys_properties.list로 이동합니다.
새로 만들기를 클릭하고 다음 구성 세부정보를 제공합니다.
- 이름:
x_chronicle.endpoint_url - 값: 피드 엔드포인트 URL을 붙여넣습니다.
- 유형:
string
- 이름:
제출을 클릭합니다.
새로 만들기를 클릭하고 다음 구성 세부정보를 제공합니다.
- 이름:
x_chronicle.api_key - 값: Google Cloud API 키를 붙여넣습니다.
- 유형:
password2
- 이름:
제출을 클릭합니다.
새로 만들기를 클릭하고 다음 구성 세부정보를 제공합니다.
- 이름:
x_chronicle.secret_key - 값: Google SecOps 피드 비밀 키를 붙여넣습니다.
- 유형:
password2
- 이름:
제출을 클릭합니다.
웹훅 유틸리티 스크립트 포함 만들기
이 스크립트 포함은 ServiceNow sn_ws.RESTMessageV2 범위 지정 API를 사용하여 JSON 페이로드를 Google SecOps 웹훅 엔드포인트에 게시합니다. API 키와 보안 비밀 키는 HTTP 헤더로 전송됩니다.
- 시스템 정의 > 스크립트 포함으로 이동합니다.
- New(새로 만들기)를 클릭합니다.
다음 구성 세부정보를 제공합니다.
- 이름:
ChronicleWebhookUtil - API 이름:
ChronicleWebhookUtil - 클라이언트 호출 가능: 선택되지 않음
- 활성: 선택됨
- 이름:
스크립트 필드에 다음 코드를 입력합니다.
var ChronicleWebhookUtil = Class.create(); ChronicleWebhookUtil.prototype = { initialize: function() { this.endpointURL = gs.getProperty('x_chronicle.endpoint_url'); this.apiKey = gs.getProperty('x_chronicle.api_key'); this.secretKey = gs.getProperty('x_chronicle.secret_key'); }, // payload: a plain object whose keys match the required JSON contract. sendEvent: function(payload) { try { if (!this.endpointURL || !this.apiKey || !this.secretKey) { gs.error('[Chronicle] Missing configuration. Check System Properties: x_chronicle.*'); return false; } var request = new sn_ws.RESTMessageV2(); request.setEndpoint(this.endpointURL); request.setHttpMethod('POST'); request.setRequestHeader('Content-Type', 'application/json'); request.setRequestHeader('X-goog-api-key', this.apiKey); request.setRequestHeader('X-Webhook-Access-Key', this.secretKey); request.setRequestBody(JSON.stringify(payload)); var response = request.execute(); var statusCode = response.getStatusCode(); if (statusCode == 200 || statusCode == 201 || statusCode == 204) { gs.info('[Chronicle] Event sent: ' + payload.event + ' | Status: ' + statusCode); return true; } gs.error('[Chronicle] Failed to send event: ' + payload.event + ' | Status: ' + statusCode + ' | Response: ' + response.getBody()); return false; } catch (ex) { gs.error('[Chronicle] Exception sending event: ' + ex.message); return false; } }, type: 'ChronicleWebhookUtil' };제출을 클릭합니다.
이벤트 레지스트리 확인
스크립트 작업은 이벤트 이름이 이벤트 등록 (sysevent_register 테이블)에 있는 경우에만 실행됩니다. ServiceNow는 이 통합에서 사용되는 5개의 이벤트를 기본적으로 제공하지만 누락된 항목은 오류를 생성하지 않고 스크립트 작업이 실행되지 않으므로 확인하는 것이 좋습니다.
- ServiceNow에서 System Policy> Events> Registry (
sysevent_register.list)로 이동합니다. 다음 항목별로 이벤트 이름 열을 필터링하고 레코드가 있는지 확인합니다.
loginlogin.failedimpersonation.startsecurity.elevated_role.enabled
항목이 누락된 경우 새로 만들기를 클릭하고 이벤트 이름 필드를 누락된 값으로 설정하고 표를
Global [global]로 설정한 후 제출을 클릭합니다.
스크립트 작업 만들기
각 스크립트 작업은 하나의 ServiceNow 이벤트 이름을 구독합니다. ServiceNow가 일치하는 레코드를 sysevent 대기열에 삽입하면 스크립트 작업이 실행되고 이벤트 매개변수를 읽고 ChronicleWebhookUtil를 호출하여 페이로드를 게시합니다.
파서는 다음 ServiceNow 이벤트에 매핑됩니다.
파서 event 값 |
ServiceNow 이벤트 이름 | 트리거 |
|---|---|---|
Failed Login |
login.failed |
로그인 시도 실패 (모든 인증 방법) |
Admin Login |
login |
admin 역할이 있는 사용자의 로그인 성공 |
SNC Login |
login |
snc_external 역할이 있는 사용자의 로그인 성공 |
Impersonation |
impersonation.start |
관리자가 다른 사용자를 가장하기 시작합니다. |
Security Elevation |
security.elevated_role.enabled |
사용자가 security_admin 또는 다른 상위 권한 역할로 승격됩니다. |
login 및 login.failed은 parm1을 사용자 이름으로, parm2을 소스 IP로 설정합니다. 다른 이벤트는 동일한 parm1/parm2 규칙을 따르지만 정확한 콘텐츠는 ServiceNow 버전에 따라 다릅니다. 각 이벤트의 레지스트리 항목을 열고 Parameters 설명이 나중에 설명된 스크립트와 일치하는지 확인합니다. 등록처가 다른 경우 parm1 또는 parm2 참조를 조정합니다.
로그인 실패 스크립트 작업 만들기
- 시스템 정책 > 이벤트 > 스크립트 작업으로 이동합니다.
- New(새로 만들기)를 클릭합니다.
다음 구성 세부정보를 제공합니다.
- 이름:
Chronicle - Failed Login - 이벤트 이름:
login.failed - 활성: 선택됨
- 이름:
스크립트 필드에 다음 코드를 입력합니다.
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event) { new ChronicleWebhookUtil().sendEvent({ event: 'Failed Login', event_created: event.sys_created_on.getValue(), user: event.parm1.toString(), ip_address: event.parm2.toString() }); })(current, event);제출을 클릭합니다.
로그인 스크립트 작업 만들기
이 스크립트 작업은 로그인에 성공할 때마다 실행되며 로그인한 사용자에게 admin 또는 snc_external 역할이 있는 경우에만 레코드를 전달합니다. event 필드가 적절하게 설정됩니다.
- 시스템 정책 > 이벤트 > 스크립트 작업으로 이동합니다.
- New(새로 만들기)를 클릭합니다.
다음 구성 세부정보를 제공합니다.
- 이름:
Chronicle - Login - 이벤트 이름:
login - 활성: 선택됨
- 이름:
스크립트 필드에 다음 코드를 입력합니다.
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event) { var userName = event.parm1.toString(); var ipAddress = event.parm2.toString(); var user = new GlideRecord('sys_user'); if (!user.get('user_name', userName)) { return; } function hasRole(roleName) { var role = new GlideRecord('sys_user_has_role'); role.addQuery('user', user.sys_id); role.addQuery('role.name', roleName); role.query(); return role.next(); } var eventValue; if (hasRole('snc_external')) { eventValue = 'SNC Login'; } else if (hasRole('admin')) { eventValue = 'Admin Login'; } else { return; } new ChronicleWebhookUtil().sendEvent({ event: eventValue, event_created: event.sys_created_on.getValue(), user: userName, ip_address: ipAddress }); })(current, event);제출을 클릭합니다.
가장 스크립트 작업 만들기
- 시스템 정책 > 이벤트 > 스크립트 작업으로 이동합니다.
- New(새로 만들기)를 클릭합니다.
다음 구성 세부정보를 제공합니다.
- 이름:
Chronicle - Impersonation - 이벤트 이름:
impersonation.start - 활성: 선택됨
- 이름:
스크립트 필드에 다음 코드를 입력합니다.
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event) { new ChronicleWebhookUtil().sendEvent({ event: 'Impersonation', event_created: event.sys_created_on.getValue(), user: event.parm1.toString(), // impersonator snc_user: event.parm2.toString() // impersonated user }); })(current, event);제출을 클릭합니다.
보안 권한 상승 스크립트 작업 만들기
- 시스템 정책 > 이벤트 > 스크립트 작업으로 이동합니다.
- New(새로 만들기)를 클릭합니다.
다음 구성 세부정보를 제공합니다.
- 이름:
Chronicle - Security Elevation - 이벤트 이름:
security.elevated_role.enabled - 활성: 선택됨
- 이름:
스크립트 필드에 다음 코드를 입력합니다.
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event) { new ChronicleWebhookUtil().sendEvent({ event: 'Security Elevation', event_created: event.sys_created_on.getValue(), user: event.parm1.toString() }); })(current, event);제출을 클릭합니다.
통합 확인
다음 방법 중 하나를 사용하세요.
합성 이벤트로 확인
실제 보안 이벤트를 기다리지 않고 각 스크립트 작업을 즉시 테스트하려면 다음 단계를 따라 백그라운드 스크립트에서 이벤트를 대기열에 추가하세요.
- ServiceNow에서 System Definition(시스템 정의) > Scripts - Background(스크립트 - 백그라운드)로 이동합니다.
스크립트 실행 필드에 다음 줄 중 하나를 입력하고 스크립트 실행을 클릭합니다.
gs.eventQueue('login.failed', null, 'test.user', '203.0.113.45'); gs.eventQueue('login', null, 'admin', '203.0.113.45'); gs.eventQueue('impersonation.start', null, 'admin', 'test.user'); gs.eventQueue('security.elevated_role.enabled', null, 'admin', 'security_admin');이벤트 관리자 작업이 큐를 처리할 때까지 최대 1분 동안 기다립니다.
실제 이벤트로 확인
통합을 확인하려면 ServiceNow에서 다음 매핑된 보안 이벤트 중 하나를 생성하세요.
- 로그인 실패: 비공개 브라우저 창에서
/login.do를 열고 유효하지 않은 비밀번호를 제출합니다. - 관리자 로그인: 평소와 다른 IP 주소에서
admin역할이 있는 사용자로 로그인합니다. - 명의 도용: 관리자 계정에서 다른 사용자를 명의 도용합니다.
- 보안 권한 상승:
admin역할이 있는 사용자에서security_admin역할로 권한을 상승합니다.
전달 확인
- ServiceNow에서 System Policy > Events > Event Log (
sysevent.list)로 이동하여 해당 이벤트 이름이 있는 레코드가 표시되는지 확인합니다. - ServiceNow에서 System Logs(시스템 로그) > System Log(시스템 로그) > All(모두)로 이동하여
[Chronicle] Event sent: ...정보 메시지가 상태 200, 201 또는 204와 함께 표시되는지 확인합니다.[Chronicle] Failed to send event또는[Chronicle] Exception sending event메시지는 사용자 인증 정보 또는 연결 문제를 나타냅니다. - Google SecOps에서
metadata.vendor_name = "SERVICENOW"및metadata.product_name = "SERVICENOW_SECURITY"에 대한 UDM 검색을 실행합니다. - 이벤트가
metadata.event_type이USER_LOGIN또는USER_CHANGE_PERMISSIONS로 설정되고principal.user.userid이 채워진 상태로 표시되는지 확인합니다.
인증 방법 참조
Google SecOps 웹훅 피드는 API 키와 비밀 키를 HTTP 헤더 또는 URL 쿼리 매개변수로 허용합니다. 이 가이드에서는 sn_ws.RESTMessageV2가 맞춤 헤더를 지원하므로 헤더 메서드를 사용합니다. 이렇게 하면 사용자 인증 정보가 URL 및 서버 액세스 로그에 표시되지 않습니다.
이 가이드에서 사용되는 헤더는 다음과 같습니다.
X-goog-api-key: Google SecOps API의 Google Cloud API 키입니다.X-Webhook-Access-Key: Google SecOps에서 생성된 피드 보안 비밀 키입니다.
통합에서 맞춤 헤더를 전송할 수 없는 경우 대신 엔드포인트 URL에 사용자 인증 정보를 추가하세요.
<ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY>
웹훅 한도 및 권장사항
| 한도 | 값 |
|---|---|
| 최대 요청 크기 | 4MB |
| 최대 QPS (초당 쿼리 수) | 15,000 |
| 요청 제한 시간 | 30초 |
| 재시도 동작 | 지수 백오프를 사용한 자동 |
볼륨을 제어하려면 감지에 필요한 보안 이벤트만 전송하고 소스에서 버스트로 이벤트를 생성하는 경우 \n 분할 구분 기호를 사용하여 요청당 여러 이벤트를 일괄 처리하세요.
Google SecOps 피드에 대한 자세한 내용은 Google SecOps 피드 문서를 참고하세요. 각 피드 유형의 요구사항은 유형별 피드 구성을 참고하세요.
피드를 만들 때 문제가 발생하면 Google SecOps 지원팀에 문의하세요.
UDM 매핑 테이블
| 로그 필드 | UDM 매핑 | 논리 |
|---|---|---|
event |
extensions.auth.type |
매핑됨: "Failed Login", "SNC Login", "Admin Login", "Impersonation" → MACHINE |
event_created |
metadata.event_timestamp |
yyyy-MM-dd HH:mm:ss로 파싱됨 |
event_type |
metadata.event_type |
이름이 변경됨/매핑됨 |
event |
metadata.product_event_type |
event이(가) ``이 아닌 경우 매핑됩니다. |
ip_address |
principal.ip |
병합됨 |
user |
principal.user.userid |
user이(가) ``이 아닌 경우 매핑됩니다. |
created_by |
target.user.userid |
created_by이(가) ``이 아닌 경우 매핑됩니다. |
snc_user |
target.user.userid |
snc_user이(가) ``이 아닌 경우 매핑됩니다. |
timestamp |
event1.idm.read_only_udm.metadata.event_timestamp |
변경 로그에서 매핑됨 |
data.sys_id |
event1.idm.read_only_udm.metadata.product_log_id |
변경 로그에서 매핑됨 |
data.message_data |
event1.idm.read_only_udm.metadata.description |
변경 로그에서 매핑됨 |
data.source |
event1.idm.read_only_udm.principal.resource.name |
변경 로그에서 매핑됨 |
source |
event1.idm.read_only_udm.additional.fields |
변경 로그에서 매핑됨 |
data.sys_created_on |
event1.idm.read_only_udm.metadata.collected_timestamp |
변경 로그에서 매핑됨 |
data.sys_created_by |
event1.idm.read_only_udm.principal.user.userid |
변경 로그에서 매핑됨 |
data.user |
event1.idm.read_only_udm.principal.user.userid |
변경 로그에서 매핑됨 |
source_instance |
event1.idm.read_only_udm.principal.namespace |
변경 로그에서 매핑됨 |
data.role |
event1.idm.read_only_udm.target.resource.attribute.roles |
변경 로그에서 매핑됨 |
data.granted_by |
event1.idm.read_only_udm.target.user.userid |
변경 로그에서 매핑됨 |
data.level |
event1.idm.read_only_udm.security_result.severity |
변경 로그에서 매핑됨 |
data.state |
event1.idm.read_only_udm.security_result.action_details |
변경 로그에서 매핑됨 |
변경 로그
도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가에게 문의하여 답변을 받으세요.