---
title: "서버 접근을 제한하고 앱 앞에 WAF 두기"
description: "erpc worker는 첫 배포부터 서버 접근을 제한하며, erpc.toml의 테이블 하나로 공개 API 앞에 WAF를 둡니다. 하는 일과 하지 않는 일을 설명합니다."
canonical: "https://erpc.global/ko/doc/erpc-cli/worker-firewall-and-waf/"
locale: "ko"
---

# 서버 접근을 제한하고 앱 앞에 WAF 두기

첫 배포부터 `erpc worker`는 사용자의 주소에서만 SSH 접속을 허용하고, 그 밖의 모든 들어오는 연결을 드롭합니다. `[waf]` 테이블은 API 앞에 필터링 프록시를 둡니다.

이 페이지는 프리릴리스인 ERPC CLI `v0.1.0-rc.4`를 설명합니다.

## 방화벽

배포는 앱이 처음 시작되기 전에 서버의 방화벽을 다음과 같이 설정합니다.

- SSH는 `firewall.ssh_allow`와 배포가 이루어지는 주소에서만 허용합니다.
- `firewall.app_allow`에서 오는 `run.port` 연결을 제외한 그 밖의 모든 들어오는 연결을 드롭합니다. WAF 뒤에 있는 앱이라면 그 자리에 WAF의 포트가 들어가며, 이는 `[waf]` 테이블이 지금 만드는 규칙을 서버의 WAF가 이미 실행하고 있는 경우에만 해당합니다([아래](https://erpc.global/ko/doc/erpc-cli/worker-firewall-and-waf/#앱-앞에-waf-두기)).

각 변경은 120초 롤백 타이머를 건 상태로 실행되며, 새 연결이 들어온 뒤에만 유지됩니다. 따라서 잘못된 변경 때문에 서버에 들어갈 수 없게 되는 일은 생기지 않습니다.

다만 허용 목록은 그것을 만든 주소보다 오래 남습니다. 집에서 접속하는 주소가 바뀌면, 다시 들어가는 방법은 ERPC 서버라면 ERPC 지원팀, 직접 보유한 서버라면 제공업체의 콘솔입니다. 집에서 접속하는 주소에 의존하기 전에, 서버에 접속할 수 있는 두 번째 주소(사무실, VPN 출구)를 `firewall.ssh_allow`에 넣어 두세요.

주소가 바뀌면 관리자들이 사용하는 고정 주소를 모두 추가하세요.

```bash
erpc worker firewall allow --to ssh 203.0.113.10 198.51.100.7
```

| 명령 | 하는 일 |
| --- | --- |
| `erpc worker firewall list` | 누가 서버에 접근할 수 있는지 보여 줍니다. `erpc.toml`과 서버의 테이블을 나란히 표시합니다 |
| `erpc worker firewall allow --to ssh\|app <addresses>` | 주소의 접속을 허용하고 적용합니다. SSH는 단일 주소에서만 허용되며, 네트워크는 `--to app`을 지정할 때만 받아들입니다 |
| `erpc worker firewall deny --to ssh\|app <addresses>` | 해당 주소의 접속 허용을 중단합니다 |
| `erpc worker firewall sync` | 서버의 방화벽을 다시 `erpc.toml`의 내용대로 맞춥니다 |

## 앱 앞에 WAF 두기

`erpc.toml`에 테이블 하나를 추가하세요.

```toml
[waf]
port = 8080
```

그러면 `erpc worker deploy`가 서버에서 자체 systemd 유닛(`erpc-waf-<app>-<env>`)으로 HTTP 리버스 프록시를 실행합니다. 이 프록시는 키도, 시크릿도, worker의 어떤 것도 갖지 않는 권한 없는 동적 사용자로 실행되며, 메모리는 1 GiB로 제한됩니다. 프록시는 `waf.port`에서 수신 대기하고, 각 요청을 앱보다 먼저 읽어 규칙이 지정한 것은 거부하며, 나머지는 `127.0.0.1:<run.port>`의 앱으로 전달합니다.

- **앱은 loopback에만 머뭅니다.** 앱 자체의 포트는 외부에 절대 열리지 않습니다. `erpc worker api`와 헬스 체크는 WAF를 거치지 않고 loopback에서 앱에 닿습니다.
- **누가 연결할 수 있는지는 여전히 방화벽의 목록이 정하며**, WAF의 포트는 그 목록이 모두를 허용해도 되는 유일한 곳입니다.

  ```sh
  erpc worker firewall allow --to app 0.0.0.0/0     # and ::/0
  ```

  아무도 등록되어 있지 않으면 WAF는 수신 대기하지만 아무도 들어올 수 없으며, 배포가 그 사실을 알려 줍니다.
- **포트는 WAF가 그 포트에서 수신 대기한 뒤에 열리며**, 그 전에는 절대 열리지 않습니다. WAF가 올라오지 않은 배포는 실패하고 이유를 알려 주며, 앱은 계속 실행됩니다.
- **`[waf]` 테이블을 변경하면,** 배포가 새 규칙을 적용하기 전까지는 새로운 상대에게 포트가 열리지 않습니다.
- `erpc.toml`에서 **`[waf]` 테이블을 빼고** 배포하면 서버에서 WAF가 제거됩니다.

## WAF 설정

필수 키는 `port`뿐입니다. 모든 키와 기본값은 다음과 같습니다.

```toml
[waf]
port = 8080          # the public port, 1024 or above; the app keeps [run] port
methods = ["GET", "POST", "PUT", "DELETE"]
allow_paths = []     # every path
deny_paths = []
max_body_bytes = 1048576
max_header_bytes = 16384
rate = { per_second = 50, burst = 100 }
max_connections_per_address = 32
ban = { after = 100, window_secs = 60, for_secs = 600 }
block_probes = true
header_timeout_secs = 10
body_timeout_secs = 30
upstream_timeout_secs = 30
new_connections = { per_second = 50, burst = 100 }
```

| 키 | 하는 일 | 거부된 요청이 받는 결과 |
| --- | --- | --- |
| `methods` | 전달되는 메서드. 대문자로 적습니다(최대 16개, `CONNECT`는 불가) | 405, `Allow` 포함 |
| `allow_paths` | 전달되는 경로. 하나도 적지 않으면 모든 경로입니다. 패턴은 경로 하나이거나, `*`로 끝나면 그 접두사 아래의 모든 경로입니다. `/api/*`는 `/api`와 그 아래 전부입니다. 퍼센트 디코딩을 한 번 거친 경로와 비교하며, 대소문자를 구분합니다 | 404 |
| `deny_paths` | 거부되는 경로. 같은 방식으로 적으며, 먼저 검사됩니다 | 403 |
| `max_body_bytes` | 요청 본문의 최대 크기. 64 MiB까지 지정할 수 있습니다 | 413 |
| `max_header_bytes` | 요청 줄과 헤더를 합친 최대 크기. 1024에서 65536 사이입니다 | 431 |
| `rate` | 한 주소에서 오는 초당 요청 수와, 속도 제한이 적용되기 전에 한꺼번에 허용되는 요청 수 | 429, `Retry-After` 포함 |
| `max_connections_per_address` | 한 주소가 동시에 유지하는 연결 수 | 응답 없이 연결이 닫힘 |
| `ban` | `window_secs` 안에 차단된 요청이 `after`개에 이르면, 그 주소는 `for_secs` 동안(최대 30일) 거부됩니다 | 그 주소의 연결이 응답 없이 닫힘 |
| `block_probes` | 스캐너가 요청하는 것(`/.env`, `/.git`, `/wp-login.php` 등)을 거부합니다 | 403 |
| `header_timeout_secs` | 클라이언트가 요청의 헤드를 보내는 데 주어지는 시간. 1에서 120 사이입니다. 본문 중간에 멈춘 클라이언트나 응답 수신을 멈춘 클라이언트를 기다리는 시간이기도 합니다 | 헤드는 408, 멈춘 본문은 400, 받아 가지 않은 응답은 연결이 닫힘 |
| `body_timeout_secs` | 요청 본문 전체에 걸릴 수 있는 시간. 1에서 600 사이이며, WAF가 앱을 기다리는 시간은 포함하지 않습니다 | 408 |
| `upstream_timeout_secs` | 앱이 연결을 받고 응답을 시작하기까지 주어지는 시간. 1에서 600 사이입니다 | 504, 앱이 수신 대기하지 않으면 502 |
| `new_connections` | 한 주소에서 WAF의 포트로 들어오는 초당 새 TCP 연결에 대한 방화벽 자체의 제한으로, 프록시에 닿기 전에 nftables가 드롭합니다. nftables의 주소 집합이 가득 찬 동안에는 집합에 없는 주소를 세지 않으므로, 집합에 다시 여유가 생길 때까지 이 제한은 그 주소에 적용되지 않습니다. 프록시 자체의 제한(`rate`, `max_connections_per_address`, `ban`)은 그대로 적용됩니다 | 패킷이 드롭됨 |

`rate`, `max_connections_per_address`, `ban`, `new_connections`는 `false`로 끕니다(`ban = false`). 생략한 키는 기본값이 됩니다. `erpc worker check`는 배포 전에 `[waf]` 테이블이 무엇을 할지, 그리고 무엇이 잘못되었는지 알려 줍니다.

## 설정 없이 WAF가 하는 일

- 모호하지 않은 HTTP/1 요청 하나로 읽히지 않는 요청은 400을 받으며, 정규형이 아닌 경로도 마찬가지입니다.
- 프로토콜 변경 요청(`Upgrade`)은 501을 받습니다.
- 모든 거부는 밴 판정 횟수에 포함되며, 각 거부는 해당 연결을 닫습니다.
- 주소란 IPv4 주소 하나, 또는 IPv6 `/64` 하나를 뜻합니다.
- WAF는 클라이언트와 프록시 사이 연결의 헤더(`Connection`과 그것이 지정하는 헤더, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `Proxy-Connection`, `TE`, `Trailer`), `Expect`, 이름에 밑줄이 있는 헤더, 클라이언트가 직접 보낸 `Forwarded`, `X-Real-IP`, `X-Forwarded-Proto`를 전달하지 않습니다. WAF는 `X-Real-IP`를 연결이 들어온 주소로 설정하고, 그 주소를 `X-Forwarded-For`의 끝에 추가합니다. `X-Forwarded-For`에서 프록시가 기록한 값은 마지막 항목뿐입니다.

## WAF 동작 확인

| 명령 | 보여 주는 내용 |
| --- | --- |
| `erpc worker status` | WAF의 포트, 전달 대상, 언제부터인지, 그 이후의 횟수 |
| `erpc worker firewall list` | 누가 WAF의 포트에 접근할 수 있는지, 그리고 규칙별로 무엇이 차단되었는지 |
| `erpc worker logs --waf` | 차단된 각 요청: 규칙, 상태 코드, 클라이언트의 주소, 메서드, 대상의 처음 200바이트 |

전달된 요청은 로그를 남기지 않으며, 헤더나 본문은 절대 로그에 남지 않습니다. 차단된 요청의 쿼리는 200바이트가 닿는 범위까지 로그에 남습니다. 클라이언트가 보내는 내용이 사용자의 터미널에 무언가를 쓸 수는 없습니다. WAF는 저널을 출력 가능한 ASCII로 기록합니다.

## WAF가 하지 않는 일

- **TLS 미지원.** WAF는 자신의 포트에서 평문 HTTP/1.1로 통신합니다.
- **HTTP/2 미지원:** 이 프로토콜에는 400으로 응답합니다.
- **WebSocket 미지원**, 그 밖의 어떤 `Upgrade`도 마찬가지이며 501을 받습니다.
- **본문 내부는 보지 않습니다.** 본문은 크기만 재며, 절대 읽지 않습니다. 프로브 목록은 짧고 노이즈 필터일 뿐 보장이 아닙니다. 입력 검사는 여전히 앱의 몫입니다.
- **인증 없음.** 누가 앱을 호출할 수 있는지는 앱이 정할 일이며, 방화벽의 목록이 정합니다.
- **주소 하나가 클라이언트 하나입니다.** 하나의 NAT, 프록시 또는 CDN 뒤에 있는 클라이언트들은 속도 제한과 밴을 공유하며, 주소가 여러 개인 클라이언트는 주소 수만큼 속도 제한을 갖습니다.
- **밴과 횟수는** WAF가 재시작되면 잊힙니다.
- **기억하는 주소는 최대 8192개이며**, 동시에 유지하는 연결은 최대 1024개입니다.
- **IPv4에서만 수신 대기하므로,** `app_allow`의 IPv6 항목은 아직 아무 데에도 닿지 않습니다.
- **경로는 디코딩된 바이트 그대로 비교됩니다.** 경로를 더 느슨하게 읽는 앱(대소문자 무시, `;` 매개변수, 이중 디코딩)은 `deny_paths`가 다루지 않는 이름으로 접근될 수 있습니다. `allow_paths`에는 이런 틈이 없습니다. 목록에 없는 것은 전달되지 않습니다.
- **느리게 읽는 클라이언트도 끝까지 응답을 받습니다.** 응답을 아무리 느리게라도 계속 받아 가는 클라이언트에게는 끝까지 응답합니다.
- **요청 폭주가 큰 업로드를 끊을 수 있습니다.** WAF는 단일 스레드로 실행되므로, 요청 폭주가 큰 업로드를 408로 끊을 수 있습니다. 큰 본문을 받는 앱은 `max_body_bytes`와 함께 `body_timeout_secs`도 늘립니다.

[worker 가이드](https://github.com/elsoul/erpc-cli/blob/v0.1.0-rc.4/docs/worker.md)에는 `app_allow` 변경이 다음 배포까지 보류되는 경우를 포함해 모든 규칙이 나와 있습니다.
