목요일, 6월 09, 2016

Haproxy 와 Let's Encrypt 인증서 설치

Haproxy 와 Let's Encrypt 인증서 설치 
2016년 5월 24일 화요일
오후 5:21
개요(Abstract)
글에서는, TLS(SSL) 종점(Termination) 위한 인증서로 오픈 CA Let's Encrypt 사용한
, 특이 사항과 Haproxy 환경 파일에 대한 주요 사항만을 기술하기로 한다.

Let's Encrypt 오픈 CA 인증서 설치
새로운 오픈CA 인증에 대한 기본 개요는 이전 SSL 인증의 새로운 대한 Let's Encrypt 참조한다.

우리는 우분투 플렛폼의 Haproxy 탑재하고자 하므로, 다음의 과정을 거친다.

[Let's Encrypt 설치 도구인 Certbot 설치]

$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto

다음 명령어를 수행하여, 인증서 설치에 도구에 필요한 종속성 패키지등 모든 클라이언트 도구 업데이트 수행
$ ./certbot-auto

Let's Encrypt 인증서 설치
 클라이언트 도구 certbot-auto 실행한다.

$ ./certbot-auto certonly --standalone

명령어를 실행하면, certbot-auto 작은 자체 서버를 80 포트로 기동한다.
이를 통해 Let's Encrypt CA 부터 도메인 인증을 수행한다.

주의)
명령어를 실행 하기 , 서비스 포트 80 서비스가 사용되고 있는지 점검한다.
만약, 서비스가 실행 중이라면 정지하거나 기존 웹서버를 사용한 webroot plugin 모드를 사용한다.
글에서는, 80 포트로 어떠한 서비스도 수행되고 있지 않다고 가정한다.

다음, 가지 정보를 요구하는 프롬프트 창이 뜨며, 관련정보를 입력한다.
먼저, 인증서 갱신 등의 안내 메일을 받을 있는 email 등록



다음, Let's Encryp사용 동의서 확인


그리고, 인증서를 사용할 도메인 지정


이상의 도메인을 사용한다며 모두 위와 같이 나열한다.

모든 작업이 성공적으로 완료되면, /etc/letsencrypt/live/도메인명/ 경로 밑에 다음 4개의
파일이 생성된다.

cert.pem: 해당 도메인에 대한 공개키 인증서
chain.pem: Let's Encrypt chain certificate
fullchain.pem: cert.pem and chain.pem combined
privkey.pem:  공개키 인증서의 private key

Haproxy TLS(SSL) Termination 구성을 위해 fullchain.pem privkey.pem 병합한다.
cat 명령을 사용하여 단순히 파일을 병합하고 pem 확장자를 지정한다.

$ sudo mkdir -p /etc/haproxy/certs

$ sudo -E bash -c 'cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/도메인/privkey.pem > /etc/haproxy/certs/ssl.pem'

보안을 강화하기 위해, 해당 파일의 권한 변경
sudo chmod -R go-rwx /etc/haproxy/certs


Let's Encrypt 인증서 갱신
인증서를 갱신하기 위해서는 Haproxy 환경 파일을 약간 수정한다.
이때, ACL(Access control list) 사용하면 요청 패킷의 url path 식별하여 let's encrypt 인증서 갱신 요청만을 분리하여 서비스 중단이 발생할 있는 80 포트 서비스를 별도로 구성했던 번거로움 없이 인증서 갱신을 처리할 있다.

Haproxy 다음과 같이 변경한다.

frontend https-frontend
    #bind *:443 ssl crt /etc/ssl/priavte/ssl.pem
    bind *:443 ssl crt /etc/haproxy/certs/ssl.pem
    reqadd X-Forwarded-Proto:\ https
    acl letsencrypt-acl path_beg /.well-known/acme-challenge/
    use_backend letsencrypt-backend if letsencrypt-acl
    default_backend wwwbackend

backend letsencrypt-backend
   server letsencrypt 127.0.0.1:54321

중요한 부분은 highlight 표현하였다.

acl 구문은 다음의 포맷을 가진다.
 acl <aclname> <criterion> [flags] [operator] [<value>] ...
자세한 도움말은 공식문서 https://cbonte.github.io/haproxy-dconv/configuration-1.6.html#7 참조하고
여기서는 주요내용만 다루자.

우선 acl 규칙 생성이다.
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
letsencrypt-acl acl 이름이고,
path_beg에서 path url 의미하며, _ 규칙 구분자 beg begin으로 이후 나타나는 문자열이 시작될 경우 true 반환한다.

여기서는  url path /.well-known/acme-challenge/ 시작하면 let's encrypt 인증서 갱신요청으로 간주한다.

 use_backend letsencrypt-backend if letsencrypt-acl
구문은 acl 규칙에 대한 사용여부를 판별한다.
, letsencrypt-acl 이름 지어진 규칙이 true 반환하면, use_backend 패킷을 보낸다.

server letsencrypt 127.0.0.1:54321
let's Encrypt인증서 갱신을 위해 certbot-auto 자체 웹서버를 54321 포트로 오픈 하고 가정하고
letsencrypt-backend   해당 요청을 54321포트로 포워딩한다.

위와 같은 메커니즘을 사용함 으로서, 기존 서비스에 영향을 주지 않고 인증서 갱신이 가능하다.


인증서 갱신

--모든 도메인의 인증서를 renewal
./certbot-auto renew --standalone --agree-tos --renew-by-default --standalone-supported-challenges http-01 --http-01-port 54321

