본문 바로가기
스터디 이야기/25' AWS EKS

HashiCorp Vault를 활용한 CI/CD 환경의 Secret 관리 자동화 (Jenkins, ArgoCD)

by lakescript 2025. 4. 11.
728x90

들어가며

Secret은 API 키, 비밀번호, 데이터베이스 자격 증명, OAuth/JWT 토큰, TLS 인증서처럼 민감한 정보를 의미합니다.

 

이러한 중요한 정보들을 GitHub 같은 공개 저장소에 평문으로 저장하는 것은 매우 위험합니다. 실수로 코드에 포함되거나 외부에 노출될 경우, 서비스 전체가 공격받을 수 있는 심각한 보안 사고로 이어질 수 있기 때문입니다.

 

그렇기 때문에 Secret을 안전하게 관리하기 위한 전용 도구가 필요하며, 그중 대표적인 솔루션이 바로 HashiCorp Vault입니다.

 

Vault를 사용하면 Secret을 중앙에서 안전하게 저장하고, 접근 권한을 세밀하게 제어할 수 있으며, Secret을 자동으로 생성하거나 일정 시간이 지나면 폐기되도록 설정할 수 있어, 수동으로 관리할 때보다 훨씬 안전하고 효율적입니다.

 

또한, CI/CD 파이프라인과도 쉽게 연동할 수 있기 때문에 Jenkins를 통해 CI Pipeline 빌드 중 Vault에 접근해 Secret 값을 받아와 애플리케이션에 주입할 수 있고, ArgoCD Vault Plugin을 사용하여 애플리케이션을 배포할 때 Vault에서 Secret을 읽어와 필요한 Kubernetes 리소스에 자동으로 반영할 수 있습니다.

 

 

Hashicorp Vault

 

Vault란?

Vaults의 사전적 정의

 

먼저 사전에서 vaults의 뜻을 찾아보면 금고라는 뜻인 것을 알 수 있습니다. 이와 같이 금고처럼 특별한 key를 이용하여 금고 안에 있는 물건을 얻을 수 있게 설계된 것이 vault입니다.

 

Hashicorp Vault 정의

 

 

https://developer.hashicorp.com/vault/docs/what-is-vault

 

 

Hashicop Vault는 identity-based의 secret 및 암호화 관리 도구입니다.  특히, 인증(authentication), 인가(authorization)을 통해 secret에 대해 안전하게 관리 및 접근을 통제할 수 있습니다.

 

또한 다양한 secret 데이터들에 대해 통합된 interface를 제공하면서, 엄격한 접근 제어와 상세한 audit log 기능 등을 제공합니다. 

 

외부 서비스용 API Key나 통신을 위한 자격 증명 등은 플랫폼마다 분산되어 있어, 누가 어떤 시크릿에 접근했는지 파악하기 어렵습니다. 이럴 때 HashiCorp Vault를 활용하면 키 회전(Key Rotation), 감사 로그(Audit Logging) 등의 기능을 통해 secret을 중앙에서 안전하게 관리할 수 있습니다.

Vault의 동작 방식

주요 기능

Static Secrets

비밀번호, API 키 등 Key/Value 형식의 secret을 암호화하여 저장하기에 저장소에 직접 접근해도 내용을 볼 수 없습니다.

 

Dynamic Secrets

요청할 때마다 자동으로 자격 증명을 생성합니다. 이때 일저 시간이 지나면 자동으로 폐기됩니다.

 

Data Encryption

Vault가 데이터를 직접 저장하지 않아도, 암호화/복호화를 대신 수행합니다. 이를 통해 개발자는 민감한 데이터를 안전하게 저장할 수 있습니다.

 

Leasing and Renewal

Secret에는 사용기한(lease)가 설정되어 있어 시간이 지나면 자동으로 만료되고 삭제됩니다. (필요시 갱신도 가능합니다.)

 

Revocation

특정 Secret 하나만 폐기하거나 특정 사용자 혹은 특정 유형 전체를 한 번에 삭제할 수 있습니다. 이를 통해 문제가 발생했을 때 빠르게 대응할 수 있습니다.

 

핵심 개념

https://developer.hashicorp.com/vault/docs/what-is-vault#how-does-vault-work

 

vault는 토근(Token)기반으로 동작하며, 이 토큰은 클라이언트의 정책(policy)과 연결되어 있습니다. 그리고 각 정책은 경로(path) 기반으로 설정되며, 정책 규칙을 통해 클라이언트가 해당 경로에서 수행할 수 있는 작업들과 접근하는 시크릿을 제한합니다.

 

