mirror of
https://github.com/coder/code-server.git
synced 2026-11-02 22:09:11 +01:00
Compare commits
8 Commits
7c0a76b096
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea7032040d | ||
|
|
68810a6bd6 | ||
|
|
ee31cdbe02 | ||
|
|
27808460b1 | ||
|
|
70fc911358 | ||
|
|
761b0d2a19 | ||
|
|
09012065c9 | ||
|
|
e999549684 |
2
.github/workflows/security.yaml
vendored
2
.github/workflows/security.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
severity: "HIGH,CRITICAL"
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
|
||||
with:
|
||||
sarif_file: "trivy-repo-results.sarif"
|
||||
|
||||
|
||||
2
.github/workflows/trivy-docker.yaml
vendored
2
.github/workflows/trivy-docker.yaml
vendored
@@ -58,6 +58,6 @@ jobs:
|
||||
severity: "HIGH,CRITICAL"
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
|
||||
with:
|
||||
sarif_file: "trivy-image-results.sarif"
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -22,6 +22,28 @@ Code v99.99.999
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [4.144.0](https://github.com/coder/code-server/releases/tag/v4.144.0) - 2026-10-29
|
||||
|
||||
Code v1.144.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Code 1.144.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- `--socket-fd` now works: the inherited socket is passed on to the process
|
||||
that listens, and the address is logged as `fd:<n>`. Previously code-server
|
||||
failed with `listen EEXIST` or `Server has no address`.
|
||||
- The cookie domain was not being set against the shared root host of proxy
|
||||
domains when proxy domains contained template variables like `{{port}}`,
|
||||
resulting in having to sign in multiple times per domain instead of only once
|
||||
as originally intended.
|
||||
- When doing the web socket origin check, if the host does not already contain a
|
||||
port, fall back to `X-Forwarded-Port`.
|
||||
- Fix default ports not matching in the web socket origin check (for example
|
||||
`https://coder.com` will now match `https://coder.com:443`).
|
||||
|
||||
## [4.143.1](https://github.com/coder/code-server/releases/tag/v4.143.1) - 2026-10-23
|
||||
|
||||
Code v1.143.1
|
||||
|
||||
@@ -101,6 +101,14 @@ function update_csp() {
|
||||
echo "Calculated hash as $hash"
|
||||
# Use octothorpe as a delimiter since the hash may contain a slash.
|
||||
sed -i.bak "s#'sha256-[^']\+'#'sha256-$hash'#" "$file"
|
||||
# For the web worker extension host iframe, there is a matching sha in the
|
||||
# web server.
|
||||
if [[ $file == */webWorkerExtensionHostIframe.html ]] ; then
|
||||
quilt add ./lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
sed -i.bak \
|
||||
"s#webWorkerExtensionHostIframeScriptSHA = 'sha256-[^']\+'#webWorkerExtensionHostIframeScriptSHA = 'sha256-$hash'#" \
|
||||
./lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
fi
|
||||
done
|
||||
|
||||
quilt refresh
|
||||
|
||||
@@ -15,9 +15,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 3.57.0
|
||||
version: 3.58.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 4.143.1
|
||||
appVersion: 4.144.0
|
||||
|
||||
@@ -4,8 +4,13 @@ metadata:
|
||||
name: {{ include "code-server.fullname" . }}
|
||||
labels:
|
||||
{{- include "code-server.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- with .Values.service.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
loadBalancerClass: {{ .Values.service.loadBalancerClass }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
|
||||
@@ -6,7 +6,7 @@ replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: codercom/code-server
|
||||
tag: '4.143.1'
|
||||
tag: '4.144.0'
|
||||
pullPolicy: Always
|
||||
|
||||
# Specifies one or more secrets to be used when pulling images from a
|
||||
@@ -48,6 +48,8 @@ priorityClassName: ""
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
loadBalancerClass: null
|
||||
annotations: {}
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
Submodule lib/vscode updated: e646868227...2c33b50b16
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1726,9 +1726,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-ftp": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
|
||||
"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-6.2.3.tgz",
|
||||
"integrity": "sha512-T7GYtQ2eKJUN5YORG7A8u2WfB9JMN0a4/wl3MI64qq2qTRfa3FXzoNq0DDojZDkDyStcL+TkfKsz4JHEZx5kGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -3245,9 +3245,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz",
|
||||
"integrity": "sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
||||
@@ -90,6 +90,9 @@
|
||||
"resolutions": {
|
||||
"@types/node": "24.x"
|
||||
},
|
||||
"overrides": {
|
||||
"basic-ftp": "^6.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"code-server": "out/node/entry.js"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/base/common/network.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/network.ts
|
||||
@@ -256,7 +256,9 @@ class RemoteAuthoritiesImpl {
|
||||
@@ -262,7 +262,9 @@ class RemoteAuthoritiesImpl {
|
||||
return URI.from({
|
||||
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
||||
authority: `${host}:${port}`,
|
||||
|
||||
@@ -2,7 +2,7 @@ Index: code-server/lib/vscode/build/gulpfile.extensions.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/build/gulpfile.extensions.ts
|
||||
+++ code-server/lib/vscode/build/gulpfile.extensions.ts
|
||||
@@ -294,6 +294,29 @@ export const compileCopilotExtensionBuil
|
||||
@@ -283,6 +283,29 @@ export const compileCopilotExtensionBuil
|
||||
task.task(compileCopilotExtensionBuildTask);
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,8 +6,8 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
- content="default-src 'none'; script-src 'sha256-7cKy9iQfWRXcgRS74x6I4q5cRDkv8nQz51jecqMmQ5w=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
+ content="default-src 'none'; script-src 'sha256-FYdHnaDkSHwSK3TmWDrs/tT+X4bK+aaoa9yy432BONk=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
- content="default-src 'none'; script-src 'sha256-Q3Tfcw0dGu/YD4dGBrRqBeOnmRMAH8HbKxArK4YSXBc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
+ content="default-src 'none'; script-src 'sha256-BmcTt6YZr75RujSQ3gF/UQL77e1RrOx62jNeikpwtxc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport"
|
||||
@@ -24,3 +24,16 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/worker/webWor
|
||||
connect-src 'self' https: wss: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*;"/>
|
||||
</head>
|
||||
<body>
|
||||
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -114,7 +114,7 @@ const APP_ROOT = dirname(FileAccess.asFi
|
||||
const STATIC_PATH = `/static`;
|
||||
const CALLBACK_PATH = `/callback`;
|
||||
const WEB_EXTENSION_PATH = `/web-extension-resource`;
|
||||
-const webWorkerExtensionHostIframeScriptSHA = 'sha256-daEgfo2VIXpx2Np71KqCCbkeQwv+68vPrx54XRcbdcs=';
|
||||
+const webWorkerExtensionHostIframeScriptSHA = 'sha256-gfjiOVjAlxqrivZAfTq2d83mOaz0fnAxbGaFpj5IjD8=';
|
||||
|
||||
/**
|
||||
* Substitutes the `{{...}}` placeholders of a workbench template. Placeholders must only ever
|
||||
|
||||
@@ -19,7 +19,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
import { renderFormattedText } from '../../../../base/browser/formattedTextRenderer.js';
|
||||
import { status } from '../../../../base/browser/ui/aria/aria.js';
|
||||
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
|
||||
@@ -55,7 +55,7 @@ import { IRecentFolder, IRecentWorkspace
|
||||
@@ -56,7 +56,7 @@ import { IRecentFolder, IRecentWorkspace
|
||||
import { OpenRecentAction } from '../../../browser/actions/windowActions.js';
|
||||
import { OpenFileFolderAction, OpenFolderAction, OpenFolderViaWorkspaceAction } from '../../../browser/actions/workspaceActions.js';
|
||||
import { EditorPane } from '../../../browser/parts/editor/editorPane.js';
|
||||
@@ -28,7 +28,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
import { IEditorOpenContext, IEditorSerializer } from '../../../common/editor.js';
|
||||
import { IWebviewElement, IWebviewService } from '../../webview/browser/webview.js';
|
||||
import './gettingStartedColors.js';
|
||||
@@ -930,6 +930,72 @@ export class GettingStartedPage extends
|
||||
@@ -944,6 +944,72 @@ export class GettingStartedPage extends
|
||||
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
|
||||
);
|
||||
|
||||
@@ -101,7 +101,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
const leftColumn = $('.categories-column.categories-column-left', {},);
|
||||
const rightColumn = $('.categories-column.categories-column-right', {},);
|
||||
|
||||
@@ -980,6 +1046,9 @@ export class GettingStartedPage extends
|
||||
@@ -1000,6 +1066,9 @@ export class GettingStartedPage extends
|
||||
recentList.setLimit(5);
|
||||
reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
@@ -64,6 +64,7 @@ import { IOpenerService } from '../../pl
|
||||
@@ -65,6 +65,7 @@ import { IOpenerService } from '../../pl
|
||||
import { mixin, safeStringify } from '../../base/common/objects.js';
|
||||
import { IndexedDB } from '../../base/browser/indexedDB.js';
|
||||
import { WebFileSystemAccess } from '../../platform/files/browser/webFileSystemAccess.js';
|
||||
@@ -177,7 +177,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
import { IProgressService } from '../../platform/progress/common/progress.js';
|
||||
import { DelayedLogChannel } from '../services/output/common/delayedLogChannel.js';
|
||||
import { dirname, joinPath } from '../../base/common/resources.js';
|
||||
@@ -140,6 +141,9 @@ export class BrowserMain extends Disposa
|
||||
@@ -141,6 +142,9 @@ export class BrowserMain extends Disposa
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
|
||||
import { TargetPlatform } from '../../extensions/common/extensions.js';
|
||||
import { ExtensionGalleryResourceType, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../extensionManagement/common/extensionGalleryManifest.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
@@ -163,9 +162,9 @@ export abstract class AbstractExtensionR
|
||||
@@ -167,9 +166,9 @@ export abstract class AbstractExtensionR
|
||||
}
|
||||
|
||||
protected _isWebExtensionResourceEndPoint(uri: URI): boolean {
|
||||
|
||||
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extens
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
|
||||
@@ -321,6 +321,10 @@ function extensionDescriptionArrayToMap(
|
||||
@@ -324,6 +324,10 @@ function extensionDescriptionArrayToMap(
|
||||
}
|
||||
|
||||
export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
|
||||
|
||||
@@ -179,7 +179,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
@@ -33,6 +33,7 @@ import { ISignService } from '../../plat
|
||||
@@ -34,6 +34,7 @@ import { ISignService } from '../../plat
|
||||
import { SignService } from '../../platform/sign/browser/signService.js';
|
||||
import { IWorkbenchConstructionOptions, IWorkbench, IWorkspace, ITunnel } from './web.api.js';
|
||||
import { BrowserStorageService } from '../services/storage/browser/storageService.js';
|
||||
@@ -187,7 +187,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
import { IStorageService } from '../../platform/storage/common/storage.js';
|
||||
import { toLocalISOString } from '../../base/common/date.js';
|
||||
import { isWorkspaceToOpen, isFolderToOpen } from '../../platform/window/common/window.js';
|
||||
@@ -482,7 +483,7 @@ export class BrowserMain extends Disposa
|
||||
@@ -488,7 +489,7 @@ export class BrowserMain extends Disposa
|
||||
serviceCollection.set(IWorkbenchConfigurationService, service);
|
||||
return service;
|
||||
}),
|
||||
@@ -196,7 +196,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
serviceCollection.set(IStorageService, service);
|
||||
return service;
|
||||
})
|
||||
@@ -604,7 +605,21 @@ export class BrowserMain extends Disposa
|
||||
@@ -610,7 +611,21 @@ export class BrowserMain extends Disposa
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -111,9 +111,14 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
|
||||
*
|
||||
* The address might be a URL or it might be a pipe or socket path.
|
||||
*/
|
||||
export const ensureAddress = (server: http.Server, protocol: string): URL | string => {
|
||||
export const ensureAddress = (server: http.Server, protocol: string, socketFd?: number): URL | string => {
|
||||
const addr = server.address()
|
||||
|
||||
// Node reports no address for a Unix socket adopted by fd.
|
||||
if (!addr && typeof socketFd === "number") {
|
||||
return `fd:${socketFd}`
|
||||
}
|
||||
|
||||
if (!addr) {
|
||||
throw new Error("Server has no address")
|
||||
}
|
||||
|
||||
@@ -235,7 +235,16 @@ export const getCookieDomain = (host: string, proxyDomains: string[]): string |
|
||||
return undefined
|
||||
}
|
||||
|
||||
proxyDomains.forEach((domain) => {
|
||||
proxyDomains.forEach((proxyDomain) => {
|
||||
// Proxy domains are patterns like `{{port}}.domain.tld` or
|
||||
// `prefix-{{port}}-suffix.domain.tld` so use the part after the label that
|
||||
// contains the port.
|
||||
const portIndex = proxyDomain.indexOf("{{port}}")
|
||||
const domain = portIndex === -1 ? proxyDomain : proxyDomain.substring(proxyDomain.indexOf(".", portIndex) + 1)
|
||||
// Skip patterns we cannot turn into a domain, like `{{port}}.{{host}}`.
|
||||
if (!domain || domain.includes("{{")) {
|
||||
return
|
||||
}
|
||||
if (host.endsWith(domain) && domain.length < host.length) {
|
||||
host = domain
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export const runCodeServer = async (
|
||||
|
||||
const app = await createApp(args)
|
||||
const protocol = args.cert ? "https" : "http"
|
||||
const serverAddress = ensureAddress(app.server, protocol)
|
||||
const serverAddress = ensureAddress(app.server, protocol, args["socket-fd"])
|
||||
const { disposeRoutes, heart } = await register(app, args)
|
||||
|
||||
logger.info(`Using config file ${args.config}`)
|
||||
|
||||
@@ -8,6 +8,9 @@ import { paths } from "./util"
|
||||
|
||||
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
|
||||
|
||||
// Fd 3 in the child is the IPC channel, so the inherited socket goes to fd 4.
|
||||
const CHILD_SOCKET_FD = 4
|
||||
|
||||
/**
|
||||
* Listen to a single message from a process. Reject if the process errors,
|
||||
* exits, or times out.
|
||||
@@ -334,13 +337,14 @@ export class ParentProcess extends Process {
|
||||
}
|
||||
|
||||
private spawn(): cp.ChildProcess {
|
||||
const socketFd = this.args?.["socket-fd"]
|
||||
return cp.fork(path.join(__dirname, "entry"), {
|
||||
env: {
|
||||
...process.env,
|
||||
CODE_SERVER_PARENT_PID: process.pid.toString(),
|
||||
NODE_EXEC_PATH: process.execPath,
|
||||
},
|
||||
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
||||
stdio: typeof socketFd === "number" ? ["pipe", "pipe", "pipe", "ipc", socketFd] : ["pipe", "pipe", "pipe", "ipc"],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,7 +363,8 @@ export class ParentProcess extends Process {
|
||||
this.logger,
|
||||
)
|
||||
this.logger.debug("got message", field("message", message))
|
||||
this.send(child, { type: "handshake", args: this.args })
|
||||
const args = typeof this.args["socket-fd"] === "number" ? { ...this.args, "socket-fd": CHILD_SOCKET_FD } : this.args
|
||||
this.send(child, { type: "handshake", args })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
80
test/integration/socketFd.test.ts
Normal file
80
test/integration/socketFd.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ChildProcess, spawn } from "child_process"
|
||||
import { promises as fs } from "fs"
|
||||
import * as http from "http"
|
||||
import * as net from "net"
|
||||
import * as path from "path"
|
||||
import { clean, tmpdir } from "../utils/helpers"
|
||||
|
||||
describe("--socket-fd", () => {
|
||||
const testName = "socketFd"
|
||||
const codeServerPath = process.env.CODE_SERVER_PATH || path.resolve("../../release-standalone/bin/code-server")
|
||||
let tempDir: string
|
||||
let socketDir: string | undefined
|
||||
let child: ChildProcess | undefined
|
||||
|
||||
beforeEach(async () => {
|
||||
await clean(testName)
|
||||
tempDir = await tmpdir(testName)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (child?.pid && child.exitCode === null) {
|
||||
// Kill the process group so that the wrapper's inner process also stops.
|
||||
process.kill(-child.pid, "SIGKILL")
|
||||
}
|
||||
if (socketDir) {
|
||||
await fs.rm(socketDir, { force: true, recursive: true })
|
||||
socketDir = undefined
|
||||
}
|
||||
})
|
||||
|
||||
// Give the listener to code-server as fd 3, as systemd socket activation does.
|
||||
const getHealthzThroughFd = async (server: net.Server, target: http.RequestOptions): Promise<number> => {
|
||||
const fd: number = (server as any)._handle.fd
|
||||
let output = ""
|
||||
child = spawn(
|
||||
codeServerPath,
|
||||
[
|
||||
"--socket-fd=3",
|
||||
"--auth=none",
|
||||
`--user-data-dir=${path.join(tempDir, "data")}`,
|
||||
`--config=${path.join(tempDir, "config.yaml")}`,
|
||||
],
|
||||
{ detached: true, stdio: ["ignore", "pipe", "pipe", fd] },
|
||||
)
|
||||
child.stdout?.on("data", (chunk) => (output += chunk))
|
||||
child.stderr?.on("data", (chunk) => (output += chunk))
|
||||
await new Promise((resolve) => server.close(resolve))
|
||||
|
||||
const exited = new Promise<never>((_, reject) => {
|
||||
child?.once("exit", (code) => reject(new Error(`code-server exited with code ${code}:\n${output}`)))
|
||||
})
|
||||
const status = new Promise<number>((resolve, reject) => {
|
||||
http
|
||||
.get({ ...target, path: "/healthz" }, (res) => {
|
||||
res.resume()
|
||||
resolve(res.statusCode ?? 0)
|
||||
})
|
||||
.once("error", (error) => reject(new Error(`${error.message}:\n${output}`)))
|
||||
})
|
||||
return Promise.race([status, exited])
|
||||
}
|
||||
|
||||
it("should serve requests on a Unix socket passed as a file descriptor", async () => {
|
||||
// macOS limits socket paths to 104 bytes, and os.tmpdir() can be too long.
|
||||
socketDir = await fs.mkdtemp("/tmp/cs-fd-")
|
||||
const server = net.createServer()
|
||||
await new Promise<void>((resolve) => server.listen(path.join(socketDir!, "listen.sock"), resolve))
|
||||
// server.close() unlinks the socket path, so connect through a hard link.
|
||||
const socketPath = path.join(socketDir, "connect.sock")
|
||||
await fs.link(path.join(socketDir, "listen.sock"), socketPath)
|
||||
expect(await getHealthzThroughFd(server, { socketPath })).toBe(200)
|
||||
}, 60000)
|
||||
|
||||
it("should serve requests on a TCP socket passed as a file descriptor", async () => {
|
||||
const server = net.createServer()
|
||||
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve))
|
||||
const { port } = server.address() as net.AddressInfo
|
||||
expect(await getHealthzThroughFd(server, { host: "127.0.0.1", port })).toBe(200)
|
||||
}, 60000)
|
||||
})
|
||||
@@ -152,6 +152,9 @@ describe("ensureAddress", () => {
|
||||
it("should throw and error if no address", () => {
|
||||
expect(() => ensureAddress(mockServer, "http")).toThrow("Server has no address")
|
||||
})
|
||||
it("should describe a --socket-fd listener by its fd when node reports no address", () => {
|
||||
expect(ensureAddress(mockServer, "http", 4)).toBe("fd:4")
|
||||
})
|
||||
it("should return the address if it's a string", async () => {
|
||||
mockServer.address = () => "/path/to/unix.sock"
|
||||
const address = ensureAddress(mockServer, "http")
|
||||
|
||||
@@ -230,6 +230,35 @@ describe("http", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("getCookieDomain", () => {
|
||||
it("should use the proxy domain for the cookie", () => {
|
||||
const proxyDomains = ["{{port}}.example.com", "code-{{port}}.test.com"]
|
||||
expect(http.getCookieDomain("example.com", proxyDomains)).toBe("example.com")
|
||||
expect(http.getCookieDomain("8080.example.com", proxyDomains)).toBe("example.com")
|
||||
expect(http.getCookieDomain("8080.example.com:443", proxyDomains)).toBe("example.com")
|
||||
expect(http.getCookieDomain("code-8080.test.com", proxyDomains)).toBe("test.com")
|
||||
})
|
||||
|
||||
it("should use the highest level proxy domain", () => {
|
||||
const proxyDomains = ["{{port}}.test.example.com", "{{port}}.example.com"]
|
||||
expect(http.getCookieDomain("test.example.com", proxyDomains)).toBe("example.com")
|
||||
expect(http.getCookieDomain("8080.test.example.com", proxyDomains)).toBe("example.com")
|
||||
})
|
||||
|
||||
it("should use the host when no proxy domain matches", () => {
|
||||
expect(http.getCookieDomain("code.other.com", ["{{port}}.example.com"])).toBe("code.other.com")
|
||||
expect(http.getCookieDomain("8080.example.com", ["{{port}}.{{host}}"])).toBe("8080.example.com")
|
||||
expect(http.getCookieDomain("8080.example.com", [])).toBe("8080.example.com")
|
||||
})
|
||||
|
||||
it("should not set a domain for localhost or IP addresses", () => {
|
||||
const proxyDomains = ["{{port}}.example.com"]
|
||||
expect(http.getCookieDomain("localhost:8080", proxyDomains)).toBe(undefined)
|
||||
expect(http.getCookieDomain("127.0.0.1:8080", proxyDomains)).toBe(undefined)
|
||||
expect(http.getCookieDomain("8080.dev.localhost", proxyDomains)).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe("constructRedirectPath", () => {
|
||||
it("should preserve slashes in queryString so they are human-readable", () => {
|
||||
const mockReq = getMockReq({
|
||||
|
||||
Reference in New Issue
Block a user