Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

HashiCorp의 비밀정보 관리 도구 Vault의 구성

얼마 전에 HashiCorpTerraform에 대해서 올렸는데 이번에는 다른 제품인 Vault에 대해 설명하게 되었다. HashiCorp를 좋아하긴 하지만 어쩌다 보니 HashiCorp의 도구를 연달아 사용해 보게 되었다.

Vault

Vault는 비밀 정보 즉, 공개되면 안 되는 비밀번호, API 키, 토큰 등을 저장하고 관리하는 도구이다.

Vault 홈페이지

개발하면 꽤 많은 비밀 정보를 사용하게 된다. 여기서 비밀 정보라는 것은 애플리케이션 등에서는 사용해야 하는데 외부에 노출이 되면 안 되는 정보를 얘기하는 것이다. 대표적으로 데이터베이스 계정명과 비밀번호가 있고(URL은 보통 접근 안 되게 차단하지만 이마저도 외부에 공개되어서 좋을 건 없다.), 외부 API를 사용한다면 엑세스 토큰이 있을 수도 있고 서버에 접근하기 위한 SSH 비밀키도 있다. 최소한 내가 경험해 본 프로젝트에서는 이중 비밀번호나 엑세스 토큰은 대부분 소스코드에 하드 코딩해서 사용하고 SSH키는 그냥 개발자의 PC에 (잘 관리할 것이라고 믿고) 저장되어 있고 개발이나 업무에 필요한 계정정보 등은 구글 스프레드시트 등에 관리되는 것이 대부분이었다.

이런 정보를 관리해주는 도구라고 생각하면 된다. 계정 정보 같은 건 구글 스프레드시트가 아니더라도 1Password 같은 솔루션도 있고 여기서는 개발에 대한 부분만 얘기해보자. 보통 이렇게 관리하는 것에 큰 문제가 없기는 하다. 코드에 하드 코딩 되어 있으면 사용하기도 쉽고 변경할 때는 업데이트하면 그만이기는 하지만 소스코드는 생각보다 관리하기가 쉽지 않다. GitHub 등의 공개 저장소의 실수로 올리는 경우를 제외하더라도 소스코드는 대부분 관련 개발자의 PC에 저장되어 있는데 악의적인 누출을 제외하더라도 개개인의 PC까지 다 제어하기는 쉽지 않기 때문에 유출될 가능성도 있고 코드에 하드 코딩 되어 있으므로 이러한 비밀정보를 바꾸기도 쉽지 않다.

프로젝트가 간단하다면 괜찮지만, 프로젝트 수가 많고 사람 수도 많아진다면 문제는 더욱 커진다. Vault는 이러한 정보를 안전하게 보관하고 관리해 주는 도구라고 생각하면 된다. 좀 더 쉽게 얘기하면 Vault에 이 정보를 저장해 놓고 애플리케이션이나 서버에서는 가져다가 사용하겠다는 의미이다. 최근 작업에서 이러한 솔루션이 필요하다는 생각을 하게 되어서 본격적으로 살펴보게 되었다.

Vault에서 사용하는 용어

Vault를 자세히 살펴보기 전에 이해를 돕기 위해서 용어 설명을 해야 할 것 같다. Vault를 사용할 때 기능을 확장할 수 있는데 이를 백엔드(Backend)라고 통칭해서 부른다.

스토리지 백엔드(Storage Backend)

Vault의 정보를 저장하면 Vault가 이를 어딘가에는 저장해야 하는데 이를 스토리지 백엔드라고 부른다. 스토리지 백엔드를 PostgreSQL로 설정하면 정보를 PostgreSQL에 저장하고 S3로 설정하면 파일에 저장한다. Consul, DynamoDB, S3, Google Cloud Storage, Azure, MySQL, PostgreSQL 등 다양한 백엔드를 지원하고 있다.

시크릿 백엔드(Secret Backend)

시크릿 백엔드는 문서를 보면 꽤 많은 시크릿 백엔드가 있다. 대표적으로 Generic과 Cubbyhole이 있는데 Gerneric 시크릿 백엔드를 사용할 때 정보를 저장하면 위의 스토리지 백엔드로 설정한 물리 스토리지에 정보를 저장하게 된다. Cubbyhole는 Gerneric과 똑같이 동작하지만, 연습용 플레이그라운드라고 생각하면 된다. 일반적으로 비밀정보를 저장할 때 생각하게 되는 username/password 등을 저장한다고 하면 이 Generic을 사용하면 된다.

