Outsider's Dev Story

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

Vault의 SSH 시크릿 백엔드

최근에 Vault에 관한 글을 계속 올렸는데 PostgreSQL 시크릿 백엔드를 살펴보면 다른 시크릿 백엔드가 어떻게 동작하는지 어느 정도 짐작할 수 있다. 처음 Vault를 살펴보려고 할 때 수동으로 관리하는 비밀 정보를 보관하는 부분이었고 PostgreSQL 시크릿 백엔드 같은 부분은 보고 나서는 괜찮다고 생각하지만 이런 기능을 생각하고 있던 것은 아니다.

개발하면서 비밀정보를 관리하려고 할 때 계정정보나 토큰 외에 관리가 어려운 것 중 하나가 SSH 키이다. 서버에 접속하는 용도로 대부분 SSH 키를 사용하는데 이 파일은 서버에 접속해야 하는 개발자 PC에 저장되어 있어야 하므로 관리가 몹시 어렵다. 큰 회사의 경우에는 서버접속을 위한 게이트웨이 서버를 두고 접속 계정을 따로 관리하기도 하지만 그 정도 규모가 아니면 직접 SSH 키를 관리하게 된다. SSH 키의 사용은 어쩔 수 없으므로 AWS 같은 곳에서 만든 비밀키를 개발자들이 나누어 가지거나 비밀키는 각 개발자의 비밀키를 사용하고 공개키를 서버의 authorized_keys에 등록해서 접속할 수 있게 해야 한다.

두 방법 중 어느 쪽을 사용하더라도 관리가 잘 안 되는 건 매한가지이다. 비밀키를 공유한 경우에는 퇴사하거나 하는 경우 비밀키를 뺏을 수도 없는 노릇이고 authorized_keys 키는 개발자 변경에 따라서 계속 변경하기가 쉽지 않고 금세 방치되고 만다. 그렇다고 개발자가 서버 접속해서 뭔가를 했다는 얘기를 들은 적은 없지만, 보안에 문제가 있는 것은 사실이고 서버 접속용 비밀키가 밖으로 유출되는 건 꽤 민감한 부분이다.

Geofront

현재 일하면서도 SSH 키 관리를 고민하면서 처음에는 spoqa에서 만든 Geofront를 고려했다.1 Geofront에 각 개발자의 공개키를 등록하고 서버 접속을 요청했을 때 임시로 해당 서버 authorized_keys에 공개키를 등록해서 접속할 수 있게 하는 방식이다. 필요할 때 Geofront에서 공개키를 제거하면 되므로 관리도 쉬워진다. 전에 공개할 때 봐서 알고 있는 도구였고 구현한 방식도 사용하기 괜찮아 보였기 때문에 고려대상 1순위였다. 그리고 SSH 키 관리에 대한 이슈는 어느 회사에나 있을 것 같은데 (검색을 잘못했는지 몰라도) 뜻밖에 이런 류의 오픈소스 프로젝트가 많지 않았다.

Vault의 SSH 시크릿 백엔드

비슷한 시기에 Vault도 살펴보고 있었기에 Vault에서 제공하는 SSH 시크릿 백엔드에 자연히 관심이 갔다. Vault를 사용한다는 전제하에 도구를 2개를 사용하는 것보다는 한곳에서 모두 관리하는 게 좋다고 생각했기 때문이다. Vault에도 SSH 시크릿 백엔드를 제공하고 있어서 원격 호스트 접속에 동적으로 SSH 인증을 제공하고 있다.

  1. 일회성 비밀번호(OTP) 방식
  2. 동적 키 방식

Vault는 위 2가지 방식을 제공하는데 문서만 봐서는 어떻게 동작하는지 잘 이해하기가 어려워서 직접 설정을 해보았다.

먼저 Vault 서버가 http://172.10.1.103:8200에 있다고 했을 때 SSH 시크릿 백엔드를 사용하기 위해 먼저 마운트를 한다.

$ vault mount ssh
Successfully mounted 'ssh' at 'ssh'!

$ 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
ssh/        ssh        system       system
sys/        system     n/a          n/a      system endpoints used for control, policy and debugging



