[Springboot] docker-compose, nginx, springboot, SSL 인증서 적용하기

일단 해볼게·2023년 11월 27일
0

Springboot

목록 보기
5/26
post-thumbnail

http에서는 쿠키가 적용되지 않아 SSL을 적용하려 한다. docker-compose, nginx, springboot 환경에서 무료로 SSL 인증서를 적용하고 shell script를 통해 인증서 유효기간이 만료되기 전에 자동으로 기간을 연장해보자.

사전 준비 사항

  • 도메인 발급 (A or AAAA record)

인증서 발급을 위한 certbot 컨테이너 만들기

version: '3'

services:
  backend:
    container_name: backend
    image: {springboot 이미지 이름}
    ports:
      - "8080:8080"
    user: "1000:1000"
    networks:
      - anifriends
    volumes:
      - /home/ec2-user/logs:/logs

  nginx:
    image: {nginx 이미지 이름}
    container_name: nginx
    volumes:
      - ./nginx/:/etc/nginx/
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - backend
    networks:
      - anifriends
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

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

  redis:
    container_name: redis
    image: redis:latest
    depends_on:
      - backend
    ports:
      - "6379:6379"
    networks:
      - anifriends

networks:
  anifriends:
volumes:
  logs:
certbot: # 추가
    container_name: certbot
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt # 추가
      - ./data/certbot/www:/var/www/certbot # 추가
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    networks:
      - anifriends
  • certbot 컨테이너 추가
  • nginxcertbot에 대한 volume 2개 추가
    • certbot이 발급한 인증서를 nginx가 브라우져의 요청에 따라 반환할 수 있어야 하므로 certbot 컨테이너와 nginx 컨테이너는 같은 폴더를 공유해야 한다.
  • nginx 에 443 포트 열기
  • nginx의 command, certbot의 entrypoint는 인증서가 만료되기 전에 자동으로 SSL 인증서를 재발급해준다.

nginx 설정파일 변경

Before

events {}

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://backend:8080;
            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-Proto $scheme;
        }
    }
}

After

events {
    worker_connections 1024;
}

https {
		upstream was {
		    server backend:8080; #서비스명
		}
		
		server {
		    listen 80;
		    server_name dev.anifriends.site; # 발급한 도메인 주소
		    server_tokens off;
		
		    location /.well-known/acme-challenge/ { 
		        root /var/www/certbot; # Certbot을 통해 Let's Encrypt 인증서를 발급받을 때 사용하는 경로
		    }
		
		    location / {
		        return 301 https://$host$request_uri; # 모든 HTTP 요청을 HTTPS로 리다이렉션
		    }
		}
		
		server {
		    listen 443 ssl;
		    server_name dev.anifriends.site;
		    server_tokens off;
		
		    ssl_certificate /etc/letsencrypt/live/dev.anifriends.site/fullchain.pem; # SSL/TLS 인증서 경로
		    ssl_certificate_key /etc/letsencrypt/live/dev.anifriends.site/privkey.pem; # SSL/TLS 개인 키 경로
		    include /etc/letsencrypt/options-ssl-nginx.conf; # Let's Encrypt에서 제공하는 Nginx SSL 옵션
		    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
		
		    location / {
		        proxy_pass  http://was;
		        proxy_set_header    Host                $http_host;
		        proxy_set_header    X-Real-IP           $remote_addr;
		        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
		    }
		}
}

docker-compose.yml 경로에 .init-letsencrypt.sh 추가

#!/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="./data/certbot" # 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

신경써야하는 부분 예시

domains=dev.anifriends.site
rsa_key_size=4096
data_path="./data/certbot"
email="test@gmail.com" # Adding a valid address is strongly recommended
staging=0 
  • data_path : docker-compose.yml의 volumes의 data_path와 일치시켜야 한다.
  • staging=1
    • 빌드를 반복하면 Let’s Encrypt에서는 Request Limits를 걸고 있는데, 일정 요청을 초과하면 당분간 해당 도메인 주소에 SSL을 발급하지 못하게 된다.

  • staging=0
    • 테스트 빌드 시에 0으로 설정하면 요청 횟수 제한이 없다.

