[AWES 3기] 10주차 스터디 내용 정리

ajufresh·2025년 4월 12일
0
post-thumbnail

1. Vault 개요

[Vault란?]
Docs: https://developer.hashicorp.com/vault/docs/what-is-vault

HashiCorp Vault는 신원 기반(identity-based)의 시크릿 및 암호화 관리 시스템으로, 민감한 데이터를 안전하게 관리하고 보호하기 위한 보안 도구로, 비밀번호, API 키, 인증서 같은 중요 정보를 중앙화된 시스템에서 안전하게 저장하고 관리한다.

Vault는 저장 데이터와 전송 데이터 모두에 대해 암호화를 제공하며, 애플리케이션이 필요로 하는 암호화 서비스도 지원한다. 필요 시점에 일회성 자격 증명을 동적으로 생성할 수도 있으며 세밀한 정책 기반 접근 제어 시스템을 갖추고 있어 누가 어떤 비밀에 접근할 수 있는지에 대한 접근 제어도 가능하다(인가).

또한 클라우드 플랫폼, 데이터베이스, SSH 등 다양한 시스템의 자격 증명을 관리하고 롤링을 지원하며, 클라우드 자격 증명 보호, 데이터베이스 접근 관리, PKI 관리, 암호화 서비스 제공, 마이크로서비스 간 안전한 통신, DevOps 및 CI/CD 파이프라인 보안 등 다양한 곳에서 사용할 수 있다. k8s와 마찬가지로 API 기반으로 작동한다.

[대표적인 시크릿 종류]

  • 비밀번호
  • Cloud Credentials : AWS, GCP, Azure, NCP
  • Database Credentials : MySQL,
  • SSH Key
  • Token, API Key : GitHub, Telegram, Slack, OpenAI, Claude
  • 인증서(PKI, TLS 등)

[Vault의 동작 방식]

  1. 인증 (Authenticate): 클라이언트가 인증 메서드를 통해 인증되면, 토큰이 생성되고 정책과 연결된다.
  2. 검증 (Validation) Github, LDAP, AppRole 등과 같은 신뢰할 수 있는 외부 소스를 통해 클라이언트를 검증한다.
  3. 인가 (Authorize) 클라이언트와 Vault의 보안 정책을 비교한다. 이 정책은 Vault 토큰을 사용하여 클라이언트가 접근할 수 있는 API 엔드포인트를 정의하는 규칙의 집합이다.
  4. 접근 (Access) Vault는 클라이언트의 신원에 연관된 정책을 기반으로 토큰을 발급하여 비밀, 키, 암호화 기능 등에 대한 접근을 허용하고, 클라이언트는 이후 작업에서 해당 Vault 토큰을 사용한다.

[Vault의 사용 사례]

(Vault 활용 및 통합사례의 발표자료 중 캡처)

2. Vault 기본 구조의 이해

  • 호텔 체크인 절차에 비유한 Vault의 동작방식 이해

호텔 체크인 과정 🔑
1. 인증 및 카드키 신청: 호텔 투숙객이 신분증/여권을 통해 신원 확인 후 카드키를 신청한다.
2. 카드키 정책 등록: 리셉션에서 다음 정보를 기반으로 정책을 등록한다

  • 1차: 엘리베이터 2층
  • 2차: 객실 208호
  • 기간: 3일
  1. 카드키 발급: 투숙객에게 카드키가 발급된다.
  2. 출입: 투숙객은 발급받은 카드키로 객실에 임시 출입할 수 있다.
    => "카드키는 객실에 대한 임시 출입 허가"를 나타낸다.

