Certbot으로 HTTPS 적용하기 with Nginx

신범철·2024년 3월 14일
0
post-thumbnail

💚 서론

프론트에서 사용하는 정적 웹사이트 호스팅 서비스인 Vercel은 HTTPS를 통해서 배포된다.

하지만 백엔드에서는 HTTP로 동작하기 때문에 CORS 에러가 발생한다.

웹서버의 TLS 인증서를 사용해 웹사이트를 HTTPS로 열 수 있게끔 Nginx 프록시 서버에 TLS 인증서를 적용하도록 구현해보자

💚 HTTPS를 사용하는 이유

유저가 어떤 웹사이트에 보내는 정보를 다른 누군가 훔쳐보지 못하도록 하기 위함

EX)

HTTP로 만들어진 네이버에 로그인 시 아이디와 비밀번호를 네이버의 서버로 보낸다고 했을 때

입력한 텍스트 그대로 누구든 알아볼 수 있는 형식으로 보내진다.

만약 누군가가 이 정보를 중간에 들여다 본다면 유저는 아이디와 비밀번호를 알게 되는 것이다.

그래서 나온 것이 HTTPS이다. HTTPS로 만들어진 네이버에 로그인 시에는

유저의 아이디와 비밀번호를 네이버 서버의 인증서와 약속한 텍스트로 인코딩해서 보낸다.

다른 누군가가 로그인 정보를 중간에 들여다 보더라도 알아볼 수가 없다.

💚 HTTP vs HTTPS

  • HTTP
    • HTTP(HyperText Transfer Protocol) 는 클라이언트와 서버 사이에서 데이터를 주고 받기 위한 프로토콜이다.
    • HTTP는 80번 포트를 사용하고 있으며, 보통 header와 body로 나누어 데이터를 전달한다.
    • HTTP는 별도의 암호화가 되어있지 않은 프로토콜이므로 민감 정보를 제 3자가 가로챌 수 있다.
  • HTTPS
    • HTTPS(HyperText Transfer Protocol Secure) 는 SSL/TLS 인증서를 통해 기존 HTTP에 보안 계층을 추가한 프로토콜이다.
    • HTTPS는 443번 포트를 사용하고 있다.

💚 HTTPS의 동작과정

HTTPS는 공개키 암호화 방식과 대칭키 암호화 방식의 장점을 활용해 하이브리드를 사용한다.

데이터를 대칭키 방식으로 암복호화하고, 공개키 방식으로 대칭키를 전달한다.

  1. 클라이언트와 서버에 접속하여 Handshking과정으로 서로 탐색
    1. Client Hello
      • 클라이언트가 서버에게 전송할 데이터
        • 클라이언트 측에서 생성한 랜덤 데이터
        • 클라이언트 ↔ 서버 암호화 방식 통일을 위한 클라이언트가 사용할 수 있는 암호화 방식
        • 이전에 이미 Handshaking 기록이 있다면 자원 절약을 위해 기존 세션을 재활용하기 위한 세션 아이디
    2. Server Hello
      • Client Hello에 대한 응답으로 전송할 데이터
        • 서버 측에서 생성한 랜덤 데이터
        • 서버가 선택한 클라이언트의 암호화 방식
        • SSL/TLS 인증서
    3. Client 인증 확인
      • 서버로부터 받은 인증서가 CA에 의해 발급되었는지 본인이 가지고 있는 목록에서 확인하고, 목록에 잇다면 CA 공개키로 인증서 복호화
      • 클라이언트 ↔ 서버 각각의 랜덤 데이터를 조합하여 pre master secret 값 생성(데이터 송수신시 대칭키 암호화에 사용할 키)
      • pre master secret 값을 공개키 방식으로 서버에 전달(공개키는 서버로부터 받은 인증서에 포함)
      • session key 생성
    4. Server 인증 확인
      • 서버는 비공개키로 복호화하여 pre master secret 값 취득(대칭키 공유)
      • session key 생성
  2. 데이터 전송
    1. 서버와 클라이언트는 session key를 활용해 데이터를 암복호화하여 데이터 송수신
  3. 연결 종료 및 session key 폐기

