Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6dea1309c | |||
| b0c376bd95 |
@@ -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
@@ -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.
|
||||
|
||||
@@ -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"],
|
||||
});
|
||||
|
||||
@@ -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")"
|
||||
|
||||
@@ -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
@@ -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
@@ -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";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user