ssh/에 마운트된 것을 볼 수 있다.

일회성 비밀번호(OTP, One-Time-Password) 방식

일회성 비밀번호를 서버 접속을 요청할 때마다 Vault가 일회성 비밀번호를 알려주고 이 비밀번호를 입력해서 접속하는 방식이다.

vault-ssh-helper 설정

SSH 시크릿 백엔드를 사용하려면 먼저 Vault를 이용해서 접속할 vault-ssh-helper를 설치해야 한다. AWS를 사용한다면 AMI에 미리 설치해서 모든 서버에 자동설치되게 하면 좋을 것이다. vault-ssh-helper도 Go로 작성되었는데 HashiCorp의 릴리스 페이지에서 빌드된 버전을 다운받을 수 있다.(현재 버전은 v0.1.3) 다운받아서 압축을 풀면 vault-ssh-helper 파일이 나오는데 이 파일을 PATH에 추가하고 다음과 같은 설정 파일을 작성한다. 여기서는 config.hcl이라고 하겠다.

vault_addr = "http://13.112.247.219:8200"
ssh_mount_point = "ssh"
tls_skip_verify=true
allowed_cidr_list="0.0.0.0/0"
allowed_roles="otp_key_role"

설정값은 문서를 참고하면 되는데 간단히 설명하면 vault_addr는 사용하는 Vault 서버의 주소이고 ssh_mount_point는 앞에 Vault 서버에서 SSH 시크릿 백엔드를 마운트한 경로이다. 이 두 값을 필수값이다. tls_skip_verify는 데모용이라 TLS을 껐으므로 TLS 확인을 건너뛰도록 한 것이다. 실제 환경이라면 양쪽에서 모두 TLS를 키거나 내부 네트워크를 사용할 때만 이 옵션을 사용해야 한다. allowed_cidr_list는 이 서버에 접속 가능한 CIDR이고 접속할 때 IP가 CIDR 블록에 포함되어 있지 않다면 접속할 수 없다. allowed_roles는 Vault에서 서버에 접속을 허용할 role 이름이고 여럿을 지정하려면 콤마로 구분하면 된다. 이 말은 필요에 따라 서버군별로 role을 나눌 수도 있다는 의미이다.

설정이 잘 되었는지 확인해 보자.

$ vault-ssh-helper -verify-only -config=config.hcl -dev
2017/02/14 16:01:19 ==> WARNING: Dev mode is enabled!
2017/02/14 16:01:19 [INFO] using SSH mount point: ssh

-verify-only는 설정이 유용한지 확인만 할 때 사용하는 옵션이고 위에서 작성한 config.hcl 파일을 설정으로 지정했다. 마지막에 -dev를 사용한 건 TLS를 껐으므로 개발 모드로 실행한 것이다. vault-ssh-helper가 뚫리면 서버가 열리는 것이므로 이 부분이 개발 모드로 강제된 것으로 보이고 TLS를 사용하려면 설정 파일에서 ca_certca_path로 인증서를 지정해주어야 한다.

여기서 끝이면 좋겠지만, SSH 접속을 할 때 vault-ssh-helper를 사용하도록 하는 설정을 추가해주어야 한다.

/etc/pam.d/sshd 파일을 열어서 상단의 내용을 다음과 같이 수정한다.

# Standard Un*x authentication.
#@include common-auth
auth requisite pam_exec.so quiet expose_authtok log=/tmp/vaultssh.log /home/ubuntu/vault-ssh-helper -config=/home/ubuntu/config.hcl -dev
auth optional pam_unix.so not_set_pass use_first_pass nodelay

표준 리눅스 인증 모듈인 @include common-auth부분을 주석 처리하고 그 아래 2줄을 추가했다. 이는 인증을 할 때 vault-ssh-helper를 실행하도록 한 것이다.(/home/ubuntu/vault-ssh-helper -config=/home/ubuntu/config.hcl -dev)

/etc/ssh/sshd_config 파일도 열어서 다음 부분을 찾아서 아래와 같이 수정한다.

ChallengeResponseAuthentication yes
UsePAM yes
PasswordAuthentication no

