안녕하세요. 스포카 개발팀의 홍민희입니다. 오늘은 스포카 개발팀에서 SSH 키를 관리하기 위해 만든 오픈 소스 소프트웨어인 Geofront를 소개하겠습니다.

흔히 일어나는 상황

서버를 다뤄야 하는 개발팀에서는 많든 적든 서버에 원격으로 접속해야 할 때가 생깁니다. 원격지에 접속하기 위해 가장 많이 사용되는 도구는 바로 SSH인데요. 원격지에 접속하기 위해서는 접속자가 해당 서버를 사용할 자격을 증명하기 위해 비밀번호나 인증 키를 사용합니다.

하지만 개발팀에서 서버에 접속해야 할 사람이 둘 이상이 될 때부터 원격 서버의 인증 문제는 귀찮음과 보안 의식 사이에서 줄다리기를 시작합니다. 보안에 크게 신경쓰지 않거나, 그것이 중요하지 않다고 여기는 조직에서는 개발팀이 아주 크게 확장되지 않는 한 공용 비밀번호를 하나 정하거나 공용 인증키를 생성하여 개발팀 전체가 공유하게 됩니다.

물론 이런 식의 인증 키 공유는 대표적인 나쁜 관습입니다. 팀이 하나의 인증 키를 공유할 경우, 누군가 팀에서 나갈 때마다 새로운 인증 키를 생성하여 전체 원격 서버와 팀원들에게 배포해야 하는데, 성가시기 때문에 안 하는 조직도 많습니다. 또, 인증 키를 쉽게 공유한다는 명목으로 Dropbox 등에 올려두는 경우도 심심치 않게 발생합니다. 만약 팀과 별개로 원래 자신이 쓰던 인증 키가 있는 경우, 접속할 때마다 -i 옵션으로 팀 공용 키를 지정해야 한다는 귀찮음도 덤으로 생깁니다.

다행히 Git이 대중화되면서 예전보다 더 많은 개발자들이 각자의 인증 키를 만들어서 쓰게 되었습니다. GitHub, Bitbucket 등의 저장소 호스팅 서비스에서 SSH를 통한 저장소 접근을 권장하고, 인증 방법도 각자의 공개키를 계정에 등록하는 방식이기 때문입니다. 원격 서버에서는 접속을 허용할 공개 키 목록을 ~/.ssh/authorized_keys에 나열하는 식으로 관리하는데, 개발팀 각자의 공개키를 여기에 올려두고 관리하는 것은 하나의 인증 키를 팀 모두가 공유하는 것보다는 나은 방법입니다.

하지만 이 방법도 여전히 문제가 있습니다. 일단 팀에 새로운 사람이 들어오거나, 누군가 나가게 될 때마다 authorized_keys 목록을 업데이트해야 한다는 점인데요. 보통 접속할 원격 서버는 한 두개가 아니고, 그 수가 조금만 많아져도 또다시 귀찮은 일이 되고 맙니다. 더군다나 이 귀찮은 업데이트 작업을 팀에서 누가 맡아야 할지도 애매합니다.

아이디어

위에서 묘사한 상황은 스포카 개발팀에서도 겪은 것들이고, 여러 논의를 하게 되었습니다. 전통적으로는 이런 상황을 위한 해결책으로 Kerberos를 설치하여 사용해왔습니다. 하지만 스포카 개발팀처럼 작은 팀에서 쓰기에는 설정이 복잡하여 누군가 세심하게 관리해야 한다는 점이 불편했습니다.

스포카 개발팀은 이 문제를 해결하기 위한 간단한 서비스를 직접 만들기로 했는데, Geofront라고 이름 짓게 된 이 서비스의 작동 아이디어는 다음과 같습니다.

  1. Geofront는 마스터 키를 갖습니다. 마스터 키의 비밀키 부분은 아무에게도 공유되지 않습니다. 마스터 키는 주기적으로 알아서 재생성됩니다.
  2. 모든 원격 서버는 마스터 키만 허용하는 아주 간단한 한 줄짜리 authorized_keys 목록만 갖습니다.
  3. 구성원은 각자의 공개키를 Geofront 서비스에 등록합니다. (단, Geofront 서비스는 공개키 저장소로 GitHub 등을 백엔드로 쓸 수 있는데, 그런 경우에는 각자의 GitHub 계정에 공개키가 이미 등록되어 있을 것이므로 등록은 생략해도 됩니다.)
  4. 구성원이 한 원격 서버에 SSH 접속을 요청하면 Geofront는 일시적으로 (약 30초에서 1분 정도) 그 사람의 공개키를 요청한 원격 서버의 authorized_keys 목록에 추가합니다.

