Kubernetes

kubernetes 스터디 5주차

들어가며

5주차에 배운 내용은 보안입니다. 현업에서 쿠버네티스를 사용해 서비스를 운영하는 회사가 많아지며 쿠버네티스 보안에 대한 관심도 높아지고 있습니다. 쿠버네티스의 기본 인증&인가 시스템을 제외하고 다양한 툴을 통해 manifest, 배포된 리소스에 대한 보안 상태를 체크할수 있습니다. 쿠버네티스를 운영하다보면 암호화된 데이터를 저장할수 있는 저장소가 필요한데 이때 운영자가 선택할수 있는 솔루션으로는 csp에서 제공하는 매니지드 키 관리 시스템 혹은 vault 라는 오픈소스 저장소를 사용할수 있습니다. 오늘은 그중에서도 vault 라는 툴이 무엇이고 사용법에 대해서 이야기해보겠습니다. 

Vault 란 ? 

Vault는 HashiCorp에서 개발한 오픈소스 키-값 저장소로, 비밀 정보(암호, API 키, 인증서 등)를 안전하게 저장하고 관리할 수 있는 시스템입니다. Vault는 중앙 집중식 비밀 관리를 제공하며, 암호화, 액세스 제어, 감사 로깅 기능을 포함합니다.

Vault 특징

보안 : 모든 데이터는 저장 전에 Vault에서 암호화되어 저장되고, 가져올 때 복호화됩니다.

인증 메커니즘 다양성 : LDAP, GitHub, kubernetes 등 다양한 인증 방식을 제공합니다.

정책 기반 액세스 제어 : 사용자 및 그룹에 대한 액세스 제어를 설정하여 권한 관리가 용이합니다.

감사 로깅 : Vault에서 수행되는 모든 작업은 로그에 기록되어 검토 및 분석이 가능합니다.

비밀 정보 자동 갱신 : 일정 시간이 지나면 자동으로 비밀 정보를 갱신하여 보안을 강화합니다.

 

이처럼 Vault 는 중앙 집중식 비밀 관리로 인한 일관성 및 보안 향상 다양한 인증 및 액세스 제어 메커니즘 지원 강력한 암호화 및 감사 로깅 기능을 지원하며 다양한 csp와 연동하여 사용할 수 있습니다. 하지만 vault 자체의 보안 및 운영에 대한 추가적인 관리가 필요, 러닝커브가 어느정도 있다는 단점도 있습니다. 

Vault 작동흐름

1. 관리자는 vault 에서 제공하는 다양한 시크릿엔진 중 하나를 선택해 활성화 합니다

2. 활성화된 시크릿 엔진에 암호화 데이터를 저장합니다.

3. 시크릿엔진 접근에 필요한 role 을 만들고, 암호데이터에 접근이 필요한 app 에 권한을 부여합니다.

4. app이 vault 에게 암호데이터를 요청합니다.

5. 인증에 통과하면 vault는 암호데이터를 app에 전달합니다. (이때 TTL 과 같은 설정을 추가할수 있습니다)

6. 가져온 암호데이터를 사용해 DB등의 시스템에 연결합니다.

Vault 설치

미리 생성해둔 eks에 vault를 설치해 보겠습니다. 

 

vault는 stand alone 모드와 HA모드로 설치할수 있다. 여기서는 stand alone 모드로 설치해보겠습니다. 

vault repo를 추가하고 설치합니다. 

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

# search version
helm search repo hashicorp/vault --versions

# standalone 설치
helm upgrade --install vault hashicorp/vault -n vault --create-namespace

아래처럼 vault 가 설치되었지만 0/1로 아직 ready 상태가 아니다.

이유는 vault를 최초 설치하면 initialize 및 unseal 작업이 필요하기 때문입니다. 

# initialize
kubectl exec -n vault vault-0 -- vault operator init \
    -key-shares=1 \
    -key-threshold=1 \
    -format=json > cluster-keys.json

# unseal
#  키값(unseal_keys_hex)을 입력하면 vault status 에서 sealed 가 false 로 변한다 
kubectl exec -n vault vault-0 -- vault operator unseal

# admin 비밀번호는 init 작업할 때 출력된 root 비밀번호를 사용한다.

vault operator init 을 입력하면 아래처럼 unseal key 와 root key를 얻을수 있습니다.

 

