Let’s Encrypt

기존에는 도메인 업체에서 1년치의 SSL을 사서 넣곤 했다. 그러나 Let’s Encrypt를 통해 SSL을 적용하면 무료로 인증서를 발급하고 HTTPS 환경을 구축할 수 있다. 하지만 단점도 존재하는데, 일반적인 방법으로 발급 시 90일동안만 유효하고, 다시 발급해야하는 번거로움이 있다. 90일 후의 연장을 알아서 하게끔 도와주는 docker 셋업 파일을 깃허브에서 찾았고, 작업 중인 프로젝트에 적용해보려 한다.

도메인 설정

당연히 도메인이 필요하다. 나는 네임칩을 이용하고 있다. 프론트엔드의 IP를 도메인에 걸어줘야 하는데, A Record나 AAAA Record 둘 중의 하나로 적용해야 인증서를 발급할 수 있다.

A Record 적용

이제 도메인에 대한 설정은 끝났다.

Docker-compose 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
version: '3.1'
networks:
app-tier:
driver: bridge
services:
# 다른 서비스들 (백엔드, DB ...)

nginx:
networks:
- app-tier
build:
context: ./frontend
dockerfile: ./Dockerfile
container_name: kiwi-frontend
volumes:
- /app/node_modules
- ./frontend:/app
- ../certbot/conf:/etc/letsencrypt # 추가
- ../certbot/www:/var/www/certbot # 추가
restart: always
ports:
- '80:80'
- '443:443'
expose:
- '80'
- '443'
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''

certbot: # certbot 서비스 추가
image: certbot/certbot
container_name: certbot_service
volumes:
- ../certbot/conf:/etc/letsencrypt
- ../certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Docker-compose 파일을 수정하는데, 위와 같이 nginx service에 certbot에 대한 볼륨 2개를 추가해주고, certbot 서비스 전체를 추가해주면 된다.

nginx.conf 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
worker_processes auto;

events { worker_connections 1024; }

http {

include mime.types;
sendfile on;

server {
listen 80;
server_name YOUR_DOMAIN_ADDRESS;
server_tokens off;

location / {
return 301 https://$host$request_uri;
}

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}

server {
listen 443 ssl;
server_name YOUR_DOMAIN_ADDRESS;
server_tokens off;

location /api { # 이 부분은 api라는 uri로 통신 시 백엔드에 프록시 처리를 하기 위함
proxy_pass http://app:4000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN_ADDRESS/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN_ADDRESS/privkey.pem;
}

include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

nginx.conf 파일을 만드는데, 기존 80 포트로 들어오는 요청을 443 포트로 리다이렉션 시켜준다. 443 포트는 SSL에 대한 정보를 같이 작성한다. YOUR_DOMAIN_ADDRESS 부분을 본인의 도메인으로 바꿔준다. api에 대한 부분은, 프론트/백엔드를 분리하여 프론트 요청을 백엔드로 프록시하기 위함인데, SSL과는 상관이 없다.

쉘 스크립트 추가

이 쉘 스크립트는 깃허브 nginx-certbot에서 찾았다. 귀찮은 SSL 세팅 작업을 수고스럽게 해주셨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/bin/bash

if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi

domains="도메인 주소"
rsa_key_size=4096
data_path="../certbot"
email="이메일 주소" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits

if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi


if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi

echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo


echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo

echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo


echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done

# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac

# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi

docker-compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo

echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload

쉘 스크립트를 init-letsencrypt.sh 파일로 docker-compose.yaml 파일이 있는 경로에 함께 저장한다.

빌드하기

이제 Docker-compose를 통해 도커 이미지 빌드 후 배포하는데, docker-compose up이 아닌 위에서 작성한 쉘 스크립트를 실행한다.

1
2
chmod +x init-letsencrypt.sh
./init-letsencrypt.sh

주의할 점은, 빌드를 반복하면 Let’s Encrypt에서는 Request Limits를 걸고 있는데, 일정 요청을 초과하면 당분간 해당 도메인 주소에 SSL을 발급하지 못하게 된다. 따라서 테스트 빌드시에는 쉘 스크립트의 staging = 0 부분을 1로 변경 후 배포를 테스트하자.

한번 배포하면 SSL에 대해 귀찮을 일이 없는 HTTPS 환경이 만들어졌다.