-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support HTTP BasicAuth for authentication if $AUTH_USER is set #7143
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
** | ||
!release-packages | ||
!ci | ||
!release-standalone | ||
!startup.sh | ||
!install_locales.sh |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
ARG SERVER_VERSION=20241223_895aa2908981 | ||
FROM registry.goldenhelix.com/gh/server:${SERVER_VERSION} | ||
|
||
USER root | ||
|
||
# Install various locales to support users from different regions | ||
COPY ./install_locales.sh /tmp/install_locales.sh | ||
RUN bash /tmp/install_locales.sh | ||
|
||
# Copy the release-standalone directory | ||
COPY ./release-standalone /opt/code-server | ||
COPY ./startup.sh /opt/code-server/startup.sh | ||
|
||
# Remove the existing node binary and create symlink to the system node | ||
RUN rm -f /opt/code-server/lib/node && \ | ||
ln -s /opt/node/bin/node /opt/code-server/lib/node && \ | ||
chmod +x /opt/code-server/startup.sh | ||
|
||
# Set the environment variables | ||
ARG LANG='en_US.UTF-8' | ||
ARG LANGUAGE='en_US:en' | ||
ARG LC_ALL='en_US.UTF-8' | ||
ARG START_XFCE4=1 | ||
ARG TZ='Etc/UTC' | ||
ENV HOME=/home/ghuser \ | ||
SHELL=/bin/bash \ | ||
USERNAME=ghuser \ | ||
LANG=$LANG \ | ||
LANGUAGE=$LANGUAGE \ | ||
LC_ALL=$LC_ALL \ | ||
TZ=$TZ \ | ||
AUTH_USER=ghuser \ | ||
CODE_SERVER_SESSION_SOCKET=/home/ghuser/.config/code-server/code-server-ipc.sock \ | ||
PASSWORD=ghuserpassword \ | ||
PORT=8080 | ||
|
||
### Ports and user | ||
EXPOSE $PORT | ||
WORKDIR $HOME | ||
USER 1000 | ||
|
||
RUN mkdir -p $HOME/.config/code-server && \ | ||
echo "bind-addr: 0.0.0.0:8080" > $HOME/.config/code-server/config.yaml && \ | ||
echo "auth: password" >> $HOME/.config/code-server/config.yaml && \ | ||
echo "password: \${PASSWORD}" >> $HOME/.config/code-server/config.yaml && \ | ||
echo "cert: true" >> $HOME/.config/code-server/config.yaml | ||
|
||
|
||
RUN mkdir -p $HOME/Workspace/Documents | ||
|
||
ENTRYPOINT ["/opt/code-server/startup.sh"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#!/bin/bash | ||
|
||
# Build a Golden Helix docker image for code-server with changes to support App Streaming on VSWarehouse | ||
|
||
# Follow the directions under [CONTRIBUTING.md](docs/CONTRIBUTING.md) to build the image | ||
|
||
# git submodule update --init | ||
# quilt push -a | ||
# npm install | ||
# npm run build | ||
# VERSION=4.96.2 npm run build:vscode | ||
# npm run release | ||
|
||
# VERSION=4.96.2 npm run package | ||
# cd release | ||
# npm install --omit=dev | ||
# cd .. | ||
# npm run release:standalone | ||
|
||
export VERSION=4.96.2 | ||
|
||
export SERVER_VERSION=20241223_895aa2908981 | ||
|
||
# Ensure we're in the correct directory | ||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" | ||
cd "$SCRIPT_DIR" | ||
|
||
# Temporarily move the bundled node out of the way | ||
if [ -f "release-standalone/lib/node" ]; then | ||
mv release-standalone/lib/node ./node | ||
fi | ||
|
||
echo "PWD: $PWD" | ||
|
||
docker build --no-cache \ | ||
--build-arg SERVER_VERSION=${SERVER_VERSION} \ | ||
-t registry.goldenhelix.com/gh/code-server:${VERSION} . | ||
|
||
# Move the bundled node back | ||
if [ -f "./node" ]; then | ||
mv ./node release-standalone/lib/node | ||
fi | ||
|
||
# Run like | ||
# docker run -it -p 8081:8080 -e PASSWORD=your_secure_password123 -e PORT=8080 registry.goldenhelix.com/gh/code-server:20241223_895aa2908981 /home/ghuser/Workspace/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
github.copilot | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we omit this file? Seems unrelated to code-server itself. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, unintended |
||
github.copilot-chat | ||
hashicorp.terraform | ||
ms-python.debugpy | ||
ms-python.python | ||
ms-python.vscode-pylance | ||
ms-toolsai.jupyter | ||
ms-toolsai.jupyter-keymap | ||
ms-toolsai.jupyter-renderers | ||
ms-toolsai.vscode-jupyter-cell-tags | ||
ms-toolsai.vscode-jupyter-slideshow | ||
ms-vscode-remote.remote-containers | ||
stkb.rewrap | ||
streetsidesoftware.code-spell-checker | ||
redhat.vscode-yaml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#!/bin/bash | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also seems unrelated to this particular PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, unintended |
||
# *GH* selected major languages for space and time | ||
LOCALES="de_DE fr_FR it_IT es_ES en_GB nl_NL sv_SE pl_PL fr_BE nl_BE de_AT da_DK fi_FI el_GR en_IE pt_PT cs_CZ hu_HU ro_RO bg_BG ja_JP zh_CN ko_KR hi_IN ru_RU" | ||
|
||
echo "Installing languages" | ||
apt-get update | ||
apt-get install -y \ | ||
locales locales-all | ||
for LOCALE in ${LOCALES}; do | ||
echo "Generating Locale for ${LOCALE}" | ||
localedef -i ${LOCALE} -f UTF-8 ${LOCALE}.UTF-8 | ||
done |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#!/bin/bash | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, unintended |
||
|
||
EXTENSIONS_FILE="./extensions.txt" | ||
CODE_SERVER="./release-standalone/bin/code-server" | ||
EXTENSIONS_DIR="~/.local/share/code-server/extensions" | ||
TARGET_DIR="./release-standalone/lib/vscode/extensions" | ||
|
||
# Check if code-server exists | ||
if [ ! -f "$CODE_SERVER" ]; then | ||
echo "Error: code-server not found at $CODE_SERVER" | ||
exit 1 | ||
fi | ||
|
||
# Create target directory if it doesn't exist | ||
mkdir -p "$TARGET_DIR" | ||
|
||
# Read extensions file line by line | ||
while IFS= read -r extension || [ -n "$extension" ]; do | ||
# Skip empty lines and comments | ||
if [[ -z "$extension" || "$extension" =~ ^# ]]; then | ||
continue | ||
fi | ||
|
||
echo "Installing extension: $extension" | ||
|
||
# Install the extension | ||
$CODE_SERVER --install-extension "$extension" | ||
|
||
if [ $? -ne 0 ]; then | ||
echo "Warning: Failed to install $extension" | ||
continue | ||
fi | ||
|
||
echo "Copying extension files to standalone directory" | ||
# Use cp -R with expanded source path | ||
if ! eval "cp -R $EXTENSIONS_DIR/${extension}* $TARGET_DIR/"; then | ||
echo "Warning: Failed to copy $extension to standalone directory" | ||
fi | ||
|
||
echo "Completed processing $extension" | ||
echo "----------------------------------------" | ||
done < "$EXTENSIONS_FILE" | ||
|
||
echo "All extensions processed" |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,6 +86,7 @@ | |
"safe-buffer": "^5.2.1", | ||
"safe-compare": "^1.1.4", | ||
"semver": "^7.5.4", | ||
"tslib": "^2.8.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was this added for? We have no direct dependency on tslib, do we? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will remove it, but I had a packaging issue without it. |
||
"ws": "^8.14.2", | ||
"xdg-basedir": "^4.0.0" | ||
}, | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -12,6 +12,7 @@ export enum Feature { | |||||
|
||||||
export enum AuthType { | ||||||
Password = "password", | ||||||
HttpBasic = "http-basic", | ||||||
None = "none", | ||||||
} | ||||||
|
||||||
|
@@ -65,6 +66,7 @@ export interface UserProvidedCodeArgs { | |||||
export interface UserProvidedArgs extends UserProvidedCodeArgs { | ||||||
config?: string | ||||||
auth?: AuthType | ||||||
"auth-user"?: string | ||||||
password?: string | ||||||
"hashed-password"?: string | ||||||
cert?: OptionalString | ||||||
|
@@ -137,6 +139,10 @@ export type Options<T> = { | |||||
|
||||||
export const options: Options<Required<UserProvidedArgs>> = { | ||||||
auth: { type: AuthType, description: "The type of authentication to use." }, | ||||||
"auth-user": { | ||||||
type: "string", | ||||||
description: "The username for http-basic authentication." | ||||||
}, | ||||||
password: { | ||||||
type: "string", | ||||||
description: "The password for password authentication (can only be passed in via $PASSWORD or the config file).", | ||||||
|
@@ -569,6 +575,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config | |||||
if (process.env.PASSWORD) { | ||||||
args.password = process.env.PASSWORD | ||||||
} | ||||||
if (process.env.AUTH_USER) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be OK to prepend this with Also, I think typically we prefer in order: command-line, env var, then config file. So this should be:
Suggested change
(We are not doing this for the password because we disallow setting the password on the command line.) |
||||||
args["auth"] = AuthType.HttpBasic | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know we are generally not planning on adding any other auth methods, but I do worry about automatically setting It may then also make sense to error if |
||||||
args["auth-user"] = process.env.AUTH_USER | ||||||
} | ||||||
|
||||||
if (process.env.CS_DISABLE_FILE_DOWNLOADS?.match(/^(1|true)$/)) { | ||||||
args["disable-file-downloads"] = true | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,6 +111,25 @@ export const ensureAuthenticated = async ( | |
} | ||
} | ||
|
||
/** | ||
* Validate basic auth credentials. | ||
*/ | ||
const validateBasicAuth = (authHeader: string | undefined, authUser: string | undefined, authPassword: string | undefined): boolean => { | ||
if (!authHeader?.startsWith('Basic ')) { | ||
return false; | ||
} | ||
|
||
try { | ||
const base64Credentials = authHeader.split(' ')[1]; | ||
const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8'); | ||
const [username, password] = credentials.split(':'); | ||
return username === authUser && password === authPassword; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I'll switch the password comparison to safeCompare |
||
} catch (error) { | ||
logger.error('Error validating basic auth:' + error); | ||
return false; | ||
} | ||
}; | ||
|
||
/** | ||
* Return true if authenticated via cookies. | ||
*/ | ||
|
@@ -132,6 +151,9 @@ export const authenticated = async (req: express.Request): Promise<boolean> => { | |
|
||
return await isCookieValid(isCookieValidArgs) | ||
} | ||
case AuthType.HttpBasic: { | ||
return validateBasicAuth(req.headers.authorization, req.args["auth-user"], req.args.password); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if someone sets HASHED_PASSWORD but not PASSWORD and then sets There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we can check HASHED_PASSWORD in this context. The password has to come through standard basic auth format. I think the use case for basic auth is to specify a user and password. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hashing is just an internal code-server thing, if you want to give code-server the password but not give it the plain text password. The hash is not used by the browser or anything like that, if that makes sense. Whether we are comparing that hashed password with a password submitted from a login form or a password from the basic auth flow, it is all the same to code-server. Basically, the only difference is |
||
} | ||
default: { | ||
throw new Error(`Unsupported auth type ${req.args.auth}`) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,6 +142,10 @@ export const runCodeServer = async ( | |
} else { | ||
logger.info(` - Using password from ${args.config}`) | ||
} | ||
} else if (args.auth === AuthType.HttpBasic) { | ||
logger.info(" - HTTP basic authentication is enabled") | ||
logger.info(" - Using user from $AUTH_USER") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be set from |
||
logger.info(" - Using password from $PASSWORD") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The password might be from the config, so we would need to check
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I'll incorporate this change |
||
} else { | ||
logger.info(" - Authentication is disabled") | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,13 @@ import * as net from "net" | |
import * as path from "path" | ||
import { WebsocketRequest } from "../../../typings/pluginapi" | ||
import { logError } from "../../common/util" | ||
import { CodeArgs, toCodeArgs } from "../cli" | ||
import { AuthType, CodeArgs, toCodeArgs } from "../cli" | ||
import { isDevMode, vsRootPath } from "../constants" | ||
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http" | ||
import { SocketProxyProvider } from "../socket" | ||
import { isFile } from "../util" | ||
import { Router as WsRouter } from "../wsRouter" | ||
import { HttpCode, HttpError } from "../../common/http" | ||
|
||
export const router = express.Router() | ||
|
||
|
@@ -118,6 +119,11 @@ router.get("/", ensureVSCodeLoaded, async (req, res, next) => { | |
const FOLDER_OR_WORKSPACE_WAS_CLOSED = req.query.ew | ||
|
||
if (!isAuthenticated) { | ||
// If auth is HttpBasic, return a 401. | ||
if (req.args.auth === AuthType.HttpBasic) { | ||
res.setHeader('WWW-Authenticate', 'Basic realm="Access to the site"') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to set this header on the other places where we throw unauthorized for basic auth? And in the Also, could be cool to put the name in the realm, maybe:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only place a fresh browser load hits and thus needs this header to prompt the user. I'll update the realm |
||
throw new HttpError("Unauthorized", HttpCode.Unauthorized) | ||
}; | ||
const to = self(req) | ||
return redirect(req, res, "login", { | ||
to: to !== "/" ? to : undefined, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/bin/bash | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated to the PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||
|
||
# Try to set up the private (per user) User Data folder | ||
set +e | ||
mkdir -p $HOME/Workspace/Documents/$USERNAME/code-server | ||
export XDG_DATA_HOME=$HOME/Workspace/Documents/$USERNAME | ||
set -e | ||
|
||
echo 'export PS1="$USERNAME:\w\$ "' >> $HOME/.bashrc | ||
|
||
# Set the default project folder | ||
DEFAULT_PROJECT_FOLDER="$HOME/Workspace/" | ||
|
||
# Use the provided PROJECT_FOLDER or default to DEFAULT_PROJECT_FOLDER | ||
STARTING_FOLDER="${PROJECT_FOLDER:-$DEFAULT_PROJECT_FOLDER}" | ||
|
||
# Your script logic here | ||
echo "Starting in folder: $STARTING_FOLDER" | ||
|
||
/opt/code-server/bin/code-server \ | ||
--disable-telemetry \ | ||
--disable-update-check \ | ||
--disable-workspace-trust \ | ||
--locale=$LANG \ | ||
--welcome-text="Welcome to your Golden Helix VSCode environment" \ | ||
--ignore-last-opened \ | ||
$STARTING_FOLDER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Docker image seems unrelated to the auth changes, could we remove it from this PR? Happy to read about it in another PR/issue, but at first glance I am not sure it is something we should be maintaining here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I didn't meant to include this.