Outsider's Dev Story

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

HashiCorp의 비밀정보 관리 도구 Vault의 사용

HashiCorp의 비밀정보 관리 도구 Vault의 구성에서 Vault의 기본적인 개념과 서버를 구성하는 방법을 살펴보았다. 이 설명도 꽤 길었지만, 아직 Vault를 어떻게 사용하는지 감이 잘 안 올 것이라고 생각하는데 이번에는 실제로 Vault를 어떻게 사용하는지 살펴보자.

Vault 서버에서 데이터 읽고 쓰기

앞에서 초기화를 할 때 root token이라는 것을 하나 받았다. 이는 Vault 서버의 루트 권한이 있는(서버의 모든 작업을 할 수 있고 sealed 상태로 바꿀 수도 있지만, unseal을 할 수는 없다.) 토큰이다. 일단 이 토큰으로 Vault를 사용해 보자. 토큰은 VAULT_TOKEN 환경변수에 저장하면 vault 클라이언트에서 사용할 수 있다.

$ export VAULT_TOKEN=c0ccbf61-cd7b-bd98-dabe-f319247cedd8

앞에서 비밀정보를 저장하려면 시크릿 백엔드를 만들어야 한다고 했는데 이 시크릿 백엔드를 파일시스템처럼 마운트해서 사용한다. 마운트를 하면 경로가 생성되고 그 경로에 원하는 정보를 저장하면 된다. 기본적으로 마운트된 백엔드가 있으니 vault mounts로 확인해 보자.

$ vault mounts
Path        Type       Default TTL  Max TTL  Description
cubbyhole/  cubbyhole  n/a          n/a      per-token private secret storage
secret/     generic    system       system   generic secret storage
sys/        system     n/a          n/a      system endpoints used for control, policy and debugging

cubbyhole/는 토큰마다 생성되는 스토리지로 연습용이라고 생각하면 된다. secret/gerneric 타입이라고 되어 있는데 이는 generic 시크릿 백엔드라는 의미이고 바로 사용을 할 수 있도록 기본적으로 하나가 있는 것이다. 추가설명을 하자면 generic 시크릿 백엔드는 secret 경로에 마운트한 것이다. sys/는 시스템용으로 ACL 정책이나 관리 정보가 여기에 저장된다.

secret에 시크릿 백엔드가 마운트되어 있으므로 여기에 구분하기 쉬운 경로를 생성해서 vault write로 데이터를 저장하면 된다.

vault write secret/database/postgres host=127.0.0.1 \
  port=5432 db=test username=postgres password=postgres \
  desc="테스트용 데이터베이스"
Success! Data written to: secret/database/postgres

데이터를 읽어올 때는 vault read 데이터가 저장된 경로를 지정하면 된다.

$ vault read secret/database/postgres
Key               Value
---               -----
refresh_interval  768h0m0s
db                test
desc              테스트용 데이터베이스
host              127.0.0.1
password          postgres
port              5432
username          postgres

$ vault read -format=json secret/database/postgres
{
  "request_id": "f0775db9-82d9-2fa2-5052-e999a0b3eed2",
  "lease_id": "",
  "lease_duration": 2764800,
  "renewable": false,
  "data": {
    "db": "test",
    "desc": "테스트용 데이터베이스",
    "host": "127.0.0.1",
    "password": "postgres",
    "port": "5432",
    "username": "postgres"
  },
  "warnings": null
}

결과를 JSON으로 받고 싶다면 -format=json 옵션을 추가하면 된다.

원하는 경로를 찾기가 어려울 때는 vault list를 사용하면 하위에 있는 경로를 볼 수 있다.

$ vault list secret
Keys
----
database/

$ vault list secret/database
Keys
----
postgres

데이터 관리를 쉽게 할 수 있도록 새로운 시크릿 백엔드를 연결하고 싶다면 vault mount를 사용하면 된다. 다음은 account/ 경로에 generic 시크릿 백엔드를 연결한 것이다.

$ vault mount -path=account generic
Successfully mounted 'generic' at 'account'!

