Skip to content

Commit

Permalink
Add --disable-proxy option (#6349)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanbrainard authored Jul 21, 2023
1 parent daac46b commit 74da516
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 2 deletions.
14 changes: 14 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server)
- [How do I change the port?](#how-do-i-change-the-port)
- [How do I hide the coder/coder promotion in Help: Getting Started?](#how-do-i-hide-the-codercoder-promotion-in-help-getting-started)
- [How do I disable the proxy?](#how-do-i-disable-the-proxy)
- [How do I disable file download?](#how-do-i-disable-file-download)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -453,6 +454,19 @@ You can pass the flag `--disable-getting-started-override` to `code-server` or
you can set the environment variable `CS_DISABLE_GETTING_STARTED_OVERRIDE=1` or
`CS_DISABLE_GETTING_STARTED_OVERRIDE=true`.

## How do I disable the proxy?

You can pass the flag `--disable-proxy` to `code-server` or
you can set the environment variable `CS_DISABLE_PROXY=1` or
`CS_DISABLE_PROXY=true`.

Note, this option currently only disables the proxy routes to forwarded ports, including
the domain and path proxy routes over HTTP and WebSocket; however, it does not
disable the automatic port forwarding in the VS Code workbench itself. In other words,
user will still see the Ports tab and notifications, but will not be able to actually
use access the ports. It is recommended to set `remote.autoForwardPorts` to `false`
when using the option.

## How do I disable file download?

You can pass the flag `--disable-file-downloads` to `code-server`
9 changes: 9 additions & 0 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface UserProvidedCodeArgs {
"disable-file-downloads"?: boolean
"disable-workspace-trust"?: boolean
"disable-getting-started-override"?: boolean
"disable-proxy"?: boolean
"session-socket"?: string
}

Expand Down Expand Up @@ -178,6 +179,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
type: "boolean",
description: "Disable the coder/coder override in the Help: Getting Started page.",
},
"disable-proxy": {
type: "boolean",
description: "Disable domain and path proxy routes.",
},
// --enable can be used to enable experimental features. These features
// provide no guarantees.
enable: { type: "string[]" },
Expand Down Expand Up @@ -564,6 +569,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["disable-getting-started-override"] = true
}

if (process.env.CS_DISABLE_PROXY?.match(/^(1|true)$/)) {
args["disable-proxy"] = true
}

const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
if (process.env.HASHED_PASSWORD) {
args["hashed-password"] = process.env.HASHED_PASSWORD
Expand Down
19 changes: 19 additions & 0 deletions src/node/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ export const replaceTemplates = <T extends object>(
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions))
}

/**
* Throw an error if proxy is not enabled. Call `next` if provided.
*/
export const ensureProxyEnabled = (req: express.Request, _?: express.Response, next?: express.NextFunction): void => {
if (!proxyEnabled(req)) {
throw new HttpError("Forbidden", HttpCode.Forbidden)
}
if (next) {
next()
}
}

/**
* Return true if proxy is enabled.
*/
export const proxyEnabled = (req: express.Request): boolean => {
return !req.args["disable-proxy"]
}

/**
* Throw an error if not authorized. Call `next` if provided.
*/
Expand Down
6 changes: 5 additions & 1 deletion src/node/routes/domainProxy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Router } from "express"
import { HttpCode, HttpError } from "../../common/http"
import { getHost, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter"

Expand Down Expand Up @@ -59,6 +59,8 @@ router.all("*", async (req, res, next) => {
return next()
}

ensureProxyEnabled(req)

// Must be authenticated to use the proxy.
const isAuthenticated = await authenticated(req)
if (!isAuthenticated) {
Expand Down Expand Up @@ -100,6 +102,8 @@ wsRouter.ws("*", async (req, _, next) => {
if (!port) {
return next()
}

ensureProxyEnabled(req)
ensureOrigin(req)
await ensureAuthenticated(req)
proxy.ws(req, req.ws, req.head, {
Expand Down
5 changes: 4 additions & 1 deletion src/node/routes/pathProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from "path"
import * as qs from "qs"
import * as pluginapi from "../../../typings/pluginapi"
import { HttpCode, HttpError } from "../../common/http"
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { proxy as _proxy } from "../proxy"

const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
Expand All @@ -21,6 +21,8 @@ export async function proxy(
passthroughPath?: boolean
},
): Promise<void> {
ensureProxyEnabled(req)

if (!(await authenticated(req))) {
// If visiting the root (/:port only) redirect to the login page.
if (!req.params[0] || req.params[0] === "/") {
Expand Down Expand Up @@ -50,6 +52,7 @@ export async function wsProxy(
passthroughPath?: boolean
},
): Promise<void> {
ensureProxyEnabled(req)
ensureOrigin(req)
await ensureAuthenticated(req)
_proxy.ws(req, req.ws, req.head, {
Expand Down
28 changes: 28 additions & 0 deletions test/unit/node/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe("parser", () => {
delete process.env.CS_DISABLE_FILE_DOWNLOADS
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
delete process.env.VSCODE_PROXY_URI
delete process.env.CS_DISABLE_PROXY
console.log = jest.fn()
})

Expand Down Expand Up @@ -103,6 +104,8 @@ describe("parser", () => {

"--disable-getting-started-override",

"--disable-proxy",

["--session-socket", "/tmp/override-code-server-ipc-socket"],

["--host", "0.0.0.0"],
Expand All @@ -123,6 +126,7 @@ describe("parser", () => {
},
"disable-file-downloads": true,
"disable-getting-started-override": true,
"disable-proxy": true,
enable: ["feature1", "feature2"],
help: true,
host: "0.0.0.0",
Expand Down Expand Up @@ -392,6 +396,30 @@ describe("parser", () => {
})
})

it("should use env var CS_DISABLE_PROXY", async () => {
process.env.CS_DISABLE_PROXY = "1"
const args = parse([])
expect(args).toEqual({})

const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults,
"disable-proxy": true,
})
})

it("should use env var CS_DISABLE_PROXY set to true", async () => {
process.env.CS_DISABLE_PROXY = "true"
const args = parse([])
expect(args).toEqual({})

const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults,
"disable-proxy": true,
})
})

it("should error if password passed in", () => {
expect(() => parse(["--password", "supersecret123"])).toThrowError(
"--password can only be set in the config file or passed in via $PASSWORD",
Expand Down
11 changes: 11 additions & 0 deletions test/unit/node/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ describe("proxy", () => {
jest.clearAllMocks()
})

it("should return 403 Forbidden if proxy is disabled", async () => {
e.get("/wsup", (req, res) => {
res.json("you cannot see this")
})
codeServer = await integration.setup(["--auth=none", "--disable-proxy"], "")
const resp = await codeServer.fetch(proxyPath)
expect(resp.status).toBe(403)
const json = await resp.json()
expect(json).toEqual({ error: "Forbidden" })
})

it("should rewrite the base path", async () => {
e.get("/wsup", (req, res) => {
res.json("asher is the best")
Expand Down

0 comments on commit 74da516

Please sign in to comment.