unseal 작업을 위해 vault operator unseal 명령어를 반복하며 출력된 unseal key를 3번 입력합니다.

3번 입력하면 아래처럼 sealed 가 false 로 바뀌게 됩니다. 

 

더불어 ready 상태가 아니였던 pod가 1/1 running 상태로 변경된 것을 확인할 수 있습니다.

 

vault service 오브젝트의 type 을 LoadBalancer로 변경합니다. 

external ip 주소로 접속합니다.

아래처럼 vault 대시보드를 확인할수 있고, 위에서 vault init 할 때 얻은 root token 을 입력하여 로그인합니다. 

 

Vault 로 암호데이터 제어하기

vault 대시보드를 로그인하면 아래와 같은 화면을 볼 수 있습니다

각각의 페이지는 아래와 같은 작업을 제공합니다. 

- secrets : secret engine 을 만들고 암호데이터를 저장

- access : 인증(authentication)을 설정

- policies : 인가(authorization)를 설정

본격적으로 secret 에 kv(key-vault) engine을 만들고 저장한 암호값을 kubernetes에서 가져오는 방법에 대해서 알아보겠습니다. 

 

먼저 vault pod 의 터미널에 접속 후 root token 을 환경변수로 export 합니다.

export VAULT_TOKEN="token value"

 

쿠버네티스 클러스터에서 vault에 인증하기 위해 kubernetes auth 를 활성화 합니다. 

vault auth enable kubernetes

 

쿠버네티스 클러스터 정보를 vault 에 등록합니다. 

vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
issuer="https://kubernetes.default.svc.cluster.local"

 

특정 네임스페이스와 서비스어카운트에 적용할 role과 policy를 생성 합니다.

( example-app 네임스페이스의 basic-secret 서비스 어카운트에 role을 부여 )

vault write auth/kubernetes/role/basic-secret-role \
   bound_service_account_names=basic-secret \
   bound_service_account_namespaces=example-app \
   policies=basic-secret-policy \
   ttl=1h
   
cat <<EOF > /home/vault/app-policy.hcl
path "secret/basic-secret/*" {
  capabilities = ["read"]
}
EOF
vault policy write basic-secret-policy /home/vault/app-policy.hcl

 

kv secret engine을 활성화하고 암호데이터를 저장합니다.

vault secrets enable -path=secret/ kv
vault kv put secret/basic-secret/helloworld username=dbuser password=sUp3rS3cUr3P@ssw0rd

 

example-app 네임스페이스를 생성하고, vault 로부터 암호데이터를 가져오는 deplyment 를 생성합니다. 

(아래는 annotation을 통해 vault로부터 시크릿 값을 가져오는 예시입니다)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: basic-secret
  labels:
    app: basic-secret
spec:
  selector:
    matchLabels:
      app: basic-secret
  replicas: 1
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/tls-skip-verify: "true"
        vault.hashicorp.com/agent-inject-secret-helloworld: "secret/basic-secret/helloworld"
        vault.hashicorp.com/agent-inject-template-helloworld: |
          {{- with secret "secret/basic-secret/helloworld" -}}
          {
            "username" : "{{ .Data.username }}",
            "password" : "{{ .Data.password }}"
          }
          {{- end }}
        vault.hashicorp.com/role: "basic-secret-role"
      labels:
        app: basic-secret
    spec:
      serviceAccountName: basic-secret
      containers:
      - name: app
        image: jweissig/app:0.0.1
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: basic-secret
  labels:
    app: basic-secret

 

위 매니페스트를 배포합니다.

kubectl -n example-app apply -f deploy.yaml

 

아래처럼 vault-agent-init 과 vault-agent 가 sidecar로 같이 생성되며, secret 값을 지정한 경로에 마운트 해줍니다. 

배포한 app 파드에 접속하여 확인하면 아래처럼 생성한 secret 값을 잘 출력하는 것을 확인할 수 있습니다. 

 

 

마치며

이상으로 vault 가 무엇인지, 왜 필요한지, 간단하게 사용하는 방법에 대해서 알아 보았습니다. 

실제 운영환경에서 사용할 경우 HA mode로 구성하면 가용성 확보도 가능하기 때문에 충분히 좋은 솔루션이라고 생각합니다.