Workday 사용자 활동 로그 수집
이 문서에서는 Cloud Storage V2를 사용하여 Workday 사용자 활동 로그를 Google Security Operations로 수집하는 방법을 설명합니다.
Workday는 사용자 활동, 인증, 보안 감사 로그를 생성하는 클라우드 기반 인적 자본 관리 (HCM) 및 금융 플랫폼입니다. 사용자 활동 로깅 REST API는 이러한 로그에 대한 프로그래매틱 액세스를 제공합니다.
시작하기 전에
다음 기본 요건이 충족되었는지 확인합니다.
- Google SecOps 인스턴스
- {storage_api}가 사용 설정된 Google Cloud 프로젝트
- Cloud Storage 버킷을 만들고 관리할 수 있는 권한
- Cloud Storage 버킷의 Identity and Access Management (IAM) 정책을 관리할 수 있는 권한
- Cloud Run 서비스, Pub/Sub 주제, Cloud Scheduler 작업을 만들 수 있는 권한
- 보안 관리자 또는 이에 준하는 권한으로 Workday에 대한 권한 액세스
- Workday 테넌트에서 사용자 활동 로깅이 사용 설정됨
Cloud Storage 버킷 만들기
- Google Cloud 콘솔로 이동합니다.
- 프로젝트를 선택하거나 새 프로젝트를 만듭니다.
- 탐색 메뉴에서 Cloud Storage> 버킷으로 이동합니다.
- 버킷 만들기를 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 버킷 이름 지정 전역적으로 고유한 이름 (예: workday-activity-logs)을 입력합니다.위치 유형 필요에 따라 선택 (리전, 이중 리전, 멀티 리전) 위치 위치를 선택합니다 (예: us-central1).스토리지 클래스 Standard (자주 액세스하는 로그에 권장) 액세스 제어 균일 (권장) 보호 조치 선택사항: 객체 버전 관리 또는 보관 정책 사용 설정 만들기를 클릭합니다.
Workday API 사용자 인증 정보 수집
사용자 활동 로깅 사용 설정
- 보안 관리자로 Workday 테넌트에 로그인합니다.
- Workday 검색창에서 Edit Tenant Setup - System(테넌트 설정 수정 - 시스템) 작업을 검색하여 엽니다.
- 보안 섹션에서 사용자 활동 로깅 사용 설정 체크박스를 선택합니다.
- 확인을 클릭하여 변경사항을 저장합니다.
통합 시스템 사용자 (ISU) 만들기
- Workday 검색창에서 통합 시스템 사용자 만들기를 검색합니다.
- 사용자 이름 (예:
ISU_SIEM_Export)과 안전한 비밀번호를 입력합니다. - 다음 로그인 시 새 비밀번호 요구 체크박스를 선택 해제합니다.
- 확인을 클릭합니다.
- 보안 그룹 만들기를 검색하고 통합 시스템 보안 그룹 (제약 없음)을 만듭니다.
- 이 보안 그룹에 ISU (
ISU_SIEM_Export)를 추가합니다. - 도메인 보기를 검색하고 사용자 활동 로깅 도메인을 찾습니다. 이 도메인의 ISU 보안 그룹에 Get 액세스 권한을 부여합니다.
- 보류 중인 보안 정책 변경사항 활성화를 검색하고 변경사항을 활성화합니다.
OAuth용 API 클라이언트 등록
- Workday 검색창에서 Register API Client for Integrations를 검색합니다.
- 클라이언트 이름을 입력합니다 (예:
SIEM_OAuth_Client). - 만료되지 않는 새로고침 토큰 옵션을 선택합니다.
- System (또는 사용자 활동 Logging API에 필요한 범위) 범위를 추가합니다.
- 확인을 클릭합니다.
- 다음 세부정보를 복사하여 안전한 위치에 저장합니다.
- 클라이언트 ID: API 클라이언트 식별자입니다.
- 클라이언트 보안 비밀번호: API 클라이언트 보안 비밀번호입니다.
- 통합의 갱신 토큰 관리를 검색합니다.
- ISU 계정 (
ISU_SIEM_Export)을 선택합니다. - API 클라이언트의 새 갱신 토큰을 생성합니다.
- Refresh Token을 복사하여 저장합니다.
테넌트 URL 확인
API 엔드포인트는 Workday 테넌트를 기반으로 합니다. 형식은 다음과 같습니다.
| 구성요소 | 값 |
|---|---|
| 토큰 엔드포인트 | https://HOST/ccx/oauth2/TENANT/token |
| Activity Logging API | https://HOST/ccx/api/privacy/v1/TENANT/activityLogging |
권한 확인
ISU에 필요한 권한이 있는지 확인하려면 다음 단계를 따르세요.
- 보안 관리자로 Workday에 로그인합니다.
- 통합 시스템 사용자 보기를 검색하고 ISU (
ISU_SIEM_Export)를 선택합니다. - 사용자가 사용자 활동 로깅 도메인에 대한 Get 액세스 권한이 있는 보안 그룹의 구성원인지 확인합니다.
- 액세스 권한이 누락된 경우 보안 그룹을 업데이트하고 대기 중인 보안 정책 변경사항을 활성화합니다.
API 액세스 테스트
통합을 진행하기 전에 사용자 인증 정보를 테스트합니다.
```bash # Replace with your actual credentials CLIENT_ID="your-client-id" CLIENT_SECRET="your-client-secret" REFRESH_TOKEN="your-refresh-token" HOST="your-workday-host" TENANT="your-tenant" # Get OAuth access token TOKEN=$(curl -s -X POST "https://${HOST}/ccx/oauth2/${TENANT}/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}" \ | jq -r '.access_token') # Test API access curl -v -H "Authorization: Bearer ${TOKEN}" \ "https://${HOST}/ccx/api/privacy/v1/${TENANT}/activityLogging?from=2024-01-01T00:00:00Z&to=2024-01-01T01:00:00Z" ```
Cloud Run 함수의 서비스 계정 만들기
Cloud Run 함수에는 Cloud Storage 버킷에 쓸 수 있고 Pub/Sub에 의해 호출될 수 있는 권한이 있는 서비스 계정이 필요합니다.
서비스 계정 만들기
- GCP 콘솔에서 IAM 및 관리자 > 서비스 계정으로 이동합니다.
- 서비스 계정 만들기를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 서비스 계정 이름:
workday-activity-collector-sa을 입력합니다. - 서비스 계정 설명:
Service account for Cloud Run function to collect Workday User Activity logs을 입력합니다.
- 서비스 계정 이름:
- 만들고 계속하기를 클릭합니다.
- 이 서비스 계정에 프로젝트에 대한 액세스 권한 부여 섹션에서 다음 역할을 추가합니다.
- 역할 선택을 클릭합니다.
- 스토리지 객체 관리자를 검색하여 선택합니다.
- + 다른 역할 추가를 클릭합니다.
- Cloud Run 호출자를 검색하여 선택합니다.
- + 다른 역할 추가를 클릭합니다.
- Cloud Functions 호출자를 검색하여 선택합니다.
- 계속을 클릭합니다.
- 완료를 클릭합니다.
이러한 역할은 다음 작업에 필요합니다.
- 스토리지 객체 관리자: Cloud Storage 버킷에 로그를 쓰고 상태 파일을 관리합니다.
- Cloud Run 호출자: Pub/Sub에서 함수를 호출하도록 허용
- Cloud Functions 호출자: 함수 호출 허용
Cloud Storage 버킷에 대한 IAM 권한 부여
다음과 같이 Cloud Storage 버킷에 대한 서비스 계정 쓰기 권한을 부여합니다.
- Cloud Storage> 버킷으로 이동합니다.
- 버킷 이름 (예:
workday-activity-logs)을 클릭합니다. - 권한 탭으로 이동합니다.
- 액세스 권한 부여를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 주 구성원 추가: 서비스 계정 이메일 (예:
workday-activity-collector-sa@your-project.iam.gserviceaccount.com)을 입력합니다. - 역할 할당: 스토리지 객체 관리자를 선택합니다.
- 주 구성원 추가: 서비스 계정 이메일 (예:
- 저장을 클릭합니다.
Pub/Sub 주제 만들기
Cloud Scheduler가 게시하고 Cloud Run 함수가 구독할 Pub/Sub 주제를 만듭니다.
- GCP 콘솔에서 Pub/Sub> 주제로 이동합니다.
- 주제 만들기를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 주제 ID:
workday-activity-trigger를 입력합니다. - 다른 설정은 기본값으로 둡니다.
- 주제 ID:
- 만들기를 클릭합니다.
로그를 수집하는 Cloud Run 함수 만들기
Cloud Run 함수는 Cloud Scheduler의 Pub/Sub 메시지에 의해 트리거되어 Workday 사용자 활동 Logging API에서 로그를 가져와 Cloud Storage에 씁니다.
- GCP 콘솔에서 Cloud Run으로 이동합니다.
- 함수 작성을 클릭합니다.
구성 섹션에서 다음 구성 세부정보를 입력합니다.
설정 값 서비스 이름 workday-activity-collector리전 Cloud Storage 버킷과 일치하는 리전을 선택합니다 (예: us-central1).런타임 Python 3.12 이상 선택 트리거 섹션에서 트리거 추가를 클릭하고 Pub/Sub 트리거를 선택합니다.
Eventarc 트리거 창에서 다음 구성 세부정보를 제공합니다.
- 트리거 이름: 생성된 이름을 유지하거나 트리거 이름을 입력합니다.
- 트리거 유형: Google 소스를 선택합니다.
- 이벤트 제공자: Pub/Sub를 선택합니다.
- 이벤트 유형: google.cloud.pubsub.topic.v1.messagePublished를 선택합니다.
- Cloud Pub/Sub 주제 선택:
workday-activity-trigger주제를 선택합니다. - 리전: 함수와 동일한 리전을 선택합니다.
- 서비스 계정: 서비스 계정
workday-activity-collector-sa를 선택합니다.
트리거 저장을 클릭합니다.
인증 섹션에서 다음을 구성합니다.
- 인증 필요를 선택합니다.
- Identity and Access Management (IAM)를 선택합니다.
컨테이너, 네트워킹, 보안으로 이동하여 펼칩니다.
보안 탭으로 이동합니다.
- 서비스 계정: 서비스 계정
workday-activity-collector-sa를 선택합니다.
- 서비스 계정: 서비스 계정
컨테이너 탭으로 이동합니다.
- 변수 및 보안 비밀을 클릭합니다.
- 각 환경 변수에 대해 + 변수 추가를 클릭합니다.
변수 이름 예시 값 설명 GCS_BUCKETworkday-activity-logsCloud Storage 버킷 이름 GCS_PREFIXworkday로그 파일의 접두사 STATE_KEYworkday-state.json로그 접두사 외부의 상태 경로 WORKDAY_HOSTyour-workday-hostWorkday 호스트 이름 WORKDAY_TENANTyour-tenantWorkday 테넌트 이름 CLIENT_IDyour-client-idOAuth 클라이언트 ID CLIENT_SECRETyour-client-secretOAuth 클라이언트 보안 비밀번호 REFRESH_TOKENyour-refresh-tokenOAuth 갱신 토큰 MAX_RECORDS10000실행당 최대 레코드 수 PAGE_SIZE100페이지당 레코드 수 LOOKBACK_HOURS24초기 전환 확인 기간 변수 및 보안 비밀 탭에서 요청으로 이동합니다.
- 요청 제한 시간:
600초 (10분)를 입력합니다.
- 요청 제한 시간:
컨테이너의 설정 탭으로 이동합니다.
- 리소스 섹션에서 다음을 수행합니다.
- 메모리: 512MiB 이상을 선택합니다.
- CPU: 1을 선택합니다.
- 리소스 섹션에서 다음을 수행합니다.
버전 확장 섹션에서 다음을 수행합니다.
- 최소 인스턴스 수:
0를 입력합니다. - 최대 인스턴스 수:
100을 입력합니다 (또는 예상 부하에 따라 조정).
- 최소 인스턴스 수:
만들기를 클릭합니다.
서비스가 생성될 때까지 기다립니다 (1~2분).
서비스가 생성되면 인라인 코드 편집기가 자동으로 열립니다.
함수 코드 추가
- 함수 진입점에 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 import hashlib # Initialize HTTP client with timeouts http = urllib3.PoolManager( timeout=urllib3.Timeout(connect=5.0, read=30.0), retries=False, ) # Initialize Storage client storage_client = storage.Client() # Environment variables GCS_BUCKET = os.environ.get('GCS_BUCKET') GCS_PREFIX = os.environ.get('GCS_PREFIX', 'workday') STATE_KEY = os.environ.get('STATE_KEY', 'workday-state.json') WORKDAY_HOST = os.environ.get('WORKDAY_HOST', '') WORKDAY_TENANT = os.environ.get('WORKDAY_TENANT', '') CLIENT_ID = os.environ.get('CLIENT_ID', '') CLIENT_SECRET = os.environ.get('CLIENT_SECRET', '') REFRESH_TOKEN = os.environ.get('REFRESH_TOKEN', '') MAX_RECORDS = int(os.environ.get('MAX_RECORDS', '10000')) PAGE_SIZE = int(os.environ.get('PAGE_SIZE', '100')) LOOKBACK_HOURS = int(os.environ.get('LOOKBACK_HOURS', '24')) def parse_datetime(value: str) -> datetime: """Parse ISO datetime string to datetime object.""" if value.endswith("Z"): value = value[:-1] + "+00:00" return datetime.fromisoformat(value) # Activity Logging entries carry no per-event identifier (taskId and # sessionId are reused), so the content hash is the identity. ID_FIELDS = () def event_keys(event): """Return every identity this event can be recognised by.""" keys = {'sha256:' + hashlib.sha256( json.dumps(event, sort_keys=True, ensure_ascii=False).encode('utf-8') ).hexdigest()} for field in ID_FIELDS: value = event.get(field) if value: keys.add(f'{field}:{value}') return keys def get_token(): """Get OAuth 2.0 access token using refresh token flow.""" token_url = f"https://{WORKDAY_HOST}/ccx/oauth2/{WORKDAY_TENANT}/token" body = f"grant_type=refresh_token&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&refresh_token={REFRESH_TOKEN}" headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' } backoff = 1.0 max_retries = 3 for attempt in range(max_retries): response = http.request('POST', token_url, body=body.encode('utf-8'), headers=headers) if response.status == 429: retry_after = int(response.headers.get('Retry-After', str(int(backoff)))) print(f"Rate limited (429) on token request. Retrying after {retry_after}s...") time.sleep(retry_after) backoff = min(backoff * 2, 30.0) continue if response.status != 200: raise RuntimeError(f"Failed to get access token: {response.status} - {response.data.decode('utf-8')}") data = json.loads(response.data.decode('utf-8')) return data['access_token'] raise RuntimeError(f"Failed to get token after {max_retries} retries due to rate limiting") @functions_framework.cloud_event def main(cloud_event): """ Cloud Run function triggered by Pub/Sub to fetch Workday User Activity logs and write to GCS. Args: cloud_event: CloudEvent object containing Pub/Sub message """ if not all([GCS_BUCKET, WORKDAY_HOST, WORKDAY_TENANT, CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN]): print('Error: Missing required environment variables') return try: bucket = storage_client.bucket(GCS_BUCKET) # Load state state = load_state(bucket, STATE_KEY) seen_keys = load_seen(bucket, STATE_KEY) # Determine time window 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"]) last_time = last_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 logs from {last_time.isoformat()} to {now.isoformat()}") # Get access token token = get_token() # Fetch logs records, newest_event_time = fetch_logs( token=token, start_time=last_time, end_time=now, page_size=PAGE_SIZE, max_records=MAX_RECORDS, ) # Drop the events the overlap window re-read from the previous run fresh = [r for r in records if not (event_keys(r) & seen_keys)] all_keys = set() for record in records: all_keys |= event_keys(record) if not fresh: print("No new log records found.") return # Write to GCS as NDJSON timestamp = now.strftime('%Y%m%d_%H%M%S') object_key = f"{GCS_PREFIX}/logs_{timestamp}.ndjson" blob = bucket.blob(object_key) ndjson = '\n'.join([json.dumps(record, ensure_ascii=False) for record in fresh]) + '\n' blob.upload_from_string(ndjson, content_type='application/x-ndjson') print(f"Wrote {len(fresh)} records to gs://{GCS_BUCKET}/{object_key}") # Remember every fetched record so the next overlap window skips it save_seen(bucket, STATE_KEY, all_keys) if newest_event_time: save_state(bucket, STATE_KEY, newest_event_time) else: save_state(bucket, STATE_KEY, now.isoformat()) print(f"Successfully processed {len(fresh)} records") except Exception as e: print(f'Error processing logs: {str(e)}') raise def load_state(bucket, key): """Load state from GCS.""" try: blob = bucket.blob(key) if blob.exists(): state_data = blob.download_as_text() return json.loads(state_data) except Exception as e: print(f"Warning: Could not load state: {e}") raise return {} def save_state(bucket, key, last_event_time_iso: str): """Save the last event timestamp to GCS state file.""" try: state = {'last_event_time': last_event_time_iso} blob = bucket.blob(key) blob.upload_from_string( json.dumps(state, indent=2), content_type='application/json' ) print(f"Saved state: last_event_time={last_event_time_iso}") except Exception as e: print(f"Warning: Could not save state: {e}") raise def load_seen(bucket, key): """Load the event keys written by the previous run (sidecar object).""" try: blob = bucket.blob(key + '.seen') if blob.exists(): return set(json.loads(blob.download_as_text())) except Exception as e: print(f"Warning: Could not load seen keys: {e}") raise return set() def save_seen(bucket, key, keys): """Save the event keys of this run to the sidecar object.""" try: blob = bucket.blob(key + '.seen') blob.upload_from_string( json.dumps(sorted(keys)), content_type='application/json' ) print(f"Saved {len(keys)} event keys for deduplication") except Exception as e: print(f"Warning: Could not save seen keys: {e}") raise def fetch_logs(token: str, start_time: datetime, end_time: datetime, page_size: int, max_records: int): """ Fetch user activity logs from Workday Activity Logging API with pagination and rate limiting. Args: token: OAuth 2.0 access token start_time: Start time for log query end_time: End time for log query page_size: Number of records per page max_records: Maximum total records to fetch Returns: Tuple of (records list, newest_event_time ISO string) """ base_url = f"https://{WORKDAY_HOST}/ccx/api/privacy/v1/{WORKDAY_TENANT}/activityLogging" headers = { 'Authorization': f'Bearer {token}', 'Accept': 'application/json', 'User-Agent': 'GoogleSecOps-WorkdayCollector/1.0' } records = [] newest_time = None page_num = 0 backoff = 1.0 offset = 0 from_iso = start_time.strftime('%Y-%m-%dT%H:%M:%SZ') to_iso = end_time.strftime('%Y-%m-%dT%H:%M:%SZ') while True: page_num += 1 if len(records) >= max_records: print(f"Reached max_records limit ({max_records})") break current_limit = min(page_size, max_records - len(records)) url = f"{base_url}?from={from_iso}&to={to_iso}&limit={current_limit}&offset={offset}" try: response = http.request('GET', url, headers=headers) if response.status == 429: retry_after = int(response.headers.get('Retry-After', str(int(backoff)))) print(f"Rate limited (429). Retrying after {retry_after}s...") time.sleep(retry_after) backoff = min(backoff * 2, 30.0) continue backoff = 1.0 if response.status != 200: print(f"HTTP Error: {response.status}") response_text = response.data.decode('utf-8') print(f"Response body: {response_text}") return [], None data = json.loads(response.data.decode('utf-8')) page_results = data.get('data', []) if not page_results: print(f"No more results (empty page)") break print(f"Page {page_num}: Retrieved {len(page_results)} events") records.extend(page_results) # Track newest event time for event in page_results: try: event_time = event.get('requestTime') or event.get('systemAccount', {}).get('signOnDateTime') if event_time: if newest_time is None or parse_datetime(event_time) > parse_datetime(newest_time): newest_time = event_time except Exception as e: print(f"Warning: Could not parse event time: {e}") # Check pagination total = data.get('total', 0) if offset + len(page_results) >= total: print(f"Reached end of results (offset={offset + len(page_results)}, total={total})") break offset += len(page_results) except Exception as e: print(f"Error fetching logs: {e}") return [], None print(f"Retrieved {len(records)} total records from {page_num} pages") return records, newest_time두 번째 파일: requirements.txt:
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0
배포를 클릭하여 함수를 저장하고 배포합니다.
배포가 완료될 때까지 기다립니다 (2~3분).
Cloud Scheduler 작업 만들기
Cloud Scheduler는 일정 간격으로 Pub/Sub 주제에 메시지를 게시하여 Cloud Run 함수를 트리거합니다.
- GCP Console에서 Cloud Scheduler로 이동합니다.
- 작업 만들기를 클릭합니다.
다음 구성 세부정보를 제공합니다.
설정 값 이름 workday-activity-collector-hourly리전 Cloud Run 함수와 동일한 리전 선택 주파수 0 * * * *(매시간 정각)시간대 시간대 선택 (UTC 권장) 타겟 유형 Pub/Sub 주제 workday-activity-trigger주제를 선택합니다.메일 본문 {}(빈 JSON 객체)만들기를 클릭합니다.
일정 빈도 옵션
로그 볼륨 및 지연 시간 요구사항에 따라 빈도를 선택합니다.
| 빈도 | 크론 표현식 | 사용 사례 |
|---|---|---|
| 5분마다 | */5 * * * * |
대용량, 저지연 |
| 15분마다 | */15 * * * * |
검색량 보통 |
| 1시간마다 | 0 * * * * |
표준 (권장) |
| 6시간마다 | 0 */6 * * * |
적은 볼륨, 일괄 처리 |
| 매일 | 0 0 * * * |
이전 데이터 수집 |
통합 테스트
- Cloud Scheduler 콘솔에서 작업을 찾습니다 (
workday-activity-collector-hourly). - 강제 실행을 클릭하여 수동으로 트리거합니다.
- 몇 초간 기다린 후 Cloud Run > 서비스 > workday-activity-collector > 로그로 이동합니다.
함수가 성공적으로 실행되었는지 확인합니다. 다음 항목을 찾습니다.
Fetching logs from YYYY-MM-DDTHH:MM:SS+00:00 to YYYY-MM-DDTHH:MM:SS+00:00 Page 1: Retrieved X events Wrote X records to gs://workday-activity-logs/workday/logs_YYYYMMDD_HHMMSS.ndjson Successfully processed X recordsCloud Storage 버킷 (
workday-activity-logs)을 확인하여 로그가 기록되었는지 확인합니다.
로그에 오류가 표시되는 경우:
- HTTP 401: 환경 변수에서 OAuth 사용자 인증 정보 확인
- HTTP 403: ISU에 사용자 활동 로깅 도메인에 필요한 권한이 있는지 확인
- HTTP 429: 비율 제한 - 함수가 백오프로 자동 재시도됨
- 액세스 토큰을 가져오지 못함:
WORKDAY_HOST,WORKDAY_TENANT,CLIENT_ID,CLIENT_SECRET,REFRESH_TOKEN이 올바른지 확인합니다.
Workday 사용자 활동 로그를 수집하도록 Google SecOps에서 피드 구성
- SIEM 설정 > 피드로 이동합니다.
- 새 피드 추가를 클릭합니다.
- 단일 피드 구성을 클릭합니다.
- 피드 이름 필드에 피드 이름을 입력합니다(예:
Workday User Activity Logs). - 소스 유형으로 Google Cloud Storage V2를 선택합니다.
- 로그 유형으로 Workday 사용자 활동을 선택합니다.
서비스 계정 가져오기를 클릭합니다. 고유한 서비스 계정 이메일이 표시됩니다. 예를 들면 다음과 같습니다.
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com이 이메일 주소를 복사합니다. 다음 단계에서 이를 사용합니다.
다음을 클릭합니다.
다음 입력 매개변수의 값을 지정합니다.
스토리지 버킷 URL: 다음 접두사 경로를 사용하여 Cloud Storage 버킷 URI를 입력합니다.
gs://workday-activity-logs/workday/- 다음과 같이 바꿉니다.
workday-activity-logs: Cloud Storage 버킷 이름workday: 로그가 저장되는 선택적 접두사 또는 경로입니다 (루트의 경우 비워 둠).
- 다음과 같이 바꿉니다.
소스 삭제 옵션: 환경설정에 따라 삭제 옵션을 선택합니다.
- 파일 삭제 안함: 소스에서 파일을 삭제하지 않습니다 (테스트에 권장).
- 전송된 파일 및 빈 디렉터리 삭제: 가져오기가 완료되면 소스에서 파일과 빈 디렉터리를 삭제합니다.
최대 파일 기간: 지난 일수 동안 수정된 파일을 포함합니다 (기본값은 180일).
애셋 네임스페이스: 애셋 네임스페이스입니다.
수집 라벨: 이 피드의 이벤트에 적용할 라벨입니다.
다음을 클릭합니다.
확정 화면에서 새 피드 구성을 검토한 다음 제출을 클릭합니다.
Google SecOps 서비스 계정에 IAM 권한 부여
Google SecOps 서비스 계정에는 Cloud Storage 버킷에 대한 두 가지 역할이 필요합니다. 로그 객체를 읽기 위한 스토리지 객체 뷰어와 버킷 메타데이터를 읽기 위한 버킷 수준 역할입니다.
- Cloud Storage> 버킷으로 이동합니다.
- 버킷 이름 (
workday-activity-logs)을 클릭합니다. - 권한 탭으로 이동합니다.
- 액세스 권한 부여를 클릭합니다.
- 다음 구성 세부정보를 제공합니다.
- 주 구성원 추가: Google SecOps 서비스 계정 이메일을 붙여넣습니다.
- 역할 할당: 다음 두 가지를 모두 선택합니다.
- 스토리지 객체 뷰어: 로그 객체를 읽습니다.
- 스토리지 기존 버킷 리더: 버킷 메타데이터를 읽습니다. 전송된 파일 및 빈 디렉터리 삭제 삭제 옵션을 선택한 경우 삭제 권한도 부여하는 스토리지 기존 버킷 작성자를 대신 선택합니다.
저장을 클릭합니다.
UDM 매핑 테이블
| 로그 필드 | UDM 매핑 | 로직 |
|---|---|---|
Account_Locked__Disabled_or_Expired_label |
additional.fields |
병합됨 |
Authentication_Type_label |
additional.fields |
병합됨 |
Created_Moment_label |
additional.fields |
병합됨 |
Current_OMS_Version_label |
additional.fields |
병합됨 |
Device_is_Trusted_label |
additional.fields |
병합됨 |
Elapsed_Time__Minutes_label |
additional.fields |
병합됨 |
Failed_Sign_On_label |
additional.fields |
병합됨 |
Hire_Date_for_Signon_Worker_label |
additional.fields |
병합됨 |
Job_Family_for_Signon_Worker_label |
additional.fields |
병합됨 |
Operating_System_label |
additional.fields |
병합됨 |
Pay_Rate_type_label |
additional.fields |
병합됨 |
Request_Originator_label |
additional.fields |
병합됨 |
SAML_Identity_Provider_label |
additional.fields |
병합됨 |
Sign-on_Time_label |
additional.fields |
병합됨 |
Signoff_Time_label |
additional.fields |
병합됨 |
Signon_Day_of_the_Week_label |
additional.fields |
병합됨 |
Signon_Hour_of_Day_label |
additional.fields |
병합됨 |
Tenant_Access_Read_Only_label |
additional.fields |
병합됨 |
UI_Client_Type_label |
additional.fields |
병합됨 |
Worker_Active_for_Signon_Worker_label |
additional.fields |
병합됨 |
Worker_Type_for_Signon_Worker_label |
additional.fields |
병합됨 |
awaiting_details_label |
additional.fields |
병합됨 |
data_label |
additional.fields |
병합됨 |
evp_nme_label |
additional.fields |
병합됨 |
evpminus_label |
additional.fields |
병합됨 |
signon_ip_address_label |
additional.fields |
병합됨 |
@requestTime |
metadata.event_timestamp |
ISO8601로 파싱됨 |
Sign-on_Time |
metadata.event_timestamp |
yyyy-MM-ddTHH:mm:ssZZ로 파싱됨 |
event_type |
metadata.event_type |
직접 매핑됨 |
status_code |
metadata.product_event_type |
직접 매핑됨 |
tenant |
metadata.product_event_type |
직접 매핑됨 |
taskId |
metadata.product_log_id |
직접 매핑됨 |
User_Agent |
network.http.parsed_user_agent |
이름이 변경됨/매핑됨 |
Browser_Type |
network.http.user_agent |
직접 매핑됨 |
User_Agent |
network.http.user_agent |
직접 매핑됨 |
userAgent |
network.http.user_agent |
직접 매핑됨 |
Session_ID |
network.session_id |
직접 매핑됨 |
sessionId |
network.session_id |
직접 매핑됨 |
Client_TLS_Version |
network.tls.version |
이름이 변경됨/매핑됨 |
Worker |
principal.asset.hostname |
직접 매핑됨 |
ip |
principal.asset.ip |
병합됨 |
ipaddress |
principal.asset.ip |
병합됨 |
Worker |
principal.hostname |
직접 매핑됨 |
manager_name |
principal.hostname |
직접 매핑됨 |
ip |
principal.ip |
병합됨 |
ipaddress |
principal.ip |
병합됨 |
Signon_Worker_label |
principal.user.attribute.labels |
병합됨 |
Supervisory_Organization_of_Signon_Worker_label |
principal.user.attribute.labels |
병합됨 |
System_Account_label |
principal.user.attribute.labels |
병합됨 |
Workday_Account_Sign_On_label |
principal.user.attribute.labels |
병합됨 |
user_credentials_label |
principal.user.attribute.labels |
병합됨 |
User_Name |
principal.user.email_addresses |
병합됨 |
Role |
principal.user.user_display_name |
직접 매핑됨 |
manager_id |
principal.user.userid |
직접 매핑됨 |
Active_Session_label |
security_result.detection_fields |
병합됨 |
Authentication_Failure_Message_label |
security_result.detection_fields |
병합됨 |
Forgotten_Password_Reset_Request_label |
security_result.detection_fields |
병합됨 |
Invalid_Credentials_label |
security_result.detection_fields |
병합됨 |
Is_Device_Managed_label |
security_result.detection_fields |
병합됨 |
MFA_Exempt_label |
security_result.detection_fields |
병합됨 |
Password_Changed_label |
security_result.detection_fields |
병합됨 |
Position_label |
security_result.detection_fields |
병합됨 |
taskDisplayName |
security_result.summary |
직접 매핑됨 |
hostname |
target.hostname |
직접 매핑됨 |
CITY_NME |
target.location.city |
직접 매핑됨 |
Location |
target.location.city |
직접 매핑됨 |
COUNTRY_NME |
target.location.country_or_region |
직접 매핑됨 |
Location_Country_for_Signon_Worker |
target.location.country_or_region |
직접 매핑됨 |
Location_State |
target.location.state |
직접 매핑됨 |
STATE_NME |
target.location.state |
직접 매핑됨 |
Exempt_labels |
target.resource.attribute.labels |
병합됨 |
browser_type_label |
target.resource.attribute.labels |
병합됨 |
class_of_instance_label |
target.resource.attribute.labels |
병합됨 |
device |
target.resource.resource_subtype |
직접 매핑됨 |
device1 |
target.resource.resource_subtype |
직접 매핑됨 |
device |
target.resource.type |
매핑됨: (?i)(Desktop/Laptop/Mobile/Phone) → DEVICE |
device1 |
target.resource.type |
매핑됨: (?i)(Desktop/Laptop/Mobile/Phone) → DEVICE |
BUSINESSGROUP_NME |
target.user.department |
병합됨 |
email_name |
target.user.email_addresses |
병합됨 |
EMPLOYEE_ID |
target.user.employee_id |
직접 매핑됨 |
Employee_ID |
target.user.employee_id |
직접 매핑됨 |
FIRST_NME |
target.user.first_name |
직접 매핑됨 |
EMPLOYEE_TYPE |
target.user.group_identifiers |
병합됨 |
LAST_NME |
target.user.last_name |
직접 매핑됨 |
JOBTITLE_NME |
target.user.title |
직접 매핑됨 |
title |
target.user.title |
직접 매핑됨 |
target.id |
target.user.userid |
직접 매핑됨 |
| 해당 사항 없음 | metadata.product_name |
상수: WORKDAY_USER_ACTIVITY |
| 해당 사항 없음 | metadata.vendor_name |
상수: WORKDAY_USER_ACTIVITY |
| 해당 사항 없음 | target.resource.type |
상수: DEVICE |
변경 로그
도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가에게 문의하여 답변을 받으세요.