시리즈: gcloud CLI 자동화 운영 플레이북 (총 11편) | 8회
CLI 보안 완성하기: 키리스 운영·KMS 감사·SCC 탐지 데이터 내보내기 실전 가이드
gcloud CLI를 쓰면서 서비스계정 키 JSON 파일을 로컬에 저장하고 있다면, 지금 바로 읽어야 할 글이에요. 키리스 인증 전환부터 KMS 감사 로그, SCC 데이터 파이프라인까지 CLI 보안의 전부를 다뤄요.
Summary
- CLI 운영 최대 리스크는 로컬에 남는 서비스계정 키(JSON) 파일이야 — 조직 정책으로 키 생성 자체를 차단하는 게 답
- KMS의 Encrypt/Decrypt는 Data Access 로그에 기록되는데, 기본 비활성화라서 반드시 수동으로 켜야 해
- SCC findings는 Pub/Sub·BigQuery로 연속 내보내기가 가능하고, 이걸 자동화하면 보안 파이프라인이 완성돼
- 보안은 “도구 설정”이 아니라 “정책 + 로그 + 경보” 3세트를 묶어야 의미가 있어
이 글의 대상
- GCP CLI 환경에서 서비스계정 키를 없애고 싶은 보안 담당자
- KMS 키 사용 이력을 감사해야 하는 컴플라이언스 팀
- SCC 탐지 결과를 자동으로 수집·분석하는 파이프라인을 구축하려는 엔지니어
목차
- CLI 보안의 최대 리스크: 로컬 자격증명
- 서비스계정 키 생성 금지 정책
- 키리스 인증으로 전환하기
- KMS: Data Access 로그 활성화 필수
- SCC: 탐지 데이터를 파이프라인으로 내보내기
- 보안 운영 통합 체크리스트
- 실패 케이스와 트러블슈팅
1. CLI 보안의 최대 리스크: 로컬 자격증명
왜 서비스계정 키가 위험한가
CLI 운영에서 가장 흔하고 가장 위험한 습관이 뭔지 알아? 서비스계정 키(JSON 파일)를 만들어서 로컬이나 CI 서버에 뿌리는 것이야.
이게 왜 문제냐면:
| 위험 요소 | 설명 | 실제 사례 |
|---|---|---|
| 키 유출 | JSON 파일이 Git에 커밋되거나 공유 폴더에 노출 | GitHub 공개 리포에 키 파일 커밋 → 수 분 내 크립토마이닝 |
| 키 회전 부재 | 만들고 나서 영영 안 바꾸는 키 | 퇴사자 키가 2년째 활성 상태 |
| 과도한 권한 | 편의상 Owner/Editor 권한 부여 | 테스트용 키에 프로젝트 전체 권한 |
| 추적 불가 | 누가 언제 어디서 키를 사용했는지 확인 어려움 | 보안 사고 후 원인 추적 불가 |
Google도 이걸 명확하게 경고하고 있어: 사용자 관리 키를 최소화하라는 게 공식 권장이야.
현실 점검: 지금 키 파일이 어디에 있어?
# 로컬에 있는 서비스계정 키 파일 찾기
find ~ -name "*.json" -exec grep -l "private_key" {} \; 2>/dev/null
# gcloud 인증 상태 확인
gcloud auth list
# 활성 서비스계정 확인
gcloud config get-value account
만약 위 명령어로 키 파일이 발견됐다면, 이 글을 끝까지 읽고 키리스로 전환하는 걸 추천해.
2. 서비스계정 키 생성 금지 정책
조직 정책으로 키 생성 자체를 차단
개인의 의지로 “키를 안 만들자”라고 하면 지켜질까? 안 지켜져. 그래서 조직 정책(Organization Policy)으로 아예 키 생성을 차단하는 게 맞아.
# 서비스계정 키 생성 금지 정책 설정
gcloud resource-manager org-policies enable-enforce \
constraints/iam.disableServiceAccountKeyCreation \
--organization=ORGANIZATION_ID
이 정책을 활성화하면:
- 조직 내 모든 프로젝트에서 서비스계정 키 생성이 차단돼
- 기존에 만든 키는 계속 동작해 (삭제는 별도로 해야 해)
- 예외가 필요한 프로젝트는 폴더/프로젝트 수준에서 override 가능
단계별 전환 전략
한 번에 모든 키를 없애는 건 현실적으로 무리야. 단계별로 접근해야 해:
키리스 전환 로드맵:
├─ 1단계: 현황 파악 (2주)
│ ├─ 모든 서비스계정 키 목록 추출
│ ├─ 키별 마지막 사용 시점 확인
│ └─ 키별 사용처 매핑
├─ 2단계: 미사용 키 정리 (2주)
│ ├─ 90일 이상 미사용 키 비활성화
│ └─ 30일 관찰 후 삭제
├─ 3단계: 키리스 전환 (4주)
│ ├─ CI/CD → Workload Identity Federation
│ ├─ 로컬 개발 → gcloud auth application-default login
│ └─ GCE/GKE → 인스턴스 서비스계정
└─ 4단계: 정책 강제 (1주)
└─ iam.disableServiceAccountKeyCreation 활성화
서비스계정 키 현황 점검 명령어
# 프로젝트 내 모든 서비스계정 목록
gcloud iam service-accounts list \
--project=my-project \
--format='table(email, disabled)'
# 특정 서비스계정의 키 목록
gcloud iam service-accounts keys list \
--iam-account=sa@my-project.iam.gserviceaccount.com \
--format='table(keyId, validAfterTime, validBeforeTime, keyType)'
# 키 사용 이력 확인 (Cloud Audit Logs)
gcloud logging read \
'resource.type="service_account" AND
protoPayload.methodName="google.iam.credentials.v1.IAMCredentials.GenerateAccessToken"' \
--project=my-project \
--limit=20 \
--format=json
3. 키리스 인증으로 전환하기
환경별 키리스 인증 방법
키를 없앴으면 대안이 있어야지. 각 환경별로 키리스 인증 방법이 달라:
| 환경 | 인증 방법 | 설명 |
|---|---|---|
| 로컬 개발 | gcloud auth application-default login |
사용자 자격증명 기반, 임시 토큰 |
| GCE/GKE | 인스턴스 서비스계정 | 메타데이터 서버에서 자동 토큰 발급 |
| GitHub Actions | Workload Identity Federation (WIF) | OIDC 토큰으로 GCP 토큰 교환 |
| Cloud Build | 빌드 서비스계정 | 자동 연결, 키 불필요 |
| Cloud Run/Functions | 런타임 서비스계정 | 배포 시 지정, 키 불필요 |
로컬 개발 환경 설정
# ADC(Application Default Credentials) 설정
gcloud auth application-default login
# 프로젝트 쿼터 프로젝트 지정 (비용 추적용)
gcloud auth application-default set-quota-project my-project
# 인증 상태 확인
gcloud auth application-default print-access-token > /dev/null && \
echo "인증 정상" || echo "인증 실패"
이렇게 하면 키 파일 없이도 로컬에서 GCP API를 사용할 수 있어. 토큰은 자동으로 갱신되고, ~/.config/gcloud/application_default_credentials.json에 저장되는데, 이건 사용자 자격증명이라 서비스계정 키보다 안전해.
Workload Identity Federation (WIF) — CI/CD용
외부 CI에서 GCP에 인증할 때 가장 안전한 방법이야:
# 1. Workload Identity Pool 생성
gcloud iam workload-identity-pools create "github-pool" \
--location="global" \
--display-name="GitHub Actions Pool"
# 2. OIDC Provider 추가 (GitHub용)
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
# 3. 서비스계정에 impersonate 권한 부여
gcloud iam service-accounts add-iam-policy-binding \
sa@my-project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"
WIF의 핵심은 키 파일이 전혀 필요 없다는 거야. GitHub Actions가 발급하는 OIDC 토큰을 GCP가 검증해서 단기 토큰을 발급하는 구조지.
4. KMS: Data Access 로그 활성화 필수
KMS 감사 로깅의 함정
KMS(Key Management Service)로 암호화/복호화를 관리하고 있다면, “누가 언제 키를 사용했는지” 추적할 수 있어야 해. 그런데 여기에 함정이 있어.
KMS의 Encrypt/Decrypt 같은 키 사용 로그는 Data Access 로그에 기록되는데, Data Access 로그는 기본적으로 비활성화일 수 있어.
이걸 모르고 있다가 보안 감사 때 “복호화 이력 보여달라”는 요청이 오면 답이 없는 거야.
Data Access 로그 활성화
# 프로젝트의 현재 감사 로그 설정 확인
gcloud projects get-iam-policy my-project \
--format=json | jq '.auditConfigs'
# KMS Data Access 로그 활성화
# (IAM 정책에 auditConfig 추가)
gcloud projects set-iam-policy my-project policy.yaml
policy.yaml에 추가할 내용:
auditConfigs:
- service: cloudkms.googleapis.com
auditLogConfigs:
- logType: ADMIN_READ
- logType: DATA_READ
- logType: DATA_WRITE
로그 확인 명령어
# KMS 키 사용 로그 조회
gcloud logging read \
'resource.type="cloudkms_cryptokey" AND
protoPayload.methodName="Decrypt"' \
--project=my-project \
--limit=10 \
--format='table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.resourceName)'
KMS 감사 체계 3세트
단순히 로그를 켜는 것만으로는 부족해. Data Access 활성화 + 싱크(장기보관) + 경보, 이 3세트를 묶어야 의미가 있어:
KMS 감사 체계:
├─ 1. Data Access 로그 활성화
│ └─ cloudkms.googleapis.com의 DATA_READ, DATA_WRITE
├─ 2. 로그 싱크로 장기보관
│ ├─ BigQuery로 내보내기 (분석용)
│ └─ Cloud Storage로 내보내기 (아카이브용)
└─ 3. 경보 설정
├─ 비정상 복호화 패턴 감지
├─ 새벽 시간 키 사용 알림
└─ 특정 IP 대역 외 접근 알림
로그 싱크 생성
# BigQuery로 KMS 로그 내보내기
gcloud logging sinks create kms-audit-sink \
bigquery.googleapis.com/projects/my-project/datasets/kms_audit \
--log-filter='resource.type="cloudkms_cryptokey"' \
--project=my-project
# 싱크 서비스계정에 BigQuery 쓰기 권한 부여
# (싱크 생성 시 출력되는 서비스계정에 권한 부여 필요)
gcloud logging sinks describe kms-audit-sink \
--project=my-project \
--format='value(writerIdentity)'
5. SCC: 탐지 데이터를 파이프라인으로 내보내기
SCC(Security Command Center)란
SCC는 GCP의 보안 위협을 자동으로 탐지하는 서비스야. 예를 들면:
- 공개 접근 가능한 Storage 버킷 감지
- 방화벽 규칙의 과도한 허용 감지
- 취약한 소프트웨어 버전 감지
- 암호화되지 않은 디스크 감지
CLI로 SCC findings 조회
# 활성 findings 목록 조회
gcloud scc findings list organizations/ORG_ID \
--source='-' \
--filter='state="ACTIVE"' \
--format='table(finding.category, finding.severity, finding.resourceName)' \
--limit=20
# 심각도별 findings 카운트
gcloud scc findings list organizations/ORG_ID \
--source='-' \
--filter='state="ACTIVE"' \
--format=json | jq 'group_by(.finding.severity) | map({severity: .[0].finding.severity, count: length})'
# 특정 카테고리 findings 상세 조회
gcloud scc findings list organizations/ORG_ID \
--source='-' \
--filter='state="ACTIVE" AND finding.category="PUBLIC_BUCKET_ACL"' \
--format=json
SCC 데이터를 Pub/Sub로 내보내기
SCC findings를 실시간으로 수집하려면 Pub/Sub로 연속 내보내기가 가장 효과적이야:
# 1. Pub/Sub 토픽 생성
gcloud pubsub topics create scc-findings-topic \
--project=security-project
# 2. SCC 연속 내보내기 설정 (Notification Config)
gcloud scc notifications create scc-all-findings \
--organization=ORG_ID \
--pubsub-topic=projects/security-project/topics/scc-findings-topic \
--filter='state="ACTIVE"'
# 3. 내보내기 설정 확인
gcloud scc notifications describe scc-all-findings \
--organization=ORG_ID
SCC 데이터를 BigQuery로 내보내기
장기 분석이 필요하면 BigQuery로 내보내는 게 좋아:
# BigQuery 데이터셋 생성
bq mk --dataset security-project:scc_findings
# SCC → BigQuery 내보내기 설정
gcloud scc bq-export create scc-bq-export \
--organization=ORG_ID \
--dataset=projects/security-project/datasets/scc_findings \
--filter='state="ACTIVE" AND severity="HIGH" OR severity="CRITICAL"'
SCC 데이터 파이프라인 아키텍처
SCC 보안 데이터 파이프라인:
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ SCC │───▶│ Pub/Sub │───▶│ Cloud │
│ Findings │ │ Topic │ │ Functions │
└──────────┘ └──────────┘ └──────┬───────┘
│ │
│ ┌──────────┐ │
└────────▶│ BigQuery │◀──────────┘
│ (분석) │
└─────┬────┘
│
┌─────▼────┐
│ Dashboard│
│ (Looker) │
└──────────┘
SCC 데이터 내보내기 시 주의사항
SCC findings 데이터 자체가 민감 정보를 포함할 수 있어. 내보낸 데이터에 대한 접근 통제도 신경 써야 해:
| 주의사항 | 대응 방법 |
|---|---|
| Findings에 리소스 이름·IP 등 민감 정보 포함 | BigQuery 데이터셋에 IAM 최소 권한 |
| Pub/Sub 메시지 보존 기간과 충돌 가능 | 메시지 보존 정책 사전 점검 |
| 대량 findings 시 비용 증가 | severity 필터로 HIGH/CRITICAL만 내보내기 |
| 내보낸 데이터의 암호화 | CMEK(고객 관리 암호화 키) 적용 검토 |
6. 보안 운영 통합 체크리스트
| 영역 | 체크 항목 | CLI 명령어/설정 | 우선순위 |
|---|---|---|---|
| 자격증명 | 서비스계정 키 현황 파악 | gcloud iam service-accounts keys list |
즉시 |
| 자격증명 | 90일 미사용 키 비활성화 | 키 목록에서 마지막 사용일 확인 | 1주 내 |
| 자격증명 | CI/CD WIF 전환 | gcloud iam workload-identity-pools create |
2주 내 |
| 자격증명 | 키 생성 금지 정책 | iam.disableServiceAccountKeyCreation |
전환 완료 후 |
| KMS | Data Access 로그 활성화 | IAM 정책에 auditConfig 추가 | 즉시 |
| KMS | 로그 싱크 설정 | gcloud logging sinks create |
1주 내 |
| KMS | 비정상 사용 경보 | Cloud Monitoring 알림 정책 | 2주 내 |
| SCC | findings 실시간 내보내기 | gcloud scc notifications create |
1주 내 |
| SCC | BigQuery 장기 저장 | gcloud scc bq-export create |
2주 내 |
| SCC | 내보낸 데이터 접근 통제 | IAM 최소 권한 | 즉시 |
7. 실패 케이스와 트러블슈팅
케이스 1: 서비스계정 키가 GitHub에 커밋됨
상황: 개발자가 실수로 service-account.json을 공개 리포에 push
대응 순서:
# 1. 즉시 키 비활성화 (삭제가 아닌 비활성화 먼저)
gcloud iam service-accounts keys disable KEY_ID \
--iam-account=sa@project.iam.gserviceaccount.com
# 2. 해당 서비스계정의 최근 활동 로그 확인
gcloud logging read \
'protoPayload.authenticationInfo.principalEmail="sa@project.iam.gserviceaccount.com"' \
--project=my-project \
--freshness=24h \
--format=json
# 3. 비정상 활동이 없으면 키 삭제
gcloud iam service-accounts keys delete KEY_ID \
--iam-account=sa@project.iam.gserviceaccount.com \
--quiet
# 4. Git 히스토리에서 파일 제거 (BFG 또는 git filter-branch)
# 5. 키 생성 금지 정책 적용
케이스 2: KMS Data Access 로그가 안 보여
상황: 감사팀에서 복호화 이력을 요청했는데 로그가 비어 있음
원인: Data Access 로그가 비활성화 상태
해결:
# 현재 감사 로그 설정 확인
gcloud projects get-iam-policy my-project \
--format=json | jq '.auditConfigs[] | select(.service == "cloudkms.googleapis.com")'
# 결과가 비어 있으면 활성화가 안 된 것
# policy.yaml에 auditConfig 추가 후 적용
# 주의: 활성화 이전 로그는 복구 불가!
# → 즉시 활성화하고, 감사팀에 "활성화 이전 이력은 없음" 보고
케이스 3: SCC 내보내기가 갑자기 중단
상황: Pub/Sub로 SCC findings가 안 들어오기 시작
확인 절차:
# 1. 내보내기 설정 상태 확인
gcloud scc notifications list --organization=ORG_ID
# 2. Pub/Sub 토픽 상태 확인
gcloud pubsub topics describe scc-findings-topic \
--project=security-project
# 3. 구독 backlog 확인
gcloud pubsub subscriptions describe scc-findings-sub \
--project=security-project \
--format='value(numUndeliveredMessages)'
# 4. IAM 권한 확인 (SCC → Pub/Sub 발행 권한)
gcloud pubsub topics get-iam-policy scc-findings-topic \
--project=security-project
핵심 정리
1. 서비스계정 키(JSON)는 조직 정책으로 생성 자체를 차단하고, 키리스(WIF, ADC)로 전환해야 해
2. KMS Data Access 로그는 기본 비활성화 — 반드시 수동으로 켜고, 싱크 + 경보까지 세트로 구성해
3. SCC findings는 Pub/Sub·BigQuery로 연속 내보내기해서 보안 데이터 파이프라인을 만들어야 해
4. 보안 설정은 "한 번 하고 끝"이 아니라, 정책 + 로그 + 경보 3세트를 항상 같이 관리해야 해
FAQ
Q. 서비스계정 키를 당장 전부 없앨 수 있어?
A. 현실적으로 한 번에 없애기는 어려워. 먼저 gcloud iam service-accounts keys list로 전체 키 현황을 파악하고, 90일 이상 미사용 키부터 비활성화→삭제하는 순서로 진행하는 게 안전해. CI/CD는 WIF로, 로컬은 ADC로 단계적으로 전환하면 돼.
Q. Workload Identity Federation(WIF)이 뭐야? 왜 키보다 안전해?
A. WIF는 외부 ID 제공자(GitHub, GitLab 등)의 토큰을 GCP가 검증해서 단기 토큰을 발급하는 방식이야. 키 파일이 아예 없으니 유출 위험이 없고, 토큰이 단기(1시간)라 탈취돼도 피해가 제한적이야. GitHub Actions에서는 google-github-actions/auth 액션으로 쉽게 구현할 수 있어.
Q. gcloud auth application-default login과 gcloud auth login은 뭐가 달라?
A. gcloud auth login은 gcloud CLI 자체를 인증하는 거고, gcloud auth application-default login은 애플리케이션 코드가 사용하는 인증이야. 로컬에서 Python/Java 등의 GCP 클라이언트 라이브러리를 쓴다면 ADC(application-default)를 설정해야 해. 둘 다 하는 게 보통이야.
Q. KMS Data Access 로그를 켜면 비용이 많이 들어?
A. Data Access 로그는 양에 따라 Cloud Logging 비용이 발생해. KMS 사용 빈도가 높으면 로그 양도 많아지거든. 비용을 통제하려면 로그 제외(exclusion) 필터로 불필요한 로그를 걸러내거나, 로그 싱크로 BigQuery에 직접 보내서 기본 로그 보존 기간을 줄이는 방법이 있어.
Q. SCC findings를 Pub/Sub 대신 직접 BigQuery로 보내면 안 돼?
A. BigQuery로 직접 내보내기도 가능해. gcloud scc bq-export create로 설정할 수 있어. 차이는 이거야 — Pub/Sub는 실시간 알림/자동 대응에 적합하고, BigQuery는 장기 분석/대시보드에 적합해. 보통은 둘 다 설정해서 실시간 대응 + 분석을 병행하는 게 좋아.
Q. SCC 무료 티어(Standard)로도 내보내기가 돼?
A. SCC Standard 티어에서는 기본적인 findings 조회와 일부 내보내기가 가능해. 하지만 연속 내보내기(Notification Config)와 고급 탐지 기능은 Premium 티어에서 제공되는 것들이 더 많아. 조직 규모와 보안 요구 수준에 따라 결정하면 돼.
Q. 조직 정책으로 키 생성을 차단하면 예외 처리는 어떻게 해?
A. 특정 프로젝트나 폴더에 예외를 둘 수 있어. gcloud resource-manager org-policies set-policy 명령으로 프로젝트/폴더 수준에서 override하면 돼. 다만 예외가 많아지면 정책의 의미가 없어지니까, 예외 목록을 주기적으로 리뷰하고 줄여나가는 게 중요해.
Q. 이미 유출된 서비스계정 키는 어떻게 처리해?
A. 즉시 4단계로 대응해야 해. 1) 키 비활성화, 2) 해당 SA의 최근 활동 로그 확인(비정상 활동 여부), 3) 비정상 리소스 정리(크립토마이닝 인스턴스 등), 4) 키 삭제 및 SA 권한 재검토. 특히 키 비활성화를 삭제보다 먼저 하는 이유는, 로그 추적 중에 키가 사라지면 분석이 어려워지기 때문이야.
Q. Data Access 로그 활성화 전의 KMS 사용 이력을 복구할 수 있어?
A. 안타깝지만 복구가 불가능해. Data Access 로그는 활성화된 시점부터만 기록돼. 그래서 KMS를 처음 설정할 때 바로 Data Access 로그를 함께 켜는 게 중요한 거야. 감사 목적이라면 이 점을 감사팀에 미리 공유하고, 앞으로의 로그 보존 정책을 합의하는 게 좋아.
참고 자료 (References)
데이터 출처
| 출처 | 설명 | 링크 |
|---|---|---|
| 서비스 계정 키 베스트프랙티스 | 사용자 관리 키 최소화 가이드 | GCP IAM 문서 |
| 서비스 계정 제한 조직 정책 | 키 생성 차단 조직 정책 설명 | Organization Policy |
| KMS 감사 로깅 | KMS Data Access 로그 가이드 | KMS 감사 로깅 |
| Data Access 로그 활성화 | Data Access 로그 설정 방법 | Configure Data Access |
| SCC 데이터 내보내기 | SCC findings 내보내기 가이드 | SCC Export |
| Workload Identity Federation | 외부 워크로드 키리스 인증 | WIF 문서 |
핵심 인용
“가장 위험한 습관은 서비스계정 키(JSON)를 만들어 로컬·CI에 뿌리는 것이다. Google은 사용자 관리 키 최소화를 명확히 권고한다.” — Google Cloud IAM 베스트프랙티스
다음 편 예고
[9편] 스크립팅·배치·CI/CD: 비대화형 실행으로 CLI 자동화 완성하기
- 자동화 3대 표준: –quiet + –format + 종료 코드 실전 적용
- GitHub Actions에서 google-github-actions/auth로 키리스 CI 구현
- Secret Manager 중앙화와 런타임 주입 패턴
