새소식

CICD

[CI/CD Study 8주차] Vault Production - HA, LDAP, SSL/TLS

  • -

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

 

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

이번 글은 이번 스터디의 마지막 글로 Vault 를 Production 레벨 (운영) 에서 활용할 때 알아야 할 기초 내용에 대해서 학습했습니다.

 

 

 

1. Vault HA

 

 

Vault HA (High Availability) 는 Vault 서버가 장애 없이 계속 서비스를 제공하도록 구성하는 방식

 

 

1.1. 핵심 개념

 

* Active / Standby 구조 (최소 Active 1 + Standby 2)

- Active(Lead) : 실제 요청 처리

- Standby(Follower) : 대기 상태

 

- Active 장애 발생 시, Standby 중 하나가 자동 승격

- 클라이언트는 항상 하나의 Vault 주소만 바라봄 (Active)

 

 

1.2. 부가 개념

 

* Seal / Unseal

- 모든 Vault 노드는 Unseal 상태여야 동작 가능

- HA 일 때에도 Unseal 은 노드별 수행 필요

 

 

 

2. Vault HA 설치

 

 

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
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
- role: worker
- role: worker
- role: worker
EOF

 

2.2. Vault HA 배포

 

# NS 배포
kubectl create ns vault

# HA vaules 생성

cat << EOF > values-ha.yaml
server:
  replicas: 3

  # Vault HA mode
  ha:
    enabled: true
    replicas: 3
    raft:
      enabled: true
    config: |
      ui = true
      listener "tcp" {
        tls_disable = 1                           # TLS 바활성화
        address = "[::]:8200"                     # 모든 IPv6 주소에서 8200 포트 수신
        cluster_address = "[::]:8201"             # 클러스터 통신 포트
      }

      service_registration "kubernetes" {}        # Kubernetes 서비스 등록 활성화

  readinessProbe:
    enabled: true

  # PVC for Raft storage
  dataStorage:
    enabled: true
    size: 10Gi

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

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

injector:
  enabled: false
EOF

# Vault 배포
helm install vault hashicorp/vault -n vault -f values-ha.yaml --version 0.31.0

# Pod 확인
kubectl get pods --selector='app.kubernetes.io/name=vault' -n vault

'''
NAME      READY   STATUS    RESTARTS   AGE
vault-0   0/1     Running   0          79s
vault-1   0/1     Running   0          79s
vault-2   0/1     Running   0          79s
'''

 

2.3. Vault Unseal - 첫번째 파드

 

Production 레벨에서는 Vault 배포 후 Unseal 을 하지 않으면 Vault 를 사용할 수 없습니다.

 

# 첫번째 파드 접근
kubectl exec -it vault-0 -n vault -- sh

# Init 실행
vault operator init

'''
Unseal Key 1: ZqCrmmi+4vw5A1lAINIs1nA6/8SkyITLobzK1ONXU2Jc
Unseal Key 2: cQBJI9QK+jFtm6megvrReINEjCj7Y9bmtdBU1CCqyhqW
Unseal Key 3: m3tK7Agew01h+pd/ysCO5g2n3bwm9IJljNdk0es3DaEa
Unseal Key 4: 2KlM6gG1N2RoVTT6t4BIYU3nh7YYmBNC3wkJ85Ukl3ny
Unseal Key 5: NNZqzvTfCCqhiFqCG25mFfKoG/mJcB+m5wVEvwP+tfPg

Initial Root Token: hvs.bQwXGUkKgVcxnkw2SbwhA7Mc
'''

# Unseal 을 각각 다른 키로 3번 실행하여 봉인 해제
vault operator unseal

 

 

첫번째 파드를 해제 했음에도 2개의 파드는 아직 동작이 안되고 있습니다.
이는 2개의 파드 모두 각각 Unseal 과정을 거쳐야 함을 의미합니다.

 

 

2.4. Vault Unseal - 나머지 파드

 

# 각각의 Pod 에서 HA 를 위해 Vault Join 실행 (1 Active + 2 Standby)
kubectl exec -n vault -it vault-1 -- vault operator raft join http://vault-0.vault-internal:8200
kubectl exec -n vault -it vault-2 -- vault operator raft join http://vault-0.vault-internal:8200

# 각각의 파드에서 Unseal 3번 수행
kubectl exec -it vault-1 -n vault -- sh
vault operator unseal

kubectl exec -it vault-2 -n vault -- sh
vault operator unseal

 

2.5. Vault 추가 설정

 

