새소식

CICD

[CI/CD Study 7주차] Vault 개요 - Vault Agent, Transit 엔진

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

 

이번 주차 스터디에서는 특별히 가시다님 대신에 형욱님께서 Vault 에 대한 개요, 필요성, 사용법 등에 대해 전반적으로 리딩해주셨습니다.

이번 기회로 인해 어설프게나마 알고 있던 Vault 에 대해서 이해할 수 있는 소중한 시간이었습니다. 

 

 

1. 보안 사전 지식

 

Vault 도구가 보안과 밀접하게 관련이 있는 만큼 보안과 관련된 사전 지식을 먼저 간략하게 알아보겠습니다.



1.1. 정보 보안의 3요소 (CIA Triad)

 

 

1. 기밀성 (Confidentiality)

- 허가된 사람, 시스템만 정보에 접근할 수 있어야 함
- Vault: Secrets Engine (KV, Transit) 을 통해 데이터를 암호화하여 저장, 토큰이 없을 경우 데이터 접근 불가

 

2. 무결성 (Integrity)

- 정보가 허가 없이 변조되거나 삭제되지 않아야 하며, 변경될 경우 알아챌 수 있어야 함
- Vault: Hasihing 기능을 통해 데이터 원본이 변하지 않았음을 검증

 

3. 가용성 (Availability)

- 정당한 사용자가 원할때는 언제든지 정보나 서비스를 이용할 수 있어야 함
- Vault: HA 구성을 통해 서버 한 대가 죽어도 대기 중인 다른 서버가 즉시 역할을 이어받아 서비스 가동



1.2. 액세스 제어 3단계 (AAA)

 

 

1. 인증 (Authentication)

- 접속하려는 대상이 누구인지 신원 확인
- Vault: Auth Method

 

2. 인가 (Authorization)

- 인증된 사용자에게 허용된 권한 (읽기/쓰기/삭제 등) 을 부여하는 절차
- Vault: Policies (ACL)

 

3. 감사/계정 관리 (Accounting/Auditing)

- 사용자가 수행한 모든 활동을 기록하고 추적
- Vault: Audit Devices



1.3. 시크릿 (Secret) 종류

 

사용자 및 시스템 접근 자격 증명 (Credentials)

 

 

1. 비밀번호(Password)

- 개인 계정, 서버 접근, 설정 파일 내의 일반 텍스트 암호

 

2. SSH Key

- 서버에 안전하게 접속하기 위한 암호화된 키 쌍

 

3. Database Credentials

- 데이터베이스에 접속하여 데이터를 읽고 쓰기 위한 사용자 ID 및 암호



서비스 연동 및 자동화 키

 

1. Cloud Credentials

- 클라우드 환경의 리소스를 제어하기 위한 접근 키

 

2. Token, API Key
- 외부 서비스 기능을 사용하기 위해 발급받는 인증 토큰 또는 키

 

보안 통신 및 암호화 자산

 

1. 인증서
- 웹사이트의 신뢰성을 보장하고 통신을 암호화 하는데 사용되는 인증서 및 개인 키

 

2. 암호화 키
- 데이터베이스 또는 파일 시스템의 데이터를 암호화/복호화 하는 데 사용되는 핵심 키




2. Vault

 

2.1. Vault 의 필요성

 

IT 인프라와 애플리케이션 아키텍처가 복잡해지면서 기존의 방식으로는 시크릿을 안전하게 관리하기 불가능해졌기 때문에 Vault 라는 솔루션이 필요

 

 

2.2. IT 아키텍처 변천 과정

 

1. 메인프레임 / 모놀리식 시대

- 모든 것이 하나의 거대한 시스템 내에 중앙 집중화
- 시크릿 관리 측면 : 관리할 시크릿 수가 적고, 변경이 거의 없으며, 접근 경로 명확 (서버 내에 저장)

 

2. 3-Tier / 클라이언트 - 서버 시대