Ubuntu 16.04에서는 ChallengeResponseAuthenticationUsePAM는 이미 있었고 PasswordAuthentication는 새로 추가했다.

설정이 완료되었으므로 sudo systemctl restart ssh로 SSH를 재시작한다.

Vault의 OTP를 이용한 서버 접속

먼저 Vault에서 OTP로 접속할 role을 생성한다.

$ vault write ssh/roles/otp_key_role \
  key_type=otp \
  default_user=ubuntu \
  cidr_list=0.0.0.0/0
Success! Data written to: ssh/roles/otp_key_role

위에서 otp_key_role로 지정했으므로 ssh/roles/otp_key_role에 새로운 Role을 만드는데 key_typeotp이고 데모에서는 Ubuntu 서버를 사용하므로 default_user로 기본 계정명인 ubuntu를 지정했다. cidr_list은 이 role이 인증을 만들 수 있는 CIDR 블록이다.

이제 접속할 서버에 대한 인증을 만들어야 한다.

$ vault write ssh/creds/otp_key_role ip=13.113.64.123
Key             Value
---             -----
lease_id        ssh/creds/otp_key_role/14fc4dc1-9dae-3079-fedc-670ac19195ae
lease_duration  768h0m0s
lease_renewable false
ip              13.113.64.123
key             e5d60719-b1d4-d6f8-a67f-42da198d950e
key_type        otp
port            22
username        ubuntu

인증 경로는 ssh/creds/otp_key_role가 되고 여기서 ip는 접속할 서버의 IP이다. 이때 이 IP는 앞에서 지정한 otp_key_role의 CIDR 블록에 포함되어야 한다.

접속할 때는 vault를 이용해서 vault ssh -role otp_key_role ubuntu@13.113.64.123와 같이 접속한다. OTP라고 하니까 일회성 비밀번호를 어디서 알려주나 싶지만, 아래와 같이 명령을 실행하면 바로 OTP를 알려준다. 여기서는 ac49f288-ca82-2c60-2567-190bd2a97c0d가 비밀번호이고 복사해서 비밀번호에 붙여주면 서버에 접속할 수 있다.

$ vault ssh -role otp_key_role ubuntu@13.113.64.123
OTP for the session is ac49f288-ca82-2c60-2567-190bd2a97c0d
[Note: Install 'sshpass' to automate typing in OTP]
Password:
Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-59-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.


Last login: Tue Feb 14 15:36:15 2017 from 175.193.64.184
ubuntu@ip-172-31-29-20:~$


동적 키 방식

동적 키 방식은 Geofront의 방식과 비슷하다고 생각하는데 서버관리용 비밀키를 Vault에 등록하고 개발자가 인증을 요청하면 새로운 SSH 키를 생성한 뒤 비밀키는 개발자에게 알려주고 서버의 authorized_keys에 공개키를 등록해주어 접속할 수 있게 하는 방식이다.

서버 설정

각 서버의 설정은 훨씬 간단한 편이다. /etc/sudoers 파일을 열어서(sudo visudo -f /etc/sudoers) 다음 내용을 추가한다.

vaultadmin   ALL=(ALL)NOPASSWD: ALL


Vault에서 SSH 키 발급받기

서버에서 사용하는 마스터 SSH 키를 Vault에 등록한다. 이 키는 서버에 어드민 권한이 있어야 한다.

vault write ssh/keys/dynamic_key key=@test-key.pem
Success! Data written to: ssh/keys/dynamic_key

여기서는 dynamic_key라는 이름으로 등록했고 test-key.pem는 마스터키의 파일명인데 현재 위치에 해당 파일이 존재해야 한다.

$ vault write ssh/roles/dynamic_key_role \
  key_type=dynamic \
  key=dynamic_key \
  admin_user=ubuntu \
  default_user=ubuntu \
  cidr_list=0.0.0.0/0
Success! Data written to: ssh/roles/dynamic_key_role

OTP 때와 같이 role을 만든다. 여기서 admin_user는 Vault가 서버에 접속할 때 사용하는 계정명이고 key는 위에서 설정한 키 이름을 지정하면 된다.

이제 해당 role에서 접속할 서버의 IP로 인증서를 발급받는다.