# 실습을 위해 Vault CLI 로그인
export VAULT_ROOT_TOKEN=hvs.bQwXGUkKgVcxnkw2SbwhA7Mc
export VAULT_ADDR='http://localhost:30000'

# Vault 로그인
vault login
'''
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.bQwXGUkKgVcxnkw2SbwhA7Mc
token_accessor       itvosXVwvwQdS6VXCxVck7qv
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
'''

# Vault 상태 확인
vault operator raft list-peers

'''
Node                                    Address                        State       Voter
----                                    -------                        -----       -----
97cb915c-5273-1592-e126-89d38d4f2f53    vault-0.vault-internal:8201    leader      true
7cbe7eb1-c221-f254-a45e-8362b77ed8cc    vault-1.vault-internal:8201    follower    true
d3574cd2-a84f-9b63-8b06-85a092b0ca68    vault-2.vault-internal:8201    follower    true
'''

## 실습용 Vault 시크릿 생성
vault secrets enable -path=mysecret kv-v2

vault kv put mysecret/logins/study \
  username="demo" \
  password="p@ssw0rd"

vault kv get mysecret/logins/study



 

3. Vault + LDAP

 

 

3.1. OpenLDAP 배포

 

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: openldap
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openldap
  namespace: openldap
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openldap
  template:
    metadata:
      labels:
        app: openldap
    spec:
      containers:
        - name: openldap
          image: osixia/openldap:1.5.0
          ports:
            - containerPort: 389
              name: ldap
            - containerPort: 636
              name: ldaps
          env:
            - name: LDAP_ORGANISATION    # 기관명, LDAP 기본 정보 생성 시 사용
              value: "Example Org"
            - name: LDAP_DOMAIN          # LDAP 기본 Base DN 을 자동 생성
              value: "example.org"
            - name: LDAP_ADMIN_PASSWORD  # LDAP 관리자 패스워드
              value: "admin"
            - name: LDAP_CONFIG_PASSWORD
              value: "admin"
        - name: phpldapadmin
          image: osixia/phpldapadmin:0.9.0
          ports:
            - containerPort: 80
              name: phpldapadmin
          env:
            - name: PHPLDAPADMIN_HTTPS
              value: "false"
            - name: PHPLDAPADMIN_LDAP_HOSTS
              value: "openldap"   # LDAP hostname inside cluster
---
apiVersion: v1
kind: Service
metadata:
  name: openldap
  namespace: openldap
spec:
  selector:
    app: openldap
  ports:
    - name: phpldapadmin
      port: 80
      targetPort: 80
      nodePort: 30001
    - name: ldap
      port: 389
      targetPort: 389
    - name: ldaps
      port: 636
      targetPort: 636
  type: NodePort
EOF

# LDAP 로그인 정보
Bind DN: cn=admin,dc=example,dc=org
Password: admin

 

3.2. OpenLDAP 설정

 

# LDAP 서버 접근
kubectl -n openldap exec -it deploy/openldap -c openldap -- bash

# LDAP OU 배포
cat <<EOF | ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin
dn: ou=people,dc=example,dc=org
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=example,dc=org
objectClass: organizationalUnit
ou: groups
EOF

# LDAP User 추가
cat <<EOF | ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin
dn: uid=alice,ou=people,dc=example,dc=org
objectClass: inetOrgPerson
cn: Alice
sn: Kim
uid: alice
mail: alice@example.org
userPassword: alice123

dn: uid=bob,ou=people,dc=example,dc=org
objectClass: inetOrgPerson
cn: Bob
sn: Lee
uid: bob
mail: bob@example.org
userPassword: bob123
EOF

# LDAP Group 추가
cat <<EOF | ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin
dn: cn=devs,ou=groups,dc=example,dc=org
objectClass: groupOfNames
cn: devs
member: uid=bob,ou=people,dc=example,dc=org

dn: cn=admins,ou=groups,dc=example,dc=org
objectClass: groupOfNames
cn: admins
member: uid=alice,ou=people,dc=example,dc=org
EOF

#
exit

 

3.3. LDAP 연동

 

# LDAP 인증 활성화
vault auth enable ldap

# LDAP 구성
vault write auth/ldap/config \
    url="ldap://openldap.openldap.svc:389" \
    starttls=false \
    insecure_tls=true \
    binddn="cn=admin,dc=example,dc=org" \
    bindpass="admin" \
    userdn="ou=people,dc=example,dc=org" \
    groupdn="ou=groups,dc=example,dc=org" \
    groupfilter="(member=uid={{.Username}},ou=people,dc=example,dc=org)" \
    groupattr="cn"

# LDAP 인증 테스트 - Password: alice123
vault login -method=ldap username=alice