- 웹, 앱, 데이터베이스 역할 분리
- 시크릿 관리 측면 : 시크릿이 각각의 서버로 분산되기 시작, 다만 인프라는 정적이며 변경 주기는 길었음

 

3. 현대 클라우드 / MSA / DevOps 시대

- 수십, 수백개의 마이크로서비스(MSA), 동적으로 생성되고 사라지는 컨테이너
- 시크릿 관리 측면: 시크릿의 폭증, 동적 환경에서 시크릿을 안전하게 보관하는 것이 어려워 짐

 

요약

 

- 마이크로서비스 아키텍처로 인해 시크릿 관리 대상이 폭발적으로 증가
- 동적 환경에서의 시크릿 사용으로 인해, 시크릿 생성 및 소멸 주기가 굉장히 짧음
- 시크릿이 중앙에서 관리되지 않고 여러 곳에 흩어져 보안에 취약

 

 

2.3. 제로 트러스트 (Zero Trust) 보안 모델의 대두

 

출처: Zero Trust Cybersecurity: ‘Never Trust, Always Verify’, NIST, Oct. 2020.

 

 

과거에는 내부망에 들어오면 내부의 시스템은 서로를 신뢰했으나, 클라우드와 MSA 환경에서는 내부와 외부의 경계가 모호해짐
해커가 일단 내부망에 침투하면 내부 신뢰를 기반으로 다른 시스템에 쉽게 접근하는 일이 가능

 

그렇기 때문에, 제로 트러스트에서는 내부에서 들어온 호출일지라도 모든 요청을 의심하고 검증하는 것이 핵심

 

 

2.4. Vault 란??

 

 

Vault 는 신원 기반(Identity-Based)의 시크릿 및 암호화 관리 시스템

이 시스템은 인증 (Authentication) 및 인가(Authorization) 방법을 통해 암호화 서비스를 제공하여

시크릿에 대한 안전하고 감사 가능한 제한된 접근을 보장

 

 

 

시크릿이란 접근 접근을 철저히 통제하고자 하는 모든 것을 의미
Vault 는 모든 시크릿에 대해 통합된 인터페이스 제공, 엄격한 접근 제어와 상세한 감사 로그 기능 제공
Vault 는 클라이언트 (사용자, 기계, 애플리케이션 등)을 검증하고 인가된 후에만 비밀이나 민감한 데이터에 접근할 수 있도록 함




3. Vault 실습

 

실습 환경은 Kind 로 구성된 쿠버네티스 클러스터 위에 Vault 를 쿠버네티스 환경으로 구성했습니다.
실습을 위해 Dev Mode 로 설치를 진행했습니다.

 

3.1. 실습 환경 구성

 

Kind 클러스터 배포

 

# kind k8s 배포
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
  extraPortMappings:
  - containerPort: 30000 # Vault UI
    hostPort: 30000
  - containerPort: 30001 # Jenkins UI
    hostPort: 30001
  - containerPort: 30002 # DB 배포(PostgreSQL 또는 MySQL)
    hostPort: 30002
  - containerPort: 30003 # # Sample App
    hostPort: 30003
EOF

 

네임스페이스 및 Helm 배포

 

# Vault 네임스페이스 생성
kubectl create namespace vault

# Helm Repo 등록
helm repo add hashicorp https://helm.releases.hashicorp.com

# Vault Dev Mode 설정
cat <<EOF > vault-values-dev.yaml
global:
  enabled: true
  tlsDisable: true

injector:
  enabled: true
  # Sidecar Injection을 위해 필요한 설정

server:
  dev:
    enabled: true
    devRootToken: "root" # 학습 편의를 위해 Root Token을 'root'로 고정

  # 데이터 영구 저장이 필요 없으므로 비활성화 (Dev모드는 메모리 사용)
  dataStorage:
    enabled: false

  # UI 활성화 및 NodePort 노출
  service:
    type: "NodePort"
    nodePort: 30000

  ui:
    enabled: true
