새소식

카테고리 없음

[CI/CD Study 8주차] Vault - Vault Secret Operator

  • -

CoudNet@ 팀의 가시다님께서 리딩하시는 CI/CD Study 8주차 스터디 내용 정리

 

이번 주차는 스터디의 마지막 8주차로 Vault 에 대해 더 자세히 배운 시간이었습니다.

이번 글에서는 Vault Secret Operator 에 대해서 학습한 내용을 서술했습니다.

 

 

1. Vault Secret Operator (VSO)



 

VSO 는 HashiCorp Vault에 저장된 비밀 정보(Secret)를 Kubernetes 리소스로 자동 동기화해주는 오퍼레이터

 

 

1.1. 핵심 개념

 

- Vault Secret 을 Kubernetes Secret 으로 변환/동기화
- Kubernetes CRD(Custom Resource Definition) 기반으로 동작
- 애플리케이션이 Vault 를 직접 호출하지 않고 VSO 가 대신 수행

 

사용 이유

 

- 애플리케이션 코드에서 Vault 연동 로직 제거 가능

- Secret Rotation 시 자동 반영

- Kubernetes Native 방식의 Secret 관리

 

1.2. 주요 구성 요소

 

1. VaultSecretsOperator

- VSO 컨트롤러

 

2. VaultAuth

- Vault 인증 방식 정의 (AppRole, Kubernetes Auth)

 

3. VaultStaticSecret

- 고정 Secret 요청

 

4. VaultDynamicSecret

- 동적 Secret 요청 (TTL)

 

 

1.3. VSO 동작

 

1. kubernetes 에 VaultAuth, VaultStaticSecret 등의 VSO CRD 배포

2. VSO 가 VaultAuth CRD 를 통해 Vault 인증

3. 배포된 VaultSaticSecret, VaultDynamicSecret Vault Secret 조회

4. Kubernetes Secret 으로 생성/변경

 

 

 

2. 실습 환경 구성

 

2.1 Kind 구성

 

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  # Vault Web UI
    hostPort: 30000
  - containerPort: 30001  # Sample application
    hostPort: 30001
EOF

# kind 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

 

2.2. Vault 배포 - Dev 모드

 

# VSO 실습 코드
git clone https://github.com/hashicorp-education/learn-vault-secrets-operator

# Vault Values 생성
cat <<EOF > ./learn-vault-secrets-operator/vault-values.yaml
server:
  image:
    repository: "hashicorp/vault"
    tag: "1.19.0"

  dev:
    enabled: true
    devRootToken: "root"

  logLevel: debug

  service:
    enabled: true
    type: ClusterIP
    port: 8200
    targetPort: 8200

ui:
  enabled: true
  serviceType: "NodePort"
  externalPort: 8200
  serviceNodePort: 30000

injector:
  enabled: "false"
EOF

# VSO 배포
helm install vault hashicorp/vault -n vault --create-namespace --values ./learn-vault-secrets-operator/vault-values.yaml --version 0.30.0

 

Vault 실습용 설정

 

# Vault Login
export VAULT_ADDR='http://localhost:30000'
vault login

### 암호는 root 로 지정

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                root
token_accessor       ulsQ0rcAQR9CaevdXSGGgzZp
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]


# k8s 인증 활성화
vault auth enable -path demo-auth-mount kubernetes
vault write auth/demo-auth-mount/config kubernetes_host="https://kubernetes.default.svc"

# Vault Secret 엔진 활성화
vault secrets enable -path=kvv2 kv-v2

# Vault Policy 생성
tee webapp.json <<EOF
path "kvv2/data/webapp/config" {
   capabilities = ["read", "list"]
}
EOF

vault policy write webapp webapp.json

# k8s vault 인증 연동
vault write auth/demo-auth-mount/role/role1 \
   bound_service_account_names=demo-static-app \
   bound_service_account_namespaces=app \
   policies=webapp \
   audience=vault \
   ttl=24h

