ecsimsw

개발자는 Vault를 알지 못하도록, Vault kubernetes injector 본문

개발자는 Vault를 알지 못하도록, Vault kubernetes injector

JinHwan Kim 2024. 5. 31. 18:43

배경

개발자 친구를 만나고 팀에서 Vault를 사용한다는 이야기를 들으면 항상 하는 질문이, '그래서 Vault 인증은 어떻게 해?' 였다. 그리고 다들 본인의 역할이 아니다보니 명확한 관리 방법이나 안전한 방식의 대답을 듣지 못했다.

 

비밀 키를 관리하는 금고를 여는 비밀 키 관리는 정말 중요해보인다. 아무리 안전한 금고할지라도 열쇠를 그 금고에 보관할 순 없는 노릇이고, 그렇다고 열쇠가 제대로 관리되지 못하면 말짱 도루묵일테니 말이다.

 

내가 그간 경험했던 환경에선 Application에서 Vault 인증을 위한 키는 갖고 있었고, 젠킨스의 경우엔 Git 사용을 위한 Github 토큰을 젠킨스 Secret 에 저장하는데 그 값으로 Vault 를 호출해 CI/CD에 필요한 비밀 값을 조회했다.

 

두 경우 모두 Vault 를 사용하는 개발자가 직접 Vault와 사용할 Secret 정보를 알아야 했고 불편함과 위험을 느끼고 있었다.

개발자들은 Vault 의 변경에 관여되어서도, 값을 가져오는 방법을 알아서도 안된다고 생각했다. 

 

Application 의 Vault 의존

Application 에서 Vault 를 직접 호출하게 되면 Vault 정보와 참조할 Secret 정보를 알아야 한다. Vault 와 Secret 정보가 포함된 코드가 작성되고, 개발자는 Vault 정보나 사용 방법을 알아야 한다.

 

예를 들어 App 에서 사용할 Vault secret의 path가 바뀌면, 해당 secret 을 사용하고 있는 모든 Application 을 추적하고 선언하고 있는 코드를 찾아 코드를 수정해야한다. Vault 설정이 바뀌어도 마찬가지다.

 

Vault에 의존적인 Application code 예시
Sample code from, https://spring.io/guides/gs/vault-config

# pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>

# application.properties
spring.application.name=gs-vault-config
spring.cloud.vault.token=00000000-0000-0000-0000-000000000000
spring.cloud.vault.scheme=http
spring.cloud.vault.kv.enabled=true
spring.config.import:  vault://

# code where to use secret
@ConfigurationProperties("example")
public class MyConfiguration {}

 

Vault 의존 제거, 배포 시점에 Secret 값 주입하기

Vault kubernetes Injector 를 사용하여 쉽게 Vault secret 을 조회하고, 조회한 값은 Container 실행 시에 환경 변수로 등록하여 App에서 편하게 사용할 수 있었다. 

 

Vault의 설정과 Secret 정보는 오직 배포 측에서만 안다. Vault 인증에 Service account 를 사용하여 Vault 인증이나 설정을 위한 추가적인 설정 파일을 피했다.

 

앞선 예시에서 Vault secret path가 바뀐다면, Deployment 에서 Vault injector 가 바라보는 Secret 값의 path 만 바꿔주면 그만이다. 개발자는 그저 배포 측과 사전에 정의된 환경 변수를 사용하면 된다.

 

# s3
object.storage.credential.accessKey=${PICUP_AWS_ACCESS_KEY}
object.storage.credential.secretKey=${PICUP_AWS_SECRET_KEY}

# cloudfront
aws.cloudfront.domain=${PICUP_CLOUDFRONT_DOMAIN}
aws.cloudfront.publicKeyId=${PICUP_CLOUDFRONT_PUBLIC_KEY_ID}
aws.cloudfront.privateKeyPath=${PICUP_CLOUDFRONT_PRIVATE_PEM_KEY_PATH}

 

Vault kubernetes injector 동작 원리

1. Pod 가 생성될 때 Init container에서 Service account(token)로 Vault k8s auth 에 로그인을 요청한다.

2. Vault agent container는 발급 받은 로그인 토큰과 함께 필요한 Secret 값을 요청한다.

3. Vault에선 로그인 토큰의 유효함, 사용자의 권한을 확인한다.

4. 요청한 Secret 값을 전달하고, Vault agent 는 이를 임시 저장 공간 (Container의 메모리 볼륨)에 저장한다.

5. Application container에서 저장된 Secret 값을 참조하여 환경 변수를 구성한다.

 

설정 방법

1. Helm values.yaml 준비

 

- KUBERNETES_IP : 쿠버네티스 host ip

- PORT : 쿠버네티스 port

 

global:
  externalVaultAddr: "${KUBERNETES_IP:PORT}
injector:
  enabled: "true"
  port: ${PORT}

 

