Skip to content

Commit 5966c06

Browse files
committed
docs on how to use
1 parent 510afd0 commit 5966c06

1 file changed

Lines changed: 263 additions & 0 deletions

File tree

docs/egress.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Egress (Outbound Interception)
2+
3+
Containers make outbound HTTP and HTTPS requests to talk to external services.
4+
By default, whether those requests succeed depends on `enableInternet`. The
5+
egress system lets you intercept, rewrite, allow, or block those requests
6+
before they leave the Cloudflare network.
7+
8+
## Setup
9+
10+
Export `ContainerProxy` from your Worker entrypoint. Without this export,
11+
outbound interception will not work.
12+
13+
```ts
14+
export { ContainerProxy } from '@cloudflare/containers';
15+
```
16+
17+
## Configuration
18+
19+
All of the following are instance properties on your `Container` subclass.
20+
21+
### `enableInternet`
22+
23+
Controls whether the container can access the public internet by default.
24+
25+
```ts
26+
enableInternet = false; // block all outbound by default
27+
```
28+
29+
### `interceptHttps`
30+
31+
When `true`, outbound HTTPS traffic is also intercepted through the same
32+
handler chain as HTTP. The container must trust the Cloudflare-provided CA
33+
certificate at `/etc/cloudflare/certs/cloudflare-containers-ca.crt`.
34+
35+
```ts
36+
interceptHttps = true;
37+
```
38+
39+
### `allowedHosts`
40+
41+
A list of hostname patterns. When non-empty, it acts as a **whitelist gate**:
42+
only matching hosts can proceed past this check. Hosts that do not match are
43+
blocked with HTTP 520.
44+
45+
Supports simple glob patterns where `*` matches any sequence of characters.
46+
47+
```ts
48+
allowedHosts = ['api.stripe.com', '*.example.com'];
49+
```
50+
51+
Behaviour depends on whether a catch-all `outbound` handler is defined:
52+
53+
- **With `outbound`:** Only matching hosts reach the catch-all handler.
54+
Everything else is blocked, even if `enableInternet` is `true`.
55+
- **Without `outbound`:** Matching hosts get internet access, regardless of
56+
`enableInternet`. Non-matching hosts fall through to `enableInternet`.
57+
58+
### `deniedHosts`
59+
60+
A list of hostname patterns. Matching hosts are **blocked unconditionally**
61+
(HTTP 520). This overrides everything else in the chain, including per-host
62+
handlers set via `outboundByHost`.
63+
64+
Supports the same simple glob patterns as `allowedHosts`.
65+
66+
```ts
67+
deniedHosts = ['evil.com', '*.malware.net'];
68+
```
69+
70+
## Outbound handlers
71+
72+
### `outboundByHost`
73+
74+
Map of hostname to handler function. Handles requests to specific hosts.
75+
76+
```ts
77+
MyContainer.outboundByHost = {
78+
'api.openai.com': (req, env, ctx) => {
79+
// custom logic for openai
80+
return fetch(req);
81+
},
82+
};
83+
```
84+
85+
### `outbound`
86+
87+
Catch-all handler invoked for any host that was not handled by a more specific
88+
rule. When this is defined, all outbound HTTP is intercepted (not just specific
89+
hosts).
90+
91+
```ts
92+
MyContainer.outbound = (req, env, ctx) => {
93+
console.log('outbound request to', new URL(req.url).hostname);
94+
return fetch(req);
95+
};
96+
```
97+
98+
### `outboundHandlers`
99+
100+
Named handlers that can be referenced at runtime via `setOutboundHandler()` or
101+
`setOutboundByHost()`. This is how you swap handler logic without redeploying.
102+
103+
```ts
104+
MyContainer.outboundHandlers = {
105+
async github(req, env, ctx) {
106+
return new Response('handled by github handler');
107+
},
108+
async logging(req, env, ctx) {
109+
console.log(req.url);
110+
return fetch(req);
111+
},
112+
};
113+
```
114+
115+
## Runtime methods
116+
117+
These methods modify the outbound configuration of a running container
118+
instance. Changes are persisted across Durable Object restarts.
119+
120+
| Method | Description |
121+
| ---------------------------------------------- | ------------------------------------------------------------ |
122+
| `setOutboundHandler(name, ...params)` | Set the catch-all to a named handler from `outboundHandlers` |
123+
| `setOutboundByHost(hostname, name, ...params)` | Set a per-host handler at runtime |
124+
| `removeOutboundByHost(hostname)` | Remove a runtime per-host override |
125+
| `setOutboundByHosts(handlers)` | Replace all runtime per-host overrides at once |
126+
| `setAllowedHosts(hosts)` | Replace the allowed hosts list |
127+
| `setDeniedHosts(hosts)` | Replace the denied hosts list |
128+
| `allowHost(hostname)` | Add a single host to the allowed list |
129+
| `denyHost(hostname)` | Add a single host to the denied list |
130+
| `removeAllowedHost(hostname)` | Remove a host from the allowed list |
131+
| `removeDeniedHost(hostname)` | Remove a host from the denied list |
132+
133+
## Processing order
134+
135+
When the container makes an outbound request, it is evaluated against the
136+
following rules **in order**. The first match wins.
137+
138+
### Step 1 — Denied hosts
139+
140+
If the hostname matches any `deniedHosts` pattern, the request is blocked
141+
(HTTP 520). This is the highest priority check. It overrides per-host handlers,
142+
the catch-all, `allowedHosts`, and `enableInternet`.
143+
144+
### Step 2 — Per-host handler (runtime)
145+
146+
If `setOutboundByHost()` was called for this exact hostname, the registered
147+
handler is invoked.
148+
149+
### Step 3 — Per-host handler (static)
150+
151+
If `outboundByHost` contains this exact hostname, the registered handler
152+
is invoked.
153+
154+
### Step 4 — Allowed hosts gate
155+
156+
If `allowedHosts` is non-empty and the hostname does **not** match any pattern,
157+
the request is blocked (HTTP 520). When `allowedHosts` is empty this step is
158+
skipped entirely and all hosts proceed.
159+
160+
### Step 5 — Catch-all handler (runtime)
161+
162+
If `setOutboundHandler()` was called at runtime, that handler is invoked.
163+
164+
### Step 6 — Catch-all handler (static)
165+
166+
If `outbound` is defined, it is invoked.
167+
168+
### Step 7 — Allowed host internet fallback
169+
170+
If the hostname matched `allowedHosts` but no outbound handler above handled
171+
it, the request is forwarded to the public internet. This is the mechanism that
172+
lets `allowedHosts` grant internet access when `enableInternet` is `false`.
173+
174+
### Step 8 — `enableInternet` fallback
175+
176+
If `enableInternet` is `true`, the request is forwarded to the public internet.
177+
178+
### Step 9 — Default deny
179+
180+
The request is blocked (HTTP 520).
181+
182+
## Interception strategy
183+
184+
The library avoids intercepting all outbound traffic when it is not necessary.
185+
186+
- **Catch-all interception** (`interceptAllOutboundHttp`) is only used when a
187+
catch-all `outbound` handler or a runtime `setOutboundHandler` override is
188+
configured. All outbound HTTP goes through `ContainerProxy`.
189+
- **Per-host interception** (`interceptOutboundHttp`) is used in all other
190+
cases. Only traffic to known hosts (from `outboundByHost`, `allowedHosts`,
191+
`deniedHosts`, and runtime overrides) is routed through `ContainerProxy`.
192+
Everything else follows the container's default network behaviour
193+
(`enableInternet`).
194+
195+
When `interceptHttps` is `true`:
196+
197+
- In catch-all mode, `interceptOutboundHttps('*', ...)` intercepts all HTTPS.
198+
- In per-host mode, `interceptOutboundHttps(host, ...)` is called for each
199+
known host individually.
200+
201+
## Glob patterns
202+
203+
Both `allowedHosts` and `deniedHosts` support simple glob patterns where `*`
204+
matches any sequence of characters.
205+
206+
| Pattern | Matches | Does not match |
207+
| --------------- | ------------------------------------ | ------------------ |
208+
| `example.com` | `example.com` | `sub.example.com` |
209+
| `*.example.com` | `api.example.com`, `a.b.example.com` | `example.com` |
210+
| `google.*` | `google.com`, `google.co.uk` | `maps.google.com` |
211+
| `api.*.com` | `api.stripe.com`, `api.test.com` | `api.stripe.co.uk` |
212+
| `*` | everything | |
213+
214+
## Full example
215+
216+
```ts
217+
import { Container, getContainer } from '@cloudflare/containers';
218+
export { ContainerProxy } from '@cloudflare/containers';
219+
220+
export class MyContainer extends Container {
221+
defaultPort = 8080;
222+
enableInternet = false;
223+
interceptHttps = true;
224+
225+
allowedHosts = ['api.stripe.com'];
226+
deniedHosts = ['evil.com'];
227+
}
228+
229+
MyContainer.outboundByHost = {
230+
'google.com': (req, env, ctx) => {
231+
return new Response('intercepted google for ' + ctx.containerId);
232+
},
233+
};
234+
235+
MyContainer.outboundHandlers = {
236+
async github(req, env, ctx) {
237+
return new Response('github handler, ' + ctx.params?.hello);
238+
},
239+
};
240+
241+
MyContainer.outbound = req => {
242+
return new Response(`catch-all for ${new URL(req.url).hostname}`);
243+
};
244+
245+
export default {
246+
async fetch(request, env) {
247+
const container = getContainer(env.MY_CONTAINER);
248+
await container.setOutboundByHost('github.com', 'github', { hello: 'world' });
249+
return await container.fetch(request);
250+
},
251+
};
252+
```
253+
254+
With this configuration:
255+
256+
| Outbound request | Result |
257+
| ------------------------ | ----------------------------------------------------------- |
258+
| `http://evil.com` | Blocked (denied host) |
259+
| `http://google.com` | Handled by `outboundByHost` handler |
260+
| `http://github.com` | Handled by runtime `github` handler |
261+
| `http://api.stripe.com` | Passes allowed gate, handled by `static outbound` catch-all |
262+
| `http://random.com` | Blocked (not in `allowedHosts`) |
263+
| `https://api.stripe.com` | Same as HTTP, intercepted via HTTPS interception |

0 commit comments

Comments
 (0)