Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@ COPY --from=build /src/container/progress.mjs ./container/progress.mjs
RUN mkdir -p /app/release \
&& chmod 0555 /app/runtime/wasm-oj-compiler /app/runtime/wasm-oj-runner \
&& chmod 0444 /app/container/server.mjs /app/container/identity.mjs /app/container/generate-identity.mjs /app/container/tree-digest.mjs /app/container/submission-result.mjs /app/container/progress.mjs \
&& chmod -R a+rX /app \
&& chmod -R a-w /app \
&& chmod u+w /app/release \
&& WASM_OJ_RELEASE_ID="$WASM_OJ_RELEASE_ID" \
WASM_OJ_GIT_COMMIT="$WASM_OJ_GIT_COMMIT" \
node /app/container/generate-identity.mjs \
&& chmod a-w /app/release /app/release/container-identity.json \
&& node -e "import('/app/container/identity.mjs').then(async m => { await m.loadEmbeddedContainerIdentity(); })" \
&& runuser -u wasmoj -- node --input-type=module -e "const dependencies = ['@wasm-oj/core', '@wasm-oj/server', '@wasm-oj/toolchain-clang', '@wasm-oj/toolchain-go', '@wasm-oj/toolchain-javascript', '@wasm-oj/toolchain-python', '@wasm-oj/toolchain-rust']; await Promise.all(dependencies.map((dependency) => import(dependency))); const { loadEmbeddedContainerIdentity } = await import('/app/container/identity.mjs'); await loadEmbeddedContainerIdentity();" \
&& runuser -u wasmoj -- node -e "const fs=require('node:fs/promises');const targets=['/app/release/container-identity.json','/app/runtime/wasm-oj-compiler','/app/runtime/wasm-oj-runner'];Promise.all(targets.flatMap((p)=>[fs.access(p,2).then(()=>{throw new Error('wasmoj can write '+p)},()=>{}),fs.unlink(p).then(()=>{throw new Error('wasmoj can delete '+p)},(error)=>{if(!['EACCES','EPERM'].includes(error.code))throw error})])).catch((error)=>{console.error(error);process.exit(1)})"
USER wasmoj
ENV NODE_ENV=production
Expand Down
28 changes: 28 additions & 0 deletions scripts/container-build-context.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,31 @@ test("judge Dockerfile fails closed if generated state still enters the context"
assert.match(audit, /-name '\.wasm-oj-build-\*'/u);
assert.match(audit, /-lname '\*\/tmp\/\*'/u);
});

test("judge image exposes its immutable runtime graph to wasmoj before committing the image", async () => {
const dockerfile = await readFile(new URL("../Dockerfile", import.meta.url), "utf8");
const grantReadIndex = dockerfile.indexOf("chmod -R a+rX /app");
const removeWriteIndex = dockerfile.indexOf("chmod -R a-w /app");
const runtimeSmokeIndex = dockerfile.indexOf("runuser -u wasmoj -- node --input-type=module");
const finalUserIndex = dockerfile.indexOf("USER wasmoj");

assert.ok(grantReadIndex >= 0, "the copied runtime tree must be readable and traversable by wasmoj");
assert.ok(removeWriteIndex > grantReadIndex, "read/traverse repair must happen before the immutable write fence");
assert.ok(runtimeSmokeIndex > removeWriteIndex, "runtime imports must execute after permission hardening");
assert.ok(finalUserIndex > runtimeSmokeIndex, "runtime imports must execute during the image build");

const runtimeSmoke = dockerfile.slice(runtimeSmokeIndex, finalUserIndex);
for (const packageName of [
"@wasm-oj/core",
"@wasm-oj/server",
"@wasm-oj/toolchain-clang",
"@wasm-oj/toolchain-go",
"@wasm-oj/toolchain-javascript",
"@wasm-oj/toolchain-python",
"@wasm-oj/toolchain-rust",
]) {
assert.ok(runtimeSmoke.includes(`'${packageName}'`), `${packageName} must be imported as wasmoj`);
}
assert.match(runtimeSmoke, /loadEmbeddedContainerIdentity\(\)/u);
assert.doesNotMatch(runtimeSmoke, /container\/server\.mjs/u);
});