실행하기

docker-compose up --build
./init-letsencrypt.sh

springboot가 실행 중이어야 Fetching Connection refused 에러가 발생하지 않는다.

build 시 ssl 인증서가 없어서 nginx가 종료된 다음 shell 파일을 실행하면 nginx가 다시 연결된다.

쉘 파일 실행 성공 로그

Existing data found for dev.anifriends.site. Continue and replace existing certificate? (y/N) y
### Creating dummy certificate for dev.anifriends.site ...
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default
Generating a RSA private key
.........................................................................................................................++++
...............................................................................................................................++++
writing new private key to '/etc/letsencrypt/live/dev.anifriends.site/privkey.pem'
-----

### Starting nginx ...
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default
[+] Running 2/2
 ✔ Container backend  Running                                                                                                                                                                                                                                       0.0s 
 ✔ Container nginx    Started                                                                                                                                                                                                                                       0.0s 

### Deleting dummy certificate for dev.anifriends.site ...
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default

### Requesting Let's Encrypt certificate for dev.anifriends.site ...
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default
[+] Building 0.0s (0/0)                                                                                                                                                                                                                                   docker:default
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for dev.anifriends.site

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/dev.anifriends.site/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/dev.anifriends.site/privkey.pem
This certificate expires on 2024-02-25.
These files will be updated when the certificate renews.

NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
We were unable to subscribe you the EFF mailing list because your e-mail address appears to be invalid. You can try again later by visiting https://act.eff.org.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

### Reloading nginx ...
2023/11/27 20:10:57 [notice] 11#11: signal process started

인증서를 발급받았으면 docker-compose up --build 만 실행하면 된다. 이후에 shell 파일은 실행하지 않아도 된다.

마주친 에러

Fetching Connection refused

Certbot failed to authenticate some domains (authenticator: webroot). The Certificate Authority reported these problems:
  Domain: dev.anifriends.site
  Type:   connection
  Detail: {ec2 ip}: Fetching http://dev.anifriends.site/.well-known/acme-challenge/GobZNeHSTjHfFAETZGqphDyU6oAZf2gI9Egw56g1NSY: Connection refused
  • nginx와 springboot 컨테이너가 서로 연동되고 띄워져 있어야 쉘 파일 실행이 가능하다. 이 문제를 해결하다가 시간을 많이 소비하니 nginx 연결을 확인하자.

Permission Error

/data/certbot 에 쓰기 권한이 없어서 SSL .pem 파일이 생성이 되지 않았다.

chmod -R 777 data/

-R 옵션을 통해 하위 폴더에도 권한을 부여했다.

변경한 이미지 적용이 안되는 문제

docker rmi $(docker images -q)

존재하는 이미지를 삭제하고 다시 빌드해서 변경된 이미지를 적용했다.

SSL pem 파일 생성 후 재시도할 때 경로가 다른 문제

staging=0일 때, 기존 경로는 anifriends.kro.kr 이었는데 파일이 여러 번 생성될 경우 자동으로 기존 경로 + -0001 을 붙인다. nginx.conf 파일에서 ssl_certificate, ssl_certificate_key 부분의 경로가 다르면 에러가 발생하므로 확인하자.

참고

https://zinirun.github.io/2021/03/31/docker-nginx-certbot/

https://velog.io/@zero-black/Docker-compose-certbot-nginx-로-SSL-인증서-발급하기

https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71

https://github.com/wmnnd/nginx-certbot

https://community.letsencrypt.org/t/the-certificate-authority-failed-to-download-the-temporary-challenge-files-created-by-certbot-connection-refused/159426

profile
시도하고 More Do하는 백엔드 개발자입니다.

0개의 댓글