EOF

# Vault 배포
helm upgrade vault hashicorp/vault -n vault -f vault-values-dev.yaml --install

 

MacOS CLI 구성

 

Mac 에서는 Homebrew 를 통해서 Vault CLI 를 설치할 수 있습니다.

 

# Mac Brew 설치
brew tap hashicorp/tap
brew install hashicorp/tap/vault

# NodePort 를 통해 변수 입력
export VAULT_ADDR='http://localhost:30000'

# Vault Login - Token 명칭에 root 입력
vault login

 

 

UI 로그인

 

Mac 일 경우 아래와 같이 명령어를 입력하면 되고, Windows 는 브라우저 창에 직접 기입합니다.

 

open http://localhost:30000

 

 

 

3.2. KV 시크릿 엔진 활성화 및 샘플 구성

 

Vault 에서 KV 엔진은 2가지 버전이 존재합니다.

KV Version1 : KV 버전 관리 불가
KV Version2 : KV 버전 관리 가능

 

# 활성화된 시크릿 엔진 목록
vault secrets list

 

 

아직 secret 경로의 시크릿에는 아무런 값이 존재하지 않습니다.
샘플 시크릿을 넣어 보겠습니다.

 

vault kv put secret/sampleapp/config \
  username="demo" \
  password="p@ssw0rd"

 

 

Vault UI 에서 값 확인

 

 

3.3. Vault Agent

 

Vault 를 도입하면 개발자들이 가장 먼저 부딪히는 문제가 있는데, 바로 애플리케이션에서 Vault 를 어떻게 연동할 것인가입니다.

 

개발자가 Vault SDK 를 직접 사용하면 부담이 크고 언어마다 구현 방식이 달라지기 때문에 쉽지 않습니다.
그렇기 때문에 Vault 는 Sidecar 기반의 Agent 로 개발자 대신에 Vault 와 연동을 해줄 수 있습니다.

 

 

기존 방식

 

- 개발자가 SDK 로 직접 Vault 와 연동
- 연동 시, 다음 사항들을 고려해야 함
   1. Auth Method 구현
   2. Token 관리 로직
   3. 에러 처리 및 재시도 로직
   4. Secret JSON Parsing & 파일 반영

 

Vault Agent (Sidecar 패턴)

 

- Agent 가 Vault 인증을 대신하며 토큰 발급/갱신, 시크릿 렌더링, 파일 생성까지 모두 자동 처리
- 애플리케이션은 해당 파일만 읽음

- Vault Agent 의 동작 기능

   1. 자동 로그인 (Auto-Auth)
   2. 토근 자동 갱신 (Lifecycle Management)
   3. 템플릿 렌더링 (Consul Template 내장)
   4. Simplified Consumption

 

Vault Agent 동작 방식

 

1. Vault 인증 요청

- Vault Agent 가 설정된 Auto-Auth 방식을 통해 Vault 에 인증
- 애플리케이션은 Vault 와 직접 통신하지 않음

 

2. 애플리케이션 신원 검증

- Vault 는 신뢰할 수 있는 플랫폼을 통해 애플리케이션이 실제로 인증된 워크로드인 지 확인

 

3. 신원확인 완료

- 신원이 검증되면 해당 워크로드에 어떤 Role 과 Policy 를 적용할 지 결정

 

4. 클라이언트 토큰 발급

- Vault 가 Agent 에게 애플리케이션 전용 토큰 발급

 

5. 토큰 저장

- 발급 토큰을 로컬 Sink 에 저장

 

6. 시크릿 조회 및 템플릿 렌더링

- 애플리케이션은 Agent 가 생성한 파일만 읽어서 수행

 

7. 애플리케이션이 렌더링된 파일 사용

- 애플리케이션은 Agent 가 생성한 파일만 읽어서 수행

 

 

3.4. Vault Agent AppRole 방식 인증 구성

 

AppRole

 