# Vault 시크릿 생성
vault kv put kvv2/webapp/config username="static-user" password="static-password"

===== Secret Path =====
kvv2/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T12:44:06.335642586Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

 

 

2.3. VSO 배포 - Helm

 

Helm 설치할 때, Helm Version 4 이상을 사용하시는 분들은 종종 오류가 발생할 수 있습니다.
우선 실습인만큼 Helm Version3 으로 설치를 하면 문제없이 배포가 됩니다.

 

# Helm Install
helm install vault-secrets-operator hashicorp/vault-secrets-operator -n vault-secrets-operator-system --create-namespace --values vault/vault-operator-values.yaml --version 0.7.1

# 배포 확인
kubectl get-all -n vault-secrets-operator-system

 

 

 

 

3. Static Secret 활용

 

 

정적 시크릿은 이때까지 활용했던 Vault 내용과 동일합니다.
다만, VSO 가 대신 Vault 를 접근한다는 것이 다릅니다.

 

Static Secret 시나리오

 

1. Vault 에 Secret/Policy/Role 생성
2. VSO 가 Vault 에 로그인 후 Token 받음
3. VSO 가 전달받은 Token 으로 Vault 에 Secret 요청 후 받음
4. VSO 는 K8S Secret 에 값 업데이트
5. VSO 는 주기적으로 Vault 에 Secret 요청 후 받음

 

주요 사용 CRD

 

- VaultAuth : VSO 가 Vault 로그인 시 사용
- VaultStaticSecret : VSO 가 Vault 에 StaticSecret 요청 시 사용

 

 

3.1. 정적 시크릿 배포 및 연동

 

# 네임스페이스 배포
kubectl create ns app

# VaultAuth CRD 배포
kubectl apply -f vault/vault-auth-static.yaml

# VaultStaticSecret CRD 배포
kubectl apply -f vault/static-secret.yaml

 

3.2. 정적 시크릿 변경

 

# K8S Secret 값 확인 - Krew 플러그인
kubectl krew install view-secret
kubectl view-secret -n app secretkv --all

'''
_raw='{"data":{"password":"static-password","username":"static-user"},"metadata":{"created_time":"2025-12-13T12:44:06.335642586Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1}}'
password='static-password'
username='static-user'
'''

# Secret 변경
vault kv put kvv2/webapp/config username="static-user2" password="static-password2"

# Secret 값 재조회
kubectl view-secret -n app secretkv --all

'''
_raw='{"data":{"password":"static-password2","username":"static-user2"},"metadata":{"created_time":"2025-12-13T13:00:28.941140014Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":2}}'
password='static-password2'
username='static-user2'
'''



 

 

4. Dynamic Secret 활용

 

 

 

동적 암호 주기 관리는 Vault 가 자동으로 암호를 갱신(삭제/재생성)하고,

VSO가 해당 암호를 K8S Secret 에 동기화하는 방식으로 구성됩니다.

 

 

주요 사용 CRD

 

- VaultAuth : VSO 가 Vault 로그인 시 사용
- VaultDynamicSecret : VSO 가 Vault 에 DynamicSecret 요청 시 사용

 

 

4.1. PostgreSQL 파드 배포 및 Vault Database Secret Engine 설정

 

 

# NS 배포
kubectl create ns postgres

# PostgreSQL 배포 : 암호 secret-pass
helm upgrade --install postgres bitnami/postgresql --namespace postgres --set auth.audit.logConnections=true  --set auth.postgresPassword=secret-pass

# DB 엔진 활성화
vault secrets enable -path=demo-db database

# vault 에 DB 정보 설정
vault write demo-db/config/demo-db \
   plugin_name=postgresql-database-plugin \
   allowed_roles="dev-postgres" \
   connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
   username="postgres" \
   password="secret-pass"