'''
Password (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.CAESICkPZ_AZNnQ0oIw2cLDUQfqk1d9kDfwMqe3JpbFgk4bnGh4KHGh2cy52ZUhkcHNsdkc1c2dpbEUwUFdLU1VpYkI
token_accessor         yPZefdU4ThHjlOrAgOKDYCUX
token_duration         768h
token_renewable        true
token_policies         ["default"]
identity_policies      []
policies               ["default"]
token_meta_username    alice
'''

 

 

3.4. LDAP 그룹 연동

 

# Vault Root 계정으로 실행 필요

# Vault Policy 생성
vault policy write admin - <<EOF
path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
EOF

# LDAP Group 에 Vault Policy 연동
vault write auth/ldap/groups/admins policies=admin

# LDAP 유저로 로그인
vault login -method=ldap username=alice password=alice123

# 정책 적용 확인 - admin read 권한
vault policy read admin



4. Vault Server TLS

 

Vault 서버를 HTTPS 로 구성하는 방법입니다.

 

4.1. kind 배포

 

# 기존 kind 삭제 - 기존에 배포되어 있을 경우
kind delete cluster --name myk8s

# 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
    hostPort: 30000
EOF

 

4.2. Ingress Nginx 배포

 

# Ingress Nginx 배포
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

# SSL Passthrough 활성화
kubectl patch deployment ingress-nginx-controller \
  -n ingress-nginx \
  --type='json' \
  -p='[
    {
      "op": "add",
      "path": "/spec/template/spec/containers/0/args/-",
      "value": "--enable-ssl-passthrough"
    }
  ]'

# NodeSelector 지정
kubectl patch deployment ingress-nginx-controller -n ingress-nginx \
  --type='merge' \
  -p='{
    "spec": {
      "template": {
        "spec": {
          "nodeSelector": {
            "ingress-ready": "true"
          }
        }
      }
    }
  }'

 

4.3. Vault 배포

 

Helm Version 4 배포 시 오류 발생 가능성이 있어 안전하게 Control Plane 노드에 Helm v3 설치 후 진행했습니다.

 

# Control Plane 접속
docker exec -it myk8s-control-plane bash

# Helm 설치
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

# CA 생성
mkdir /tls && cd /tls

openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes \
  -key ca.key \
  -subj "/CN=Vault-CA" \
  -days 3650 \
  -out ca.crt

# Vault 서버 인증서 생성
openssl genrsa -out vault.key 2048
openssl req -new -key vault.key \
  -subj "/CN=vault.example.com" \
  -out vault.csr

# 인증서 서명
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
)

# Kubernetes TLS Secret 등록
kubectl create namespace vault
kubectl -n vault create secret tls vault-tls \
  --cert=vault.crt \
  --key=vault.key

# CA도 저장 (init 과정에 사용)
kubectl -n vault create secret generic vault-ca \
  --from-file=ca.crt=ca.crt

# TLS 설정이 적용된 Vault 배포
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

# Vault 배포
helm install vault hashicorp/vault -n vault -f values-tls.yaml --version 0.29.0

exit

 

4.4. Vault 활성화

 

Unseal 상태이므로 봉인을 해제해줍니다.

 

# Vault Key 확인
kubectl exec vault-0 -n vault -- vault operator init -tls-skip-verify \
    -key-shares=1 \
    -key-threshold=1 \
    -format=json > cluster-keys.json

# Unseal
VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
kubectl exec vault-0 -n vault -- vault operator unseal -tls-skip-verify $VAULT_UNSEAL_KEY

# Pod 상태 확인 - Unseal 일 경우 1/1
kubectl get pod -n vault

'''
NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          5m34s
'''

# Vault Login - HTTPS (SSL/TLS 활용)
kubectl patch svc -n vault vault -p '{"spec":{"type":"NodePort","ports":[{"port":8200,"targetPort":8200,"nodePort":30000}]}}' 
export VAULT_ADDR='https://localhost:30000'

vault login -tls-skip-verify

 

4.5. Ingress 배포

 

# 로컬 PC 에서 도메인 테스트를 위해 PC 호스트파일 수정
echo "127.0.0.1 vault.example.com" | sudo tee -a /etc/hosts

# Ingress 배포

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 확인
kubectl get ingress -n vault -w

'''
NAME          CLASS   HOSTS               ADDRESS   PORTS     AGE
vault-https   nginx   vault.example.com             80, 443   3s
vault-https   nginx   vault.example.com   localhost   80, 443   31s
'''

# 도메인 접근
open "https://vault.example.com"

 

Contents

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