Vault를 통한 클라우드 접근 절차 🚙
1. 인증 및 접근키 신청: 사용자/앱이 다양한 인증 시스템(Okta, LDAP, TLS, Azure 등)을 통해 접근키를 신청한다.
2. 접근키 생성: HashiCorp Vault에서 다음 정보를 기반으로 접근키를 생성한다

  • AWS 접근 권한
  • 대상 서비스: S3
  • 기간: 1일
  • 접근자 위치: x.x.x.0/24 (특정 IP 대역)
  1. 접근키 발급: 사용자/앱에 접근키가 발급된다.
  2. 접근: 사용자/앱은 발급받은 키로 클라우드 서비스(AWS, Google Cloud, Azure 등)에 접근할 수 있게 된다.
    => "Vault는 클라우드에 접근할 수 있는 임시 접근키를 발급"함을 나타낸다.

[Vault 내부 아키텍처]
Docs: https://developer.hashicorp.com/vault/docs/internals/architecture

3. Kubernetes에 Vault 설치

# Create a Kubernetes namespace.
kubectl create namespace vault

# View all resources in a namespace.
kubectl get all --namespace vault

# Setup Helm repo
helm repo add hashicorp https://helm.releases.hashicorp.com

# Check that you have access to the chart.
helm search repo hashicorp/vault
# NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                          
# hashicorp/vault                         0.30.0          1.19.0          Official HashiCorp Vault Chart       
# hashicorp/vault-secrets-gateway         0.0.2           0.1.0           A Helm chart for Kubernetes          
# hashicorp/vault-secrets-operator        0.10.0          0.10.0          Official Vault Secrets Operator Chart

cat <<EOF > override-values.yaml
global:
  enabled: true
  tlsDisable: true  # Disable TLS for demo purposes

server:
  image:
    repository: "hashicorp/vault"
    tag: "1.19.0"

  standalone:
    enabled: true
    replicas: 1  # 단일 노드 실행

    config: |
      ui = true
      disable_mlock = true
      cluster_name = "vault-local"

      listener "tcp" {
        address = "[::]:8200"
        cluster_address = "[::]:8201"
        tls_disable = 1
      }

      storage "raft" { # Raft 구성 권장
        path = "/vault/data"
        node_id = "vault-dev-node-1"
      }
  service:
    enabled: true
    type: NodePort
    port: 8200
    targetPort: 8200
    nodePort: 30000   # Kind에서 열어둔 포트 중 하나 사용

injector:
  enabled: true

ui:
  enabled: true
  serviceType: "NodePort"
EOF

helm install을 실행한 뒤 제대로 설치가 되었는지 확인한다.

# Helm Install 실행
helm upgrade vault hashicorp/vault -n vault -f override-values.yaml --install

# 네임스페이스 변경 : vault
kubens vault
Context "kind-myk8s" modified.
Active namespace is "vault".

# 배포확인
k get pods,svc,pvc

제대로 설치가 된 것을 확인할 수 있다. 이제 Vault 초기화 및 잠금해제를 진행해본다.

kubectl exec -ti vault-0 -- vault status

Seal Type: shamir - Vault가 Shamir 알고리즘을 사용하여 seal 키를 관리하고 있다.
Initialized: false - Vault가 아직 초기화되지 않았다.
Sealed: true - Vault가 현재 봉인된 상태이다.
Total Shares: 0 및 Threshold: 0 - 아직 초기화되지 않았기 때문에 키 공유 수와 임계값이 설정되지 않았다.
Unseal Progress: 0/0 - 봉인 해제 진행 상황이 없다.
Version: 1.19.0 - 실행 중인 Vault의 버전
Storage Type: raft - 데이터 저장소로 Raft 합의 알고리즘 기반 스토리지를 사용 중이다.
HA Enabled: true - 고가용성(High Availability) 기능이 활성화되어 있다.

Vault Unseal 자동화를 하기 위해 아래 스크립트를 입력한다.

cat <<EOF > init-unseal.sh
#!/bin/bash

# Vault Pod 이름
VAULT_POD="vault-0"

# Vault 명령 실행
VAULT_CMD="kubectl exec -ti \$VAULT_POD -- vault"

