hashicorp사의 Vault(볼트) - 개요 - 1
목차
- 들어가며
- 개요
- 용어 정의
- 아키텍처 개요
들어가며
본 게시물은 Vault 공식 문서의 내용과 필자의 생각을 정리하였다. Vault는 무엇인지, 어디에 사용하는지, 왜 사용하는지, 어떻게 사용하는지 서술한다. Vault를 처음 접하는 인원은 Vault의 흐름과 골격을 이해할 수 있는 시간을 갖는다. 또한 Vault를 실질적으로 사용해보는 시간을 갖는다. Vault는 Dev 서버 모드를 지원한다. Dev 서버 모드는 사전 설정이 되어있는 데모 서버라고 생각하면 좋다. 사용자는 해당 데모 서버에서 Vault를 학습할 수 있다. 시간이 괜찮으면 Vault HA를 구성한다(시간이 있으면...).
본 게시물의 시리즈는 다음과 같다.
- hashicorp사의 Vault(볼트) - 개요 - 1
- hashicorp사의 Vault(볼트) - Tutorial-Dev(With-커맨드) - 2
- hashicorp사의 Vault(볼트) - Tutorial-Real(With-REST-API) - 3
- hashicorp사의 Vault(볼트) - Tutorial-Docker - 4
- hashicorp사의 Vault(볼트) - Tutorial-HA - 5
- hashicorp사의 Vault(볼트) - Java-Spring With Vault - 6
개요
HashiCorp의 Vault는 민감한 데이터를 안전하게 저장하는 저장소이다. 민감한 데이터의 종류는 다음과 같다. 1)Secret 2)Credential 3)Password 4)Enctyption-Key 등이다. 이와 같이 기밀성이 요구되는 정보는 DBMS에 단순 저장하면 안 된다. Vault는 위의 개체들을 암호화하여 안전하게 저장한다.
용어 정의
Storage Backend
- Vault의 암호화된 Secret을 보관하는 곳이다.
Barrier
- Barrier는 Vault의 일부 구성요소를 감싸고 있다. 때문에 Vault와 Storage Backend 간의 통신은 Barrier를 거쳐야 하며, Barrier가 프록시 역할(대문 역할)을 한다. Barrier의 구성요소들은 서로 Trust 한 관계를 유지/성립한다.
- Vault는 암호화된 데이터만 밖으로 나오게 한다.
- Vault는 Barrier의 상태가 “unsealed”가 되어야 접근할 수 있다.
Secret Engine
- Secret의 관리를 책임진다.
- Secret 관련 작업은 Secret Engine으로 전달하고 Engine의 구현체마다 상이한 방식으로 저장한다.
- Secret Engine의 인터페이스를 활용하여 DB, File System 또는 유저가 정의한 방식으로 저장한다.
Audit Device
- 모든 Vault의 Request/Respones는 Audit Device에 의해 감사 로깅이 된다.
Auth Method
- Vault에 접근하는 클라이언트를 인증한다.
- 인증된 클라이언트의 토큰을 반환한다.
Client Token
- HTTP에서의 세션 ID와 같은 토큰을 반환한다.
- Vault의 REST API를 사용하는 경우 HTTP 헤더에 토큰을 적재한다.
Secret
- Vault에서 관리하는 비밀 객체이다.
- Secret은 일정 주기를 가지며, 해당 주기가 만료되면 폐기해야 한다.
아키텍처 개요
[전체 흐름]
Vault는 기밀성이 요구되는 데이터(이하 Secret)를 안전하게 보관하는 저장소이다. Vault를 사용하기 위해서는 “unsealing”과 초기화 작업이 필요하다. Vault 서버 초기화 시 Vault는 “sealed” 상태이다. “Unsealed-Key”는 “sealed” 상태를 “unsealed” 상태로 전이할 수 있다. “unsealed” 상태가 되면 Barrier안에 있는 Vault의 모든 기능 및 구성요소에 원활하게 접근할 수 있다.
Vault에서 실질적으로 Secret을 암/복호화하는 키는 "Encryption-Key"이다.
1) "Master-Key"는 “Encryption-Key”를 암호화한다.
2) "Unsealed-Key"는 “Master-Key”를 암호화한다.
3) “Unsealed-key”는 SSS(Shamir Secret Sharing) 알고리즘으로분할 보관한다.
키들의 관계는 아래 도식화 자료와 같다.
“Encryption-Key”는 “Secret”을 안전하게 암/복호화한다. “Secret”들은 “Storage backend”에 보관하며, “Secret”에 접근하기 위해서는 REST API를 사용한다. REST API는 Create, Register, Rotate, Destroy 등의 명령을 제공하며, "Secret"을 제어할 수 있다. REST API를 사용하기 위해서는 "토큰 인증"과 "토큰에 상응하는 정책 인가" 작업이 필요하다.
[고가용성]
Vault는 HA를 구성할 수 있다. HA의 구조는 Active-Standby 구조이다. 오픈 소스 버전의 경우 Standby는 Read-Only 기능을 제공하지 않는다. Standby에 요청이 들어오면 요청을 Active에 포워딩한다. Read-Only 기능은 엔터프라이즈에서 제공한다. 그래도 오픈 소스 버전에서 Hot-Standby를 제공하기 때문에 Failover는 문제없다. 제일 큰 단점은 Standby의 read-only의 미지원이다. Product 레벨에서 Vault를 사용하기 위해서는 해당 기능이 제일 필요할 것 같다.
Replication 방식은 PostgreSQL의 방식과 같이 Active-Standby 구성이며, Active의 변경/추가/삭제된 것에 대한 로그(WAL, Write Ahead Log)를 Standby에 전송한다. 데이터 처리에 있어 보통의 DBMS과 같이 WAL 기법을 사용하는 것 같다.
[보안 특성]
- Vault와 클라이언트 상호 인증은 제공하지 않는다. 다만 서버에서 제공하는 네트워크 계층 보안, TLS를 제공한다.
- 정상 토큰을 보유한 주체는 리소스("Secret")에 접근할 수 있다.
- 토큰 인가 정책은 리소스에 접근하는 주체를 제어한다.
- Secret에 접근하는 모든 행위를 감사 로깅한다.
- "Secret"을 암호화하여 Storage Backend에 저장한다.
- Vault의 Barrier를 통과하는 모든 요청과 반환은 AES-256(GCM)으로 암호화한다. IV의 경우, 자동으로 임의로 생성한다.
- SSS(Shamir Secret Sharing) 알고리즘으로 MasterKey를 분리 보관한다.
[Key 회전(갱신)]
rekey 명령은 Unsealed-Key와 Master-Key를 갱신한다. 갱신된 Master-Key는 Encryption-Key를 재암호화하며,갱신된 Unsealed-Key는 SSS에 의해 다시 분리하여 보관하여야 한다.
rotate 명령은 Encryption-Key를 갱신한다. 기존 Encryption-Key는 별도의 keyring에 보관한다. 후의 요청들은 새로운 Encryption-Key로 암호화를 한다. Keyring에 있는 Encryption-Key는 복호화 용도에만 사용한다. 이와 같이 사용하면 re-encryption을 수행하지 않아도 괜찮다(keyring에 예전 Encryption-Key를 다 보관하고 있다. 갱신에 의미가 있나?).
[정책]
사용자는 Identifier/Authentication값을 요청한다. 요청 성공 시 토큰을 반환받는다. 해당 토큰은 다음과 같이 화이트 리스트 정책을 갖고 있다
# This section grants all access on "secret/*". Further restrictions can be # applied to this broad policy, as shown below. path "secret/*" { capabilities = ["create", "read", "update", "delete", "list"] } # Permit reading secret/foo/bar/teamb, secret/bar/foo/teamb, etc. path "secret/+/+/teamb" { capabilities = ["read"] } # Policies can also specify allowed, disallowed, and required parameters. Here # the key "secret/restricted" can only contain "foo" (any value) and "bar" (one # of "zip" or "zap"). path "secret/restricted" { capabilities = ["create"] allowed_parameters = { "foo" = [] "bar" = ["zip", "zap"] } } |