$ vault mounts
Path        Type       Default TTL  Max TTL  Description
account/     generic    system       system
cubbyhole/  cubbyhole  n/a          n/a      per-token private secret storage
secret/     generic    system       system   generic secret storage
sys/        system     n/a          n/a      system endpoints used for control, policy and debugging

ACL 정책

여태까지는 루트 토큰을 사용했는데 루트 토큰은 권한이 너무 많으므로 일반적인 사용사례를 생각하면 이 토큰은 관리용으로 보관해 두고 개인이나 애플리케이션별로 토큰 등을 발급해서(혹은 인증 백엔드로 발급할 수 있게 해서) 사용할 것이다. 그러자면 ACL을 설정해서 사용자가 접근 가능한 정보만 볼 수 있도록 해야 한다. Vault에서는 이를 정책(policy)이라고 부른다.

$ vault policies
default
root

기본으로 생성된 정책은 rootdefault가 있다.

새로운 권한을 만들려면 먼저 정책을 hcl 파일(혹은 JSON 파일)로 작성해야 한다. 아래의 내용으로 policy.hcl 파일을 만들어 보자.

path "account/*" {
  capabilities = ["create", "update", "read"]
}

이는 account/ 하위의 모든 경로에서 생성, 수정, 읽기가 가능하다는 정책이다. capabilities에는 다음의 내용

  • create: 경로에 값을 생성한다.
  • read: 경로의 값을 읽는다.
  • update: 경로의 값을 바꾼다.
  • delete: 경로의 값을 제거한다.
  • list: 경로에서 키의 이름을 읽는다. list로 조회할 때 반환되는 키 정보는 정책에 따라 필터링 되지 않으므로 키 이름에 민감한 정보를 넣지 않도록 조심해야 한다.
  • sudo: root로 보호된 경로를 얻는다. sudo는 특수 기능을 부가하는 것이므로 별도로 read, update 등을 지정해 주어야 한다.
  • deny: 해당 경로에 접근을 차단한다. 이는 sudo나 다른 설정과 관계없이 모두 차단한다.

앞에서는 path를 하나만 설정했지만 여러 개 설정해서 복잡한 경로에도 특정 부분만 권한을 주거나 권한을 빼거나 할 수 있다. 이제 앞에서 만든 policy.hcl 파일을 이용해서 정책을 생성해보자.

$ vault policy-write owner policy.hcl
Policy 'owner' written.

$ vault policies
owner
default
root

위 명령어는 owner라는 이름으로 정책을 생성한 것이다. 구분하기 쉽게 정책 이름을 잘 정하면 좋고 정책이 잘 적용된 것을 볼 수 있다.

인증 백엔드

앞에서도 설명했지만 인증 백엔드를 사용하려면 새로운 인증을 추가해야 한다. Vault에서 제공하는 인증 백엔드 중에 자신에게 적합한 타입을 골라서 사용하면 되고 꼭 하나만 사용해야 하는 것은 아니다.

$ vault auth -methods
Path    Type   Default TTL  Max TTL  Description
token/  token  system       system   token based credentials

vault auth -methods 명령어로 사용 중인 인증 백엔드의 목록을 볼 수 있다. 여태까지 토큰 인증 백엔드는 기본적으로 포함되어 있다. 새로운 토큰을 발급받으려면 vault token-create를 사용하면 된다.

$ vault token-create
Key             Value
---             -----
token           681b522a-5689-519f-bde0-3cddf334f35a
token_accessor  91e7588f-a0a0-70aa-6cca-0583fc2f8cbe
token_duration  0s
token_renewable false
token_policies  [root]

token의 토큰을 사용하면 되고 token_accessor의 용도는 정확히 모르겠다. 여기서는 현재 사용 중인 토큰이 루트 토큰이므로 같은 정책인 root 정책이 적용된 것을 볼 수 있다. 앞에서 만든 owner 정책으로 토큰을 생성하려면 -policy 옵션을 사용하면 된다.

vault token-create -policy=owner
Key             Value
---             -----
token           276fbe3f-4026-d054-8eae-c2897ecba1b8
token_accessor  8548431b-95ef-db7b-2c9d-fb8b6c57a566
token_duration  768h0m0s
token_renewable true
token_policies  [default owner]

