Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Vault를 공부해보려 한다. 여기에서는 진짜 컨셉만 다룬다.

Vault

Vault는 HashiCorp에 의해서 개발된 크로스플랫폼 패스워드 및 인증 관리 시스템이다. 공개되면 안되는 비밀번호, API 키, 토큰 등을 저장하고 관리한다.

아키텍처

 Vault 아키텍처

Storage Backend

스토리지 백엔드는 암호화된 데이터를 저장하기 위한 스토리지를 담당한다. Vault는 스토리지의 종류, 가용성 등을 책임지지 않는다. 어떤 스토리지 백엔드를 쓸 것인지는 고객이 결정한다. Vault는 15가지가 넘는 스토리지를 지원한다.
  • HashCorp Consul
  • Etcd
  • Zookeeper
  • AWS S3
  • AWS DynamoDB
  • Azure Storage Container
  • Triton Manta Object Storage
  • GCP Cloud Storage
  • GCP Cloud Spanner
  • MySQL
  • PostgreSQL
  • MSSQL
  • Swift
  • Raft
  • Filesystem
  • CockroachDB
  • Cassandra
어떤 스토리지를 써야할지 헷갈릴 수 있겠다. 스토리지 선택에 도움을 줄 수 있는 다이어그램을 소개한다.

 Vault Storage Backend Decision Tree

filesystemin-memory 스토리지는 백앤드는 개발단계에서 빠르게 활용하기에 좋은 시스템이다. 나머지 스토리지 백앤드는 서비스 전개 환경에 따라서 선택적으로 구성하면 된다.

Barrier

Barrier는 장벽이라는 의미 그대로 데이터의 안전한 사용을 위한 강철벽이라고 생각하면 된다. 스토리지 백앤드간에 흐르는 모든 데이터는 Barrier를 통과한다. Barrier는 암호화된 데이터만을 기록하며, 데이터가 필요한 시점에 복호화(암호해독)이 되도록 한다. 은행금고와 마찬가지로 barrier를 통과해야만 내부 데이터에 접근 할 수 있다.

Secret Engine

Secrets Engine은 데이터를 저장, 생성, 암호화하는 구성요소다. 간단한 secret engine는 암호화된 Redis/Memcached에 단순하게 key&value 형식으로 데이터를 저장하고 읽을 수 있다. 다른 시크릿 엔진은 서비스에 연결하고 요청을 하는 시점에서 동적으로 자격증명을 생성 할 수도 있다.

다양한 서비스를 위한 시크릿 엔진이 존재한다. 아래는 그 목록이다.
  • Active Directory
  • AliCloud
  • AWS
  • Azure
  • Consul
  • Cubbyhole
  • Database
  • Google Cloud
  • Google Cloud KMS
  • KMIP
  • Key/Value
  • Identity
  • Nomad
  • PKI
  • RabbitMQ
  • SSH
  • TOTP
  • Transit
예를 들어 AWS를 대상으로 할 경우, 시크릿 엔진은 AWS IAM 정책을 기반으로 AWS 자격증명을 동적으로 생성한다. 이렇게 할 경우 웹 UI를 통하지 않고, 체계적으로 자격증명을 관리 할 수 있다. AWS IAM 자격 증명은 시간을 기반으로 하기 때문에, 일정 시간이 지나면 자동으로 자격증명을 삭제한다. SSH를 이용해서 인스턴스에 대한 접근권한을 관리할 경우에는 SH 시크릿엔진을 사용할 수 있다. SSH 유저, 권한, 유효시간, 원타임패스워드등을 안전하게 관리 할 수 있다.

Audit Device

Audit device는 감사로그를 관리한다. Valut에 대한 모든 요청과 응답은 Audit device를 통과하면서 로그를 남기고 관리자는 로그를 통해서 시스템 상태를 관리 할 수 있다.

Auth Method

Auth Method는 Vault에 연결하려는 유저와 애플리케이션을 인증하기 위해서 사용한다. 인증되면 auth method는 인증된 사용자와 사용 할 수 있는 애플리케이션 정책목록을 리턴한다. 이때 클라이언트 토큰(client token)을 리턴하는데, 한번 인증을 끝내고 나면 클라이언트 토큰을 이용하게 된다. 다른 구성요소들과 마찬가지로 다양한 서비스를 Auth Method로 사용 할 수 있다.
  • AppRole
  • AliCloud
  • AWS
  • Azure
  • Google Cloud
  • JWT/OIDC
  • Kubernetes
  • GitHub
  • LDAP
  • Okta
  • PCF
  • RADIUS
  • TLS Certificates
  • Tokens
  • Username & Password
예를 들어서 Userpass(Username & Password) auth method는 유저이름과 패스워드 기반의 인증 매커니즘을 제공한다. 마찬가지로 GitHub를 인증 수단으로 이용 할 수도 있다.

Client Token

