- 아키텍처 다이어그램 추가 - Vault 초기 설정 (KV 시크릿 엔진, 정책, AppRole) - Cloudflare API Token 생성 가이드 - CLI 사용법 및 자동화 스크립트 (Fish, Bash) - Terraform 통합 예제 - CI/CD 통합 (GitHub Actions, GitLab CI) - 문제 해결 가이드 - 보안 체크리스트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
14 KiB
Cloudflare + HashiCorp Vault 통합 가이드
Cloudflare API 크레덴셜을 Vault에서 안전하게 관리하는 완벽 가이드
목차
- 개요
- 아키텍처
- Vault 초기 설정
- Cloudflare API Token 생성
- Vault에 크레덴셜 저장
- CLI 사용법
- 자동화 스크립트
- Terraform 통합
- CI/CD 통합
- 문제 해결
개요
왜 Vault를 사용하는가?
| 문제 | Vault 해결책 |
|---|---|
| 크레덴셜 하드코딩 | 중앙 집중식 시크릿 관리 |
| 평문 저장 | 암호화된 저장소 |
| 무제한 유효기간 | 동적 시크릿, TTL 설정 |
| 감사 불가 | 모든 접근 로깅 |
| 권한 관리 어려움 | 세분화된 ACL 정책 |
지원하는 Cloudflare 크레덴셜
| 유형 | 용도 | 권장 |
|---|---|---|
| API Token | 특정 권한만 부여 | ✅ 권장 |
| Global API Key | 전체 권한 | ⚠️ 레거시 |
| Tunnel Credentials | cloudflared 터널 | ✅ 필수 |
| Origin CA Key | 오리진 인증서 | 필요시 |
아키텍처
┌─────────────────────────────────────────────────────────────┐
│ HashiCorp Vault │
│ (https://vault.anvil.it.com) │
├─────────────────────────────────────────────────────────────┤
│ secret/cloudflare/ │
│ ├── tokens/ │
│ │ ├── dns-edit # DNS 편집용 토큰 │
│ │ ├── zone-read # Zone 읽기 전용 │
│ │ └── workers-deploy # Workers 배포용 │
│ ├── tunnels/ │
│ │ ├── production # 프로덕션 터널 │
│ │ └── staging # 스테이징 터널 │
│ └── global/ # Global API Key (레거시) │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Terraform │ │ n8n │ │ CI/CD │
│ │ │ Workflows │ │ Pipeline │
└───────────┘ └───────────┘ └───────────┘
Vault 초기 설정
1. Vault 로그인
# 환경변수 설정
export VAULT_ADDR="https://vault.anvil.it.com"
# 토큰 로그인
vault login
# 또는 토큰 직접 지정
vault login hvs.xxxxxxxxxxxxx
2. KV Secrets Engine 활성화
# KV v2 시크릿 엔진 활성화 (이미 있으면 스킵)
vault secrets enable -path=secret kv-v2
# 확인
vault secrets list
3. Cloudflare 정책 생성
# 정책 파일 생성
cat > cloudflare-policy.hcl << 'EOF'
# Cloudflare 토큰 읽기 권한
path "secret/data/cloudflare/*" {
capabilities = ["read", "list"]
}
# 토큰 메타데이터 읽기
path "secret/metadata/cloudflare/*" {
capabilities = ["read", "list"]
}
EOF
# 정책 적용
vault policy write cloudflare-read cloudflare-policy.hcl
# 정책 확인
vault policy read cloudflare-read
4. AppRole 설정 (자동화용)
# AppRole 활성화
vault auth enable approle
# Role 생성
vault write auth/approle/role/cloudflare-automation \
token_policies="cloudflare-read" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=24h
# Role ID 확인
vault read auth/approle/role/cloudflare-automation/role-id
# Secret ID 생성
vault write -f auth/approle/role/cloudflare-automation/secret-id
Cloudflare API Token 생성
Cloudflare 대시보드에서 생성
- https://dash.cloudflare.com/profile/api-tokens 접속
- Create Token 클릭
- 템플릿 선택 또는 Custom token 생성
권장 토큰 템플릿
DNS 편집용 토큰
| 권한 | 리소스 |
|---|---|
| Zone - DNS - Edit | 특정 Zone 또는 All zones |
| Zone - Zone - Read | 특정 Zone 또는 All zones |
Workers 배포용 토큰
| 권한 | 리소스 |
|---|---|
| Account - Workers Scripts - Edit | 계정 |
| Account - Workers KV Storage - Edit | 계정 |
| Account - Workers Routes - Edit | 계정 |
Zone 읽기 전용 토큰
| 권한 | 리소스 |
|---|---|
| Zone - Zone - Read | All zones |
| Zone - DNS - Read | All zones |
| Zone - Analytics - Read | All zones |
API 토큰 테스트
# 토큰 유효성 검증
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer YOUR_API_TOKEN"
# 성공 응답
{
"result": { "status": "active" },
"success": true
}
Vault에 크레덴셜 저장
1. API Token 저장 (권장)
# DNS 편집용 토큰 저장
vault kv put secret/cloudflare/tokens/dns-edit \
api_token="your-dns-edit-token-here" \
account_id="your-account-id" \
description="DNS record management"
# Workers 배포용 토큰 저장
vault kv put secret/cloudflare/tokens/workers-deploy \
api_token="your-workers-token-here" \
account_id="your-account-id" \
description="Workers deployment"
# 확인
vault kv get secret/cloudflare/tokens/dns-edit
2. Global API Key 저장 (레거시)
# Global API Key 저장 (권장하지 않음)
vault kv put secret/cloudflare/global \
email="your-email@example.com" \
api_key="your-global-api-key" \
account_id="your-account-id"
3. Tunnel 크레덴셜 저장
# cloudflared tunnel 생성 시 credentials.json 내용 저장
vault kv put secret/cloudflare/tunnels/production \
tunnel_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
account_tag="your-account-tag" \
tunnel_secret="base64-encoded-secret"
# 또는 JSON 파일 전체 저장
vault kv put secret/cloudflare/tunnels/production \
credentials="$(cat ~/.cloudflared/xxxxxxxx.json | base64)"
4. 시크릿 버전 관리
# 시크릿 버전 목록
vault kv metadata get secret/cloudflare/tokens/dns-edit
# 특정 버전 읽기
vault kv get -version=2 secret/cloudflare/tokens/dns-edit
# 시크릿 삭제 (소프트 삭제)
vault kv delete secret/cloudflare/tokens/old-token
# 영구 삭제
vault kv destroy -versions=1 secret/cloudflare/tokens/old-token
CLI 사용법
환경변수로 로드
# API Token 방식 (권장)
export CLOUDFLARE_API_TOKEN=$(vault kv get -field=api_token secret/cloudflare/tokens/dns-edit)
# Global API Key 방식 (레거시)
export CF_API_EMAIL=$(vault kv get -field=email secret/cloudflare/global)
export CF_API_KEY=$(vault kv get -field=api_key secret/cloudflare/global)
# Account ID
export CLOUDFLARE_ACCOUNT_ID=$(vault kv get -field=account_id secret/cloudflare/tokens/dns-edit)
Tunnel credentials.json 복원
# JSON 파일로 추출
vault kv get -format=json secret/cloudflare/tunnels/production | \
jq -r '.data.data | {
AccountTag: .account_tag,
TunnelID: .tunnel_id,
TunnelSecret: .tunnel_secret
}' > ~/.cloudflared/production.json
chmod 600 ~/.cloudflared/production.json
한 번에 모든 크레덴셜 로드
# JSON에서 환경변수로 변환
eval $(vault kv get -format=json secret/cloudflare/tokens/dns-edit | \
jq -r '.data.data | to_entries[] | "export CF_\(.key | ascii_upcase)=\(.value)"')
자동화 스크립트
Fish Shell 함수
# ~/.config/fish/functions/cf-auth.fish
function cf-auth
set -l token_name $argv[1]
if test -z "$token_name"
set token_name "dns-edit"
end
set -gx CLOUDFLARE_API_TOKEN (vault kv get -field=api_token secret/cloudflare/tokens/$token_name)
set -gx CLOUDFLARE_ACCOUNT_ID (vault kv get -field=account_id secret/cloudflare/tokens/$token_name)
echo "✅ Cloudflare 인증 설정 완료 (토큰: $token_name)"
end
function cf-auth-clear
set -e CLOUDFLARE_API_TOKEN
set -e CLOUDFLARE_ACCOUNT_ID
set -e CF_API_EMAIL
set -e CF_API_KEY
echo "🧹 Cloudflare 환경변수 정리 완료"
end
Bash 함수
# ~/.bashrc 또는 ~/.bash_aliases
cf-auth() {
local token_name="${1:-dns-edit}"
export CLOUDFLARE_API_TOKEN=$(vault kv get -field=api_token "secret/cloudflare/tokens/${token_name}")
export CLOUDFLARE_ACCOUNT_ID=$(vault kv get -field=account_id "secret/cloudflare/tokens/${token_name}")
echo "✅ Cloudflare 인증 설정 완료 (토큰: ${token_name})"
}
cf-auth-clear() {
unset CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID CF_API_EMAIL CF_API_KEY
echo "🧹 Cloudflare 환경변수 정리 완료"
}
# 자동 완성
_cf_auth_completions() {
local tokens=$(vault kv list -format=json secret/cloudflare/tokens 2>/dev/null | jq -r '.[]')
COMPREPLY=($(compgen -W "${tokens}" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _cf_auth_completions cf-auth
Terraform 통합
Provider 설정
# providers.tf
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
vault = {
source = "hashicorp/vault"
version = "~> 3.0"
}
}
}
provider "vault" {
address = "https://vault.anvil.it.com"
# 토큰은 VAULT_TOKEN 환경변수에서 자동 로드
}
# Vault에서 Cloudflare 토큰 읽기
data "vault_kv_secret_v2" "cloudflare" {
mount = "secret"
name = "cloudflare/tokens/dns-edit"
}
provider "cloudflare" {
api_token = data.vault_kv_secret_v2.cloudflare.data["api_token"]
}
DNS 레코드 관리 예시
# dns.tf
data "cloudflare_zone" "main" {
name = "example.com"
}
resource "cloudflare_record" "api" {
zone_id = data.cloudflare_zone.main.id
name = "api"
value = "192.168.1.100"
type = "A"
proxied = true
}
Tunnel 설정 예시
# tunnel.tf
data "vault_kv_secret_v2" "tunnel" {
mount = "secret"
name = "cloudflare/tunnels/production"
}
resource "cloudflare_tunnel" "production" {
account_id = data.vault_kv_secret_v2.cloudflare.data["account_id"]
name = "production-tunnel"
secret = data.vault_kv_secret_v2.tunnel.data["tunnel_secret"]
}
CI/CD 통합
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to Cloudflare
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Import Secrets from Vault
uses: hashicorp/vault-action@v2
with:
url: https://vault.anvil.it.com
method: approle
roleId: ${{ secrets.VAULT_ROLE_ID }}
secretId: ${{ secrets.VAULT_SECRET_ID }}
secrets: |
secret/data/cloudflare/tokens/workers-deploy api_token | CLOUDFLARE_API_TOKEN ;
secret/data/cloudflare/tokens/workers-deploy account_id | CLOUDFLARE_ACCOUNT_ID
- name: Deploy Worker
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ env.CLOUDFLARE_API_TOKEN }}
accountId: ${{ env.CLOUDFLARE_ACCOUNT_ID }}
GitLab CI
# .gitlab-ci.yml
variables:
VAULT_ADDR: "https://vault.anvil.it.com"
.vault-auth: &vault-auth
before_script:
- export VAULT_TOKEN=$(vault write -field=token auth/approle/login role_id=$VAULT_ROLE_ID secret_id=$VAULT_SECRET_ID)
- export CLOUDFLARE_API_TOKEN=$(vault kv get -field=api_token secret/cloudflare/tokens/dns-edit)
deploy:
<<: *vault-auth
script:
- wrangler deploy
문제 해결
일반적인 오류
| 오류 | 원인 | 해결 |
|---|---|---|
permission denied |
Vault 정책 부족 | 정책에 경로 추가 |
token expired |
Vault 토큰 만료 | vault login 재실행 |
secret not found |
잘못된 경로 | vault kv list 로 확인 |
authentication error |
CF 토큰 만료/삭제 | Cloudflare에서 토큰 재생성 |
Vault 연결 확인
# Vault 상태 확인
vault status
# 토큰 정보 확인
vault token lookup
# 시크릿 경로 목록
vault kv list secret/cloudflare/
vault kv list secret/cloudflare/tokens/
Cloudflare 토큰 검증
# 토큰 유효성 확인
curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer $(vault kv get -field=api_token secret/cloudflare/tokens/dns-edit)" | jq
# 권한 확인
curl -s -X GET "https://api.cloudflare.com/client/v4/user" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result.id'
디버그 모드
# Vault 상세 로그
VAULT_LOG_LEVEL=debug vault kv get secret/cloudflare/tokens/dns-edit
# curl 상세 출력
curl -v -X GET "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
보안 체크리스트
- Global API Key 대신 API Token 사용
- 토큰별 최소 권한 원칙 적용
- Vault 정책으로 접근 제한
- 환경변수는 세션 종료 시 정리
- CI/CD에서 AppRole 사용
- 정기적인 토큰 로테이션
- Vault 감사 로그 활성화
참고 자료
마지막 업데이트: 2026-01