개요
Vault 서버는 보안 강화를 위해 설치 후 초기 시작 시 봉인된(Sealed) 상태로 시작된다. 이 상태에서 Vault를 사용하기 위해서는 마스터 키를 이용한 봉인 해제, 즉 "Unseal" 과정이 필수적이다. 일반적으로, 이 과정은 보안을 위해 수동으로 진행되며, 이는 특히 Vault가 재시작될 때마다 반복되어야 하므로, 특히 대규모 클러스터 환경에서는 운영 부담을 크게 증가시킨다. 이러한 문제를 해결하기 위해 Vault는 Auto Unseal 기능을 제공한다.
Auto Unseal은 AWS의 Key Management Service(KMS), Azure의 Key Vault, Google Cloud의 Cloud KMS와 같은 다양한 클라우드 플랫폼에서 지원된다. 이러한 클라우드 기반 키 관리 서비스를 활용함으로써, 사용자는 Unseal 프로세스를 해당 플랫폼에 위임하여, Vault의 운영 부담을 크게 줄일 수 있다.
본 글에서는 AWS KMS를 활용하여 Vault의 Auto Unseal 기능을 설정하고, 실제로 이 기능을 통해 Vault 서버를 어떻게 자동으로 Unseal 할 수 있는지에 대한 실습을 다룬다.
실습 환경
- Amazon EKS 1.27.7
- Helm Cli v3.8.2
- Vault 1.15.2 (Vault 설치 참고: [Vault 설치])
전제 조건
- AWS KMS (고객 관리형)
- AWS IAM ROLE
1. KMS 권한을 위한 AWS IRSA 설정
먼저, Vault Pod에서 Auto unseal을 사용할 수 있게 필요한 권한을 부여하는 IRSA(IAM Roles for Service Accounts) 설정을 해야 한다.
참고
해당 글에서는 IAM ROLE이 생성되어 있다는 가정하에 진행한다.
만들어진 IAM ROLE에 다음 정책을 추가한다.
{
"Statement": [
{
"Action": [
"ec2:DescribeInstances",
"iam:*",
"sts:*",
"kms:Encrypt",
"kms:Decrypt",
"kms:DescribeKey"
],
"Effect": "Allow",
"Resource": [
"*"
]
}
],
"Version": "2012-10-17"
}
만들어진 IAM ROLE에 다음 신뢰관계를 추가한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{Account_ID}:oidc-provider/oidc.eks.{REGION}.amazonaws.com/id/{공급자_ID}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.{REGION}.amazonaws.com/id/{공급자_ID}:aud": "sts.amazonaws.com",
"oidc.eks.{REGION}.amazonaws.com/id/{공급자_ID}:sub": "system:serviceaccount:{vault_namespace}:{vault_serviceaccount_name}"
}
}
}
]
}
2. Vault 설치 (HA Cluster)
Vault helm repo가 등록되어 있지 않으면 다음 명령을 통해 등록한다.
$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm repo update
설치에 필요한 values 파일을 다음과 같이 작성한다.
$ cat <<EOF > vault-values.yaml
server:
enabled: "true"
service:
type: NodePort
dataStorage:
storageClass: "{Storage Class Name}"
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::{AWS ACCOUNT}:role/{AWS IAM ROLE ARN}
ha:
enabled: true
replicas: 3
raft:
enabled: true
config: |
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "raft" {
path = "/vault/data"
}
seal "awskms" {
region = "{AWS REGION}"
kms_key_id = "{KMS KEY ID}"
}
EOF
생성한 values 파일로 설치한다.
$ helm install vault hashicorp/vault -n vault -f vault-values.yaml
정상으로 설치가 되었는지 확인한다.
$ kubectl get po -n vault -w
NAME READY STATUS RESTARTS AGE
vault-0 0/1 Running 0 2m18s
vault-1 0/1 Running 0 2m18s
vault-2 0/1 Running 0 2m18s
vault-agent-injector-7f7f68d457-n8kfv 1/1 Running 0 2m18s
아직 init을 진행하지 않았기 때문에 Readiness probe가 통과되지 않아 READY에서 "0/1"로 보이는 걸 확인할 수 있다.
3. Vault Auto unseal 적용 & 확인
설정된 AWS KMS로 Auto unseal을 진행하기 위해서는 먼저 init을 해야 한다.
$ kubectl exec -n vault vault-0 -- vault operator init -format=json > cluster-keys.json
참고
AWS KMS를 사용하여 Auto unseal을 구성하면 매개변수 "key-shares=1, key-threshold=1" 값이 적용되지 않는다
init 이후 vault-0 파드의 상태를 확인해 보면 다음과 같이 Readiness probe가 통과된 걸 확인할 수 있다.
$ kubectl get po -n vault -w
NAME READY STATUS RESTARTS AGE
vault-0 1/1 Running 0 2m18s
vault-1 0/1 Running 0 2m18s
vault-2 0/1 Running 0 2m18s
vault-agent-injector-7f7f68d457-n8kfv 1/1 Running 0 2m18s
vault status를 확인해 보면 정상적으로 Sealed 값이 false로 변경된 걸 확인할 수 있다.
$ kubectl exec vault-0 -n vault -- vault status
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed false # true -> false로 변경 됨
Total Recovery Shares 5
Threshold 3
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type raft
Cluster Name vault-cluster-62c0c55f
Cluster ID e042bf9f-4579-a303-604d-bf920df5158d
HA Enabled true
HA Cluster https://vault-0.vault-internal:8201
HA Mode active
Active Since 2024-04-03T05:33:56.565086545Z
Raft Committed Index 57
Raft Applied Index 57
그런데 아직 vault-1, vault-2 파드는 Readiness probe를 통과하지 못한 상태이다.
로그를 확인해 보면 아래와 같이 Unseal이 되지 않아 통과하지 못한다는 것을 확인할 수 있다.
$ kubectl logs -n vault vault-1
#... 생략
2024-04-03T05:33:27.760Z [INFO] core: stored unseal keys supported, attempting fetch
2024-04-03T05:33:27.760Z [WARN] failed to unseal core: error="stored unseal keys are supported, but none were found"
2024-04-03T05:33:32.298Z [INFO] core: security barrier not initialized
2024-04-03T05:33:32.299Z [INFO] core.autoseal: recovery seal configuration missing, but cannot check old path as core is sealed
#... 생략
vault status로 확인해 보면 Sealed 값은 true로 Unseal이 되지 않은 상태이고, HA 클러스터 정보가 보이지 않는 것을 확인할 수 있다.
$ kubectl exec vault-1 -n vault -- vault status
Key Value
--- -----
Recovery Seal Type awskms
Initialized false
Sealed true
Total Recovery Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type raft
HA Enabled true
command terminated with exit code 2
vault-1, vault-2 파드를 vault-0 파드에 join 시킨다.
$ kubectl exec -n vault vault-1 -- vault operator raft join http://vault-0.vault-internal:8200
Key Value
--- -----
Joined true
$ kubectl exec -n vault vault-2 -- vault operator raft join http://vault-0.vault-internal:8200
Key Value
--- -----
Joined true
이제 vault 파드 상태를 확인해 보면 다음과 같이 Readiness probe가 통과되어 READY가 "1/1"로 변경된 걸 확인할 수 있다.
$ kubectl get po -n vault -w
NAME READY STATUS RESTARTS AGE
vault-0 1/1 Running 0 6m14s
vault-1 1/1 Running 0 2m46s
vault-2 1/1 Running 0 6m14s
vault-agent-injector-7f7f68d457-5wdlp 1/1 Running 0 6m14s
vault status를 확인해 보면 다음과 같이 Sealed 상태가 false로 변경되고, HA Cluster 정보가 나오는 걸 확인할 수 있다.
$ kubectl exec vault-1 -n vault -- vault status
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed false
Total Recovery Shares 5
Threshold 3
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type raft
Cluster Name vault-cluster-62c0c55f
Cluster ID e042bf9f-4579-a303-604d-bf920df5158d
HA Enabled true
HA Cluster https://vault-0.vault-internal:8201
HA Mode standby
Active Node Address http://10.10.3.114:8200
Raft Committed Index 71
Raft Applied Index 71
vault-1 파드의 로그를 확인해 보자
$
# 에러 로그
2024-04-03T05:38:36.729Z [INFO] core: security barrier not initialized
2024-04-03T05:38:36.729Z [INFO] core.autoseal: recovery seal configuration missing, but cannot check old path as core is sealed
2024-04-03T05:38:37.229Z [INFO] core: stored unseal keys supported, attempting fetch
2024-04-03T05:38:37.229Z [WARN] failed to unseal core: error="stored unseal keys are supported, but none were found"
#... 생략
# Cluster join 로그
2024-04-03T05:38:38.021Z [INFO] core: attempting to join possible raft leader node: leader_addr=http://vault-0.vault-internal:8200
2024-04-03T05:38:38.109Z [INFO] core.cluster-listener.tcp: starting listener: listener_address=[::]:8201
2024-04-03T05:38:38.109Z [INFO] core.cluster-listener: serving cluster requests: cluster_listen_address=[::]:8201
2024-04-03T05:38:38.111Z [WARN] core.cluster-listener: no TLS config found for ALPN: ALPN=["raft_storage_v1"]
2024-04-03T05:38:38.116Z [INFO] storage.raft: creating Raft: config="&raft.Config{ProtocolVersion:3, HeartbeatTimeout:15000000000, ElectionTimeout:15000000000, CommitTimeout:50000000, MaxAppendEntries:64, BatchApplyCh:true, ShutdownOnRemove:true, TrailingLogs:0x2800, SnapshotInterval:120000000000, SnapshotThreshold:0x2000, LeaderLeaseTimeout:2500000000, LocalID:\"39726938-6cd8-590c-b554-376cd9f2dd9c\", NotifyCh:(chan<- bool)(0xc0034867e0), LogOutput:io.Writer(nil), LogLevel:\"DEBUG\", Logger:(*hclog.interceptLogger)(0xc002f46630), NoSnapshotRestoreOnStart:true, skipStartup:false}"
2024-04-03T05:38:38.120Z [INFO] storage.raft: initial configuration: index=1 servers="[{Suffrage:Voter ID:77d7f7bc-83e5-b20e-b49a-5b88de7147ca Address:vault-0.vault-internal:8201} {Suffrage:Nonvoter ID:39726938-6cd8-590c-b554-376cd9f2dd9c Address:vault-1.vault-internal:8201}]"
2024-04-03T05:38:38.120Z [INFO] storage.raft: entering follower state: follower="Node at vault-1.vault-internal:8201 [Follower]" leader-address= leader-id=
2024-04-03T05:38:38.120Z [INFO] core: successfully joined the raft cluster: leader_addr=http://vault-0.vault-internal:8200
2024-04-03T05:38:38.220Z [WARN] storage.raft: failed to get previous log: previous-index=63 last-index=1 error="log not found"
2024-04-03T05:38:42.230Z [INFO] core: stored unseal keys supported, attempting fetch
2024-04-03T05:38:42.256Z [WARN] core: cluster listener is already started
# Auto Unseal 로그
2024-04-03T05:38:42.256Z [INFO] core: vault is unsealed
2024-04-03T05:38:42.256Z [INFO] core: unsealed with stored key
2024-04-03T05:38:42.256Z [INFO] core: entering standby mode
#... 생략
unseal 에러 이후 leader에 join 된 후 unseal이 진행되어 standby mode로 변경된 로그를 확인할 수 있다.
AWS CloudTrail에서 해당 시간에 진행된 kms를 통해 진행된 Decrypt 이벤트 내용도 확인할 수 있다.
Vault Ui 대시보드에 접근하여 root token으로 정상 로그인이 되는지 확인한다.
- root token은 Vault 서버 init을 진행할 때 백업했던 파일에서 확인할 수 있다.
$ cat cluster-keys.json
{
"unseal_keys_b64": [],
"unseal_keys_hex": [],
"unseal_shares": 1,
"unseal_threshold": 1,
"recovery_keys_b64": [
"sZjdeObftJXf8XXXXXXXXXXXxNrqFyUqoKsxgxbE5gws",
"LEY5XXXXXXXXXXXXXXXxNrqFyUqoKPaguau3z/QQPBvu",
"ghTIHh1N+EUdBamIoXEP1o7zaMPvdXXXXXrErMhootM3",
"8FklKAmmafXXXXXXXXXXXZEtxiam0r6cTCvgODLuLPQh",
"r+tNIdneVXXXXXXXXKWiUkYBKzEsgAYMkLQJMAZFut7"
],
"recovery_keys_hex": [
"b198dd78e6dxxxxxxxxxxxxxxxxxxxx58db04ec6f4f17d6d39a6238316c4e60c2c",
"2c4639ad2axxxxxxxxxxxxx58db04ec6f4f1794aa828f6a0b9abb7cff4103c1bee",
"8214c81e1d4df8451d05a988a171xxxxxxxxxxxxx58db04ec6f4f17cc868a2d337",
"f059252809a6xxxxxxxxxxxxx58db04ec6f4f17d2591xe9c4c2be03832ee2cf421",
"afeb4d21d9de5xxxxxxxxxxxxx58db04ec6f4f17xxxxxxxxxxxxx58db04ec16eb7b"
],
"recovery_keys_shares": 5,
"recovery_keys_threshold": 3,
"root_token": "hvs.xxxxxxxxxxxxxxxxx"
}
대시보드 로그인 화면 Token 부분에 root_token 값을 입력하여 로그인한다.
정상 로그인 된 걸 확인할 수 있다.
'Security > Vault' 카테고리의 다른 글
Vault Transit Secret Engine 사용하기 (0) | 2024.04.01 |
---|---|
Vault Secrets Operator(VSO)를 활용한 Kubernetes secrets 관리 (PKI 동적 시크릿) (0) | 2024.02.28 |
Vault Secrets Operator(VSO)를 활용한 Kubernetes secrets 관리 (정적 시크릿) (0) | 2024.02.26 |
HashiCorp Vault 란? 개념부터 설치까지 (0) | 2023.04.08 |
댓글