Archived
1
0

Add --disable-proxy option (#6349)

This commit is contained in:
Ryan Brainard 2023-07-21 19:23:21 -04:00 committed by GitHub
parent daac46b3cf
commit 74da5167a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 2 deletions

View File

@ -34,6 +34,7 @@
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server) - [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 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 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) - [How do I disable file download?](#how-do-i-disable-file-download)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -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 you can set the environment variable `CS_DISABLE_GETTING_STARTED_OVERRIDE=1` or
`CS_DISABLE_GETTING_STARTED_OVERRIDE=true`. `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? ## How do I disable file download?
You can pass the flag `--disable-file-downloads` to `code-server` You can pass the flag `--disable-file-downloads` to `code-server`

View File

@ -51,6 +51,7 @@ export interface UserProvidedCodeArgs {
"disable-file-downloads"?: boolean "disable-file-downloads"?: boolean
"disable-workspace-trust"?: boolean "disable-workspace-trust"?: boolean
"disable-getting-started-override"?: boolean "disable-getting-started-override"?: boolean
"disable-proxy"?: boolean
"session-socket"?: string "session-socket"?: string
} }
@ -178,6 +179,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
type: "boolean", type: "boolean",
description: "Disable the coder/coder override in the Help: Getting Started page.", 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 // --enable can be used to enable experimental features. These features
// provide no guarantees. // provide no guarantees.
enable: { type: "string[]" }, enable: { type: "string[]" },
@ -564,6 +569,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["disable-getting-started-override"] = true 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 const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
if (process.env.HASHED_PASSWORD) { if (process.env.HASHED_PASSWORD) {
args["hashed-password"] = process.env.HASHED_PASSWORD args["hashed-password"] = process.env.HASHED_PASSWORD

View File

@ -75,6 +75,25 @@ export const replaceTemplates = <T extends object>(
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions)) .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. * Throw an error if not authorized. Call `next` if provided.
*/ */

View File

@ -1,6 +1,6 @@
import { Request, Router } from "express" import { Request, Router } from "express"
import { HttpCode, HttpError } from "../../common/http" 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 { proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
@ -59,6 +59,8 @@ router.all("*", async (req, res, next) => {
return next() return next()
} }
ensureProxyEnabled(req)
// Must be authenticated to use the proxy. // Must be authenticated to use the proxy.
const isAuthenticated = await authenticated(req) const isAuthenticated = await authenticated(req)
if (!isAuthenticated) { if (!isAuthenticated) {
@ -100,6 +102,8 @@ wsRouter.ws("*", async (req, _, next) => {
if (!port) { if (!port) {
return next() return next()
} }
ensureProxyEnabled(req)
ensureOrigin(req) ensureOrigin(req)
await ensureAuthenticated(req) await ensureAuthenticated(req)
proxy.ws(req, req.ws, req.head, { proxy.ws(req, req.ws, req.head, {

View File

@ -3,7 +3,7 @@ import * as path from "path"
import * as qs from "qs" import * as qs from "qs"
import * as pluginapi from "../../../typings/pluginapi" import * as pluginapi from "../../../typings/pluginapi"
import { HttpCode, HttpError } from "../../common/http" 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" import { proxy as _proxy } from "../proxy"
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => { const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
@ -21,6 +21,8 @@ export async function proxy(
passthroughPath?: boolean passthroughPath?: boolean
}, },
): Promise<void> { ): Promise<void> {
ensureProxyEnabled(req)
if (!(await authenticated(req))) { if (!(await authenticated(req))) {
// If visiting the root (/:port only) redirect to the login page. // If visiting the root (/:port only) redirect to the login page.
if (!req.params[0] || req.params[0] === "/") { if (!req.params[0] || req.params[0] === "/") {
@ -50,6 +52,7 @@ export async function wsProxy(
passthroughPath?: boolean passthroughPath?: boolean
}, },
): Promise<void> { ): Promise<void> {
ensureProxyEnabled(req)
ensureOrigin(req) ensureOrigin(req)
await ensureAuthenticated(req) await ensureAuthenticated(req)
_proxy.ws(req, req.ws, req.head, { _proxy.ws(req, req.ws, req.head, {

View File

@ -47,6 +47,7 @@ describe("parser", () => {
delete process.env.CS_DISABLE_FILE_DOWNLOADS delete process.env.CS_DISABLE_FILE_DOWNLOADS
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
delete process.env.VSCODE_PROXY_URI delete process.env.VSCODE_PROXY_URI
delete process.env.CS_DISABLE_PROXY
console.log = jest.fn() console.log = jest.fn()
}) })
@ -103,6 +104,8 @@ describe("parser", () => {
"--disable-getting-started-override", "--disable-getting-started-override",
"--disable-proxy",
["--session-socket", "/tmp/override-code-server-ipc-socket"], ["--session-socket", "/tmp/override-code-server-ipc-socket"],
["--host", "0.0.0.0"], ["--host", "0.0.0.0"],
@ -123,6 +126,7 @@ describe("parser", () => {
}, },
"disable-file-downloads": true, "disable-file-downloads": true,
"disable-getting-started-override": true, "disable-getting-started-override": true,
"disable-proxy": true,
enable: ["feature1", "feature2"], enable: ["feature1", "feature2"],
help: true, help: true,
host: "0.0.0.0", host: "0.0.0.0",
@ -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", () => { it("should error if password passed in", () => {
expect(() => parse(["--password", "supersecret123"])).toThrowError( expect(() => parse(["--password", "supersecret123"])).toThrowError(
"--password can only be set in the config file or passed in via $PASSWORD", "--password can only be set in the config file or passed in via $PASSWORD",

View File

@ -45,6 +45,17 @@ describe("proxy", () => {
jest.clearAllMocks() 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 () => { it("should rewrite the base path", async () => {
e.get("/wsup", (req, res) => { e.get("/wsup", (req, res) => {
res.json("asher is the best") res.json("asher is the best")