Profile picture

[Docker] certbot + nginx로 SSL 인증서 발급하기 - 내가 만든 사이트에 HTTPS 설정하기

JaehyoJJAng2023년 05월 15일

🔐 SSL/TLS

  • SSL(Secure Sockets Layer)
  • TLS(Transport Layer Security)
  • SSL/TLS는 브라우저와 서버가 통신할 때 암호화를 지원하는 보안 프르토콜
  • SSL 인증서는 웹 사이트의 ID를 공개 키와 개인 키로 구성된 암호화 키 쌍에 바인딩하는 디지털 문서이다
    • 사용자는 인증서에 포함된 공개 키를 사용해 데이터를 암호화하여 전송할 수 있고, 서버는 안전하게 보관되어 있는 개인 키를 사용자의 데이터를 복호할 때나 웹 페이지 및 기타 문서를 디지털 서명하는 데 사용할 수 있다.
  • SSL 인증서의 역할은 아래와 같다.
    • 클라이언트가 접속한 서버가 신뢰할 수 있는 서버임을 보장
    • SSL 통신에 사용할 공개키를 클라이언트에게 제공

🔐 HTTPS

  • HTTPS는 기존 HTTP(HyperText Transfer Protocol)에 Secure(S)를 붙인 용어이다. 기존 HTTP에 말 그대로 Secure(보안)만 추가한 것인데, HTTP 통신의 경우 네트워크단에서 데이터가 유출될 가능성이 많아 데이터를 안전하게 보호하기 위해 HTTPS로 개선되었다.
  • HTTP는 80번 포트를 사용하며, HTTPS는 443번 포트를 사용한다.

▪️ HTTPS 설정 이유?

서비스에 대한 신뢰성

접속하는 사이트가 신뢰할 수 있는 사이트인지 알려준다. 새로 오픈한 서비스가 있다고 가정할 때, 사용자 입장에서 아래와 같은 창이 뜨면 해당 서비스를 이용하고 싶은 마음이 생길까? 다시는 들어가고 싶지 않을 것이다. (2018년 구글은 SSL 인증서가 없는 사이트에게 불이익을 주고있다., 아래와 같이 '안전하지 않음' 표시를 띄우고 있음)
image


◾️ SSL 인증을 위해 해야할 일

  • 1. 도메인 구매 (가비아, 구글 도메인, Route53 ...)
  • 2. 인증서 발급
  • 3. Nginx 및 docker-compose 설정 (Docker 환경 기반에서의 웹 서비스 배포 시)

▪️ 도메인 구매

나의 경우 가비아에서 도메인을 구입하였다.
image


▪️ IP 연결

  • @www를 기본 호스트로 넣어준다.
  • 호스트에는 서버의 공인(Public) IP를 넣어주었다.

image
image


◾️ OpenSSL 인증서 발급

OpenSSL 인증서 중 가장 유명한 "Let's Encrypt SSL" 인증서를 사용할 것이다.


▪️ Let's Encrypt

  • CA 중 하나로, 무료로 SSL 인증서를 발급해준다.
  • 유효기간이 90일이므로, 기간이 다 되면 재발급 받아야 함.

▪️ Certbot

  • "Let's Encrypt"에서 SSL 인증서를 발급받는 자동화 툴.
  • 오픈소스, 도커에 공식 이미지가 올라와 있기 때문에 컨테이너 방식으로 어떤 환경에서도 편하게 사용 가능.

▪️ certbot 설치

서버 터미널 창에서 다음 명령어를 입력 해주자.

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y letsencrypt
sudo apt-get install -y python3-certbot-nginx

▪️ 인증서 발급

• Standalone

  • 도메인으로 인증서를 발급
    • 명령어 실행 시 이메일을 작성하라고 함.
      • 긴급 통보 혹은 키 복구를 위해 사용되는 것이니 실제 메일 받을 이메일을 적어야 함.
    • 이후 이용약관 동의 등의 상호작용이 나오면 Yes 또는 Agree 버튼 클릭/엔터

인증서 발급 명령은 아래와 같다.

sudo letsencrypt certonly -a standalone -d [발급 받을 도메인]

image


위 사항을 완료하면 /etc/letsencrypt/live/{도메인} 디렉토리에 .pem 파일이 생긴다.

sudo ls -lh /etc/letsencrypt/live/waytothem.shop/

image

  • cert.pem : 도메인 인증서
  • chain.pem : Let's Encrypt chain 인증서
  • fullchain.pem : cert.pem과 chain 인증서 합본
  • privkey.pem : 개인키

하지만.. 위 방법(인증서 발급 - Standalone)은 단점이 존재한다

  • 90일마다 인증서가 만료됨 => 배포 작업에서 사용자가 수동으로 체크해야되는 부분이 발생 => 배포 자동화의 의미가 퇴색되게 된다.
  • 이런 배포 자동화 환경에서 딱 맞는(docker + nginx) 오픈소스가 존재한다!

• Shell Script

  • https://github.com/wmnnd/nginx-certbot
  • Let's Encrypt를 NGINX + 도커 환경에서 인증받을 수 있게 도와주는 보일러플레이트.
  • 셸 스크립트를 사용해 인증서를 가져와 갱신할 수 있도록 도와준다.

위 깃허브에 올라온 init-letsencrypt.sh 파일을 현재 프로젝트 경로에 다운로드 받자.

$ curl -L -O https://github.com/wmnnd/nginx-certbot/raw/master/init-letsencrypt.sh

init-letsencrypt.sh의 내용 (위 깃허브 주소에서 확인할 수 있음)

