INTEGRITY Документация

Обработка исходящего трафика

Outbound handlers позволяют перехватывать и изменять HTTP-трафик от контейнера с помощью доверенного кода.

Используйте их, чтобы:

Блокируйте исходящий трафик

Используйте enableInternet = false чтобы по умолчанию блокировать доступ к публичному интернету:

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

export class MyContainer extends Container {
	enableInternet = false;
}

Когда enableInternet это false, только трафик, который вы явно разрешите далее на этой странице через allowedHosts или исходящие обработчики могут покидать контейнер. Только порты 80, 443, и DNS доступны, а DNS-запросы используют DNS-серверы Cloudflare.

Блокируйте или разрешайте трафик по хосту

Исходящий трафик можно фильтровать с помощью allowedHosts и deniedHosts свойства класса Container.

Когда allowedHosts задан, он становится белым списком с запретом по умолчанию. Любой хост или IP, не входящий в список, блокируется, и только совпадающие адреса назначения могут получить доступ к outbound или outboundByHost обработчики.

allowedHosts и deniedHosts также поддерживают простые 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"];
}

Определите обработчики исходящих запросов

Outbound handlers представляют собой программируемые egress-прокси, которые работают на той же машине, что и контейнер. У них есть доступ ко всем привязкам 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, за пределами песочницы контейнера.

deniedHosts и allowedHosts вычисляются раньше любого outbound-обработчика. Если вы используете allowedHosts, укажите там имя хоста для outbound или outboundByHost для запуска. outboundByHost обработчики имеют приоритет над catch-all 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);
	},
};

Это особенно полезно для агентных нагрузок, где нельзя полностью доверять коду, выполняющемуся внутри контейнера. При таком подходе:

Здесь 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);
};

Это полезно для сервисов вроде песочниц, которые перенаправляют ненадёжный трафик от экземпляра контейнера в Workers для фильтрации и изменения.

Когда перехват HTTPS активен, временный файл CA будет создан по пути /etc/cloudflare/certs/cloudflare-containers-ca.crt после запуска контейнера. Сертификат CA внедряется только в том случае, если вы одновременно задали interceptHttps = true и определите outbound или outboundByHost обработчик.

Доверие сертификату CA

Чтобы перехват HTTPS работал, необходимо добавить файл CA в доверенные. CA является временным и существует только во время выполнения, поэтому не пытайтесь встраивать его в образ во время docker build. Вместо этого скопируйте его в хранилище доверенных сертификатов вашего дистрибутива и обновите это хранилище из контейнера entrypoint перед запуском приложения.

Если базовый образ ещё не включает инструменты для работы с trust-store, установите соответствующий пакет дистрибутива 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_CERTS или REQUESTS_CA_BUNDLE.

Трафик не по HTTP

Исходящие обработчики перехватывают только HTTP- и HTTPS-трафик. Трафик на портах, отличных от 80 и 443 никогда не направляется через outbound или outboundByHost.

Если вы зададите enableInternet = false, такой трафик блокируется. Исключение составляют DNS-запросы, но они направляются только на DNS-серверы Cloudflare. Это исключает использование произвольных 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. Обработчики для конкретного хоста всегда имеют приоритет над catch-all обработчиками, поэтому outboundByHost выполняется перед outbound.
  5. Обработчики уровня инстанса, заданные с помощью setOutboundHandler() проверяются раньше, чем на уровне класса outbound обработчик.
  6. Если ни один обработчик не подошёл, запрос всё равно может выйти в публичный интернет, если он соответствовал allowedHosts или enableInternet = true. В противном случае запрос отклоняется.

Низкоуровневый API

Чтобы настроить перехват исходящих запросов напрямую в ctx.container, используйте interceptOutboundHttp для конкретного glob-шаблона имени хоста, IP-адреса или диапазона CIDR, либо 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 поддерживает перехват исходящего трафика. Внутри сетевого пространства имён контейнера запускается сайдкар-процесс, который применяет TPROXY правила для маршрутизации подходящего трафика к локальному экземпляру Workerd, повторяя поведение продакшена.