# DB 사용자 동적 Role 생성
vault write demo-db/roles/dev-postgres \
   db_name=demo-db \
   creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
      GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
   revocation_statements="REVOKE ALL ON DATABASE postgres FROM  \"{{name}}\";" \
   backend=demo-db \
   name=dev-postgres \
   default_ttl="10m" \
   max_ttl="20m"

# Vault 정책 생성
vault policy write demo-auth-policy-db - <<EOF
path "demo-db/creds/dev-postgres" {
   capabilities = ["read"]
}
EOF

 

4.2. Dynamic Secret 을 활용하는 Application 배포

 

# NS 배포
kubectl create ns demo-ns

# App 배포
kubectl apply -f dynamic-secrets/.

# Username 시크릿 정보 확인
kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/username ; echo

# Password 시크릿 정보 확인
kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/password ; echo



 

5. PKI Secret 활용

 

VSO 에서 애플리케이션이 PKI 인증서를 사용할 때, 동적으로 변경된 PKI 인증서를 사용할 수 있도록 지원

 

5.1. PKI Secret 생성

 

# PKI 엔진 활성화
vault secrets enable pki

# PKI TTL 설정
vault secrets tune -max-lease-ttl=8760h pki

# PKI 생성
vault write pki/root/generate/internal \
    common_name=example.com \
    ttl=8760h

 

 

PKI 추가 설정

 

# 인증서 발급자(CA) 접근 URL, CRL(인증서 폐기 목록) 접근 URL 설정
vault write pki/config/urls \
    issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" \
    crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl"

# 인증서 생성
vault write pki/roles/example-dot-com \
    allowed_domains=example.com \
    allow_subdomains=true \
    max_ttl=72h

# Vault Policy 생성
vault policy write pki - <<EOF
path "pki*"                        { capabilities = ["read", "list"] }
path "pki/sign/example-dot-com"    { capabilities = ["create", "update"] }
path "pki/issue/example-dot-com"   { capabilities = ["create"] }
EOF

 

5.2. PKI Secret 활용

 

# k8s 인증 활성화
vault auth enable kubernetes
vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc"

# K8S 인증 Role 생성
vault write auth/kubernetes/role/issuer \
    bound_service_account_names=issuer \
    bound_service_account_namespaces=default \
    policies=pki \
    ttl=20m

 

Cert Manager 배포

 

# Cert Manager CRD 배포
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.12.3/cert-manager.crds.yaml

# NS 생성
kubectl create namespace cert-manager
helm repo add jetstack https://charts.jetstack.io && helm repo update

# Cert Manager 설치
helm install cert-manager --namespace cert-manager --version v1.12.3 jetstack/cert-manager

 

인증서 활용

 

# NS 생성
kubectl create serviceaccount issuer

# Issuer Secret 배포
cat >> issuer-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: issuer-token-lmzpj
  annotations:
    kubernetes.io/service-account.name: issuer
type: kubernetes.io/service-account-token
EOF

kubectl apply -f issuer-secret.yaml

# Vault Issuer 배포
ISSUER_SECRET_REF=$(kubectl get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith("issuer-token-")).name')

cat > vault-issuer.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: vault-issuer
  namespace: default
spec:
  vault:
    server: http://vault.vault.svc:8200
    path: pki/sign/example-dot-com
    auth:
      kubernetes:
        mountPath: /v1/auth/kubernetes
        role: issuer
        secretRef:
          name: $ISSUER_SECRET_REF
          key: token
EOF

kubectl apply --filename vault-issuer.yaml

# example-com 인증서 생성
cat > example-com-cert.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
  namespace: default
spec:
  secretName: $ISSUER_SECRET_REF
  issuerRef:
    name: vault-issuer
  commonName: www.example.com
  dnsNames:
  - www.example.com
EOF
kubectl apply --filename example-com-cert.yaml

# 인증서 확인
kubectl get certificate.cert-manager.io/example-com -owide

 

 

UI 에서도 인증서를 확인할 수 있습니다.

 

Contents

포스팅 주소를 복사했습니다