문서를 보면 이 외에 AWS, MySQL, PostgreSQL 등 많은 시크릿 백엔드가 있는 걸 볼 수 있다. 다 테스트해 본 것은 아니지만 내가 본대로는 Generic과 Cubbyhole을 제외한 나머지 시크릿 백엔드는 해당 서비스에 연결해서 동적으로 동작하는 백엔드이다. 이해가 안될 텐데 예를 들어 보자.

AWS 시크릿 백엔드를 사용한다고 하면 AWS에서 API 키와 시크릿을 만들어서 Vault에 이 정보로 AWS 시크릿 백엔드를 설정한다. 그러면 AWS 키가 필요할 때 이 백엔드에 요청을 하면 AWS에 접속해서 새로운 Role을 만들고 그 엑세스 키와 시크릿 정보를 관리해 주게 된다. 즉 요청할 때마다 새로운 IAM Role이 생성되어 반환해 준다.

MySQL이나 PostgreSQL같은 데이터베이스도 마찬가지인데 데이터베이스의 계정 정보로 해당 시크릿 백엔드를 구성하면 요청할 때 데이터베이스에서 새로운 계정을 만들고 expire도 설정하고 이 계정 정보를 돌려준다.

즉, 비밀정보를 관리할 수 있는 권한을 Vault에 주고 Vault가 필요에 따라 새로 만들어 줌으로써 계정이나 비밀정보를 관리할 수 있게 하겠다는 것이다. 처음에 이 개념을 보았을 때는 "어째서?"라는 느낌이었지만 현실에서 데이터베이스의 계정이나 비밀번호를 운영 중에 바꾸는 것을 한 번도 본적이 없다는 점을 생각해 보면 이런 Vault의 관리하에서는 가능하겠다는 생각이 들었다.

인증 백엔드(Auth Backend)

Vault에 중요한 정보를 관리하기 시작했으면 이 Vault에 접근할 수 있는 권한을 제어해야 하는데 이게 인증 백엔드이다. 인증 백엔드는 username/password도 있고 Token 백엔드도 있어서 필요에 따라 사람이나 애플리케이션 등에서 사용하도록 만들 수 있다. 그 외에 LDAP이나 GitHub 등을 연결할 수 있다. 회사에서 LDAP를 쓰고 있다면 LDAP auth backend를 연결해서 사용하게 될 테고 이때 퇴사를 해서 LDAP 계정을 삭제한다면 더는 Vault에 접근할 수 없으므로 관리 포인트를 줄일 수 있다. GitHub 같은 경우는 GitHub의 특정 org에 연결해서 해당 org의 사용자만 Vault에 접근할 수 있게 하는 식으로 연결할 수 있다.

Vault 설치

다운로드 사이트에서 OS에 맞는 파일을 다운로드받고 압축을 풀어서 경로에 넣어주면 설치가 끝이다. 정상적으로 설정되었다면 다음과 같이 버전을 확인할 수 있다. 현재 최신 버전은 v0.6.4다.(HashiCorp는 버전 업그레이드에 아주 짜다.)

$ vault -v
Vault v0.6.4 ('f4adc7fa960ed8e828f94bc6785bcdbae8d1b263')


Vault 서버 설정

위에서 설치한 vault는 서버이면서 클라이언트이다. 클라이언트를 보기 전에 서버가 필요하므로 먼저 서버 설정을 해보자. Vault 서버의 고가용성을 보장하려면 HashiCorp가 만든 Consul을 사용해야 하지만 설정이 복잡해지므로 여기서는 PostgreSQL을 사용해보자. 로컬 5432 포트로 PostgreSQL v9.6.1이 떠 있다고 할 때 vault_test라는 데이터베이스를 만들고(CREATE DATABASE vault_test;) 다음 명령어로 테이블과 인덱스를 미리 생성해야 한다.

CREATE TABLE vault_kv_store (
  parent_path TEXT COLLATE "C" NOT NULL,
  path        TEXT COLLATE "C",
  key         TEXT COLLATE "C",
  value       BYTEA,
  CONSTRAINT pkey PRIMARY KEY (path, key)
);

CREATE INDEX parent_path_idx ON vault_kv_store (parent_path);

데이터베이스 준비가 완료되었으면 다음의 내용으로 config.hcl 파일을 만든다.(파일명을 중요치 않다.)

backend "postgresql" {
  connection_url = "postgres://postgres:postgres@127.0.0.1:5432/vault_test?sslmode=disable"
}

listener "tcp" {
  address = "0.0.0.0:8200"
  tls_disable = 1
}