자세한 설명 링크

  • HTTPS의 장점
    • 세션키를 통해 데이터를 암호화한 형태로 전달하기 때문에 민감 정보를 보호할 수 있다.
    • 검색 엔진에게 HTTP보다 HTTPS가 신뢰성이 더 높기 때문에, 웹사이트 컨텐츠 순위를 더 높게 받을 수 있다.

서버(WAS)에 HTTPS를 적용할 때는 보통 리버스 프록시 서버를 두어 TLS 인증에 대한 엔드포인트 역할을 수행하도록 한다. 이렇게 하면, TLS 인증과 같은 부가 기능 처리와 비즈니스 로직을 분리하여 한쪽으로 치우치는 부하를 분산 시킬 수 있다.


💚 리버스 프록시 & 포워드 프록시

프록시(Proxy) 란, 클라이언트와 서버 사이에 위치한 중계 서버로써 대리자 역할을 하는 것을 말한다.

클라이언트와 서버 사이에 프록시 서버를 두면, 보안이 강화되고 통신 성능이 높아진다는 장점이 있다.

크게 포워드 프록시(Forward Proxy)와 리버스 프록시(Reverse Proxy)로 나눌 수 있다

💚 포워드 프록시(Forward Proxy)

  • 포워드 포록시란?
    • 포워드 프록시는 클라이언트와 인터넷 사이에 존재하는 프록시 서버를 의미한다.
    • 클라이언트가 서버에 직접 요청하지 않고, 포워드 프록시 서버가 해당 요청을 받아서 서버에 전달한 후 그 응답 값을 클라이언트에게 전달한다.
  • 역할
    • 주로 캐시(Cache) 용도로 많이 사용된다.
    • 기업에서는 내부망에 포워드 프록시를 두어 정해진 사이트만 연결하는 등의 보안 용도로도 사용한다.

💚 리버스 프록시(Reverse Proxy)

  • 리버 포록시란?
    • 리버 프록시는 인터넷과 서버 사이에 존재하는 프록시 서버를 의미한다.
    • 클라이언트의 요청을 서버가 직접 받지 않고, 리버스 프록시 서버가 해당 요청을 받아서 서버에 전달한 후 그 응답 값을 인터넷으로 전달한다. 클라이언트는 프록시 뒤의 서버의 존재를 모르게 된다.
  • 역할
    • 로드 밸런서의 역할로 사용하여, 집중적으로 발생하는 부하를 여러 서버로 나눠 보낼 수 있다.
    • 클라이언트의 요청을 나눠서 보낼 수 있으므로, 무중단 배포 시 배포 중인 서버에 요청을 보내지 않도록 할 수 있다.
    • 서버는 내부망에 넣고, 리버스 프록시 서버는 외부에 두어 보안을 강화시킬 수 있다.

리버스 프록시 서버는 별도의 웹 서버(Apache, Nginx)를 클라이언트와 WAS 사이에 두는 방식으로 구현된다.

💚 Apache vs Nginx

웹 서버(Web Server) 란, HTTP(HTTPS)를 통해 클라이언트에서 요청하는 문서나 이미지 파일 등의 정적 리소스를 전송해주는 서버를 말한다. 대표적으로 Apache HTTP ServerNginx가 있다.

  • 웹 서버의 역할
    • 동적 리소스를 응답하는 WAS(웹 어플리케이션 서버) 앞에 붙여, 단순한 정적 리소스를 반환하는 역할을 하여 WAS에 가해지는 부하를 줄여준다.
    • WAS의 존재를 숨겨서 보안을 강화시킨다.
    • WAS에 장애가 났을 때 에러 페이지를 응답함으로써 사용자 경험을 해치지 않도록 한다.

💚 Apache HTTP Server란?

Apache는 프로세스 / 스레드 기반 구조로 동작하는 웹 서버 이다.

클라이언트로부터 요청이 들어오면 새 커넥션을 생성하기 위해 새로운 프로세스(혹은 스레드)를 할당한다.

두 가지 방식으로 프로세스(혹은 스레드)를 분배한다.

  1. Prefork 방식
    • 하나의 클라이언트 요청에 하나의 자식 프로세스를 할당한다.
    • 따라서 요청이 늘어나면 프로세스도 함께 늘어난다.
    • 하나의 자식 프로세스 당 하나의 스레드를 가지며, 총 1024개의 자식 프로세스를 할당 가능하다.
  2. Worker 방식
    • 하나의 요청에 하나의 프로세스를 할당하지 않고, 하나의 요청마다 자식 프로세스에 하나의 스레드를 새로 할당한다.
    • 하나의 자식 프로세스 당 여러 개의 스레드를 가진다.
    • 따라서 Prefork에 비해 비교적 메모리를 적게 사용하다.