서버 설치 및 설정

Geofront 서비스는 인증을 중계하기 위한 Geofront 서버와, 그 중계 서버에 인증을 요청할 팀원 각자가 설치해야 하는 geofront-cli로 이루어집니다. 우선 서버는 Python으로 작성되어 있고 Python 3.3 이상을 요구합니다. pip를 이용하면 간단하게 설치가 됩니다.

$ pip install Geofront

설치한 뒤에는 서버 설정을 해야 합니다. 설정 파일은 간단한 Python 스크립트 형식이고, 각 항목은 대문자로만 이뤄진 변수로 표현됩니다. 저장소에 포함된 예제 설정 파일을 참고하면 좋습니다. 주요 설정 항목은 다음과 같은 것들이 있습니다.

  • TEAM: (geofront.team.Team) 팀 구성원의 인증을 할 수단을 정합니다. 예를 들어 특정 GitHub 단체 계정의 일원만 허가할 경우 GitHubOrganization 구현을 사용하면 됩니다.

    from geofront.backends.github import GitHubOrganization
    
    TEAM = GitHubOrganization(
        client_id='GitHub OAuth 앱 클라이언트 ID',
        client_secret='GitHub OAuth 앱 클라이언트 시크릿',
        org_login='your_org_name'  #  in https://github.com/your_org_name
    )
    

    Team 클래스를 상속해서 직접 구현하는 방법도 있습니다.

  • REMOTE_SET: (collections.abc.Mapping) Geofront가 인증을 중계할 원격 서버 목록입니다. 이 값은 매핑 객체이기만 하면 어떤 타입이든 상관 없습니다. 예를 들어 Python의 기본 빌트인 dict 자료구조를 써서 하드코딩하는 것도 가능합니다.

    from geofront.remote import Remote
    
    REMOTE_SET = {
        'web-1': Remote('ubuntu', '192.168.0.5'),
        'web-2': Remote('ubuntu', '192.168.0.6'),
        'web-3': Remote('ubuntu', '192.168.0.7'),
        'worker-1': Remote('ubuntu', '192.168.0.25'),
        'worker-2': Remote('ubuntu', '192.168.0.26'),
        'db-1': Remote('ubuntu', '192.168.0.50'),
        'db-2': Remote('ubuntu', '192.168.0.51')
    }
    

    매핑 객체의 모든 키는 문자열이어야 하며, 각 키에 연결된 값은 모두 Remote 객체여야 합니다. Remote는 SSH로 접속할 사용자명, 호스트명, 포트 번호, 이렇게 세 필드로 이루어진 간단한 자료형입니다.

    REMOTE_SET 설정은 dict와 같은 인터페이스의 사제 매핑 객체를 구현해서 동적으로 바뀌게 할 수 있습니다. (표준 라이브러리에 있는 collections.abc.Mapping 믹스인을 쓰면 매핑 객체를 간편하게 구현할 수 있습니다.) 예를 들어 Geofront에 포함된 CloudRemoteSet 클래스는 Mapping 믹스인을 상속하여 AWSEC2 같은 클라우드 상에서 만들어져 있는 인스턴스 목록을 동적으로 가져옵니다. Apache Libcloud 라이브러리 덕분에 AWS 외에도 Azure, ucloud biz 등 20여 가지의 클라우드 서비스에서 쓸 수 있습니다.

    from geofront.backends.cloud import CloudRemoteSet
    from libcloud.compute.types import Provider
    from libcloud.compute.providers import get_driver
    
    driver_cls = get_driver(Provider.EC2_US_WEST)
    driver = driver_cls('access id', 'secret key')
    REMOTE_SET = CloudRemoteSet(driver)
    
  • TOKEN_STORE: (werkzeug.contrib.cache.BaseCache) 각 구성원에게 발급할 토큰을 보관할 저장소를 설정합니다. 이 설정은 Werkzeug의 캐시 저장소 인터페이스를 사용하는데, Werkzeug에서 기본적으로 다음과 같은 구현체를 함께 제공하고 있습니다.

    예를 들어 토큰을 Redis에 저장하려고 한다면 다음과 같이 설정하면 됩니다.

    from werkzeug.contrib.cache import RedisCache
    
    TOKEN_STORE = RedisCache(host='localhost', db=0)
    

    혹은 BaseCache를 상속 받아서 저장소를 직접 구현하는 것도 물론 가능합니다.

  • KEY_STORE: (geofront.keystore.KeyStore) 각 구성원의 공개키를 보존할 저장소를 설정합니다. (마스터 키가 아니니 MASTER_KEY_STORE와 혼동하지 마세요.)

    만약 TEAMGitHubOrganization으로 설정되어 있다면 KEY_STOREGitHubKeyStore로 설정하면 됩니다. 이 저장소 클래스는 각자의 GitHub 계정에 연결된 공개키를 사용합니다.

    DatabaseKeyStore 클래스를 쓰면 SQLite, PostgreSQL, MySQL 같은 관계형 데이터베이스에 공개키를 저장할 수도 있습니다.

    import sqlite3
    from geofront.backends.dbapi import DatabaseKeyStore
    
    KEY_STORE = DatabaseKeyStore(sqlite3,
                                 '/var/lib/geofront/public_keys.db')
    

    Amazon EC2나 Rackspace 같은 몇몇 클라우드 서비스는 키 페어 서비스를 제공하는데요. CloudKeyStore를 쓰면 해당 서비스의 키 페어 서비스를 공개 키 저장소로 쓸 수 있습니다.

    from geofront.backends.cloud import CloudKeyStore
    from libcloud.storage.types import Provider
    from libcloud.storage.providers import get_driver
    
    driver_cls = get_driver(Provider.EC2)
    driver = driver_cls('api key', 'api secret key')
    KEY_STORE = CloudKeyStore(driver)
    
  • MASTER_KEY_STORE: (geofront.masterkey.MasterKeyStore) 마스터 키를 보관할 저장소를 설정합니다. (공개키가 아니니 KEY_STORE와 혼동하지 마세요.)

    마스터 키 저장소는 될 수 있으면 탈취로부터 안전하면서도 분실의 위험도 적어야 합니다. Geofront는 몇가지 내장 구현들을 제공하고 있습니다.

    • FileSystemMasterKeyStore: 이름에서 알 수 있듯 마스터 키를 지정한 위치에 파일로 저장합니다. 별로 안전하지는 않을 수 있지만 Geofront를 한번 설치해서 써보는 데에는 간편한 방법입니다.
    • CloudMasterKeyStore: AWSS3 같은 클라우드 오브젝트 스토리지 서비스에 마스터 키를 보관합니다. Libcloud 덕분에 20여 종류의 클라우드 서비스를 지원합니다.
    from geofront.masterkey import FileSystemMasterKeyStore
    
    MASTER_KEY_STORE = FileSystemMasterKeyStore('/var/lib/geofront/id_rsa')
    