설정을 설명하자면 스토리지 백엔드로 PostgreSQL을 설정해서 로컬에 띄워놓은 디비를 지정했다. 마지막에 sslmode=disable이 붙인 이유는 PostgreSQL의 SSL이 기본으로 활성화되어 있지 않으므로 Vault가 보안 문제로 오류를 던지므로 수동으로 끈 것이다. 이는 물론 테스트라서 그렇고 프로덕션이라면 당연히 SSL을 고려해야 할 것이다.

listener "tcp"는 이 서버에 접속할 포트를 여는 것이다. 이 설정에 따라 8200 포트로 서버에 접속할 수 있고 여기서는 테스트이므로 tls를 비활성화했다.(프로덕션에서도 보통은 Vault 앞의 로드밸런서나 웹서버에서 HTTPS 인증을 구성해서 사용할 것이므로 프로덕션에서도 상황에 따라서는 끌 수 있다.)

이제 Vault 서버를 띄워보자. 서버를 띄울 때는 vault server 명령어를 사용하고 -config 옵션으로 위에서 작성한 config.hcl 파일을 지정하면 된다.

$ vault server -config=config.hcl
==> WARNING: mlock not supported on this system!

  An `mlockall(2)`-like syscall to prevent memory from being
  swapped to disk is not supported on this system. Running
  Vault on an mlockall(2) enabled system is much more secure.

==> Vault server configuration:

                 Backend: postgresql
                     Cgo: disabled
              Listener 1: tcp (addr: "0.0.0.0:8200", cluster address: "", tls: "disabled")
               Log Level: info
                   Mlock: supported: false, enabled: false
                 Version: Vault v0.6.4
             Version Sha: f4adc7fa960ed8e828f94bc6785bcdbae8d1b263

==> Vault server started! Log data will stream in below:

WARNING: mlock not supported on this system! 경고는 테스트를 macOS에서 해서 나온 것이다. 메시지로 추정하건대 서버의 메모리를 탈취해서 비밀 정보를 뺏어가는 공격을 막기 위한 것으로 보인다. 그래서 프로덕션에서는 mlockall(2)를 활성화하기 위해 root 권한으로 실행해야 한다. 그 아래 구성할 설정 정보가 나오고 이제 이 서버를 사용하면 하단에 로그가 출력된다.

Vault 서버 초기화

서버를 처음 띄웠으므로 Vault를 초기화해야 사용할 수 있다. 서버 구성은 이게 끝이고 나머지 작업은 모두 클라이언트에서 하게 된다. 클라이언트가 서버를 찾을 수 있도록 VAULT_ADDR 환경변수에 서버 경로를 지정한다.

$ export VAULT_ADDR='http://52.198.157.64:8200'

Vault의 서버 상태를 확인하는 명령어인 vault status를 실행하면 서버가 초기화되지 않았다고 오류가 나온다.

$ vault status
Error checking seal status: Error making API request.

URL: GET http://127.0.0.1:8200/v1/sys/seal-status
Code: 400. Errors:

* server is not yet initialized

초기화는 vault init 명령어를 사용한다.

$ vault init
Unseal Key 1: 986qtg5W7YOewHvLxj/0M42TkfjAY/+uIu2MJfoRFMkB
Unseal Key 2: 2v7+to7s3s6E/ZOMBN8PNkm/a8QdUyrSF++FiNamkhoC
Unseal Key 3: Noc5jbbGOgTZsWcoGX+ClgDETjUytTjCYG+0l2D+65oD
Unseal Key 4: iv9I1FB6rHyU5yZEx3NSF7n0c1iWNtlNVyC+Q/rrvNQE
Unseal Key 5: ZoaP72hQSLbJq9Lg2tPft/CPVqm50MtdIKCPXEyzxVQF
Initial Root Token: c0ccbf61-cd7b-bd98-dabe-f319247cedd8

Vault initialized with 5 keys and a key threshold of 3. Please
securely distribute the above keys. When the Vault is re-sealed,
restarted, or stopped, you must provide at least 3 of these keys
to unseal it again.

Vault does not store the master key. Without at least 3 keys,
your Vault will remain permanently sealed.

긴 로그가 나왔는데 정리하면 Unseal Key를 5개 주고 Root Token을 하나 준다.

Seal/Unseal

초기화는 서버를 껐다 키더라도 서버에서 단 한 번만 실행하면 된다. 초기화를 하더라도 Vault 서버는 Sealed상태가 된다. 즉, 잠겨있어서 Vault 서버에서 정보를 읽는 등의 작업을 할 수 없으므로 서버를 Unsealed상태로 만들어야 한다. 이는 PC의 잠금 상태와 잠금 해제상태를 생각하면 된다. 이때 사용하는 것이 초기화할 때 알려준 Unseal Key이다. 위의 메시지를 보면 Unseal Key가 5개이고 스레스홀드가 3인 걸 볼 수 있는데 이 5개의 키 중 3개를 제공해야 Unseal을 할 수 있다.