AppRole 인증(AppRole Auth Method) 은 서버나 배치 작업, CI/CD 파이프라인처럼 사람이 아닌 애플리케이션이 Vault에 인증하고 Secret을 받아가기 위한 방식입니다.
즉 Application + Role 방식이며, Vault는 어플리케이션이 제출한 인증 정보가 해당 Role 정책에 부합하면 Token을 발급해줍니다.

 

실습은 Vault CLI 를 통해서 진행됩니다.

 

# 기존 Vault Auth 리스트 조회
vault auth list

# AppRole 인증 활성화
vault auth enable approle || echo "AppRole already enabled"

 

 

AppRole 에 적용할 정책 생성

 

AppRole 을 활성화 했지만, 권한 (Authorization) 이 부여되어 있지 않으므로 아무런 권한이 없습니다.
다음 명령어를 통해 정책을 추가할 수 있습니다.

 

# 정책 파일 생성 (secret/data/sampleapp/* 경로에 대해 read 권한 부여)
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
  capabilities = ["read"]
}
EOF

# AppRole 과 정책 연결
vault write auth/approle/role/sampleapp-role \
  token_policies="sampleapp-policy" \
  secret_id_ttl="1h" \
  token_ttl="1h" \
  token_max_ttl="4h"

# 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"

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

# Kubernetes 시크릿으로 등록
kubectl create secret generic vault-approle -n vault \
  --from-literal=role_id="${ROLE_ID}" \
  --from-literal=secret_id="${SECRET_ID}" \
  --save-config \
  --dry-run=client -o yaml | kubectl apply -f -

 

ROLE_ID 와 SECRET_ID 는 Vault 가 관리하는 AppRole 접속 토큰 정보입니다.

 

Vault Agent 설정 파일 생성

 

Vault Agent 의 동작 방식 및 설정은 ConfigMap 으로 구성할 수 있습니다.

 

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

 

 

샘플 애플리케이션 배포

 

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

 

 

Service 배포

 

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

 

Pod 가동 후 Localhost:30001 로 들어가면 앞서 Vault KV 엔진에 저장한 시크릿을 확인할 수 있습니다.
Nginx Pod 대신 Vault Agent Pod 가 사이드카로 동작하면서 Vault 접근을 실행한 것입니다.

 

 

KV 값 변경 후 확인

 

Vault UI 에서 KV 값을 변경하면 어떻게 동작하는 지 확인해보겠습니다.
기존 password 와 username 에서 new- 값을 추가했습니다.

 

 

새롭게 업데이트 된 시크릿 정보를 가져오는 것을 확인했습니다.




4. Jenkins - Vault 연동

 

CI/CD 는 민감 정보를 다루는 파이프라인이기 때문에 비밀번호, API Key, Token 을 절대 코드에 넣어서는 안됩니다.
이를 숨기기 위해 Vault 와 같은 시크릿 관리도구와 연계하여 접근 정보를 안전하게 관리할 수 있습니다.

 

 

4.1. Jenkins Vault Plugin

 

해당 실습은 젠킨스가 설치되어 있는 전제하에 진행됩니다.

 

Vault Plugin 설치

 

먼저 Vault와 연동하기 위해서는 Vault Plugin 을 설치해야 합니다.

 

 

Jenkins 에 AppRole 정보 기입

 

Vault 에서 발급된 AppRole 에 대한 접근 정보를 Jenkins Credentials 에 기입합니다.

 

 

그 후, Jenkins System 정보에서 Vault 서버 정보를 기입합니다.

 



4.2. Jenkins Pipeline 생성

 

이제 실제로 Vault Plugin 을 파이프라인에서 사용해서 Vault 시크릿을 잘 가져오는지 확인해보겠습니다.

 

Jenkins Pipeline 을 생성해주고, 아래와 같이 Jenkinsfile 을 구성합니다.

 

pipeline {
  agent any

  environment {
    VAULT_ADDR = 'http://vault.vault.svc:8200' // 실제 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}"
          }
        }
      }
    }
  }
}

 

