Hustle #7. (3부) AWS 인프라 초기 셋팅 - HTTPS 적용, 리버스 프록싱 설정, CloudFront 배포
안녕하세요.
저번 포스팅에선 AWS 아키텍처를 설계하고 관련 리소스를 생성했습니다.
다음 인프라 셋팅으로 세 가지 작업만을 남았습니다.
도메인 인증서를 발급하고 NginX 으로 HTTPS 를 적용합니다!
NginX 설정하는 김에 스프링 부트 서버 포트로 리버스 프록싱을 설정합니다.
저번에 가비아에 AWS Route53 네임 서버를 적용했는데요.
이를 기반으로 ACM 인증서를 발급하고 CloudFront 까지 배포해보도록 하겠습니다.
HTTPS 프로토콜을 적용해야 하는 이유
HTTPS 프로토콜을 적용하여 주고 받는 데이터를 암호화합니다.
중간에 데이터가 탈취 되어도 내용을 해독할 수 없게 만듭니다.
토큰 정보 등 중요한 데이터를 보호하여 보안을 강화합니다.
도메인 인증서 발급
Https 를 적용하려면 인증서를 발급해야 합니다.
AWS EC2 Ubuntu 환경에서 도메인 인증서를 발급합니다.
LetsEncrypt (Certbot) 으로 무료로 도메인 인증서를 발급할 수 있습니다.
letsencrypt 또는 certbot 패키지를 설치합니다.
$ sudo apt install letsencrypt
= ( 둘중 아무거나 )$ sudo apt install certbot
도메인 인증서를 발급합니다.
-d 속성에 내 도메인을 입력합니다.
참고로 제 도메인은 가비아에서 구매한 도메인입니다.
AWS Route53 네임서버 등록까지 마친 도메인입니다.
$ sudo letsencrypt certonly -d sport-hustle.com
인증서 발급에 필요한 정보를 입력합니다.
첫 번째로 이메일을 입력합니다.
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): 내이메일
그 다음 나오는 안내문에 동의합니다.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.
Requesting a certificate for sport-hustle.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/sport-hustle.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/sport-hustle.com/privkey.pem
This certificate expires on 2024-11-19.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
인증서는 /etc/letsencrypt/live/api.sport-hustle.com/ 디렉토리에 저장됩니다.
fullchain.pem 파일과 privkey.pem 파일이 저장됩니다.
다음으로 NginX 에서 SSL 설정 시 또 하나의 암호화 키를 추가할 수 있습니다.
바로 디피-헬만 (Diffie-Hellman) 암호화 키를 추가할 수 있는데요.
디피-헬만 암호화 키를 추가하면 보안을 강화하고 암호화 강도를 향상시킵니다.
OpenSSL 도구를 이용해 디피-헬만 키를 생성할 수 있습니다.
이를 /etc/letsencrypt/ 디렉토리에 저장합니다.
$ sudo openssl dhparam -out /etc/letsencrypt/ssl-dhparams.pem 2048
Generating DH parameters, 2048 bit long safe prime
...
NginX 기본 설정에서 HTTPS/SSL 설정을 해줍니다.
$ sudo vim /etc/nginx/sites-available/default
server {
root /var/www/HUSTLE_server;
server_name sport-hustle.com api.sport-hustle.com www.sport-hustle.com;
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/sport-hustle.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sport-hustle.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = api.sport-hustle.com) {
return 301 https://$host$request_uri;
}
if ($host = www.sport-hustle.com) {
return 301 https://$host$request_uri;
}
if ($host = sport-hustle.com) {
return 301 https://$host$request_uri;
}
listen 80 default_server;
listen [::]:80 default_server;
server_name sport-hustle.com api.sport-hustle.com www.sport-hustle.com;
return 404;
}
첫 번째 server 블록은 listen 443 ssl 으로 HTTPS 프로토콜 응답을 처리합니다.
ssl_certificate, ssl_certifcate_key, ssl_dhparam 설정으로 아까 만들었던 파일 경로를 입력합니다.
Certbot 에서 자동으로 추가했을 수도 있습니다.
리버스 프록시 설정
리버스 프록시는 NginX 에서 들어온 요청을 그대로 내부 포트로 전달해주는 서버를 말합니다.
NginX 에서 proxy_pass, proxy_set_header 으로 특정 포트(여기선 8080)로 요청을 전달합니다.
즉 외부에서 8080 포트에 접속하게끔 포트를 열고 직접 8080 포트로 요청을 보내지 않아도 됩니다.
NginX 가 받는 80 포트에 요청을 보내면, 그 요청을 8080 포트를 받는 내부 서버로 요청을 전달합니다.
내부 서버의 응답을 다시 사용자에게 전달해줍니다.
NginX 설정은 아래와 같이 할 수 있습니다.
모든 경로의 요청(location /)을 8080 포트의 백엔드 서버로 보냅니다.
...
server_name sport-hustle.com api.sport-hustle.com www.sport-hustle.com;
location / {
proxy_pass http://localhost:8080;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
...
리버스 프록시를 왜 사용하는 걸까요?
NginX 에서 요청을 담당하므로 실제 서버의 위치를 숨길 수 있습니다.
현재는 로컬 호스트의 다른 포트로 요청을 보내지만, 다른 호스트로도 보낼 수 있습니다.
실제 위치를 숨겨 내부 네트워크의 보안을 향상시킬 수 있습니다.
이제 NginX 를 테스트하고 정상적이면 설정을 다시 불러옵니다.
NginX 문법 오류 같은 설정 오류가 있다면 표시해줍니다.
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo service nginx reload # 서버 중지 없이 설정만 새로고침
$ sudo service nginx restart # 서버 중지 후 시작
크론탭을 활용한 자동 인증서 갱신
인증서는 만료되면 LetsEncrypt (Certbot) 으로 갱신해야 합니다.
수동으로 갱신해줄 수도 있지만, Ubuntu 에서 크론탭을 활용할 수 있습니다.
설정한 주기마다 명령어를 실행시킬 수 있게 해줍니다.
$ sudo crontab -e
원하는 편집기를 선택한 후 아래 내용을 추가합니다.
# m h dom mon dow command
10 1 * * * /usr/bin/letsencrypt renew >> /var/log/le-renew.log
10 1 * * * /usr/sbin/service nginx reload
매일 오전 1시 10분마다 인증서를 갱신합니다.
16시 10분으로 한 이유는 16시 10분 근처로 인증서가 만료되기 때문입니다.
인증서 만료 시간은 브라우저에서도 확인할 수 있고 아래처럼 명령어로도 확인이 가능합니다.
$ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: sport-hustle.com
Serial Number: ...
Key Type: ECDSA
Domains: sport-hustle.com api.sport-hustle.com www.sport-hustle.com
Expiry Date: 2023-11-18 16:07:14+00:00 (VALID: 78 days)
Certificate Path: /etc/letsencrypt/live/sport-hustle.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/sport-hustle.com/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AWS EC2 에서 스프링 서버 구동
헬스체크를 위한 루트 경로의 컨트롤러를 작성합니다.
단순 200 OK 상태 응답만을 보여줍니다.
package com.sporthustle.hustle;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "Health", description = "헬스 체크 API")
@RequestMapping
@RestController
public class HealthController {
@Operation(summary = "헬스 체크 API", description = "헬스 체크 API 입니다.")
@GetMapping
public ResponseEntity<String> health() {
return ResponseEntity.ok(null);
}
}
EC2 환경이 아닌 로컬에서 프로젝트를 빌드합니다.
빌드 결과물은 /build/libs 에 jar 파일로 생성됩니다.
$ ./gradlew build
다음 scp 도구로 jar 파일을 AWS EC2 서버로 전송합니다.
PEM 키로 인증하고 jar 파일을 AWS EC2 서버의 /home/ubuntu 경로로 전달합니다.
$ scp -i ~/.ssh/hustle/hustle-production-keypair.pem ./build/libs/hustle-v0.0.1-SNAPSHOT.jar ubuntu@{AWS EC2 IP}:/home/ubuntu
다시 EC2 환경에서 nohup 으로 스프링 서버를 백그라운드로 구동합니다.
$ nohup java -jar hustle-v0.0.1-SNAPSHOT.jar &
아까 NginX HTTPS 설정과 리버스 프록시 설정까지 다 했기에,
도메인으로 바로 접속을 시도해봅시다.
위와 같이 흰 화면에 HTTPS 프로토콜이 적용된 모습을 확인할 수 있었습니다.
NginX 를 사용하지 않고도 HTTPS 를 적용할 수 있는 방법
EC2 내에서 인증서를 직접 발급하지 않고 NginX 을 설치하지 않더라도 HTTPS 를 적용할 수 있는 방법이 있습니다.
뿐만 아니라 리버스 프록싱도 적용할 수 있습니다.
바로 AWS ELB (Elastic Load Balancing) 서비스를 이용하는 방법입니다.
뒤에서 이야기할 ACM 인증서를 발급하면 HTTPS 프로토콜이 적용된 AWS ELB (Elastic Load Balancing) 주소를 얻을 수 있습니다.
AWS ELB 의 리스너로 스프링 부트 서버 포트인 TCP 8080 포트를 적용한다면, HTTPS 프로토콜과 로드밸런싱 처리되는 리버스 프록싱까지 적용할 수 있습니다.
도메인은 Route53 에서 api.sport-hustle.com 레코드를 만들어 별칭으로 AWS ELB 를 선택하면 매핑이 됩니다.
허슬 프로젝트에선 프로필을 활용한 무중단 배포 설정을 NginX 으로 구현해야 했기에,
AWS ELB 를 사용하지 않고 NginX 으로 직접 사용했습니다.
ACM 인증서 발급
다음으로 CloudFront 를 설정하려고 합니다.
CloudFront 는 S3 파일에 접근할 때 앞 단에서 캐싱을 해주는 서비스입니다.
CloudFront 를 생성하려면, ACM 인증서를 발급해야 합니다.
ACM 은 SSL/TLS 인증서를 발급해주는 서비스입니다.
여기서 발급한 인증서를 AWS ELB (Elastic Load Balancing), AWS CloudFront 같은 서비스에서 사용합니다.
CloudFront 에서 사용할 인증서는, ACM 버지니아 북부에서 발급한 것만 취급합니다.
따라서 리전을 서울에서 버지니아 북부로 변경해야 합니다.
변경한 다음 인증서를 요청합니다.
기본 선택인 퍼블릭 인증서 요청을 선택하여 다음으로 넘어갑니다.
다음으로 도메인 이름을 입력합니다.
두 번째 도메인 이름으로 서브도메인에 와일드카드(*)를 붙입니다.
모든 서브도메인에 대해서도 적용되도록 했습니다.
검증 방법은 기본 설정인 DNS 검증으로 선택합니다.
모든 설정이 끝나면 요청 버튼을 누릅니다.
인증서 상세 페이지에서 도메인과 CNAME 이름을 확인할 수 있는데요.
이를 Route 53 에서 레코드를 생성해야 인증받을 수 있습니다.
Route 53 에서 레코드 생성 버튼을 눌러, 레코드를 생성합니다.
그 다음 5분 ~ 10분 정도의 시간을 기다리면 아래와 같이 성공이 나타납니다.
만약 인증에 실패하면 '부적격' 이라고 나타납니다.
이 경우엔 구입한 도메인이 AWS Route 53 호스팅 영역의 네임서버(NS)가 등록되지 않은 경우 발생합니다.
또는 Route 53 에서 해당 CNAME 의 레코드가 없어서 발생할 수도 있습니다.
구입한 도메인의 네임서버와 Route 53 레코드를 다시 한 번 점검해보시기 바랍니다.
점검 후 이상이 없으면 다시 인증서를 요청해 봅시다.
CloudFront 배포 생성하기
ACM 인증서까지 발급했습니다.
이제 CloudFront 배포를 바로 생성해봅시다.
원본 도메인은 이전에 만든 S3 버킷을 선택합니다.
그 다음으로 원본 액세스를 설정합니다.
'Legacy access identifies' 옵션을 선택합니다.
이 옵션을 선택하면 CloudFront 요청을 통해서만 S3 에 접근할 수 있게 됩니다.
다음으로 원본 액세스 ID 목록을 선택해야 하는데요, 처음에는 없으니 '새 OAI 생성' 버튼을 누릅니다.
원본 액세스 ID 는 별다른 설정 없이 바로 생성이 가능합니다.
생성을 마친 후 목록에서 선택합니다.
버킷 정책에서 '예, 버킷 정책 업데이트' 옵션을 선택합니다.
CloudFront 배포와 동시에 S3 버킷에 자동으로 정책이 업데이트 됩니다.
뷰어는 HTTPS only 으로 선택합니다.
다음으로 세부 설정입니다.
가격 분류는 서울에서만 사용할 예정이므로, 아시아가 포함된 옵션을 선택했습니다.
대체 도메인 이름은 storage 서브 도메인으로 사용할 예정이라, storage.sport-hustle.com 으로 입력했습니다.
이후 Route53 에서 별칭으로 연결할 예정입니다.
만약 대체 도메인 이름과 Route53 에서 생성할 서브도메인과 맞지 않다면, 별칭 목록에서 나타나지 않으니 주의해주세요.
CloudFront 대체 도메인을 와일드카드 (*.sport-hustle.com) 으로 설정해도 목록에서 나타나지 않아 정확히 명시해야 합니다.
사용자 정의 SSL 인증서는 앞서 생성한 ACM 인증서를 선택합니다.
만약 생성했는데도 ACM 인증서가 나타나지 않는다면, 버지니아 북부에서 생성했는지 다시 한 번 확인해주세요.
또는 아래의 '인증서 요청' 버튼을 눌러 버지니아 북부 ACM 페이지로 이동할 수 있습니다.
모든 세부 설정이 끝나 CloudFront 를 배포합니다.
이제 S3 버킷의 링크로 접속하면 아래와 같이 Access Denied 오류가 발생합니다.
AWS S3 버킷 페이지에서 권한 > 정책을 보면 아래와 같이 업데이트 되어 있습니다.
CloudFront Origin Access Identity 가 추가된 걸 확인할 수 있습니다.
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
...
{
"Sid": "2",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {고유아이디}"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::{버킷이름}/*"
}
]
}
만약 실수로 버킷 정책 업데이트 선택을 하지 않았다면, 수동으로 작성해주면 됩니다.
CloudFront OAI 고유아이디를 입력을 해줘야 하는데요.
CloudFront 페이지에서 보안 > 원본 액세스 페이지 > ID(레거시) 목록에서 확인할 수 있습니다.
마지막으로 Route53 호스팅 영역에 서브도메인을 등록합니다.
CloudFront 에서 등록한 CNAME 서브도메인과 마찬가지로 storage 를 입력합니다.
값/트래픽 라우팅 대상에서 'CloudFront 배포에 대한 별칭' 을 선택합니다.
그리고 생성한 CloudFront 배포를 선택합니다.
'단순 레코드 정의'로 레코드를 추가합니다.
만약 별칭 목록이 나타나지 않는다면, CloudFront 배포가 잘 되었는지, CloudFront CNAME 설정이 내가 입력했던 것과 일치하는지 다시 한 번 검토해주시기 바랍니다.
마지막으로 Route53 에서 설정한 CloudFront 도메인으로 객체가 잘 보이는지 확인합니다.
아래처럼 CloudFront 배포로 객체가 잘 보인다면 성공입니다.
마무리
이번 포스팅에선 Ubuntu 에서 SSL/TLS 인증서를 LetsEncrypt(CertBot) 으로 발급받고
발급받은 인증서를 NginX 에서 HTTPS 를 설정했습니다.
스프링 부트 포트로 리버스 프록싱 설정도 해주었고
AWS ACM 으로 인증서를 발급받고 AWS CloudFront 배포하여 Route 53 서브도메인으로 연동했습니다.
인프라 설정이 마무리가 되었는데요.
나머지 인프라 설정은 배포를 자동화할 때 다시 한 번 건드려보도록 하겠습니다.
긴 글 읽어주셔서 감사합니다.