# 출력 저장 파일
VAULT_KEYS_FILE="./vault-keys.txt"
UNSEAL_KEY_FILE="./vault-unseal-key.txt"
ROOT_TOKEN_FILE="./vault-root-token.txt"

# Vault 초기화 (Unseal Key 1개만 생성되도록 설정)
\$VAULT_CMD operator init -key-shares=1 -key-threshold=1 | sed \$'s/\\x1b\\[[0-9;]*m//g' | tr -d '\r' > "\$VAULT_KEYS_FILE"

# Unseal Key / Root Token 추출
grep 'Unseal Key 1:' "\$VAULT_KEYS_FILE" | awk -F': ' '{print \$2}' > "\$UNSEAL_KEY_FILE"
grep 'Initial Root Token:' "\$VAULT_KEYS_FILE" | awk -F': ' '{print \$2}' > "\$ROOT_TOKEN_FILE"

# Unseal 수행
UNSEAL_KEY=\$(cat "\$UNSEAL_KEY_FILE")
\$VAULT_CMD operator unseal "\$UNSEAL_KEY"

# 결과 출력
echo "[🔓] Vault Unsealed!"
echo "[🔐] Root Token: \$(cat \$ROOT_TOKEN_FILE)"
EOF

# 실행 권한 부여
chmod +x init-unseal.sh

# 실행
./init-unseal.sh

init-unseal.sh를 실행한 결과이다. 실행이 완료되면 [Vault Unsealed!] 메세지를 확인할 수 있다.

kubectl exec -ti vault-0 -- vault status

실행한 이후에는 몇 가지 값들이 바뀐 것을 확인할 수 있다.

Seal Type: shamir - Vault가 Shamir 비밀 공유 알고리즘을 사용하여 봉인(seal) 키를 관리한다.
Initialized: true - Vault가 성공적으로 초기화되었다.
Sealed: false - Vault가 봉인 해제(unsealed) 상태입니다. 이는 Vault가 활성화되어 저장된 비밀에 접근할 수 있게 된다.
Total Shares: 1 - 봉인 키가 1개의 조각으로 나눠져 있습니다. (단일 키로 관리)
Threshold: 1 - 봉인 해제에 필요한 키 조각의 수 (단일 키)
Version: 1.19.0 - 실행 중인 Vault의 버전
Storage Type: raft - 데이터 저장소로 Raft 합의 알고리즘 기반 스토리지를 사용 중이다.
Cluster Name: vault-local - Vault 클러스터의 이름
Cluster ID: 클러스터의 고유 식별자입니다.
HA Enabled: true - 고가용성(High Availability) 기능이 활성화되어 있다.
HA Cluster: https://vault-0.vault-internal:8201 - HA 클러스터의 내부 통신 URL
HA Mode: active - 이 Vault 인스턴스가 현재 활성(active) 상태로 작동 중이다.
Raft Committed/Applied Index: 37 - Raft 합의 알고리즘으로 적용된 인덱스 번호

포트포워딩 이후에 UI에 접근도 가능하다. 실습을 위해 KV 엔진 활성화 및 샘플 데이터를 추가한다.

# KV v2 형태로 엔진 활성화
vault secrets enable -path=secret kv-v2

# 샘플 시크릿 저장
vault kv put secret/sampleapp/config \
  username="demo" \
  password="p@ssw0rd"
  
# 입력된 데이터 확인
vault kv get secret/sampleapp/config

======== Secret Path ========
secret/data/sampleapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-03-30T05:18:47.797852422Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    p@ssw0rd
username    demo

이후에 UI에서 확인하면 Version 1로 설정한 password와 username을 확인할 수 있다.

4. Vault Sidecar 연동 (Vault Agent)

Docs: https://developer.hashicorp.com/vault/docs/platform/k8s/injector/examples#before-using-the-vault-agent-injector

Vault Agent Injector는 Kubernetes Pod 내부에 Vault Agent를 자동으로 주입해주는 기능이다. 이를 통해 어플리케이션이 Vault로부터 자동으로 비밀 정보를 받아올 수 있게 된다.