Apache가 등장하고 시간이 지나면서 PC의 보급률이 높아져 점점 트래픽이 많아졌다.

클라이언트 요청 하나당 하나의 프로세스(혹은 스레드)를 생성하는 구조는 C10K 의 문제가 발생하게 된다.

  • C10K란? C10K 문제란, Connection 10,000개 문제 라는 의미로, 요청에 의해 생겨나는 커넥션들이 10,000개를 넘어가지 못하는 문제를 말한다. 당시 CPU는 충분히 요청을 처리할 수 있었으나 Apache 구조 상, 한 개 커넥션 당 하나의 프로세스(스레드) 할당으로 인해 메모리가 버티지 못했다.

C10K 문제를 극복하고자 Nginx가 등장한다.

💚 Nginx란?

Nginx는 Event-Driven 기반 구조 로 작동하는 웹 서버이다.

Apache와 달리 하나의 고정된 프로세스만 생성하고, 새로 들어오는 클라이언트의 요청은 이벤트 핸들러를 이용해 비동기 방식으로 처리하는 방식

요청이 늘어날 때 추가적으로 프로세스(혹은 스레드)를 할당하지 않기 때문에 메모리를 적게 사용한다.

Master-Worker 방식

  • Nginx는 설정 파일을 읽고 Worker 프로세스를 생성하는 Master 프로세스 와, 실제로 클라이언트의 요청을 처리하는 Worker 프로세스 로 이루어진다.

  • Nginx를 구동하면, Master 프로세스는 정해진 수 만큼의 Worker 프로세스를 생성한다.

    • 보통 CPU 코어 개수만큼 Worker 프로세스를 생성하여 각 코어의 컨텍스트 스위치 비용을 줄인다.
  • Worker 프로세스는 Working Queue에 담긴 이벤트(커넥션 연결, 요청, 커넥션 종료 등)들을 순차적으로 처리한다. 새로운 프로세스(혹은 스레드)를 생성하지 않기 때문에 이 Worker 프로세스는 끊임없이 작업을 수행한다.

  • 따라서 커넥션마다 프로세스(혹은 스레드)를 할당하던 Apache와 달리 메모리를 적게 점유하여 효율적으로 요청을 처리한다.

    향상된 Nginx(Feat. Thread pool)

Working Queue에 시간이 오래 걸리는 작업이 들어오면 Worker 프로세스가 해당하는 이벤트를 처리하는 동안 Queue에 쌓여있는 다른 이벤트들은 계속 대기하게 된다. ⇒ 성능 저하

그래서 Nginx에서는 스레드 풀(Thread Pool)을 추가하여, 오래 걸리는 작업을 처리하는 스레드 묶음을 따로 만들어 이를 해결한다. ⇒ Nginx 1.7.11 버전부터 도입

  • 동작과정
    • 오래 걸리는 작업은 Worker 프로세스에서 Task Queue로 전달된다.
    • Task Queue는 스레드 풀에 있는 스레드 중 하나에 작업을 할당한다.
    • 작업이 완료되면 다시 Worker 프로세스에게 결과를 전달한다.

💚 결론

Nginx를 통해서 Apache에 비해 처리 가능한 동시 커넥션 양이 1000배 정도 증가하게 되었다.

또한 동일한 커넥션 일 때 Apache에 비해 약 2배 정도 속도가 향상되었다.

지금부터 Nginx를 통해서 리버스 프록시용 웹서버를 구현해보겠다.

💚 진짜 적용하기

1. 도메인 구입 및 DNS 등록

가비아 홈페이지에서 원하는 도메인을 검색한 후 신청하기로 구매한다.

