跳转到内容
搜索文档

处理出站流量

最后更新 查看 MarkdownAgent 设置

出站处理程序允许您使用受信任的代码拦截并修改来自容器的 HTTP 流量。

使用它们来:

  • 允许或拒绝特定的源目标
  • 安全地注入授权标头或令牌
  • 透明地重新路由流量
  • 对出站流量添加自定义策略(例如拒绝特定的 HTTP 请求)
  • 连接到 Workers 绑定,如 KV、R2 和 Durable Objects

阻止出站流量

默认情况下使用 enableInternet = false 阻止公共互联网访问:

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	enableInternet = false;
}

enableInternetfalse 时,只有您稍后在本页通过 allowedHosts 或出站处理程序显式允许的流量才能离开容器。仅端口 80443 和 DNS 可用,并且 DNS 查询使用 Cloudflare 的 DNS 服务器。

按主机阻止或允许流量

您可以使用 Container 类上的 allowedHostsdeniedHosts 属性过滤出站流量。

设置 allowedHosts 后,它将成为默认拒绝的允许列表。任何不在列表中的主机或 IP 都将被拒绝,只有匹配的目标才能到达 outboundoutboundByHost 处理程序。

allowedHostsdeniedHosts 还支持简单的 glob 模式,其中 * 匹配任何字符序列。

默认情况下,Container 将允许互联网访问,您可以设置 deniedHosts 以禁止特定主机或 IP:

import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	// Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt
	interceptHttps = true;
	deniedHosts = ["some-nefarious-website.com", "141.101.64.0/18"];
}

您还可以默认禁用互联网访问,但允许特定主机和 IP:

import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	// Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt
	interceptHttps = true;

	// default internet access to off unless overridden by 'allowedHosts' or outbound proxy
	enableInternet = false;

	// overrides enableInternet = false
	allowedHosts = ["allowed.com"];
}

定义出站处理程序

出站处理程序是在与容器相同的机器上运行的可编程出口代理。它们有权访问所有 Workers 绑定。

使用 outbound 拦截所有 HTTP 和 HTTPS 流量:

import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	interceptHttps = true;
}

MyContainer.outbound = async (request, env, ctx) => {
	if (request.method !== "GET") {
		console.log(`Blocked ${request.method} to ${request.url}`);
		return new Response("Method Not Allowed", { status: 405 });
	}
	return fetch(request);
};

使用 outboundByHost 将特定的域名或 IP 地址映射到代理函数:

import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	interceptHttps = true;
}

MyContainer.outboundByHost = {
	"my.worker": async (request, env, ctx) => {
		// Run arbitrary Workers logic from this hostname
		return await someWorkersFunction(request.body);
	},
};

从容器调用 http://my.worker 会调用处理程序,该处理程序在容器沙盒外部的 Workers 运行时内运行。

deniedHostsallowedHosts 在任何出站处理程序之前进行评估。如果您使用 allowedHosts,请在其中包含主机名以运行 outboundoutboundByHostoutboundByHost 处理程序优先于包罗万象的 outbound 处理程序。

安全注入凭证

因为出站处理程序在 Workers 运行时中运行(在容器沙盒之外),所以它们可以保存容器本身永远看不到的机密。容器发出纯 HTTP 请求,处理程序在将凭证转发到上游服务之前附加凭证。

export class MyContainer extends Container {
	// Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt
	interceptHttps = true;
}

MyContainer.outboundByHost = {
	"github.com": (request, env, ctx) => {
		const requestWithAuth = new Request(request);
		requestWithAuth.headers.set("x-auth-token", env.SECRET);
		return fetch(requestWithAuth);
	},
};

这对于无法完全信任在容器内部运行的代码的代理工作负载特别有用。使用此模式:

  • 没有令牌暴露给容器。 机密存在于 Worker 的环境中,永远不会传递到沙盒中。
  • 容器内无需轮换令牌。 在您的 Worker 环境中轮换机密,每个请求都会立即获取它。
  • 每主机和每实例规则。outboundByHostctx.containerId 结合使用,以将凭证或权限的作用域限制为特定的容器实例。

