Drop privileges to respective uid

This commit is contained in:
Radon Rosborough 2020-06-22 15:28:27 -06:00
parent afad563d56
commit 91fe6ffd65
25 changed files with 716 additions and 243 deletions

View File

@ -2,6 +2,9 @@ FROM ubuntu:focal
ARG UID
COPY scripts/docker-install-phase0.bash /tmp/
RUN /tmp/docker-install-phase0.bash
COPY scripts/docker-install-phase1.bash /tmp/
RUN /tmp/docker-install-phase1.bash
@ -31,6 +34,7 @@ RUN /tmp/docker-install-phase6.bash "$UID"
USER docker
WORKDIR /home/docker
RUN chmod go-rwx /home/docker
EXPOSE 6119
EXPOSE 6120

View File

@ -4,6 +4,9 @@ FROM ubuntu:focal
# prod, it's not actually read by anything.
ARG UID
COPY scripts/docker-install-phase0.bash /tmp/
RUN /tmp/docker-install-phase0.bash
COPY scripts/docker-install-phase1.bash /tmp/
RUN /tmp/docker-install-phase1.bash
@ -33,6 +36,7 @@ RUN /tmp/docker-install-phase6.bash
USER docker
WORKDIR /home/docker
RUN chmod go-rwx /home/docker
EXPOSE 6119
EXPOSE 6120
@ -40,7 +44,7 @@ ENTRYPOINT ["/usr/local/bin/pid1.bash"]
COPY scripts/pid1.bash /usr/local/bin/
CMD ["yarn", "run", "server"]
RUN mkdir /tmp/riju
RUN mkdir /tmp/riju /tmp/riju/scripts
COPY --chown=docker:docker package.json yarn.lock /tmp/riju/
RUN cd /tmp/riju && yarn install
COPY --chown=docker:docker webpack.config.js tsconfig.json tsconfig-webpack.json /tmp/riju/
@ -48,10 +52,11 @@ COPY --chown=docker:docker frontend /tmp/riju/frontend
RUN cd /tmp/riju && yarn run frontend
COPY --chown=docker:docker backend /tmp/riju/backend
RUN cd /tmp/riju && yarn run backend
COPY --chown=docker:docker scripts/compile-system.bash /tmp/riju/scripts
COPY --chown=docker:docker system /tmp/riju/system
RUN cd /tmp/riju && RIJU_PRIVILEGED=1 yarn run system
COPY --chown=docker:docker . /home/docker/src
RUN cp -R /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju
RUN sudo cp -a /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju
WORKDIR /home/docker/src
RUN sudo deluser docker sudo

View File

@ -18,7 +18,7 @@ image-prod: ## Build Docker image for production
.PHONY: docker
docker: image-dev ## Run shell with source code and deps inside Docker
scripts/docker.bash run -it --rm -v "$(PWD):/home/docker/src" -p 6119:6119 -p 6120:6120 riju bash
scripts/docker.bash run -it --rm -v "$(PWD):/home/docker/src" -p 6119:6119 -p 6120:6120 -h riju riju bash
.PHONY: deploy
deploy: ## Deploy current master from GitHub to production

View File

@ -18,18 +18,18 @@ documenting it until it has reached feature-completeness.
To run the webserver, all you need is Yarn. Just run `yarn install` as
usual to install dependencies. For production, it's:
$ yarn backend
$ yarn frontend
$ yarn system
$ yarn backend |- or run all three with 'yarn build'
$ yarn frontend |
$ yarn system |
$ yarn server
For development with file watching and automatic server rebooting and
all that, it's:
$ yarn backend-dev
$ yarn frontend-dev
$ yarn system-dev
$ yarn server-dev
$ yarn backend-dev |- or run all four with 'yarn dev'
$ yarn frontend-dev |
$ yarn system-dev |
$ yarn server-dev |
The webserver listens on `localhost:6119`. Now, although the server
itself will work, the only languages that will work are the ones that

View File

@ -1,49 +1,48 @@
import * as fs from "fs";
import * as path from "path";
import * as WebSocket from "ws";
import * as mkdirp from "mkdirp";
import * as nodeCleanup from "node-cleanup";
import * as pty from "node-pty";
import { IPty } from "node-pty";
import * as tmp from "tmp";
import { v4 as getUUID } from "uuid";
import { PRIVILEGED } from "./config";
import { LangConfig, langs } from "./langs";
import { borrowUser } from "./users";
import { callPrivileged, getEnv, spawnPrivileged } from "./util";
export class Session {
id: string;
uuid: string;
code: string;
config: LangConfig;
term: { pty: IPty | null; live: boolean };
ws: WebSocket;
tmpdir: string | null;
tmpdirCleanup: (() => void) | null;
homedir: string | null;
uid: number | null;
uidCleanup: (() => Promise<void>) | null;
log = (msg: string) => console.log(`[${this.id}] ${msg}`);
log = (msg: string) => console.log(`[${this.uuid}] ${msg}`);
constructor(ws: WebSocket, lang: string) {
this.id = getUUID();
this.uuid = getUUID();
this.log(`Creating session, language ${lang}`);
this.ws = ws;
this.config = langs[lang];
this.term = { pty: null, live: false };
this.code = "";
this.tmpdir = null;
this.tmpdirCleanup = null;
this.homedir = null;
this.uid = null;
this.uidCleanup = null;
ws.on("message", this.handleClientMessage);
ws.on("close", () =>
this.cleanup().catch((err) =>
this.log(`Error during session cleanup: ${err}`)
)
this.cleanup().catch((err) => {
this.log(`Error during session cleanup`);
console.log(err);
})
);
nodeCleanup();
this.run().catch((err) => this.log(`Error while running: ${err}`));
this.run().catch((err) => {
this.log(`Error while setting up environment for pty`);
console.log(err);
});
}
handleClientMessage = (event: string) => {
let msg: any;
@ -81,9 +80,18 @@ export class Session {
({ uid: this.uid, cleanup: this.uidCleanup } = await borrowUser(
this.log
));
this.log(`Borrowed uid ${this.uid}`);
}
this.log(`Borrowed uid ${this.uid}`);
const { name, repl, main, suffix, compile, run, hacks } = this.config;
const {
name,
repl,
main,
suffix,
alwaysCreate,
compile,
run,
hacks,
} = this.config;
if (this.term.pty) {
this.term.pty.kill();
this.term.live = false;
@ -93,20 +101,9 @@ export class Session {
} catch (err) {
//
}
if (this.tmpdir == null) {
({ path: this.tmpdir, cleanup: this.tmpdirCleanup } = await new Promise(
(resolve, reject) =>
tmp.dir(
{ unsafeCleanup: true, dir: "riju" },
(err, path, cleanup) => {
if (err) {
reject(err);
} else {
resolve({ path, cleanup });
}
}
)
));
if (this.homedir == null) {
this.homedir = `/tmp/riju/${this.uuid}`;
await callPrivileged(["setup", `${this.uid}`, this.uuid], this.log);
}
let cmdline: string;
if (!run) {
@ -117,22 +114,33 @@ export class Session {
code += suffix;
}
if (main.includes("/")) {
await mkdirp(path.dirname(path.resolve(this.tmpdir!, main)));
await spawnPrivileged(
this.uid,
this.uuid,
["mkdir", "-p", path.dirname(path.resolve(this.homedir, main))],
this.log
);
}
await new Promise((resolve, reject) =>
fs.writeFile(path.resolve(this.tmpdir!, main), code, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
})
await spawnPrivileged(
this.uid,
this.uuid,
["sh", "-c", `cat > ${path.resolve(this.homedir, main)}`],
this.log,
{ input: code }
);
cmdline = run;
if (compile) {
cmdline = `( ${compile} ) && ( ${run} )`;
}
} else if (repl) {
if (alwaysCreate) {
await spawnPrivileged(
this.uid,
this.uuid,
["touch", `${path.resolve(this.homedir, main)}`],
this.log
);
}
cmdline = repl;
} else {
cmdline = `echo '${name} has no REPL, press Run to see it in action'`;
@ -140,32 +148,38 @@ export class Session {
if (hacks && hacks.includes("ghci-config") && run) {
if (this.code) {
const contents = ":load Main\nmain\n";
await new Promise((resolve, reject) => {
fs.writeFile(path.resolve(this.tmpdir!, ".ghci"), contents, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
await spawnPrivileged(
this.uid,
this.uuid,
["sh", "-c", `cat > ${path.resolve(this.homedir, ".ghci")}`],
this.log,
{ input: contents }
);
} else {
await new Promise((resolve, reject) =>
fs.unlink(path.resolve(this.tmpdir!, ".ghci"), (err) => {
if (err && err.code !== "ENOENT") {
reject(err);
} else {
resolve();
}
})
await spawnPrivileged(
this.uid,
this.uuid,
["rm", "-f", path.resolve(this.homedir, ".ghci")],
this.log
);
}
}
const args = PRIVILEGED
? [
"/home/docker/src/system/out/riju-system-privileged",
"spawn",
`${this.uid}`,
`${this.uuid}`,
"bash",
"-c",
cmdline,
]
: ["bash", "-c", cmdline];
const env = getEnv(this.uuid);
const term = {
pty: pty.spawn("bash", ["-c", cmdline], {
pty: pty.spawn(args[0], args.slice(1), {
name: "xterm-color",
cwd: this.tmpdir!,
env: process.env as { [key: string]: string },
env,
}),
live: true,
};
@ -186,8 +200,8 @@ export class Session {
};
cleanup = async () => {
this.log(`Cleaning up session`);
if (this.tmpdirCleanup) {
this.tmpdirCleanup();
if (this.homedir) {
await callPrivileged(["teardown", this.uuid], this.log);
}
if (this.uidCleanup) {
await this.uidCleanup();

3
backend/src/config.ts Normal file
View File

@ -0,0 +1,3 @@
import * as process from "process";
export const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false;

View File

@ -6,6 +6,7 @@ export interface LangConfig {
main: string;
prefix?: string;
suffix?: string;
alwaysCreate?: boolean;
compile?: string;
run: string;
template: string;
@ -220,7 +221,7 @@ int main() {
monacoLang: "csharp",
main: "main.cs",
compile: "mcs main.cs",
run: "./main.exe",
run: "mono main.exe",
template: `class main {
static void Main(string[] args) {
System.Console.WriteLine("Hello, world!");
@ -326,9 +327,9 @@ output = "Hello, world!"
aliases: ["elv"],
name: "Elvish",
monacoLang: "plaintext",
repl: "SHELL=/usr/bin/elvish HOME=. elvish",
repl: `SHELL=/usr/bin/elvish HOME="$PWD" elvish`,
main: ".elvish/rc.elv",
run: "SHELL=/usr/bin/elvish HOME=. elvish",
run: `SHELL=/usr/bin/elvish HOME="$PWD" elvish`,
template: `echo "Hello, world!"
`,
},
@ -451,8 +452,8 @@ main = putStrLn "Hello, world!"
repl: "ink",
main: "main.ink",
run: "ink main.ink; ink",
template: `std := load('../../opt/ink/std')
str := load('../../opt/ink/str')
template: `std := load('../../../opt/ink/std')
str := load('../../../opt/ink/str')
log := std.log
@ -535,9 +536,9 @@ PLEASE GIVE UP
aliases: ["kshell"],
name: "Ksh",
monacoLang: "shell",
repl: "SHELL=/usr/bin/ksh HOME=. ksh",
repl: `SHELL=/usr/bin/ksh HOME="$PWD" ksh`,
main: ".kshrc",
run: "SHELL=/usr/bin/ksh HOME=. ksh",
run: `SHELL=/usr/bin/ksh HOME="$PWD" ksh`,
template: `echo "Hello, world!"
`,
},
@ -830,9 +831,9 @@ binding_irb.run(IRB.conf)
aliases: ["shell", "posix", "posixsh", "ash", "dash", "posh"],
name: "Sh",
monacoLang: "shell",
repl: "SHELL=/usr/bin/sh HOME=. posh -l",
repl: `SHELL=/usr/bin/sh HOME="$PWD" posh -l`,
main: ".profile",
run: "SHELL=/usr/bin/sh HOME=. posh -l",
run: `SHELL=/usr/bin/sh HOME="$PWD" posh -l`,
template: `echo "Hello, world!"
`,
},
@ -992,7 +993,7 @@ END
monacoLang: "tcl",
repl: "tclsh",
main: ".tclshrc",
run: "HOME=. tclsh",
run: `HOME="$PWD" tclsh`,
template: `puts {Hello, world!}
`,
},
@ -1000,9 +1001,9 @@ END
aliases: ["tcshell", "tcshrc"],
name: "Tcsh",
monacoLang: "shell",
repl: "SHELL=/usr/bin/tcsh HOME=. tcsh",
repl: `SHELL=/usr/bin/tcsh HOME="$PWD" tcsh`,
main: ".tcshrc",
run: "SHELL=/usr/bin/tcsh HOME=. tcsh",
run: `SHELL=/usr/bin/tcsh HOME="$PWD" tcsh`,
template: `echo "Hello, world!"
`,
},
@ -1104,7 +1105,8 @@ message:
monacoLang: "shell",
repl: "SHELL=/usr/bin/zsh zsh",
main: ".zshrc",
run: "SHELL=/usr/bin/zsh ZDOTDIR=. zsh",
alwaysCreate: true,
run: `SHELL=/usr/bin/zsh ZDOTDIR="$PWD" zsh`,
template: `echo "Hello, world!"
`,
},

View File

@ -1,16 +1,17 @@
import { spawn } from "child_process";
import * as fs from "fs";
import * as process from "process";
import * as AsyncLock from "async-lock";
import * as _ from "lodash";
import * as parsePasswd from "parse-passwd";
import { PRIVILEGED } from "./config";
import { callPrivileged } from "./util";
// Keep in sync with system/src/riju-system-privileged.c
const MIN_UID = 2000;
const MAX_UID = 65000;
const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false;
const CUR_UID = parseInt(process.env.UID || "") || null;
let availIds: number[] | null = null;
@ -29,7 +30,7 @@ async function readExistingUsers(log: (msg: string) => void) {
})
)
)
.filter(({ username }) => username.startsWith("riju_user"))
.filter(({ username }) => username.startsWith("riju"))
.map(({ uid }) => parseInt(uid))
.filter((uid) => !isNaN(uid) && uid >= MIN_UID && uid < MAX_UID);
nextId = (_.max(availIds) || MIN_UID - 1) + 1;
@ -40,33 +41,11 @@ async function createUser(log: (msg: string) => void): Promise<number> {
if (nextId! >= MAX_UID) {
throw new Error("too many users");
}
return await new Promise((resolve, reject) => {
const uid = nextId!;
const useradd = spawn("system/out/riju-system-privileged", [
"useradd",
`${uid}`,
]);
let output = "";
useradd.stdout.on("data", (data) => {
output += `${data}`;
});
useradd.stderr.on("data", (data) => {
output += `${data}`;
});
useradd.on("close", (code) => {
output = output.trim();
if (output) {
log("Output from useradd:\n" + output);
}
if (code === 0) {
log(`Created new user with ID ${uid}`);
nextId! += 1;
resolve(uid);
} else {
reject(`useradd failed with error code ${code}`);
}
});
});
const uid = nextId!;
await callPrivileged(["useradd", `${uid}`], log);
log(`Created new user with ID ${uid}`);
nextId! += 1;
return uid;
}
export async function borrowUser(log: (msg: string) => void) {

83
backend/src/util.ts Normal file
View File

@ -0,0 +1,83 @@
import { spawn, SpawnOptions } from "child_process";
import * as process from "process";
interface Options extends SpawnOptions {
input?: string;
}
export function getEnv(uuid: string) {
const cwd = `/tmp/riju/${uuid}`;
return {
HOME: cwd,
HOSTNAME: "riju",
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8",
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin",
PWD: cwd,
SHELL: "/usr/bin/bash",
TERM: "xterm-color",
};
}
export async function call(
args: string[],
log: (msg: string) => void,
options?: Options
) {
options = options || {};
const input = options.input;
delete options.input;
const proc = spawn(args[0], args.slice(1), options);
if (input) {
proc.stdin!.end(input);
}
let output = "";
proc.stdout!.on("data", (data: Buffer) => {
output += `${data}`;
});
proc.stderr!.on("data", (data: Buffer) => {
output += `${data}`;
});
await new Promise((resolve, reject) => {
proc.on("error", reject);
proc.on("close", (code: number) => {
output = output.trim();
if (output) {
log(`Output from ${args[0]}:\n` + output);
}
if (code === 0) {
resolve();
} else {
reject(`command ${args[0]} failed with error code ${code}`);
}
});
});
}
export async function callPrivileged(
args: string[],
log: (msg: string) => void,
options?: Options
) {
await call(
["/home/docker/src/system/out/riju-system-privileged"].concat(args),
log,
options
);
}
export async function spawnPrivileged(
uid: number,
uuid: string,
args: string[],
log: (msg: string) => void,
options?: Options
) {
options = options || {};
options.env = getEnv(uuid);
await callPrivileged(
["spawn", `${uid}`, `${uuid}`].concat(args),
log,
options
);
}

View File

@ -10,92 +10,98 @@ interface RijuConfig {
template: string;
}
const config: RijuConfig = (window as any).rijuConfig;
async function main() {
const config: RijuConfig = (window as any).rijuConfig;
const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById("terminal")!);
const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById("terminal")!);
fitAddon.fit();
window.addEventListener("resize", () => fitAddon.fit());
fitAddon.fit();
window.addEventListener("resize", () => fitAddon.fit());
term.write("Connecting to server...");
const initialRetryDelayMs = 200;
let retryDelayMs = initialRetryDelayMs;
function tryConnect() {
console.log("Connecting to server...");
socket = new WebSocket(
(document.location.protocol === "http:" ? "ws://" : "wss://") +
document.location.host +
`/api/v1/ws?lang=${encodeURIComponent(config.id)}`
await new Promise((resolve) =>
term.write("Connecting to server...", resolve)
);
socket.addEventListener("open", () => {
console.log("Successfully connected to server");
});
socket.addEventListener("message", (event: MessageEvent) => {
let message: any;
try {
message = JSON.parse(event.data);
} catch (err) {
console.error("Malformed message from server:", event.data);
return;
}
if (message?.event && message?.event !== "error") {
retryDelayMs = initialRetryDelayMs;
}
switch (message?.event) {
case "terminalClear":
term.reset();
const initialRetryDelayMs = 200;
let retryDelayMs = initialRetryDelayMs;
function tryConnect() {
console.log("Connecting to server...");
socket = new WebSocket(
(document.location.protocol === "http:" ? "ws://" : "wss://") +
document.location.host +
`/api/v1/ws?lang=${encodeURIComponent(config.id)}`
);
socket.addEventListener("open", () => {
console.log("Successfully connected to server");
});
socket.addEventListener("message", (event: MessageEvent) => {
let message: any;
try {
message = JSON.parse(event.data);
} catch (err) {
console.error("Malformed message from server:", event.data);
return;
case "terminalOutput":
if (typeof message.output !== "string") {
}
if (message?.event && message?.event !== "error") {
retryDelayMs = initialRetryDelayMs;
}
switch (message?.event) {
case "terminalClear":
term.reset();
return;
case "terminalOutput":
if (typeof message.output !== "string") {
console.error("Unexpected message from server:", message);
return;
}
term.write(message.output);
return;
default:
console.error("Unexpected message from server:", message);
return;
}
term.write(message.output);
return;
default:
console.error("Unexpected message from server:", message);
return;
}
}
});
socket.addEventListener("close", (event: CloseEvent) => {
if (event.wasClean) {
console.log("Connection closed cleanly");
} else {
console.error("Connection died");
}
scheduleConnect();
});
}
function scheduleConnect() {
const delay = retryDelayMs * Math.random();
console.log(`Trying to reconnect in ${Math.floor(delay)}ms`);
setTimeout(tryConnect, delay);
retryDelayMs *= 2;
}
let socket: WebSocket | null = null;
tryConnect();
term.onData(
(data) =>
socket &&
socket.send(JSON.stringify({ event: "terminalInput", input: data }))
);
const editor = monaco.editor.create(document.getElementById("editor")!, {
minimap: { enabled: false },
scrollbar: { verticalScrollbarSize: 0 },
});
socket.addEventListener("close", (event: CloseEvent) => {
if (event.wasClean) {
console.log("Connection closed cleanly");
} else {
console.error("Connection died");
}
scheduleConnect();
window.addEventListener("resize", () => editor.layout());
editor.getModel()!.setValue(config.template);
monaco.editor.setModelLanguage(editor.getModel()!, config.monacoLang);
document.getElementById("runButton")!.addEventListener("click", () => {
socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() }));
});
}
function scheduleConnect() {
const delay = retryDelayMs * Math.random();
console.log(`Trying to reconnect in ${Math.floor(delay)}ms`);
setTimeout(tryConnect, delay);
retryDelayMs *= 2;
}
let socket: WebSocket | null = null;
tryConnect();
term.onData(
(data) =>
socket &&
socket.send(JSON.stringify({ event: "terminalInput", input: data }))
);
const editor = monaco.editor.create(document.getElementById("editor")!, {
minimap: { enabled: false },
scrollbar: { verticalScrollbarSize: 0 },
});
window.addEventListener("resize", () => editor.layout());
editor.getModel()!.setValue(config.template);
monaco.editor.setModelLanguage(editor.getModel()!, config.monacoLang);
document.getElementById("runButton")!.addEventListener("click", () => {
socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() }));
});
main().catch(console.error);

View File

@ -1,6 +1,6 @@
{
"name": "riju",
"version": "0",
"version": "0.0.0",
"license": "MIT",
"private": true,
"dependencies": {
@ -11,6 +11,7 @@
"@types/lodash": "^4.14.155",
"@types/mkdirp": "^1.0.1",
"@types/parse-passwd": "^1.0.0",
"@types/rimraf": "^3.0.0",
"@types/tmp": "^0.2.0",
"@types/uuid": "^8.0.0",
"app-root-path": "^3.0.0",
@ -21,13 +22,12 @@
"express-ws": "^4.0.0",
"file-loader": "^6.0.0",
"lodash": "^4.17.15",
"mkdirp": "^1.0.4",
"monaco-editor": "^0.20.0",
"node-cleanup": "^2.1.2",
"node-pty": "^0.9.0",
"npm-run-all": "^4.1.5",
"parse-passwd": "^1.0.0",
"style-loader": "^1.2.1",
"tmp": "^0.2.1",
"ts-loader": "^7.0.5",
"typescript": "^3.9.5",
"uuid": "^8.1.0",
@ -38,12 +38,14 @@
},
"scripts": {
"backend": "tsc",
"backend-dev": "tsc --watch",
"backend-dev": "tsc --watch --preserveWatchOutput",
"frontend": "webpack --production",
"frontend-dev": "webpack --development --watch",
"server": "scripts/setup.bash && node backend/out/server.js",
"server-dev": "watchexec -w backend/out -r 'scripts/setup.bash && node backend/out/server.js'",
"system": "scripts/compile-system.bash",
"system-dev": "watchexec -w system/src -n scripts/compile-system.bash"
"system-dev": "watchexec -w system/src -n scripts/compile-system.bash",
"build": "run-s backend frontend system",
"dev": "run-p backend-dev frontend-dev system-dev server-dev"
}
}

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
set -o pipefail
set -x
export DEBIAN_FRONTEND=noninteractive
apt-get update
(yes || true) | unminimize
rm -rf /var/lib/apt/lists/*
rm "$0"

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
dpkg --add-architecture i386

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
packages="

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
packages="

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
packages="

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
packages="

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
packages="

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
npm config set unsafe-perm true
PERL_MM_USE_DEFAULT=1 cpan App::cpanminus

View File

@ -2,6 +2,7 @@
set -e
set -o pipefail
set -x
# Needed for project infrastructure
cd /tmp

View File

@ -2,19 +2,22 @@
set -e
set -o pipefail
set -x
uid="$1"
rm -rf /tmp/hsperfdata_root
if [[ -n "$uid" ]] && (( "$uid" != 0 )); then
useradd --uid="$uid" --create-home --groups sudo docker
passwd -d docker
useradd --uid="$uid" --password "!" --create-home --groups sudo docker
else
useradd --create-home --groups sudo docker
passwd -d docker
useradd --password "!" --create-home --groups sudo docker
fi
tee /etc/sudoers.d/99-passwordless >/dev/null <<"EOF"
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
EOF
touch /home/docker/.zshrc
chown docker:docker /home/docker/.zshrc

View File

@ -18,4 +18,4 @@ else
fi
docker run ${it} -e TLS -e TLS_PRIVATE_KEY -e TLS_CERTIFICATE \
--rm -p 0.0.0.0:80:6119 -p 0.0.0.0:443:6120 riju:prod
--rm -p 0.0.0.0:80:6119 -p 0.0.0.0:443:6120 -h riju riju:prod

View File

@ -4,4 +4,7 @@ set -e
set -o pipefail
mkdir -p /tmp/riju
rm -rf /tmp/riju/*
if [[ -x system/out/riju-system-privileged ]]; then
system/out/riju-system-privileged teardown "*"
fi
chmod a=x,u=rwx /tmp/riju

View File

@ -1,14 +1,18 @@
#define _GNU_SOURCE
#include <errno.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// Keep in sync with backend/src/users.ts
const int MIN_UID = 2000;
const int MAX_UID = 65000;
void die(const char *msg)
void die(char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
@ -17,19 +21,84 @@ void die(const char *msg)
void die_with_usage()
{
die("usage:\n"
" riju-system-privileged useradd UID");
" riju-system-privileged useradd UID\n"
" riju-system-privileged spawn UID CMDLINE...\n"
" riju-system-privileged setup UID UUID\n"
" riju-system-privileged teardown UUID");
}
int parseUID(char *str)
{
char *endptr;
long uid = strtol(str, &endptr, 10);
if (!*str || *endptr)
die("uid must be an integer");
if (uid < MIN_UID || uid >= MAX_UID)
die("uid is out of range");
return uid;
}
char *parseUUID(char *uuid)
{
if (!*uuid)
die("illegal uuid");
for (char *ptr = uuid; *ptr; ++ptr)
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') || *ptr == '-'))
die("illegal uuid");
return uuid;
}
void useradd(int uid)
{
char *cmdline;
if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d riju_user%1$d", uid) < 0) {
if (asprintf(&cmdline, "groupadd -g %1$d riju%1$d", uid) < 0)
die("asprintf failed");
}
int status = system(cmdline);
if (status) {
if (status)
die("groupadd failed");
if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d -g %1$d -p '!' riju%1$d", uid) < 0)
die("asprintf failed");
status = system(cmdline);
if (status)
die("useradd failed");
}
}
void spawn(int uid, char *uuid, char **cmdline)
{
char *cwd;
if (asprintf(&cwd, "/tmp/riju/%s", uuid) < 0)
die("asprintf failed");
if (chdir(cwd) < 0)
die("chdir failed");
if (setgid(uid) < 0)
die("setgid failed");
if (setgroups(0, NULL) < 0)
die("setgroups failed");
if (setuid(uid) < 0)
die("setuid failed");
umask(077);
execvp(cmdline[0], cmdline);
die("execvp failed");
}
void setup(int uid, char *uuid)
{
char *cmdline;
if (asprintf(&cmdline, "install -d -o riju%1$d -g riju%1$d -m 700 /tmp/riju/%2$s", uid, uuid) < 0)
die("asprintf failed");
int status = system(cmdline);
if (status)
die("install failed");
}
void teardown(char *uuid)
{
char *cmdline;
if (asprintf(&cmdline, "rm -rf /tmp/riju/%s", uuid) < 0)
die("asprintf failed");
int status = system(cmdline);
if (status)
die("rm failed");
}
int main(int argc, char **argv)
@ -40,15 +109,28 @@ int main(int argc, char **argv)
if (!strcmp(argv[1], "useradd")) {
if (argc != 3)
die_with_usage();
char *endptr;
long uid = strtol(argv[2], &endptr, 10);
if (!argv[2] || *endptr) {
die("uid must be an integer");
}
if (uid < MIN_UID || uid >= MAX_UID) {
die("uid is out of range");
}
useradd(uid);
useradd(parseUID(argv[2]));
return 0;
}
if (!strcmp(argv[1], "spawn")) {
if (argc < 5)
die_with_usage();
spawn(parseUID(argv[2]), parseUUID(argv[3]), &argv[4]);
return 0;
}
if (!strcmp(argv[1], "setup")) {
if (argc != 4)
die_with_usage();
int uid = parseUID(argv[2]);
char *uuid = parseUUID(argv[3]);
setup(uid, uuid);
return 0;
}
if (!strcmp(argv[1], "teardown")) {
if (argc != 3)
die_with_usage();
char *uuid = strcmp(argv[2], "*") ? parseUUID(argv[2]) : "*";
teardown(uuid);
return 0;
}
die_with_usage();

313
yarn.lock
View File

@ -55,6 +55,14 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/glob@*":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987"
integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/json-schema@^7.0.4":
version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
@ -70,6 +78,11 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.2.tgz#857a118d8634c84bba7ae14088e4508490cd5da5"
integrity sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mkdirp@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6"
@ -97,6 +110,14 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/rimraf@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f"
integrity sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ==
dependencies:
"@types/glob" "*"
"@types/node" "*"
"@types/serve-static@*":
version "1.13.4"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.4.tgz#6662a93583e5a6cabca1b23592eb91e12fa80e7c"
@ -663,7 +684,7 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
chalk@2.4.2, chalk@^2.3.0, chalk@^2.4.2:
chalk@2.4.2, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -880,7 +901,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-spawn@6.0.5, cross-spawn@^6.0.0:
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@ -954,6 +975,13 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
dependencies:
object-keys "^1.0.12"
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@ -1100,6 +1128,39 @@ errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
es-abstract@^1.17.0-next.1, es-abstract@^1.17.5:
version "1.17.6"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.0"
is-regex "^1.1.0"
object-inspect "^1.7.0"
object-keys "^1.1.1"
object.assign "^4.1.0"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
dependencies:
is-callable "^1.1.4"
is-date-object "^1.0.1"
is-symbol "^1.0.2"
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@ -1410,6 +1471,11 @@ fsevents@~2.1.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@ -1500,6 +1566,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-symbols@^1.0.0, has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@ -1531,6 +1602,13 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
hash-base@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
@ -1564,6 +1642,11 @@ homedir-polyfill@^1.0.1:
dependencies:
parse-passwd "^1.0.0"
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
@ -1695,6 +1778,11 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-binary-path@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@ -1714,6 +1802,11 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-callable@^1.1.4, is-callable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@ -1728,6 +1821,11 @@ is-data-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-date-object@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@ -1801,11 +1899,25 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
dependencies:
has-symbols "^1.0.1"
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
is-symbol@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
dependencies:
has-symbols "^1.0.1"
is-windows@^1.0.1, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@ -1848,7 +1960,7 @@ jake@^10.6.1:
filelist "^1.0.1"
minimatch "^3.0.4"
json-parse-better-errors@^1.0.2:
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@ -1903,6 +2015,16 @@ lcid@^2.0.0:
dependencies:
invert-kv "^2.0.0"
load-json-file@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
dependencies:
graceful-fs "^4.1.2"
parse-json "^4.0.0"
pify "^3.0.0"
strip-bom "^3.0.0"
loader-runner@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@ -2021,6 +2143,11 @@ memory-fs@^0.5.0:
errno "^0.1.3"
readable-stream "^2.0.1"
memorystream@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@ -2141,11 +2268,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.3:
dependencies:
minimist "^1.2.5"
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
monaco-editor@^0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea"
@ -2251,6 +2373,16 @@ node-pty@^0.9.0:
dependencies:
nan "^2.14.0"
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies:
hosted-git-info "^2.1.4"
resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
@ -2263,6 +2395,21 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
npm-run-all@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
dependencies:
ansi-styles "^3.2.1"
chalk "^2.4.1"
cross-spawn "^6.0.5"
memorystream "^0.3.1"
minimatch "^3.0.4"
pidtree "^0.3.0"
read-pkg "^3.0.0"
shell-quote "^1.6.1"
string.prototype.padend "^3.0.0"
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@ -2284,6 +2431,16 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-inspect@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@ -2291,6 +2448,16 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
dependencies:
define-properties "^1.1.2"
function-bind "^1.1.1"
has-symbols "^1.0.0"
object-keys "^1.0.11"
object.pick@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
@ -2386,6 +2553,14 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@ -2426,11 +2601,23 @@ path-key@^2.0.0, path-key@^2.0.1:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-type@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
dependencies:
pify "^3.0.0"
pbkdf2@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
@ -2447,6 +2634,16 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
pidtree@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
@ -2645,6 +2842,15 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
read-pkg@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
dependencies:
load-json-file "^4.0.0"
normalize-package-data "^2.3.2"
path-type "^3.0.0"
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@ -2741,6 +2947,13 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.10.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
dependencies:
path-parse "^1.0.6"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -2753,13 +2966,6 @@ rimraf@^2.5.4, rimraf@^2.6.3:
dependencies:
glob "^7.1.3"
rimraf@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@ -2815,7 +3021,7 @@ schema-utils@^2.6.5, schema-utils@^2.6.6:
ajv "^6.12.2"
ajv-keywords "^3.4.1"
semver@^5.5.0, semver@^5.6.0:
"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@ -2906,6 +3112,11 @@ shebang-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
shell-quote@^1.6.1:
version "1.7.2"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
signal-exit@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@ -2980,6 +3191,32 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
spdx-expression-parse@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.5"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@ -3048,6 +3285,30 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string.prototype.padend@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3"
integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
string.prototype.trimend@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
string.prototype.trimstart@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@ -3069,6 +3330,11 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@ -3140,13 +3406,6 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tmp@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
dependencies:
rimraf "^3.0.0"
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@ -3334,6 +3593,14 @@ v8-compile-cache@2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"