Post

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
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
./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
    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/;
        }

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 참고)

특정 url은 일부 ip만 허용하고 나머지는 block
1
2
3
4
location ~* ^/somepath(\/.*)?$ {
  allow 10.0.0.0/8;
  deny all;
}

참고

SSL 인증서 관리

인증서 발급 & 인증 받기

인증서는 주기적으로 갱신되어야 하고, 갱신된 최신 인증서를 사용해야 하므로 nginx도 주기적인 reload 필요하다.

보안 관련 설정

1
2
3
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 옵션은 읽어보는게 나을 듯.

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
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

https://stackoverflow.com/questions/60502584/tomcat-what-happens-when-number-of-connections-exceeds-number-of-threads

This post is licensed under CC BY 4.0 by the author.