필자는 [review-ranger.store](http://review-ranger.store) 를 구매하였다.

My가비아 → DNS 관리 → 레코드 수정을 통해 DNS에 EC2 인스턴스의 public IP를 등록한다.

필자는 [api.review-ranger.store](http://api.review-ranger.store)로 백엔드 API 도메인을 지정해주었다.

2. EC2에 Nginx 설치

필자의 EC2 환경은 Ubuntu 22.04.3 LTS를 사용하고 있다.

2.1 설치 가능한 패키지 목록 최신화

$ sudo apt update

2.2 Nginx 설치

$ sudo apt install nginx

2.3 Nginx 설치 확인

$ nginx -v

=> nginx version: nginx/1.18.0 (Ubuntu)

2.4 Nginx 실행 확인

$ sudo systemctl status nginx

=> running이 나오면 실행되고 있는 것이다.

2.5 Nginx Routing 코드 작성

$ cd /etc/nginx/sites-available/

# api.review-ranger.store 파일 생성 및 수정
$ sudo vi api.review-ranger.store
# api.review-ranger.store 파일 내용
server {
		server_name api.review-ranger.store;

    location / {
        proxy_pass http://localhost: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;
    }
}

=> **server** : 해당 도메인으로 요청이 들어오면 어떻게 처리할 것인지를 적는다.
=> **server_name** : 어떤 도메인의 요청을 처리할 것인지 명시한다. 여러 개도 지정 가능
=> **location** : 어떤 endpoint 요청을 처리할 것인지 작성한다.
=>   **proxy_pass** : 해당 요청을 지정한 경로로 포워딩한다.(위에서는 스프링 서버와 매칭)
=>   **proxy_set_header** : 포워딩 시의 헤더 값을 정의한다.
# 서버와 설정 파일 연결
$ sudo ln -s /etc/nginx/sites-available/api.review-ranger.store /etc/nginx/sites-enabled/api.review-ranger.store

# 파일이 잘 동작하는지 확인
$ sudo nginx -t

=> nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
=> nginx: configuration file /etc/nginx/nginx.conf test is successful

# Nginx 재시작
$ sudo service nginx restart

3. EC2에 CertBot 설치

# snap 설치
$ sudo apt install snapd
$ sudo snap install core && sudo snap refresh core

# CertBot 설치
$ sudo snap install -–classic certbot

# 심볼릭 링크 생성
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

# 버전 확인
$ certbot --version

4. TLS 설치하기

# 인증서 설치
$ sudo certbot --nginx -d api.review-ranger.store

=> 이메일 입력 beombu13@gmail.com
=> 약관 동의 Y
=> successfully received certificate.
=> ...
=> (공개 키 저장 위치) Certificate is saved at: /etc/letsencrypt/live/api.review-ranger.store/fullchain.pem
=> (비밀 키 저장 위치) Key is saved at: /etc/letsencrypt/live/api.review-ranger.store/privkey.pem
=> (인증서 유효기간) This certificate expires on 2024-05-05.

# nignx 재시작
$ sudo service nignx restart

5. TLS 인증서 확인

TLS 인증서를 받았으니 브라우저에서 접속해서 확인하자.

인증서도 아래 명령어를 통해 확인할 수 있다.

# 인증서 확인
$ sudo certbot certificates

=>
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: api.review-ranger.store
    Serial Number: 40d84bac8226ac3d8e6eb8b834a337ed046
    Key Type: ECDSA
    Domains: api.review-ranger.store
    Expiry Date: 2024-05-05 04:13:34+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/api.review-ranger.store/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/api.review-ranger.store/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

6. TLS 인증서 자동 갱신

Let’s Encrypt로 만들어진 TLS 인증서는 90일마다 갱신이 필요하다.

Certbot 패키지는 인증서가 만료되기 전에 자동으로 갱신하는 cron 작업 또는 시스템 타이머와 함께 제공된다.

/etc/cron.d 에 자동으로 갱신 시켜주는 커멘드가 추가되어 있다. 구성을 변경하지 않는 한 Certbot을 다시 실행 할 필요가 없다.

# 인증서 자동 갱신 테스트
$ sudo certbot renew --dry-run

$ cd etc/

# 타이머 조회
$ systemctl list-timers

=> **snap.certbot.renew.service이 실행되고 있는지 확인**

NEXT                        LEFT        LAST                        PASSED       UNIT                           ACTIVATES
Tue 2024-02-13 16:09:00 KST 13min left  n/a                         n/a          snap.certbot.renew.timer       snap.certbot.renew.service
...

👁️ 참고문헌

profile
https://github.com/beombu

0개의 댓글