From bc33fd38cb5082ad5235de8968f985cc72c2a4ff Mon Sep 17 00:00:00 2001 From: Nulo Date: Thu, 20 Jul 2023 15:23:52 -0300 Subject: [PATCH] WIP --- server/.gitignore | 3 ++ server/index.js | 115 ++++++++++++++++++++++++++++++++++------- server/package.json | 6 ++- server/pnpm-lock.yaml | 22 ++++++++ server/tar2squashfs.js | 2 + 5 files changed, 127 insertions(+), 21 deletions(-) diff --git a/server/.gitignore b/server/.gitignore index 4c2386f..909392e 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -3,3 +3,6 @@ container-baby.js # cache, will move later cache/ + +# jailer +jailer/ \ No newline at end of file diff --git a/server/index.js b/server/index.js index 71c875e..f7c8983 100644 --- a/server/index.js +++ b/server/index.js @@ -1,11 +1,13 @@ import { delay } from "nanodelay"; -import { nanoid } from "nanoid"; +import { customAlphabet as nanoidCustomAlphabet, nanoid } from "nanoid"; +import which from "which"; import { spawn, execFile as _execFile } from "node:child_process"; import { createServer, request as _request } from "node:http"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { basename, join } from "node:path"; import { downloadImage, parseImageRef } from "./container-baby.js"; import { promisify } from "node:util"; +import { access, chmod, chown, link, mkdir } from "node:fs/promises"; const execFile = promisify(_execFile); const server = createServer(listener); @@ -55,52 +57,115 @@ async function runVm(image) { * @prop {boolean} is_read_only */ +/** + * gets absolute path to firecracker executable + * @returns {Promise} + */ +async function getFirecrackerExe() { + const firecrackerExe = await which("firecracker", { nothrow: true }); + console.debug( + `[fireactions] Using firecracker executable '${firecrackerExe}'` + ); + return firecrackerExe; +} + +const jailerDir = "./jailer"; +const [jailerUid, jailerGid] = [65534, 65534]; + +// we need to use a custom alphabet because jailer is picky +const jailerNanoid = nanoidCustomAlphabet( + "0123456789abcdefghijklmnopqrstuvwxyz-", + 64 +); + class FirecrackerInstance { - proc; + vmid; socketPath; - constructor() { - const vmid = nanoid(); - this.socketPath = join(tmpdir(), `firecracker-${vmid}.sock`); + proc; + /** + * @param {{ firecrackerExe: string }} param0 + * @private + */ + constructor({ firecrackerExe }) { + this.vmid = jailerNanoid(); + this.socketPath = join(this.chrootPath, `firecracker.sock`); console.debug(this.socketPath); - // TODO: jailer this.proc = spawn( - "firecracker", + "jailer", [ - ...["--api-sock", this.socketPath], - ...["--level", "Debug"], - ...["--log-path", "/dev/stderr"], - "--show-level", - "--show-log-origin", + ...["--id", this.vmid], + ...["--exec-file", firecrackerExe], + ...["--uid", "" + jailerUid], + ...["--gid", "" + jailerGid], + ...["--chroot-base-dir", jailerDir], + "--", + ...["--api-sock", "/firecracker.sock"], + // ...["--level", "Debug"], + // ...["--log-path", "/stderr.log"], + // "--show-level", + // "--show-log-origin", ], { stdio: "inherit", } ); + + console.debug( + `[fireactions] Started firecracker with jailer at ${this.chrootPath}` + ); } + + get chrootPath() { + return join(jailerDir, "firecracker", this.vmid, "root"); + } + + /** + * @param {string} path path to file to hardlink to root of chroot + * @param {number} mod mod inside the chroot + */ + async hardlinkToChroot(path, mod = 0o600) { + const chrootPath = join(this.chrootPath, basename(path)); + let accesed = false; + try { + await access(chrootPath); + accesed = true; + } catch {} + if (accesed) + throw new Error("file with same name already exists inside chroot"); + await link(path, chrootPath); + await chown(chrootPath, jailerUid, jailerGid); + await chmod(chrootPath, mod); + } + /** * @param [opts] {{ drives?: Drive[] }} */ static async run(opts) { - const self = new FirecrackerInstance(); + await mkdir(jailerDir, { recursive: true }); + const self = new FirecrackerInstance({ + firecrackerExe: await getFirecrackerExe(), + }); + await mkdir(self.chrootPath, { recursive: true }); // TODO: retry until success await delay(15); const { ifname, hostAddr, guestAddr } = await createNetworkInterface(); + await self.hardlinkToChroot("../vmlinux.bin", 0o400); await self.request("PUT", "/boot-source", { - kernel_image_path: "../vmlinux.bin", + kernel_image_path: "/vmlinux.bin", boot_args: `console=ttyS0 reboot=k panic=1 pci=off fireactions_ip=${guestAddr}:${hostAddr}`, }); - await self.request("PUT", "/drives/rootfs", { + // TODO: readonly + self.attachDrive({ drive_id: "rootfs", - path_on_host: "../rootfs.ext4", + path_on_host: "/rootfs.ext4", is_root_device: true, - // TODO: readonly is_read_only: false, }); if (opts?.drives) { for (const drive of opts.drives) { - await self.request("PUT", "/drives/" + drive.drive_id, drive); + await self.attachDrive(drive); } } await self.request("PUT", "/network-interfaces/eth0", { @@ -119,6 +184,18 @@ class FirecrackerInstance { }); } + /** + * @param {Drive} drive + */ + async attachDrive(drive) { + const perms = drive.is_read_only ? 0o400 : 0o600; + await this.hardlinkToChroot(drive.path_on_host, perms); + await this.request("PUT", "/drives/" + drive.drive_id, { + ...drive, + path_on_host: "/" + basename(drive.path_on_host), + }); + } + /** * @param {string} method * @param {string} path diff --git a/server/package.json b/server/package.json index 3d6449e..2def3f6 100644 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,7 @@ "description": "", "main": "index.js", "scripts": { - "start": "esbuild --bundle index.js --platform=node --sourcemap > build.cjs && sudo node --enable-source-maps build.cjs" + "start": "esbuild --bundle index.js --platform=node --sourcemap > build.cjs && sudo env PATH=\"$PATH:$HOME/.local/bin\" node --enable-source-maps build.cjs" }, "keywords": [], "author": "", @@ -15,12 +15,14 @@ "@types/gunzip-maybe": "^1.4.0", "@types/node": "^20.2.5", "@types/tar-stream": "^2.2.2", + "@types/which": "^3.0.0", "undici": "^5.22.1" }, "dependencies": { "gunzip-maybe": "^1.4.2", "nanodelay": "^2.0.2", "nanoid": "^4.0.2", - "tar-stream": "^3.0.0" + "tar-stream": "^3.0.0", + "which": "^3.0.1" } } diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 3a21b22..af03f54 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: tar-stream: specifier: ^3.0.0 version: 3.0.0 + which: + specifier: ^3.0.1 + version: 3.0.1 devDependencies: '@tsconfig/node16': @@ -31,6 +34,9 @@ devDependencies: '@types/tar-stream': specifier: ^2.2.2 version: 2.2.2 + '@types/which': + specifier: ^3.0.0 + version: 3.0.0 undici: specifier: ^5.22.1 version: 5.22.1 @@ -57,6 +63,10 @@ packages: '@types/node': 20.2.5 dev: true + /@types/which@3.0.0: + resolution: {integrity: sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==} + dev: true + /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -170,6 +180,10 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: false + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: false + /nanodelay@2.0.2: resolution: {integrity: sha512-6AS5aCSXsjoxq2Jr9CdaAeT60yoYDOTp6po9ziqeOeY6vf6uTEHYSqWql6EFILrM3fEfXgkZ4KqE9L0rTm/wlA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -301,6 +315,14 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false + /which@3.0.1: + resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: false + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: false diff --git a/server/tar2squashfs.js b/server/tar2squashfs.js index bff7a7f..1537bec 100644 --- a/server/tar2squashfs.js +++ b/server/tar2squashfs.js @@ -2,6 +2,8 @@ // TODO: sandboxear mkquashfs // TODO: fijarse como hace firecracker-containerd // TODO: fijarse como hace [ignite](https://github.com/weaveworks/ignite) +// TODO: fijarse como hace [thi](https://github.com/thi-startup/init) +// TODO: fijarse como hace [firebuild](https://combust-labs.github.io/firebuild-docs/) import gunzip from "gunzip-maybe"; import { spawn } from "node:child_process";