在这里,ctx.containerId 从 KV 中查找每实例键:

export class MyContainer extends Container {
	// Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt
	interceptHttps = true;
}

MyContainer.outboundByHost = {
	"my-internal-vcs.dev": async (request, env, ctx) => {
		const authKey = await env.KEYS.get(ctx.containerId);

		const requestWithAuth = new Request(request);
		requestWithAuth.headers.set("x-auth-token", authKey);
		return fetch(requestWithAuth);
	},
};

HTTPS 流量

默认情况下,出站处理程序不会拦截 HTTPS 流量。要选择加入,您必须设置 interceptHttps 属性。

export class MyContainer extends Container {
	// Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt
	interceptHttps = true;
}

MyContainer.outbound = (req, env, ctx) => {
	// All HTTP(S) requests will trigger this hook.
	return fetch(req);
};

这对于类似 Sandbox 的服务很有用,这些服务将不受信任的流量从容器实例重定向到 Workers 以进行过滤和修改。

当 HTTPS 拦截处于活动状态时,容器启动后将在 /etc/cloudflare/certs/cloudflare-containers-ca.crt 处创建一个临时 CA 文件。仅当您既设置 interceptHttps = true 又定义 outboundoutboundByHost 处理程序时,才会注入 CA。

信任 CA 证书

为了使 HTTPS 拦截起作用,您必须信任 CA 文件。该 CA 是临时的,仅在运行时存在,因此不要尝试在 docker build 期间将其烘焙到映像中。相反,请将其复制到发行版的信任存储中,并在应用程序启动之前从容器 entrypoint 刷新信任存储。

如果您的基础映像尚未包含信任存储工具,请先在您的映像中安装发行版的 ca-certificates 包。

import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	interceptHttps = true;
	entrypoint = [
		"sh",
		"-lc",
		[
			"cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /usr/local/share/ca-certificates/cloudflare-containers-ca.crt",
			"update-ca-certificates",
			"exec node server.js",
		].join(" && "),
	];
}
import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	interceptHttps = true;
	entrypoint = [
		"sh",
		"-lc",
		[
			"cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /usr/local/share/ca-certificates/cloudflare-containers-ca.crt",
			"update-ca-certificates",
			"exec node server.js",
		].join(" && "),
	];
}
import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	interceptHttps = true;
	entrypoint = [
		"sh",
		"-lc",
		[
			"cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /etc/pki/ca-trust/source/anchors/cloudflare-containers-ca.crt",
			"update-ca-trust",
			"exec node server.js",
		].join(" && "),
	];
}
import { Container, ContainerProxy } from "@cloudflare/containers";
export { ContainerProxy };

export class MyContainer extends Container {
	interceptHttps = true;
	entrypoint = [
		"sh",
		"-lc",
		[
			"cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /etc/ca-certificates/trust-source/anchors/cloudflare-containers-ca.crt",
			"trust extract-compat",
			"exec node server.js",
		].join(" && "),
	];
}

node server.js 替换为启动应用程序的命令。

然后,大多数运行时将通过系统根存储自动信任 CA。如果您的运行时使用自己的 CA 捆绑包,请将其直接指向 /etc/cloudflare/certs/cloudflare-containers-ca.crt,例如使用 NODE_EXTRA_CA_CERTSREQUESTS_CA_BUNDLE

非 HTTP 流量

出站处理程序仅拦截 HTTP 和 HTTPS 流量。除了 80443 之外的端口上的流量永远不会通过 outboundoutboundByHost 路由。

如果您设置 enableInternet = false,该流量将被拒绝。DNS 查询是唯一的例外,但它们仅转到 Cloudflare 的 DNS 服务器。这可以防止使用任意 DNS 目标进行数据泄露。

在运行时更改策略

使用 outboundHandlers 定义命名处理程序,然后使用 setOutboundByHost() 在运行时将它们分配给特定主机。您还可以使用 setOutboundHandler() 全局应用处理程序。