클라이언트 토큰(Vault Token이라고 부르기도 한다.)은 웹 사이트에서 사용하는 세션 쿠기(session cookie)과 개념적으로 비슷하다. 사용자 인증이 끝나면 Vault는 클라이언트 토큰을 리턴하는데, 이후에는 클라이언트 토큰을 이용해서 작업을 수행 할 수 있다. Vault는 클라이언트 토큰을 이용해서 클라이언트를 식별하고 애플리케이션 접근 정책을 검사한다. 이 토큰은 HTTP 헤더를 통해서 전달된다.

Secret

Secret는 암호화된 데이터를 포함해서 Vault가 리턴하는 모든 정보를 의미한다. Vault가 반환하는 모든 정보가 Secret 인 것은 아니다. 예를 들어 시스템 설정, 상태 정보, 정책등은 secret로 간주되지 않는다. Secret는 lease(임대)객체다. Lease가 끝나면 Vault는 Secret를 파기한다. 관리자는 lease 정보를 확인해서 Secret를 철회하거나 연장하기 위해서 개입 할 수 있다.

예를 들어 관리자는 아래와 같은 Secret를 만들 수 있다.
$ vault kv put secret/hello foo=world
Key              Value
---              -----
created_time     2019-08-19T03:53:56.491587848Z
deletion_time    n/a
destroyed        false
version          1

Server

Vault는 클라이언트/서버 애플리케이션이다. Vault 서버는 데이터 스토리지와 백앤드등과 상호작용을 하며, 전체 Vault 아키텍처에서 하나의 컴포넌트로 작동한다. 사용자는 Vault CLI를 이용해서 TLS 연결로 서버와 통신 할 수 있다.

Vault 서버 인스톨 및 기본 사용

Download Vault페이지에서 운영체제별로 컴파일된 실행파일을 다운로드 할 수 있다. 나는 vault_1.2.2_linux_amd64.zip 를 다운로드했다.
# unzip vault_1.2.2_linux_amd64.zip
# sudo cp vault /usr/local/bin
# vault version
Vault v1.2.2

Dev 서버 시작

Vaulte dev 서버를 제공한다. 테스트나 학습목적으로 사용 할 수 있다.
# vault server -dev
==> Vault server configuration:

             Api Address: http://127.0.0.1:8200
                     Cgo: disabled
         Cluster Address: https://127.0.0.1:8201
              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level: info
                   Mlock: supported: true, enabled: false
                 Storage: inmem
                 Version: Vault v1.2.2

WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variable:

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

The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.

Unseal Key: lgdr00WR/supFOZYzn6Pnkup4lLgCF1dsKJ+G4x/Tmg=
Root Token: s.sKZpL9GncNY8BtaJsgGBwpfr

Development mode should NOT be used in production installations!

==> Vault server started! Log data will stream in below:
VAULT_ADDR 환경 변수를 설정하자.
# export VAULT_ADDR='http://127.0.0.1:8200'
  • Root Token : vault web ui 에 로그인하기 위해서 필요하다. vault login 명령을 수행하기 위해서도 필요하다.
  • Unseal Key : vault 서버를 unseal 상태로 만들기 위한 key. 밑에서 설명하고 있다.

Seal/UnSeal

Vault 서버는 Seal/UnSeal 상태를 가진다. Seal(봉인)은 봉인된 상태인데, 이 상태에서 Vault는 접근해야 하는 스토리지의 위치와 방법은 알고 있지만, 데이터를 해독 할 수 없다. 데이터를 해독하기 위해서는 UnSeal(봉인해제) 상태로 만들어서 마스터키를 구성해야 한다. UnSeal을 위해서는 Unseal Key가 필요하다.

value status로 서버 상태를 확인해보자.
# vault status
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.2.2
Cluster Name    vault-cluster-eb3cf07e
Cluster ID      fa8f56e4-d020-d001-e841-0cfb18e4d091
HA Enabled      false
Sealed 가 false 이다. 봉인해제된 상태를 데이터를 쓰고 읽을 수 있다.
# vault kv put secret/hello foo=world                                     
Key              Value
---              -----
created_time     2019-08-19T07:52:57.687010855Z
deletion_time    n/a
destroyed        false
version          1

# vault kv get secret/hello
====== Metadata ======
Key              Value
---              -----
created_time     2019-08-19T07:52:57.687010855Z
deletion_time    n/a
destroyed        false
version          1

=== Data ===
Key    Value
---    -----
foo    world

Vault 서버를 Seal 상태로 만들어 보자.
# vault operator seal
Success! Vault is sealed.

Seal 상태에서 값을 가져오면 에러가 발생한다.
# vault kv get secret/hello
Error making API request.

URL: GET http://127.0.0.1:8200/v1/sys/internal/ui/mounts/secret/hello
Code: 503. Errors:

* error performing token check: Vault is sealed

unseal 명령을 이용해서 Unseal 상태로 만들 수 있다. 이때 unseal key가 필요하다.
# vault operator unseal lgdr00WR/supFOZYzn6Pnkup4lLgCF1dsKJ+G4x/Tmg=
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.2.2
Cluster Name    vault-cluster-eb3cf07e
Cluster ID      fa8f56e4-d020-d001-e841-0cfb18e4d091
HA Enabled      false

