diff --git a/.changeset/bumpy-suns-press.md b/.changeset/bumpy-suns-press.md new file mode 100644 index 000000000000..21ce7c8bfbab --- /dev/null +++ b/.changeset/bumpy-suns-press.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/vite-plugin": minor +--- + +Support local explorer `/cdn-cgi/` routes + +The local explorer UI can now be accessed at `/cdn-cgi/explorer`. diff --git a/.changeset/deprecate-ssh-passthrough-flags.md b/.changeset/deprecate-ssh-passthrough-flags.md new file mode 100644 index 000000000000..777a7a842734 --- /dev/null +++ b/.changeset/deprecate-ssh-passthrough-flags.md @@ -0,0 +1,7 @@ +--- +"wrangler": minor +--- + +Deprecate SSH passthrough flags in `wrangler containers ssh` + +The `--cipher`, `--log-file`, `--escape-char`, `--config-file`, `--pkcs11`, `--identity-file`, `--mac-spec`, `--option`, and `--tag` flags are now deprecated. These flags expose OpenSSH-specific options that are tied to the current implementation. A future release will replace the underlying SSH transport, at which point these flags will be removed. They still function for now. diff --git a/.changeset/thin-poems-dream.md b/.changeset/thin-poems-dream.md new file mode 100644 index 000000000000..eed356804992 --- /dev/null +++ b/.changeset/thin-poems-dream.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/containers-shared": minor +--- + +Update the `proxy-everything` image used for containers local dev + +The egress interceptor image now supports HTTPS and ingress over HTTP CONNECT in workerd. diff --git a/packages/containers-shared/src/images.ts b/packages/containers-shared/src/images.ts index 9d41ecb20918..81411a9745cc 100644 --- a/packages/containers-shared/src/images.ts +++ b/packages/containers-shared/src/images.ts @@ -18,7 +18,7 @@ import type { } from "./types"; export const DEFAULT_CONTAINER_EGRESS_INTERCEPTOR_IMAGE = - "cloudflare/proxy-everything:3f5e832@sha256:816255f5b6ebdc2cdcddb578d803121e7ee9cfe178442da07725d75a66cdcf37"; + "cloudflare/proxy-everything:233db0f@sha256:f159d9e1b0f28bc01bd106f38d62479c018d050e3f95b365c5f9b5f83f60df82"; export function getEgressInterceptorImage(): string { return ( diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index 9a07b9c47542..515f3a963bb0 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -2,6 +2,7 @@ import { assertWranglerVersion } from "./assert-wrangler-version"; import { PluginContext } from "./context"; import { resolvePluginConfig } from "./plugin-config"; import { additionalModulesPlugin } from "./plugins/additional-modules"; +import { cdnCgiPlugin } from "./plugins/cdn-cgi"; import { configPlugin } from "./plugins/config"; import { debugPlugin } from "./plugins/debug"; import { devPlugin } from "./plugins/dev"; @@ -14,7 +15,6 @@ import { outputConfigPlugin } from "./plugins/output-config"; import { previewPlugin } from "./plugins/preview"; import { rscPlugin } from "./plugins/rsc"; import { shortcutsPlugin } from "./plugins/shortcuts"; -import { triggerHandlersPlugin } from "./plugins/trigger-handlers"; import { virtualClientFallbackPlugin, virtualModulesPlugin, @@ -81,7 +81,7 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] { previewPlugin(ctx), shortcutsPlugin(ctx), debugPlugin(ctx), - triggerHandlersPlugin(ctx), + cdnCgiPlugin(ctx), virtualModulesPlugin(ctx), virtualClientFallbackPlugin(ctx), outputConfigPlugin(ctx), diff --git a/packages/vite-plugin-cloudflare/src/plugins/cdn-cgi.ts b/packages/vite-plugin-cloudflare/src/plugins/cdn-cgi.ts new file mode 100644 index 000000000000..5d13dae9bea6 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/plugins/cdn-cgi.ts @@ -0,0 +1,43 @@ +import { CoreHeaders } from "miniflare"; +import { createPlugin, createRequestHandler } from "../utils"; + +/** + * Plugin to forward `/cdn-cgi/` routes to Miniflare in development + * We handle specified routes rather than using a catch all so that users can add their own routes using Vite's proxy functionality + */ +export const cdnCgiPlugin = createPlugin("cdn-cgi", (ctx) => { + return { + enforce: "pre", + async configureServer(viteDevServer) { + const entryWorkerConfig = ctx.entryWorkerConfig; + + if (!entryWorkerConfig) { + return; + } + + const entryWorkerName = entryWorkerConfig.name; + const requestHandler = createRequestHandler((request) => { + request.headers.set(CoreHeaders.ROUTE_OVERRIDE, entryWorkerName); + return ctx.miniflare.dispatchFetch(request, { + redirect: "manual", + }); + }); + + viteDevServer.middlewares.use(async (req, res, next) => { + const url = req.originalUrl ?? ""; + + const isLocalExplorer = + url === "/cdn-cgi/explorer" || + url.startsWith("/cdn-cgi/explorer/") || + url.startsWith("/cdn-cgi/explorer?"); + const isTriggerHandler = url.startsWith("/cdn-cgi/handler/"); + + if (isLocalExplorer || isTriggerHandler) { + await requestHandler(req, res, next); + } else { + next(); + } + }); + }, + }; +}); diff --git a/packages/vite-plugin-cloudflare/src/plugins/trigger-handlers.ts b/packages/vite-plugin-cloudflare/src/plugins/trigger-handlers.ts deleted file mode 100644 index 961f1c28cdf9..000000000000 --- a/packages/vite-plugin-cloudflare/src/plugins/trigger-handlers.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CoreHeaders } from "miniflare"; -import { createPlugin, createRequestHandler } from "../utils"; - -/** - * Plugin to forward `/cdn-cgi/handler/*` routes to trigger handlers in development - */ -export const triggerHandlersPlugin = createPlugin("trigger-handlers", (ctx) => { - return { - enforce: "pre", - async configureServer(viteDevServer) { - const entryWorkerConfig = ctx.entryWorkerConfig; - - if (!entryWorkerConfig) { - return; - } - - const entryWorkerName = entryWorkerConfig.name; - const requestHandler = createRequestHandler((request) => { - request.headers.set(CoreHeaders.ROUTE_OVERRIDE, entryWorkerName); - return ctx.miniflare.dispatchFetch(request, { - redirect: "manual", - }); - }); - - viteDevServer.middlewares.use("/cdn-cgi/handler/", requestHandler); - }, - }; -}); diff --git a/packages/wrangler/src/__tests__/containers/ssh.test.ts b/packages/wrangler/src/__tests__/containers/ssh.test.ts index bb6a2afa56cc..d74731c1ea24 100644 --- a/packages/wrangler/src/__tests__/containers/ssh.test.ts +++ b/packages/wrangler/src/__tests__/containers/ssh.test.ts @@ -35,18 +35,7 @@ describe("containers ssh", () => { -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] -h, --help Show help [boolean] - -v, --version Show version number [boolean] - - OPTIONS - --cipher Sets \`ssh -c\`: Select the cipher specification for encrypting the session [string] - --log-file Sets \`ssh -E\`: Append debug logs to log_file instead of standard error [string] - --escape-char Sets \`ssh -e\`: Set the escape character for sessions with a pty (default: '~') [string] - -F, --config-file Sets \`ssh -F\`: Specify an alternative per-user ssh configuration file [string] - --pkcs11 Sets \`ssh -I\`: Specify the PKCS#11 shared library ssh should use to communicate with a PKCS#11 token providing keys for user authentication [string] - -i, --identity-file Sets \`ssh -i\`: Select a file from which the identity (private key) for public key authentication is read [string] - --mac-spec Sets \`ssh -m\`: A comma-separated list of MAC (message authentication code) algorithms, specified in order of preference [string] - -o, --option Sets \`ssh -o\`: Set options in the format used in the ssh configuration file. May be repeated [string] - --tag Sets \`ssh -P\`: Specify a tag name that may be used to select configuration in ssh_config [string]" + -v, --version Show version number [boolean]" `); }); @@ -65,12 +54,12 @@ describe("containers ssh", () => { setWranglerConfig({}); msw.use( http.get(`*/instances/:instanceId/ssh`, async () => { - return new HttpResponse( - `{"success": false, "errors": [{"code": 1000, "message": "something happened"}]}`, + return HttpResponse.json( { - type: "applicaton/json", - status: 500, - } + success: false, + errors: [{ code: 1000, message: "something happened" }], + }, + { status: 500 } ); }) ); @@ -95,12 +84,9 @@ describe("containers ssh", () => { setWranglerConfig({}); msw.use( http.get(`*/instances/:instanceId/ssh`, async () => { - return new HttpResponse( - `{"success": true, "result": {"url": "${wsUrl}", "token": "${sshJwt}"}}`, - { - type: "applicaton/json", - status: 200, - } + return HttpResponse.json( + { success: true, result: { url: wsUrl, token: sshJwt } }, + { status: 200 } ); }) ); diff --git a/packages/wrangler/src/containers/ssh.ts b/packages/wrangler/src/containers/ssh.ts index 42a56d640e03..42309a0795fa 100644 --- a/packages/wrangler/src/containers/ssh.ts +++ b/packages/wrangler/src/containers/ssh.ts @@ -12,78 +12,90 @@ import { import { createCommand } from "../core/create-command"; import { logger } from "../logger"; import { containersScope } from "./index"; -import type { - CommonYargsArgv, - StrictYargsOptionsToInterface, -} from "../yargs-types"; +import type { HandlerArgs, NamedArgDefinitions } from "../core/types"; import type { WranglerSSHResponse } from "@cloudflare/containers-shared"; import type { Config } from "@cloudflare/workers-utils"; import type { Server } from "node:net"; -export function sshYargs(args: CommonYargsArgv) { - return ( - args - .positional("ID", { - describe: "ID of the container instance", - type: "string", - demandOption: true, - }) - // Following are SSH flags that should be directly passed in - .option("cipher", { - describe: - "Sets `ssh -c`: Select the cipher specification for encrypting the session", - type: "string", - }) - .option("log-file", { - describe: - "Sets `ssh -E`: Append debug logs to log_file instead of standard error", - type: "string", - }) - .option("escape-char", { - describe: - "Sets `ssh -e`: Set the escape character for sessions with a pty (default: ‘~’)", - type: "string", - }) - .option("config-file", { - alias: "F", - describe: - "Sets `ssh -F`: Specify an alternative per-user ssh configuration file", - type: "string", - }) - .option("pkcs11", { - describe: - "Sets `ssh -I`: Specify the PKCS#11 shared library ssh should use to communicate with a PKCS#11 token providing keys for user authentication", - type: "string", - }) - .option("identity-file", { - alias: "i", - describe: - "Sets `ssh -i`: Select a file from which the identity (private key) for public key authentication is read", - type: "string", - }) - .option("mac-spec", { - describe: - "Sets `ssh -m`: A comma-separated list of MAC (message authentication code) algorithms, specified in order of preference", - type: "string", - }) - .option("option", { - alias: "o", - describe: - "Sets `ssh -o`: Set options in the format used in the ssh configuration file. May be repeated", - type: "string", - }) - .option("tag", { - describe: - "Sets `ssh -P`: Specify a tag name that may be used to select configuration in ssh_config", - type: "string", - }) - ); -} +// Deprecated SSH flags are hidden because a future SSH implementation +// will not use OpenSSH, at which point these options will not work. +const sshArgDefs = { + ID: { + describe: "ID of the container instance", + type: "string", + demandOption: true, + }, + cipher: { + describe: + "Sets `ssh -c`: Select the cipher specification for encrypting the session", + type: "string", + hidden: true, + deprecated: true, + }, + "log-file": { + describe: + "Sets `ssh -E`: Append debug logs to log_file instead of standard error", + type: "string", + hidden: true, + deprecated: true, + }, + "escape-char": { + describe: + "Sets `ssh -e`: Set the escape character for sessions with a pty (default: '~')", + type: "string", + hidden: true, + deprecated: true, + }, + "config-file": { + alias: "F", + describe: + "Sets `ssh -F`: Specify an alternative per-user ssh configuration file", + type: "string", + hidden: true, + deprecated: true, + }, + pkcs11: { + describe: + "Sets `ssh -I`: Specify the PKCS#11 shared library ssh should use to communicate with a PKCS#11 token providing keys for user authentication", + type: "string", + hidden: true, + deprecated: true, + }, + "identity-file": { + alias: "i", + describe: + "Sets `ssh -i`: Select a file from which the identity (private key) for public key authentication is read", + type: "string", + hidden: true, + deprecated: true, + }, + "mac-spec": { + describe: + "Sets `ssh -m`: A comma-separated list of MAC (message authentication code) algorithms, specified in order of preference", + type: "string", + hidden: true, + deprecated: true, + }, + option: { + alias: "o", + describe: + "Sets `ssh -o`: Set options in the format used in the ssh configuration file. May be repeated", + type: "string", + hidden: true, + deprecated: true, + }, + tag: { + describe: + "Sets `ssh -P`: Specify a tag name that may be used to select configuration in ssh_config", + type: "string", + hidden: true, + deprecated: true, + }, +} as const satisfies NamedArgDefinitions; -export async function sshCommand( - sshArgs: StrictYargsOptionsToInterface, - _config: Config -) { +type SshArgs = HandlerArgs; + +async function sshCommand(sshArgs: SshArgs, _config: Config) { if (sshArgs.ID.length !== 64) { throw new UserError(`Expected an instance ID but got ${sshArgs.ID}`); } @@ -261,9 +273,7 @@ export function createSshTcpProxy(sshResponse: WranglerSSHResponse): Server { return proxy; } -function buildSshArgs( - sshArgs: StrictYargsOptionsToInterface -): string[] { +function buildSshArgs(sshArgs: SshArgs): string[] { const flags = [ // Never use a control socket. "-o", @@ -334,61 +344,7 @@ export const containersSshCommand = createCommand({ owner: "Product: Cloudchamber", hidden: true, }, - args: { - ID: { - describe: "ID of the container instance", - type: "string", - demandOption: true, - }, - cipher: { - describe: - "Sets `ssh -c`: Select the cipher specification for encrypting the session", - type: "string", - }, - "log-file": { - describe: - "Sets `ssh -E`: Append debug logs to log_file instead of standard error", - type: "string", - }, - "escape-char": { - describe: - "Sets `ssh -e`: Set the escape character for sessions with a pty (default: '~')", - type: "string", - }, - "config-file": { - alias: "F", - describe: - "Sets `ssh -F`: Specify an alternative per-user ssh configuration file", - type: "string", - }, - pkcs11: { - describe: - "Sets `ssh -I`: Specify the PKCS#11 shared library ssh should use to communicate with a PKCS#11 token providing keys for user authentication", - type: "string", - }, - "identity-file": { - alias: "i", - describe: - "Sets `ssh -i`: Select a file from which the identity (private key) for public key authentication is read", - type: "string", - }, - "mac-spec": { - describe: - "Sets `ssh -m`: A comma-separated list of MAC (message authentication code) algorithms, specified in order of preference", - type: "string", - }, - option: { - alias: "o", - describe: - "Sets `ssh -o`: Set options in the format used in the ssh configuration file. May be repeated", - type: "string", - }, - tag: { - describe: - "Sets `ssh -P`: Specify a tag name that may be used to select configuration in ssh_config", - type: "string", - }, - }, + args: sshArgDefs, positionalArgs: ["ID"], async handler(args, { config }) { await fillOpenAPIConfiguration(config, containersScope);