인증(Authenticate)

Vault에서 인증은 클라이언트가 Vault에 자신이 누구인지 증명할 수 있는 정보를 제공하는 과정입니다. 클라이언트가 인증 method를 통해 인증이 완료되면, Token이 생성되고 Policy와 연결됩니다.

 

검증(Vaildation)

Vault는 Github, OIDC, LDAP, AppRole등과 같이 신뢰할 수 있는 외부 소스를 통해 클라이언트를 검증합니다.

 

인가(Authorize)

클라이언트가 Vault의 보안정책를 통해 Vault Token을 사용하여 클라이언트가 접근할 수 있는 API 엔드포인트를 할당합니다. 이때 정책은 Vault 내 특정 경로나 작업에 대한 접근을 허용하거나 거부하는 방식으로 제어합니다.

 

접근(Access)

클라이언트와 연결된 정책을 기반으로 Token을 발급하여 secret, key, 암호화 등에 대한 접근을 허용합니다.

 

 Vault Workflow

 

Vault는 다양한 Identity Provider(IdP)와의 통합을 통해 사용자 인증을 수행합니다. 사용자가 인증되면, 미리 정의된 정책(Policy)과 연계된 토큰을 발급하여 자격 증명을 검증하고, 최종적으로 Vault에 접근할 수 있는 Vault Token을 반환합니다. 이 Token은 해당 사용자의 권한 범위를 Vault Policy와 연동하여 엔진(Engine) 및 리소스 접근 제어에 활용됩니다.

Vault의 인증 및 권한 부여
Vault는 사용자 인증이 아닌 권한 부여를 제공합니다. 그렇기 때문에 실제 인증 프로세스는 관리하지 않습니다.

 

Vault Agent

https://docmoa.github.io/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-manually-using-the-sidecar.html

 

Vault Agent는 애플리케이션이 직접 Vault와 통신하지 않아도 대신 Vault에 인증 후 필요한 secret을 가져와 파일로 저장하거나 환경변수로 주입해 주는 중간 역할을 합니다. 이를 통해 애플리케이션 코드를 수정하지 않아도 secret을 안전하게 사용할 수 있고, Token 갱신이나 Secret 갱신 같은 번거로운 작업을 자동으로 처리할 수 있습니다. 

 

https://medium.com/@muppedaanvesh/a-hand-on-guide-to-vault-in-kubernetes-%EF%B8%8F-1daf73f331bd

 

 

특히 Kubernetes 환경에서는 Vault Agent Ingector를 이용하여 Pod에 Vault Agent를 sidecar 형식으로 자동으로 주입할 수 있습니다. 

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

 

vault에서 Kubernetes Auth Method를 활성화하고, 적절한 Policy와 Role이 설정되어있어야 하며 위와 같이 애플리케이션 Pod에 주석을 추가해야 Vault Agent Injector이 Vault Agent를 주입합니다.

 

 

Vault AppRole

https://www.hashicorp.com/ko/blog/how-and-why-to-use-approle-correctly-in-hashicorp-vault

 

Vault AppRole은 사람이 아닌 애플리케이션이나 자동화된 작업이 Vault에 인증할 수 있게 해주는 인증 방식입니다. 

 

일반적으로 공개되어도 무방하고 누구인지 식별하는 값인 RoleID 외부에 노출되면 안되고 인증을 완료하기 위한 일종의 비밀번호인 SecretID로 구성됩니다. 이 둘을 함께 사용하여 Vault에 로그인하면, 해당 Role에 연결된 Policy 권한이 부여된 Token을 얻게 됩니다.

 

AppRole의 인증 흐름 방식을 간단하게 나타내면 아래와 같습니다.

 

1. Admin이 Role을 Vault에 등록

2. Vault가 RoleID를 생성

3. Vault가 SecretID를 발급

4. Client가 RoleID + SecretID로 Vault에 로그인

5. Vault가 인증된 Token을 반환

 

즉, Vault AppRole은 CI/CD 파이프라인, 스크립트, 마이크로서비스 등 사람의 개입 없이 Vault인증이 필요한 경우에 적합한 기능입니다.

 

실습 환경 구성

Jenkins 구축

docker compose yaml 파일 생성

