Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-remote-proxy-error-propagation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Fix remote proxy worker not catching errors thrown by bindings during `wrangler dev`
18 changes: 18 additions & 0 deletions .changeset/vpc-networks-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"wrangler": minor
"miniflare": minor
"@cloudflare/workers-utils": minor
---

Add `vpc_networks` binding support for routing Worker traffic through a Cloudflare Tunnel or network.

```jsonc
{
"vpc_networks": [
// Route through a specific Cloudflare Tunnel
{ "binding": "MY_FIRST_VPC", "tunnel_id": "<tunnel-id>" },
// Route through the Cloudflare One mesh network
{ "binding": "MY_SECOND_VPC", "network_id": "cf1:network" },
],
}
```
4 changes: 4 additions & 0 deletions packages/miniflare/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
VERSION_METADATA_PLUGIN,
VERSION_METADATA_PLUGIN_NAME,
} from "./version-metadata";
import { VPC_NETWORKS_PLUGIN, VPC_NETWORKS_PLUGIN_NAME } from "./vpc-networks";
import { VPC_SERVICES_PLUGIN, VPC_SERVICES_PLUGIN_NAME } from "./vpc-services";
import {
WORKER_LOADER_PLUGIN,
Expand Down Expand Up @@ -68,6 +69,7 @@ export const PLUGINS = {
[IMAGES_PLUGIN_NAME]: IMAGES_PLUGIN,
[STREAM_PLUGIN_NAME]: STREAM_PLUGIN,
[VECTORIZE_PLUGIN_NAME]: VECTORIZE_PLUGIN,
[VPC_NETWORKS_PLUGIN_NAME]: VPC_NETWORKS_PLUGIN,
[VPC_SERVICES_PLUGIN_NAME]: VPC_SERVICES_PLUGIN,
[MTLS_PLUGIN_NAME]: MTLS_PLUGIN,
[HELLO_WORLD_PLUGIN_NAME]: HELLO_WORLD_PLUGIN,
Expand Down Expand Up @@ -134,6 +136,7 @@ export type WorkerOptions = z.input<typeof CORE_PLUGIN.options> &
z.input<typeof IMAGES_PLUGIN.options> &
z.input<typeof STREAM_PLUGIN.options> &
z.input<typeof VECTORIZE_PLUGIN.options> &
z.input<typeof VPC_NETWORKS_PLUGIN.options> &
z.input<typeof VPC_SERVICES_PLUGIN.options> &
z.input<typeof MTLS_PLUGIN.options> &
z.input<typeof HELLO_WORLD_PLUGIN.options> &
Expand Down Expand Up @@ -214,6 +217,7 @@ export * from "./dispatch-namespace";
export * from "./images";
export * from "./stream";
export * from "./vectorize";
export * from "./vpc-networks";
export * from "./vpc-services";
export * from "./mtls";
export * from "./hello-world";
Expand Down
86 changes: 86 additions & 0 deletions packages/miniflare/src/plugins/vpc-networks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { z } from "zod";
import {
getUserBindingServiceName,
Plugin,
ProxyNodeBinding,
remoteProxyClientWorker,
RemoteProxyConnectionString,
} from "../shared";

const VpcNetworksSchema = z.union([
z.object({
tunnel_id: z.string(),
remoteProxyConnectionString: z
.custom<RemoteProxyConnectionString>()
.optional(),
}),
z.object({
network_id: z.string(),
remoteProxyConnectionString: z
.custom<RemoteProxyConnectionString>()
.optional(),
}),
]);

export const VpcNetworksOptionsSchema = z.object({
vpcNetworks: z.record(VpcNetworksSchema).optional(),
});

export const VPC_NETWORKS_PLUGIN_NAME = "vpc-networks";

export const VPC_NETWORKS_PLUGIN: Plugin<typeof VpcNetworksOptionsSchema> = {
options: VpcNetworksOptionsSchema,
async getBindings(options) {
if (!options.vpcNetworks) {
return [];
}

return Object.entries(options.vpcNetworks).map(([name, binding]) => {
const identifier =
"tunnel_id" in binding ? binding.tunnel_id : binding.network_id;
return {
name,

service: {
name: getUserBindingServiceName(
VPC_NETWORKS_PLUGIN_NAME,
identifier,
binding.remoteProxyConnectionString
),
},
};
});
},
getNodeBindings(options: z.infer<typeof VpcNetworksOptionsSchema>) {
if (!options.vpcNetworks) {
return {};
}
return Object.fromEntries(
Object.keys(options.vpcNetworks).map((name) => [
name,
new ProxyNodeBinding(),
])
);
},
async getServices({ options }) {
if (!options.vpcNetworks) {
return [];
}

return Object.entries(options.vpcNetworks).map(([name, binding]) => {
const identifier =
"tunnel_id" in binding ? binding.tunnel_id : binding.network_id;
return {
name: getUserBindingServiceName(
VPC_NETWORKS_PLUGIN_NAME,
identifier,
binding.remoteProxyConnectionString
),
worker: remoteProxyClientWorker(
binding.remoteProxyConnectionString,
name
),
};
});
},
};
1 change: 1 addition & 0 deletions packages/workers-utils/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,5 @@ export const defaultWranglerConfig: Config = {
streaming_tail_consumers: undefined,
pipelines: [],
vpc_services: [],
vpc_networks: [],
};
28 changes: 28 additions & 0 deletions packages/workers-utils/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,34 @@ export interface EnvironmentNonInheritable {
/** Whether the VPC service is remote or not */
remote?: boolean;
}[];

/**
* Specifies VPC networks that are bound to this Worker environment.
*
* NOTE: This field is not automatically inherited from the top level environment,
* and so must be specified in every named environment.
*
* @default []
* @nonInheritable
*/
vpc_networks: (
| {
/** The binding name used to refer to the VPC network in the Worker. */
binding: string;
/** The tunnel ID of the Cloudflare Tunnel to route traffic through. Mutually exclusive with network_id. */
tunnel_id: string;
/** Whether the VPC network is remote or not */
remote?: boolean;
}
| {
/** The binding name used to refer to the VPC network in the Worker. */
binding: string;
/** The network ID to route traffic through. Mutually exclusive with tunnel_id. */
network_id: string;
/** Whether the VPC network is remote or not */
remote?: boolean;
}
)[];
}

/**
Expand Down
75 changes: 74 additions & 1 deletion packages/workers-utils/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ export type ConfigBindingFieldName =
| "assets"
| "unsafe_hello_world"
| "worker_loaders"
| "vpc_services";
| "vpc_services"
| "vpc_networks";
Comment thread
RiscadoA marked this conversation as resolved.

/**
* @deprecated new code should use getBindingTypeFriendlyName() instead
Expand Down Expand Up @@ -145,6 +146,7 @@ export const friendlyBindingNames: Record<ConfigBindingFieldName, string> = {
unsafe_hello_world: "Hello World",
worker_loaders: "Worker Loader",
vpc_services: "VPC Service",
vpc_networks: "VPC Network",
} as const;

/**
Expand Down Expand Up @@ -187,6 +189,7 @@ const bindingTypeFriendlyNames: Record<Binding["type"], string> = {
ratelimit: "Rate Limit",
worker_loader: "Worker Loader",
vpc_service: "VPC Service",
vpc_network: "VPC Network",
media: "Media",
assets: "Assets",
inherit: "Inherited",
Expand Down Expand Up @@ -1911,6 +1914,16 @@ function normalizeAndValidateEnvironment(
validateBindingArray(envName, validateVpcServiceBinding),
[]
),
vpc_networks: notInheritable(
diagnostics,
topLevelEnv,
rawConfig,
rawEnv,
envName,
"vpc_networks",
validateBindingArray(envName, validateVpcNetworkBinding),
[]
),
version_metadata: notInheritable(
diagnostics,
topLevelEnv,
Expand Down Expand Up @@ -2958,6 +2971,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
"pipeline",
"worker_loader",
"vpc_service",
"vpc_network",
"stream",
"media",
];
Expand Down Expand Up @@ -4093,6 +4107,65 @@ const validateVpcServiceBinding: ValidatorFn = (diagnostics, field, value) => {
return isValid;
};

const validateVpcNetworkBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
`"vpc_networks" bindings should be objects, but got ${JSON.stringify(
value
)}`
);
return false;
}
let isValid = true;
// VPC network bindings must have a binding and exactly one of tunnel_id or network_id.
if (!isRequiredProperty(value, "binding", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
const hasTunnelId = hasProperty(value, "tunnel_id");
const hasNetworkId = hasProperty(value, "network_id");
if (hasTunnelId && hasNetworkId) {
diagnostics.errors.push(
`"${field}" bindings must have either a "tunnel_id" or "network_id", but not both.`
);
isValid = false;
} else if (!hasTunnelId && !hasNetworkId) {
diagnostics.errors.push(
`"${field}" bindings must have either a "tunnel_id" or "network_id" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
} else if (hasTunnelId && typeof value.tunnel_id !== "string") {
diagnostics.errors.push(
`"${field}" bindings must have a string "tunnel_id" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
} else if (hasNetworkId && typeof value.network_id !== "string") {
diagnostics.errors.push(
`"${field}" bindings must have a string "network_id" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

validateAdditionalProperties(diagnostics, field, Object.keys(value), [
"binding",
"tunnel_id",
"network_id",
"remote",
]);

return isValid;
};

/**
* Check that bindings whose names might conflict, don't.
*
Expand Down
15 changes: 15 additions & 0 deletions packages/workers-utils/src/map-worker-metadata-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,21 @@ export function mapWorkerMetadataBindings(
];
}
break;
case "vpc_network":
{
if (binding.tunnel_id !== undefined) {
configObj.vpc_networks = [
...(configObj.vpc_networks ?? []),
{ binding: binding.name, tunnel_id: binding.tunnel_id },
];
} else if (binding.network_id !== undefined) {
configObj.vpc_networks = [
...(configObj.vpc_networks ?? []),
{ binding: binding.name, network_id: binding.network_id },
];
}
}
break;
Comment thread
RiscadoA marked this conversation as resolved.
default: {
configObj.unsafe = {
bindings: [...(configObj.unsafe?.bindings ?? []), binding],
Expand Down
8 changes: 8 additions & 0 deletions packages/workers-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import type {
CfUnsafeBinding,
CfUserLimits,
CfVectorize,
CfVpcNetwork,
CfVpcService,
CfWorkerLoader,
CfWorkflow,
Expand Down Expand Up @@ -165,6 +166,12 @@ export type WorkerMetadataBinding =
simple: { limit: number; period: 10 | 60 };
}
| { type: "vpc_service"; name: string; service_id: string }
| {
type: "vpc_network";
name: string;
tunnel_id?: string;
network_id?: string;
}
| {
type: "worker_loader";
name: string;
Expand Down Expand Up @@ -328,6 +335,7 @@ export type Binding =
| ({ type: "ratelimit" } & NameOmit<CfRateLimit>)
| ({ type: "worker_loader" } & BindingOmit<CfWorkerLoader>)
| ({ type: "vpc_service" } & BindingOmit<CfVpcService>)
| ({ type: "vpc_network" } & BindingOmit<CfVpcNetwork>)
| ({ type: "media" } & BindingOmit<CfMediaBinding>)
| ({ type: `unsafe_${string}` } & Omit<CfUnsafeBinding, "name" | "type">)
| { type: "assets" }
Expand Down
7 changes: 7 additions & 0 deletions packages/workers-utils/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@ export interface CfVpcService {
remote?: boolean;
}

export interface CfVpcNetwork {
binding: string;
tunnel_id?: string;
network_id?: string;
remote?: boolean;
}

export interface CfAnalyticsEngineDataset {
binding: string;
dataset?: string;
Expand Down
Loading
Loading