From c396a54cc6a2a0aa97f37ee85f0e8c3220fa29ff Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Thu, 13 Mar 2025 22:13:51 +0100 Subject: [PATCH] feat: add auth to inspector --- .../actor-core-cli/src/commands/deploy.tsx | 134 +++++++++--------- .../src/actor/runtime/actor_router.ts | 20 ++- .../actor-core/src/actor/runtime/config.ts | 6 +- .../actor-core/src/actor/runtime/inspect.ts | 24 +++- packages/platforms/rivet/src/actor_handler.ts | 37 ++++- .../platforms/rivet/src/manager_handler.ts | 2 +- 6 files changed, 142 insertions(+), 81 deletions(-) diff --git a/packages/actor-core-cli/src/commands/deploy.tsx b/packages/actor-core-cli/src/commands/deploy.tsx index 5b7206f70..b0917e057 100644 --- a/packages/actor-core-cli/src/commands/deploy.tsx +++ b/packages/actor-core-cli/src/commands/deploy.tsx @@ -210,83 +210,83 @@ export const deploy = new Command() ); } - if (managers.length > 0) { - for (const manager of managers) { - await Rivet.actor.upgrade(manager.id, { - project: projectName, - environment: envName, - body: { - buildTags: { - name: "manager", - current: "true", - }, - }, - }); - } - - const manager = managers.find( - (m) => !!createActorEndpoint(m.network), - ); - - if (!manager) { - throw ctx.error("Failed to find Actor Core Endpoint.", { - hint: "Any existing manager actor is not running or not accessible.", - }); - } + // if (managers.length > 0) { + // for (const manager of managers) { + // await Rivet.actor.upgrade(manager.id, { + // project: projectName, + // environment: envName, + // body: { + // buildTags: { + // name: "manager", + // current: "true", + // }, + // }, + // }); + // } + + // const manager = managers.find( + // (m) => !!createActorEndpoint(m.network), + // ); + + // if (!manager) { + // throw ctx.error("Failed to find Actor Core Endpoint.", { + // hint: "Any existing manager actor is not running or not accessible.", + // }); + // } + + // return manager; + // } else { + const serviceToken = await getServiceToken(RivetHttp, { + project: projectName, + env: envName, + }); - return manager; - } else { - const serviceToken = await getServiceToken(RivetHttp, { - project: projectName, - env: envName, - }); + const { regions } = await Rivet.actor.regions.list({ + project: projectName, + environment: envName, + }); - const { regions } = await Rivet.actor.regions.list({ - project: projectName, - environment: envName, - }); + // find closest region + const region = regions.find( + (r) => r.id === "atl" || r.id === "local", + ); - // find closest region - const region = regions.find( - (r) => r.id === "atl" || r.id === "local", + if (!region) { + throw ctx.error( + "No closest region found. Please contact support.", ); + } - if (!region) { - throw ctx.error( - "No closest region found. Please contact support.", - ); - } - - const { actor } = await Rivet.actor.create({ - project: projectName, - environment: envName, - body: { - region: region.id, - tags: { name: "manager", owner: "rivet" }, - buildTags: { name: "manager", current: "true" }, - runtime: { - environment: { - RIVET_SERVICE_TOKEN: serviceToken, - }, + const { actor } = await Rivet.actor.create({ + project: projectName, + environment: envName, + body: { + region: region.id, + tags: { name: "manager", owner: "rivet" }, + buildTags: { name: "manager", current: "true" }, + runtime: { + environment: { + RIVET_SERVICE_TOKEN: serviceToken, }, - network: { - mode: "bridge", - ports: { - http: { - protocol: "https", - routing: { - guard: {}, - }, + }, + network: { + mode: "bridge", + ports: { + http: { + protocol: "https", + routing: { + guard: {}, }, }, }, - lifecycle: { - durable: true, - }, }, - }); - return actor; - } + lifecycle: { + durable: true, + }, + }, + }); + return actor; + // } }, ); diff --git a/packages/actor-core/src/actor/runtime/actor_router.ts b/packages/actor-core/src/actor/runtime/actor_router.ts index 6b9195224..9acda11be 100644 --- a/packages/actor-core/src/actor/runtime/actor_router.ts +++ b/packages/actor-core/src/actor/runtime/actor_router.ts @@ -352,10 +352,22 @@ export function createActorRouter( } }); - app.route( - "/inspect", - createInspectorRouter(handler.upgradeWebSocket, handler.onConnectInspector), - ); + if ( + (typeof config.inspector === "object" && + config.inspector.enabled === true) || + config.inspector === true + ) { + app.route( + "/inspect", + createInspectorRouter( + handler.upgradeWebSocket, + handler.onConnectInspector, + typeof config.inspector === "object" + ? config.inspector.validateRequest + : undefined, + ), + ); + } app.notFound(handleRouteNotFound); app.onError(handleRouteError); diff --git a/packages/actor-core/src/actor/runtime/config.ts b/packages/actor-core/src/actor/runtime/config.ts index f0bdcf93c..a7e188c68 100644 --- a/packages/actor-core/src/actor/runtime/config.ts +++ b/packages/actor-core/src/actor/runtime/config.ts @@ -8,6 +8,7 @@ import type { Handler as HonoHandler, } from "hono"; import type { cors } from "hono/cors"; +import { InspectorConfigSchema } from "./inspect"; // Define CORS options schema type CorsOptions = NonNullable[0]>; @@ -56,7 +57,7 @@ export type GetUpgradeWebSocket = ( /** Base config used for the actor config across all platforms. */ export const BaseConfigSchema = z.object({ actors: z.record(z.string(), z.custom()), - topology: TopologySchema.optional(), // Default value depends on the platform selected + topology: TopologySchema.optional(), // Default value depends on the platform selected drivers: z .object({ manager: z.custom().optional(), @@ -82,5 +83,8 @@ export const BaseConfigSchema = z.object({ /** Peer configuration for coordinated topology. */ actorPeer: ActorPeerConfigSchema.optional().default({}), + + /** Inspector configuration */ + inspector: InspectorConfigSchema.optional().default(false), }); export type BaseConfig = z.infer; diff --git a/packages/actor-core/src/actor/runtime/inspect.ts b/packages/actor-core/src/actor/runtime/inspect.ts index f166b9eff..dab91f916 100644 --- a/packages/actor-core/src/actor/runtime/inspect.ts +++ b/packages/actor-core/src/actor/runtime/inspect.ts @@ -2,7 +2,7 @@ import type { AnyActor } from "@/actor/runtime/actor"; import type { Connection, ConnectionId } from "@/actor/runtime/connection"; import { throttle } from "@/actor/runtime/utils"; import type { UpgradeWebSocket, WSContext } from "hono/ws"; -import { Hono, type HonoRequest } from "hono"; +import { Hono, type HonoRequest, type Context as HonoContext } from "hono"; import * as errors from "@/actor/errors"; import { deconstructError, safeStringify } from "@/common/utils"; import { @@ -11,12 +11,22 @@ import { } from "@/actor/protocol/inspector/to_server"; import type { ToClient } from "@/actor/protocol/inspector/to_client"; import { logger } from "./log"; +import { z } from "zod"; + +export type ValidateInspectorRequest = (c: HonoContext) => Promise; + +export const InspectorConfigSchema = z + .object({ + enabled: z.boolean().optional().default(false), + validateRequest: z.custom().optional(), + }) + .or(z.boolean()); export interface ConnectInspectorOpts { req: HonoRequest; } -export interface ConnectInspectortOutput { +export interface ConnectInspectorOutput { onOpen: (ws: WSContext) => Promise; onMessage: (message: ToServer) => Promise; onClose: () => Promise; @@ -24,7 +34,7 @@ export interface ConnectInspectortOutput { export type InspectorConnectionHandler = ( opts: ConnectInspectorOpts, -) => Promise; +) => Promise; /** * Create a router for the inspector. @@ -33,6 +43,7 @@ export type InspectorConnectionHandler = ( export function createInspectorRouter( upgradeWebSocket: UpgradeWebSocket | undefined, onConnect: InspectorConnectionHandler | undefined, + validateRequest: ValidateInspectorRequest | undefined, ) { const app = new Hono(); @@ -45,6 +56,13 @@ export function createInspectorRouter( } return app.get( "/", + async (c, next) => { + const result = (await validateRequest?.(c)) ?? true; + if (!result) { + return c.json({ error: "Unauthorized" }, 403); + } + await next(); + }, upgradeWebSocket(async (c) => { try { const handler = await onConnect({ req: c.req }); diff --git a/packages/platforms/rivet/src/actor_handler.ts b/packages/platforms/rivet/src/actor_handler.ts index 385ceb9d9..27556c631 100644 --- a/packages/platforms/rivet/src/actor_handler.ts +++ b/packages/platforms/rivet/src/actor_handler.ts @@ -7,10 +7,9 @@ import type { RivetHandler } from "./util"; import { PartitionTopologyActor } from "actor-core/topologies/partition"; import { ConfigSchema, type InputConfig } from "./config"; import { RivetActorDriver } from "./actor_driver"; +import { rivetRequest } from "./rivet_client"; -export function createActorHandler( - inputConfig: InputConfig, -): RivetHandler { +export function createActorHandler(inputConfig: InputConfig): RivetHandler { const config = ConfigSchema.parse(inputConfig); const handler = { @@ -31,7 +30,36 @@ export function createActorHandler( if (!config.drivers.actor) { config.drivers.actor = new RivetActorDriver(ctx); } - + + // Setup inspector + config.inspector = { + enabled: true, + async validateRequest(c) { + const token = c.req.query("token"); + + if (!token) { + return false; + } + + try { + await rivetRequest( + { + endpoint: "http://rivet-server:8080", + token, + project: ctx.metadata.project.slug, + environment: ctx.metadata.environment.slug, + }, + "GET", + "/cloud/auth/inspect", + ); + return true; + } catch (e) { + console.log("error", e); + return false; + } + }, + }; + // Setup WebSocket upgrader if (!config.getUpgradeWebSocket) { config.getUpgradeWebSocket = () => upgradeWebSocket; @@ -70,4 +98,3 @@ export function createActorHandler( return handler; } - diff --git a/packages/platforms/rivet/src/manager_handler.ts b/packages/platforms/rivet/src/manager_handler.ts index e758e2d8f..5b1339bc3 100644 --- a/packages/platforms/rivet/src/manager_handler.ts +++ b/packages/platforms/rivet/src/manager_handler.ts @@ -29,7 +29,7 @@ export function createManagerHandler(inputConfig: InputConfig): RivetHandler { if (!token) throw new Error("missing RIVET_SERVICE_TOKEN"); const clientConfig: RivetClientConfig = { - endpoint, + endpoint: "http://rivet-server:8080", token, project: ctx.metadata.project.slug, environment: ctx.metadata.environment.slug,