#!/bin/bash

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

domains=(example.org www.example.org) # 본인 도메인 주소 입력
rsa_key_size=4096
data_path="인증서를 저장할 경로 기입"
email="내 이메일 주소 기입" # Adding a valid address is strongly recommended

... // 그 외에 설정은 건드릴게 없음. 디폴트로 냅두기

image
이제 NGINX와 Docker Compose를 설정한 후 위 스크립트를 실행하기만 하면 내 홈페이지를 HTTPS로 배포할 수 있게된다.


• Docker Compose

🔺 certbot을 지원하는 도커 컴포즈 버전이 정해져있기 때문에 Docker Compose Version 3.3 이하로 해야한다는 단점이 존재한다. 그 이상 버전은 오류를 발생시킨다.

🔺 docker compose 명령어가 아닌 docker-compose 명령어에만 작동이 가능하기 때문에 과거의 docker-compose를 설치해줘야 한다.

# Docker Compose 다운로드
sudo curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 실행 권한 부여
sudo chmod +x /usr/local/bin/docker-compose

# 심볼릭 생성
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

# 도커 컴포즈 버전 확인
docker-compose -v

백엔드의 Docker-Compose

version: "3.3"

services:
  nginx:
    image: nginx
    restart: always
    volumes:
      - "./nginx-conf:/etc/nginx/conf.d"
      - "./data/certbot/conf:/etc/letsencrypt"
      - "./data/certbot/www:/var/www/certbot"
    ports:
      - "80:80"
      - "443:443"
    container_name: nginx
    depends_on:
      - "your-app"
    networks:
      - "web-net"
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  certbot:
    image: certbot/certbot
    restart: always
    volumes:
      - "./data/certbot/conf:/etc/letsencrypt"
      - "./data/certbot/www:/var/www/certbot"
    container_name: certbot
    depends_on:
      - "nginx"
    networks:
      - "web-net"
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

  # .. Your apps ..

networks:
  web-net:
    driver: bridge
    external: false

volume 설정을 통해 서버에 있는 인증서를 docker 환경과 연결해야 한다.

      - type: bind
        source: "/letsencrypt/certbot/conf:/etc/letsencrypt"
        target: "/etc/letsencrypt"
      - type: bind
        source: "/letsencrypt/certbot/www"
        target: "/var/www/certbot"

위 설정은 certbotnginx 컨테이너가 동일 해야하고 /letsencrypt/certbot 부분은 'init-letsencrypt.sh의 인증서를 저장할 경로와도 맞춰줘야 한다.
image


아래 내용은 nginx-설정 챕터에서 작성할 nginx 설정 파일을 Docker 환경에 연동시켜주는 부분이다.

      - type: bind
        source: "./nginx-conf"
        target: "/etc/nginx/conf.d"

nginx의 command와 certbot의 entrypoint는 왜 있는 걸까?

  nginx: 
    command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
  certbot:
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

이유는 아래와 같다

nginx.command

  • 인증서가 만료될 때쯤 자동으로 SSL 인증서 갱신을 위한 커맨드를 추가한 것
  • nginx의 백그라운드(nginx 환경에서 nginx가 돌면서 다른 프로세스를 실행시킬 수 있는 상태)에서 6시간마다 구성 및 인증서를 다시 로드하고, daemon off를 통해 포어그라운드(nginx 환경에서 nginx가 돌면 아무것도 실행할 수 없는 상태)에서 nginx를 실행함

certbot.entrypoint

  • certbot은 12시간마다 갱신하는 것처럼 짜여있고, 이는 Let's Encrypt의 권장사항이라고 함. Let's Encrypt는 매주 발급받을 수 있는 인증서가 한정되어 있다고 하는데 12시간마다 갱신하면 인증서를 발급받는데 문제가 있지 않을까 걱정할 수도 있다.

▪️ Nginx 설정

백엔드의 NGINX

server {
    listen  80 default_server;
    server_name waytothem.shop;
    server_tokens off;

    # certbot이 발급한 challenge 파일을 nginx가 서빙
    location /.well-known/acme-challenge/ {
        allow all;
        root /var/www/certbot;
    }

    # 모든 http(80포트) 요청을 https로 리다이렉팅
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name waytothem.shop;
    server_tokens off;

    ssl_certificate     /etc/letsencrypt/live/{도메인명_기입}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{도메인명_기입}/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://express-app:4000; # 프록시 요청 주소 (서버 URL OR 서버 IP OR 도커_컨테이너명)
        proxy_http_version 1.1;
    }
}

image


◾️ 스크립트 실행

이제 모든 준비가 끝났다!


먼저 프로젝트 경로에 있는 init-letsencrypt.sh 파일에 실행 권한을 주도록 하자.

chmod +x ./init-letsencrypt.sh

이제 root 권한으로 해당 스크립트를 실행하면 된다.

sudo bash ./init-letsencrypt.sh

image
정상적으로 인증서 발급이 완료된 모습.


☺️ 인증서 적용 확인

마지막으로 인증서가 사이트에 제대로 적용됐는지 확인해보면 된다.
브라우저에 주소(https://<도메인명>)를 치고 서비스에 접근해보자.
image
제대로 적용되었다!


🔓 OpenSSL 인증 이슈

1. 인증서 과발급 문제

Duplicate Certificate Limit

  • 공식문서에 따르면 주당 5회까지 중복 발급 가능
  • 인증서 삭제 후 계속 다시 발급하다보니 정지 당함.
  • 인증서 인증 후 init-letsencrypt.sh 재실행 x

📕 프로젝트 소스 코드


Loading script...