963 lines · 34.8 KB · TypeScript
RawDownload
import * as NodeCrypto from "node:crypto";import {  AuthRelayReadScope,  AuthRelayWriteScope,  AuthStandardClientScopes,  EnvironmentCloudEndpointUnavailableError,  EnvironmentCloudLinkStateResult,  EnvironmentCloudRelayConfigResult,  EnvironmentHttpApi,  EnvironmentHttpBadRequestError,  EnvironmentHttpConflictError,  EnvironmentHttpInternalServerError,  EnvironmentHttpUnauthorizedError,} from "@t3tools/contracts";import {  RelayCloudEnvironmentHealthProofPayload,  RelayCloudEnvironmentHealthRequest,  RelayCloudMintCredentialProofPayload,  RelayCloudMintCredentialRequest,  RelayEnvironmentHealthResponseProofPayload,  type RelayEnvironmentHealthResponse as RelayEnvironmentHealthResponseShape,  RelayEnvironmentConfigRequest,  RelayEnvironmentLinkChallengeResponse,  RelayEnvironmentLinkResponse,  RelayEnvironmentMintResponseProofPayload,  type RelayEnvironmentMintResponse as RelayEnvironmentMintResponseShape,  RelayEnvironmentLinkProof,  RelayEnvironmentLinkProofPayload,  RelayLinkProofRequest,  RelayManagedEndpointOrigin,} from "@t3tools/contracts/relay";import { withRelayClientTracing } from "@t3tools/shared/relayTracing";import {  normalizeRelayIssuer,  RELAY_HEALTH_REQUEST_TYP,  RELAY_HEALTH_RESPONSE_TYP,  RELAY_LINK_PROOF_TYP,  RELAY_MINT_REQUEST_TYP,  RELAY_MINT_RESPONSE_TYP,  signRelayJwt,  verifyRelayJwt,} from "@t3tools/shared/relayJwt";import { isSecureRelayUrl } from "@t3tools/shared/relayUrl";import * as DateTime from "effect/DateTime";import * as Crypto from "effect/Crypto";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import * as HttpEffect from "effect/unstable/http/HttpEffect";import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";import * as ServerSecretStore from "../auth/ServerSecretStore.ts";import { requireEnvironmentScope } from "../auth/http.ts";import * as ServerEnvironment from "../environment/ServerEnvironment.ts";import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";import {  CLOUD_ENDPOINT_RUNTIME_CONFIG,  CLOUD_LINKED_USER_ID,  CLOUD_MINT_PUBLIC_KEY,  encodeEndpointRuntimeConfigJson,  PUBLISH_AGENT_ACTIVITY_SECRET,  RELAY_ENVIRONMENT_CREDENTIAL_SECRET,  RELAY_ISSUER_SECRET,  RELAY_URL_SECRET,} from "./config.ts";import { relayUrlConfig } from "./publicConfig.ts";import { readCliDesiredLinkMode, setCliDesiredCloudLink } from "./CliState.ts";import * as CliTokenManager from "./CliTokenManager.ts";import { getOrCreateEnvironmentKeyPairFromSecretStore } from "./environmentKeys.ts";import { traceRelayRequest } from "./traceRelayRequest.ts"; const CLOUD_MINT_NONCE_PREFIX = "cloud-mint-nonce-";const CLOUD_MINT_JTI_PREFIX = "cloud-mint-jti-";const CLOUD_HEALTH_NONCE_PREFIX = "cloud-health-nonce-";const CLOUD_HEALTH_JTI_PREFIX = "cloud-health-jti-";const CLOUD_PROOF_MAX_LIFETIME_SECONDS = 5 * 60;const CLOUD_PROOF_CLOCK_SKEW_SECONDS = 60;const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]);const CLOUD_CREDENTIAL_RESPONSE_HEADERS = {  "cache-control": "no-store",  pragma: "no-cache",} as const; const appendCloudCredentialResponseHeaders = HttpEffect.appendPreResponseHandler(  (_request, response) =>    Effect.succeed(HttpServerResponse.setHeaders(response, CLOUD_CREDENTIAL_RESPONSE_HEADERS)),); const failEnvironmentCloudInternalError =  (message: string) =>  (cause: unknown): Effect.Effect<never, EnvironmentHttpInternalServerError> =>    Effect.logError(message, { cause }).pipe(      Effect.flatMap(() => Effect.fail(new EnvironmentHttpInternalServerError({ message }))),    ); const failCloudCliTokenManagerError = (error: CliTokenManager.CloudCliTokenManagerError) =>  failEnvironmentCloudInternalError(error.message)(error); const requireRelayUrl = relayUrlConfig.pipe(  Effect.mapError(    () =>      new EnvironmentHttpInternalServerError({        message: "T3CODE_RELAY_URL must be configured as a secure absolute HTTPS origin.",      }),  ),); function bytesToString(bytes: Uint8Array): string {  return new TextDecoder().decode(bytes);} function stringToBytes(value: string): Uint8Array {  return new TextEncoder().encode(value);} export function consumeCloudReplayGuards(input: {  readonly secrets: ServerSecretStore.ServerSecretStore["Service"];  readonly names: ReadonlyArray<string>;  readonly value: Uint8Array;}) {  return Effect.all(    input.names.map((name) =>      input.secrets.create(name, input.value).pipe(        Effect.as(true),        Effect.catchIf(ServerSecretStore.isSecretStoreError, (error) =>          ServerSecretStore.isSecretAlreadyExistsError(error)            ? Effect.succeed(false)            : Effect.fail(error),        ),      ),    ),    { concurrency: input.names.length },  ).pipe(Effect.map((created) => created.every(Boolean)));} function normalizePemForSignedPayload(value: string): string {  return value.trim();} function normalizeHostname(hostname: string): string {  return hostname    .trim()    .toLowerCase()    .replace(/^\[(.*)\]$/, "$1");} function validateCloudMintPublicKey(  publicKey: string,): Effect.Effect<void, EnvironmentHttpBadRequestError> {  return Effect.try({    try: () => NodeCrypto.createPublicKey(publicKey.replace(/\\n/g, "\n")),    catch: () =>      new EnvironmentHttpBadRequestError({        message: "Cloud mint public key must be a valid Ed25519 public key.",      }),  }).pipe(    Effect.flatMap((key) =>      key.asymmetricKeyType === "ed25519"        ? Effect.void        : Effect.fail(            new EnvironmentHttpBadRequestError({              message: "Cloud mint public key must be a valid Ed25519 public key.",            }),          ),    ),  );} function validateRelayConfigPayload(  payload: RelayEnvironmentConfigRequest,): Effect.Effect<void, EnvironmentHttpBadRequestError> {  if (!isSecureRelayUrl(payload.relayUrl)) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        message: "Relay URL must be a secure absolute HTTPS URL.",      }),    );  }  if (payload.relayIssuer !== undefined && !isSecureRelayUrl(payload.relayIssuer)) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        message: "Relay issuer must be a secure absolute HTTPS URL.",      }),    );  }  if (payload.environmentCredential.trim().length === 0) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        message: "Relay environment credential is required.",      }),    );  }  if (payload.cloudUserId.trim().length === 0) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        message: "Cloud user id is required.",      }),    );  }  return Effect.void;} function validateLinkedCloudUser(input: {  readonly secrets: ServerSecretStore.ServerSecretStore["Service"];  readonly cloudUserId: string;}): Effect.Effect<void, EnvironmentAuth.ServerAuthInternalError | EnvironmentHttpConflictError> {  return input.secrets.get(CLOUD_LINKED_USER_ID).pipe(    Effect.mapError(      (cause) =>        new EnvironmentAuth.ServerAuthLinkedCloudAccountVerificationError({          cause,        }),    ),    Effect.flatMap((existing) => {      if (Option.isNone(existing)) {        return Effect.void;      }      const existingCloudUserId = bytesToString(existing.value);      return existingCloudUserId === input.cloudUserId        ? Effect.void        : Effect.fail(            new EnvironmentHttpConflictError({              message:                "This environment is already linked to a different cloud account. Unlink it before switching accounts.",            }),          );    }),  );} function readInstalledCloudUserId(  secrets: ServerSecretStore.ServerSecretStore["Service"],): Effect.Effect<string, EnvironmentAuth.ServerAuthInternalError> {  return secrets.get(CLOUD_LINKED_USER_ID).pipe(    Effect.mapError(      (cause) =>        new EnvironmentAuth.ServerAuthLinkedCloudAccountReadError({          cause,        }),    ),    Effect.flatMap((bytes) =>      Option.isSome(bytes)        ? Effect.succeed(bytesToString(bytes.value))        : Effect.fail(new EnvironmentAuth.ServerAuthLinkedCloudAccountMissingError({})),    ),  );} function isLoopbackHostname(hostname: string): boolean {  return LOOPBACK_HOSTNAMES.has(normalizeHostname(hostname));} function firstForwardedHeaderValue(value: string | undefined): string | undefined {  const first = value?.split(",")[0]?.trim();  return first && first.length > 0 ? first : undefined;} function requestAbsoluteUrl(request: HttpServerRequest.HttpServerRequest): string | null {  try {    return new URL(request.originalUrl).href;  } catch {    const host = firstForwardedHeaderValue(request.headers.host) ?? "127.0.0.1";    try {      return new URL(request.originalUrl, `http://${host}`).href;    } catch {      return null;    }  }} function hasForwardedAuthorityHeaders(request: HttpServerRequest.HttpServerRequest): boolean {  return (    firstForwardedHeaderValue(request.headers["x-forwarded-host"]) !== undefined ||    firstForwardedHeaderValue(request.headers["x-forwarded-proto"]) !== undefined  );} function endpointRequestPort(url: URL): number {  return Number(url.port || (url.protocol === "https:" ? 443 : 80));} function isAllowedEndpointOrigin(input: {  readonly origin: RelayManagedEndpointOrigin;  readonly requestUrl: string;}): boolean {  if (!isLoopbackHostname(input.origin.localHttpHost)) {    return false;  }   const url = new URL(input.requestUrl);  if (!isLoopbackHostname(url.hostname)) {    return false;  }   return input.origin.localHttpPort === endpointRequestPort(url);} // A managed (Cloudflare tunnel) endpoint is provisioned by the relay and must// point at a loopback origin. A manual endpoint is reached out of band (e.g.// Tailscale) or not advertised at all for publish-only links, so it is not// tied to the managed-tunnel scope.export function isSupportedLinkProviderKind(request: RelayLinkProofRequest): boolean {  return (    request.endpoint.providerKind === "cloudflare_tunnel" ||    request.endpoint.providerKind === "manual"  );} export function linkProofScopes(  request: RelayLinkProofRequest,): RelayEnvironmentLinkProofPayload["scopes"] {  return request.endpoint.providerKind === "cloudflare_tunnel"    ? ["agent_activity_notifications", "managed_tunnels"]    : ["agent_activity_notifications"];} function hasExactScope(input: {  readonly scopes: ReadonlyArray<string>;  readonly expected: string;}): boolean {  return input.scopes.length === 1 && input.scopes[0] === input.expected;} function hasBoundedCloudProofLifetime(input: {  readonly iat: number;  readonly exp: number;  readonly nowSeconds: number;}): boolean {  return (    input.exp > input.iat &&    input.exp - input.iat <= CLOUD_PROOF_MAX_LIFETIME_SECONDS &&    input.iat <= input.nowSeconds + CLOUD_PROOF_CLOCK_SKEW_SECONDS  );} const decodeCloudHealthProof = Schema.decodeUnknownEffect(RelayCloudEnvironmentHealthProofPayload);const decodeCloudMintProof = Schema.decodeUnknownEffect(RelayCloudMintCredentialProofPayload); interface CloudHttpDependencies {  readonly secrets: ServerSecretStore.ServerSecretStore["Service"];  readonly environment: ServerEnvironment.ServerEnvironment["Service"];  readonly endpointRuntime: ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"];  readonly environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"];  readonly cliTokenManager: CliTokenManager.CloudCliTokenManager["Service"];  readonly httpClient: HttpClient.HttpClient;} const cloudHttpDependencies = Effect.gen(function* () {  return {    secrets: yield* ServerSecretStore.ServerSecretStore,    environment: yield* ServerEnvironment.ServerEnvironment,    endpointRuntime: yield* ManagedEndpointRuntime.CloudManagedEndpointRuntime,    environmentAuth: yield* EnvironmentAuth.EnvironmentAuth,    cliTokenManager: yield* CliTokenManager.CloudCliTokenManager,    httpClient: yield* HttpClient.HttpClient,  } satisfies CloudHttpDependencies;}); const makeCloudLinkProof = Effect.fn("environment.cloud.makeLinkProof")(function* (  dependencies: CloudHttpDependencies,  request: RelayLinkProofRequest,  requestUrl: string,) {  const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(dependencies.secrets);  if (    !isSupportedLinkProviderKind(request) ||    !isAllowedEndpointOrigin({      origin: request.origin,      requestUrl,    })  ) {    return yield* new EnvironmentHttpBadRequestError({      message: "Invalid managed endpoint origin.",    });  }  const now = yield* DateTime.now;  const expiresAt = DateTime.add(now, { minutes: 5 });  const nowSeconds = Math.floor(now.epochMilliseconds / 1_000);  const descriptor = yield* dependencies.environment.getDescriptor;  const payload = {    iss: `t3-env:${descriptor.environmentId}`,    aud: normalizeRelayIssuer(request.relayIssuer),    sub: descriptor.environmentId,    jti: yield* Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4)),    iat: nowSeconds,    exp: Math.floor(expiresAt.epochMilliseconds / 1_000),    challenge: request.challenge,    descriptor,    environmentId: descriptor.environmentId,    environmentPublicKey: normalizePemForSignedPayload(keyPair.publicKey),    endpoint: request.endpoint,    origin: request.origin,    scopes: linkProofScopes(request),  } satisfies RelayEnvironmentLinkProofPayload;  return yield* signRelayJwt({    privateKey: keyPair.privateKey,    typ: RELAY_LINK_PROOF_TYP,    payload,  }).pipe(    Effect.mapError(      (cause) =>        new EnvironmentAuth.ServerAuthCloudLinkJwtSigningError({          cause,        }),    ),  );}); const cloudLinkProofHandler = Effect.fn("environment.cloud.linkProof")(  function* (dependencies: CloudHttpDependencies, request: RelayLinkProofRequest) {    yield* requireEnvironmentScope(AuthRelayWriteScope);    const httpRequest = yield* HttpServerRequest.HttpServerRequest;    const requestUrl = requestAbsoluteUrl(httpRequest);    if (requestUrl === null || hasForwardedAuthorityHeaders(httpRequest)) {      return yield* new EnvironmentHttpBadRequestError({        message: "Invalid managed endpoint origin.",      });    }    const proof = yield* makeCloudLinkProof(dependencies, request, requestUrl);    yield* appendCloudCredentialResponseHeaders;    return proof satisfies RelayEnvironmentLinkProof;  },  Effect.catchIf(EnvironmentAuth.isServerAuthInternalError, (error) =>    failEnvironmentCloudInternalError(error.message)(error),  ),  Effect.catchIf(    ServerSecretStore.isSecretStoreError,    failEnvironmentCloudInternalError("Could not generate environment link proof."),  ),  Effect.catchTag(    "PlatformError",    failEnvironmentCloudInternalError("Could not generate environment link proof."),  ),); const applyCloudRelayConfig = Effect.fn("environment.cloud.applyRelayConfig")(function* (  dependencies: CloudHttpDependencies,  payload: RelayEnvironmentConfigRequest,) {  yield* validateRelayConfigPayload(payload);  yield* validateLinkedCloudUser({    secrets: dependencies.secrets,    cloudUserId: payload.cloudUserId,  });  yield* validateCloudMintPublicKey(payload.cloudMintPublicKey);  const endpointRuntimeStatus = yield* dependencies.endpointRuntime.applyConfig(    payload.endpointRuntime,  );  const ok =    endpointRuntimeStatus.status === "disabled" || endpointRuntimeStatus.status === "running";  if (!ok) {    return yield* new EnvironmentCloudEndpointUnavailableError({      message: "Managed endpoint runtime could not be started.",      endpointRuntimeStatus,    });  }   yield* dependencies.secrets.set(RELAY_URL_SECRET, stringToBytes(payload.relayUrl));  yield* dependencies.secrets.set(    RELAY_ISSUER_SECRET,    stringToBytes(payload.relayIssuer ?? payload.relayUrl),  );  yield* dependencies.secrets.set(CLOUD_LINKED_USER_ID, stringToBytes(payload.cloudUserId));  yield* dependencies.secrets.set(    RELAY_ENVIRONMENT_CREDENTIAL_SECRET,    stringToBytes(payload.environmentCredential),  );  yield* dependencies.secrets.set(CLOUD_MINT_PUBLIC_KEY, stringToBytes(payload.cloudMintPublicKey));  if (payload.endpointRuntime) {    const endpointRuntimeJson = yield* encodeEndpointRuntimeConfigJson(payload.endpointRuntime);    yield* dependencies.secrets.set(      CLOUD_ENDPOINT_RUNTIME_CONFIG,      stringToBytes(endpointRuntimeJson),    );  } else {    yield* dependencies.secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG);  }  return { ok, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult;}); const cloudRelayConfigHandler = Effect.fn("environment.cloud.relayConfig")(  function* (dependencies: CloudHttpDependencies, payload: RelayEnvironmentConfigRequest) {    yield* requireEnvironmentScope(AuthRelayWriteScope);    return yield* applyCloudRelayConfig(dependencies, payload);  },  Effect.catchIf(EnvironmentAuth.isServerAuthInternalError, (error) =>    failEnvironmentCloudInternalError(error.message)(error),  ),  Effect.catchIf(    ServerSecretStore.isSecretStoreError,    failEnvironmentCloudInternalError("Could not persist environment relay configuration."),  ),  Effect.catchTag(    "SchemaError",    failEnvironmentCloudInternalError("Could not persist environment relay configuration."),  ),

Lines 1–500 of 963.