구리의 창고

Python - HTTP Digest Authentication 서버, 클라이언트 구축 (HTTPDigestAuthHandler) 본문

Python

Python - HTTP Digest Authentication 서버, 클라이언트 구축 (HTTPDigestAuthHandler)

구리z 2017. 9. 20. 10:57

머리글

HTTP 서비스를 구축 할 때, 외부에 공개되지 않도록 하기위해 인증 시스템을 도입하는 경우를 생각해보자. HTTP 프로토콜에서 기본으로 제공하는 인증 시스템이 있는데 가장 간단한 방법은 Basic Authentication이다. 이 방법은 아이디/암호를 통해 인증을 하도록 되어있는데, 만약 TLS(HTTPS) 설정이 되어있지 않다면 plain text가 노출 돼 스니핑을 당해서 아이디/암호가 노출 됐을 때, 다른 시스템까지 보안에 취약해 질 수 있어서 위험하다. 모든 서버에 TLS를 설정하면 된다고는 하지만 속도나 비용문제 등을 생각 했을 때 항상 옳은 법은 아니다. 그래서 HTTP Digest Authentication이란 인증 방법을 사용해보려고 한다.

설명

HTTP Digest Authentication의 자세한 설명은 다른 아티클에 있으니 아주 간단한게 설명하고 코드 위주로 정리 할 것이다. 

1. 클라이언트가 서버에 접근 요청을 하면 Basic Authentication과 같이 401을 보냄. 이 때 헤더에 인증이나 필요한 암호화 알고리즘과 키 값을 포함해서 보냄.
2 클라이언트에서는 아이디와 암호를 서버에서 받은 키 값과 암호화 알고리즘을 이용하여 인증 요청
3. 서버에서 설정된 값이 맞는지 확인

간단하게 말하면 인증 요청 시, 서버에서 임시 키 값을 발급하고 클라이언트에서는 그 키 값을 조합해 암호화 된 값을 전송함으로써 plain text가 노출되어도 replay attack까지도 방지가 된다는 의미이다.

서버 구현 with NGINX

 가장 많이 사용하는 웹 서버 엔진으로 apache2와 nginx를 들 수 있는데, 이 중 nginx를 이용해 구축하도록 할 것이다. 아직 HTTP Digest Authentication 모듈은 NGINX에 공식적으로 들어가있지 않으며 github repository에서 직접 다운 받아 설치해야한다. 기본 패키지에 포함되어있지 않아 nginx를 아래처럼 소스컴파일을 해야한다.

1) NGINX 설치

cd /usr/local/src
wget http://nginx.org/download/nginx-1.12.1.tar.gz
tar zxf nginx-1.12.1.tar.gz
git clone https://github.com/samizdatco/nginx-http-auth-digest.git
cp nginx-http-auth-digest/htdigest.py ~/htdigest.py
cd nginx-1.12.1
./configure --add-module=/usr/local/src/nginx-http-auth-digest
make && make install

2) 암호 생성

위 명령어 중 htdigest.py를 복사하는 걸 볼 수 있는데, 아이디와 암호를 생성해 파일로 만들어주는 유틸리티다. 생성을 위해서는 아이디, 암호, Realm이 필요하다. 여기서 입력하는 모든 정보는 다음 과정에서 설정 할 nginx.conf과 일치해야한다. 여기서 아이디는 guri, 암호는 1234, realm은 Guri Blog라고 입력하겠다.
$ python htdigest.py passwd guri 'Guri Blog'
/home/guri/git-repo/docker-nginx-with-vts/passwd does not exist. Create it? (y/n) y
Password for new user "guri":
Please repeat the password:
$ cat passwd
guri:Guri Blog:0247e8108bda8bd62baf454d636410b6

3) nginx.conf

127.0.0.1:3000 HTTP 서버 Reverse Proxy와 같이 설정한다고 가정하겠다. 설정에 필요한 것만 최소로 남기면 아래와 같다.
worker_processes 4;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;
    gzip  on;

    server {
        listen       80;
        server_name  localhost;

        auth_digest_user_file /usr/local/etc/nginx/passwd;
        auth_digest_shm_size 4m;

        location / {
                auth_digest 'Guri Blog';
                auth_digest_timeout 60s; # 인증 정보를 받기전까지 기다리는 시간
                auth_digest_expires 10s; # 인증 후 또는 재사용 가능한 간격 시간
                auth_digest_replays 20; # 재사용 가능 횟수
                auth_digest_maxtries 5; # Status code가 200이 아닐 시 최대 시도 횟수
                auth_digest_evasion_time 300s; # auth_digest_maxtries가 넘었을 때 서버에서 Deny 하는 시간

                proxy_pass http://127.0.0.1:3000;
        }
    }
}

클라이언트 구현 with Python

urllib2.HTTPDigestAuthHandler를 사용 할 것인데 사실 코드가 너무 간단해서 구현이라고 볼 것도 없다.
import urllib2


def main():
    passwd_manager = urllib2.HTTPPasswordMgr()
    passwd_manager.add_password('Guri Blog', 'http://127.0.0.1/', 'guri', '1234')

    digest_auth_handler = urllib2.HTTPDigestAuthHandler(passwd_manager)

    opener = urllib2.build_opener(digest_auth_handler)
    urllib2.install_opener(opener)

    try:
        res = urllib2.urlopen('http://127.0.0.1/')
        print res.read()
    except urllib2.HTTPError as e:
        print e


if __name__ == '__main__':
    main()
HTTPPasswordMgr를 사용하기 싫으면 HTTPDigestAuthHandler에 바로 아래와 같이 설정해 줄 수도 있다
digest_auth_handler.add_password(realm='Guri Blog', uri='http://127.0.0.1/', user='guri', passwd='1234')

확인

이제 127.0.0.1:3000에 HTTP서버를 띄어놓고 테스트를 해보자.




























Comments