위 설정은 필수 항목들이고, 이 외에도 원격 서버 별로 접근 권한을 조절하는 설정(PERMISSION_POLICY) 등 부가적인 항목들이 존재합니다. 전체 설정 항목은 설정 문서을 참고하세요.

설정 파일을 저장한 뒤(여기서는 설정 파일명을 geofront.cfg.py라고 하겠습니다), Geofront 서버를 실행하기 위해 geofront-server 명령을 사용합니다. 해당 명령은 필수 인자로 설정 파일명을 받습니다.

$ geofront-server -p 8080 geofront.cfg.py

처음 실행하면 다음과 같은 오류와 마주할 수도 있습니다.

$ geofront-server -p 8080 geofront.cfg.py
usage: geofront-server [...] FILE
geofront-server: error: no master key;
try --create-master-key option if you want to create one

이는 설정한 마스터 키 저장소(MASTER_KEY_STORE)에 아직 아무런 마스터 키가 없을 때 나는 오류입니다. --create-master-key 옵션과 함께 실행하면 마스터 키가 아직 없을 경우 알아서 새 마스터 키를 생성하고 저장소에 보관합니다.

$ geofront-server -p 8080 --create-master-key geofront.cfg.py
no master key;  create one...
created new master key: 2b:d5:64:fd:27:f9:7a:6a:12:7d:88:76:a7:54:bd:6a
serving on http://0.0.0.0:8080