그 후 실행을 누르고 Console Output 을 확인해봅니다.

 

Vault 시크릿을 가져온 것을 확인했습니다.

 

 

4.3. Jenkins + 동적(Dynamic) 시크릿 활용

 

이 때까지의 모든 실습은 정적(Static) 시크릿을 이용한 것입니다.
정적 시크릿은 미리 생성해둔 접근 정보를 데이터베이스에 저장해놓고 사용하는 것을 말하며,
동적 시크릿은 요청 시마다 발급되고, TTL 이 지나면 자동으로 사라지는 단기 인증 정보를 말합니다.

 

동적 시크릿을 사용하는 가장 큰 이유는 정적 시크릿이 변경되지 않으면 보안 위협이 도사리고 있기 때문에,

암호 변경에 대한 리스크를 해소하기 위함입니다.


하지만 이러한 장점에도 불구하고 현실적으로는 동적 시크릿을 사용하기가 쉽지않습니다.
애플리케이션에 대한 DB 접속 키가 계속 바뀐다면, 성능이나 안정성에 영향을 끼칠 수도 있기 때문입니다.

 

그럼에도 불구하고, 동적 시크릿이 어떻게 동작하는 지 확인해보는 실습을 진행하겠습니다.

 

PostgreSQL 생성

 

Vault 가 계정을 생성해줄 대상 DB 를 배포합니다.

 

# postgres-deploy.yaml
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: default
spec:
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:13
          env:
            - name: POSTGRES_PASSWORD
              value: "rootpassword"
            - name: POSTGRES_DB
              value: "mydb"
          ports:
            - containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: default
spec:
  type: NodePort
  ports:
    - port: 5432
      targetPort: 5432
      nodePort: 30002  # [External] Jenkins 접속용
  selector:
    app: postgres
EOF

 

Vault Database Engine 설정

 

Vault 가 DB 관리자 권한을 가지고 요청 시마다 임시 유저를 생성합니다.

 

# 터미널에서 Vault 접속 정보 입력
export VAULT_ADDR=http://127.0.0.1:30000
export VAULT_TOKEN=root

# DB Secret Engine 활성화
vault secrets enable database

# Postgre 연결
vault write database/config/my-postgresql-database \
    plugin_name=postgresql-database-plugin \
    allowed_roles="jenkins-role" \
    connection_url="postgresql://{{username}}:{{password}}@postgres.default.svc.cluster.local:5432/mydb?sslmode=disable" \
    username="postgres" \
    password="rootpassword"

# 1시간짜리 임시 계정 생성 규칙
vault write database/roles/jenkins-role \
    db_name=my-postgresql-database \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

 

AppRole 권한 확장

 

새로운 AppRole 을 생성하는 대신 기존 AppRole 을 활용합니다.

 

# 기존 KV 권한 + 새로운 DB 권한(database/creds/jenkins-role) 병합
vault policy write sampleapp-policy - <<EOF
# 1. KV v2 데이터 읽기
path "secret/data/sampleapp/*" {
  capabilities = ["read"]
}
# 2. KV v2 목록 조회 (플러그인 에러 방지용 필수!)
path "secret/metadata/sampleapp/*" {
  capabilities = ["list", "read"]
}
# 3. DB Creds 발급
path "database/creds/jenkins-role" {
  capabilities = ["read"]
}
EOF

# 관리자 로그인 상태에서 실행
vault write auth/approle/role/sampleapp-role \
  token_policies="default,sampleapp-policy" \
  secret_id_ttl="0" \
  token_ttl="1h" \
  token_max_ttl="4h"

 

파이프라인 작성

 