您还可以使用 setOutboundByHosts()setAllowedHosts()setDeniedHosts()allowHost()denyHost()removeAllowedHost()removeDeniedHost() 管理运行时策略。

这使受信任的 Worker 可以保存凭证而不将其暴露给不受信任的容器:

export class MyContainer extends Container {
	// Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt
	interceptHttps = true;
}

MyContainer.outboundHandlers = {
	authenticatedGithub: async (request, env, ctx) => {
		const githubToken = env.GITHUB_TOKEN;
		return authenticateGitHttpsRequest(request, githubToken, ctx.containerId);
	},
};

从您的 Worker 以编程方式将处理程序应用到主机:

async setUpContainer(req, env) {
  const container = await env.MY_CONTAINER.getByName("my-instance");

  // Give the container access to github.com on a specific host during setup
  await container.setOutboundByHost("github.com", "authenticatedGithub");

	// do something with github.com on your container...
}

async removeAccessToGithub(req, env) {
  const container = await env.MY_CONTAINER.getByName("my-instance");

  // Remove access to Github
  await container.removeOutboundByHost("github.com");
}

处理程序优先级

请求按以下顺序评估:

  1. 首先检查 deniedHosts。匹配的主机或 IP 将立即被拒绝。
  2. 接下来检查 allowedHosts。设置后,任何不在列表中的主机或 IP 都将被拒绝。匹配的主机将继续到出站处理程序,如果未设置处理程序,则出口到公共互联网。
  3. 使用 setOutboundByHost() 设置的实例级规则在类级 outboundByHost 规则之前进行检查。
  4. 每主机处理程序始终优先于包罗万象的处理程序,因此 outboundByHostoutbound 之前运行。
  5. 使用 setOutboundHandler() 设置的实例级处理程序在类级 outbound 处理程序之前进行检查。
  6. 如果没有匹配的处理程序,当请求匹配 allowedHostsenableInternet = true 时,它仍然可以出口到公共互联网。否则,它将被拒绝。

底层 API

要直接在 ctx.container 上配置出站拦截,请对特定的主机名 glob、IP 或 CIDR 范围使用 interceptOutboundHttp,或者对所有流量使用 interceptAllOutboundHttp。两者都接受 WorkerEntrypoint

import { WorkerEntrypoint } from "cloudflare:workers";

export class MyOutboundWorker extends WorkerEntrypoint {
	fetch(request) {
		// Inspect, modify, or deny the request before passing it on
		return fetch(request);
	}
}

// Inside your Container DurableObject
this.ctx.container.start({ enableInternet: false });
const worker = this.ctx.exports.MyOutboundWorker({ props: {} });
await this.ctx.container.interceptAllOutboundHttp(worker);

您可以在启动容器之前或之后,甚至在连接打开时调用这些方法。运行中的 TCP 连接会自动获取新处理程序 — 不会断开任何连接。

// Intercept a specific CIDR range
await this.ctx.container.interceptOutboundHttp("203.0.113.0/24", worker);
// Intercept by hostname
this.ctx.container.interceptOutboundHttp("foo.com", worker);

// Update the handler while the container is running
const updated = this.ctx.exports.MyOutboundWorker({
	props: { phase: "post-install" },
});
await this.ctx.container.interceptOutboundHttp("203.0.113.0/24", updated);

对于 HTTPS,interceptOutboundHttps 的工作方式与 interceptOutboundHttp 相同。

// Intercept a specific hostname
this.ctx.container.interceptOutboundHttps("foo.com", worker);

// Intercept all traffic
this.ctx.container.interceptOutboundHttps("*", worker);

当您使用上面显示的函数时,Container 类会自动调用这些方法。您也可以针对类未涵盖的情况直接调用它们。

本地开发

wrangler dev 支持出站拦截。在容器的网络命名空间内生成一个 sidecar 进程。它应用 TPROXY 规则将匹配的流量路由到本地 Workerd 实例,从而反映生产行为。

相关资源

这篇文档对您有帮助吗?