웹에서 HTTPS는 보안을 위해서 기본적으로 지원해야 하는 부분이다. 구글에서도 작년부터 HTTPS를 지원 여부를 사이트의 신뢰할 수 있다는 척도로 판단하고 검색 순위에서 올리겠다고 발표했다. 이 블로그에서도 HTTPS를 붙이고 싶었지만, SSL 인증서를 구매해야 하다 보니 개인 블로그에서는 부담돼서 적용을 못 하고 있었다.
Let's Encrypt
Lets' Encrypt는 HTTPS를 사용하기 위해 SSL을 구매해야 하는 부분이 HTTPS 보급에 방해된다고 생각해서 SSL을 무료로 제공해서 HTTPS를 보급하기 위해 작년 말에 만들어졌다. 초기에는 Mozilla, Cisco, Akamai, EFF, id entrust 등이 모여서 ISRG(Internet Security Research Group)라는 새로운 SSL 인증기관을 만들어서 올해 SSL을 무료로 제공하겠다고 발표했다. 지금은 이 Lets' Encrypt에 Facebook, 워드프레스를 만드는 Automattic, shopify 등 많은 회사가 스폰서로 참여하고 있다.
올여름부터 약간씩 구체적인 내용이 나오기 시작하면서 클로즈 베타로 신청해서 진행하다가 지난 12월 3일부터 퍼블릭 베타를 시작했다. 나는 클로즈 베타를 신청해서 초대권을 받았었지만 귀차니즘에 설치를 못 하고 있다가 이번에 퍼블릭 베타가 시작되어 설치하고 적용해 봤다. 설치는 공식 문서를 참고했다.
Let's Encrypt 클라이언트
Lets' Encrypt를 통해서 인증서를 발급받으려면 Let's Encrypt 클라이언트를 사용해야 한다. 아직 퍼블릭 베타이므로 클라이언트도 아직 베타 상태이지만 내 경우에는 적용을 해봤을 때 큰 문제가 없었다. 내 서버 환경은 Ubuntu 13.04이다.
$ git clone https://github.com/letsencrypt/letsencrypt
클라이언트 저장소에서 소스를 다운받는다.
$ ./letsencrypt-auto --help
Bootstrapping dependencies for Debian-based OSes...
Get:1 http://apt.newrelic.com newrelic Release.gpg [198 B]
Hit http://archive.ubuntu.com trusty Release.gpg
Get:2 http://apt.newrelic.com newrelic Release [3,364 B]
Get:3 http://apt.newrelic.com newrelic/non-free amd64 Packages [16.3 kB]
Get:4 http://archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://ppa.launchpad.net raring Release.gpg
Get:5 http://archive.ubuntu.com trusty-security Release.gpg [933 B]
...
다운받은 폴더에 들어가서 letsencrypt-auto --help
를 실행하면 자동으로 관련 의존성을 다운받아서 설치한다. 의존성이 많아서 꽤 많은 시간이 걸린다. 설치를 다 한 뒤에 다시 --help
의 내용을 보면 다음과 같다.
$ ./letsencrypt-auto --help
Updating letsencrypt and virtual environment dependencies.......
Running with virtualenv: sudo /home/outsider/.local/share/letsencrypt/bin/letsencrypt --help
letsencrypt [SUBCOMMAND] [options] [-d domain] [-d domain] ...
The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates. By
default, it will attempt to use a webserver both for obtaining and installing
the cert. Major SUBCOMMANDS are:
(default) run Obtain & install a cert in your current webserver
certonly Obtain cert, but do not install it (aka "auth")
install Install a previously obtained cert in a server
revoke Revoke a previously obtained certificate
rollback Rollback server configuration changes made during install
config_changes Show changes made to server config during installation
plugins Display information about installed plugins
Choice of server plugins for obtaining and installing cert:
--apache Use the Apache plugin for authentication & installation
--standalone Run a standalone webserver for authentication
(nginx support is experimental, buggy, and not installed by default)
--webroot Place files in a server's webroot folder for authentication
OR use different plugins to obtain (authenticate) the cert and then install it:
--authenticator standalone --installer apache
More detailed help:
-h, --help [topic] print this message, or detailed help on a topic;
the available topics are:
all, automation, paths, security, testing, or any of the subcommands or
plugins (certonly, install, nginx, apache, standalone, webroot, etc)
퍼블릭 베타 이전에는 설치시도를 안 해봤지만 관련 글을 보면 명령어가 약간은 달라진 부분이 있다. 이 명령어를 이용해서 인증서를 받아야 하는데 Let's Encrypt 클라이언트에서 설치를 위한 여러 가지 플러그인을 제공하고 있다. 그래서 Apache 웹서버를 사용하고 있다면 --apache
옵션으로 Apache 플러그인을 사용하면 인증서 발급부터 서버 설정까지 자동으로 해주는 것으로 보인다. Nginx 플러그인도 제공하고 있지만, 아직 실험단계로 사용을 적극적으로 추천하지 않고 클라이언트에도 기본으로 포함되어 있지 않다.
나 같은 경우 nginx 웹서버를 사용하고 있었으므로 플러그인을 사용하는 대신 수동 인증서 발급 후 설치하는 방법을 사용했다.
Let's Encrypt 인증서 발급
플러그인을 사용하지 않고 수동으로 인증서만 받으려면 ./letsencrypt-auto certonly --manual
명령어를 사용하면 된다.
$ ./letsencrypt-auto certonly --manual
Updating letsencrypt and virtual environment dependencies.......
Running with virtualenv: sudo /home/outsider/.local/share/letsencrypt/bin/letsencrypt certonly --manual
이 명령어를 실행하면 인증서발급을 위한 정보를 받기 위한 화면이 나온다.
먼저 인증서에 관련된 연락을 받을 이메일을 입력한다. 그다음에는 약관에 대한 동의를 물어본다.
인증서를 사용할 도메인을 물어본다. 여기서는 블로그에 사용할 것이므로 하나만 입력했지만 여러 도메인을 사용하려면 콤마나 공백으로 분리해서 여러 도메인을 입력하면 된다.
마지막으로 현재 도메인이 공개적으로 수집되는 부분에 대한 안내가 나온다. 인증서만 발급받는 경우에는 운영 서버에서 직접 하지 않고 로컬에서 발급받아서 서버에 업로드 후 적용할 수도 있으므로 이 부분에 대한 안내이다. 나 같은 경우에는 운영서버에서 직접 발급을 받았으므로 Yes를 선택했다. 여기까지 완료하고 나면 다음과 같은 안내메시지가 나오고 대기상태가 된다.
Make sure your web server displays the following content at
http://blog.outsider.ne.kr/.well-known/acme-challenge/6J19PJntZgEYHiGDKQp97PtdZ4KsNare1jP3dotIB2U before continuing:
6J19PJntZgEYHiGDKQp97PtdZ4KsNare1jP3dotIB2U.ez2wa0J1JFO5k_X6K6nOXT_rklKlENka8WuYTIrfVmc
If you don't have HTTP server configured, you can run the following
command on the target server (as root):
mkdir -p /tmp/letsencrypt/public_html/.well-known/acme-challenge
cd /tmp/letsencrypt/public_html
printf "%s" v6jTNjl519uT9PQO0JKEOoiaY2mA8sy4wloh0HpMV7M.ez2wa0J1JFO5k_X6K6nOXT_rklKlENka8WuYTIrfVmc > .well-known/acme-challenge/v6jTNjl519uT9PQO0JKEOoiaY2mA8sy4wloh0HpMV7M
# run only once per server:
$(command -v python2 || command -v python2.7 || command -v python2.6) -c \
"import BaseHTTPServer, SimpleHTTPServer; \
s = BaseHTTPServer.HTTPServer(('', 80), SimpleHTTPServer.SimpleHTTPRequestHandler); \
s.serve_forever()"
Press ENTER to continue
이 단계는 내가 발급받는 인증서의 주인이 정말 나인지를 확인하는 단계이다. 이 확인 방법으로는 특정 URL에 요청을 보내서 지정된 키가 응답으로 오는지 확인하는 방법을 취한다. 위에 나온 안내대로 http://blog.outsider.ne.kr/.well-known/acme-challenge/6J19PJntZgEYHiGDKQp97PtdZ4KsNare1jP3dotIB2U
URL로 요청을 보냈을 때 중간에 나온 6J19PJntZgEYHiGDKQp97PtdZ4KsNare1jP3dotIB2U.ez2wa0J1JFO5k_X6K6nOXT_rklKlENka8WuYTIrfVmc
을 반환해야 이 인증단계가 마무리된다. 하단에 나온 부분은 웹서버가 따로 없는 경우 python으로 웹서버를 임시로 띄어서 이 인증단계를 하는 방법을 알려주는 것이다.
나 같은 경우는 nginx를 이미 사용하고 있으므로 nginx에 이 설정을 추가했다.
location /.well-known {
root /home/outsider/www/well-known;
}
위처럼 추가하고 /home/outsider/www/well-known/.well-known/acme-challenge/B-4nYiiS8NlRP4Lq4UhRZOZiREDlmSqzFiFzM6B3MAI
경로에 파일을 생성했다.(파일이름이 B-4nYiiS8NlRP4Lq4UhRZOZiREDlmSqzFiFzM6B3MAI
이다.) 그리고 이 파일에 위의 키 내용을 입력하고 nginx를 리로드해서 위 URL로 요청을 보냈을 때 정상적으로 응답이 와야 한다.
이 설정을 완료한 뒤에 엔터를 누르면 확인을 진행하는데 이 인증단계에 실패하면 다음과 같은 오류가 나온다.
IMPORTANT NOTES:
- The following 'urn:acme:error:unauthorized' errors were reported by
the server:
Domains: blog.outsider.ne.kr
Error: The client lacks sufficient authorization
인증이 제대로 이뤄졌다면 다음과 같은 성공 메시지와 함께 인증서가 생성된다.
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/blog.outsider.ne.kr/fullchain.pem. Your cert
will expire on 2016-03-04. To obtain a new version of the
certificate in the future, simply run Let's Encrypt again.
- If like Let's Encrypt, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
위 내용을 보면 인증서가 2016-03-04에 만료된다고 나온 것을 볼 수 있다. 한번 발급받으면 평생 쓸 수 있는 것은 아니다. Let's Encrypt의 인증서는 90일 동안 유효한 인증서이므로 90일마다 새로 갱신을 해야 한다. 갱신에 대해서는 문서에 안내가 되어 있는데 이제 처음 사용했으므로 아직 갱신을 해보지 않았다.(갱신은 나중에 하게 될 때 다시...) /etc/letsencrypt/live/blog.outsider.ne.kr/
아래를 보면 다음과 같은 파일이 생긴 것을 볼 수 있다.
cert.pem -> ../../archive/blog.outsider.ne.kr/cert1.pem
chain.pem -> ../../archive/blog.outsider.ne.kr/chain1.pem
fullchain.pem -> ../../archive/blog.outsider.ne.kr/fullchain1.pem
privkey.pem -> ../../archive/blog.outsider.ne.kr/privkey1.pem
nginx에 SSL 인증서 설치
웹사이트에서 HTTPS를 지원하려면 앞에서 발급받은 인증서를 nginx에 설정해야 한다. nginx의 SSL 설정은 Mozilla SSL Configuration Generator를 사용했다. 사용하는 웹서버와 관련 버전을 명시하면 권장하는 설정파일을 만들어 주므로 이 파일을 그대로 사용했다.
나 같은 경우는 nginx를 사용하고 많은 브라우저를 지원하기 위해 Intermediate를 선택했다. nginx는 1.6.2버전을 사용하고 OpenSSL 버전은 1.0.1c였다.(OpenSSL 버전 확인은 openssl version
)
server {
listen 443 ssl;
# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
ssl_certificate /path/to/signed_cert_plus_intermediates;
ssl_certificate_key /path/to/private_key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
ssl_dhparam /path/to/dhparam.pem;
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;
## verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
resolver <IP DNS resolver>;
....
}
여기서 몇 가지 정보는 실제 자신의 정보로 바꾸어 주어야 한다.
ssl_certificate /etc/letsencrypt/live/blog.outsider.ne.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blog.outsider.ne.kr/privkey.pem;
ssl_certificate
와 ssl_certificate_key
를 발급받은 파일 중에 fullchain.pem
와 privkey.pem
로 지정한다.
ssl_dhparam /etc/letsencrypt/live/blog.outsider.ne.kr/dhparam.pem;
ssl_dhparam는 openssl dhparam -out dhparam.pem 2048
명령어를 통해서 새로 생성해야 한다.
ssl_trusted_certificate /etc/letsencrypt/live/blog.outsider.ne.kr/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=86400;
resolver_timeout 10;
ssl_trusted_certificate
부분에는 앞에서 발급받은 chain.pem
파일을 사용하고 resolver
에는 구글의 Public DNS를 사용했다. 이렇게 하면 SSL 설정은 완료되었으므로 server { }
부분 안에 서버의 관련 설정을 추가하면 된다.
인증서 정보를 보면 Lets' Encrypt에서 발급받은 인증서라면서 녹색 자물쇠 표시가 예쁘게 잘 나온다. 덕분에 무료로 블로그에서도 HTTPS를 지원하게 되었다. ㅎㅎ
[출처] https://blog.outsider.ne.kr/1178