Ssoon
HashiCorp Vault - Server TLS 본문
🧩 Vault Server TLS란
- Vault는 기본적으로 HTTP가 아닌 HTTPS (즉, TLS) 통신을 통해서만 API를 노출하는 것이 권장됩니다. 이를 통해 Vault ↔ 클라이언트 간 통신을 암호화하고, 중간자 공격(MITM) 또는 평문 노출을 방지할 수 있습니다.
- TLS 설정은 Vault 서버의 listener 설정에서 구성하며, 인증서(cert)와 개인키(key), TLS 버전, 암호화 방식(cipher suite) 등을 지정합니다.
- Vault 서버 설정은 HCL 또는 JSON 형식의 설정 파일(config file)을 사용하며, TLS 설정을 포함한 listener stanza를 정의합니다.
🔧 TLS 설정 방법 (Configuration)
▶ listener stanza — 기본 구조
Vault 설정 파일 내에서 listener "tcp" 블록을 사용해 TLS를 설정합니다.
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/path/to/server.crt"
tls_key_file = "/path/to/server.key"
# (optional) 여러 TLS 설정들...
}
- address : Vault가 바인딩할 IP + 포트 (예: "0.0.0.0:8200"). 외부 연결을 허용하려면 0.0.0.0 또는 적절한 IP를 지정해야 합니다.
- tls_cert_file / tls_key_file : 각각 PEM 형식의 TLS 인증서와 개인키의 경로. 이 두 파일이 있어야 Vault가 TLS로 요청을 받을 수 있습니다.
- 만약 이 값들이 없거나 tls_disable = true 로 설정된다면 TLS가 비활성화되어 plain HTTP (비암호화)로 동작합니다. 하지만 보안상 권장되지 않습니다.
▶ TLS 버전 및 Cipher 설정
Vault는 기본적으로 TLS 1.2 또는 TLS 1.3 연결만 허용하며, TLS 1.0 또는 1.1은 거부합니다.
- TLS 1.3을 사용할 경우 (권장), 아래와 같이 설정할 수 있습니다.
listener "tcp" {
address = "127.0.0.1:8200"
tls_cert_file = "cert.pem"
tls_key_file = "key.pem"
tls_min_version = "tls13"
}
- 만약 TLS 1.2를 사용하고 싶거나, 특정 Cipher suite를 강제하고 싶다면 아래처럼 설정 가능합니다.
listener "tcp" {
address = "127.0.0.1:8200"
tls_cert_file = "cert.pem"
tls_key_file = "key.pem"
tls_min_version = "tls12"
tls_max_version = "tls12"
tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,..."
}
- 다만 TLS 1.3에서는 Cipher suite를 명시할 수 없고, Go 언어의 TLS 기본 설정으로 동작합니다.
- 기본값으로 Vault는 안전한 Cipher suites들을 사용하도록 구성되어 있으며, 예전의 취약한 암호 방식(예: 3DES)은 기본 설정에서 제외됩니다.
✅ TLS 적용의 실제 예시
많은 운영 환경에서 다음과 같은 설정 예시를 사용합니다.
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/vault/tls/server.crt"
tls_key_file = "/vault/tls/server.key"
}
api_addr = "https://vault.example.com:8200"
cluster_addr = "https://vault.example.com:8201"
storage "raft" {
path = "/vault/data"
node_id = "vault-node-1"
}
ui = true
disable_mlock = true
- 위 설정은 Vault 서버가 :8200 포트에서 TLS로 API를 노출하고, api_addr 및 cluster_addr를 HTTPS URL로 지정한 구성입니다.
- 이처럼 TLS를 적용한 Vault 서버는 클라이언트가 https://vault.example.com:8200 으로 접속하도록 설정해야 합니다.
🔒 mTLS (Mutual TLS) 설정
Vault는 단순한 서버 TLS뿐 아니라, 클라이언트 인증서를 사용하는 mTLS 설정도 지원할 수 있습니다. 이는 Vault 서버 ↔ 클라이언트 간 상호 인증(mutual authentication)까지 고려하는 환경에서 유용합니다.
listener "tcp" {
tls_min_version = "tls12"
tls_cert_file = "/path/to/server.crt"
tls_key_file = "/path/to/server.key"
tls_require_and_verify_client_cert = "true"
tls_client_ca_file = "/path/to/client-ca.crt"
}
- tls_require_and_verify_client_cert = "true": 클라이언트에게 인증서를 요구하고 검증함.
- tls_client_ca_file : 클라이언트 인증서를 서명한 CA certificate 경로. 이를 통해 Vault 서버가 클라이언트 인증서를 검증할 수 있습니다.
※ 다만 mTLS은 TLS 설정 그 자체만으로 인증(Authentication)이나 권한 부여(Authorization)를 제공하는 것은 아닙니다. Vault의 Auth Method (token, LDAP, AppRole 등)와 별개로, 전송 계층 보안을 강화하는 용도입니다.
⚠️ 유의사항 & Best Practices
- Vault 설정 파일(config file)은 HCL 또는 JSON 형식이어야 하며, TLS를 설정하려면 반드시 listener "tcp" 블록 내에 인증서 경로를 지정해야 합니다.
- TLS 설정을 변경한 경우 Vault 클러스터를 graceful restart 해야 반영됩니다. SIGHUP 만으로는 TLS 설정이 reload 되지 않습니다.
- 기본적으로 Vault는 TLS 1.2 또는 1.3을 사용하도록 설계되어 있어, 구형 TLS (1.0 / 1.1) 요청은 거부됩니다. 이는 보안 측면에서 권장되는 기본 동작입니다.
- mTLS를 사용하면 클라이언트 인증서를 통한 추가 보안이 가능하지만, 인증서 관리(CA, 클라이언트 cert 발급/갱신 등)가 필요합니다.
- 운영 환경에서는 인증서와 키 파일의 권한(permission), 파일 소유자(owner), 디렉터리 권한 등을 적절히 설정하여 보안을 유지해야 합니다. Vault는 설정 파일의 권한 검사 기능을 제공할 수 있으므로, 환경 변수 VAULT_ENABLE_FILE_PERMISSIONS_CHECK 설정을 고려하는 것이 좋습니다.
✅실습
- kind k8s + ingress-nginx
(⎈|N/A:N/A) ssoon@DESKTOP-72C919S:~$ kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
labels:
ingress-ready: true
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- containerPort: 30000
hostPort: 30000
EOF
Creating cluster "myk8s" ...
✓ Ensuring node image (kindest/node:v1.32.8) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-myk8s"
You can now use your cluster with:
kubectl cluster-info --context kind-myk8s
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
namespace/ingress-nginx created
...
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl get deployment ingress-nginx-controller -n ingress-nginx -o yaml \
| sed '/- --publish-status-address=localhost/a\
- --enable-ssl-passthrough' | kubectl apply -f -
# nodeSelector 지정
kubectl patch deployment ingress-nginx-controller -n ingress-nginx \
--type='merge' \
-p='{
"spec": {
"template": {
"spec": {
"nodeSelector": {
"ingress-ready": "true"
}
}
}
}
}'
deployment.apps/ingress-nginx-controller configured
deployment.apps/ingress-nginx-controller patched
- Kubernetes 환경에서 Helm 패키지 매니저를 설치하고 구성
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ docker exec -it myk8s-control-plane bash
root@myk8s-control-plane:/# curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 11929 100 11929 0 0 174k 0 --:--:-- --:--:-- --:--:-- 176k
[WARNING] Could not find git. It is required for plugin installation.
Downloading https://get.helm.sh/helm-v3.19.2-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm
root@myk8s-control-plane:/# helm version
version.BuildInfo{Version:"v3.19.2", GitCommit:"8766e718a0119851f10ddbe4577593a45fadf544", GitTreeState:"clean", GoVersion:"go1.24.9"}
root@myk8s-control-plane:/# helm repo add hashicorp https://helm.releases.hashicorp.com
"hashicorp" has been added to your repositories
root@myk8s-control-plane:/# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "hashicorp" chart repository
Update Complete. ⎈Happy Helming!⎈
- TLS(Transport Layer Security) 통신에 사용할 자체 서명된 인증 기관(CA) 인증서 쌍을 생성
root@myk8s-control-plane:/# mkdir /tls && cd /tls
root@myk8s-control-plane:/tls# openssl genrsa -out ca.key 2048
root@myk8s-control-plane:/tls# openssl req -x509 -new -nodes \
-key ca.key \
-subj "/CN=Vault-CA" \
-days 3650 \
-out ca.crt
root@myk8s-control-plane:/tls# ls
ca.crt ca.key
- 생성한 CA(인증 기관)를 사용하여 Vault 서버의 인증서 발급 준비
root@myk8s-control-plane:/tls# openssl genrsa -out vault.key 2048
root@myk8s-control-plane:/tls# openssl req -new -key vault.key \
-subj "/CN=vault.example.com" \
-out vault.csr
root@myk8s-control-plane:/tls# ls
ca.crt ca.key vault.csr vault.key
- Vault 서버의 인증서 서명 요청(CSR)에 자체 서명 CA를 사용하여 최종 서명
- Subject Alternative Names (SANs) 확장을 추가하여, Vault 서버가 다양한 이름과 IP 주소로 접근될 때도 유효한 TLS 인증서가 되도록 구성
root@myk8s-control-plane:/tls# openssl x509 -req \
-in vault.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out vault.crt \
-days 3650 \
-extensions v3_req \
-extfile <(cat <<EOF
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vault.example.com
DNS.2 = vault
DNS.3 = localhost
DNS.4 = vault.vault.svc.cluster.local
DNS.5 = vault.vault-internal
DNS.6 = vault-0.vault-internal
DNS.7 = vault-1.vault-internal
DNS.8 = vault-2.vault-internal
DNS.9 = 127.0.0.1
EOF
)
Certificate request self-signature ok
subject=CN = vault.example.com
- Kubernetes 네임스페이스 및 Secret을 생성하고, 이를 사용하여 TLS가 활성화된 HashiCorp Vault를 Helm으로 배포
root@myk8s-control-plane:/tls# kubectl create namespace vault
namespace/vault created
root@myk8s-control-plane:/tls# kubectl -n vault create secret tls vault-tls \
--cert=vault.crt \
--key=vault.key
secret/vault-tls created
root@myk8s-control-plane:/tls# kubectl -n vault create secret generic vault-ca \
--from-file=ca.crt=ca.crt
secret/vault-ca created
root@myk8s-control-plane:/tls# cat << EOF > values-tls.yaml
global:
tlsDisable: false
injector:
enabled: false
server:
volumes:
- name: vault-tls
secret:
secretName: vault-tls
- name: vault-ca
secret:
secretName: vault-ca
volumeMounts:
- name: vault-tls
mountPath: /vault/user-tls
readOnly: true
- name: vault-ca
mountPath: /vault/user-ca
readOnly: true
# Vault HTTPS listener 설정
standalone:
enabled: "true"
config: |-
ui = true
listener "tcp" {
tls_disable = 0
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_cert_file = "/vault/user-tls/tls.crt"
tls_key_file = "/vault/user-tls/tls.key"
}
storage "file" {
path = "/vault/data"
}
api_addr = "https://vault.vault.svc.cluster.local:8200"
cluster_addr = "https://vault-0.vault-internal:8201"
EOF
root@myk8s-control-plane:/tls# helm install vault hashicorp/vault -n vault -f values-tls.yaml --version 0.29.0
NAME: vault
LAST DEPLOYED: Wed Dec 10 12:06:18 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!
Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:
https://developer.hashicorp.com/vault/docs
Your release is named vault. To learn more about the release, try:
$ helm status vault
$ helm get manifest vault
root@myk8s-control-plane:/tls# helm list -A
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
vault vault 1 2025-12-10 12:06:18.732285266 +0000 UTC deployed vault-0.29.0 1.18.1
- Vault Pod 내부에서 Vault 서버의 현재 상태를 확인
- 첫 번째 시도 실패 (TLS/SAN 오류)
- -tls-skip-verify 옵션을 추가하여 TLS 인증서 유효성 검사를 건너뛰도록 Vault CLI에 지시 > Vault 서버의 실제 상태를 확인하는 데 성공
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl exec -ti vault-0 -n vault -- vault status
Error checking seal status: Get "https://127.0.0.1:8200/v1/sys/seal-status": tls: failed to verify certificate: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
command terminated with exit code 1
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl exec -ti vault-0 -n vault -- vault status -tls-skip-verify
Key Value
--- -----
Seal Type shamir
Initialized false
Sealed true
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 1.18.1
Build Date 2024-10-29T14:21:31Z
Storage Type file
HA Enabled false
command terminated with exit code 2
- Kubernetes 클러스터 내에서 실행 중인 Vault 서버를 성공적으로 초기화하고 봉인 해제
- Vault 초기화 (vault operator init)
- Vault 봉인 해제 (vault operator unseal)
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl exec vault-0 -n vault -- vault operator init -tls-skip-verify \
-key-shares=1 \
-key-threshold=1 \
-format=json > cluster-keys.json
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ cat cluster-keys.json| jq
{
"unseal_keys_b64": [
"MfrUShfJpsil8YY5lyby6c/xkfj2vMFxXd3x33ssIRs="
],
"unseal_keys_hex": [
"31fad44a17c9a6c8a5f186399726f2e9cff191f8f6bcc1715dddf1df7b2c211b"
],
"unseal_shares": 1,
"unseal_threshold": 1,
"recovery_keys_b64": [],
"recovery_keys_hex": [],
"recovery_keys_shares": 0,
"recovery_keys_threshold": 0,
"root_token": "hvs.MtkuRY7vqE7gzXYtJIT1La4y"
}
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl exec vault-0 -n vault -- vault operator unseal -tls-skip-verify $VAULT_UNSEAL_KEY
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.18.1
Build Date 2024-10-29T14:21:31Z
Storage Type file
Cluster Name vault-cluster-19a37576
Cluster ID 2a08f6a5-9323-88c8-00fb-7a56896921a3
HA Enabled false
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl get pod -n vault
NAME READY STATUS RESTARTS AGE
vault-0 1/1 Running 0 4m20s
- Vault 인증 (Authentication) 준비
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ jq -r ".root_token" cluster-keys.json
hvs.MtkuRY7vqE7gzXYtJIT1La4y
- Vault에 접근하는 방법을 포트 포워딩(kubectl port-forward)에서 NodePort 서비스로 변경하고, 루트 토큰을 사용하여 성공적으로 로그인
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl patch svc -n vault vault -p '{"spec":{"type":"NodePort","ports":[{"port":8200,"targetPort":8200,"nodePort":30000}]}}'
service/vault patched
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ export VAULT_ADDR='https://localhost:30000'
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ vault status -tls-skip-verify
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.18.1
Build Date 2024-10-29T14:21:31Z
Storage Type file
Cluster Name vault-cluster-19a37576
Cluster ID 2a08f6a5-9323-88c8-00fb-7a56896921a3
HA Enabled false
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ vault login -tls-skip-verify
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.MtkuRY7vqE7gzXYtJIT1La4y
token_accessor iJUdl8kCW6tj3au6rxAdAYwu
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
- /etc/hosts 파일에 127.0.0.1 vault.example.com 항목을 추가하여 vault.example.com 도메인을 로컬 주소(localhost)로 매핑
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ echo "127.0.0.1 vault.example.com" | sudo tee -a /etc/hosts
127.0.0.1 vault.example.com
- Vault Pod (vault-0)의 내부 포트 설정
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl describe pod -n vault vault-0 | grep -i ports
Ports: 8200/TCP (https), 8201/TCP (https-internal), 8202/TCP (https-rep)
Host Ports: 0/TCP (https), 0/TCP (https-internal), 0/TCP (https-rep)
- Ingress 리소스를 생성하여 Vault에 접근하는 방식을 NodePort에서 Ingress 기반 접근
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vault-https
namespace: vault
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: "nginx"
rules:
- host: vault.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vault
port:
number: 8200
tls:
- hosts:
- vault.example.com
secretName: vault-tls
EOF
ingress.networking.k8s.io/vault-https created
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl get ingress -n vault
NAME CLASS HOSTS ADDRESS PORTS AGE
vault-https nginx vault.example.com localhost 80, 443 2m15s'CICD Study [1기]' 카테고리의 다른 글
| HashiCorp Vault - Auth with LDAP (0) | 2025.12.07 |
|---|---|
| HashiCorp Vault - High Availability (HA) (0) | 2025.12.07 |
| HashiCorp Vault - Vault Secrets Operator (0) | 2025.12.07 |
| HashiCorp Vault - 암호화(Encryption)와 Vault Transit 엔진 (0) | 2025.11.27 |
| HashiCorp Vault - Jenkins + Vault (AppRole) - CI (0) | 2025.11.27 |
Comments