$ vault write ssh/creds/dynamic_key_role ip=13.112.193.255
Key             Value
---             -----
lease_id        ssh/creds/dynamic_key_role/7182734d-ab58-ba0b-8efe-98d01f71e58f
lease_duration  768h0m0s
lease_renewable true
ip              13.112.193.255
key             -----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCwa1gZT4FincS86tlNSNL2J3VGO6YQMICQOI4DWB5hKtHc7TOZ
odXlb4C61b73KAUN/bHwX1W7aVaQ5MM2Vp6EVkpX9odq/koXj/SkwcdQMTCRv8SY
0ZMhWbv/IY/wQgg8nJI3mvigPI5b7suOEzUPR61UKZHnXjS6kORxmuUT2wIDAQAB
AoGAdh446yFfSI7HVZGMEoGqtaKvk2mGgxpmSamD89tA49/OiTPLs5Y2ZxpjvzQz
WrnRwI9WXtEFzqf1jKeNyEjwch/cOzvggL0ECcp8HeY9QU2tEcLyvriO/9lbeqFG
iNSAmMJijHRgbkqVbhHaJMNT5/xLnn3E6B7ssTeMR8MoetkCQQDT52NvIJ5IURp+
f2CqjcuFzCpJOsEYSq8pEODPLxIStVCalcjNxWMdwAQYPho6IQ4kda5Uwg3NJrGz
yReVRN8dAkEA1SGcfuKXrjbije6LrjCxl+vU3EpMV6MehPcafXmQRj59KdUADoVU
CABtFy1RgsNAR7xxHGg/D079+ESiAIl1VwJALSt7xKp9UwkGzsQ0RObo5WJ5+RYv
JxB0ehqA8WklPxurTOh033geAq91r/089fsp2pfDS4n6CyseYiaRgl4l+QJAfVns
YhBBJ7yuGM4RJx0KhpC0u++S4QRWQdvXn66stTOxh7X395JhLueZQcVsqFzP5KEn
YY7Kb+WEp80t/uTZtwJATa35+aKg7uJggCvYXUSLMM4mEi43Xq47Z7BJEOBTJomo
zBwWP5N+iK0JC/Qkj2WCpf0UgwmntO6JKXn//AlADA==
-----END RSA PRIVATE KEY-----
key_type        dynamic
port            22
username        ubuntu

여기서 key로 나온 부분이 동적으로 발급된 내 비밀키이다. 이 키의 내용을 demo라는 파일에 저장했다면 이 비밀키로 SSH 접속을 하면 서버에 접속할 수 있다.

$ ssh -i demo ubuntu@13.112.193.255
Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-59-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.


Last login: Tue Feb 14 16:39:45 2017 from 175.193.64.184
ubuntu@ip-172-31-16-78:~$

테스트해보니 다른 서버에 키를 요구하면 다른 키가 발급된다. 접속할 때마다 발급받기는 귀찮아 보이고 서버마다 키를 다르게 사용한다는 건 현실적으로 말이 안되서 사실 이 방식은 어떻게 쓰라는지 잘 모르겠다. 그리고 Vault가 서버 접속에는 관여하지 않으므로 Vault의 Audit 로그가 서버 접속까지 감시하지는 못한다.

정리

문서에서는 가능하면 OTP 방식을 권장하고 있다. 동적 키의 경우 테스트해보니 서버마다 키가 새로 발급되므로 실 사용사례가 잘 이해가 되지 않는다. SSH 접속방식은 기존과 같지만 접속할 때마다 발급받아 사용하는 건 귀찮을 테고(만료시간을 짧게 준다면 아마도 이 방식) 서버마다 키를 다르게 쓴다는 건 실무에서 말이 되지 않는다고 생각한다. 그리고 동적 키 방식은 Vault가 인증키 발급에만 관여하고 서버 접속에는 관여하지 않기 때문에 Vault의 Audit 로그가 서버접속을 감시할 수가 없다.


  1. 스포카에서 작성한 https://spoqa.github.io/2014/07/09/geofront.html 참고. 

2017/02/15 02:13 2017/02/15 02:13