Merge pull request #835 from cdr/log-failed-logins
Add failed authentication attempt logger
This commit is contained in:
commit
f25a614333
15
doc/security/code-server.fail2ban.conf
Normal file
15
doc/security/code-server.fail2ban.conf
Normal file
@ -0,0 +1,15 @@
|
||||
# Fail2Ban filter for code-server
|
||||
#
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
|
||||
failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"remote_address\":\"<HOST>\"
|
||||
|
||||
ignoreregex =
|
||||
|
||||
datepattern = "timestamp":{EPOCH}}$
|
||||
|
||||
# Author: Dean Sheather
|
||||
|
42
doc/security/fail2ban.md
Normal file
42
doc/security/fail2ban.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Protecting code-server from bruteforce attempts
|
||||
|
||||
code-server outputs all failed login attempts, along with the IP address,
|
||||
provided password, user agent and timestamp by default. When using a reverse
|
||||
proxy such as Nginx or Apache, the remote address may appear to be `127.0.0.1`
|
||||
or a similar address unless the `--trust-proxy` argument is provided to
|
||||
code-server.
|
||||
|
||||
When used with the `--trust-proxy` argument, code-server will use the last IP in
|
||||
`X-Forwarded-For` (if provided) instead of the remote socket address. Ensure
|
||||
that you are setting this value in your reverse proxy:
|
||||
|
||||
Nginx:
|
||||
```
|
||||
location / {
|
||||
...
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Apache:
|
||||
```
|
||||
<VirtualEnv>
|
||||
...
|
||||
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
|
||||
...
|
||||
</VirtualEnv>
|
||||
```
|
||||
|
||||
It is extremely important that if you enable `--trust-proxy` you ensure your
|
||||
code-server instance is not accessible from the internet (block it in your
|
||||
firewall).
|
||||
|
||||
## Fail2Ban
|
||||
|
||||
Fail2Ban allows for automatically banning and logging repeated failed
|
||||
authentication attempts for many applications through regex filters. A working
|
||||
filter for code-server can be found in `./code-server.fail2ban.conf`. Once this
|
||||
is installed and configured correctly, repeated failed login attempts should
|
||||
automatically be banned from connecting to your server.
|
||||
|
@ -38,21 +38,24 @@ Usage: code-server [options]
|
||||
Run VS Code on a remote server.
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
-V, --version output the version number
|
||||
--cert <value>
|
||||
--cert-key <value>
|
||||
-e, --extensions-dir <dir> Set the root path for extensions.
|
||||
-d, --user-data-dir <dir> Specifies the directory that user data is kept in, useful when running as root.
|
||||
--data-dir <value> DEPRECATED: Use '--user-data-dir' instead. Customize where user-data is stored.
|
||||
-h, --host <value> Customize the hostname. (default: "0.0.0.0")
|
||||
-o, --open Open in the browser on startup.
|
||||
-p, --port <number> Port to bind on. (default: 8443)
|
||||
-N, --no-auth Start without requiring authentication.
|
||||
-H, --allow-http Allow http connections.
|
||||
-P, --password <value> Specify a password for authentication.
|
||||
--disable-telemetry Disables ALL telemetry.
|
||||
--help output usage information
|
||||
```
|
||||
-e, --extensions-dir <dir> Override the main default path for user extensions.
|
||||
--extra-extensions-dir [dir] Path to an extra user extension directory (repeatable). (default: [])
|
||||
--extra-builtin-extensions-dir [dir] Path to an extra built-in extension directory (repeatable). (default: [])
|
||||
-d, --user-data-dir <dir> Specifies the directory that user data is kept in, useful when running as root.
|
||||
-h, --host <value> Customize the hostname. (default: "0.0.0.0")
|
||||
-o, --open Open in the browser on startup.
|
||||
-p, --port <number> Port to bind on. (default: 8443)
|
||||
-N, --no-auth Start without requiring authentication.
|
||||
-H, --allow-http Allow http connections.
|
||||
--disable-telemetry Disables ALL telemetry.
|
||||
--socket <value> Listen on a UNIX socket. Host and port will be ignored when set.
|
||||
--trust-proxy Trust the X-Forwarded-For header, useful when using a reverse proxy.
|
||||
--install-extension <value> Install an extension by its ID.
|
||||
-h, --help output usage information
|
||||
```
|
||||
|
||||
### Data Directory
|
||||
Use `code-server -d (path/to/directory)` or `code-server --user-data-dir=(path/to/directory)`, excluding the parentheses to specify the root folder that VS Code will start in.
|
||||
@ -79,23 +82,23 @@ Options:
|
||||
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md)
|
||||
|
||||
### Nginx Reverse Proxy
|
||||
Nginx is for reverse proxy. Below is a virtual host example that works with code-server. Please also pass --allow-http. You can also use certbot by EFF to get a ssl certificates for free.
|
||||
Below is a virtual host example that works with code-server. Please also pass `--allow-http` and `--trust-proxy` to code-server to allow the proxy to connect. You can also use Let's Encrypt to get a SSL certificates for free.
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name code.example.com code.example.org;
|
||||
location / {
|
||||
proxy_pass http://localhost:8443/;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection upgrade;
|
||||
proxy_set_header Accept-Encoding gzip;
|
||||
}
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://localhost:8443/;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection upgrade;
|
||||
proxy_set_header Accept-Encoding gzip;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Apache Reverse Proxy
|
||||
Example of https virtualhost configuration for Apache as a reverse proxy. Please also pass --allow-http on code-server startup to allow the proxy to connect.
|
||||
Example of a HTTPS virtualhost configuration for Apache as a reverse proxy. Please also pass `--allow-http` and `--trust-proxy` to code-server to allow the proxy to connect. You can also use Let's Encrypt to get a SSL certificates for free.
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName code.example.com
|
||||
|
@ -38,6 +38,7 @@ commander.version(process.env.VERSION || "development")
|
||||
.option("-P, --password <value>", "DEPRECATED: Use the PASSWORD environment variable instead. Specify a password for authentication.")
|
||||
.option("--disable-telemetry", "Disables ALL telemetry.", false)
|
||||
.option("--socket <value>", "Listen on a UNIX socket. Host and port will be ignored when set.")
|
||||
.option("--trust-proxy", "Trust the X-Forwarded-For header, useful when using a reverse proxy.", false)
|
||||
.option("--install-extension <value>", "Install an extension by its ID.")
|
||||
.option("--bootstrap-fork <name>", "Used for development. Never set.")
|
||||
.option("--extra-args <args>", "Used for development. Never set.")
|
||||
@ -74,6 +75,7 @@ const bold = (text: string | number): string | number => {
|
||||
readonly cert?: string;
|
||||
readonly certKey?: string;
|
||||
readonly socket?: string;
|
||||
readonly trustProxy?: boolean;
|
||||
|
||||
readonly installExtension?: string;
|
||||
|
||||
@ -273,6 +275,7 @@ const bold = (text: string | number): string | number => {
|
||||
},
|
||||
},
|
||||
password,
|
||||
trustProxy: options.trustProxy,
|
||||
httpsOptions: hasCustomHttps ? {
|
||||
key: certKeyData,
|
||||
cert: certData,
|
||||
|
@ -31,6 +31,7 @@ interface CreateAppOptions {
|
||||
httpsOptions?: https.ServerOptions;
|
||||
allowHttp?: boolean;
|
||||
bypassAuth?: boolean;
|
||||
trustProxy?: boolean;
|
||||
}
|
||||
|
||||
export const createApp = async (options: CreateAppOptions): Promise<{
|
||||
@ -62,6 +63,21 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
||||
return true;
|
||||
};
|
||||
|
||||
const remoteAddress = (req: http.IncomingMessage): string | void => {
|
||||
let xForwardedFor = req.headers["x-forwarded-for"];
|
||||
if (Array.isArray(xForwardedFor)) {
|
||||
xForwardedFor = xForwardedFor.join(", ");
|
||||
}
|
||||
|
||||
if (options.trustProxy && xForwardedFor !== undefined) {
|
||||
const addresses = xForwardedFor.split(",").map(s => s.trim());
|
||||
|
||||
return addresses.pop();
|
||||
}
|
||||
|
||||
return req.socket.remoteAddress;
|
||||
};
|
||||
|
||||
const isAuthed = (req: http.IncomingMessage): boolean => {
|
||||
try {
|
||||
if (!options.password || options.bypassAuth) {
|
||||
@ -70,7 +86,22 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
||||
|
||||
// Try/catch placed here just in case
|
||||
const cookies = parseCookies(req);
|
||||
if (cookies.password && safeCompare(cookies.password, options.password)) {
|
||||
if (cookies.password) {
|
||||
if (!safeCompare(cookies.password, options.password)) {
|
||||
let userAgent = req.headers["user-agent"];
|
||||
let timestamp = Math.floor(new Date().getTime() / 1000);
|
||||
if (Array.isArray(userAgent)) {
|
||||
userAgent = userAgent.join(", ");
|
||||
}
|
||||
logger.info("Failed login attempt",
|
||||
field("password", cookies.password),
|
||||
field("remote_address", remoteAddress(req)),
|
||||
field("user_agent", userAgent),
|
||||
field("timestamp", timestamp));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (ex) {
|
||||
@ -214,7 +245,9 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
||||
const staticGzip = expressStaticGzip(path.join(baseDir, "build/web"));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.originalUrl}`, field("host", req.hostname), field("ip", req.ip));
|
||||
logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.originalUrl}`,
|
||||
field("host", req.hostname),
|
||||
field("remote_address", remoteAddress(req)));
|
||||
|
||||
// Force HTTPS unless allowing HTTP.
|
||||
if (!isEncrypted(req.socket) && !options.allowHttp) {
|
||||
|
Reference in New Issue
Block a user