cat <<EOT > docker-compose.yaml
services:

  jenkins:
    container_name: jenkins
    image: jenkins/jenkins
    restart: unless-stopped
    networks:
      - cicd-network
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - jenkins_home:/var/jenkins_home

volumes:
  jenkins_home:

networks:
  cicd-network:
    driver: bridge
EOT

 

위와 같이 Jenkins를 호스트 OS 포트 노출(expose)로 접속하게끔 docker-compose 파일을 생성합니다.

Docker-Compose를 통해 Jenkins 구축

docker compose up -d

 

그 후 docker compose 명령어를 통해 배포합니다.

 

 

Jenkins 비밀번호 획득

docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

 

Jenkins 접속

open "http://127.0.0.1:8080"

 

위의 docker-compose.yaml 파일에서 port를 8080으로 지정했기 때문에 해당 port로 접속해보겠습니다.

 

위와 같이 접속이 되는 것을 확인하실 수 있고, 획득한 초기 비밀번호로 접속합니다.

 

초기 추천하는 Plugin을 설치하면서 Jenkins 구축은 완료됩니다.

 

 

Jenkins 구축 확인

 

Kubernetes Cluster 구축

IP를 환경 변수에 지정

MyIP=122.46.200.191

 

위와 같이 현재 작업 중인 PC의 IP를 환경 변수에 지정합니다.

 

kube config 파일 생성 및 지정

cat > ~/.kube/config-cicd <<EOF
apiVersion: v1
kind: Config
preferences: {}
EOF

 

실제 업무에서 사용하는 cluster와 충돌이 나지 않게 하기 위해 config-cicd라는 kube config 파일을 만들겠습니다.

 

export KUBECONFIG=~/.kube/config-tmp

 

그 후 해당 파일로 kubeconifg를 지정합니다.

 

kind yaml 파일 생성

cat > kind-3node.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: "127.0.0.1" # $MyIP로 설정하셔도 됩니다.
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  - containerPort: 30004
    hostPort: 30004
  - containerPort: 30005
    hostPort: 30005
  - containerPort: 30006
    hostPort: 30006
- role: worker
- role: worker
EOF

 

그 후 url로 접속을 하기 위해 몇 개의 port를 매핑하여 cluster를 생성하는 yaml파일을 작성합니다.

Kind를 통해 local용 cluster 구축

kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.32.2

 

위의 명세파일을 토대로 kind cluster를 배포합니다.

 

Cluster 정보 확인

kubectl cluster-info

 

현재 구축된 cluster 정보를 확인해 보겠습니다.

 

ArgoCD 구축

argocd namespace 생성

kubectl create ns argocd

 

argocd 관련 리소스가 배포될 namespace를 생성합니다.

 

argocd yaml 파일 생성

cat <<EOF > argocd-values.yaml
dex:
  enabled: false

server:
  service:
    type: NodePort
    nodePortHttps: 30002
  extraArgs:
    - --insecure
EOF

 

argocd를 웹에서 접근하기 위해 nodeport type으로 service를 생성하고 해당 port를 3002로 지정합니다. 그 후 insecure 옵션을 통해  HTTPS 대신 HTTP 사용하게끔 설정합니다.

 

Helm을 통한 ArgoCD 구축

helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 7.8.13 -f argocd-values.yaml --namespace argocd

 

 

웹으로 접근

open "http://127.0.0.1:30002"

 

설정 파일에서 명시한 대로 30002 port로 접근해 보겠습니다.

 

 

최초 접속 암호 확인

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

 

위 명령어를 통해 ArgoCD 웹에 접근하기 위해 초기 비밀번호를 획득합니다.

 

ArgoCD 웹 로그인

ID: admin / PW : 초기비밀번호 입력 후 접속합니다.

 

UPDATE PASSWORD 로 admin 계정 암호 변경

이후의 실습을 원활하게 진행하기 위해 User Info > UPDATE PASSWORD를 통해 비밀번호를 qwe12345로 변경합니다.

 

K8s에 Vault 설치

namespace 생성

kubectl create namespace vault

 

helm repo 추가 및 vault repo 확인

helm repo add hashicorp https://helm.releases.hashicorp.com

 

hashicorp의 helm repo를 추가합니다.

 

helm search repo hashicorp/vault

 

vault에 관한 repo만 검색해 보겠습니다.

 

helm yaml 파일 생성

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

 

