최근에 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 인증을 제공하고 있다.
- 일회성 비밀번호(OTP) 방식
- 동적 키 방식
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_cert
와 ca_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에서는 ChallengeResponseAuthentication
와 UsePAM
는 이미 있었고 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_type
은 otp
이고 데모에서는 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 로그가 서버접속을 감시할 수가 없다.
-
스포카에서 작성한 https://spoqa.github.io/2014/07/09/geofront.html 참고. ↩
안녕하세요. OTP인증방식에서 접속할 서버에 대한 인증을 만드는 부분에서, IP를 여러개 설정하려면 어떻게 해야하나요?
한번에 여러 서버에 접속하시려는 건가요? 해보진 않았는데 따로 명령어를 실행해야 할것 같은데요.
[hadoop@KERPI-03 ~]$ cat /etc/pam.d/sshd
#%PAM-1.0
auth required pam_sepermit.so
##auth include password-auth
auth requisite pam_exec.so quiet expose_authtok log=/tmp/vaultssh.log /usr/local/vault_config/vault-ssh-helper -config=/usr/local/vault_config/vault-ssh-helper_conf/ssh_config.hcl -dev
auth optional pam_unix.so not_set_pass use_first_pass nodelay
account required pam_nologin.so
account include password-auth
password include password-auth
# pam_selinux.so close should be the first session rule
session required pam_selinux.so close
session required pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session required pam_selinux.so open env_params
session required pam_namespace.so
session optional pam_keyinit.so force revoke
##session include password-auth
centos6라서 이렇게 설정했는데요.
OTP비밀번호 인식을 못하는거 같습니다.
OTP 입력해도 정상로그인이 안되네요.
ssh-copy-id로 public 키를 넘겨주었는데,,ssh-pass가 된상태에서 여태 OTP로 정상로그인되는걸로 착각하고 있었네요.
ssh-pass 지우고 테스트해보니 OTP가 뜨긴뜨는데,,패스워드에 OTP입력하면 로그인이 안되네요..
ㅠㅠ..아웃사이더님의 도움이 절실합니다.
public-key공유안했을때 오류.
[hadoop@XXXXXX ~]$ vault ssh -role=otp_key_role hadoop@XXX.XXX.XXX.XXX
WARNING: No -mode specified. Use -mode to tell Vault which ssh authentication
mode to use. In the future, you will need to tell Vault which mode to use.
For now, Vault will attempt to guess based on the API response. This guess
involves creating a temporary credential, reading its type, and then revoking
it. To reduce the number of API calls and surface area, specify -mode
directly. This will be removed in Vault 0.11 (or later).
Permission denied, please try again.
failed to run ssh command: exit status 5
vault ssh -v해서 정상디버그 로그 좀 보고 싶습니다.
음... 지금은 제가 OTP가 설정된 Vault가 없어서 테스트해보려면 주말 정도가 될 것 같긴 합니다. 저도 새 Vault 버전으로 테스트를 다시 해보고 싶은데 주말에 제가 해볼 시간이 있다고 장담은 못드리겠네요. 노력은 해보겠습니다.
vault-ssh-helper를 배포노드에 전부 설치하니 모두 해결되었습니다. 좋은 글 감사합니다. 90프로 정보에 10프로의 노력으로 문제해결했습니다. 감사합니다.
제가 요즘 바빠서 테스트해보지 못했는데 해결되셨다니 다행이네요. 이번 기회에 저도 시간나는대로 테스트해보고 새로 정리해봐야겠네요. OTP말고 CA로 로그인하는 기능도 추가되었더라고요.