$ vault status
Sealed: true
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0
Version: 0.6.4

High-Availability Enabled: false

상태를 확인해 보면 Sealedtrue인 걸 볼 수 있다. 이제 unseal을 해보자. unseal은 vault unseal 명령어를 사용한다.

$ vault unseal
Key (will be hidden):
Sealed: true
Key Shares: 5
Key Threshold: 3
Unseal Progress: 1

$ vault unseal
Key (will be hidden):
Sealed: true
Key Shares: 5
Key Threshold: 3
Unseal Progress: 2

$ vault unseal
Key (will be hidden):
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0

$ vault status
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0
Version: 0.6.4
Cluster Name: vault-cluster-fdb80367
Cluster ID: edf425f4-9350-d914-80d5-b3b3a91e0971

High-Availability Enabled: false

unseal을 시도하면 키를 입력하라고 나오고 초기화 때 받은 키를 넣으면 된다. 위에서 보면 총 3번 실행했고 실행할 때마다 Unseal Progress의 숫자가 줄어드는 것을 볼 수 있다. unseal이 완료되고 상태를 확인해 보면 Sealedfalse가 된 것을 볼 수 있다. unseal을 할 때는 키의 순서 등은 전혀 상관이 없고 5개 중 3개만 제공하면 된다.

총 Unseal key의 개수와 스레스홀드를 바꾸고 싶다면 초기화할 때 vault init -key-shares=3 -key-threshold=2처럼 숫자를 지정하면 된다. 이는 키가 총 3개이고 2개를 입력하면 unseal을 할 수 있다는 의미이다.

여기서는 연속적으로 unseal을 3번 실행했지만, 실제 시나리오를 생각하면 저 키를 5명이 나누어 갖고 3명이 unseal을 하면 Vault 서버가 열린다는 의미가 된다. Vault에 중요한 정보를 다 담아놓았으므로 서버를 재시작하거나(이때는 항상 sealed가 된다.) 보안 문제가 있어서 수동으로 막아야 해서 sealed 상태로 바꾸었을 때 다시 서버를 열려면 unseal 단계를 거쳐야 한다. 이는 꽤 괜찮은 방법이라고 생각한다. 초기화를 할 때 5명이 키를 나누어 가지고 있다가 중요한 때에 이 중 3명이 unseal로 바꿀 수 있게 되는 것이다. 위에서는 3번 연속실행했지만, unseal 과정은 서버가 기억하고 있으므로 한자리에 모일 필요 없이 각자의 자리에서 unseal을 하기만 하면 된다. 즉, 스토리지 백엔드에도 암호화되어서 저장되어 있고 이를 복호화하려면 이 unseal 단계가 필수적이 된다. 설명에 따르면 저 키 3개가 없으면 Vault 서버도 복호화키를 만들어 낼 수 없다고 한다. 그러므로 DB가 통째로 털려도 Vault 서버가 통째로 털려도 이 키 3개가 없다면 공격자는 정보를 볼 수 없게 된다.

하지만 더 현실적으로 생각해 보자. 이런 작업을 한두 번 해보는 게 아니지 않은가! 5명이 이 키를 나누어 가진 뒤 어떻게 보관하게 할 것이냐 하는 문제를 제쳐놓고 지금까지의 경험으로 보건대 초기화를 하고 이 5개의 키를 한꺼번에 관리할 가능성이 훨씬 크다. 탈취당하는 경우보다 운영하면서 unseal 하는 경우가 많을 텐데 어떻게 이 5명 중 3번을 매번 찾겠는가.

그래서 Vault는 초기화를 PGP로 암호화하는 방법을 제공하고 있고 사용상의 편의를 위해서 keybase.io를 직접 이용할 수 있도록 해놓았다. 다음과 같이 -pgp-keys로 keybase의 계정명을 제공하면 이 공개키로 초기화를 하게 되고 unseal을 할 때는 이 사람들의 비밀키를 제공하면 unseal을 할 수 있다.

$ vault init \
  -pgp-keys="keybase:outsideris,keybase:james,keybase:jefferai,keybase:vishalnayak,keybase:sethvargo"

이 방법을 이용하면 자연스럽게 Unseal key를 나눠 가질 수 있다. 회사의 중요한 5명이 이 방법을 사용하면 되긴 하지만 퇴사한 경우에 이 키를 어떻게 교체할 수 있는지까지는 아직 찾아보지 못했다.

2017/01/18 02:50 2017/01/18 02:50