아래는 vault가 key를 관리하는 방법이다.

 vault의 key 관리법

Vault는 데이터를 암호화해서 저장한다. 암호화한 데이터를 복호화 하기 위해서는 Encryption Key가 필요하다. 이 encryption key는 데이터와 함께 저장이 되는데, 이 키는 마스터키라고 하는 다른 키로 암호화가 된다. 마스터키는 누구에게도 노출되지 않는다.

따라서 데이터를 복호화 하기 위해서는 vault에서 마스터키를 가져와서 encryption key를 복호화해야만 한다. Unseal이 이 마스터키를 재구성하는 과정이다. 이것은 봉투 암호화의 다른 구현이라도 봐도 될 듯 하다.

Vault는 마스터키를 단일키로 배포하는 대신에 Shamir's Secret Sharing 이라는 알고리즘을 사용해서 키를 샤드로 분할 한다. 마스터키를 재구성하기 위해서는 특정 임계값의 샤드가 필요하다. 이렇게 하는 이유는 ? "마스터키를 안전하게 보관"하기 위함이다.

vault를 seal 상태로 만든 다음에 unseal로 전환해보자.
# vault operator seal  
Success! Vault is sealed.
vault operator unseal
Unseal Key (will be hidden): <여기에 unseal key 들을 입력해야 한다.) 

지금 vault 는 dev 상태라서 단지 하나의 키만 입력하면 된다. vault status 를 보면 Total Shares 1, Threshold 1 인 것을 확인 할 수 있다.

데이터 입/출력

간단하게 입출력 테스트를 해보기로 했다. kv 명령을 이용해서 값을 넣고 뺄 수 있다. 값을 넣어보자.
# vault kv put secret/creds passcode=my-long-passcode
Key              Value
---              -----
created_time     2019-08-19T08:41:05.371486114Z
deletion_time    n/a
destroyed        false
version          1

값을 읽어보자.
vault kv get secret/creds
====== Metadata ======
Key              Value
---              -----
created_time     2019-08-19T08:41:05.371486114Z
deletion_time    n/a
destroyed        false
version          1

====== Data ======
Key         Value
---         -----
passcode    my-long-passcode

데이터 읽기/쓰기를 테스트 했으니, 프로그래밍 언어로 테스트해보면 재미있을 것 같아서 코드를 만들어봤다.
package main

import (
    "fmt"
    "github.com/hashicorp/vault/api"
    "os"
)

var token = os.Getenv("TOKEN")
var vault_addr = os.Getenv("VAULT_ADDR")

func main() {
    config := &api.Config{
        Address: vault_addr,
    }
    client, err := api.NewClient(config)
    if err != nil {
        fmt.Println(err)
        return
    }
    client.SetToken(token)
    secret, err := client.Logical().Read("secret/data/creds")
    if err != nil {
        fmt.Println(err)
        return
    }
    m, ok := secret.Data["data"].(map[string]interface{})
    if !ok {
        fmt.Println("%T", secret.Data["data"])
        return
    }
    fmt.Printf(m["passcode"].(string))
}
값을 읽기 위한 패스가 "secret/creds"가 아니고 "secret/data/creds"인 것에 주의하자.

Policy

Vault는 Policy를 이용해서 액세스 권한을 설정한다. 애플리케이션에 정책을 부여함으로써, 애플리케이션을 제어하고 역할기반엑세스제어(Role-Based Access Control)를 수행 할 수 있다.

Vault를 처음 만들면 기본적으로 루트(root) 정책이 만들어진다. 루트 정책은 vault의 모든 항목에 액세스 할 수 있는 슈퍼권한 정책을 담고 있다. 즉 슈퍼유저가 된다. 이 권한을 통해서 초기 정책, 토큰등을 설정 할 수 있다. policy 명령으로 정책정보를 확인 할 수 있다.
# vault policy list
default
root

read 명령으로 정책 세부 내용을 읽을 수 있다.
$ vault policy read default
# Allow tokens to look up their own properties
path "auth/token/lookup-self" {
    capabilities = ["read"]
}

# Allow tokens to renew themselves
path "auth/token/renew-self" {
    capabilities = ["update"]
}

# Allow tokens to revoke themselves
path "auth/token/revoke-self" {
    capabilities = ["update"]
}
...... 생략

정책은 HCL(HashiCorp configuration language) 로 정의한다.

Web UI

vault는 웹 UI를 제공한다. 웹 브라우저로 접근해 보자.

vault는 다양한 인증 방식을 제공한다. 나는 Token 방식을 이용해서 접근했다. Token은 vault 서버 실행시 출력했던 Root Token을 사용한다. 웹을 이용해서 secret, Policies, Access Group, Auth Methods 등을 관리 할 수 있다.

앞으로 할 것들

  • consul기반으로 vault클러스터 구성
  • Seal 과 KMS 조합
  • Policy 구성
  • LDAP 조합을 봐야 할 거 같은데. LDAP 스터디를 해야하나.
  • Auth Method
  • SSH Key 관리도 해봐야 겠다.
  • Vault 레퍼런스 아키텍처 검토

참고