방법은, 해당 사이트에 여러 도메인에 해당하는 인증서가 있을 경우 모든 인증서를 갱신하도록 한다.


--특정 도메인의 인증서만 renewal
/home/user/certbot-auto certonly --standalone --agree-tos --renew-by-default --standalone-supported-challenges http-01 --http-01-port 54321 -d mysite.co.kr

명령어는 -d 지정한 특정 도메인 만을 갱신한다.

--갱신된 fullchain.pem privkey.pem 병합 /etc/haproxy/certs/ssl.pem 재생성
sudo bash -c "cat /etc/letsencrypt/live/mysite.co.kr/fullchain.pem /etc/letsencrypt/live/mysite.co.kr/privkey.pem > /etc/haproxy/certs/ssl.pem"

변경된 환경을 사용하도록 haproxy  다시 시작한다.
sudo service haproxy reload

crontab job 으로 등록
Job 등록할 스크립트를 다음과 같이 작성

$ vi le-renew-haproxy
#!/bin/bash

domain='mysite.co.kr'
exp_limit=30;

cert_file="/etc/letsencrypt/live/$domain/fullchain.pem"
key_file="/etc/letsencrypt/live/$domain/privkey.pem"

if [ ! -f $cert_file ]; then
echo "[ERROR] certificate file not found for domain $domain."
fi

exp=$(date -d "`openssl x509 -in $cert_file -text -noout|grep "Not After"|cut -c 25-`" +%s)
datenow=$(date -d "now" +%s)
days_exp=$(echo \( $exp - $datenow \) / 86400 |bc)

echo "checking expiration date for $domain..."

if [ "$days_exp" -gt "$exp_limit" ] ; then
echo "The certificate is up to date, no need for renewal ($days_exp days left)."
exit 0;
else

echo "the certificate for $domain will be updated"
/home/user/certbot-auto certonly --standalone --agree-tos --renew-by-default --standalone-supported-challenges http-01 --http-01-port 54321 -d $domain

echo "creating /etc/haproxy/certs/ssl.pem with latest certs"
sudo bash -c "cat /etc/letsencrypt/live/$domain/fullchain.pem /etc/letsencrypt/live/$domain/privkey.pem > /etc/haproxy/certs/ssl.pem"

echo "reloading haproxy service"
sudo service haproxy reload
fi

실행 권한 지정
$ sudo chmod +x le-renew-haproxy

crontab 등록
$ sudo crontab -e

다음과 같이 매주 월요일 오전 04시에 수행하도록 설정
# m h  dom mon dow   command
0 4 * * 1 /home/user/le-renew-haproxy >> /var/log/le-renew-proxy.log


구성 파일
Haproxy
/etc/haproxy/haproxy.cfg
global
        log /dev/log    local0
        log /dev/log    local1 notice
        maxconn 8192
        tune.ssl.default-dh-param 2048
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3

defaults
        log     global
        mode    http
        option  forwardfor
        option  http-server-close
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

        #HAProxy 상태 모니터링url
        stats enable
        stats uri /stats #상태모니터링 uri명
        stats realm Haproxy\ Statistics
        stats auth user:1111 #사용자 아이디/비번

frontend https-frontend
    #bind *:443 ssl crt /etc/ssl/priavte/ssl.pem
    bind *:443 ssl crt /etc/haproxy/certs/ssl.pem
    reqadd X-Forwarded-Proto:\ https
    acl letsencrypt-acl path_beg /.well-known/acme-challenge/
    use_backend letsencrypt-backend if letsencrypt-acl
    default_backend wwwbackend

backend wwwbackendd
    redirect scheme https if !{ ssl_fc }
    server www-1 127.0.0.1:8080 check

backend letsencrypt-backend
   server letsencrypt 127.0.0.1:54321

frontend http-frontend
    bind *:80
    reqadd X-Forwarded-Proto:\ http
    default_backend wwwbackend

Let's Encrypt 갱신 시크립트
#!/bin/bash

domain='mysite.co.kr'
exp_limit=30;

cert_file="/etc/letsencrypt/live/$domain/fullchain.pem"
key_file="/etc/letsencrypt/live/$domain/privkey.pem"

if [ ! -f $cert_file ]; then
echo "[ERROR] certificate file not found for domain $domain."
fi

exp=$(date -d "`openssl x509 -in $cert_file -text -noout|grep "Not After"|cut -c 25-`" +%s)
datenow=$(date -d "now" +%s)
days_exp=$(echo \( $exp - $datenow \) / 86400 |bc)

echo "checking expiration date for $domain..."

if [ "$days_exp" -gt "$exp_limit" ] ; then
echo "The certificate is up to date, no need for renewal ($days_exp days left)."
exit 0;
else

echo "the certificate for $domain will be updated"
/home/user/certbot-auto certonly --standalone --agree-tos --renew-by-default --standalone-supported-challenges http-01 --http-01-port 54321 -d $domain

echo "creating /etc/haproxy/certs/ssl.pem with latest certs"
sudo bash -c "cat /etc/letsencrypt/live/$domain/fullchain.pem /etc/letsencrypt/live/$domain/privkey.pem > /etc/haproxy/certs/ssl.pem"

echo "reloading haproxy service"
sudo service haproxy reload
fi

참조
https://certbot.eff.org/#ubuntutrusty-haproxy
https://certbot.eff.org/docs/using.html#renewal
https://www.digitalocean.com/community/tutorials/how-to-secure-haproxy-with-let-s-encrypt-on-ubuntu-14-04
https://www.haproxy.com/doc/aloha/7.0/haproxy/acls.html#id2

댓글 없음: