Compare commits

...

2 Commits

Author SHA1 Message Date
Timothy J. Aveni a6dea1309c Bump runtimes to strict auth helper 2026-07-12 15:14:10 -07:00
Timothy J. Aveni b0c376bd95 Tighten Camino runtime auth enforcement 2026-07-12 15:12:26 -07:00
14 changed files with 101 additions and 22 deletions
+15 -1
View File
@@ -1223,6 +1223,14 @@ helpers attach those tokens when calling Camino. Camino verifies the token with
shared secret or future stronger mechanism and derives package provenance from
the signed claims.
V0 implementation uses `CAMINO_RUNTIME_AUTH_SECRET` as the shared signing
secret. The dev start scripts for Camino and orch create/read the same local
secret under `quixos-instance/.var` when the env var is not already set. Orch
mints `CAMINO_RUNTIME_AUTH_TOKEN` for spawned package processes, and the
TypeScript package runtime helper attaches it to Camino RPCs. If orch has a
secret, spawned package processes also receive `CAMINO_RUNTIME_AUTH_REQUIRED=1`
so helper startup fails if the token is missing.
V0 token claims are intentionally small:
```text
@@ -1239,13 +1247,19 @@ solve package sandboxing. A compromised package can still use its own package
token, but it cannot claim to be a different package without orch's signing
secret.
Camino rejects invalid runtime-token headers. Requests with no runtime token are
still accepted as `local-rpc` in v0 so CLI/dev tooling can mutate objects before
we have a real user/session auth layer. That means this is runtime provenance
enforcement, not full write authorization.
Later this should grow into:
- invocation/watch correlation IDs
- exact function ref and schema slot provenance
- expiry and rotation
- per-object/field capability scope where useful
- Camino-side rejection for package writes that lack a valid runtime token
- a user/session auth path distinct from package runtime auth
- Camino-side rejection for writes whose caller identity cannot be proven
- audit records that distinguish user intent from package-side effects
## Mounted/External State
+4 -1
View File
@@ -141,7 +141,10 @@ Camino replaces "packages with state contexts" as the conceptual center.
to Camino RPCs. Camino verifies signed token claims when
`CAMINO_RUNTIME_AUTH_SECRET` is configured and records package actor
provenance such as `package:quixos.todo/todo-runtime`. Unauthenticated local
writes still record `local-rpc` for v0 dev ergonomics.
writes still record `local-rpc` for v0 dev ergonomics. Invalid runtime-token
headers are rejected. The dev start scripts share a local runtime auth secret,
and orch-spawned package processes set `CAMINO_RUNTIME_AUTH_REQUIRED=1` so the
TypeScript package runtime helper fails fast if the minted token is missing.
- Each Camino class should have a real proto schema file. TypeScript object
bindings are runtime/client sugar generated from schema, not the schema source
of truth.
+1 -1
View File
@@ -71,7 +71,7 @@
camino-orch-cli = mkNodeLauncher "camino-orch" "cli";
camino-orch-server-start = pkgs.writeShellApplication {
name = "camino-orch-server-start";
runtimeInputs = [ pkgs.coreutils pkgs.procps pkgs.util-linux camino-orch-server ];
runtimeInputs = [ pkgs.coreutils pkgs.git pkgs.procps pkgs.util-linux camino-orch-server ];
text = builtins.readFile ./nix/camino-orch-server-start.sh;
};
camino-orch-server-status = pkgs.writeShellApplication {
@@ -1,10 +1,24 @@
set -euo pipefail
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
root="${CAMINO_ORCH_DEV_ROOT:-$PWD/.var}"
shared_root="$repo_root/quixos-instance/.var"
pid_file="$root/camino-orch-server.pid"
log_file="$root/camino-orch-server.log"
runtime_auth_secret_file="$shared_root/camino-runtime-auth-secret"
mkdir -p "$root"
mkdir -p "$shared_root"
if [ -z "${CAMINO_RUNTIME_AUTH_SECRET:-}" ]; then
if [ ! -s "$runtime_auth_secret_file" ]; then
umask 077
od -An -N32 -tx1 /dev/urandom | tr -d ' \n' > "$runtime_auth_secret_file"
printf '\n' >> "$runtime_auth_secret_file"
fi
CAMINO_RUNTIME_AUTH_SECRET="$(cat "$runtime_auth_secret_file")"
export CAMINO_RUNTIME_AUTH_SECRET
fi
if [ -s "$pid_file" ]; then
pid="$(cat "$pid_file")"
@@ -223,6 +223,7 @@ export const createPackageRuntimeManager = () => {
QUIXOS_RUNTIME_HOST: "127.0.0.1",
QUIXOS_RUNTIME_PORT: String(port),
CAMINO_RUNTIME_AUTH_TOKEN: runtimeToken,
CAMINO_RUNTIME_AUTH_REQUIRED: runtimeTokenSecret ? "1" : "",
},
stdio: ["ignore", "pipe", "pipe"],
});
+1 -1
View File
@@ -99,7 +99,7 @@
};
camino-server-start = pkgs.writeShellApplication {
name = "camino-server-start";
runtimeInputs = [ pkgs.coreutils pkgs.procps pkgs.util-linux camino-server ];
runtimeInputs = [ pkgs.coreutils pkgs.git pkgs.procps pkgs.util-linux camino-server ];
text = builtins.readFile ./nix/camino-server-start.sh;
};
camino-server-status = pkgs.writeShellApplication {
@@ -1,13 +1,26 @@
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
camino_dir="$repo_root/quixos-instance/camino"
dev_root="${CAMINO_DEV_ROOT:-$camino_dir/.var}"
shared_root="$repo_root/quixos-instance/.var"
pid_file="$dev_root/camino-server.pid"
log_file="$dev_root/camino-server.log"
runtime_auth_secret_file="$shared_root/camino-runtime-auth-secret"
socket_dir="$dev_root/postgres-run"
db_name="${CAMINO_DB_NAME:-camino}"
db_user="${CAMINO_DB_USER:-$USER}"
mkdir -p "$dev_root"
mkdir -p "$shared_root"
if [ -z "${CAMINO_RUNTIME_AUTH_SECRET:-}" ]; then
if [ ! -s "$runtime_auth_secret_file" ]; then
umask 077
od -An -N32 -tx1 /dev/urandom | tr -d ' \n' > "$runtime_auth_secret_file"
printf '\n' >> "$runtime_auth_secret_file"
fi
CAMINO_RUNTIME_AUTH_SECRET="$(cat "$runtime_auth_secret_file")"
export CAMINO_RUNTIME_AUTH_SECRET
fi
if [ -s "$pid_file" ]; then
pid="$(cat "$pid_file")"
+13 -6
View File
@@ -94,13 +94,20 @@ const handle = async <T>(fn: () => Promise<T>) => {
const inferActor = (headers: Headers) => {
const token = headers.get("x-camino-runtime-token");
const secret = process.env.CAMINO_RUNTIME_AUTH_SECRET;
if (token && secret) {
const claims = verifyRuntimeToken(token, secret);
if (claims) {
return `package:${claims.packageNamespace}/${claims.packageName}`;
}
if (!token) {
return "local-rpc";
}
return "local-rpc";
if (!secret) {
throw new ConnectError(
"Runtime token was provided, but Camino runtime auth is not configured",
Code.FailedPrecondition,
);
}
const claims = verifyRuntimeToken(token, secret);
if (!claims) {
throw new ConnectError("Invalid Camino runtime token", Code.Unauthenticated);
}
return `package:${claims.packageNamespace}/${claims.packageName}`;
};
export const createCaminoConnectRoutes =
@@ -531,6 +531,28 @@ test("Connect RPC exposes the Camino object flow", async () => {
});
const packageOps = await client.listOps({ objectId: taskObject.id });
assert.equal(packageOps.ops.at(-1)?.actor, "package:quixos.todo/todo-runtime");
const invalidTokenInterceptor: Interceptor = (next) => async (request) => {
request.header.set("x-camino-runtime-token", "qrt1.invalid.invalid");
return await next(request);
};
const invalidPackageClient = createClient(
CaminoService,
createConnectTransport({
baseUrl: `http://127.0.0.1:${address.port}`,
httpVersion: "1.1",
interceptors: [invalidTokenInterceptor],
}),
);
await assert.rejects(
() =>
invalidPackageClient.setField({
objectId: taskObject.id,
fieldName: "status",
value: jsToProtoValue("ARCHIVED"),
}),
/Invalid Camino runtime token/,
);
} finally {
if (previousRuntimeSecret === undefined) {
delete process.env.CAMINO_RUNTIME_AUTH_SECRET;
@@ -721,6 +721,11 @@ export const serveQuixosPackageRuntime = <
const port = Number(process.env.QUIXOS_RUNTIME_PORT ?? "0");
const caminoUrl = process.env.CAMINO_URL ?? "http://127.0.0.1:7310";
const runtimeToken = process.env.CAMINO_RUNTIME_AUTH_TOKEN ?? "";
if (process.env.CAMINO_RUNTIME_AUTH_REQUIRED === "1" && !runtimeToken) {
throw new Error(
"CAMINO_RUNTIME_AUTH_REQUIRED=1 but CAMINO_RUNTIME_AUTH_TOKEN is missing",
);
}
const runtimeTokenInterceptor: Interceptor = (next) => async (request) => {
if (runtimeToken) {
request.header.set("x-camino-runtime-token", runtimeToken);
+5 -5
View File
@@ -27,17 +27,17 @@
]
},
"locked": {
"lastModified": 1783891969,
"narHash": "sha256-G8aUGAZP2ee0A4lGfRNMqzmDmlKFFKSBmkTbsGeWL7A=",
"lastModified": 1783894346,
"narHash": "sha256-XPEPnTA9h8geIdEfi2omeJcGWpM0fHECj1yvSfScym8=",
"ref": "refs/heads/exported",
"rev": "978450888cbb35f591818b5d78167b68f3ac9079",
"revCount": 16,
"rev": "4424720987e99d2b03a8fcfaacaf91e6277c1dfa",
"revCount": 18,
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
},
"original": {
"ref": "refs/heads/exported",
"rev": "978450888cbb35f591818b5d78167b68f3ac9079",
"rev": "4424720987e99d2b03a8fcfaacaf91e6277c1dfa",
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
}
@@ -7,7 +7,7 @@
camino.url = "path:../../camino";
camino-package-runtime = {
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git?ref=refs/heads/exported&rev=978450888cbb35f591818b5d78167b68f3ac9079";
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git?ref=refs/heads/exported&rev=4424720987e99d2b03a8fcfaacaf91e6277c1dfa";
inputs.quixos-protocol.follows = "quixos-protocol";
inputs.quixosNixHelpers.follows = "quixosNixHelpers";
};
+5 -5
View File
@@ -27,17 +27,17 @@
]
},
"locked": {
"lastModified": 1783891969,
"narHash": "sha256-G8aUGAZP2ee0A4lGfRNMqzmDmlKFFKSBmkTbsGeWL7A=",
"lastModified": 1783894346,
"narHash": "sha256-XPEPnTA9h8geIdEfi2omeJcGWpM0fHECj1yvSfScym8=",
"ref": "refs/heads/exported",
"rev": "978450888cbb35f591818b5d78167b68f3ac9079",
"revCount": 16,
"rev": "4424720987e99d2b03a8fcfaacaf91e6277c1dfa",
"revCount": 18,
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
},
"original": {
"ref": "refs/heads/exported",
"rev": "978450888cbb35f591818b5d78167b68f3ac9079",
"rev": "4424720987e99d2b03a8fcfaacaf91e6277c1dfa",
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
}
@@ -7,7 +7,7 @@
camino.url = "path:../../camino";
camino-package-runtime = {
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git?ref=refs/heads/exported&rev=978450888cbb35f591818b5d78167b68f3ac9079";
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git?ref=refs/heads/exported&rev=4424720987e99d2b03a8fcfaacaf91e6277c1dfa";
inputs.quixos-protocol.follows = "quixos-protocol";
inputs.quixosNixHelpers.follows = "quixosNixHelpers";
};