Browse Source

update helmet and apply explicit policies on it

pull/270/head
butlerx 4 years ago
parent
commit
8e976699c0
No known key found for this signature in database GPG Key ID: B37CA765BAA89170
  1. 56
      package.json
  2. 4
      src/client/wetty/term/confiruragtion/clipboard.ts
  3. 22
      src/main.ts
  4. 4
      src/server/command.ts
  5. 20
      src/server/socketServer.ts
  6. 25
      src/server/socketServer/security.ts
  7. 18
      src/shared/config.ts
  8. 4
      src/shared/defaults.ts
  9. 2
      src/shared/interfaces.ts
  10. 1357
      yarn.lock

56
package.json

@ -66,6 +66,7 @@
},
"exclude": [
"src/server/**/*.ts",
"src/client/**/*.spec.ts",
"src/*.ts"
],
"plugins": [
@ -97,63 +98,60 @@
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"compression": "^1.7.4",
"express": "^4.17.1",
"express-winston": "^4.0.1",
"express-winston": "^4.0.5",
"file-type": "^12.3.0",
"fs-extra": "^8.1.0",
"helmet": "^3.20.1",
"fs-extra": "^9.0.1",
"helmet": "^4.1.0",
"json5": "^2.1.3",
"lodash": "^4.17.19",
"lodash": "^4.17.20",
"node-pty": "^0.9.0",
"node-sass-middleware": "^0.11.0",
"sass": "^1.26.10",
"serve-favicon": "^2.5.0",
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0",
"toastify-js": "^1.6.1",
"winston": "^3.2.1",
"socket.io": "^2.3.0",
"socket.io-client": "^2.3.0",
"toastify-js": "^1.9.1",
"winston": "^3.3.3",
"xterm": "^4.8.1",
"xterm-addon-fit": "^0.4.0",
"yargs": "^14.0.0"
"yargs": "^15.4.1"
},
"devDependencies": {
"@types/chai": "^4.2.5",
"@types/compression": "^1.0.1",
"@types/express": "^4.17.1",
"@types/fs-extra": "^8.0.0",
"@types/helmet": "^0.0.47",
"@types/compression": "^1.7.0",
"@types/express": "^4.17.8",
"@types/fs-extra": "^9.0.1",
"@types/helmet": "^0.0.48",
"@types/jsdom": "^12.2.4",
"@types/lodash": "^4.14.138",
"@types/lodash": "^4.14.161",
"@types/mocha": "^5.2.7",
"@types/morgan": "^1.7.37",
"@types/node": "^12.7.3",
"@types/node-sass-middleware": "^0.0.31",
"@types/serve-favicon": "^2.2.31",
"@types/node": "^14.6.3",
"@types/serve-favicon": "^2.5.0",
"@types/sinon": "^7.5.1",
"@types/socket.io": "^2.1.2",
"@types/socket.io-client": "^1.4.32",
"@types/webpack-env": "^1.14.0",
"@types/socket.io": "^2.1.11",
"@types/socket.io-client": "^1.4.33",
"@types/winston": "^2.4.4",
"@types/yargs": "^15.0.5",
"@typescript-eslint/eslint-plugin": "^2.5.0",
"@typescript-eslint/parser": "^2.5.0",
"all-contributors-cli": "^6.17.0",
"all-contributors-cli": "^6.17.2",
"chai": "^4.2.0",
"concurrently": "^5.2.0",
"eslint": "^7.6.0",
"eslint": "^7.8.1",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.4",
"git-authors-cli": "^1.0.27",
"git-authors-cli": "^1.0.28",
"husky": "^4.2.5",
"jsdom": "^15.2.1",
"lint-staged": "^10.2.11",
"lint-staged": "^10.2.13",
"mocha": "^6.2.2",
"nodemon": "^2.0.4",
"prettier": "^2.0.5",
"prettier": "^2.1.1",
"sinon": "^7.5.0",
"snowpack": "^2.7.5",
"typescript": "^3.9.7"
"snowpack": "^2.10.1",
"typescript": "^4.0.2"
},
"contributors": [
"Krishna Srinivas <krishna.srinivas@gmail.com>",

4
src/client/wetty/term/confiruragtion/clipboard.ts

@ -4,8 +4,8 @@
@returns boolean to indicate success or failure
*/
export function copySelected(text: string): boolean {
if (window.clipboardData?.setData) {
window.clipboardData.setData('Text', text);
if ((window as any).clipboardData?.setData) {
(window as any).clipboardData.setData('Text', text);
return true;
}
if (

22
src/main.ts

@ -78,8 +78,9 @@ const opts = yargs
description: 'command to run in shell',
type: 'string',
})
.option('bypass-helmet', {
description: 'disable helmet from placing security restrictions',
.option('allow-iframe', {
description:
'Allow wetty to be embedded in an iframe, defaults to allowing same origin',
type: 'boolean',
})
.option('help', {
@ -90,20 +91,15 @@ const opts = yargs
.boolean('allow_discovery').argv;
if (!opts.help) {
(async () => {
const config = await loadConfigFile(opts.conf);
const conf = mergeCliConf(opts, config);
startServer(
conf.ssh,
conf.server,
conf.command,
conf.forceSSH,
conf.ssl,
).catch(err => {
loadConfigFile(opts.conf)
.then(config => mergeCliConf(opts, config))
.then(conf =>
startServer(conf.ssh, conf.server, conf.command, conf.forceSSH, conf.ssl),
)
.catch((err: Error) => {
logger.error(err);
process.exitCode = 1;
});
})();
} else {
yargs.showHelp();
process.exitCode = 0;

4
src/server/command.ts

@ -1,6 +1,6 @@
import url from 'url';
import { Socket } from 'socket.io';
import { SSH } from '../shared/interfaces.js';
import type { Socket } from 'socket.io';
import type { SSH } from '../shared/interfaces';
import { address } from './command/address.js';
import { loginOptions } from './command/login.js';
import { sshOptions } from './command/ssh.js';

20
src/server/socketServer.ts

@ -1,6 +1,5 @@
import express from 'express';
import compression from 'compression';
import helmet from 'helmet';
import winston from 'express-winston';
import type { SSL, SSLBuffer, Server } from '../shared/interfaces.js';
@ -9,10 +8,11 @@ import { html } from './socketServer/html.js';
import { listen } from './socketServer/socket.js';
import { logger } from '../shared/logger.js';
import { serveStatic, trim } from './socketServer/assets.js';
import { policies } from './socketServer/security.js';
import { loadSSL } from './socketServer/ssl.js';
export async function server(
{ base, port, host, title, bypassHelmet }: Server,
{ base, port, host, title, allowIframe }: Server,
ssl?: SSL,
): Promise<SocketIO.Server> {
const basePath = trim(base);
@ -24,6 +24,7 @@ export async function server(
});
const app = express();
const client = html(basePath, title);
app
.use(`${basePath}/web_modules`, serveStatic('web_modules'))
.use(`${basePath}/assets`, serveStatic('assets'))
@ -31,17 +32,10 @@ export async function server(
.use(winston.logger(logger))
.use(compression())
.use(favicon)
.use(redirect);
// Allow helmet to be bypassed.
// Unfortunately, order matters with middleware
// which is why this is thrown in the middle
if (!bypassHelmet) {
app.use(helmet());
}
const client = html(basePath, title);
app.get(basePath, client).get(`${basePath}/ssh/:user`, client);
.use(redirect)
.use(policies(allowIframe))
.get(basePath, client)
.get(`${basePath}/ssh/:user`, client);
const sslBuffer: SSLBuffer = await loadSSL(ssl);

25
src/server/socketServer/security.ts

@ -0,0 +1,25 @@
import helmet from 'helmet';
import type { Request, Response } from 'express';
export const policies = (allowIframe: boolean) => (
req: Request,
res: Response,
next: (err?: unknown) => void,
) => {
helmet({
frameguard: allowIframe ? false : { action: 'sameorigin' },
referrerPolicy: { policy: ['no-referrer-when-downgrade'] },
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
styleSrc: ["'self'", "'unsafe-inline'"],
fontSrc: ["'self'", 'data:'],
connectSrc: [
"'self'",
(req.protocol === 'http' ? 'ws://' : 'wss://') + req.get('host'),
],
},
},
})(req, res, next);
};

18
src/shared/config.ts

@ -2,6 +2,7 @@ import fs from 'fs-extra';
import path from 'path';
import JSON5 from 'json5';
import isUndefined from 'lodash/isUndefined.js';
import type { Arguments } from 'yargs';
import type { Config, SSH, Server, SSL } from './interfaces';
import {
@ -11,7 +12,15 @@ import {
defaultCommand,
} from './defaults.js';
type confValue = boolean | string | number | undefined | SSH | Server | SSL;
type confValue =
| boolean
| string
| number
| undefined
| unknown
| SSH
| Server
| SSL;
/**
* Cast given value to boolean
*
@ -92,10 +101,7 @@ const objectAssign = (
* @returns merged configuration
*
*/
export function mergeCliConf(
opts: Record<string, confValue>,
config: Config,
): Config {
export function mergeCliConf(opts: Arguments, config: Config): Config {
const ssl = {
key: opts['ssl-key'],
cert: opts['ssl-cert'],
@ -116,7 +122,7 @@ export function mergeCliConf(
host: opts.host,
port: opts.port,
title: opts.title,
bypassHelmet: opts['bypass-helmet'],
allowIframe: opts['allow-iframe'],
}) as Server,
command: isUndefined(opts.command) ? config.command : `${opts.command}`,
forceSSH: isUndefined(opts['force-ssh'])

4
src/shared/defaults.ts

@ -1,4 +1,4 @@
import type { SSH, Server } from "./interfaces";
import type { SSH, Server } from './interfaces';
export const sshDefault: SSH = {
user: process.env.SSHUSER || '',
@ -15,7 +15,7 @@ export const serverDefault: Server = {
port: parseInt(process.env.PORT || '3000', 10),
host: '0.0.0.0',
title: process.env.TITLE || 'WeTTy - The Web Terminal Emulator',
bypassHelmet: false,
allowIframe: false,
};
export const forceSSHDefault = process.env.FORCESSH === 'true' || false;

2
src/shared/interfaces.ts

@ -25,7 +25,7 @@ export interface Server {
host: string;
title: string;
base: string;
bypassHelmet: boolean;
allowIframe: boolean;
}
export interface Config {

1357
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save