Source commit: 750f29790fe2046599fbe5b5d06fdb63d92fabff Public tree identity: sha256:02326e08850bb287585789733ea5f3f153e1f7f3fd88afcc24e249107df91506
179 lines
9.6 KiB
JavaScript
179 lines
9.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const repo = path.resolve(__dirname, "..");
|
|
|
|
function read(relativePath) {
|
|
return fs.readFileSync(path.join(repo, relativePath), "utf8");
|
|
}
|
|
|
|
function maybeRead(segments) {
|
|
const fullPath = path.join(repo, ...segments);
|
|
if (!fs.existsSync(fullPath)) return null;
|
|
return fs.readFileSync(fullPath, "utf8");
|
|
}
|
|
|
|
function expect(source, name, pattern) {
|
|
assert.match(source, pattern, `missing tenant-isolation evidence: ${name}`);
|
|
}
|
|
|
|
function expectGate(script, gateName) {
|
|
assert(
|
|
script.includes("node scripts/tenant-isolation-contract-smoke.js"),
|
|
`${gateName} must run tenant-isolation-contract-smoke.js`
|
|
);
|
|
}
|
|
|
|
const auth = read("crates/disasmer-core/src/auth.rs");
|
|
const artifact = read("crates/disasmer-core/src/artifact.rs");
|
|
const operatorPanel = read("crates/disasmer-core/src/operator_panel.rs");
|
|
const source = read("crates/disasmer-core/src/source.rs");
|
|
const coordinatorService = read("crates/disasmer-coordinator/src/service.rs");
|
|
const artifactDownloadSmoke = read("scripts/artifact-download-smoke.js");
|
|
const operatorPanelSmoke = read("scripts/operator-panel-smoke.js");
|
|
const schedulerSmoke = read("scripts/scheduler-placement-smoke.js");
|
|
const sourcePreparationSmoke = read("scripts/source-preparation-smoke.js");
|
|
const releaseBlockerSmoke = read("scripts/release-blocker-smoke.js");
|
|
const publicAcceptance = read("scripts/acceptance-public.sh");
|
|
const publicSplit = read("scripts/verify-public-split.sh");
|
|
const privateAcceptance = read("scripts/acceptance-private.sh");
|
|
|
|
for (const [name, pattern] of [
|
|
["auth contexts carry tenant and project", /pub struct AuthContext[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["enrollment grants carry tenant and project", /pub struct EnrollmentGrant[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["node credentials carry tenant and project", /pub struct NodeCredential[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["same tenant/project denies tenant mismatch", /pub fn same_tenant_project[\s\S]*tenant mismatch/],
|
|
["same tenant/project denies project mismatch", /pub fn same_tenant_project[\s\S]*project mismatch/],
|
|
["auth unit denies cross-tenant access", /fn tenant_project_scope_denies_cross_tenant_access\(\)/],
|
|
]) {
|
|
expect(auth, name, pattern);
|
|
}
|
|
|
|
for (const [name, pattern] of [
|
|
["artifact metadata carries tenant and project", /pub struct ArtifactMetadata[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["download links carry tenant project process and actor", /pub struct DownloadLink[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId[\s\S]*pub process: ProcessId[\s\S]*pub actor: Actor/],
|
|
["downloads authorize against scoped metadata", /let authz = same_tenant_project\(context, &scope\)/],
|
|
["artifact test denies cross-tenant download", /fn cross_tenant_download_is_denied_even_with_known_artifact_id\(\)/],
|
|
["artifact test denies cross-project download", /fn cross_project_download_is_denied_even_with_known_artifact_id\(\)/],
|
|
]) {
|
|
expect(artifact, name, pattern);
|
|
}
|
|
|
|
for (const [name, pattern] of [
|
|
["panel state carries tenant project and process", /pub struct PanelState[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId[\s\S]*pub process: ProcessId/],
|
|
["panel events carry tenant project and process", /pub struct PanelEvent[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId[\s\S]*pub process: ProcessId/],
|
|
["panel events reject scope mismatch", /PanelError::ScopeMismatch/],
|
|
["panel scope validation compares tenant project process", /self\.tenant != event\.tenant[\s\S]*self\.project != event\.project[\s\S]*self\.process != event\.process/],
|
|
]) {
|
|
expect(operatorPanel, name, pattern);
|
|
}
|
|
|
|
expect(
|
|
source,
|
|
"source preparation carries tenant and project",
|
|
/pub struct SourcePreparation[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/
|
|
);
|
|
|
|
for (const [name, pattern] of [
|
|
["project creation rejects foreign tenant reuse", /project id is outside the signed-in tenant scope/],
|
|
["project selection rejects foreign tenant", /project is outside the signed-in tenant scope/],
|
|
["project listing uses tenant-scoped context", /CoordinatorRequest::ListProjects[\s\S]*list_projects\(&context\)/],
|
|
["node capability reports check enrollment tenant scope", /node capability report is outside the enrolled tenant\/project scope/],
|
|
["operator panel stop state is tenant/project/process keyed", /type PanelStopKey = \(TenantId, ProjectId, ProcessId\)/],
|
|
["download service tests tenant mismatch", /cross_tenant\.to_string\(\)\.contains\("tenant mismatch"\)/],
|
|
["download service tests project mismatch", /cross_project\.to_string\(\)\.contains\("project mismatch"\)/],
|
|
["node capability test rejects cross-scope report", /fn service_rejects_node_capability_report_outside_enrollment_scope\(\)/],
|
|
["source preparation test rejects cross-scope completion", /fn service_rejects_source_preparation_completion_outside_node_scope\(\)/],
|
|
]) {
|
|
expect(coordinatorService, name, pattern);
|
|
}
|
|
|
|
for (const [name, sourceText, patterns] of [
|
|
[
|
|
"artifact download smoke",
|
|
artifactDownloadSmoke,
|
|
[
|
|
/const crossTenant = await send/,
|
|
/const crossProject = await send/,
|
|
/const crossTenantOpen = await send/,
|
|
/const crossProjectOpen = await send/,
|
|
/tenant mismatch/,
|
|
/project mismatch/,
|
|
/token is invalid/,
|
|
],
|
|
],
|
|
[
|
|
"operator panel smoke",
|
|
operatorPanelSmoke,
|
|
[/const crossTenant = await send/, /render_operator_panel/, /scope\|tenant\|project/],
|
|
],
|
|
[
|
|
"scheduler and capability smoke",
|
|
schedulerSmoke,
|
|
[
|
|
/const crossScopeInspection = await send/,
|
|
/assert\.strictEqual\(crossScopeInspection\.descriptors\.length, 0\)/,
|
|
/const crossTenantReport = await send/,
|
|
/tenant\\\/project scope/,
|
|
],
|
|
],
|
|
[
|
|
"source preparation smoke",
|
|
sourcePreparationSmoke,
|
|
[/const crossTenantCompletion = await send/, /complete_source_preparation/, /tenant\\\/project scope/i],
|
|
],
|
|
]) {
|
|
for (const pattern of patterns) {
|
|
expect(sourceText, name, pattern);
|
|
}
|
|
}
|
|
|
|
expect(releaseBlockerSmoke, "release blockers list tenant-isolation failure", /cross-tenant access is listed as a release blocker/);
|
|
expectGate(publicAcceptance, "public acceptance");
|
|
expectGate(publicSplit, "public split acceptance");
|
|
expectGate(privateAcceptance, "private acceptance");
|
|
|
|
const hostedLib = maybeRead(["private", "hosted-policy", "src", "lib.rs"]);
|
|
const hostedSmoke = maybeRead([
|
|
"private",
|
|
"hosted-policy",
|
|
"scripts",
|
|
"hosted-community-smoke.js",
|
|
]);
|
|
|
|
if (hostedLib && hostedSmoke) {
|
|
for (const [name, pattern] of [
|
|
["hosted agent keys are tenant/project keyed", /agent_public_keys: BTreeMap<\(TenantId, ProjectId, AgentId\), AgentPublicKeyRecord>/],
|
|
["hosted agent records carry tenant and project", /pub struct AgentPublicKeyRecord[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["hosted node statuses carry tenant and project", /pub struct HostedNodeStatus[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["hosted process statuses carry tenant and project", /pub struct HostedProcessStatus[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["hosted log entries carry tenant and project", /pub struct HostedLogEntry[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["hosted debug statuses carry tenant and project", /pub struct HostedDebugSessionStatus[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["hosted observability snapshots carry tenant and project", /pub struct HostedObservabilitySnapshot[\s\S]*pub tenant: TenantId[\s\S]*pub project: ProjectId/],
|
|
["hosted observability filters nodes by tenant/project", /node_statuses[\s\S]*filter\(\|status\| status\.tenant == context\.tenant && status\.project == context\.project\)/],
|
|
["hosted observability filters logs by tenant/project", /log_entries[\s\S]*filter\(\|entry\| entry\.tenant == context\.tenant && entry\.project == context\.project\)/],
|
|
["hosted observability filters artifacts by tenant/project", /metadata\.tenant == context\.tenant && metadata\.project == context\.project/],
|
|
["hosted observability filters debug by tenant/project", /debug_sessions[\s\S]*filter\(\|status\| status\.tenant == context\.tenant && status\.project == context\.project\)/],
|
|
["hosted artifact metadata inspection authorizes scoped metadata", /artifact_metadata_for_context[\s\S]*self\.authorize\(context, &scope, Action::Inspect/],
|
|
["hosted scheduling rejects foreign nodes", /hosted scheduling targets only authorized user-attached project nodes/],
|
|
]) {
|
|
expect(hostedLib, name, pattern);
|
|
}
|
|
|
|
for (const [name, pattern] of [
|
|
["foreign agent list is empty", /const foreignAgentList = await send[\s\S]*records, \[\]/],
|
|
["foreign start is denied", /const foreignStart = await send[\s\S]*authorized user-attached project nodes\|tenant mismatch/],
|
|
["cross-project debug is denied", /const crossProjectDebug = await send[\s\S]*tenant\|project\|debug/],
|
|
["cross-actor debug is denied", /const crossActorDebug = await send[\s\S]*debug\|permission\|actor\|user/],
|
|
["cross-tenant metadata is denied", /const crossTenantMetadata = await send[\s\S]*tenant mismatch/],
|
|
["foreign snapshot reveals no objects", /foreignSnapshot[\s\S]*snapshot\.nodes, \[\][\s\S]*snapshot\.processes, \[\][\s\S]*snapshot\.logs, \[\][\s\S]*snapshot\.artifacts, \[\][\s\S]*snapshot\.debug_sessions, \[\]/],
|
|
["cross-tenant download is denied", /const crossTenantDownload = await send[\s\S]*tenant mismatch/],
|
|
]) {
|
|
expect(hostedSmoke, name, pattern);
|
|
}
|
|
}
|
|
|
|
console.log("Tenant isolation contract smoke passed");
|