pipeline {
  agent any

  environment {
    // Jenkins(Docker) -> Vault(K8s NodePort)
    VAULT_ADDR = 'http://vault.vault.svc:8200' 

    // Jenkins(Docker) -> DB(K8s NodePort)
    DB_HOST = 'postgres.default.svc'
    DB_PORT = '5432'
  }

  stages {
    stage('Vault 통합 및 DB 접속 테스트') {
      steps {
        withVault([
          configuration: [
            vaultUrl: "${VAULT_ADDR}",
            vaultCredentialId: 'vault-approle-creds',
            // ⚠️ 중요: 여기서 전역 engineVersion 설정을 하지 않습니다.
            skipSslVerification: true
          ],
          vaultSecrets: [
            // 1. KV Secret (정적 시크릿)
            // KV v2 엔진을 사용하므로 engineVersion: 2를 명시합니다.
            [
              path: 'secret/sampleapp/config',
              engineVersion: 2,
              secretValues: [
                [envVar: 'STATIC_USER', vaultKey: 'username']
              ]
            ],
            // 2. Database Secret (동적 시크릿)
            // DB 엔진은 기본 방식(v1)으로 통신해야 경로 에러가 없습니다.
            [
              path: 'database/creds/jenkins-role',
              engineVersion: 1,
              secretValues: [
                [envVar: 'DB_USER', vaultKey: 'username'],
                [envVar: 'DB_PASS', vaultKey: 'password']
              ]
            ]
          ]
        ]) {
          script {
            echo "=================================================="
            echo "             Vault 연동 테스트 시작                "
            echo "=================================================="

            // 1. 정적 시크릿 확인
            // sed 명령어로 글자 사이에 공백을 넣어 마스킹(****)을 우회합니다.
            // 예: d e m o
            sh '''
              echo "[1] KV Secret (Static)"
              echo " - 원본 값은 보안상 **** 로 표시됩니다."
              echo " - 실제 값 확인: $(echo $STATIC_USER | sed "s/./& /g")"
            '''

            // 2. 동적 시크릿 확인 (핵심!)
            // Vault가 생성한 임시 DB 계정(v-token-...)을 확인합니다.
            sh '''
              echo "--------------------------------------------------"
              echo "[2] Database Secret (Dynamic)"
              echo " - Vault가 생성한 임시 계정 ID입니다."
              echo " - 실제 값 확인: $(echo $DB_USER | sed "s/./& /g")"
              echo "--------------------------------------------------"
            '''

            // 3. DB 접속 시뮬레이션
            // 실제 애플리케이션에서 DB 연결 문자열을 만드는 과정입니다.
            sh '''
              echo "[3] DB Connection Simulation"
              echo " - Connecting to: ${DB_HOST}:${DB_PORT}"
              echo " - User: ${DB_USER}"
              echo " - Password: (Hidden)"
              echo " >> ✅ DB 접속 테스트 성공! (가상)"
            '''
          }
        }
      }
    }
  }

  post {
    success {
      script {
        echo "🎉 Pipeline 성공!" 
        echo "   -> 확인된 DB 계정(${env.DB_USER})은 Vault의 TTL 설정에 따라 1시간 후 자동 삭제됩니다."
      }
    }
    failure {
      echo "💥 Pipeline 실패! Vault 로그나 네트워크 설정을 확인하세요."
    }
  }
}

 

 

이후 파이프라인이 실행될 때마다 PostgreSQL 에는 계정이 추가되는 것을 알 수 있습니다.

 




5. Vault 를 이용한 암호화

 

Vault 에서도 데이터를 암호화할 수 있는 Encryption as a Service(EaaS) 기능을 제공합니다.

 

 

5.1. 암호화 기본 개념

 

3가지 핵심 암호화 방식

 

 

1. 대칭키

- 암호화와 복호화에 같은 키 사용
- 빠름, 대용량 데이터 처리에 적합

 

2. 비대칭키

- 공개키(암호화)와 개인키(복호화)가 분리
- 키 교환, 전자 서명, 신원 인증에 적합
- 연산 비용이 높아 대용량 데이터에는 부적합

 

3. 해시

