docs(knowledge-base): prune stale content and add agent-facing index (#7035)

This commit is contained in:
Zhengchao An
2026-09-02 08:26:59 +08:00
committed by GitHub
parent ceeff52229
commit 0a975f2fe2
99 changed files with 3312 additions and 10590 deletions
+38 -110
View File
@@ -1,120 +1,62 @@
# Outbound Connection Policy
This document describes the outbound connection policy that RustFS applies to
server-initiated HTTP(S) requests, and the `RUSTFS_OUTBOUND_ALLOW_ORIGINS`
allowlist operators can use to reach endpoints on private or container networks.
**Use this when:** a webhook, audit target, OIDC provider, or object-lambda endpoint on a private or container network (Compose service names, `host.docker.internal`, RFC 1918 addresses) is not being reached, or you need to know which server-initiated connections RustFS restricts and how to allowlist one.
**Source of truth:** `crates/utils/src/egress.rs` (`OutboundPolicy`, `OutboundDnsResolver`, `validate_outbound_url`, `ENV_OUTBOUND_ALLOW_ORIGINS`).
It is written for operators whose outbound integrations stopped reaching
endpoints after an upgrade — typically Docker Compose service names,
`host.docker.internal`, or RFC 1918 addresses. Webhook and audit clients adopted
this policy in `1.0.0-beta.11`; OIDC provider requests adopted it in
`1.0.0-beta.12`.
RustFS validates every operator-configured outbound destination to close a server-side request forgery (SSRF) class. Two layers exist:
## Background: what the policy protects
| Layer | What it checks | Escape hatch |
| --- | --- | --- |
| Literal URL check (`validate_outbound_url`) | Scheme is `http`/`https`; the host is not `localhost` or a loopback, private, shared, reserved, link-local, unspecified, or metadata address (IPv4-mapped and embedded IPv6 forms are classified by the embedded IPv4) | None |
| Full policy (`OutboundPolicy` + `OutboundDnsResolver`) | The literal check, plus re-validation of every address DNS returns on each new connection, so a hostname cannot be rebound to a restricted address after it was accepted | `RUSTFS_OUTBOUND_ALLOW_ORIGINS` for the loopback, private, shared, and reserved classes |
Several RustFS subsystems open connections to operator-configured URLs. To close
a server-side request forgery (SSRF) class of problem, RustFS validates every such
destination and re-checks the addresses returned by DNS on each new connection,
so a hostname cannot be rebound to a restricted address after it is first
accepted.
## Which subsystem uses which layer
The policy governs the outbound clients used by:
| Subsystem | Layer | Notes |
| --- | --- | --- |
| Event-notification webhooks (`RUSTFS_NOTIFY_WEBHOOK_*`) and audit webhooks (`RUSTFS_AUDIT_WEBHOOK_*`) | Full policy | Proxies disabled and redirects not followed, so the endpoint must be reachable directly (`crates/targets/src/target/webhook.rs`) |
| Target configuration validation (startup and admin API) | Full policy | `crates/targets/src/config/common.rs` `validate_outbound_http_url`; `rustfs/src/admin/handlers/target_descriptor.rs` |
| OIDC discovery, JWKS, and token requests | Full policy | A blocked provider logs `OIDC provider discovery blocked by outbound policy` naming the origin to allowlist (`crates/iam/src/oidc.rs`) |
| Object Lambda targets | Full policy | `rustfs/src/admin/router.rs` `outbound_policy` |
| Bucket replication targets | Literal check, relaxed | Private addresses are always allowed; loopback only with `RUSTFS_REPLICATION_ALLOW_LOOPBACK_TARGET=true` (`crates/ecstore/src/bucket/bucket_target_sys.rs` `validate_replication_target_endpoint`) |
| Site replication peers | Literal check | `rustfs/src/site_replication/mod.rs` |
| Tiering warm backends (S3, MinIO, RustFS, Azure, GCS, Aliyun, Tencent, Huawei, R2) | Literal check | `crates/ecstore/src/services/tier/warm_backend.rs` `validate_endpoint`; the RustFS provider adds a debug-only, env-gated loopback exception for e2e tests |
| Keystone `auth_url` | Literal check | `crates/keystone/src/config.rs` |
- event-notification webhooks (`RUSTFS_NOTIFY_WEBHOOK_*`);
- audit webhooks (`RUSTFS_AUDIT_WEBHOOK_*`);
- OIDC identity-provider discovery, JWKS, and token requests (since `1.0.0-beta.12`);
- S3 tiering (warm-backend) endpoints;
- Keystone auth URLs.
The webhook and audit outbound clients also **disable proxies and do not follow
redirects**, so the destination must be reachable directly at the configured URL.
## What changed in beta.11 (and for OIDC in beta.12)
For webhook and audit clients:
| | beta.10 | beta.11+ |
|---|---|---|
| Literal `localhost` / private / loopback IPs | Rejected | Rejected |
| Hostnames that resolve to private/loopback addresses (`logstash`, `host.docker.internal`, Compose service DNS, …) | Allowed | **Blocked at DNS/connect time** unless allowlisted |
| Escape hatch for private destinations | None | `RUSTFS_OUTBOUND_ALLOW_ORIGINS` |
| Proxies / redirects for outbound clients | Followed | Disabled |
Before beta.11 a webhook endpoint whose hostname happened to resolve to a
private address was accepted. Beta.11 fails that resolution check unless the
exact origin is on the allowlist. This is why a Compose setup that delivered
events on beta.10 can go silent after the upgrade even though the configuration
is unchanged.
OIDC joined the same policy in beta.12. An internal identity provider that
worked in beta.11 can therefore fail discovery after upgrading to beta.12 unless
its exact origin is allowlisted. The policy remains active for discovery, JWKS,
and token requests.
The allowlist affects only the "Full policy" rows. A literal-check subsystem rejects a hostname that is itself a restricted IP literal, does not re-check what a hostname resolves to, and cannot be widened by `RUSTFS_OUTBOUND_ALLOW_ORIGINS`.
## Symptoms
- Bucket event rules and webhook configuration look correct.
- Uploads and audited API calls succeed.
- No HTTP POST reaches the internal webhook receiver.
- The target may appear offline or fail activation when its endpoint resolves to
a loopback, private, shared, or reserved address.
- Startup or target validation reports `webhook endpoint is not allowed: ...`
with a reason such as `private address` or `loopback host`.
- An OIDC provider or login button is missing, and startup reports
`OIDC provider discovery blocked by outbound policy` with the exact origin to
allowlist.
- Bucket event rules and webhook configuration look correct and uploads succeed, but no POST reaches the receiver.
- Target validation reports `<field> is not allowed: ...` with a reason such as `private address` or `loopback host`; when an exact-origin allowlist entry would fix it, the message says so.
- An OIDC login button is missing and startup logs `OIDC provider discovery blocked by outbound policy`.
## `RUSTFS_OUTBOUND_ALLOW_ORIGINS`
`RUSTFS_OUTBOUND_ALLOW_ORIGINS` is a comma-separated list of exact HTTP(S)
origins that are permitted to resolve to otherwise-restricted addresses. It is an
operator-owned process setting read once at startup; individual target
configuration cannot extend it.
A comma-separated list of exact HTTP(S) origins permitted to resolve to otherwise-restricted addresses. It is a process-level setting read once at startup; individual target configuration cannot extend it.
```bash
# exact scheme://host:port — comma-separate multiple origins
RUSTFS_OUTBOUND_ALLOW_ORIGINS=http://logstash:8080,http://host.docker.internal:3020
```
### Origin format rules
Each entry is matched as an **exact origin** (`scheme://host:port`):
- The scheme must be `http` or `https`.
- The host and port must match the destination exactly. An allowlisted
`http://logstash:8080` does **not** authorize `http://logstash:9090` or
`https://logstash:8080`.
- If the port is omitted, the scheme's default is used (`80` for `http`, `443`
for `https`); the destination must then use that same default port.
- Entries must be origins only. A trailing `/` is accepted, but a path, query,
or fragment (for example `http://logstash:8080/events`) is **rejected** as an
invalid origin — the process fails closed rather than silently ignoring the
path.
- Userinfo (`http://user:pass@host`) is not allowed.
- An empty entry (for example a trailing or doubled comma) is rejected.
An invalid list fails closed: the affected subsystem reports an
`invalid outbound policy` / `invalid origin at position N` error instead of
starting with a partially applied allowlist.
| Rule | Detail |
| --- | --- |
| Exact origin | `scheme://host:port`. `http://logstash:8080` does not authorize `http://logstash:9090` or `https://logstash:8080` |
| Scheme | `http` or `https` only |
| Default port | If omitted, the scheme default (`80` / `443`) applies and the destination must use that port |
| Origin only | A trailing `/` is accepted; any path, query, or fragment (`http://logstash:8080/events`) is rejected |
| No userinfo | `http://user:pass@host` is rejected |
| No empty entries | A trailing or doubled comma is rejected |
| Fail closed | An invalid list yields `invalid outbound policy` / `invalid origin at position N` and the affected subsystem does not start with a partially applied allowlist |
### What stays blocked even when allowlisted
Allowlisting an origin only relaxes the loopback, private, shared, and reserved
address classes for that exact origin. The following remain forbidden for every
origin, allowlisted or not:
- Cloud metadata endpoints (`169.254.169.254` and the other well-known IMDS addresses).
- Link-local addresses (`169.254.0.0/16`, `fe80::/10`) and the unspecified address (`0.0.0.0`, `::`).
- IPv4-mapped, IPv4-compatible, and NAT64/6to4-embedded forms of the above; the embedded IPv4 address is what gets classified, so `::ffff:127.0.0.1` cannot bypass the policy.
- cloud metadata endpoints (for example `169.254.169.254` and the other
well-known IMDS addresses);
- link-local addresses (`169.254.0.0/16`, `fe80::/10`);
- the unspecified address (`0.0.0.0`, `::`);
- IPv4-mapped, IPv4-compatible, and NAT64/6to4-embedded forms of any of the
above (RustFS classifies the embedded IPv4 destination, so `::ffff:127.0.0.1`
and similar cannot be used to bypass the policy).
The allowlist authorizes only the exact host you name. A DNS answer for a
different hostname that points at a private address is still rejected, and each
new connection re-validates the resolved addresses so a rebinding answer fails
closed.
The allowlist authorizes only the exact host named. A DNS answer for a different hostname that points at a private address is still rejected, and each new connection re-validates the resolved addresses.
## Docker Compose example
@@ -128,25 +70,11 @@ services:
RUSTFS_NOTIFY_WEBHOOK_ENDPOINT_PRIMARY: "http://logstash:8080/events"
RUSTFS_NOTIFY_WEBHOOK_QUEUE_DIR_PRIMARY: "/tmp/rustfs-events"
# Allow the webhook host to resolve to the Compose private network.
# Note: the allowlist takes the origin only, without the /events path.
# The allowlist takes the origin only, without the /events path.
RUSTFS_OUTBOUND_ALLOW_ORIGINS: "http://logstash:8080"
logstash:
image: docker.elastic.co/logstash/logstash:8.15.0
# ...
```
The endpoint keeps its full path (`/events`); the allowlist entry is the origin
(`http://logstash:8080`) only.
## Upgrade checklist (beta.10 → beta.11+, or OIDC beta.11 → beta.12+)
1. List every outbound endpoint whose hostname resolves to a loopback, private,
shared, or reserved address: notification webhooks, audit webhooks, OIDC
providers, tiering endpoints, and Keystone auth URLs.
2. Add each one to `RUSTFS_OUTBOUND_ALLOW_ORIGINS` as an exact
`scheme://host:port` origin (no path).
3. Ensure the endpoint is reachable directly — for webhook and audit targets,
proxies are disabled and redirects are not followed.
4. Restart RustFS; the policy is read at startup.
5. Confirm delivery, and check the logs for `... is not allowed` messages if a
target still fails to activate.
The endpoint keeps its full path (`/events`); the allowlist entry is the origin only. Restart RustFS after changing the variable — the policy is read at startup — and check the logs for `is not allowed` messages if a target still fails to activate.