Vault(2) - 동적 시크릿과 시크릿 엔진
볼트에는 정적 시크릿(Static Secret)과 동적 시크릿(Dynamic Secret)의 개념이 존재한다. 정적 시크릿은 고정된 패스워드나 API 키, 인증값 등 사용자가 직접 설정하는 모든 종류의 시크릿을 의미하며, 앞에서 우리가 실습한 값들 역시 여기에 해당한다. 개발자나 운영자는 필요한 만큼 시크릿 패스를 정의하여 값을 저장할 수 있으며, 하나의 패스 아래에 여러 개의 값을 동시에 지정할 수도 있다. 이런 값은 개발자나 시스템 운영자가 직접 생성하고 관리하는 것이 일반적이다.
반면 동적 시크릿은 볼트 서버가 특정 시스템과 연동하여 자동으로 발급받아 관리하는 시크릿 데이터를 의미한다. AWS 시스템에 연결하여 엑세스 토큰을 받아오거나 OIDC에 로그인하는 것, MySQL 접속용 패스워드를 자동으로 생성하는 것 등 볼트가 다루는 많은 시크릿 관련 기능들이 동적 시크릿에 해당한다.
동적 시크릿과 정적 시크릿은 시크릿 데이터를 다루기 위한 방식이 상이하며, 동적 시크릿도 어떤 시스템과 연동하는가에 따라 내부 동작 방식이 크게 달라진다. MySQL에 로그인하는 것과 OIDC에 로그인하는 로직이 같을 수는 없기 때문이다.
그래서 볼트는 이를 지원하기 위해 다양한 시크릿 엔진(Secret Engine)을 제공한다. 각각의 시크릿 엔진은 저마다 특성이 있는데, 예를 들어 KV 엔진은 정적인 시크릿 데이터를 키-값 형태로 저장하고 관리하는 데 사용된다(이전 포스팅에서 vault kv put
형식의 커맨드를 계속 사용했는데, 이는 KV 엔진을 사용하여 시크릿 데이터를 처리하라는 의미이다). KV는 볼트의 기본 시크릿 엔진이기도 하다.
반면, AWS 엔진은 아마존의 키-권한 관리 시스템인 IAM과 연동하여 동적으로 키를 발급받고 관리하는 데 사용된다. 또, Database 엔진은 MySQL과 같은 DBMS에 접속하여 동적으로 사용자 아이디와 비밀번호를 발급받는 역할을 한다. 시크릿 엔진이란 한마디로, 시크릿 데이터를 용도별로 관리하기 위한 일종의 플러그인인 셈이다.
시크릿 엔진과 시크릿 패스의 관계
시크릿 엔진은 시크릿 패스와 맞물려 동작한다. 시크릿 성격별로 패스가 나누어 지고, 그에 따라 적합한 시크릿 엔진이 연결되는 것이다. 예를 들어 이전 실습에서 루트 패스로 사용한 secret/ 은 KV 엔진과 연결된다. secret/ 아래의 모든 경로에 저장되는 시크릿 데이터는 KV 엔진을 통해 관리되는 것이다.
확인을 위해, 볼트 서버에 로그인한 후 아래와 같이 실행하자. 시크릿 패스와 그에 연결된 시크릿 엔진 타입을 보여주는 명령이다.
$ vault secrets list ⏎
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_24eeb02e per-token private secret storage
identity/ identity identity_fef6cdd5 identity store
secret/ kv kv_fa34ed02 n/a
sys/ system system_2710aec4 system endpoints used for control, policy
이전 실습에서 secret/ 경로에 정적 시크릿을 저장할 때 vault kv XXX
처럼 매번 kv 명령을 포함했는데, 이는 secret/ 경로에 KV 엔진이 연결되어 있었기 때문이다. 만약 다른 시크릿 엔진이 패스에 연결된다면, 시크릿 관련 명령 실행 시에도 kv 대신 그에 맞는 엔진을 지정해 주어야 한다.
하지만 시크릿 엔진과 시크릿 패스의 관계는 영구적인 것이 아니며, 필요에 따라 얼마든지 연결하고 끊을 수 있다. 다음은 secret/ 패스에 연결된 시크릿 엔진을 끊는 과정을 보여준다.
$ vault secrets disable secret/ ⏎
Success! Disabled the secrets engine (if it existed) at: secret/
vault secrets disable <시크릿 패스>
를 실행하면 해당 경로에서 시크릿 엔진이 비활성화된다. 다시 말해, 연결이 끊어지는 것이다. 동시에, 해당 경로 하위에 저장된 모든 시크릿도 함께 삭제된다. vault secrets list
를 다시 실행해 보자.
$ vault secrets list ⏎
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_24eeb02e per-token private secret storage
identity/ identity identity_fef6cdd5 identity store
sys/ system system_2710aec4 system endpoints used for control, policy
출력 결과에서 secret/ 경로가 사라진 것을 확인할 수 있다. 이는 시크릿 엔진이 해당 경로에서 비활성화되면서 저장된 시크릿도 함께 삭제되었기 때문이다. 이제 시크릿 엔진을 해당 패스에 다시 연결해보자.
$ vault secrets -version=1 -path=secret/ kv ⏎
Success! Enabled the kv secrets engine at: secret/
kv 엔진을 secret/ 패스에서 다시 활성화시켰다. 이후부터 secret/ 하위에 작성된 모든 시크릿은 kv 엔진을 통해 저장/관리된다. 다만, 기존과 달리 -version=1
옵션을 주었는데, 이는 커맨드 실행시 kv 명령어를 포함하지 않아도 되는 구 버전 엔진을 활성화했다는 것을 의미한다. 시크릿 패스 리스트를 출력한 결과는 다음과 같다.
$ vault secrets list ⏎
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_95df80c1 per-token private secret storage
identity/ identity identity_92ffb6a2 identity store
secret/ kv kv_b2f17d52 n/a
sys/ system system_2e06c4c2 system endpoints used for control, policy
사라졌던 secret/ 패스가 다시 추가되었고, 연결된 시크릿 엔진은 kv임을 확인할 수 있다. 이번에는 -detailed
옵션을 주어 상세 정보까지 출력해보자.
$ vault secrets list -detailed ⏎
출력 결과가 너무 많아 그림으로 식별하기는 어렵지만, secret/ 패스의 Option 필드 정보를 확인하면 map[version:1]
이 설정된 것이 확인된다.
버전 1에서 KV 엔진은 다음과 같이 사용한다.
$ vault write secret/app/tutorial username=winny ⏎
Success! Data written to: secret/app/tutorial
$ vault read secret/app/tutorial ⏎
Key Value
--- -----
refresh_interval 768h
username winny
이전에 쓰던 명령어인 put
, get
과 달리 이번에는 write
, read
가 사용되었는데, 이는 KV 엔진의 버전 때문이다. 버전별로 제공하는 커맨드 및 API가 다르다고 이해하면 된다.
기존 시크릿 엔진을 비활성화하고 새로운 엔진을 연결하는 과정을 정리해보자.
vault secrets disable <시크릿 패스>
vault secrets enable -path=<시크릿 패스> <시크릿 엔진>
예) aws/iam/ 경로에 AWS 엔진을 연결하는 경우
$ vault secrets disable aws/iam/ ⏎
$ vault secrets enable -path=aws/iam/ aws ⏎
참고로, 시크릿 패스가 존재하지 않을 경우 disable 명령은 스킵해도 된다. 다만 실행해도 에러는 발생하지 않는다.
시크릿 엔진의 라이프 사이클
시크릿 엔진은 CLI나 API를 통해 활성화(Enable), 비활성화(Disable), 이동(Move) 또는 조정(Tune)된다. 활성화와 비활성화는 주어진 시크릿 패스에 연결하거나 연결을 끊는 과정으로, 조금 전의 실습에서 다루어 본 바가 있다.
이동(Move)은 활성화와 비활성화를 합친 것으로, 지정된 경로에서 해당 시크릿 엔진을 비활성화한 후 새로운 경로에서 활성화하는 과정을 의미한다. 이 과정에서 저장된 시크릿은 모두 삭제되지만, 시크릿 엔진의 설정 정보는 그대로 유지된다.
조정(Tune)은 TTL과 같은 설정값을 시크릿 엔진에 적용하는 과정이다. 한번 설정한 값은 해당 시크릿 엔진 전체에 적용된다.
이전 버전에서는 이들의 동작을 마운트(Mount)라고 불렀으나 현재 이 용어는 더이상 권장되지 않는다. 해당 용어를 사용하는 포스팅을 본다면 오래된 버전에 대한 것임을 명심하자. 다만 동작 방식을 보면, 활성화 보다는 마운트라는 용어가 더 어울린다는 느낌을 지울 수가 없다. 필자 역시도 마운트 용어에서 출발한 연결 개념을 더 선호한다. .
UUID를 이용한 시크릿 엔진의 데이터 격리
앞서, 특정 경로에 저장된 시크릿은 연결된 시크릿 엔진으로만 접근할 수 있다고 설명한 바 있다. 이는 물리 스토리지 계층에 적용되는 배리어 뷰(Barrier View) 구조 때문이다.
시크릿 엔진이 활성화되면 임의의 UUID가 발급된다. 이 값은 해당 엔진의 데이터 저장 루트 폴더로 사용되며, 물리 스토리지에 데이터를 기록할 때마다 해당 UUID 폴더가 접두사처럼 붙는다. 볼트의 물리 스토리지 계층은 ../
와 같은 상대 경로를 지원하지 않으므로 자신과 다른 UUID 폴더 아래에 저장된 시크릿에는 접근할 수 없다.
따라서 시크릿 엔진 별로 저장된 시크릿은 물리적으로 격리되며, 시크릿을 읽기 위해서는 반드시 해당 시크릿 엔진으로 접근해야 한다.
(시크릿 리스트를 -detailed
옵션으로 출력하면 UUID를 볼 수 있다. 이 값이 배리어 뷰 역할을 하는 루트 폴더로 사용된다)
TL;DR
- 볼트는 동적 시크릿과 정적 시크릿으로 나뉘며, 시크릿 종류에 따라 적용되는 시크릿 엔진의 종류도 다양하다.
- 볼트의 기본 시크릿 엔진은 KV로, 이는 정적 시크릿을 관리할 때 사용하는 엔진이다.
- 시크릿 엔진과 시크릿 패스는 서로 연결되어 있기 때문에, 특정 패스에 값을 저장할 때에는 올바른 시크릿 엔진을 사용해야 한다.
- 시크릿 패스에서 시크릿 엔진을 분리할 수 있다. 일단 시크릿 엔진을 분리하면 시크릿 패스에 저장된 시크릿은 모두 삭제된다.
- 시크릿 엔진이 비활성화된 임의의 시크릿 패스에는 원하는 시크릿 엔진을 연결할 수 있다.
- 시크릿 엔진이 특정 경로와 연결되면 UUID가 발급된다. 이 값이 물리적인 루트 폴더로 동작하므로 UUID가 다른 시크릿 엔진은 접근할 수 없다.