이 기능을 사용하기 위한 제약 조건
1. Vault가 설치되어 있고, Kubernetes와 통합되어 있어야 한다
2. Vault Agent Injector가 클러스터에 배포되어 있어야 한다 (별도의 구성요소)
3. Kubernetes 인증 방식이 활성화되어야 한다
4. 정책과 역할이 정의되어 있어야 한다
5. 애플리케이션 Pod에 주입할 주석(annotation)을 추가해야 한다 (특정 주석이 있는 Pod에 대해서만 Vault Agent를 주입하기 때문)

vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "example-role"

[실습]

Vault AppRole 방식 인증 구성해본다.

# 1. AppRole 인증 방식 활성화
vault auth enable approle || echo "AppRole already enabled"
vault auth list

approle이 추가된 것을 확인할 수 있다.

# 2. 정책 생성
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
  capabilities = ["read"]
}
EOF

# 3. AppRole Role 생성
vault write auth/approle/role/sampleapp-role \
  token_policies="sampleapp-policy" \
  secret_id_ttl="1h" \
  token_ttl="1h" \
  token_max_ttl="4h"

# 4. Role ID 및 Secret ID 추출 및 저장
ROLE_ID=$(vault read -field=role_id auth/approle/role/sampleapp-role/role-id)
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/sampleapp-role/secret-id)

echo "ROLE_ID: $ROLE_ID"
echo "SECRET_ID: $SECRET_ID"


# 5. 파일로 저장
mkdir -p approle-creds
echo "$ROLE_ID" > approle-creds/role_id.txt
echo "$SECRET_ID" > approle-creds/secret_id.txt

그 뒤에는 Vault Agent Sidecar를 연동한다. Vault Agent는 vault-agent-config.hcl 설정을 통해 연결할 Vault의 정보와, Template 구성, 렌더링 주기, 참조할 Vault KV 위치정보 등을 정의한다.

  1. Vault Agent 설정 파일 작성 및 생성 (vault-agent-config.hcl) - HCL
cat <<EOF | kubectl create configmap vault-agent-config -n vault --from-file=agent-config.hcl=/dev/stdin --dry-run=client -o yaml | kubectl apply -f -
vault {
  address = "http://vault.vault.svc:8200"
}

auto_auth {
  method "approle" {
    config = {
      role_id_file_path = "/etc/vault/approle/role_id"
      secret_id_file_path = "/etc/vault/approle/secret_id"
      remove_secret_id_file_after_reading = false
    }
  }

  sink "file" {
    config = {
      path = "/etc/vault-agent-token/token"
    }
  }
}

template_config {
  static_secret_render_interval = "20s"
}

template {
  destination = "/etc/secrets/index.html"
  contents = <<EOH
  <html>
  <body>
    <p>username: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.username }}{{ end }}</p>
    <p>password: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.password }}{{ end }}</p>
  </body>
  </html>
EOH
}
EOF
  1. 샘플 애플리케이션 + Sidecar 배포(수동방식)
kubectl apply -n vault -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-vault-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-vault-demo
  template:
    metadata:
      labels:
        app: nginx-vault-demo
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      - name: vault-agent-sidecar
        image: hashicorp/vault:latest
        args:
          - "agent"
          - "-config=/etc/vault/agent-config.hcl"
        volumeMounts:
        - name: vault-agent-config
          mountPath: /etc/vault
        - name: vault-approle
          mountPath: /etc/vault/approle
        - name: vault-token
          mountPath: /etc/vault-agent-token
        - name: html-volume
          mountPath: /etc/secrets
      volumes:
      - name: vault-agent-config
        configMap:
          name: vault-agent-config
      - name: vault-approle
        secret:
          secretName: vault-approle
      - name: vault-token
        emptyDir: {}
      - name: html-volume
        emptyDir: {}
