- 아키텍처 다이어그램 추가 - 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>
541 lines
14 KiB
Markdown
541 lines
14 KiB
Markdown
# Cloudflare + HashiCorp Vault 통합 가이드
|
|
|
|
> Cloudflare API 크레덴셜을 Vault에서 안전하게 관리하는 완벽 가이드
|
|
|
|
## 목차
|
|
|
|
1. [개요](#개요)
|
|
2. [아키텍처](#아키텍처)
|
|
3. [Vault 초기 설정](#vault-초기-설정)
|
|
4. [Cloudflare API Token 생성](#cloudflare-api-token-생성)
|
|
5. [Vault에 크레덴셜 저장](#vault에-크레덴셜-저장)
|
|
6. [CLI 사용법](#cli-사용법)
|
|
7. [자동화 스크립트](#자동화-스크립트)
|
|
8. [Terraform 통합](#terraform-통합)
|
|
9. [CI/CD 통합](#cicd-통합)
|
|
10. [문제 해결](#문제-해결)
|
|
|
|
---
|
|
|
|
## 개요
|
|
|
|
### 왜 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 로그인
|
|
|
|
```bash
|
|
# 환경변수 설정
|
|
export VAULT_ADDR="https://vault.anvil.it.com"
|
|
|
|
# 토큰 로그인
|
|
vault login
|
|
|
|
# 또는 토큰 직접 지정
|
|
vault login hvs.xxxxxxxxxxxxx
|
|
```
|
|
|
|
### 2. KV Secrets Engine 활성화
|
|
|
|
```bash
|
|
# KV v2 시크릿 엔진 활성화 (이미 있으면 스킵)
|
|
vault secrets enable -path=secret kv-v2
|
|
|
|
# 확인
|
|
vault secrets list
|
|
```
|
|
|
|
### 3. Cloudflare 정책 생성
|
|
|
|
```bash
|
|
# 정책 파일 생성
|
|
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 설정 (자동화용)
|
|
|
|
```bash
|
|
# 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 대시보드에서 생성
|
|
|
|
1. https://dash.cloudflare.com/profile/api-tokens 접속
|
|
2. **Create Token** 클릭
|
|
3. 템플릿 선택 또는 **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 토큰 테스트
|
|
|
|
```bash
|
|
# 토큰 유효성 검증
|
|
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 저장 (권장)
|
|
|
|
```bash
|
|
# 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 저장 (레거시)
|
|
|
|
```bash
|
|
# 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 크레덴셜 저장
|
|
|
|
```bash
|
|
# 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. 시크릿 버전 관리
|
|
|
|
```bash
|
|
# 시크릿 버전 목록
|
|
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 사용법
|
|
|
|
### 환경변수로 로드
|
|
|
|
```bash
|
|
# 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 복원
|
|
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
### 한 번에 모든 크레덴셜 로드
|
|
|
|
```bash
|
|
# 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 함수
|
|
|
|
```fish
|
|
# ~/.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 함수
|
|
|
|
```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 설정
|
|
|
|
```hcl
|
|
# 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 레코드 관리 예시
|
|
|
|
```hcl
|
|
# 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 설정 예시
|
|
|
|
```hcl
|
|
# 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
|
|
|
|
```yaml
|
|
# .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
|
|
|
|
```yaml
|
|
# .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 연결 확인
|
|
|
|
```bash
|
|
# Vault 상태 확인
|
|
vault status
|
|
|
|
# 토큰 정보 확인
|
|
vault token lookup
|
|
|
|
# 시크릿 경로 목록
|
|
vault kv list secret/cloudflare/
|
|
vault kv list secret/cloudflare/tokens/
|
|
```
|
|
|
|
### Cloudflare 토큰 검증
|
|
|
|
```bash
|
|
# 토큰 유효성 확인
|
|
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'
|
|
```
|
|
|
|
### 디버그 모드
|
|
|
|
```bash
|
|
# 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 감사 로그 활성화
|
|
|
|
---
|
|
|
|
## 참고 자료
|
|
|
|
- [Cloudflare API Tokens](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)
|
|
- [HashiCorp Vault KV Secrets](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2)
|
|
- [Vault AppRole Auth](https://developer.hashicorp.com/vault/docs/auth/approle)
|
|
- [Terraform Cloudflare Provider](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs)
|
|
|
|
---
|
|
|
|
*마지막 업데이트: 2026-01*
|