여태까지는 VAULT_TOKEN 환경변수로 토큰을 지정했지만 vault auth 명령어로 로그인할 수도 있다.

$ vault auth 276fbe3f-4026-d054-8eae-c2897ecba1b8
Successfully authenticated! You are now logged in.
token: 276fbe3f-4026-d054-8eae-c2897ecba1b8
token_duration: 2764532
token_policies: [default owner]

이 토큰은 account/에 권한이 있는 owner 정책이 적용된 토큰이므로 실제로 사용을 해보자.

$ vault write account/twitter username=outsideris password=test
Success! Data written to: account/twitter

$ vault read account/twitter
Key               Value
---               -----
refresh_interval  768h0m0s
password          test
username          outsideris

목록 조회 기능이나 다른 경로를 사용하려고 하면 권한 거부가 일어나는 것을 볼 수 있다.

$ vault list account
Error reading account/: Error making API request.

URL: GET http://127.0.0.1:8200/v1/account/?list=true
Code: 403. Errors:

* permission denied

$ vault read secret/database/postgres
Error reading secret/database/postgres: Error making API request.

URL: GET http://127.0.0.1:8200/v1/secret/database/postgres
Code: 403. Errors:

* permission denied

추가로 새로운 인증 백엔드를 추가해보자. 여기서는 간단한 사용자명/비밀번호 인증 백엔드를 추가해 보겠다. owner에는 백엔드를 추가할 기능이 없으므로 앞에서 사용하던 루트 토큰을 다시 사용해보자.

$ vault auth-enable userpass
Successfully enabled 'userpass' at 'userpass'!

userpass 인증 백엔드가 활성화되었으므로 이를 통해 새로운 사용자를 추가할 수 있다.

$ vault write auth/userpass/users/outsider password=asdf policies=owner
Success! Data written to: auth/userpass/users/outsider

이제 생성한 계정으로 로그인을 해보자. vault auth만 사용하면 토큰 인증이므로 -method=userpass를 지정해야 한다.

$ vault auth -method=userpass username=outsider password=asdf
Successfully authenticated! You are now logged in.
The token below is already saved in the session. You do not
need to "vault auth" again with the token.
token: ae75f594-70a0-ae40-af7d-2449e0d1f46f
token_duration: 2764800
token_policies: [default owner]

성공적으로 인증이 되고 토큰이 발급된 것을 볼 수 있다.

에필로그

최근에 새로운 도구를 여러 가지 만져보고 있지만, Vault는 꽤 맘에 드는 것 중 하나이다. 실제로 사용하려면 경로 정책도 정해야 하고 개발단계나 CI, 배포 등에서 사용하기 위해 Vault와 통합하는 작업을 해야 하지만 보안에 집중한 도구라서 이 부분에서 신뢰가 가고 사용성도 상당히 고려했다는 느낌이 든다. 여기서는 모든 명령어를 vault 클라이언트를 사용했지만, 이 모든 기능은 RESTful API가 동시에 제공되기 때문에 용량이 꽤 되는 Vault를 설치하기 부담되는 곳에서도 curl 등으로 정보를 얼마든지 조회할 수 있다. 설명이 꽤 길었지만, 상당히 잘 만들어진 도구라고 생각한다.

보안이 중요하다는 부분에는 누구나 동의하겠지만, 보안을 강화하려면 노력과 비용이 많이 들고 대부분은 이 노력과 비용을 실무자한테 전가하기 때문에 불편한 보안 장벽 내에서 생산성을 내기 위해서 오히려 보안이 약해지는 문제가 생긴다고 본다. Vault 같은 도구로 개발환경이나 CI, 배포에 잘 통합을 한다면 기존과 거의 차이 없는 환경에서 비밀번호를 주기적으로 바꾼다거나 실수로라도 외부에 유출되지 않도록 잘 관리한다거나 하는 것이 가능할 것이라고 본다.

2017/01/18 08:30 2017/01/18 08:30