apps/server/src/cloud/CloudLink.ts
// @effect-diagnostics-next-line nodeBuiltinImport:off -- Effect's Crypto has no createPublicKey.
import * as NodeCrypto from "node:crypto";
import {
AuthStandardClientScopes,
EnvironmentAuthenticatedPrincipal,
type EnvironmentCloudLinkStateResult,
type EnvironmentCloudPreferencesRequest,
type EnvironmentCloudRelayConfigResult,
} from "@t3tools/contracts";
import {
RelayCloudEnvironmentHealthProofPayload,
type RelayCloudEnvironmentHealthRequest,
RelayCloudMintCredentialProofPayload,
type RelayCloudMintCredentialRequest,
type RelayEnvironmentConfigRequest,
type RelayEnvironmentHealthResponse,
type RelayEnvironmentHealthResponseProofPayload,
type RelayEnvironmentLinkProof,
type RelayEnvironmentLinkProofPayload,
RelayEnvironmentLinkChallengeResponse,
RelayEnvironmentLinkResponse,
type RelayEnvironmentMintResponse,
type RelayEnvironmentMintResponseProofPayload,
type RelayLinkProofRequest,
type RelayManagedEndpointOrigin,
type RelayManagedEndpointRecoveryProofPayload,
RelayManagedEndpointRecoveryRegistrationResponse,
RelayManagedEndpointRecoveryResponse,
type RelayManagedEndpointRuntimeConfig,
RelayOkResponse,
} 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_MANAGED_TUNNEL_RECOVERY_TYP,
RELAY_MINT_REQUEST_TYP,
RELAY_MINT_RESPONSE_TYP,
signRelayJwt,
verifyRelayJwt,
} from "@t3tools/shared/relayJwt";
import { isSecureRelayUrl } from "@t3tools/shared/relayUrl";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as DateTime from "effect/DateTime";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import type * as PlatformError from "effect/PlatformError";
import * as Schedule from "effect/Schedule";
import * as Schema from "effect/Schema";
import * as Semaphore from "effect/Semaphore";
import {
HttpClient,
HttpClientRequest,
HttpClientResponse,
type HttpServerRequest,
} from "effect/http";
import type * as HttpClientError from "effect/http/HttpClientError";
import * as HttpServer from "effect/http/HttpServer";
import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as ServerConfig from "../config.ts";
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
import * as AgentAwarenessRelay from "../relay/AgentAwarenessRelay.ts";
import { makeRelayEnvironmentClient } from "../relay/relayEnvironmentClient.ts";
import { loopbackUrlHostForAddress } from "../startupAccess.ts";
import {
type CliDesiredLinkMode,
readCliDesiredCloudLink,
readCliDesiredLinkMode,
setCliDesiredCloudLink,
} from "./CliState.ts";
import * as CliTokenManager from "./CliTokenManager.ts";
import {
CLOUD_ENDPOINT_CONFIRMED_ORIGIN,
CLOUD_ENDPOINT_RUNTIME_CONFIG,
CLOUD_LINK_REVOKED,
CLOUD_LINKED_USER_ID,
CLOUD_MINT_PUBLIC_KEY,
decodeConfirmedOrigin,
decodeRuntimeConfig,
encodeConfirmedOriginJson,
encodeEndpointRuntimeConfigJson,
HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET,
PUBLISH_AGENT_ACTIVITY_SECRET,
readRelayConnection,
RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
RELAY_ISSUER_SECRET,
RELAY_URL_SECRET,
} from "./config.ts";
import { getOrCreateEnvironmentKeyPairFromSecretStore } from "./environmentKeys.ts";
import * as ConnectReachability from "./ConnectReachability.ts";
import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";
import { relayUrlConfig } from "./publicConfig.ts";
import {
filterRelayResponse,
relayRequestError,
type RelayRequestError,
shouldRetryRelayRequest,
} from "./relayResponse.ts";
import {
hasBoundedCloudProofLifetime,
hasExactScope,
hasForwardedAuthorityHeaders,
isAllowedEndpointOrigin,
isSupportedLinkProviderKind,
linkProofScopes,
managedEndpointRuntimeConfigsMatch,
parseManagedEndpointLocalOrigin,
requestAbsoluteUrl,
} from "./linkChecks.ts";
import { desktopUpdateRestartPending, pendingUpdateHandoffExists } from "./updateHandoff.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-";
export const CLOUD_REPLAY_MARKER_PREFIXES = [
CLOUD_MINT_NONCE_PREFIX,
CLOUD_MINT_JTI_PREFIX,
CLOUD_HEALTH_NONCE_PREFIX,
CLOUD_HEALTH_JTI_PREFIX,
] as const;
const MANAGED_ENDPOINT_PROVISION_REQUEST_TIMEOUT = Duration.minutes(2);
const RELAY_CONFIG_FIELD_MESSAGES = {
relayUrl: "Relay URL must be a secure absolute HTTPS URL.",
relayIssuer: "Relay issuer must be a secure absolute HTTPS URL.",
environmentCredential: "Relay environment credential is required.",
cloudUserId: "Cloud user id is required.",
cloudMintPublicKey: "Cloud mint public key must be a valid Ed25519 public key.",
} as const;
export class CloudLinkRelayConfigInvalidError extends Schema.TaggedError<CloudLinkRelayConfigInvalidError>()(
"CloudLinkRelayConfigInvalidError",
{
field: Schema.Literals([
"relayUrl",
"relayIssuer",
"environmentCredential",
"cloudUserId",
"cloudMintPublicKey",
]),
},
) {
override get message(): string {
return RELAY_CONFIG_FIELD_MESSAGES[this.field];
}
}
export class CloudLinkOriginInvalidError extends Schema.TaggedError<CloudLinkOriginInvalidError>()(
"CloudLinkOriginInvalidError",
{ origin: Schema.Literals(["endpoint", "local"]) },
) {
override get message(): string {
return this.origin === "endpoint"
? "Invalid managed endpoint origin."
: "Could not resolve local environment origin.";
}
}
export class CloudLinkNotLinkedError extends Schema.TaggedError<CloudLinkNotLinkedError>()(
"CloudLinkNotLinkedError",
{},
) {
override get message(): string {
return "Link this environment to T3 Connect first.";
}
}
export class CloudLinkAccountMismatchError extends Schema.TaggedError<CloudLinkAccountMismatchError>()(
"CloudLinkAccountMismatchError",
{},
) {
override get message(): string {
return "This environment is already linked to a different cloud account. Unlink it before switching accounts.";
}
}
export class CloudLinkAuthorizationMissingError extends Schema.TaggedError<CloudLinkAuthorizationMissingError>()(
"CloudLinkAuthorizationMissingError",
{},
) {
override get message(): string {
return "Run `t3 connect link` to authorize this environment.";
}
}
const SignedRelayRequest = Schema.Literals(["health", "mint"]);
export class CloudLinkProofRejectedError extends Schema.TaggedError<CloudLinkProofRejectedError>()(
"CloudLinkProofRejectedError",
{ request: SignedRelayRequest },
) {
override get message(): string {
return `Invalid cloud ${this.request} request.`;
}
}
export class CloudLinkProofReplayedError extends Schema.TaggedError<CloudLinkProofReplayedError>()(
"CloudLinkProofReplayedError",
{ request: SignedRelayRequest },
) {
override get message(): string {
return `Cloud ${this.request} request was already consumed.`;
}
}
export class CloudLinkTunnelSupersededError extends Schema.TaggedError<CloudLinkTunnelSupersededError>()(
"CloudLinkTunnelSupersededError",
{},
) {
override get message(): string {
return "The managed tunnel configuration changed during registration.";
}
}
export class CloudLinkEndpointUnavailableError extends Schema.TaggedError<CloudLinkEndpointUnavailableError>()(
"CloudLinkEndpointUnavailableError",
{
reason: Schema.Literals(["runtime-not-started", "origin-unconfirmed"]),
endpointRuntimeStatus: Schema.Unknown,
},
) {
override get message(): string {
return this.reason === "runtime-not-started"
? "Managed endpoint runtime could not be started."
: "Managed endpoint origin could not be confirmed.";
}
}
const INTERNAL_OPERATION_MESSAGES = {
"relay-url-unconfigured":
"T3CODE_RELAY_URL must be configured as a secure absolute HTTPS origin.",
"generate-link-proof": "Could not generate environment link proof.",
"persist-relay-config": "Could not persist environment relay configuration.",
"register-endpoint-origin": "Could not register the managed endpoint origin.",
"resolve-server-origin": "Could not resolve the local server origin.",
"persist-desired-link": "Could not persist desired T3 Connect link state.",
"sign-recovery-proof": "Could not sign the managed tunnel recovery request.",
"unsupported-recovered-tunnel":
"T3 Connect returned an unsupported managed tunnel configuration.",
"persist-recovered-tunnel": "Could not persist the recovered managed tunnel configuration.",
"read-relay-config": "Could not read environment relay configuration.",
"remove-relay-config": "Could not remove environment relay configuration.",
"update-webhook-settings": "Could not update T3 Connect webhook settings.",
"read-preferences": "Could not read environment cloud preferences.",
"persist-preferences": "Could not persist environment cloud preferences.",
"answer-health": "Could not answer cloud health request.",
"issue-credential": "Could not issue cloud connection credential.",
} as const;
export class CloudLinkInternalError extends Schema.TaggedError<CloudLinkInternalError>()(
"CloudLinkInternalError",
{
operation: Schema.Literals(
Object.keys(INTERNAL_OPERATION_MESSAGES) as [
keyof typeof INTERNAL_OPERATION_MESSAGES,
...Array<keyof typeof INTERNAL_OPERATION_MESSAGES>,
],
),
cause: Schema.optional(Schema.Defect()),
},
) {
override get message(): string {
return INTERNAL_OPERATION_MESSAGES[this.operation];
}
}
const internalError =
(operation: CloudLinkInternalError["operation"]) =>
(cause: unknown): Effect.Effect<never, CloudLinkInternalError> =>
Effect.fail(new CloudLinkInternalError({ operation, cause }));
const isPermanentLinkError = Schema.is(
Schema.Union([
CloudLinkRelayConfigInvalidError,
CloudLinkOriginInvalidError,
CloudLinkNotLinkedError,
CloudLinkAccountMismatchError,
CloudLinkAuthorizationMissingError,
CloudLinkProofRejectedError,
CloudLinkProofReplayedError,
CloudLinkTunnelSupersededError,
]),
);
export const shouldRetryCloudLink = (error: unknown): boolean =>
shouldRetryRelayRequest(error) &&
!isPermanentLinkError(error) &&
!(CliTokenManager.isCloudCliCredentialRefreshError(error) && error.requiresLogin);
const rollbackFailed = (cause: unknown) =>
Effect.logWarning("Could not roll back a T3 Connect preference", { cause });
const requireRelayUrl = relayUrlConfig.pipe(
Effect.mapError(
(cause) => new CloudLinkInternalError({ operation: "relay-url-unconfigured", cause }),
),
);
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.forEach(
input.names,
(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 validateCloudMintPublicKey(
publicKey: string,
): Effect.Effect<void, CloudLinkRelayConfigInvalidError> {
const invalid = new CloudLinkRelayConfigInvalidError({ field: "cloudMintPublicKey" });
return Effect.try({
try: () => NodeCrypto.createPublicKey(publicKey.replace(/\\n/g, "\n")),
catch: () => invalid,
}).pipe(
Effect.flatMap((key) =>
key.asymmetricKeyType === "ed25519" ? Effect.void : Effect.fail(invalid),
),
);
}
function invalidRelayConfigField(
payload: RelayEnvironmentConfigRequest,
): CloudLinkRelayConfigInvalidError["field"] | null {
if (!isSecureRelayUrl(payload.relayUrl)) return "relayUrl";
if (payload.relayIssuer !== undefined && !isSecureRelayUrl(payload.relayIssuer)) {
return "relayIssuer";
}
if (payload.environmentCredential.trim().length === 0) return "environmentCredential";
if (payload.cloudUserId.trim().length === 0) return "cloudUserId";
return null;
}
function validateRelayConfigPayload(
payload: RelayEnvironmentConfigRequest,
): Effect.Effect<void, CloudLinkRelayConfigInvalidError> {
const field = invalidRelayConfigField(payload);
return field === null
? Effect.void
: Effect.fail(new CloudLinkRelayConfigInvalidError({ field }));
}
function normalizePemForSignedPayload(value: string): string {
return value.trim();
}
const decodeCloudHealthProof = Schema.decodeUnknownEffect(RelayCloudEnvironmentHealthProofPayload);
const decodeCloudMintProof = Schema.decodeUnknownEffect(RelayCloudMintCredentialProofPayload);
type ManagedTunnelRecoveryProofInput = {
readonly environmentId: RelayManagedEndpointRecoveryProofPayload["environmentId"];
readonly cloudUserId: string;
readonly relayUrl: string;
} & (
| {
readonly action: "register";
readonly tunnelId: string;
readonly origin: RelayManagedEndpointOrigin;
}
| { readonly action: "recover"; readonly origin: RelayManagedEndpointOrigin }
);
export type CloudLinkError =
| CloudLinkAccountMismatchError
| CloudLinkAuthorizationMissingError
| CloudLinkEndpointUnavailableError
| CloudLinkInternalError
| CloudLinkNotLinkedError
| CloudLinkOriginInvalidError
| CloudLinkProofRejectedError
| CloudLinkProofReplayedError
| CloudLinkRelayConfigInvalidError
| CloudLinkTunnelSupersededError;
type CloudLinkBackgroundError =
| CloudLinkEndpointUnavailableError
| CloudLinkInternalError
| CloudLinkOriginInvalidError
| RelayRequestError
| ServerSecretStore.SecretStoreError
| Schema.SchemaError
| PlatformError.PlatformError;
type ManagedTunnelRegistration =
| { readonly status: "not_linked" | "superseded" | "revoked" }
| { readonly status: "recovery_required"; readonly config: RelayManagedEndpointRuntimeConfig }
| {
readonly status: "ready";
readonly endpointRuntimeStatus: ManagedEndpointRuntime.CloudManagedEndpointRuntimeStatus;
};
export class CloudLink extends Context.Service<
CloudLink,
{
/**
* Signs the proof the relay asks for to link this environment. The request
* must reach this server directly. Desktop bootstrap sessions may use the
* WSL IP; all other sessions must use the loopback origin they name.
*/
readonly linkProof: (
request: RelayLinkProofRequest,
httpRequest: HttpServerRequest.HttpServerRequest,
) => Effect.Effect<
RelayEnvironmentLinkProof,
| CloudLinkOriginInvalidError
| CloudLinkInternalError
| EnvironmentAuth.ServerAuthInternalError,
EnvironmentAuthenticatedPrincipal
>;
readonly applyRelayConfig: (
payload: RelayEnvironmentConfigRequest,
) => Effect.Effect<
EnvironmentCloudRelayConfigResult,
| CloudLinkAccountMismatchError
| CloudLinkEndpointUnavailableError
| CloudLinkInternalError
| CloudLinkOriginInvalidError
| CloudLinkRelayConfigInvalidError
| CloudLinkTunnelSupersededError
| EnvironmentAuth.ServerAuthInternalError
| RelayRequestError
>;
readonly linkState: () => Effect.Effect<
EnvironmentCloudLinkStateResult,
CloudLinkInternalError
>;
readonly unlink: () => Effect.Effect<EnvironmentCloudRelayConfigResult, CloudLinkInternalError>;
readonly updatePreferences: (
input: EnvironmentCloudPreferencesRequest,
) => Effect.Effect<
EnvironmentCloudLinkStateResult,
CloudLinkNotLinkedError | CloudLinkInternalError
>;
readonly answerHealthRequest: (
request: RelayCloudEnvironmentHealthRequest,
) => Effect.Effect<
RelayEnvironmentHealthResponse,
| CloudLinkProofRejectedError
| CloudLinkProofReplayedError
| CloudLinkInternalError
| EnvironmentAuth.ServerAuthInternalError
>;
readonly mintCredential: (
request: RelayCloudMintCredentialRequest,
) => Effect.Effect<
RelayEnvironmentMintResponse,
| CloudLinkProofRejectedError
| CloudLinkProofReplayedError
| CloudLinkInternalError
| EnvironmentAuth.ServerAuthInternalError
>;
readonly reconcileDesiredLinkIfStillDesired: (
localOrigin: string,
) => Effect.Effect<
CliDesiredLinkMode | null,
| CloudLinkBackgroundError
| CloudLinkAccountMismatchError
| CloudLinkAuthorizationMissingError
| CloudLinkRelayConfigInvalidError
| EnvironmentAuth.ServerAuthInternalError
| CliTokenManager.CloudCliTokenManagerError
>;
readonly registerManagedTunnelRecovery: (
localOrigin: string,
options?: { readonly retryRuntimeFailures?: boolean },
) => Effect.Effect<ManagedTunnelRegistration, CloudLinkBackgroundError>;
readonly recoverManagedTunnel: (
localOrigin: string,
expectedConfig?: RelayManagedEndpointRuntimeConfig,
options?: { readonly retryRuntimeFailures?: boolean },
) => Effect.Effect<boolean, CloudLinkBackgroundError>;
readonly startManagedTunnelIfOriginConfirmed: (
localOrigin: string,
options?: { readonly requireConfirmedOrigin?: boolean },
) => Effect.Effect<
boolean,
| CloudLinkEndpointUnavailableError
| CloudLinkOriginInvalidError
| ServerSecretStore.SecretStoreError
>;
readonly releaseManagedTunnelOnShutdown: () => Effect.Effect<
boolean,
| CloudLinkBackgroundError
| CliTokenManager.CloudCliTokenManagerError
| HttpClientError.HttpClientError
>;
}
>()("t3/cloud/CloudLink") {}
const make = Effect.gen(function* () {
const secrets = yield* ServerSecretStore.ServerSecretStore;
const environment = yield* ServerEnvironment.ServerEnvironment;
const endpointRuntime = yield* ManagedEndpointRuntime.CloudManagedEndpointRuntime;
const reachability = yield* ConnectReachability.ConnectReachability;
const environmentAuth = yield* EnvironmentAuth.EnvironmentAuth;
const cliTokenManager = yield* CliTokenManager.CloudCliTokenManager;
const httpClient = yield* HttpClient.HttpClient;
const httpServer = yield* HttpServer.HttpServer;
const awarenessRelay = yield* AgentAwarenessRelay.AgentAwarenessRelay;
const crypto = yield* Crypto.Crypto;
const config = yield* ServerConfig.ServerConfig;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const withSecrets = Effect.provideService(ServerSecretStore.ServerSecretStore, secrets);
const withRuntimeFiles = <A, E>(
effect: Effect.Effect<A, E, ServerConfig.ServerConfig | FileSystem.FileSystem | Path.Path>,
) =>
effect.pipe(
Effect.provideService(ServerConfig.ServerConfig, config),
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(Path.Path, path),
);
const validateLinkedCloudUser = (
cloudUserId: string,
): Effect.Effect<void, EnvironmentAuth.ServerAuthInternalError | CloudLinkAccountMismatchError> =>
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 === cloudUserId
? Effect.void
: Effect.fail(new CloudLinkAccountMismatchError({}));
}),
);
const readInstalledCloudUserId: Effect.Effect<string, EnvironmentAuth.ServerAuthInternalError> =
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({})),
),
);
const makeCloudLinkProof = Effect.fn("environment.cloud.makeLinkProof")(function* (
request: RelayLinkProofRequest,
requestUrl: string,
allowNonLoopbackRequestHost = false,
) {
const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
if (
!isSupportedLinkProviderKind(request) ||
!isAllowedEndpointOrigin({
origin: request.origin,
requestUrl,
allowNonLoopbackRequestHost,
})
) {
return yield* new CloudLinkOriginInvalidError({ origin: "endpoint" });
}
const now = yield* DateTime.now;
const expiresAt = DateTime.add(now, { minutes: 5 });
const nowSeconds = Math.floor(now.epochMilliseconds / 1_000);
const descriptor = yield* environment.getDescriptor;
const payload = {
iss: `t3-env:${descriptor.environmentId}`,
aud: normalizeRelayIssuer(request.relayIssuer),
sub: descriptor.environmentId,
jti: yield* 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 linkProof = Effect.fn("environment.cloud.linkProof")(
function* (request: RelayLinkProofRequest, httpRequest: HttpServerRequest.HttpServerRequest) {
const requestUrl = requestAbsoluteUrl(httpRequest);
if (requestUrl === null || hasForwardedAuthorityHeaders(httpRequest)) {
return yield* new CloudLinkOriginInvalidError({ origin: "endpoint" });
}
const principal = yield* EnvironmentAuthenticatedPrincipal;
// WSL desktop requests use the distro IP; only the server-issued bootstrap subject qualifies.
return yield* makeCloudLinkProof(
request,
requestUrl,
principal.subject === "desktop-bootstrap",
);
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("generate-link-proof")),
Effect.catchTags({ PlatformError: internalError("generate-link-proof") }),
);
const activateManagedTunnel = Effect.fn("environment.cloud.activateManagedTunnel")(
function* (input: {
readonly config: RelayManagedEndpointRuntimeConfig;
readonly configJson: string;
readonly origin: RelayManagedEndpointOrigin;
}) {
return yield* endpointRuntime.withLinkStateLock(
Effect.gen(function* () {
const currentConfig = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG);
if (
Option.isNone(currentConfig) ||
bytesToString(currentConfig.value) !== input.configJson
) {
return null;
}
const status = yield* endpointRuntime.applyConfig(input.config);
if (status.status !== "running") {
return yield* new CloudLinkEndpointUnavailableError({
reason: "runtime-not-started",
endpointRuntimeStatus: status,
});
}
const marker = yield* encodeConfirmedOriginJson({
config: input.config,
origin: input.origin,
});
yield* secrets.set(CLOUD_ENDPOINT_CONFIRMED_ORIGIN, stringToBytes(marker));
return status;
}),
);
},
);
const activateManagedTunnelWithRetry = (
input: {
readonly config: RelayManagedEndpointRuntimeConfig;
readonly configJson: string;
readonly origin: RelayManagedEndpointOrigin;
},
retryRuntimeFailures: boolean,
) => {
const activate = activateManagedTunnel(input);
return retryRuntimeFailures
? activate.pipe(
Effect.retry({
while: (error) =>
error._tag === "CloudLinkEndpointUnavailableError" &&
ManagedEndpointRuntime.isRetryableManagedEndpointRuntimeStatus(
error.endpointRuntimeStatus,
),
schedule: Schedule.exponential("1 second").pipe(
Schedule.modifyDelay(({ duration }) =>
Effect.succeed(Duration.min(duration, Duration.seconds(30))),
),
Schedule.jittered,
),
}),
)
: activate;
};
const startManagedTunnelIfOriginConfirmed = Effect.fn(
"environment.cloud.startManagedCloudTunnelIfOriginConfirmed",
)(function* (localOrigin: string, options?: { readonly requireConfirmedOrigin?: boolean }) {
const requireConfirmedOrigin = options?.requireConfirmedOrigin ?? true;
const parsedOrigin = yield* Effect.try({
try: () => parseManagedEndpointLocalOrigin(localOrigin),
catch: () => new CloudLinkOriginInvalidError({ origin: "local" }),
});
return yield* endpointRuntime.withLinkStateLock(
Effect.gen(function* () {
const [runtimeBytes, markerBytes] = yield* Effect.all([
secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
secrets.get(CLOUD_ENDPOINT_CONFIRMED_ORIGIN),
]);
if (Option.isNone(runtimeBytes)) return false;
const config = Option.getOrNull(decodeRuntimeConfig(bytesToString(runtimeBytes.value)));
if (config === null || config.providerKind !== "cloudflare_tunnel") return false;
// Require the relay-confirmed port unless falling back after the relay stayed unreachable.
if (requireConfirmedOrigin) {
if (Option.isNone(markerBytes)) return false;
const marker = Option.getOrNull(decodeConfirmedOrigin(bytesToString(markerBytes.value)));
if (
marker === null ||
!managedEndpointRuntimeConfigsMatch(marker.config, config) ||
marker.origin.localHttpHost !== parsedOrigin.origin.localHttpHost ||
marker.origin.localHttpPort !== parsedOrigin.origin.localHttpPort
) {
return false;
}
}
const status = yield* endpointRuntime.applyConfig(config);
if (status.status !== "running") {
return yield* new CloudLinkEndpointUnavailableError({
reason: "runtime-not-started",
endpointRuntimeStatus: status,
});
}
return true;
}),
);
});
const applyCloudRelayConfig = Effect.fn("environment.cloud.applyRelayConfig")(function* (
payload: RelayEnvironmentConfigRequest,
options?: {
readonly lockHeld?: boolean;
readonly confirmedOrigin?: RelayManagedEndpointOrigin;
},
) {
const apply = Effect.gen(function* () {
yield* validateRelayConfigPayload(payload);
yield* validateLinkedCloudUser(payload.cloudUserId);
yield* validateCloudMintPublicKey(payload.cloudMintPublicKey);
// Reject unsupported runtimes before they can stop a healthy tunnel.
if (
payload.endpointRuntime !== null &&
payload.endpointRuntime.providerKind !== "cloudflare_tunnel"
) {
return yield* new CloudLinkEndpointUnavailableError({
reason: "runtime-not-started",
endpointRuntimeStatus: {
status: "unsupported",
providerKind: payload.endpointRuntime.providerKind,
},
});
}
const endpointRuntimeJson = payload.endpointRuntime
? yield* encodeEndpointRuntimeConfigJson(payload.endpointRuntime)
: null;
yield* endpointRuntime.applyConfig(null);
yield* secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN);
// Store runtime config last so partial writes cannot appear managed or respawn a stale tunnel.
yield* secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG);
yield* secrets.set(RELAY_URL_SECRET, stringToBytes(payload.relayUrl));
yield* secrets.set(
RELAY_ISSUER_SECRET,
stringToBytes(payload.relayIssuer ?? payload.relayUrl),
);
yield* secrets.set(CLOUD_LINKED_USER_ID, stringToBytes(payload.cloudUserId));
yield* secrets.set(
RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
stringToBytes(payload.environmentCredential),
);
yield* secrets.remove(CLOUD_LINK_REVOKED);
yield* secrets.set(CLOUD_MINT_PUBLIC_KEY, stringToBytes(payload.cloudMintPublicKey));
yield* awarenessRelay.requestCatchUp();
if (endpointRuntimeJson !== null) {
yield* secrets.set(CLOUD_ENDPOINT_RUNTIME_CONFIG, stringToBytes(endpointRuntimeJson));
}
if (payload.endpointRuntime === null || options?.confirmedOrigin === undefined) {
return {
ok: true,
endpointRuntimeStatus: { status: "disabled" },
} satisfies EnvironmentCloudRelayConfigResult;
}
const endpointRuntimeStatus = yield* endpointRuntime.applyConfig(payload.endpointRuntime);
if (endpointRuntimeStatus.status !== "running") {
return yield* new CloudLinkEndpointUnavailableError({
reason: "runtime-not-started",
endpointRuntimeStatus,
});
}
const marker = yield* encodeConfirmedOriginJson({
config: payload.endpointRuntime,
origin: options.confirmedOrigin,
});
yield* secrets.set(CLOUD_ENDPOINT_CONFIRMED_ORIGIN, stringToBytes(marker));
return { ok: true, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult;
});
return yield* options?.lockHeld ? apply : endpointRuntime.withLinkStateLock(apply);
});
const relayClientRequest = <A>(input: {
readonly url: string;
readonly token: string;
readonly payload: unknown;
readonly schema: Schema.Decoder<A>;
readonly timeout?: Duration.Input;
}) =>
HttpClientRequest.post(input.url).pipe(
HttpClientRequest.bearerToken(input.token),
HttpClientRequest.bodyJson(input.payload),
Effect.flatMap(httpClient.execute),
Effect.flatMap(filterRelayResponse),
Effect.flatMap(HttpClientResponse.schemaBodyJson(input.schema)),
Effect.timeout(input.timeout ?? "10 seconds"),
Effect.mapError(relayRequestError),
withRelayClientTracing,
);
const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesiredLinkWith")(
function* (localOrigin: string) {
const parsedOrigin = yield* Effect.try({
try: () => parseManagedEndpointLocalOrigin(localOrigin),
catch: () => new CloudLinkOriginInvalidError({ origin: "local" }),
});
const token = yield* cliTokenManager.getExisting.pipe(
Effect.flatMap(
Option.match({
onNone: () => Effect.fail(new CloudLinkAuthorizationMissingError({})),
onSome: Effect.succeed,
}),
),
);
const mode = yield* readCliDesiredLinkMode.pipe(withSecrets);
const managedTunnelsEnabled = mode !== "publish_only";
const relayUrl = yield* requireRelayUrl;
const challenge = yield* relayClientRequest({
url: `${relayUrl}/v1/client/environment-link-challenges`,
token: token.accessToken,
payload: {
notificationsEnabled: true,
liveActivitiesEnabled: true,
managedTunnelsEnabled,
},
schema: RelayEnvironmentLinkChallengeResponse,
});
const proof = yield* makeCloudLinkProof(
{
challenge: challenge.challenge,
relayIssuer: relayUrl,
endpoint: {
httpBaseUrl: parsedOrigin.httpBaseUrl,
wsBaseUrl: parsedOrigin.wsBaseUrl,
providerKind: managedTunnelsEnabled ? "cloudflare_tunnel" : "manual",
},
origin: parsedOrigin.origin,
},
parsedOrigin.httpBaseUrl,
);
const link = yield* relayClientRequest({
url: `${relayUrl}/v1/client/environment-links`,
token: token.accessToken,
payload: {
proof,
notificationsEnabled: true,
liveActivitiesEnabled: true,
managedTunnelsEnabled,
},
schema: RelayEnvironmentLinkResponse,
timeout: MANAGED_ENDPOINT_PROVISION_REQUEST_TIMEOUT,
});
yield* setCliDesiredCloudLink(true, mode).pipe(withSecrets);
yield* applyCloudRelayConfig(
{
relayUrl,
relayIssuer: link.relayIssuer,
cloudUserId: link.cloudUserId,
environmentCredential: link.environmentCredential,
cloudMintPublicKey: link.cloudMintPublicKey,
endpointRuntime: link.endpointRuntime,
},
{
lockHeld: true,
confirmedOrigin: parsedOrigin.origin,
},
);
return mode;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("persist-desired-link")),
);
const reconcileDesiredLinkIfStillDesired = Effect.fn(
"environment.cloud.reconcileDesiredLinkIfStillDesired",
)(function* (localOrigin: string) {
return yield* endpointRuntime.withLinkStateLock(
Effect.gen(function* () {
if (!(yield* readCliDesiredCloudLink.pipe(withSecrets))) {
return null;
}
return yield* reconcileDesiredCloudLinkWith(localOrigin);
}),
);
});
const makeManagedTunnelRecoveryProof = Effect.fn(
"environment.cloud.makeManagedTunnelRecoveryProof",
)(function* (input: ManagedTunnelRecoveryProofInput) {
const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
const configuredIssuer = yield* secrets.get(RELAY_ISSUER_SECRET);
const now = yield* DateTime.now;
const issuedAt = Math.floor(now.epochMilliseconds / 1_000);
const claims = {
iss: `t3-env:${input.environmentId}`,
aud: normalizeRelayIssuer(
Option.isSome(configuredIssuer) ? bytesToString(configuredIssuer.value) : input.relayUrl,
),
sub: input.environmentId,
jti: yield* crypto.randomUUIDv4(),
iat: issuedAt,
exp: issuedAt + 60,
environmentId: input.environmentId,
cloudUserId: input.cloudUserId,
};
const payload =
input.action === "register"
? {
...claims,
action: "register" as const,
tunnelId: input.tunnelId,
origin: input.origin,
}
: { ...claims, action: "recover" as const, origin: input.origin };
return yield* signRelayJwt({
privateKey: keyPair.privateKey,
typ: RELAY_MANAGED_TUNNEL_RECOVERY_TYP,
payload,
}).pipe(
Effect.mapError(
(cause) => new CloudLinkInternalError({ operation: "sign-recovery-proof", cause }),
),
);
});
/** Keep credentials: clock skew can cause 401s. A relink during the request returns false without marking; accepted registration clears the mark. */
const markLinkRevoked = (credential: string, cause: RelayRequestError) =>
endpointRuntime.withLinkStateLock(
Effect.gen(function* () {
const current = yield* secrets.get(RELAY_ENVIRONMENT_CREDENTIAL_SECRET);
if (Option.isNone(current) || bytesToString(current.value) !== credential) {
return false;
}
yield* Effect.logWarning(
"T3 Connect no longer accepts this environment's link. Link it again to restore remote access.",
{ cause },
);
yield* secrets.set(CLOUD_LINK_REVOKED, stringToBytes("true"));
return true;
}),
);
const registerManagedTunnelRecovery = Effect.fn(
"environment.cloud.registerManagedCloudTunnelRecovery",
)(function* (localOrigin: string, options?: { readonly retryRuntimeFailures?: boolean }) {
const [runtimeConfig, relayUrl, cloudUserId, environmentCredential] = yield* Effect.all([
secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
secrets.get(RELAY_URL_SECRET),
secrets.get(CLOUD_LINKED_USER_ID),
secrets.get(RELAY_ENVIRONMENT_CREDENTIAL_SECRET),
]);
if (
Option.isNone(runtimeConfig) ||
Option.isNone(relayUrl) ||
Option.isNone(cloudUserId) ||
Option.isNone(environmentCredential)
) {
return { status: "not_linked" as const };
}
const config = Option.getOrNull(decodeRuntimeConfig(bytesToString(runtimeConfig.value)));
if (config?.providerKind !== "cloudflare_tunnel") {
return { status: "not_linked" as const };
}
const parsedOrigin = yield* Effect.try({
try: () => parseManagedEndpointLocalOrigin(localOrigin),
catch: () => new CloudLinkOriginInvalidError({ origin: "local" }),
});
if (config.tunnelId === undefined) {
return { status: "recovery_required" as const, config };
}
const origin = parsedOrigin.origin;
const environmentId = yield* environment.getEnvironmentId;
const relayUrlValue = bytesToString(relayUrl.value);
const cloudUserIdValue = bytesToString(cloudUserId.value);
const proof = yield* makeManagedTunnelRecoveryProof({
action: "register",
environmentId,
cloudUserId: cloudUserIdValue,
relayUrl: relayUrlValue,
tunnelId: config.tunnelId,
origin,
});
const credential = bytesToString(environmentCredential.value);
const registered = yield* relayClientRequest({
url: `${relayUrlValue}/v1/environments/${encodeURIComponent(environmentId)}/tunnel/recovery`,
token: credential,
payload: {
cloudUserId: cloudUserIdValue,
tunnelId: config.tunnelId,
origin,
proof,
},
schema: RelayManagedEndpointRecoveryRegistrationResponse,
}).pipe(
Effect.catchTags({
RelayRequestError: (error) =>
error.rejection === "unauthorized"
? markLinkRevoked(credential, error).pipe(
Effect.map((marked) => (marked ? ("revoked" as const) : ("superseded" as const))),
)
: Effect.fail(error),
}),
);
if (registered === "revoked" || registered === "superseded") {
return { status: registered };
}
yield* secrets.remove(CLOUD_LINK_REVOKED);
if (registered.status === "recovery_required") {
return { status: registered.status, config };
}
const endpointRuntimeStatus = yield* activateManagedTunnelWithRetry(
{
config,
configJson: bytesToString(runtimeConfig.value),
origin,
},
options?.retryRuntimeFailures === true,
);
return endpointRuntimeStatus === null
? { status: "superseded" as const }
: { status: "ready" as const, endpointRuntimeStatus };
});
const recoverManagedTunnel = Effect.fn("environment.cloud.recoverManagedCloudTunnel")(function* (
localOrigin: string,
expectedConfig?: RelayManagedEndpointRuntimeConfig,
options?: { readonly retryRuntimeFailures?: boolean },
) {
const [runtimeConfig, relayUrl, cloudUserId, environmentCredential] = yield* Effect.all([
secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
secrets.get(RELAY_URL_SECRET),
secrets.get(CLOUD_LINKED_USER_ID),
secrets.get(RELAY_ENVIRONMENT_CREDENTIAL_SECRET),
]);
if (
Option.isNone(runtimeConfig) ||
Option.isNone(relayUrl) ||
Option.isNone(cloudUserId) ||
Option.isNone(environmentCredential)
) {
return false;
}
if (expectedConfig !== undefined) {
const current = Option.getOrNull(decodeRuntimeConfig(bytesToString(runtimeConfig.value)));
if (
current === null ||
current.providerKind !== expectedConfig.providerKind ||
current.connectorToken !== expectedConfig.connectorToken ||
current.tunnelId !== expectedConfig.tunnelId ||
current.tunnelName !== expectedConfig.tunnelName
) {
return false;
}
}
const parsedOrigin = yield* Effect.try({
try: () => parseManagedEndpointLocalOrigin(localOrigin),
catch: () => new CloudLinkOriginInvalidError({ origin: "local" }),
});
const environmentId = yield* environment.getEnvironmentId;
const relayUrlValue = bytesToString(relayUrl.value);
const cloudUserIdValue = bytesToString(cloudUserId.value);
const origin = parsedOrigin.origin;
const proof = yield* makeManagedTunnelRecoveryProof({
action: "recover",
environmentId,
cloudUserId: cloudUserIdValue,
relayUrl: relayUrlValue,
origin,
});
const credential = bytesToString(environmentCredential.value);
const recovered = yield* relayClientRequest({
url: `${relayUrlValue}/v1/environments/${encodeURIComponent(environmentId)}/tunnel`,
token: credential,
payload: {
cloudUserId: cloudUserIdValue,
origin,
proof,
},
schema: RelayManagedEndpointRecoveryResponse,
timeout: MANAGED_ENDPOINT_PROVISION_REQUEST_TIMEOUT,
}).pipe(
Effect.tapError((error) =>
error.rejection === "unauthorized" ? markLinkRevoked(credential, error) : Effect.void,
),
);
if (recovered.endpointRuntime.providerKind !== "cloudflare_tunnel") {
return yield* new CloudLinkInternalError({ operation: "unsupported-recovered-tunnel" });
}
const encoded = yield* encodeEndpointRuntimeConfigJson(recovered.endpointRuntime).pipe(
Effect.mapError(
(cause) => new CloudLinkInternalError({ operation: "persist-recovered-tunnel", cause }),
),
);
const stored = yield* endpointRuntime.withLinkStateLock(
Effect.gen(function* () {
const currentConfig = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG);
if (
Option.isNone(currentConfig) ||
bytesToString(currentConfig.value) !== bytesToString(runtimeConfig.value)
) {
return false;
}
yield* secrets.set(CLOUD_ENDPOINT_RUNTIME_CONFIG, stringToBytes(encoded));
yield* secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN);
yield* secrets.remove(CLOUD_LINK_REVOKED);
return true;
}),
);
if (!stored) return false;
const status = yield* activateManagedTunnelWithRetry(
{
config: recovered.endpointRuntime,
configJson: encoded,
origin,
},
options?.retryRuntimeFailures === true,
);
return status !== null;
});
const applyRelayConfig = Effect.fn("environment.cloud.relayConfig")(
function* (payload: RelayEnvironmentConfigRequest) {
const result = yield* applyCloudRelayConfig(payload);
if (payload.endpointRuntime?.providerKind === "cloudflare_tunnel") {
const address = httpServer.address;
if (typeof address === "string" || !("port" in address)) {
return yield* new CloudLinkInternalError({ operation: "resolve-server-origin" });
}
const registration = yield* registerManagedTunnelRecovery(
`http://${loopbackUrlHostForAddress(address)}:${address.port}`,
).pipe(
Effect.retry({
times: 2,
while: (error) =>
shouldRetryCloudLink(error) && error._tag !== "CloudLinkEndpointUnavailableError",
}),
);
if (registration.status === "superseded") {
return yield* new CloudLinkTunnelSupersededError({});
}
if (registration.status === "recovery_required") {
yield* endpointRuntime.requestRecovery(registration.config);
}
if (registration.status !== "ready") {
return yield* new CloudLinkEndpointUnavailableError({
reason: "origin-unconfirmed",
endpointRuntimeStatus: { status: "disabled" },
});
}
return {
ok: true,
endpointRuntimeStatus: registration.endpointRuntimeStatus,
} satisfies EnvironmentCloudRelayConfigResult;
}
return result;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("persist-relay-config")),
Effect.catchTags({
SchemaError: internalError("persist-relay-config"),
PlatformError: internalError("register-endpoint-origin"),
}),
);
// Cloudflare bills provisioned tunnels even while environments are offline.
const releaseManagedTunnelOnShutdown = Effect.fn(
"environment.cloud.releaseManagedTunnelOnShutdown",
)(function* () {
const runtimeConfig = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG);
if (Option.isNone(runtimeConfig)) {
return false;
}
// CLI authorization releases only CLI-desired managed links; web/mobile links recover by credential and expire through the relay reaper.
if (
!(yield* readCliDesiredCloudLink.pipe(withSecrets)) ||
(yield* readCliDesiredLinkMode.pipe(withSecrets)) !== "managed"
) {
return false;
}
// Update handoffs reuse the tunnel: replacing it adds 1-2 minutes of hostname propagation.
if (
(yield* withRuntimeFiles(desktopUpdateRestartPending)) ||
(yield* withRuntimeFiles(pendingUpdateHandoffExists))
) {
yield* Effect.logInfo("Keeping the managed tunnel across the update restart");
return false;
}
const token = yield* cliTokenManager.getExisting;
if (Option.isNone(token)) {
return false;
}
// Use the persisted relay URL; T3CODE_RELAY_URL may have changed since linking.
const relayUrl = yield* secrets.get(RELAY_URL_SECRET);
if (Option.isNone(relayUrl)) {
return false;
}
const environmentId = yield* environment.getEnvironmentId;
yield* endpointRuntime.applyConfig(null);
const response = yield* HttpClientRequest.delete(
`${bytesToString(relayUrl.value)}/v1/client/environment-links/${encodeURIComponent(environmentId)}/tunnel`,
).pipe(
HttpClientRequest.bearerToken(token.value.accessToken),
httpClient.execute,
Effect.flatMap(filterRelayResponse),
Effect.flatMap(HttpClientResponse.schemaBodyJson(RelayOkResponse)),
withRelayClientTracing,
);
// ok:false means concurrent provisioning owns the tunnel; preserve its config.
if (!response.ok) {
return false;
}
// Released tokens are dead; remove only this shutdown's config to preserve a fast restart's replacement.
const storedConfig = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG);
if (
Option.isSome(storedConfig) &&
bytesToString(storedConfig.value) === bytesToString(runtimeConfig.value)
) {
yield* secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG);
yield* secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN);
}
return true;
});
const readCloudLinkState = Effect.fn("environment.cloud.readLinkState")(function* () {
const [
cloudUserId,
relayUrl,
relayIssuer,
endpointRuntimeConfig,
publishAgentActivity,
holdWebhooks,
revoked,
latestReachability,
] = yield* Effect.all(
[
secrets.get(CLOUD_LINKED_USER_ID),
secrets.get(RELAY_URL_SECRET),
secrets.get(RELAY_ISSUER_SECRET),
secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
secrets.get(PUBLISH_AGENT_ACTIVITY_SECRET),
secrets.get(HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET),
secrets.get(CLOUD_LINK_REVOKED),
reachability.latest,
],
{ concurrency: 8 },
);
return {
linked: Option.isSome(cloudUserId),
linkRevoked: Option.isSome(cloudUserId) && Option.isSome(revoked),
cloudUserId: Option.isSome(cloudUserId) ? bytesToString(cloudUserId.value) : null,
relayUrl: Option.isSome(relayUrl) ? bytesToString(relayUrl.value) : null,
relayIssuer: Option.isSome(relayIssuer) ? bytesToString(relayIssuer.value) : null,
managedTunnelActive: Option.isSome(endpointRuntimeConfig),
publishAgentActivity: Option.isSome(publishAgentActivity)
? bytesToString(publishAgentActivity.value) === "true"
: false,
holdWebhooksWhileOffline:
Option.isSome(holdWebhooks) && bytesToString(holdWebhooks.value) === "true",
...(Option.isSome(latestReachability) ? { reachability: latestReachability.value } : {}),
} satisfies EnvironmentCloudLinkStateResult;
});
const linkState = Effect.fn("environment.cloud.linkState")(
function* () {
return yield* readCloudLinkState();
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("read-relay-config")),
);
const unlink = Effect.fn("environment.cloud.unlink")(
function* () {
return yield* endpointRuntime.withLinkStateLock(
Effect.gen(function* () {
const endpointRuntimeStatus = yield* endpointRuntime.applyConfig(null);
yield* Effect.all(
[
secrets.remove(CLOUD_LINKED_USER_ID),
secrets.remove(RELAY_URL_SECRET),
secrets.remove(RELAY_ISSUER_SECRET),
secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET),
secrets.remove(CLOUD_MINT_PUBLIC_KEY),
secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG),
secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN),
secrets.remove(PUBLISH_AGENT_ACTIVITY_SECRET),
secrets.remove(HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET),
secrets.remove(CLOUD_LINK_REVOKED),
],
{ concurrency: 10 },
);
yield* setCliDesiredCloudLink(false).pipe(withSecrets);
return { ok: true, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult;
}),
);
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("remove-relay-config")),
);
const pushHoldWebhooksWhileOffline = Effect.fn("CloudPreferences.pushHoldWebhooksWhileOffline")(
function* (holdWebhooksWhileOffline: boolean) {
const connection = yield* readRelayConnection.pipe(withSecrets);
if (connection === null) {
return yield* new CloudLinkNotLinkedError({});
}
const environmentId = yield* environment.getEnvironmentId;
const client = yield* makeRelayEnvironmentClient(connection);
yield* client.server
.updateLinkPreferences({
params: { environmentId },
payload: { holdWebhooksWhileOffline },
})
.pipe(Effect.timeout("10 seconds"), Effect.catch(internalError("update-webhook-settings")));
},
);
const savePreference = (name: string, value: boolean) =>
secrets
.set(name, stringToBytes(String(value)))
.pipe(Effect.catch(internalError("persist-preferences")));
const preferencesLock = yield* Semaphore.make(1);
const savePreferences = Effect.fn("CloudPreferences.update")(
function* (input: EnvironmentCloudPreferencesRequest) {
// Read rollback targets before changing anything; failed reads must abort.
const readCurrent = (name: string) =>
secrets.get(name).pipe(Effect.catch(internalError("read-preferences")));
const previousActivity = yield* readCurrent(PUBLISH_AGENT_ACTIVITY_SECRET);
const previousHold = yield* readCurrent(HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET);
yield* savePreference(PUBLISH_AGENT_ACTIVITY_SECRET, input.publishAgentActivity);
if (input.holdWebhooksWhileOffline !== undefined) {
const next = input.holdWebhooksWhileOffline;
const previous = Option.match(previousHold, {
onNone: () => false,
onSome: (bytes) => bytesToString(bytes) === "true",
});
yield* pushHoldWebhooksWhileOffline(next).pipe(
Effect.andThen(
savePreference(HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET, next).pipe(
Effect.tapError(() =>
previous === next
? Effect.void
: pushHoldWebhooksWhileOffline(previous).pipe(Effect.catch(rollbackFailed)),
),
),
),
Effect.tapError(() =>
Option.match(previousActivity, {
onNone: () => secrets.remove(PUBLISH_AGENT_ACTIVITY_SECRET),
onSome: (bytes) => secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, bytes),
}).pipe(Effect.catch(rollbackFailed)),
),
);
}
yield* awarenessRelay.requestCatchUp();
},
// Finish or roll back even if the client disconnects mid-update.
Effect.uninterruptible,
preferencesLock.withPermits(1),
);
const updatePreferences = Effect.fn("environment.cloud.preferences")(
function* (input: EnvironmentCloudPreferencesRequest) {
yield* savePreferences(input);
return yield* readCloudLinkState();
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("read-preferences")),
);
const answerHealthRequest = Effect.fn("environment.cloud.health")(
function* (request: RelayCloudEnvironmentHealthRequest) {
const cloudMintPublicKey = yield* secrets
.get(CLOUD_MINT_PUBLIC_KEY)
.pipe(
Effect.flatMap((bytes) =>
Option.isSome(bytes)
? Effect.succeed(bytesToString(bytes.value))
: Effect.fail(new EnvironmentAuth.ServerAuthCloudMintPublicKeyMissingError({})),
),
);
const relayIssuer = yield* secrets
.get(RELAY_ISSUER_SECRET)
.pipe(
Effect.flatMap((bytes) =>
Option.isSome(bytes)
? Effect.succeed(bytesToString(bytes.value))
: secrets
.get(RELAY_URL_SECRET)
.pipe(
Effect.flatMap((fallbackBytes) =>
Option.isSome(fallbackBytes)
? Effect.succeed(bytesToString(fallbackBytes.value))
: Effect.fail(
new EnvironmentAuth.ServerAuthCloudRelayIssuerMissingError({}),
),
),
),
),
);
const environmentId = yield* environment.getEnvironmentId;
const linkedCloudUserId = yield* readInstalledCloudUserId;
const now = yield* DateTime.now;
const nowSeconds = Math.floor(now.epochMilliseconds / 1_000);
const proofOption = yield* verifyRelayJwt({
publicKey: cloudMintPublicKey,
token: request.proof,
typ: RELAY_HEALTH_REQUEST_TYP,
issuer: normalizeRelayIssuer(relayIssuer),
audience: `t3-env:${environmentId}`,
nowEpochSeconds: nowSeconds,
}).pipe(Effect.flatMap(decodeCloudHealthProof), Effect.option);
if (
Option.isNone(proofOption) ||
proofOption.value.environmentId !== environmentId ||
proofOption.value.sub !== linkedCloudUserId ||
!hasBoundedCloudProofLifetime({ ...proofOption.value, nowSeconds }) ||
!hasExactScope({ scopes: proofOption.value.scope, expected: "environment:status" })
) {
return yield* new CloudLinkProofRejectedError({ request: "health" });
}
const proof = proofOption.value;
const jtiSecretName = `${CLOUD_HEALTH_JTI_PREFIX}${proof.jti}`;
const nonceSecretName = `${CLOUD_HEALTH_NONCE_PREFIX}${proof.nonce}`;
const consumedReplayGuards = yield* consumeCloudReplayGuards({
secrets,
names: [jtiSecretName, nonceSecretName],
value: stringToBytes(DateTime.formatIso(now)),
});
if (!consumedReplayGuards) {
return yield* new CloudLinkProofReplayedError({ request: "health" });
}
const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
const descriptor = yield* environment.getDescriptor;
const responseExpiresAt = DateTime.add(now, { minutes: 5 });
const responsePayload = {
iss: `t3-env:${environmentId}`,
aud: normalizeRelayIssuer(relayIssuer),
sub: environmentId,
jti: yield* crypto.randomUUIDv4(),
iat: nowSeconds,
exp: Math.floor(responseExpiresAt.epochMilliseconds / 1_000),
environmentId,
requestNonce: proof.nonce,
status: "online",
descriptor,
checkedAt: DateTime.formatIso(now),
} satisfies RelayEnvironmentHealthResponseProofPayload;
const responseProof = yield* signRelayJwt({
privateKey: keyPair.privateKey,
typ: RELAY_HEALTH_RESPONSE_TYP,
payload: responsePayload,
}).pipe(
Effect.mapError(
(cause) =>
new EnvironmentAuth.ServerAuthCloudHealthJwtSigningError({
cause,
}),
),
);
return {
environmentId,
status: "online",
descriptor,
checkedAt: responsePayload.checkedAt,
proof: responseProof,
} satisfies RelayEnvironmentHealthResponse;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("answer-health")),
Effect.catchTags({ PlatformError: internalError("answer-health") }),
);
const mintCredential = Effect.fn("environment.cloud.mintCredential")(
function* (request: RelayCloudMintCredentialRequest) {
const cloudMintPublicKey = yield* secrets
.get(CLOUD_MINT_PUBLIC_KEY)
.pipe(
Effect.flatMap((bytes) =>
Option.isSome(bytes)
? Effect.succeed(bytesToString(bytes.value))
: Effect.fail(new EnvironmentAuth.ServerAuthCloudMintPublicKeyMissingError({})),
),
);
const relayIssuer = yield* secrets
.get(RELAY_ISSUER_SECRET)
.pipe(
Effect.flatMap((bytes) =>
Option.isSome(bytes)
? Effect.succeed(bytesToString(bytes.value))
: secrets
.get(RELAY_URL_SECRET)
.pipe(
Effect.flatMap((fallbackBytes) =>
Option.isSome(fallbackBytes)
? Effect.succeed(bytesToString(fallbackBytes.value))
: Effect.fail(
new EnvironmentAuth.ServerAuthCloudRelayIssuerMissingError({}),
),
),
),
),
);
const environmentId = yield* environment.getEnvironmentId;
const linkedCloudUserId = yield* readInstalledCloudUserId;
const now = yield* DateTime.now;
const nowSeconds = Math.floor(now.epochMilliseconds / 1_000);
const proofOption = yield* verifyRelayJwt({
publicKey: cloudMintPublicKey,
token: request.proof,
typ: RELAY_MINT_REQUEST_TYP,
issuer: normalizeRelayIssuer(relayIssuer),
audience: `t3-env:${environmentId}`,
nowEpochSeconds: nowSeconds,
}).pipe(Effect.flatMap(decodeCloudMintProof), Effect.option);
if (
Option.isNone(proofOption) ||
proofOption.value.environmentId !== environmentId ||
proofOption.value.sub !== linkedCloudUserId ||
proofOption.value.cnf.jkt !== proofOption.value.clientProofKeyThumbprint ||
!hasBoundedCloudProofLifetime({ ...proofOption.value, nowSeconds }) ||
!hasExactScope({ scopes: proofOption.value.scope, expected: "environment:connect" })
) {
return yield* new CloudLinkProofRejectedError({ request: "mint" });
}
const proof = proofOption.value;
const jtiSecretName = `${CLOUD_MINT_JTI_PREFIX}${proof.jti}`;
const nonceSecretName = `${CLOUD_MINT_NONCE_PREFIX}${proof.nonce}`;
const consumedReplayGuards = yield* consumeCloudReplayGuards({
secrets,
names: [jtiSecretName, nonceSecretName],
value: stringToBytes(DateTime.formatIso(now)),
});
if (!consumedReplayGuards) {
return yield* new CloudLinkProofReplayedError({ request: "mint" });
}
const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
const issued = yield* environmentAuth.createPairingLink({
scopes: AuthStandardClientScopes,
subject: "cloud-connect",
ttl: Duration.minutes(2),
label: "T3 Connect connect",
proofKeyThumbprint: proof.clientProofKeyThumbprint,
});
const responsePayload = {
iss: `t3-env:${environmentId}`,
aud: normalizeRelayIssuer(relayIssuer),
sub: environmentId,
jti: yield* crypto.randomUUIDv4(),
iat: nowSeconds,
exp: Math.floor(issued.expiresAt.epochMilliseconds / 1_000),
environmentId,
clientProofKeyThumbprint: proof.clientProofKeyThumbprint,
requestNonce: proof.nonce,
credential: issued.credential,
} satisfies RelayEnvironmentMintResponseProofPayload;
const responseProof = yield* signRelayJwt({
privateKey: keyPair.privateKey,
typ: RELAY_MINT_RESPONSE_TYP,
payload: responsePayload,
}).pipe(
Effect.mapError(
(cause) =>
new EnvironmentAuth.ServerAuthCloudMintJwtSigningError({
cause,
}),
),
);
return {
credential: issued.credential,
expiresAt: DateTime.formatIso(issued.expiresAt),
proof: responseProof,
} satisfies RelayEnvironmentMintResponse;
},
Effect.catchIf(ServerSecretStore.isSecretStoreError, internalError("issue-credential")),
Effect.catchTags({ PlatformError: internalError("issue-credential") }),
);
return CloudLink.of({
linkProof,
applyRelayConfig,
linkState,
unlink,
updatePreferences,
answerHealthRequest,
mintCredential,
reconcileDesiredLinkIfStillDesired,
registerManagedTunnelRecovery,
recoverManagedTunnel,
startManagedTunnelIfOriginConfirmed,
releaseManagedTunnelOnShutdown,
});
});
export const layer = Layer.effect(CloudLink, make);