"$data/account/$env/account.db", "account's signing key" => "$data/account/$env/assert-key.pem", "account's code" => "$apps/account/$env/public/index.php", "account's secret for account" => dirname(env("RC_SECRETS"), 2) . "/account/$env/account", "account's facts" => dirname(env("RC_FACTS")) . "/account/deploy.json", "the platform's secrets" => "/etc/ratcloud/secrets", // a fixed platform path: the door, not a setting "account's repository" => "/opt/account.git/config", ]; $connects = [ "the server's own web server (127.0.0.1:80)" => ["127.0.0.1", 80], "the cloud metadata service (169.254.169.254:80)" => ["169.254.169.254", 80], "the private network (10.0.0.1:80)" => ["10.0.0.1", 80], ]; $out = []; foreach ($reads as $what => $path) { $php = @file_get_contents($path, false, null, 0, 1) !== false; $p = proc_open(["/usr/bin/head", "-c", "1", "--", $path], [1 => ["pipe", "w"], 2 => ["pipe", "w"]], $pipes); stream_get_contents($pipes[1]); $err = trim(stream_get_contents($pipes[2])); $rc = proc_close($p); $why = preg_replace('#^.*: #', "", $err) ?: "read"; $state = $php || $rc === 0 ? "opened" : (str_contains($err, "Permission denied") ? "refused" : "missing"); if (str_contains($err, "Is a directory")) { // a directory: try to list it instead $p = proc_open(["/usr/bin/ls", "--", $path], [1 => ["pipe", "w"], 2 => ["pipe", "w"]], $pipes); stream_get_contents($pipes[1]); $err = trim(stream_get_contents($pipes[2])); $rc = proc_close($p); $why = preg_replace('#^.*: #', "", $err) ?: "listed"; $state = $rc === 0 ? "opened" : (str_contains($err, "Permission denied") ? "refused" : "missing"); } $out[] = ["door" => "read $what", "target" => $path, "result" => $state, "why" => "as uid " . posix_getpwuid(posix_geteuid())["name"] . ": $why"]; } foreach ($connects as $what => [$ip, $port]) { $s = @fsockopen($ip, $port, $no, $err, 2); if ($s) fclose($s); $out[] = ["door" => "connect to $what", "target" => "$ip:$port", "result" => $s ? "opened" : "refused", "why" => $s ? "connected" : ($err ?: "error $no")]; } return $out; } // The control: the same tries on doors that should open. If these fail, so would everything. function controls(): array { $u = parse_url(env("RC_PUBLISH")); $s = @fsockopen($u["host"], $u["port"], $no, $err, 2); if ($s) fclose($s); $p = proc_open(["/usr/bin/head", "-c", "1", "--", env("RC_DATA") . "/zoo.db"], [1 => ["pipe", "w"], 2 => ["pipe", "w"]], $pipes); stream_get_contents($pipes[1]); stream_get_contents($pipes[2]); return ["own data" => proc_close($p) === 0, "own publish port" => (bool)$s]; } return [ "n" => 9, "wing" => "Isolation", "title" => "Try the doors", "promise" => "An app cannot open another app's files, and it cannot reach the server's own internal network.", "block" => "Every app runs as its own unix user in a sandbox, behind a firewall. RC_DATA is the one place it can write; everything else on the box is someone else's.", "show" => function (?array $me): string { $root = ""; foreach ((fact("platform.json")["check"]["checks"] ?? []) as $c) { if (str_starts_with($c["line"], "(exhibit 9)") || str_starts_with($c["line"], "An app cannot read")) $root .= "
  • " . h($c["status"]) . "" . h($c["detail"]) . "
  • "; } return '

    ' . ($root ? '

    Root tries them too, as every app, every hour:

    " : ""); }, "api" => function (string $do, ?array $me, array $in, bool $post): ?array { if ($do !== "try" || !$post) return null; return ["doors" => doors(), "controls" => controls()]; }, "check" => function (): array { $d = doors(); $bad = array_filter($d, fn($x) => $x["result"] !== "refused"); $c = controls(); if ($bad) return [false, implode("; ", array_map(fn($x) => "{$x['door']}: {$x['result']}", $bad)), $d]; if (in_array(false, $c, true)) return [false, "the control failed: the zoo could not open its own " . implode(", ", array_keys(array_filter($c, fn($v) => !$v))), $d]; return [true, count($d) . " doors tried, all refused; its own data and publish port open", $d]; }, "script" => <<<'JS' document.getElementById("doors-go")?.addEventListener("click", async (ev) => { ev.target.disabled = true; ev.target.textContent = "trying…"; const j = await zoo.call("/api/9/try", {}); ev.target.disabled = false; ev.target.textContent = "Try again"; if (j.error) { document.getElementById("doors").textContent = j.error; return; } document.getElementById("doors").innerHTML = j.doors.map(d => '
  • ' + d.result + '' + zoo.esc(d.door) + ' ' + zoo.esc(d.why) + '
  • ').join(""); document.getElementById("doors-ctl").textContent = "Control, the zoo's own doors: " + Object.entries(j.controls).map(([k, v]) => k + " " + (v ? "opened" : "FAILED")).join(", ") + "."; }); JS, ];