diff --git a/deploy/containers/ql3-local-application/Dockerfile b/deploy/containers/ql3-local-application/Dockerfile index 3c0f99c9..21d77904 100644 --- a/deploy/containers/ql3-local-application/Dockerfile +++ b/deploy/containers/ql3-local-application/Dockerfile @@ -123,6 +123,8 @@ COPY --from=workspace /workspace/packages/ql3-local-process/package.json \ node_modules/@qinglong/local-process/package.json COPY --from=workspace /workspace/packages/ql3-local-process/dist \ node_modules/@qinglong/local-process/dist +COPY --from=workspace /workspace/packages/ql3-local-process/assets \ + node_modules/@qinglong/local-process/assets COPY --from=workspace /workspace/packages/ql3-local-secret/package.json \ node_modules/@qinglong/local-secret/package.json COPY --from=workspace /workspace/packages/ql3-local-secret/dist \ @@ -132,6 +134,8 @@ COPY --from=workspace /workspace/packages/ql3-local-sqlite/package.json \ COPY --from=workspace /workspace/packages/ql3-local-sqlite/dist \ node_modules/@qinglong/local-sqlite/dist +RUN chmod 0555 node_modules/@qinglong/local-process/assets/ql3-launcher.sh + RUN rm -rf node_modules/.bin \ && node /tmp/ql3-prune-runtime-artifact.cjs node_modules/@qinglong \ @qinglong/local-application \ @@ -190,6 +194,8 @@ COPY --from=workspace /workspace/packages/ql3-local-process/package.json \ node_modules/@qinglong/local-process/package.json COPY --from=workspace /workspace/packages/ql3-local-process/dist \ node_modules/@qinglong/local-process/dist +COPY --from=workspace /workspace/packages/ql3-local-process/assets \ + node_modules/@qinglong/local-process/assets COPY --from=workspace /workspace/packages/ql3-local-secret/package.json \ node_modules/@qinglong/local-secret/package.json COPY --from=workspace /workspace/packages/ql3-local-secret/dist \ @@ -199,6 +205,8 @@ COPY --from=workspace /workspace/packages/ql3-local-sqlite/package.json \ COPY --from=workspace /workspace/packages/ql3-local-sqlite/dist \ node_modules/@qinglong/local-sqlite/dist +RUN chmod 0555 node_modules/@qinglong/local-process/assets/ql3-launcher.sh + RUN rm -rf node_modules/.bin \ && node /tmp/ql3-prune-runtime-artifact.cjs node_modules/@qinglong \ @qinglong/local-api/config \ diff --git a/scripts/ql3-local-image-audit.cjs b/scripts/ql3-local-image-audit.cjs index b2d5afc0..f74506d0 100644 --- a/scripts/ql3-local-image-audit.cjs +++ b/scripts/ql3-local-image-audit.cjs @@ -266,6 +266,13 @@ function auditDockerfile(contents, findings) { sortedObject( Object.fromEntries(RUNTIME_PACKAGES.map((name) => [name, 2])), ), + ) || + !assembledStage.includes( + 'COPY --from=workspace /workspace/packages/ql3-local-process/assets \\\n' + + ' node_modules/@qinglong/local-process/assets', + ) || + !assembledStage.includes( + 'RUN chmod 0555 node_modules/@qinglong/local-process/assets/ql3-launcher.sh', ) ) { addFinding(findings, 'RUNTIME_INTERNAL_PACKAGE_CLOSURE_DRIFT'); @@ -280,14 +287,19 @@ function auditDockerfile(contents, findings) { !sameJson( sortedObject(consoleRuntimeCopyCounts), sortedObject( - Object.fromEntries( - CONSOLE_RUNTIME_PACKAGES.map((name) => [name, 2]), - ), + Object.fromEntries(CONSOLE_RUNTIME_PACKAGES.map((name) => [name, 2])), ), ) || !consoleAssembledStage.includes( 'COPY --from=workspace /workspace/packages/ql3-local-api/assets \\\n' + ' node_modules/@qinglong/local-api/assets', + ) || + !consoleAssembledStage.includes( + 'COPY --from=workspace /workspace/packages/ql3-local-process/assets \\\n' + + ' node_modules/@qinglong/local-process/assets', + ) || + !consoleAssembledStage.includes( + 'RUN chmod 0555 node_modules/@qinglong/local-process/assets/ql3-launcher.sh', ) ) { addFinding(findings, 'CONSOLE_RUNTIME_INTERNAL_PACKAGE_CLOSURE_DRIFT'); @@ -448,9 +460,7 @@ function auditLocalImageContract(root) { ), consoleRuntimePackages: Object.freeze( [ - ...CONSOLE_RUNTIME_PACKAGES.map( - (name) => `@qinglong/${name.slice(4)}`, - ), + ...CONSOLE_RUNTIME_PACKAGES.map((name) => `@qinglong/${name.slice(4)}`), ...Object.keys(RUNTIME_DEPENDENCIES), ].sort(), ), diff --git a/test/back/ql3LocalImageAudit.test.cjs b/test/back/ql3LocalImageAudit.test.cjs index 02071670..8c9a433b 100644 --- a/test/back/ql3LocalImageAudit.test.cjs +++ b/test/back/ql3LocalImageAudit.test.cjs @@ -89,19 +89,47 @@ test('rejects Console assets, loopback identity or retained JavaScript drift', ( '--retain-js=local-api/assets/console/console.js', '--retain-js=local-api/assets/console/unreviewed.js', ) - .replace('io.qinglong.local.console="offline-loopback"', 'io.qinglong.local.console="public"'); + .replace( + 'io.qinglong.local.console="offline-loopback"', + 'io.qinglong.local.console="public"', + ); + fs.writeFileSync(dockerfilePath, dockerfile); + const report = auditLocalImageContract(current.root); + assert.equal(report.compatible, false); + assert.ok( + report.findings.some( + ({ code }) => code === 'CONSOLE_RUNTIME_NONESSENTIAL_FILES_NOT_REMOVED', + ), + ); + assert.ok( + report.findings.some( + ({ code }) => code === 'CONSOLE_RUNTIME_IDENTITY_OR_LABEL_DRIFT', + ), + ); + } finally { + current.close(); + } +}); + +test('rejects a Local runtime without the verified process launcher asset', () => { + const current = fixture(); + try { + const dockerfilePath = path.join(current.target, 'Dockerfile'); + const dockerfile = fs + .readFileSync(dockerfilePath, 'utf8') + .replaceAll( + 'COPY --from=workspace /workspace/packages/ql3-local-process/assets \\\n' + + ' node_modules/@qinglong/local-process/assets\n', + '', + ); fs.writeFileSync(dockerfilePath, dockerfile); const report = auditLocalImageContract(current.root); assert.equal(report.compatible, false); assert.ok( report.findings.some( ({ code }) => - code === 'CONSOLE_RUNTIME_NONESSENTIAL_FILES_NOT_REMOVED', - ), - ); - assert.ok( - report.findings.some( - ({ code }) => code === 'CONSOLE_RUNTIME_IDENTITY_OR_LABEL_DRIFT', + code === 'RUNTIME_INTERNAL_PACKAGE_CLOSURE_DRIFT' || + code === 'CONSOLE_RUNTIME_INTERNAL_PACKAGE_CLOSURE_DRIFT', ), ); } finally {