feat(wrangler): add wrangler tunnel#12492
feat(wrangler): add wrangler tunnel#12492thomasgauvin wants to merge 7 commits intocloudflare:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 41a958f The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
.changeset/tunnel-commands.md
Outdated
|
|
||
| The `run` and `quick-start` commands automatically download and manage the cloudflared binary, caching it in `~/.wrangler/cloudflared/`. You can override the binary location with the `WRANGLER_CLOUDFLARED_PATH` environment variable. | ||
|
|
||
| These commands align with the `cloudflared tunnel` CLI naming conventions. |
There was a problem hiding this comment.
Huge PR!
Maybe we should start with one/a few commands, do a review and add more later?
Who is the POC on the ANT team for this feature?
These commands align with the
cloudflared tunnelCLI naming conventions.
IMO we should rather align with other wrangler commands... or use cloudflared tunnel directly ?
There was a problem hiding this comment.
I believe @petebacondarwin is the POC for us. Our Tunnel requirement is that we need to align wrangler commands with cloudflared so that there is no discrepancy between how developers use different Cloudflare CLIs.
| force: { | ||
| type: "boolean", |
There was a problem hiding this comment.
| force: { | |
| type: "boolean", | |
| force: { | |
| alias: "y", | |
| type: "boolean", |
There was a problem hiding this comment.
This suggestion will likely break CI as the indentation is wrong. This either needs to be applied manually or have its indentation fixed to match the type property.
| export const tunnelDeleteCommand = createCommand({ | ||
| metadata: { | ||
| description: "Delete a Cloudflare Tunnel", | ||
| status: "stable", |
There was a problem hiding this comment.
IMO we should always introduce new feature as experimental first
| logger.log(`Deleting tunnel "${args.tunnel}"`); | ||
| const tunnelId = await resolveTunnelId(sdk, accountId, args.tunnel); | ||
| await deleteTunnel(sdk, accountId, tunnelId); |
There was a problem hiding this comment.
What if the tunnel does not exist?
There was a problem hiding this comment.
The API error is passed to the user as is in this case:
✔ Are you sure you want to delete tunnel "player"? This action cannot be undone. … yes
Deleting tunnel "player"
✘ [ERROR] "player" is neither the ID nor the name of any of your tunnels
|
Converting to draft since this PR is to act as a spec for the final commands that are to be landed as part of this feature. |
|
|
||
| Adds a new set of commands for managing Cloudflare Tunnels directly from Wrangler: | ||
|
|
||
| - `wrangler tunnel create <name>` - Create a new Cloudflare Tunnel |
There was a problem hiding this comment.
For these API operation to work the user usually has to run cloudflared login. How does this work here?
.changeset/tunnel-commands.md
Outdated
|
|
||
| - `wrangler tunnel token <tunnel>` - Print the tunnel token (or write credentials JSON with `--cred-file`) | ||
| - `wrangler tunnel cleanup <tunnels...>` - Remove stale tunnel connections | ||
| - `wrangler tunnel service install <tunnel>` / `wrangler tunnel service uninstall` - Install/uninstall cloudflared as a system service |
There was a problem hiding this comment.
This will also require downloading cloudflared, but it's not mentioned below
.changeset/tunnel-commands.md
Outdated
|
|
||
| - `wrangler tunnel token <tunnel>` - Print the tunnel token (or write credentials JSON with `--cred-file`) | ||
| - `wrangler tunnel cleanup <tunnels...>` - Remove stale tunnel connections | ||
| - `wrangler tunnel service install <tunnel>` / `wrangler tunnel service uninstall` - Install/uninstall cloudflared as a system service |
There was a problem hiding this comment.
Not sure if this makes sense for wrangler to support. Cloudflared running as a service is more for servers, wrangler is a cli tool for users. This doesn't seem useful
.changeset/tunnel-commands.md
Outdated
| - `wrangler tunnel cleanup <tunnels...>` - Remove stale tunnel connections | ||
| - `wrangler tunnel service install <tunnel>` / `wrangler tunnel service uninstall` - Install/uninstall cloudflared as a system service | ||
| - `wrangler tunnel route dns <tunnel> <hostname>` - Create a DNS CNAME route to a tunnel | ||
| - `wrangler tunnel route ip add <network> <tunnel>` / `list` / `delete` / `get` - Manage private network routes for WARP |
There was a problem hiding this comment.
Not sure if this makes sense for wrangler to support. Wrangler will be mostly used by Workers VPC users. Those do not use ip routes in any capacity
| "--credentials-contents", | ||
| "SECRET_CREDS", | ||
| "--origincert", | ||
| "/path/to/cert.pem", |
There was a problem hiding this comment.
origin cert is something no longer supported so this can be removed
| "run", | ||
| "--token", | ||
| "SECRET_TOKEN", | ||
| "--credentials-contents", |
There was a problem hiding this comment.
we should drive only to use secret_token, and avoid them using credential files. We can fetch the token from the API's
|
|
||
| expect(binPath).toContain(expectedDir); | ||
|
|
||
| if (process.platform === "win32") { |
There was a problem hiding this comment.
Is win32 the only Windows platform?
| import { decodeTunnelTokenToCredentialsFile } from "../tunnel/credentials"; | ||
|
|
||
| describe("tunnel token credentials decoding", () => { | ||
| it("decodes cloudflared-style tunnel token into credentials file shape", () => { |
There was a problem hiding this comment.
Why does wrangler need to know about the tunnel token format?
| conns_active_at: tunnel.conns_active_at as string | undefined, | ||
| conns_inactive_at: tunnel.conns_inactive_at as string | undefined, | ||
| tun_type: tunnel.tun_type as CloudflareTunnelResource["tun_type"], | ||
| connections: tunnel.connections as CloudflareTunnelResource["connections"], |
There was a problem hiding this comment.
On the tunnel list endpoint, connections are deprecated and will be removed in the future, so just avoid using it there.
| import { resolveTunnelId } from "../client"; | ||
|
|
||
| /** | ||
| * Hostname validation matching cloudflared's implementation. |
There was a problem hiding this comment.
Does wrangler need to do an extra validation? Can't it let cloudflared do the validation and return any error?
There was a problem hiding this comment.
Oh this is not calling cloudflared tunnel route dns. It's calling the API directly.
| @@ -0,0 +1,10 @@ | |||
| import { createNamespace } from "../../core/create-command"; | |||
|
|
|||
| export const tunnelRouteNamespace = createNamespace({ | |||
| conns_active_at?: string; | ||
| conns_inactive_at?: string; | ||
| tun_type?: | ||
| | "cfd_tunnel" |
There was a problem hiding this comment.
For wranlger we only need to care about cfd_tunnel.
| * Create a new tunnel | ||
| */ | ||
| export async function createTunnel( | ||
| sdk: Cloudflare, |
There was a problem hiding this comment.
I'm not familiar with this code base. What is sdk? Is it a cloudflare API client?
| } | ||
|
|
||
| async function getExpectedAssetSha256(assetName: string): Promise<string> { | ||
| const release = await getGithubRelease(CLOUDFLARED_VERSION); |
| return cached; | ||
| } | ||
|
|
||
| const url = `https://api.github.com/repos/cloudflare/cloudflared/releases/tags/${version}`; |
There was a problem hiding this comment.
Are we worried about hitting GitHub API rate limit?
There was a problem hiding this comment.
We actually have a worker called by cloudflared for updates https://github.com/cloudflare/cloudflared/blob/master/cmd/cloudflared/updater/update.go#L29. It takes the OS, arch and current version in query parameters https://github.com/cloudflare/cloudflared/blob/master/cmd/cloudflared/updater/workers_service.go#L64.
This worker uses KV for caching.
There was a problem hiding this comment.
Yes please use this to check for versions, its the same API used by cloudflared
| if (!response.ok) { | ||
| throw new UserError( | ||
| `[cloudflared] Failed to fetch cloudflared release metadata from ${url}\n\n` + | ||
| `HTTP ${response.status}: ${response.statusText}` |
There was a problem hiding this comment.
Do we want to return the error message in response body?
| logger.log(` You'll get a random *.trycloudflare.com URL to share.`); | ||
| logger.log(` The tunnel will stop when you press Ctrl+C.\n`); | ||
|
|
||
| // Spawn cloudflared process with automatic binary management |
There was a problem hiding this comment.
What does automatic binary management mean?
There was a problem hiding this comment.
If wrangler downloads cloudflared for the user, it also auto-updates it. Every invocation queries what the latest cloudflared version is, and if the cached version doesn't match the latest, it downloads the new one.
There was a problem hiding this comment.
We don't need this if we only support quick tunnels.
|
Love it. It would be great if we could support something like |
I think that is a nice feature that is sort of orthogonal to this - except that they would both potentially download and run cloudflared. |
.changeset/tunnel-commands.md
Outdated
| Additional tunnel tooling: | ||
|
|
||
| - `wrangler tunnel token <tunnel>` - Print the tunnel token (or write credentials JSON with `--cred-file`) | ||
| - `wrangler tunnel cleanup <tunnels...>` - Remove stale tunnel connections |
There was a problem hiding this comment.
This functionality shouldn't be needed as it's a legacy supported command but extremely unlikely to need to be used by current customers.
.changeset/tunnel-commands.md
Outdated
| - `wrangler tunnel create <name>` - Create a new Cloudflare Tunnel | ||
| - `wrangler tunnel list` - List all tunnels in your account | ||
| - `wrangler tunnel info <tunnel>` - Display details about a specific tunnel | ||
| - `wrangler tunnel update <tunnel> --name <new-name>` - Rename a tunnel |
There was a problem hiding this comment.
I don't imagine customers will really leverage this functionality as you can only adjust the tunnel name. Let's omit it for now and only add it if we get some user requests for it.
.changeset/tunnel-commands.md
Outdated
|
|
||
| Additional tunnel tooling: | ||
|
|
||
| - `wrangler tunnel token <tunnel>` - Print the tunnel token (or write credentials JSON with `--cred-file`) |
There was a problem hiding this comment.
We would not like this functionality to be provided for two reasons:
- We are hoping to remove this functionality in the future and lean more heavily into proper token management instead of allowing customers to read the token after tunnel creation.
- We don't want to restrict our future functionality (described in 1.) by then needing to support this command in wrangler for the lifetime of the wrangler-cli. In cloudflared we support a strict 1 year lifetime for existing cloudflared versions to allow us the capability to make backwards incompatible changes in the future.
The recommended way that we should support running tunnels in wrangler should be:
- Create a tunnel (via the command)
- Save the secret returned in the
~/.cloudflareddirectory (the same way that cloudflared does) - Running a tunnel then requires either:
- A provided token (as manual input) via
wrangler tunnel run --token <token> - Fetching the token from
~/.cloudflareddirectory viawrangler tunnel run <tunnel>(requires looking up the tunnel id to fetch the right secret from the directory) - Providing token file manually via
wrangler tunnel run --token-file <token-file>
- A provided token (as manual input) via
.changeset/tunnel-commands.md
Outdated
|
|
||
| Additional tunnel tooling: | ||
|
|
||
| - `wrangler tunnel token <tunnel>` - Print the tunnel token (or write credentials JSON with `--cred-file`) |
There was a problem hiding this comment.
--cred-file in wrangler should just be unified to --token-file as there is no reason to rely on the confusing legacy naming that cloudflared uses.
There are two options that should be supported for wrangler tunnel run:
--token <token>--token-file <token-file>
| // PUT /zones/{zoneTag}/tunnels/{tunnelId}/routes | ||
| const result = await fetchResult<DNSRouteResult>( | ||
| config, | ||
| `/zones/${zoneTag}/tunnels/${tunnelId}/routes`, |
There was a problem hiding this comment.
Instead of using this endpoint, please leverage the standard DNS API to create a CNAME route for the tunnel.
https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/
|
|
||
| logger.log(`Deleting tunnel "${args.tunnel}"`); | ||
| const tunnelId = await resolveTunnelId(sdk, accountId, args.tunnel); | ||
| await deleteTunnel(sdk, accountId, tunnelId); |
There was a problem hiding this comment.
If there are resources (hostname routes, IP routes) attached to the tunnel, a user will be unable to delete it. Not sure what this error condition will look like in that case.
There was a problem hiding this comment.
It passes the error onto the user as is:
✔ Are you sure you want to delete tunnel "play"? This action cannot be undone. … yes
Deleting tunnel "play"
✘ [ERROR] A request to the Cloudflare API failed.
Cannot delete tunnel because it has Warp routing configured. You can delete the route with
`cloudflared tunnel route ip delete`. [code: 1023]
If you think this is a bug, please open an issue at:
https://github.com/cloudflare/workers-sdk/issues/new/choose
| ): Promise<CloudflareTunnelResource> { | ||
| return withTunnelPermissionCheck(async () => { | ||
| const response = (await sdk.zeroTrust.tunnels.cloudflared.create({ | ||
| account_id: accountId, |
There was a problem hiding this comment.
Every tunnel through this tool should only be creating remotely managed tunnels which requires another field: config_src: "cloudflare". Locally managed tunnels are not the direction that we want to support for this use-case. Future improvements to ingress rule APIs will also go in this direction.
| import type { ChildProcess } from "node:child_process"; | ||
|
|
||
| // cloudflared version to download | ||
| export const CLOUDFLARED_VERSION = "2026.1.2"; |
There was a problem hiding this comment.
We are pinning a cloudflared version to a wrangler version? Do we instead want to keep the latest version of cloudflared?
| errorMessage += ` - You're missing required system libraries\n`; | ||
|
|
||
| if (process.platform === "linux") { | ||
| errorMessage += `\nOn Linux, make sure you have the required dependencies:\n`; |
There was a problem hiding this comment.
What is the error for? When does this happen?
| ` - The version ${CLOUDFLARED_VERSION} doesn't exist\n` + | ||
| ` - GitHub is temporarily unavailable\n` + | ||
| ` - You're being rate limited\n\n` + | ||
| `You can manually download cloudflared from:\n` + |
There was a problem hiding this comment.
Let's point users to normal client download mechanisms instead of github if we can help it.
| `Spawning cloudflared: ${binPath} ${redactCloudflaredArgsForLogging(args).join(" ")}` | ||
| ); | ||
|
|
||
| const cloudflared = spawn(binPath, args, { |
There was a problem hiding this comment.
We have the capability to output cloudflared logs as json (--output json), do we want to leverage that here at all, or just dump the standard log lines?
| } | ||
|
|
||
| const knownPlatforms: Record<string, BinarySpec> = { | ||
| "darwin arm64 LE": { |
Manage Cloudflare Tunnels COMMANDS wrangler tunnel create <name> Create a new Cloudflare Tunnel wrangler tunnel delete <tunnel> Delete a Cloudflare Tunnel wrangler tunnel info <tunnel> Display details about a Cloudflare Tunnel wrangler tunnel list List all Cloudflare Tunnels in your account wrangler tunnel update <tunnel> Update a Cloudflare Tunnel wrangler tunnel run [tunnel] Run a Cloudflare Tunnel using cloudflared wrangler tunnel quick-start <url> Start a free, temporary tunnel without an account (https://try.cloudflare.com) wrangler tunnel route Configure routing for a Cloudflare Tunnel (DNS hostnames or private IP networks) wrangler tunnel service Manage cloudflared as a system service wrangler tunnel cleanup <tunnels..> Remove stale tunnel connections wrangler tunnel token <tunnel> Fetch the credentials token for an existing tunnel (by name or UUID) that allows to run it GLOBAL FLAGS -c, --config Path to Wrangler configuration file [string] --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] -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] commands for managing Cloudflare Tunnels Adds tunnel management commands that align with the cloudflared CLI: - CRUD: create, list, info, update, delete - Runtime: run (with token/token-file support), quick-start (Try Cloudflare) - Routing: route dns, route ip (add/list/delete/get) - Operations: cleanup, token (with --cred-file), service install/uninstall Includes automatic cloudflared binary download and caching in ~/.wrangler/cloudflared/ with SHA256 verification, platform detection, and WRANGLER_CLOUDFLARED_PATH override support.
Remove commands not needed for remotely managed tunnels: route (dns/ip), service, cleanup, token, update, and credentials. Enforce token-only auth with config_src:'cloudflare' on create, strip deprecated tunnel types and connections field from the API client. Rewrite cloudflared binary management to use Cloudflare's update worker (update.argotunnel.com) instead of GitHub API, with a GitHub release URL fallback for platforms the update worker doesn't cover (e.g. darwin/arm64). Prompt users before downloading cloudflared, warn when PATH-installed binary is outdated, and pass tunnel tokens via TUNNEL_TOKEN env var instead of --token-file for broader compatibility and security. Mark all tunnel commands as experimental and add subway emoji to the namespace description.
0007853 to
0288b26
Compare
create-cloudflare
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
- Remove expect from vitest imports in all tunnel test files - Add expect parameter to all test callbacks per workers-sdk/no-vitest-import-expect rule - Fix type annotations to avoid explicit any usage - Use type-only import for CloudflaredModule to avoid forbidden import() type annotation - Remove duplicate test in tunnel-resolve.test.ts
- Fix handler access by casting tunnelRunCommand to any - Add config_src property to mockTunnelCreate return type - Remove duplicate CloudflaredModule import
Manage Cloudflare Tunnels
COMMANDS
wrangler tunnel create Create a new Cloudflare Tunnel
wrangler tunnel delete Delete a Cloudflare Tunnel
wrangler tunnel info Display details about a Cloudflare Tunnel
wrangler tunnel list List all Cloudflare Tunnels in your account
wrangler tunnel update Update a Cloudflare Tunnel
wrangler tunnel run [tunnel] Run a Cloudflare Tunnel using cloudflared
wrangler tunnel quick-start Start a free, temporary tunnel without an account (https://try.cloudflare.com)
wrangler tunnel route Configure routing for a Cloudflare Tunnel (DNS hostnames or private IP networks)
wrangler tunnel service Manage cloudflared as a system service
wrangler tunnel cleanup <tunnels..> Remove stale tunnel connections
wrangler tunnel token Fetch the credentials token for an existing tunnel (by name or UUID) that allows to run it
GLOBAL FLAGS
-c, --config Path to Wrangler configuration file [string]
--cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string]
-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] commands for managing Cloudflare Tunnels
Adds tunnel management commands that align with the cloudflared CLI:
Includes automatic cloudflared binary download and caching in
~/.wrangler/cloudflared/ with SHA256 verification, platform detection,
and WRANGLER_CLOUDFLARED_PATH override support.
Fixes #[insert GH or internal issue link(s)].
Describe your change...
A picture of a cute animal (not mandatory, but encouraged)