Public release release-f6c01dca61ee
Source commit: f6c01dca61eefca1a7431c48878a04dbb7a3f710 Public tree identity: sha256:3df7a93eb61c8c5c7841ac9ad6eedec89b167fd77fdd32e9fc31b57c10207387
This commit is contained in:
commit
a2664f4345
210 changed files with 78559 additions and 0 deletions
181
scripts/tenant-isolation-contract-smoke.js
Normal file
181
scripts/tenant-isolation-contract-smoke.js
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#!/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}`);
|
||||
}
|
||||
|
||||
const auth = read("crates/clusterflux-core/src/auth.rs");
|
||||
const artifact = read("crates/clusterflux-core/src/artifact.rs");
|
||||
const operatorPanel = read("crates/clusterflux-core/src/operator_panel.rs");
|
||||
const source = read("crates/clusterflux-core/src/source.rs");
|
||||
const coordinatorService = [
|
||||
read("crates/clusterflux-coordinator/src/service.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/routing.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/nodes.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/keys.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/artifacts.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/processes.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/process_launch.rs"),
|
||||
read("crates/clusterflux-coordinator/src/service/tests.rs"),
|
||||
].join("\n");
|
||||
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");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const hostedLibRoot = maybeRead(["private", "hosted-policy", "src", "lib.rs"]);
|
||||
const hostedServiceSource = maybeRead([
|
||||
"private",
|
||||
"hosted-policy",
|
||||
"src",
|
||||
"bin",
|
||||
"clusterflux-hosted-service.rs",
|
||||
]);
|
||||
const hostedLib = hostedLibRoot;
|
||||
const hostedSmoke = maybeRead([
|
||||
"private",
|
||||
"hosted-policy",
|
||||
"scripts",
|
||||
"hosted-client-compat-smoke.js",
|
||||
]);
|
||||
|
||||
if (hostedLib && hostedServiceSource && hostedSmoke) {
|
||||
for (const [name, pattern] of [
|
||||
["hosted private layer is a compact identity broker", /pub struct HostedIdentityBroker[\s\S]*cli_session_ttl_seconds/],
|
||||
["hosted private layer has no parallel coordinator", /Runtime, persistence, nodes, processes,[\s\S]*remain owned by public CoordinatorService/],
|
||||
["hosted service delegates Client state to Core", /core_coordinator: CoordinatorService/],
|
||||
["hosted login creates project through Core", /issue_cli_session[\s\S]*AuthenticatedCoordinatorRequest::CreateProject/],
|
||||
]) {
|
||||
expect(`${hostedLib}\n${hostedServiceSource}`, name, pattern);
|
||||
}
|
||||
|
||||
for (const forbidden of [
|
||||
"HostedCommunityControlPlane",
|
||||
"HostedObservabilitySnapshot",
|
||||
"agent_public_keys: BTreeMap",
|
||||
"node_statuses: BTreeMap",
|
||||
"process_statuses: BTreeMap",
|
||||
"debug_sessions: BTreeMap",
|
||||
]) {
|
||||
assert(
|
||||
!hostedLib.includes(forbidden),
|
||||
`private hosted policy must not reimplement Core state: ${forbidden}`
|
||||
);
|
||||
}
|
||||
|
||||
for (const [name, pattern] of [
|
||||
["client-supplied identity is denied", /const forged = await sendHostedControl[\s\S]*authenticated CLI session/],
|
||||
["second OIDC subject receives a different tenant", /assert\.notStrictEqual\(victimLogin\.session\.tenant, session\.tenant\)/],
|
||||
["cross-tenant process events are denied", /const crossTenantTaskEventsDenied = await sendHostedControl[\s\S]*vp-victim[\s\S]*scope\|denied\|unauthorized/],
|
||||
]) {
|
||||
expect(hostedSmoke, name, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Tenant isolation contract smoke passed");
|
||||
Loading…
Add table
Add a link
Reference in a new issue