2. Helm Vault-agent-injector 설치

 

helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault -f values.yaml hashicorp/vault

 

3. Vault kubernetes 인증에 사용할 Secret 생성

 

- SECRET_NAME : 생성할 secret 이름

 

apiVersion: v1
kind: Secret
metadata:
  name: $SECRET_NAME
  annotations:
    kubernetes.io/service-account.name: vault
type: kubernetes.io/service-account-token

 

4. Vault kubernetes 인증 생성

 

Vault auth 로 등록할 Kubernetes 설정을 생성한다. Vault는 Kubernetes에서 접속이 가능해야 한다.

 

# kubectl - kubernetes 설정 확인, 인증에 사용할 secret 의 jwt 키 확인

TOKEN_REVIEWER_JWT=$(kubectl get secret $SECRET_NAME --output='go-template={{ .data.token }}' | base64 --decode)
KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten --output 'jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)
KUBE_HOST=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')

 

# vault - 설정 등록

vault auth enable kubernetes

vault write auth/kubernetes/config  \
   token_reviewer_jwt="$TOKEN_REVIEW_JWT} \
   kubernetes_ca_cert="$KUBE_CA_CERT" \
   kubernetes_host="$KUBE_HOST"

 

5. Vault auth role 정의

 

- ROLE_NAME : 생성할 Role 이름

- SERVICE_ACCOUNT_NAME_OF_POD : Vault agent 가 생성될 Pod 의 Service account 이름

- POD_NAME_SPACE : Vault agent 가 생성될 Pod 의 namespace

- POLICY_NAME : 미리 정의한 Vault policy 이름, 사용할 Secret 의 Read 권한이 포함된 Policy 이어야 한다.

- TTL : 로그인 유효 기간 (ex, 24h)

 

vault write auth/kubernetes/role/$ROLE_NAME \
    bound_service_account_names=$SERVICE_ACCOUNT_NAME_OF_POD \
    bound_service_account_namespaces=$POD_NAME_SPACE \
    policies=$POLICY_NAME \
    ttl=$TTL

 

6. Pod에서 사용할 Vault의 Secret 정보 기입

 

어노테이션을 추가하여 Init container에서 읽을 Vault의 Secret 정보 기입한다. 읽은 secret 값은 파일로 저장되고 컨테이너 실행 시에 파일의 값을 환경 변수로 등록한다.

 

- vault.hashicorp.com/role
: 사용할 Vault auth role 이름을 정의한다.

- vault.hashicorp.com/agent-inject-secret-$PREFIX 
: 키는 저장될 파일 경로 prefix를 표시한다. 
: 예를 들어 'agent-inject-secret-picup' 을 key로 하면 읽어온 데이터는 '/vault/secret/picup'에 저장된다.
: 값은 읽을 vault secret path를 의미한다.

- vault.hashicorp.com/agent-inject-template-$PREFIX 
: 파일에 저장되는 형태를 결정한다. 
: Data 의 형태를 확인하고 저장할 포맷을 정의할 수 있다. 
: 예시의 .Data는 map[created:20240516 version:1] 꼴이었고, k=v 로 저장하는 Template 코드를 정의했다.

- vault.hashicorp.com/agent-pre-populate-only
: Vault injector 의 Side car 등록 여부를 결정한다. 
: True로 설정하면 init container에서 최초 한 회 Secret 를 읽고 Side car 를 생성하지 않는다.

 

7. Deployment 예시

 

아래는 테스트에 사용한 Deployment 예시이다.

vault 에 /picup/common 을 경로로 kv 타입의 secret 을 생성해두었다. 

읽은 secret 값은 {Key=Value} 형식으로 컨테이너 메모리 볼륨에 "/vault/secrets/picup" 경로에 파일로 저장된다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: vault-injector-test
  name: vault-injector-test
  namespace: picup
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vault-injector-test
  template:
    metadata:
      annotations:
        vault.hashicorp.com/role: internal-app
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-inject-secret-picup: picup/common
        vault.hashicorp.com/agent-inject-template-picup: |
            {{- with secret "picup/common" -}}
            {{- range $k, $v := .Data }}
            {{- $k }}={{ $v }}
            {{end}}{{end}}
      labels:
        app: vault-injector-test
    spec:
      containers:
        - image: alpine
          args:
            - "sh"
            - "-c"
            - "source /vault/secrets/picup && sleep 10000"
          imagePullPolicy: IfNotPresent
          name: vault-injector-test
          resources: {}
      terminationGracePeriodSeconds: 30

 

정의한 Deployment 로 Pod가 실행될 때 Init container 에서 vault-agent-init 수행, Side car로 vault-agent 가 실행되는 것을 확인할 수 있다.

 

 

8. REF

 

Github-vault-helm

Hashicorp-k8s-injector

Comments