EOF
  1. SVC 생성
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx-vault-demo
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30001 # Kind에서 설정한 Port
EOF
  1. 생성된 컨테이너 확인

kubectl get pod -l app=nginx-vault-demo
kubectl describe pod -l app=nginx-vault-demo

파드 내에 사이드카 컨테이너 추가되어 2/2가 된 것을 확인할 수 있다.

# 볼륨 마운트 확인
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/vault-agent-token

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault-agent-token/token ; echo

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/agent-config.hcl

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault/approle

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/role_id ; echo

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/secret_id ; echo

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/secrets       
total 4
-rw-r--r--    1 vault    vault           94 Apr 10 02:09 index.html


# mutating admission
kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io   
NAME                       WEBHOOKS   AGE
vault-agent-injector-cfg   1          3h10m

그 뒤에 vault ui에서 password를 변경하고 nginx에서 다시 확인한다.

kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/secrets/index.html

5. Jenkins + Vault (AppRole) - CI

*회사 컴퓨터에서 SSL 통신이 차단되어 Jenkins 설치가 제대로 안되어서 실습을 별도로 진행하지는 않았다.

젠킨스는 Vault에 시크릿으로 분류된 데이터를 필요로 하는 작업(job)을 실행한다.
젠킨스는 마스터 노드와 워커 노드를 가지고 있으며, 워커 노드는 짧은 시간 동안 실행되는 컨테이너 러너에서 작업을 실행한다.

  • 프로세스
    1. 젠킨스 워커가 Vault에 인증
    2. Vault는 토큰을 반환
    3. 워커는 이 토큰을 사용해 작업에 해당하는 역할의 Wrapped SecretID를 요청
    4. Vault는 Wrapped SecretID를 반환
    5. 워커는 작업 러너를 생성하고, Wrapped SecretID를 변수로 전달
    6. 러너 컨테이너는 Wrapped SecretID의 unwrap을 요청
    7. Vault는 SecretID를 반환
    8. 러너는 RoleID와 SecretID를 사용해 Vault에 인증
    9. Vault는 필요한 시크릿 정보를 읽을 수 있는 정책이 포함된 토큰을 반환
    10. 러너는 이 토큰을 사용해 Vault에서 시크릿을 가져옴
pipeline {
  agent any

  environment {
    VAULT_ADDR = 'http://192.168.0.2:30000' // 실제 Vault 주소로 변경
  }

  stages {
    stage('Read Vault Secret') {
      steps {
        withVault([
          vaultSecrets: [
            [
              path: 'secret/sampleapp/config',
              engineVersion: 2,
              secretValues: [
                [envVar: 'USERNAME', vaultKey: 'username'],
                [envVar: 'PASSWORD', vaultKey: 'password']
              ]
            ]
          ],
          configuration: [
            vaultUrl: "${VAULT_ADDR}",
            vaultCredentialId: 'vault-approle-creds'
          ]
        ]) {
          sh '''
            echo "Username from Vault: $USERNAME"
            echo "Password from Vault: $PASSWORD"
          '''
          script {
            echo "Username (env): ${env.USERNAME}"
            echo "Password (env): ${env.PASSWORD}"
          }
        }
      }
    }
  }
}

6. ArgoCD + Vault Plugin (Kubernetes Auth/AppRole) - CD

📖 Vault + ArgoCD Plugin 패턴

  • Argo CD에는 다양한 시크릿 관리 도구(HashiCorp Vault, IBM Cloud Secrets Manager, AWS Secrets Manager 등)플러그인을 통해 Kubernetes 리소스에 주입할 수 있도록 지원한다.
  • 플러그인을 통해 Operator 또는 CRD(Custom Resource Definition)에 의존하지 않고 GitOps와 Argo CD로 시크릿 관리 문제를 해결할 수 있다.
  • 특히 Secret 뿐만 아니라, deployment, configMap 또는 기타 Kubernetes 리소스에도 사용할 수 있다.
profile
공블로그

0개의 댓글