pingap-plugin 0.16.1

Plugin for pingap
Documentation
# ip_restriction

Allow- or deny-list access by client IP address or CIDR range.

- **Step:** `request` (fixed)
- **Registered as:** `ip_restriction`

## Configuration

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `category` | string | — | Must be `ip_restriction`. |
| `ip_list` | string[] | `[]` | IPv4/IPv6 addresses and CIDR ranges. An entry that is neither fails configuration validation. |
| `type` | string | `allow` | `allow` permits **only** listed IPs; `deny` blocks them. Case-insensitive; any other value is a configuration error. |
| `message` | string | `Request is forbidden` | Body of the 403 response. |
| `dry_run` | bool | `false` | Let a request that would be refused go on, and log the refusal as a warning instead: a new rule can be watched on real traffic before it turns anyone away. See [Dry run]limit.md#dry-run. |

Leaving `type` unset means `allow`, so an empty `ip_list` blocks every request.
Set it explicitly.

## Examples

Allow-list an internal admin path:

```toml
[plugins.internalOnly]
category = "ip_restriction"
type = "allow"
ip_list = ["127.0.0.1", "10.0.0.0/8", "192.168.1.0/24", "::1"]
message = "Internal access only"

[locations.admin]
upstream = "admin"
path = "/admin"
plugins = ["internalOnly"]
```

Deny-list a few abusive ranges:

```toml
[plugins.blockList]
category = "ip_restriction"
type = "deny"
ip_list = ["1.2.3.4", "203.0.113.0/24"]
```

## Client IP resolution

The list is checked against an address the client cannot choose. Directly
connected, that is the address of the connection. Behind a load balancer or a
CDN it is the address that proxy reports in `X-Forwarded-For` / `X-Real-IP` —
but only when the proxy is named in `basic.trusted_proxies`:

```toml
[basic]
trusted_proxies = ["10.0.0.0/8"]
```

With `trusted_proxies` set:

- A connection that does not come from a trusted proxy is identified by its
  peer address; its forwarded headers are ignored.
- Through a trusted proxy, `X-Forwarded-For` is read from the right. Each proxy
  appends the address it received the request from, so entries that are
  trusted proxies themselves are skipped and the first one that is not is the
  client. Whatever stands to the left of it was sent by that client and is not
  looked at: for `X-Forwarded-For: 6.6.6.6, 9.9.9.9` arriving from `10.0.0.2`
  the client is `9.9.9.9`. Several `X-Forwarded-For` lines are read as one
  list, and an entry may carry a port (`9.9.9.9:4321`, `[2001:db8::1]:4321`).
- Without `X-Forwarded-For` the address is `X-Real-IP`, then the peer.

The trusted proxy has to do its part: append to `X-Forwarded-For` (nginx:
`proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for`), or, if it does
not send that header at all, set `X-Real-IP` itself. A proxy that passes either
header through as the client sent it leaves the client in charge of its
address.

Without `trusted_proxies` the forwarded headers are not looked at and the
address is the peer's own. Behind a proxy that is not listed there, every
request therefore has the proxy's address: an allow list lets everyone in or
no one, a deny list never matches a client. List the proxy.

(The headers used to be believed from anyone when no proxy was configured: an
allow list for `10.0.0.0/8` let in whoever sent `X-Forwarded-For: 10.1.2.3`.
The same rule now holds for [`limit`](limit.md) by ip,
[`geo_restriction`](geo_restriction.md) and the `ip_list` of
[`combined_auth`](combined_auth.md). The access log's client ip and
`hash:ip` are not checks and still read the headers as before.)

An IPv4 client of a dual-stack listener (`[::]:80`) has the address
`::ffff:1.2.3.4`. It is matched, logged and counted as `1.2.3.4`, so IPv4
entries in `ip_list` and in `trusted_proxies` apply to it.

## Responses

| Situation | Status | Body |
| --- | --- | --- |
| Blocked by policy | 403 | `message` |
| Client IP is unparseable | 400 | Parse error text |

## Usage notes

- The result is a plain 403 with no `Retry-After` or challenge; use
  [`limit`]limit.md if you want throttling rather than blocking.
- For country-level rules see [`geo_restriction`]geo_restriction.md.