데모 목적이기 때문에 TLS는 비활성화(운영에서는 절대 비활성화 금지!)하고, injector를 true로 함으로써 추후에 있을  Kubernetes Pod에 자동으로 Vault 인증 및 비밀 주입 가능하는 AppRole 실습을 위해  Vault Agent Injector 활성화합니다.

 

Helm을 통해 Vault 배포

helm upgrade vault hashicorp/vault -n vault -f override-values.yaml --install

 

기본 namespace 변경

kubectl config set-context --current --namespace=vault

 

실습을 원활하게 진행하기 위해 기본 namespace를 vault로 지정합니다.

 

Vault 리소스 검색

 

pod, svc, pvc 등을 조회했을 때 vault-0 pod는 Running 상태이지만 READY가 0/1인 것을 확인하실 수 있습니다.

 

kubectl describe pod vault-0

 

describe 명령어를 통해 vault-0 pod의 상세 내용을 확인해 보겠습니다.

 

 

Initialized가 false로 되어있습니다. 즉, 초기화에 실패했다는 건데요. 

 

Vault는 기본적으로 '금고(Vault)'라는 이름답게 처음 배포하면 잠긴(sealed) 상태로 시작합니다. 이 상태에서는 정상적인 기능을 사용할 수 없으며, 먼저 초기화(init)를 통해 금고를 생성하고, 이어서 잠금 해제(unseal) 과정을 거쳐야만 본격적으로 동작하게 됩니다.

따라서 Initialized: false 상태는 아직 Vault가 초기화되지 않았다는 뜻이며, 이는 초기화 절차를 거치지 않았기 때문에 초기화를 수행한 후, unseal 명령어로 잠금을 해제해야 Vault가 정상적으로 작동합니다.

 

init-unseal.sh을 사용하여 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

 

위와 같은 unseal script를 생성하고 실행합니다.

 

 

vault status 명령을 사용하여 Unseal 되었는지 확인

kubectl exec -ti vault-0 -- vault status

 

vault pod 내부에 접속하여 vault status 명령을 통해 Unseal 되었는지 확인해 보겠습니다.

 

 

Initalzed가 true로, Sealed가 false로 변경된 것을 확인하실 수 있습니다.

 

 

vault-0 pod도 정상적으로 실행 중인 것을 확인하실 수 있습니다.

 

CLI 설정

brew tap hashicorp/tap
brew install hashicorp/tap/vault

 

vault 관련 실습을 진행하는데 cli를 통해 접근하거나 제어를 해야 할 수도 있기 때문에 vault cli를 설치합니다.

 

NodePort로 공개한 30000 Port로 설정

export VAULT_ADDR='http://localhost:30000'

 

vault 상태확인

vault status

 

Root Token으로 로그인

ls -l | grep vault-

 

위에서 실행한 unseal script의 결과로 root token의 정보가 담긴 txt 파일이 생성되었고, 해당 파일에서 root token 값을 획득합니다.

 

 

vault login

 

vault login 명령어를 실행하여 위에서 획득한 root token을 입력하여 로그인을 진행합니다.

 

 

실습 - Vault 기본 실습

KV Secret Engine 활성화 및 샘플 구성

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

 

 

 

Vault UI에서 확인

 

[Secrets Engine] 탭에 접속 후 [sampleapp - config] 접속하면 위와 같이 실제 저장된 Key / Value을 확인하실 수 있습니다.

 

Vault Sidecar 연동(Vault Agent)

Vault AppRole 인증 방식 활성화

vault auth enable approle || echo "AppRole already enabled"

 

정책 생성

vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
  capabilities = ["read"]
}
EOF

 

AppRole Role 생성

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)

 

파일로 저장

mkdir -p approle-creds
echo "$ROLE_ID" > approle-creds/role_id.txt
echo "$SECRET_ID" > approle-creds/secret_id.txt

 

Kubernetes Secret으로 저장

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 -

 

 

Vault Agent Sidecar 연동

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

 

