개요
최근 프로젝트 진행 중 Nginx Proxy 구성 요청을 받아 진행했던 기록을 남깁니다.
문제점
클라이언트에서 인터넷에 운영중인 Web-server에 접근하고 싶어 했으나. 기본적으로 On-prem 환경에서 인터넷 접근이 보안상 불가한 상황이었습니다. 따라서, DX로 연결된 AWS를 통해 Nginx Proxy를 구성하고, AWS 경유해 인터넷에 운영중인 Web-server에 접근하기를 원했습니다.
요구사항
1. Client는 Nginx Proxy를 통해 Public Web-server에 접근 가능해야 합니다.
2. 사용하는 도메인 주소는 총 3개 입니다.
3. Client - Nginx - Origin Web 간의 통신은 HTTPS(보안 웹), WSS(보안 웹소켓)을 사용해야 합니다.
4. Client의 요청 URL을 그대로 Origin Web에 전달해야 합니다.
구성하기
구현 순서
1. 공인 인증서를 사용하는 Web Server, Web Socket Server를 구성합니다.
2. Nginx가 사용할 사설 공인인증서를 발급하고, 클라이언트에 적용합니다.
3. Nginx에 Proxy를 구성합니다.
Web, Web Socket 서버 구성 (아키텍터 상 Web-server)
위 아키텍처 중 Web Server 구성에 관한 부분입니다. 2대의 EC2 Instance를 사용합니다.
Nginx Public Web Server
Nginx 서비스를 설치하고, Certbot을 사용해 Let's Encrypt 공인인증서 발급 후 HTTPS 서비스를 게시합니다. 이를 위해선 공인 DNS를 구성해야 합니다. AWS EC2 Instance의 Amazon Linux 2023을 사용합니다.
1. 관리자 권한을 위해 `sudo su`를 입력합니다.
2. yum 패키지를 통해 nginx를 설치하고, systemctl을 통해 서비스 시작 및 자동 시작을 등록합니다.
yum update
yum install nginx
systemctl enable nginx.service
systemctl restart nginx.service
3. (선택사항) `curl localhost`를 통해 nginx가 잘 구동중인지 확인합니다.
4. DNS A 레코드를 Nginx 서버의 공인IP로 등록합니다. nslookup을 통해 조회 합니다.
5. Certbot을 통해 Lets's Encrypt 공인인증서 발급 및 HTTPS 서비스를 게시합니다. 아래 명령으로 certbot을 설치합니다.
yum install certbot python-certbot-nginx
nginx 구성 파일에 도메인을 등록합니다. /etc/nnginx/conf.d 디렉토리에 domain-name.conf 라는 파일을 만듭니다. demo.te.kocron.com 이라는 도메인을 사용하는 서비스 구성으로 작성합니다.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name demo.te.kocron.com;
}
Nginx 서비스를 재기동 합니다.
systemctl restart nginx.service
아래 certbot 명령으로 demo.te.kocron.com에 대한 도메인 등록을 진행합니다. 이메일 주소 입력과 약관을 동의해야 합니다.
certbot --nginx -d demo.te.kocron.com
성공적으로 작업이 완료되면 domain-name.conf 에 구성파일이 아래와 같이 변경되고, 공인인증서를 사용하는 nginx 웹서버가 구성됩니다.
server {
server_name demo.te.kocron.com;
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/demo.te.kocron.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/demo.te.kocron.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = demo.te.kocron.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 default_server;
listen [::]:80 default_server;
server_name demo.te.kocron.com;
return 404; # managed by Certbot
}
`demo1.kocron.com` 도메인에 대해서도 동일하게 구성을 진행합니다.
Web Socket Server
Web Socket Server는 Node.js 를 통해 구동합니다. 먼저 Node를 설치합니다.
sudo yum install yum-utils
sudo yum update
sudo yum install g++ make
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
npm install websocket
websocket.js 파일을 생성하고 아래 코드를 붙여 넣습니다.
var WebSocketServer = require('websocket').server;
var http = require('http');
var clients = [];
var idlist = [];
var id = 0;
var array = [];
var server = http.createServer(function(request, response) {
console.log(new Date() + ' Received request for ' + request.url);
// 요청 URL 경로가 '/session'인 경우에만 처리
if (request.url === '/session') {
console.log(new Date() + ' Received request for session');
// WebSocket 요청 처리
wsServer.handleRequest(request, response);
} else {
response.writeHead(404);
response.end();
}
});
server.listen(9000, function(){
console.log((new Date()) + 'Server is listening on port 9000')
});
wsServer = new WebSocketServer({
httpServer: server,
autoAcceptConnections: false
});
function originIsAllowed(origin) {
return true;
}
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
request.reject();
console.log((new Date()) + 'Connection from origin' + request.origin + 'rejected.');
return;
}
var connection = request.accept(null, request.origin);
clients.push(connection);
idlist[request.key] = id++;
console.log((new Date()) + 'Connection accepted.');
for(var i=0; i<array.length;i++){
console.log(array[i]); connection.sendUTF(array[i]);
}
connection.on('message', function(message){
if (message.type === 'utf8'){
console.log('Received Message: ' + message.utf8Data);
msg = message.utf8Data;
if(msg.indexOf("move") != -1){
if(array.length < 50000){
array.push(msg);
}else{
array.shift();
array.push(msg);
}
}else if(msg.indexOf("refresh") != -1){
array = [];
}else{
}
clients.forEach(function(cli){
cli.sendUTF(msg);
});
}
else if (message.type ==='binary'){
console.log('Received Binary Message of' + message.binaryData.length + 'bytes');
}
});
connection.on('close', function(reasonCode,description) {
console.log((new Date() + 'Peer' + connection.remoteAddress + 'desconnected.'));
});
});
websock.js 파일에 실행 권한을 추가합니다.
chmod +x websock.js
`node websock.js`명령으로 웹소켓 서버를 실행합니다.
웹소켓 테스트는 Simple Web Socket Client 크롬 확장 프로그램을 통해 테스트 합니다.
WebSocket Test Client
A Simple tool to help test WebSocket Service
chromewebstore.google.com
웹소켓의 경우 http -> ws, https -> wss와 매핑된다고 생각하시면 됩니다. 원래라면 인증서를 설정한 wss로 접근해야 하지만, 부득이하게 ws로 테스트 합니다.
server location에 web socket 서버의 주소와 포트번호를 입력하고, [open]을 클릭하면 web socket이 활성화 되며, Request에 메시지를 입력하고 Send를 클릭하면 Message Log에 해당 내용이 출력되는것을 확인할 수 있습니다.
Nginx Proxy가 사용할 사설인증서 구성 (아키텍터 상 Nginx에서 작업)
Client <-> Nginx Proxy 통신에 적용할 사설인증서를 구성합니다. 공인 인증서 대신 사설 인증서를 사용하다 보니, 자체 CA 기관 인증서를 발급하고, Client 단에 신뢰할 수 있는 인증기관 인증서로 설치해야 합니다. 그 후 자체 CA 기관에 Nginx가 사용할 Server 인증서에 대해 서명을 요청하고, 이를 적용합니다. 현재 Sector에서는 자체(root) CA 구성 및 Client 설치에 대해 작성합니다.
아래 구성은 Nginx-Proxy 서버에서 진행해야 합니다. 위에서 구성한 서버외 별도의 EC2 Instance에서 진행합니다.
우선 Self-sigend root CA를 발급합니다. `Enter PEM pass phrase :` 문구가 나오면 비밀번호를 입력합니다.
cd ~ # /home/ec2-user 경로 입니다.
mkdir ssl && cd ssl
openssl genrsa -des3 -out localCA.key 2048
`localCA.key`파일이 생성되면 private key를 이용해서 root CA 인증서를 아래 명령어로 생성합니다.
openssl req -x509 -new -nodes -key localCA.key -sha256 -days 365 -out localCA.pem
인증서 구성 정보를 아래와 같이 입력합니다. `te.kocron.com` 도메인을 사용하는 예시 입니다.
Enter pass phrase for localCA.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:KR
State or Province Name (full name) []:Seoul
Locality Name (eg, city) [Default City]:Shindo-rim
Organization Name (eg, company) [Default Company Ltd]:Cloudmt
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:*.te.kocron.com
Email Address []:demo@cloudmt.co.kr
윈도우 클라이언트에 인증서를 설치하기 위해 `.pem` 파일을 `.crt` 파일로 추출 합니다.
openssl x509 -outform der -in localCA.pem -out localCA.crt
변환된 `.crt` 파일을 scp를 사용해 Windows Client에 옮겨준 뒤 인증서를 설치합니다.
scp -i hakjun_osaka.pem ec2-user@13.208.245.223:/home/ec2-user/ssl/localCA.crt .
Windows로 옮긴 localCA.crt 파일을 실행하고, 다음과 같이 설치합니다.
Nginx Proxy 구성하기 (아키텍터 상 Nginx에서 작업)
위 Self-signed CA를 구성한 EC2 Instance에서 Nginx를 설치하고, Proxy 기능을 구현합니다. 먼저 아래 명령으로 Nginx 서버를 설치합니다.
yum update
yum install nginx
systemctl enable nginx.service
systemctl restart nginx.service
https 및 wss를 사용하기 위한 서버 인증서를 발급한 뒤 Self-signed CA에게 서명 요청을 진행합니다.
cd ~
cd ssl
openssl genrsa -out server.key 2048
CSR을 생성합니다.
openssl req -new -key server.key -out server.csr
`vim server.ext`를 통해 CA에 인증요청 시 참고 정보를 작성합니다. [alt_names]에서 사용할 도메인 주소를 입력합니다.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = demo.te.kocron.com
DNS.2 = demo1.te.kocron.com
DNS.3 = demo2.te.kocron.com
아래 명령을 수행하여 pass pharese를 입력면 `demo.te.kocron.com`, `demo1.te.kocron.com`, `demo2.te.kocron.com` 호스트를 `te.kocron.com` CA가 인증하는 인증서를 발급받을 수 있습니다.
openssl x509 -req -in server.csr -CA localCA.pem -CAkey localCA.key \
-CAcreateserial -out server.crt -days 365 -sha256 -extfile server.ext
Nginx Proxy가 사용할 Private 인증서 발급은 완료되었으니, Nginx Proxy 구성을 진행합니다.
우선 생성한 인증서를 nginx 폴더에 붙여넣습니다. 지금 부턴 `sudo su`로 root 권한으로 진행합니다.
sudo su
cp -arp /home/ec2-user/ssl/ /etc/nginx/
cd /etc/nginx/conf.d
`vi server.conf`를 통해 proxy를 구성합니다.
# nginx가 https 요청을 web socket으로 변환하기 위한 변수로 사용됩니다.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# upstream은 proxy 대상의 Pool을 지정합니다.
upstream websocket {
server demo2.te.kocron.com:9000;
}
server {
# 443 ssl, demo2.te.kocron.com으로 들어오는 패킷에 대해 처리합니다.
listen 443 ssl;
server_name demo2.te.kocron.com;
# ssl 통신을 위한 인증서 구성입니다. rootCA에게 서명받은 인증서 경로를 설정합니다.
ssl_certificate "/etc/nginx/ssl/server.crt";
ssl_certificate_key "/etc/nginx/ssl/server.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
# client로 부터 요청받은 모든 URL에 대해 proxy_pass로 http://websocket을 보냅니다.
# websocket은 인증서 적용이 되어있지 않아 http로 구성되었습니다.
location ~ ^/.*$ {
proxy_pass http://websocket;
# http -> websocket으로 변환하기 위해 header를 변경합니다.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
access_log /var/log/nginx/websocket.log;
}
}
server {
# 443 ssl, demo.te.kocron.com으로 들어오는 패킷에 대해 처리합니다.
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name demo.te.kocron.com;
# ssl 통신을 위한 인증서 구성입니다. rootCA에게 서명받은 인증서 경로를 설정합니다.
ssl_certificate "/etc/nginx/ssl/server.crt";
ssl_certificate_key "/etc/nginx/ssl/server.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
# client로 부터 요청받은 모든 URL에 대해 proxy_pass를 사용해 https://demo.te.kocron.com 쪽으로 보냅니다.
location ~ ^/.*$ {
proxy_pass https://demo.te.kocron.com;
proxy_set_header Host $host;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name demo1.te.kocron.com;
ssl_certificate "/etc/nginx/ssl/server.crt";
ssl_certificate_key "/etc/nginx/ssl/server.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
location = / {
proxy_pass https://demo1.te.kocron.com/;
proxy_set_header Host $host;
}
}
nginx를 재기동 합니다.
systemctl restart nginx.service
Windows의 hosts파일을 수정합니다. `C:\Windows\System32\drivers\etc\hosts` 경로에서 아래 내용을 추가합니다.(관리자 권한)
# Copyright (c) 1993-2009 Microsoft Corp.
---생략---
13.208.164.94 demo.te.kocron.com demo1.te.kocron.com demo2.te.kocron.com
웹 브라우저로 demo.te.kocron에 접속해 사설인증서로 https 접근이 가능함을 확인합니다.
Proxy의 Access log를 확인해보면 클라이언트 요청을 304 Redirect 한 것을 확인할 수 있고
211.44.192.110 - - [11/Apr/2024:02:08:31 +0000] "GET / HTTP/2.0" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0" "-"
Origin Web-server의 Access log를 확인해보면 Proxy로부터 요청받아 응답하는것을 확인할 수 있습니다.
13.208.164.94 - - [11/Apr/2024:02:08:31 +0000] "GET / HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0" "-"
'운영체제' 카테고리의 다른 글
[devocen 오픈랩] kubernetes 스터디 1주 - 컨테이너 실습 (0) | 2024.05.13 |
---|---|
[RHEL 8] Apache + JBoss 조합으로 WAS 구성하기 -2 (0) | 2023.07.12 |
[RHEL 8] Apache + JBoss 조합으로 WAS 구성하기 -1 (0) | 2023.07.12 |
[RHEL 8]LVM 설정 (0) | 2023.07.12 |
[LDAP]LDAP과 GitLab 연동해서 로그인 해보기 (0) | 2023.03.08 |