- 데이터를 고정된 길이의 문자열로 변환
- 일방향성, 데이터 무결성 검증
- 데이터 저장 시 Salt + Iteration 필수 사용

 

계층별 암호화 방안
데이터가 존재하는 위치에 따른 암호화 전략

 

1. 전송 구간(Data in Transit)

- 데이터가 네트워크를 이동할 때 보호

 

2. 저장 구간(Data at Rest)

- 디스크나 DB 에 저장될 때 보호

 

3. 애플리케이션 계층(Application Level Encryption) - Vault 지원
- 데이터가 DB 에 저장되기 전, 앱 단계에서 암호화 수행
- DB 에는 암호문만 저장되므로, DB 가 해킹당하더라도 데이터 내용을 알 수 없음

 

 

 

5.2. Vault 암호화 실습 준비

 

이번 Vault 스터디를 리딩해주신 형욱님의 Dockerhub 이미지를 활용했습니다.

 

실습 편의를 위한 변수 입력

 

export NS=vault-demo
export IMAGE=hyungwookhub/vault-transit-demo:v1
export VAULT_ADDR=http://localhost:30000                 # NodePort 엔드포인트
export VAULT_TOKEN=root       

 

Vault Transit 활성화

 

Vault 암복호화 전용 엔진인 Transit 을 활성화 합니다.

 

# Transit 엔진 활성화
vault secrets enable transit

# 암호화 키 생성 (AES-256-GCM 모드)
vault write -f transit/keys/ds-poc type=aes256-gcm96

 

 

MySQL 배포

 

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: ${NS}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: ${NS}
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0.31
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "rootpassword"
        ports:
        - containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: ${NS}
spec:
  type: NodePort
  selector:
    app: mysql
  ports:
  - name: mysql
    port: 3306
    targetPort: 3306
    nodePort: 30002
EOF

# DB 생성
kubectl -n ${NS} exec -it deploy/mysql -- \
  mysql -uroot -prootpassword -e "CREATE DATABASE IF NOT EXISTS VaultData;"

 

Transit Demo 앱 배포

 

Transit Engine 의 암복호화를 테스트해볼수 있는 애플리케이션을 배포합니다.

 

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vault-transit-demo
  namespace: ${NS}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vault-transit-demo
  template:
    metadata:
      labels:
        app: vault-transit-demo
    spec:
      containers:
      - name: app
        image: ${IMAGE}
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        env:
        - name: MYSQL_HOST
          value: mysql.${NS}.svc.cluster.local
        - name: MYSQL_PORT
          value: "3306"
        - name: MYSQL_DB_NAME
          value: VaultData
        - name: MYSQL_USERNAME
          value: root
        - name: MYSQL_USERPW
          value: rootpassword
        - name: VAULT_HOST
          value: vault.vault.svc.cluster.local
        - name: VAULT_PORT
          value: "8200"
        - name: VAULT_SCHEME
          value: http
        - name: VAULT_TOKEN
          value: root
        - name: VAULT_TRANSIT_KEY_NAME
          value: ds-poc
        - name: SERVER_PORT
          value: "8080"
        - name: AWS_REGION
          value: "ap-northeast-2"
---
apiVersion: v1
kind: Service
metadata:
  name: vault-transit-demo
  namespace: ${NS}
spec:
  type: NodePort
  selector:
    app: vault-transit-demo
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30003
    name: http
EOF

 

텍스트 암/복호화

 

데모앱이 정상 동작한다면, localhost:30003 으로 접속시 해당 화면이 나오게 됩니다.

 

Insert Data 에서 텍스트를 입력하여 주입하면 Decrypt Data 와 Raw Data 가 나뉘게 되는데
Decrypt Data 는 복호화된 원문을 보여주고, Raw Data 는 DB 에 저장된 값을 보여줍니다.

 

 

 

파일 암/복호화

 

파일 자체도 Vault 를 이용해 암복호화를 진행할 수 있습니다.

 

echo "hello vault transit" > original.txt

 

 

 


 

Contents

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