Vault Agent 설정 파일 작성 및 생성 (feat.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

 

 

샘플 애플리케이션 + 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

 

위의 yaml 파일을 배포하여 Nginx + Vault Agent 생성합니다.

 

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
​

 

 생성된 컨테이너 확인

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

 

 

pod를 조회하면 위와 같이 2개의 container가 생성된 것을 확인하실 수 있습니다.

 

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

 

 

해당 pod를 자세히 살펴보면 vault-agent-sidecar container가 생성된 것을 확인하실 수 있습니다.

 

 

secret mount 확인

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

 

해당 pod에 접근하여 vault/approle에 role_id와 secret_id에 대한 파일이 있는지 확인해 보겠습니다.

 

 

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

 

그 후 /etc/secrets 경로에 설정 파일에서 secret 값들을 매핑하여 html 파일을 생성하는데, 해당 파일이 있는지 조회해 보겠습니다.

 

 

index.html 파일이 존재하는 것을 확인하실 수 있습니다.

 

 

그 후 해당 nodeport인 30001로 접근해 보면 secret값이 보이는 것을 확인하실 수 있습니다.

 

이처럼 Vault Agent sidecar를 통해 vault 내에 있는 secret을 pod내에 주입시켜 사용하는 것을 확인하실 수 있습니다.

 

실습 - Jenkins + Vault

Jenkins를 통해 Vault KV Store에 저장한 username, password와 같은 secret을 획득하여 CI 파이프라인에서 static secret을 외부에 저장하고 관리하는 경우를 실습해 보겠습니다.

 

Workflow

 

jenkins와 Vault를 결합하여 CI 파이프라인을 구성하는 흐름을 정리하면 아래와 같습니다.

 

1. Jenkins Worker가 Vault에 인증

2. Vault가 Token 반환

3. Jenkins Worker는 해당 Token을 사용하여, 실행할 Job의 Role에 대한 Wrapping 된 SecretID를 검색

4. Vault가 Wrapping된 SecretID 반환

5. Worker는 Runner 생성 후 SecretID를 Job의 변수로 전달

6. Runner Container가 Vault에게 SecretID의 Wrapping 해제 요청

7. Vault가 실제 SecretID 반환

8. Runner는 RoleID와 SecretID를 사용하여 Vault에 인증

9. Vault는 권한이 부여된 Token 반환

10. Runenr는 해당 Token을 사용하여 Vault에서 필요한 Secret 획득

 

jenkins에서 Vault Plugin 설치

Jenkins UI 접속 -> Manage JenkinsPlugins -> Available -> Jenkins 검색 -> HashiCorp Vault Plugin 설치 후 Jenkins 재시작

 

 

Vault 설정 및 Credential 추가

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)

Vault에서 발급된 ROLE_ID, SECRET_ID는 이전에 생성한 role_id.txt secret_id.txt  값을 참고하여 사용할 수 있습니다. 

 

 

Jenkins에서 Vault 설정 및 Credentials 추가

 

Jenkins UI→ Manage Jenkins → System 경로로 이동합니다. 

 

 

하단의 Vault Plugin Configuration 섹션으로 이동합니다.

 

 

vault의 URL을 입력하신 후 + Add 버튼을 클릭합니다.

 

Vault AppRole Credential을 선택하여 생성해 놓은 Role ID와 Secret ID를 입력합니다.

 

그 후 Vault Credential에 방금 생성한 vault-approle-creds를 선택하고 저장합니다.

 

Jenkins Pipeline Job 생성

Pipeline 생성

 

Jenkins UI → New Item → Pipeline을 생성합니다.

 

Jenkinsfile 작성

