disallow shell scripting in web by default

This commit is contained in:
Jan Prochazka
2022-03-20 11:17:49 +01:00
parent 6fb582249c
commit 2bec053809
11 changed files with 204 additions and 75 deletions

View File

@@ -34,6 +34,7 @@ module.exports = {
singleDatabase: connections.singleDatabase,
hideAppEditor: !!process.env.HIDE_APP_EDITOR,
allowShellConnection: platformInfo.allowShellConnection,
allowShellScripting: platformInfo.allowShellConnection,
permissions,
...currentVersion,
};

View File

@@ -73,7 +73,7 @@ module.exports = {
const res = [];
for (const packageName of _.union(files1, files2)) {
if (packageName == 'dist') continue;
// if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
try {
if (packagedContent && packagedContent[packageName]) {
const manifest = {

View File

@@ -6,9 +6,10 @@ const byline = require('byline');
const socket = require('../utility/socket');
const { fork } = require('child_process');
const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
const { extractShellApiPlugins, extractShellApiFunctionName } = require('dbgate-tools');
const { extractShellApiPlugins, extractShellApiFunctionName, jsonScriptToJavascript } = require('dbgate-tools');
const { handleProcessCommunication } = require('../utility/processComm');
const processArgs = require('../utility/processArgs');
const platformInfo = require('../utility/platformInfo');
function extractPlugins(script) {
const requireRegex = /\s*\/\/\s*@require\s+([^\s]+)\s*\n/g;
@@ -148,12 +149,18 @@ module.exports = {
},
start_meta: true,
async start({ script, isGeneratedScript }) {
if (!isGeneratedScript && process.env.DISABLE_SHELL) {
return { errorMessage: 'Shell is disabled' };
async start({ script }) {
const runid = uuidv1();
if (script.type == 'json') {
const js = jsonScriptToJavascript(script);
return this.startCore(runid, scriptTemplate(js, false));
}
if (!platformInfo.allowShellScripting) {
return { errorMessage: 'Shell scripting is not allowed' };
}
const runid = uuidv1();
return this.startCore(runid, scriptTemplate(script, false));
},

View File

@@ -40,6 +40,7 @@ const platformInfo = {
platform,
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
allowShellConnection: !!process.env.SHELL_CONNECTION || !!isElectron(),
allowShellScripting: !!process.env.SHELL_SCRIPTING || !!isElectron(),
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
};

View File

@@ -0,0 +1,165 @@
import _uniq from 'lodash/uniq';
import { extractShellApiFunctionName, extractShellApiPlugins } from './packageTools';
export class ScriptWriter {
s = '';
packageNames: string[] = [];
varCount = 0;
constructor(varCount = '0') {
this.varCount = parseInt(varCount) || 0;
}
allocVariable(prefix = 'var') {
this.varCount += 1;
return `${prefix}${this.varCount}`;
}
_put(s = '') {
this.s += s;
this.s += '\n';
}
endLine() {
this._put();
}
assignCore(variableName, functionName, props) {
this._put(`const ${variableName} = await ${functionName}(${JSON.stringify(props)});`);
}
assign(variableName, functionName, props) {
this.assignCore(variableName, extractShellApiFunctionName(functionName), props);
this.packageNames.push(...extractShellApiPlugins(functionName, props));
}
assignValue(variableName, jsonValue) {
this._put(`const ${variableName} = ${JSON.stringify(jsonValue)};`);
}
requirePackage(packageName) {
this.packageNames.push(packageName);
}
copyStream(sourceVar, targetVar, colmapVar = null) {
if (colmapVar) {
this._put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar}, {columns: ${colmapVar}});`);
} else {
this._put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`);
}
}
comment(s) {
this._put(`// ${s}`);
}
getScript(schedule = null) {
const packageNames = this.packageNames;
let prefix = _uniq(packageNames)
.map(packageName => `// @require ${packageName}\n`)
.join('');
if (schedule) prefix += `// @schedule ${schedule}`;
if (prefix) prefix += '\n';
return prefix + this.s;
}
}
export class ScriptWriterJson {
s = '';
packageNames: string[] = [];
varCount = 0;
commands = [];
constructor(varCount = '0') {
this.varCount = parseInt(varCount) || 0;
}
allocVariable(prefix = 'var') {
this.varCount += 1;
return `${prefix}${this.varCount}`;
}
endLine() {
this.commands.push({
type: 'endline',
});
}
assign(variableName, functionName, props) {
this.commands.push({
type: 'assign',
variableName,
functionName: extractShellApiFunctionName(functionName),
props,
});
this.packageNames.push(...extractShellApiPlugins(functionName, props));
}
assignValue(variableName, jsonValue) {
this.commands.push({
type: 'assignValue',
variableName,
jsonValue,
});
}
copyStream(sourceVar, targetVar, colmapVar = null) {
this.commands.push({
type: 'copyStream',
sourceVar,
targetVar,
colmapVar,
});
}
comment(text) {
this.commands.push({
type: 'comment',
text,
});
}
getScript(schedule = null) {
return {
type: 'json',
schedule,
commands: this.commands,
packageNames: this.packageNames,
};
}
}
export function jsonScriptToJavascript(json) {
const { schedule, commands, packageNames } = json;
const script = new ScriptWriter();
for (const packageName of packageNames) {
if (!/^dbgate-plugin-.*$/.test(packageName)) {
throw new Error('Unallowed package name:' + packageName);
}
script.packageNames.push(packageName);
}
for (const cmd of commands) {
switch (cmd.type) {
case 'assign':
script.assignCore(cmd.variableName, cmd.functionName, cmd.props);
break;
case 'assignValue':
script.assignValue(cmd.variableName, cmd.jsonValue);
break;
case 'copyStream':
script.copyStream(cmd.sourceVar, cmd.targetVar, cmd.colmapVar);
break;
case 'endLine':
script.endLine();
break;
case 'comment':
script.comment(cmd.text);
break;
}
}
return script.getScript(schedule);
}

View File

@@ -17,3 +17,4 @@ export * from './yamlModelConv';
export * from './stringTools';
export * from './computeDiffRows';
export * from './preloadedRowsTools';
export * from './ScriptWriter';

View File

@@ -1,58 +0,0 @@
import _ from 'lodash';
import { extractShellApiFunctionName, extractShellApiPlugins } from 'dbgate-tools';
export default class ScriptWriter {
s = '';
packageNames: string[] = [];
varCount = 0;
constructor(varCount = '0') {
this.varCount = parseInt(varCount) || 0;
}
allocVariable(prefix = 'var') {
this.varCount += 1;
return `${prefix}${this.varCount}`;
}
put(s = '') {
this.s += s;
this.s += '\n';
}
assign(variableName, functionName, props) {
this.put(`const ${variableName} = await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)});`);
this.packageNames.push(...extractShellApiPlugins(functionName, props));
}
assignValue(variableName, jsonValue) {
this.put(`const ${variableName} = ${JSON.stringify(jsonValue)};`);
}
requirePackage(packageName) {
this.packageNames.push(packageName);
}
copyStream(sourceVar, targetVar, colmapVar = null) {
if (colmapVar) {
this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar}, {columns: ${colmapVar}});`);
} else {
this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`);
}
}
comment(s) {
this.put(`// ${s}`);
}
getScript(schedule = null) {
const packageNames = this.packageNames;
let prefix = _.uniq(packageNames)
.map(packageName => `// @require ${packageName}\n`)
.join('');
if (schedule) prefix += `// @schedule ${schedule}`;
if (prefix) prefix += '\n';
return prefix + this.s;
}
}

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import ScriptWriter from './ScriptWriter';
import { ScriptWriter, ScriptWriterJson } from 'dbgate-tools';
import getAsArray from '../utility/getAsArray';
import { getConnectionInfo } from '../utility/metadataLoaders';
import { findEngineDriver, findObjectLike } from 'dbgate-tools';
@@ -187,8 +187,12 @@ export function normalizeExportColumnMap(colmap) {
return null;
}
export default async function createImpExpScript(extensions, values, addEditorInfo = true) {
const script = new ScriptWriter(values.startVariableIndex || 0);
export default async function createImpExpScript(extensions, values, addEditorInfo = true, forceScript = false) {
const config = getCurrentConfig();
const script =
config.allowShellScripting || forceScript
? new ScriptWriter(values.startVariableIndex || 0)
: new ScriptWriterJson(values.startVariableIndex || 0);
const [sourceConnection, sourceDriver] = await getConnection(
extensions,
@@ -222,7 +226,7 @@ export default async function createImpExpScript(extensions, values, addEditorIn
}
script.copyStream(sourceVar, targetVar, colmapVar);
script.put();
script.endLine();
}
if (addEditorInfo) {
script.comment('@ImportExportConfigurator');

View File

@@ -16,7 +16,7 @@
import { getDefaultFileFormat } from '../plugins/fileformats';
import RunnerOutputFiles from '../query/RunnerOutputFiles.svelte';
import SocketMessageView from '../query/SocketMessageView.svelte';
import { currentArchive, currentDatabase, extensions, selectedWidget } from '../stores';
import { currentArchive, currentDatabase, extensions, getCurrentConfig, selectedWidget } from '../stores';
import { apiCall, apiOff, apiOn } from '../utility/api';
import createRef from '../utility/createRef';
import openNewTab from '../utility/openNewTab';
@@ -90,7 +90,7 @@
const handleGenerateScript = async e => {
closeCurrentModal();
const code = await createImpExpScript($extensions, e.detail);
const code = await createImpExpScript($extensions, e.detail, undefined, true);
openNewTab(
{
title: 'Shell #',
@@ -108,7 +108,7 @@
const script = await createImpExpScript($extensions, values);
executeNumber += 1;
let runid = runnerId;
const resp = await apiCall('runners/start', { script, isGeneratedScript: true });
const resp = await apiCall('runners/start', { script });
runid = resp.runid;
runnerId = runid;

View File

@@ -12,6 +12,7 @@
execute: true,
toggleComment: true,
findReplace: true,
executeAdditionalCondition: () => getCurrentConfig().allowShellScripting,
});
registerCommand({
@@ -51,6 +52,7 @@
import AceEditor from '../query/AceEditor.svelte';
import RunnerOutputPane from '../query/RunnerOutputPane.svelte';
import useEditorData from '../query/useEditorData';
import { getCurrentConfig } from '../stores';
import { apiCall, apiOff, apiOn } from '../utility/api';
import { copyTextToClipboard } from '../utility/clipboard';
import { changeTab } from '../utility/common';
@@ -177,6 +179,11 @@
const resp = await apiCall('runners/start', {
script: getActiveScript(),
});
if (resp.errorMessage) {
showSnackbarError(resp.errorMessage);
return;
}
runid = resp.runid;
runnerId = runid;
busy = true;

View File

@@ -1,9 +1,10 @@
import ScriptWriter from '../impexp/ScriptWriter';
import { ScriptWriter, ScriptWriterJson } from 'dbgate-tools';
import getElectron from './getElectron';
import { showSnackbar, showSnackbarInfo, showSnackbarError, closeSnackbar } from '../utility/snackbar';
import resolveApi from './resolveApi';
import { apiCall, apiOff, apiOn } from './api';
import { normalizeExportColumnMap } from '../impexp/createImpExpScript';
import { getCurrentConfig } from '../stores';
export async function exportQuickExportFile(dataName, reader, format, columnMap = null) {
const electron = getElectron();
@@ -25,7 +26,7 @@ export async function exportQuickExportFile(dataName, reader, format, columnMap
if (!filePath) return;
const script = new ScriptWriter();
const script = getCurrentConfig().allowShellScripting ? new ScriptWriter() : new ScriptWriterJson();
const sourceVar = script.allocVariable();
script.assign(sourceVar, reader.functionName, reader.props);
@@ -42,9 +43,9 @@ export async function exportQuickExportFile(dataName, reader, format, columnMap
}
script.copyStream(sourceVar, targetVar, colmapVar);
script.put();
script.endLine();
const resp = await apiCall('runners/start', { script: script.getScript(), isGeneratedScript: true });
const resp = await apiCall('runners/start', { script: script.getScript() });
const runid = resp.runid;
let isCanceled = false;