NGINX
Reverse Proxy
실 운영 환경에서는 80, 443 빼고는 inbound를 막아두는 경우가 많아서 iptables 써서 80 -> xxxx로 포워딩하거나, nginx써서 80 -> xxxx로 포워딩한다.
후자를 더 많이 사용하는데 그 이유는
- nginx를 쓰면 설정이 좀 귀찮기는 하지만, 한 장비에 여러 인스턴스를 띄우고 이들을 domain(혹은 location)으로 구분하므로 모두 80으로 받을 수 있다.
- jenkins나 sonarqube 등 설정 파일에서 home location (e.g., /jenkins)을 설정할 수 있도록 지원하는 것이 이 것 때문.
- 어차피 server IP가 변경될 상황을 대비해서 도메인을 쓰긴 써야하니 그럴 바에 nginx로 리버스 프록시하는게 낫다.
- IP로 hook 같은거 막 등록해놨다가 서버 이전이나 인스턴스 분리하는 순간이 오면… 다 찾아가서 바꿔줘야 하니 난감함.
proxy_pass 설정하면?
1
2
3
4
5
a.b.c:80, d.e.f:80 모두 nginx에서 받아서,
a.b.c:80 -> localhost:8080
d.e.f:80 -> localhost:9001
로 포워딩이 가능하다. (nginx, 인스턴스들 모두 같은 물리장비에 위치) domain 뿐만 아니라 location으로도 구분할 수 있다.
configure 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
./configure \
--prefix=/home1/user1/apps/nginx-1.20.2 \
--user=user1 \
--group=user1 \
--error-log-path=/home1/user1/logs/nginx/error.log \
--http-log-path=/home1/user1/logs/nginx/access.log \
--without-http_scgi_module \
--without-http_uwsgi_module \
--without-http_fastcgi_module \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_stub_status_module \
설정 예시
설정 예시는 기본적으로 설치하면 딸려오는 nginx.conf에서 주석 처리 되어 있는 부분을 참고하는게 제일 좋다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
server {
listen 80;
server\_name aaaa.bbbb.ccc.com;
location /http\_stub\_status {
stub\_status on;
allow 127.0.0.1;
deny all;
}
location ~ /WEB-INF/ {
access\_log off;
log\_not\_found off;
deny all;
}
location / {
if ($request\_method !~ ^(GET|POST|PUT|DELETE)$ ) {
return 444;
}
add\_header X-XSS-Protection "1; mode=block";
proxy\_set\_header Connection "";
proxy\_set\_header Host $host;
proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for;
proxy\_set\_header X-Real-IP $remote\_addr;
proxy\_set\_header X-Forwarded-Proto $scheme;
proxy\_set\_header X-Forwarded-By $server\_addr:$server\_port;
proxy\_connect\_timeout 3600;
proxy\_read\_timeout 3600;
proxy\_send\_timeout 3600;
proxy\_pass http://localhost:8080/;
}
- Client IP 구하기 : X-Forwarded-For와 X-Real-IP
- WAS 입장에서는 nginx가 client 이므로 nginx의 IP만 알 수 있게 되므로, X-Forwarded-For/X-Real-IP 헤더를 이용해 nginx가 받은 IP를 WAS로 전달해준다.
NGINX even provides a $proxy_add_x_forwarded_for variable to automatically append $remote_addr to any incoming X-Forwarded-For headers.
https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
location 지정 (반드시 docs 참고)
- 한 uri에 매치되는 여러 개의 location이 있으면 가장 긴 prefix 매칭을 찾아 그 블럭을 타게 된다.
- 이 것도 우선순위가 있어서 그게 먼저이긴 함.
- https://hyeonguj.github.io/2019/02/01/nginx-location%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC/
- http://nginx.org/en/docs/varindex.html
- 정규식 사용하려면
~
, case-insensitive 정규식은~\*
- 정규식 쓸 때는 패턴에
^ $
안붙여주면 중간에서 등장하더라도 다 매칭된다.
- 정규식 쓸 때는 패턴에
http://nginx.org/en/docs/http/ngx_http_core_module.html#location
- 반드시 docs 참고해서 작성한다. = ~ ~* 등 차이가 있어서 왜 안되지… 하고 시간 낭비하는 경우가 많기 때문.
특정 url은 일부 ip만 허용하고 나머지는 block
1
2
3
4
5
6
7
8
9
10
11
12
13
location
~\* ^/somepath(\/.\*)?$ {
allow
10.0.0.0/8
;
deny all
;
}
참고
- nginx reverse-proxyhttps://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
- 젠킨스https://www.jenkins.io/doc/book/system-administration/reverse-proxy-configuration-nginx/
- 소나큐브https://docs.sonarqube.org/latest/setup/operate-server/
SSL 인증서 관리
인증서는 주기적으로 갱신되어야 하고, 갱신된 최신 인증서를 사용해야 하므로 nginx도 주기적인 reload 필요하다.
보안 관련 설정
1
2
3
4
add_header Strict-Transport-Security "max-age=31536000; preload";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
- HSTS 설정
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
브라우저에게 해당 서버는 아예 80 요청 받지 않는다고 헤더에 추가해 response.브라우저는 이를 max-age 동안 기억해 놓고 앞으로 해당 호스트에는 443으로만 접근함.
- 따라서 최초 접근 이후로는 브라우저 단에서 443 통신 강제.
- 매번 80 요청 들어왔을 때 443으로 redirect 하도록 처리하는 것 보다 안전.
- 최초 접근 시 80에서 redirect 할 때, MITM을 통해 의도했던 443 site가 아니라 malicious site로 이동할 가능성이 있기 때문.
- preload 설정?
- 브라우저는 preload 리스트를 보고, 이 리스트에 존재하는 호스트로는 HSTS header response를 받은 적이 없더라도 443으로만 접근함.
- 해당 사이트에 최초 접근하는 순간 까지도 443 통신할 수 있다는 장점이 있음.
- X-Content-Type-Options
- 브라우저가 리소스를 해석할 때, 설정된 MIME type 대로만 해석 가능하도록 제약 설정.
- e.g., text/css 일 때만 css로 해석 가능하고, javascript 일 때만 javascript로 해석 가능하다. MIME type이 image이면 javascript로 해석하지 않는다.
- X-XSS-Protection
Upstream ( 부하 분산, load balancing )
https://opentutorials.org/module/384/4328
Rate Limiting
https://yobi.navercorp.com/phoenix_tf/posts/11
https://www.nginx.com/blog/rate-limiting-nginx/
https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-http/
- NGINX는 요청 한도를 ms 단위로 쪼개서 관리한다.
- 즉, 5r/s이면 1초에 5개를 보장하는게 아니라 200ms 당 1개씩 요청을 수용한다.
- 따라서 200ms 안에 요청 5개가 한꺼번에 오면 1개만 처리하고 4개는 503 응답 나간다.
- 1r/s인데 1초 안에 요청이 2개 들어왔다면? 뒤에 들어온건 503이 나가겠지만, 이렇게 넘치는 요청을 버리지 않고 대기 Queue에 넣고 싶을 수 있음. 이 옵션이 바로 burst
- 10r/s, burst=20으로 설정되어 있는데 100ms 안에 요청 25개가 들어오는 경우,
- 1개는 바로 업스트림으로 전달
- 20개는 대기큐로 간 다음 100ms 마다 1개 씩 꺼내서 업스트림으로 전달
- 4개는 503 거절
- nodelay 옵션은 대기큐에 있는걸 100ms 마다 꺼내서 전달하면 대기큐 맨 마지막에 있는 애는 너무 늦게 서빙되니까, 애초에 처음에는 대기큐에 안넣고 burst 개수 만큼 바로바로 전송해버리자. 라는 것임.
- 페이지 로딩 시 정적 리소스가 여러개 필요할 수 있는데 이런 애들 하나 당 rate에 설정된 시간만큼 씩 기다려야 한다면 페이지 로딩이 무지 느리다.
- 10r/s, burst=20, nodelay인데 100ms 안에 25개가 들어오는 경우,
- 21개 즉시 전달하고, 대신 20개의 대기슬롯에 taken표시.
- taken 표시 되어 있는 슬롯은 못쓴다. 100ms 마다 1개씩 슬롯 taken 표시 해제.
- delay 옵션은 읽어보는게 나을 듯.
- 단순 초기 리소스 한꺼번에 받아오기 위함이라면 nodelay보다 delay가 나을 수 있음.
- https://www.nginx.com/blog/rate-limiting-nginx/#Two-Stage-Rate-Limiting
Illustration of rate-limiting behavior with rate=5r/s burst=12 delay=8
rate limit for each uri
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
limit\_req\_zone
$server\_name
zone
=slow:
10m
rate=
10r
/s
;
limit\_req\_zone
$server\_name
zone
=fast:
10m
rate=
100r
/s
;
이렇게 설정하면?
모든 요청에 대해서, slow, fast zone에 카운트가 추가되고...
모든 요청이
10r
/s를 넘어가는 경우 => slow 지정된 것들은 block된다.
모든 요청이
100r
/s를 넘어가는 경우 => fast 지정된 것들이 이제서야 block됨.
그니까 카운트하는건 모든 요청에 대해서라 이렇게는 안될 것 같고...
각 uri 별로 다 다르게 주고 싶다면
limit\_req\_zone
$uri
zone
=slow:
10m
rate=
10r
/s
;
limit\_req\_zone
$uri
zone
=fast:
10m
rate=
100r
/s
;
각 uri 별로 카운트를 따로 함. (한 uri에 대해서 slow, fast 둘 다 카운트가 올라가긴 한다)
한 uri에 대한 요청이 10r/s를 넘어가는 경우 => slow로 지정된 location이라면 block
ㅇㅇ 이렇게 하면 될 듯.
근데 WAS 단에서도 설정하는게 있는데… 어디서 하는게 더 나은건지
https://stackoverflow.com/questions/39002090/spring-boot-limit-on-number-of-connections-created
https://docs.bmc.com/docs/brid91/en/tomcat-container-workload-configuration-825210082.html