pipeline {
  agent any

  environment {
    VAULT_ADDR = ${VAULT IP}
  }

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

 

위와 같이 pipeline의 코드를 입력합니다.

 

그 후 Save 하고 빌드를 확인합니다.

 

빌드 확인

 

위와 같이 secret 값은 보안상 취약하므로 마스킹처리가 되는 것을 확인하실 수 있습니다.

 

실습 - ArgoCD + Vault

ArgoCD Vault Plugin이란?

ArgoCD에는 다양한 secret 관리 도구(Hashicorp Vault, IBM Cloud Secret Manager, AWS Secrets Manager 등) 플러그인을 통해 Kuberentes 리소스에 주입할 수 있도록 설정이 가능합니다. 특히, Secret 뿐만 아니라 Deloyment, ConfigMap 등 다양한 Kuberntes 리소스에도 사용할 수 있습니다.

 

Credential 활성화

Role_ID, Secret_ID을 secret으로 생성

kubectl apply -f - <<EOF
kind: Secret
apiVersion: v1
metadata:
  name: argocd-vault-plugin-credentials
  namespace: argocd
type: Opaque
stringData:
  VAULT_ADDR: "http://vault.vault:8200"
  AVP_TYPE: "vault"
  AVP_AUTH_TYPE: "approle"
  AVP_ROLE_ID: $ROLE_ID
  AVP_SECRET_ID: $SECRET_ID
EOF

 

ArgoCD Vault Plugin 설치

ArgoCD Vault Plugin 설치 방법은 2가지가 있으며 현재는 Installation via a sidecar container  방식을 사용하는 것을 권장합니다.

 

실습을 위한 git repo clone

git clone https://github.com/hyungwook0221/argocd-vault-plugin.git
cd argocd-vault-plugin/manifests/cmp-sidecar

 

namespace 변경

kubectl config set-context --current --namespace=argocd

 

실습을 원활하게 하기 위해 argocd namespace로 변경합니다.

 

생성될 매니페스트 파일에 대한 확인

kubectl kustomize .

 

kusomize 실행

kubectl apply -n argocd -k .

-k 옵션으로 kusomize 실행합니다.

 

pod에 접근하여 vault login

kubectl exec -it -n vault vault-0 -- sh

 

 

 

Sample App 배포

Application yaml 파일 작성

kubectl apply -n argocd -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: argocd
    server: https://kubernetes.default.svc
  project: default
  source:
    path: infra/helm
    repoURL: https://github.com/hyungwook0221/spring-boot-debug-app
    targetRevision: main
    plugin:
      name: argocd-vault-plugin-helm
      env:
        - name: HELM_ARGS
          value: -f new-values.yaml
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF

 

 

Application 배포 시 참조하는 new-values.yaml 확인

serviceAccount:
  create: true

image:
  repository: luafanti/spring-boot-debug-app
  tag: main
  pullPolicy: IfNotPresent

replicaCount: 1

resources:
  memoryRequest: 256Mi
  memoryLimit: 512Mi
  cpuRequest: 500m
  cpuLimit: 1

probes:
  liveness:
    initialDelaySeconds: 15
    path: /actuator/health/liveness
    failureThreshold: 3
    successThreshold: 1
    timeoutSeconds: 3
    periodSeconds: 5
  readiness:
    initialDelaySeconds: 15
    path: /actuator/health/readiness
    failureThreshold: 3
    successThreshold: 1
    timeoutSeconds: 3
    periodSeconds: 5

ports:
  http:
    name: http
    value: 8080
  management:
    name: management
    value: 8081

envs:
  - name: VAULT_SECRET_USER
    value: <path:secret/data/sampleapp/config#username>
  - name: VAULT_SECRET_PASSWORD
    value: <path:secret/data/sampleapp/config#password>

log:
  level:
    spring: "info"
    service: "info"

 

 

실제 배포시 적용된 화면

 

ArgoCD에 접근하면 위와 같이 Demo Application이 생성된 것이 보입니다.

 

 

DETAILS > PARAMETERS 탭에 접근하면 위와 같이 argocd vault plugin helm이 적용되어 있는 것을 확인하실 수 있습니다.

 

 

위와 같이 vault에 저장되어 있던 secret 값이  Deployment의 env으로 설정된 것을 확인할 수 있습니다.

 

 


참고

https://developer.hashicorp.com/vault/docs/what-is-vault

 

Introduction | Vault | HashiCorp Developer

Welcome to the intro guide to Vault! This guide is the best place to start with Vault. We cover what Vault is, what problems it can solve, how it compares to existing software, and contains a quick start for using Vault.

developer.hashicorp.com

https://medium.com/@muppedaanvesh/a-hand-on-guide-to-vault-in-kubernetes-%EF%B8%8F-1daf73f331bd

 

⎈ A Hands-On Guide to Vault in Kubernetes ⚙️

⇢ Manage k8s Secrets Using HashiCorp Vault: With Practical Examples

medium.com

https://developer.hashicorp.com/vault/docs/auth/approle/approle-pattern#jenkins-ci-cd

 

Best practices for AppRole authentication | Vault | HashiCorp Developer

Follow best practices for AppRole authentication to secure access and validate application workload identity.

developer.hashicorp.com

 

https://docmoa.github.io/04-HashiCorp/06-Vault/04-UseCase/argocd-vault-plugin.html#_3-%E1%84%89%E1%85%A2%E1%86%B7%E1%84%91%E1%85%B3%E1%86%AF-%E1%84%8B%E1%85%A2%E1%86%B8-%E1%84%87%E1%85%A2%E1%84%91%E1%85%A9

 

ArgoCD Vault Plugin

ArgoCD Vault Plugin 연동방안

docmoa.github.io

 

728x90