Cloud Monitoring consumed_api 메트릭으로 GCP API 키 사용 패턴을 추적한다
·수정 2026.05.13·수정 1회
요약
consumed_api::serviceruntime.googleapis.com/api/request_count메트릭은 API 키별·메서드별·응답코드별 호출 시계열을 담고 있다- MQL(Monitoring Query Language)로 쿼리하면 호출자 IP만 빼고 거의 모든 정보 추출 가능
- 윈도우를 1d → 1h → 10m으로 점진 확장하며 폭증 시점을 분 단위까지 좁힐 수 있음
본문
메트릭 구조
리소스 타입: consumed_api
메트릭: serviceruntime.googleapis.com/api/request_count
보관: 약 2년 (730일)
라벨 (모두 필터/group_by 가능):
| 라벨 | 종류 | 예시 |
|---|---|---|
resource.credential_id |
어떤 키 | apikey:8783620425315600619, serviceaccount:115030646863660571478 |
resource.service |
어떤 API | generativelanguage.googleapis.com |
resource.method |
어떤 메서드 | google.ai.generativelanguage.v1beta.GenerativeService.GenerateContent |
resource.version |
API 버전 | v1, v1beta |
resource.location |
호출 리전 | us-central1, asia-southeast1 |
resource.project_id |
프로젝트 | connecting-208115 |
metric.response_code |
HTTP 응답 | 200, 400, 404, 429, 503 |
metric.response_code_class |
응답 클래스 | 2xx, 4xx, 5xx |
metric.protocol |
프로토콜 | grpc, http, grpc_loas2 |
metric.grpc_status_code |
gRPC 상태 | 0, 8, 14 |
기본 쿼리 패턴
ACCESS_TOKEN=$(gcloud auth print-access-token)
curl -s "https://monitoring.googleapis.com/v3/projects/PROJECT_ID/timeSeries:query" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "<MQL>"}'
핵심 MQL 템플릿
1) 특정 키의 서비스/메서드별 사용량 (가장 자주 씀)
fetch consumed_api::serviceruntime.googleapis.com/api/request_count
| filter (resource.credential_id == 'apikey:KEY_UID')
| within 90d
| every 1d
| group_by [resource.service, resource.method, metric.response_code], sum(value.request_count)
| top 100
2) 프로젝트 내 특정 API를 호출하는 모든 키 (유출 키 색출)
fetch consumed_api::serviceruntime.googleapis.com/api/request_count
| filter (resource.service == 'generativelanguage.googleapis.com')
| within 730d
| every 30d
| group_by [resource.credential_id], sum(value.request_count)
3) 폭증 시점 분 단위 추적
fetch consumed_api::serviceruntime.googleapis.com/api/request_count
| filter (resource.credential_id == 'apikey:KEY_UID' && resource.service == 'TARGET_SERVICE')
| within 2d
| every 10m
| group_by [metric.response_code], sum(value.request_count)
4) 리전 분포 (공격자 위치 변화 추적)
fetch consumed_api::serviceruntime.googleapis.com/api/request_count
| filter (resource.credential_id == 'apikey:KEY_UID')
| within 120d
| every 1d
| group_by [resource.location], sum(value.request_count)
시간 윈도우 점진 확장 전략
1단계: within 30d, every 1d → 큰 그림, 폭증 여부
2단계: within 7d, every 1h → 폭증 일자 정밀화
3단계: within 2d, every 10m → 분 단위 시작/종료 시각
4단계: within 730d, every 30d → 보관 한계까지 봐서 첫 호출 시점 확인
MQL 주의사항
every <interval>의 단위가align윈도우보다 작으면 에러:rate(1m)+every 1d같이 쓰면 "alignment period is 1d"라며 거부됨top N은 group_by 후 합계 상위 N개 시계열만 반환. 누락 우려 있으면top 200이상으로filter안에서 문자열 비교는==(single equal)이 아니라==맞음.&&로 AND,||로 ORint64Value또는doubleValue둘 다 들어올 수 있어서 응답 파싱 시 양쪽 모두 처리
어뷰징 패턴 시그니처
이 메트릭만으로 식별 가능한 유출 신호:
- 메서드 불일치: Firebase Android 키인데
generativelanguage호출 - 시간 패턴 이상: 오랜 기간 0 → 갑자기 폭증 (정상 앱은 점진 증가)
- 응답코드 분포: 200 비율 < 80%, 특히 429(rate limit) 폭증 = 한계까지 긁기
- 리전 jumping:
resource.location가 자주 바뀌면 VPN/프록시 의심 - 정찰 메서드:
ListModels,ListCollectionIds,ListFiles같은 열거 메서드는 정상 SDK가 거의 안 부름
한계
- ❌ 호출자 IP 안 보임 → Cloud Audit Data Access Log 필요 (GCP API 키 호출자 IP는 Cloud Audit Data Access Log를 켜야 추적할 수 있다)
- ❌ 요청 페이로드 안 보임
- ❌
apikey:UNKNOWN: credential_id 매핑 실패 시 UNKNOWN 으로 기록됨 (공격 트래픽 일부가 여기 떨어질 수 있음) - ❌ 분당 호출 수가 너무 적으면 정상화/0으로 떨어져 추적 어려움
실전 활용 흐름
- 유출 의심: 광범위 윈도우(730d, 30d 단위)로 어떤 키가 의심 API 호출하는지 색출
- 타임라인 구축: 점진 좁히기로 첫 호출 시점 → 첫 성공 시점 → 폭증 시점 추적
- 패턴 분류: 메서드/응답코드/리전 분포로 정상 vs 어뷰징 판별
- 차단:
gcloud services api-keys update로 apiTargets 화이트리스트 적용 - 차단 후 검증: 동일 쿼리로 어뷰징 트래픽 사라졌는지 / 정상 트래픽 유지되는지 확인