성공적으로 서버가 시작하면 위와 같이 연결된 호스트와 포트 번호를 출력합니다. 일반적으로 서버는 이대로 사용하기보다는, 앞쪽에 Nginx 같은 웹 서버를 따로 두고 리버스 프록시로 붙여서 사용하는 경우가 많습니다. 서버는 일반적인 HTTP 프로토콜을 사용합니다.

클라이언트 명령행 도구

Geofront를 통해 인증할 구성원들은 각자 컴퓨터에 geofront-cli라는 명령행 클라이언트 도구를 설치해야 합니다. 서버와 달리 명령행 클라이언트 도구는 Python 2.6 이상을 지원합니다. 마찬가지로 pip를 이용해서 설치가 가능합니다.

$ pip install geofront-cli

클라이언트 도구를 쓰기 위해서는 최초 설정이 필요합니다. 이는 geofront-cli start 명령으로 가능합니다. 실행하면 다음과 같은 프롬프트가 나옵니다.

$ geofront-cli start
Geofront server URL:

서버 URL을 입력하고 나면 웹 브라우저에서 인증 페이지가 열리게 됩니다.

$ geofront-cli start
Geofront server URL: https://geofront-example.org/
Continue to authenticate in your web browser...
Press return to continue

웹 브라우저에서 인증을 마치고 나면(가령 서버에서 TEAMGitHubOrganization로 설정했으면 GitHub 로그인 및 앱 허가 페이지가 뜹니다), 명령행 도구로 돌아가 리턴을 눌러 초기 설정을 마무리합니다. 초기 설정이 끝나고 나면 geofront-cli remotes 명령을 실행하여 Geofront 서버로부터 접속할 수 있는 원격 서버 목록을 출력해 봅시다.

$ geofront-cli remotes
web-1
web-2
...

geofront-cli에 대한 좀더 자세한 정보는 geofront-cli --help 명령이나 README 문서를 보시면 됩니다.

원격 서버 식민지화 (remote colonization)

원격 서버를 목록에 추가한 맨 처음에는 해당 원격 서버가 Geofront의 마스터 키로 접속을 허용하지 않으므로 제대로 동작하지 않습니다. 그래서 맨 처음 원격 서버가 마스터 키만 허용하게끔 authorized_keys를 수정해야 하는데, 이를 식민지화(colonization)라고 합니다. 이 작업은 geofront-cli masterkey -v 명령으로 현재 마스터 키의 공개키를 구한 다음 원격 서버에 직접 접속하여 authorized_keys 목록을 수동으로 수정하는 것으로도 가능합니다. 그러나 geofront-cli는 이를 간편하게 해주기 위한 geofront-cli colonize 명령을 함께 제공합니다.

$ geofront-cli remotes
web-1
web-2
...
$ geofront-cli colonize web-1

geofront-cli colonize 명령은 Geofront를 위한 ssh-copy-id 같은 것이라고 보면 됩니다. 한번 식민지화된 원격 서버는 Geofront로 인증된 구성원이 접속할 수 있게 되며, 마스터 키가 주기적으로 재생성될 때에도 authorized_keys 목록은 함께 업데이트됩니다.

Geofront를 통한 SSH 접속

마침내 모든 준비가 다 끝났습니다. 이제 팀 구성원은 다음 명령으로 원격 서버에 SSH 접속이 가능합니다.

$ geofront-cli ssh web-1
Last login: Sat May  3 16:32:15 2014 from hong-minhees-macbook-pro.local
$

오픈 소스

Geofrontgeofront-cli는 모두 스포카에서 만든 오픈 소스 소프트웨어입니다. GeofrontAGPLv3 혹은 그 이상으로 배포되며, geofront-cliGPLv3로 배포됩니다. 누구나 사용하거나, 고쳐서 재배포할 수 있습니다.

자세한 사용법은 Geofront 서버 문서geofront-cli README를 참고하세요.