From a6505657921e5579d964a8547fad5fab17b8509e Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Mon, 13 Oct 2025 13:40:45 +0200 Subject: [PATCH 01/15] Combine `ClustersService` logout functions (#59539) * Remove clusters immediately after a logout, move `useClusterLogout` to `AppContext` * Review callsites to ensure cluster is properly checked before being accessed * Revert "Review callsites to ensure cluster is properly checked before being accessed" This reverts commit 8343c3c291f92851f1e464b470a70b92195b2122. * Switch to removing the cluster at the end of logout sequence * Lint * Move `logoutWithCleanup` to `ui/ClusterLogout` (cherry picked from commit de6b4edb6b5435e024a5c759304b30cb252c2d8f) --- .../src/ui/ClusterLogout/ClusterLogout.tsx | 11 ++- .../src/ui/ClusterLogout/logoutWithCleanup.ts | 69 +++++++++++++++++ .../src/ui/ClusterLogout/useClusterLogout.ts | 74 ------------------- .../services/clusters/clustersService.test.ts | 11 +-- .../ui/services/clusters/clustersService.ts | 44 +++-------- .../workspacesService/workspacesService.ts | 1 + 6 files changed, 91 insertions(+), 119 deletions(-) create mode 100644 web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts delete mode 100644 web/packages/teleterm/src/ui/ClusterLogout/useClusterLogout.ts diff --git a/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx b/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx index f4936f9dafd1f..3a5aa35af6544 100644 --- a/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx +++ b/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx @@ -25,10 +25,12 @@ import DialogConfirmation, { } from 'design/DialogConfirmation'; import { Cross } from 'design/Icon'; import { P } from 'design/Text/Text'; +import { useAsync } from 'shared/hooks/useAsync'; +import { useAppContext } from 'teleterm/ui/appContextProvider'; import { RootClusterUri, routing } from 'teleterm/ui/uri'; -import { useClusterLogout } from './useClusterLogout'; +import { logoutWithCleanup } from './logoutWithCleanup'; export function ClusterLogout({ clusterUri, @@ -39,9 +41,10 @@ export function ClusterLogout({ hidden?: boolean; onClose(): void; }) { - const { removeCluster, status, statusText } = useClusterLogout({ - clusterUri, - }); + const ctx = useAppContext(); + const [{ status, statusText }, removeCluster] = useAsync(() => + logoutWithCleanup(ctx, clusterUri) + ); async function removeClusterAndClose(): Promise { const [, err] = await removeCluster(); diff --git a/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts b/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts new file mode 100644 index 0000000000000..8b8b95769ef52 --- /dev/null +++ b/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts @@ -0,0 +1,69 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Logger from 'teleterm/logger'; +import { IAppContext } from 'teleterm/ui/types'; +import { RootClusterUri, routing } from 'teleterm/ui/uri'; + +/** Disposes cluster-related resources and then logs out. */ +export async function logoutWithCleanup( + ctx: IAppContext, + clusterUri: RootClusterUri +): Promise { + const logger = new Logger('logoutWithCleanup'); + // This function checks for updates, do not wait for it. + ctx.mainProcessClient + .maybeRemoveAppUpdatesManagingCluster(clusterUri) + .catch(err => { + logger.error('Failed to remove managing cluster', err); + }); + + if (ctx.workspacesService.getRootClusterUri() === clusterUri) { + const [firstConnectedWorkspace] = ctx.workspacesService + .getConnectedWorkspacesClustersUri() + .filter(c => c !== clusterUri); + if (firstConnectedWorkspace) { + await ctx.workspacesService.setActiveWorkspace(firstConnectedWorkspace); + } else { + await ctx.workspacesService.setActiveWorkspace(null); + } + } + + // Remove connections first, they depend both on the cluster and the workspace. + ctx.connectionTracker.removeItemsBelongingToRootCluster(clusterUri); + // Remove the workspace next, because it depends on the cluster. + ctx.workspacesService.removeWorkspace(clusterUri); + + // If there are active ssh connections to the agent, killing it will take a few seconds. To work + // around this, kill the agent only after removing the workspace. Removing the workspace closes + // ssh tabs, so it should terminate connections to the cluster from the app. + await ctx.connectMyComputerService.killAgentAndRemoveData(clusterUri); + + await ctx.clustersService.removeClusterGateways(clusterUri); + + const { + params: { rootClusterId }, + } = routing.parseClusterUri(clusterUri); + await ctx.mainProcessClient.removeKubeConfig({ + relativePath: rootClusterId, + isDirectory: true, + }); + + // Remove the cluster, it does not depend on anything. + await ctx.clustersService.logout(clusterUri); +} diff --git a/web/packages/teleterm/src/ui/ClusterLogout/useClusterLogout.ts b/web/packages/teleterm/src/ui/ClusterLogout/useClusterLogout.ts deleted file mode 100644 index 90683da79f324..0000000000000 --- a/web/packages/teleterm/src/ui/ClusterLogout/useClusterLogout.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { useAsync } from 'shared/hooks/useAsync'; - -import { useLogger } from 'teleterm/ui/hooks/useLogger'; -import { RootClusterUri } from 'teleterm/ui/uri'; - -import { useAppContext } from '../appContextProvider'; - -export function useClusterLogout({ - clusterUri, -}: { - clusterUri: RootClusterUri; -}) { - const ctx = useAppContext(); - const logger = useLogger('useClusterLogout'); - const [{ status, statusText }, removeCluster] = useAsync(async () => { - await ctx.clustersService.logout(clusterUri); - // This function checks for updates, do not wait for it. - ctx.mainProcessClient - .maybeRemoveAppUpdatesManagingCluster(clusterUri) - .catch(err => { - logger.error('Failed to remove managing cluster', err); - }); - - if (ctx.workspacesService.getRootClusterUri() === clusterUri) { - const [firstConnectedWorkspace] = - ctx.workspacesService.getConnectedWorkspacesClustersUri(); - if (firstConnectedWorkspace) { - await ctx.workspacesService.setActiveWorkspace(firstConnectedWorkspace); - } else { - await ctx.workspacesService.setActiveWorkspace(null); - } - } - - // Remove connections first, they depend both on the cluster and the workspace. - ctx.connectionTracker.removeItemsBelongingToRootCluster(clusterUri); - - // Remove the workspace next, because it depends on the cluster. - ctx.workspacesService.removeWorkspace(clusterUri); - - // If there are active ssh connections to the agent, killing it will take a few seconds. To work - // around this, kill the agent only after removing the workspace. Removing the workspace closes - // ssh tabs, so it should terminate connections to the cluster from the app. - // - // If ClustersService.logout above fails, the user should still be able to manage the agent. - await ctx.connectMyComputerService.killAgentAndRemoveData(clusterUri); - - // Remove the cluster, it does not depend on anything. - await ctx.clustersService.removeClusterAndResources(clusterUri); - }); - - return { - status, - statusText, - removeCluster, - }; -} diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts index 465b3d85e9041..1265af5d4f494 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts @@ -136,7 +136,7 @@ test('add cluster does not overwrite the existing cluster', async () => { ); }); -test('remove cluster', async () => { +test('remove gateways', async () => { const { removeGateway } = getClientMocks(); const service = createService({ removeGateway }); const gatewayFromRootCluster = makeDatabaseGateway({ @@ -164,14 +164,11 @@ test('remove cluster', async () => { ]); }); - await service.removeClusterAndResources(clusterUri); + await service.removeClusterGateways(clusterUri); - expect(service.findCluster(clusterUri)).toBeUndefined(); - expect(service.findCluster(leafClusterMock.uri)).toBeUndefined(); expect(service.state.gateways).toEqual( new Map([[gatewayFromOtherCluster.uri, gatewayFromOtherCluster]]) ); - expect(removeGateway).toHaveBeenCalledWith({ gatewayUri: gatewayFromRootCluster.uri, }); @@ -222,8 +219,8 @@ test('logout from cluster', async () => { expect(logout).toHaveBeenCalledWith({ clusterUri }); expect(removeCluster).toHaveBeenCalledWith({ clusterUri }); - expect(service.findCluster(clusterMock.uri).connected).toBe(false); - expect(service.findCluster(leafClusterMock.uri).connected).toBe(false); + expect(service.findCluster(clusterMock.uri)).toBeUndefined(); + expect(service.findCluster(leafClusterMock.uri)).toBeUndefined(); }); test('create a gateway', async () => { diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index eb10ddde510ac..b12050707912a 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -80,15 +80,7 @@ export class ClustersService extends ImmutableStore { return cluster; } - /** - * Logs out of the cluster and removes the profile. - * Does not remove the cluster from the state, but sets the cluster and its leafs as disconnected. - * It needs to be done, because some code can operate on the cluster the intermediate period between logout - * and actually removing it from the state. - * A code that operates on that intermediate state is in `useClusterLogout.tsx`. - * After invoking `logout()`, it looks for the next workspace to switch to. If we hadn't marked the cluster as disconnected, - * the method might have returned us the same cluster we wanted to log out of. - */ + /** Logs out of the cluster. */ async logout(clusterUri: uri.RootClusterUri) { // TODO(gzdunek): logout and removeCluster should be combined into a single acton in tshd await this.client.logout({ clusterUri }); @@ -97,7 +89,7 @@ export class ClustersService extends ImmutableStore { this.setState(draft => { draft.clusters.forEach(cluster => { if (routing.belongsToProfile(clusterUri, cluster.uri)) { - cluster.connected = false; + draft.clusters.delete(cluster.uri); } }); }); @@ -192,6 +184,13 @@ export class ClustersService extends ImmutableStore { ]); } + /** + * Synchronizes root clusters. + * + * This should only be called before creating workspaces. + * If called afterward, a cluster might be removed without first removing + * its associated workspace, resulting in an invalid state. + */ async syncRootClustersAndCatchErrors(abortSignal?: AbortSignal) { let clusters: Cluster[]; @@ -315,22 +314,9 @@ export class ClustersService extends ImmutableStore { return response; } - /** Removes cluster, its leafs and other resources. */ - async removeClusterAndResources(clusterUri: uri.RootClusterUri) { - this.setState(draft => { - draft.clusters.forEach(cluster => { - if (routing.belongsToProfile(clusterUri, cluster.uri)) { - draft.clusters.delete(cluster.uri); - } - }); - }); - await this.removeClusterKubeConfigs(clusterUri); - await this.removeClusterGateways(clusterUri); - } - // TODO(ravicious): Create a single RPC for this rather than sending a separate request for each // gateway. - private async removeClusterGateways(clusterUri: uri.RootClusterUri) { + async removeClusterGateways(clusterUri: uri.RootClusterUri) { for (const [, gateway] of this.state.gateways) { if (routing.belongsToProfile(clusterUri, gateway.targetUri)) { try { @@ -512,16 +498,6 @@ export class ClustersService extends ImmutableStore { return this.getClusters().filter(c => !c.leaf); } - async removeClusterKubeConfigs(clusterUri: string): Promise { - const { - params: { rootClusterId }, - } = routing.parseClusterUri(clusterUri); - return this.mainProcessClient.removeKubeConfig({ - relativePath: rootClusterId, - isDirectory: true, - }); - } - useState() { return useStore(this).state; } diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index cba7fab5f5407..b5eb013b50995 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -352,6 +352,7 @@ export class WorkspacesService extends ImmutableStore { } if (cluster.profileStatusError) { + // TODO(gzdunek): We should only sync the target cluster, not all of them. await this.clustersService.syncRootClustersAndCatchErrors(abortSignal); // Update the cluster. cluster = this.clustersService.findCluster(clusterUri); From 9458e5d990759a9ddcc99f5006a048c74e73ddd9 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 22 Oct 2025 12:14:04 +0200 Subject: [PATCH 02/15] Enable sending messages from main to renderer with acknowledgments (#59642) * Create awaitable sender * Review comments * Fix test and lint (cherry picked from commit 5dc76fecac38484a40cd0e8151b64d781290be0a) --- .../awaitableSender/awaitableSender.test.ts | 101 ++++++++++++ .../awaitableSender/awaitableSender.ts | 155 ++++++++++++++++++ .../src/mainProcess/awaitableSender/index.ts | 19 +++ .../src/mainProcess/mainProcessClient.ts | 43 +++++ 4 files changed, 318 insertions(+) create mode 100644 web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.test.ts create mode 100644 web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts create mode 100644 web/packages/teleterm/src/mainProcess/awaitableSender/index.ts diff --git a/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.test.ts b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.test.ts new file mode 100644 index 0000000000000..5c28bc621396e --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.test.ts @@ -0,0 +1,101 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { EventEmitter } from 'events'; + +import Logger, { NullService } from 'teleterm/logger'; + +import { + AwaitableSender, + MessageAcknowledgementError, +} from './awaitableSender'; + +beforeAll(() => { + Logger.init(new NullService()); +}); + +test('starts port and add event listeners in constructor', () => { + const port = new MockMessagePortMain(); + new AwaitableSender(port); + expect(port.listeners('message')).toHaveLength(1); + expect(port.listeners('close')).toHaveLength(1); +}); + +test('send posts message and returns a promise that resolves on ack', async () => { + const port = new MockMessagePortMain(); + const sender = new AwaitableSender(port); + + const payload = { foo: 'bar' }; + const promise = sender.send(payload); + + expect(port.postMessage).toHaveBeenCalledWith({ + type: 'data', + id: expect.any(String), + payload, + }); + + // The other side responds: first emit ack for other id and then an empty message. + port.emitMessage({ id: 'wrong-id', type: 'ack' }); + port.emitMessage(undefined); + // Wait a short time and make sure the promise hasn't resolved. + const result = await Promise.race([ + promise.then(() => 'resolved'), + new Promise(resolve => setTimeout(() => resolve('pending'), 50)), + ]); + + expect(result).toBe('pending'); + + // The other side responds again: now emit ack for our send request. + const correctId = port.postMessage.mock.calls[0][0].id; + port.emitMessage({ id: correctId, type: 'ack' }); + + await expect(promise).resolves.toBeUndefined(); +}); + +test('dispose removes listeners, resolves pending messages, clears map, and resolves disposeSignal', async () => { + const port = new MockMessagePortMain(); + const sender = new AwaitableSender(port); + let sendPromise = sender.send(undefined); + + port.close(); + expect(port.listeners('message')).toHaveLength(0); + expect(port.listeners('close')).toHaveLength(0); + + const disposedPromise = sender.whenDisposed(); + + // The pending send promise should resolve after dispose + await expect(sendPromise).rejects.toThrow(MessageAcknowledgementError); + await expect(disposedPromise).resolves.toBeUndefined(); +}); + +class MockMessagePortMain extends EventEmitter { + public postMessage = jest.fn(); + public start = jest.fn(); + + constructor() { + super(); + } + + emitMessage(data: unknown): void { + this.emit('message', { data }); + } + + close(): void { + this.emit('close'); + } +} diff --git a/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts new file mode 100644 index 0000000000000..1fe081e672dc2 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts @@ -0,0 +1,155 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { MessageEvent, MessagePortMain } from 'electron'; + +export type Message = MessageData | MessageAck; + +export interface MessageData { + id: string; + type: 'data'; + payload: unknown; +} + +export interface MessageAck { + id: string; + type: 'ack'; + /** + * Optional error. + * Present when the renderer received the message, but failed to process it. + */ + error?: unknown; +} + +function isMessageAck(v: unknown): v is MessageAck { + return typeof v === 'object' && 'type' in v && v.type === 'ack'; +} + +/** + * Enables sending messages from the main process to the renderer + * and awaiting delivery confirmation. + * + * Unlike the standard `webContents.send()` API, which is push-based, + * `AwaitableSender` is pull-based — the renderer must explicitly subscribe + * to receive messages. + */ +export class AwaitableSender { + private messages = new Map< + string, + { resolve(): void; reject(reason: unknown): void } + >(); + private disposeSignal = Promise.withResolvers(); + + constructor(private port: MessagePortMain) { + this.port.start(); + this.port.on('message', this.processMessage); + this.port.on('close', this.dispose); + } + + /** + * Sends a message and awaits delivery confirmation from the receiver. + * + * This method returns a promise that resolves once the other side + * acknowledges receiving and processing the message. + * If the acknowledgment is not received within the specified timeout + * (default 10 seconds), the promise rejects with a `MessageAcknowledgementError`. + * + * If the renderer received the message, but failed to process it, the promise + * is also rejected. + */ + send( + payload: T, + { signal = AbortSignal.timeout(10_000) }: { signal?: AbortSignal } = {} + ): Promise { + const id = crypto.randomUUID(); + + return new Promise((resolve, reject) => { + const cleanup = () => { + this.messages.delete(id); + signal.removeEventListener('abort', abort); + }; + + const abort = () => { + cleanup(); + reject(new MessageAcknowledgementError(signal.reason)); + }; + + if (signal.aborted) { + return abort(); + } + + signal.addEventListener('abort', abort, { once: true }); + + this.messages.set(id, { + resolve: () => { + cleanup(); + resolve(); + }, + reject: reason => { + cleanup(); + reject(reason); + }, + }); + + const message: MessageData = { type: 'data', id, payload }; + this.port.postMessage(message); + }); + } + + /** Returns a promise that resolves when the sender is disposed. */ + whenDisposed(): Promise { + return this.disposeSignal.promise; + } + + private processMessage = (event: MessageEvent): void => { + const message = event.data; + // Only to satisfy TypeScript. + // We don't expect non-ack messages to be received on this port. + if (!isMessageAck(message)) { + return; + } + const item = this.messages.get(message.id); + if (!item) { + return; + } + if (message.error) { + item.reject(message.error); + return; + } + item.resolve(); + }; + + private dispose = (): void => { + this.port.off('message', this.processMessage); + this.port.off('close', this.dispose); + + for (const { reject } of this.messages.values()) { + reject(new MessageAcknowledgementError(new Error('Sender was disposed'))); + } + this.disposeSignal.resolve(); + }; +} + +/** Error thrown when waiting for message acknowledgement confirmation was abandoned. */ +export class MessageAcknowledgementError extends Error { + constructor(cause?: unknown) { + super('Stopped waiting for message acknowledgment from renderer', { + cause, + }); + } +} diff --git a/web/packages/teleterm/src/mainProcess/awaitableSender/index.ts b/web/packages/teleterm/src/mainProcess/awaitableSender/index.ts new file mode 100644 index 0000000000000..53610a590dc63 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/awaitableSender/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './awaitableSender'; diff --git a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts index dc56c2a4832c1..56196f08f7a64 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -18,6 +18,7 @@ import { ipcRenderer } from 'electron'; +import type { Message, MessageAck } from 'teleterm/mainProcess/awaitableSender'; import { CreateAgentConfigFileArgs } from 'teleterm/mainProcess/createAgentConfigFile'; import { AppUpdateEvent } from 'teleterm/services/appUpdater'; import { createFileStorageClient } from 'teleterm/services/fileStorage'; @@ -265,3 +266,45 @@ export default function createMainProcessClient(): MainProcessClient { }, }; } + +/** + * Sets up a `MessagePort` listener in the renderer process and transfers + * the port to the main process via the specified IPC `channel`. + * + * The main process is expected to create an `AwaitableSender` using the received port, + * enabling it to send messages that require acknowledgments from the renderer. + */ +// eslint-disable-next-line unused-imports/no-unused-vars +function startAwaitableSenderListener( + channel: string, + listener: (value: T) => void +): { + close: () => void; +} { + const { port1: localPort, port2: transferablePort } = new MessageChannel(); + + localPort.onmessage = (event: MessageEvent) => { + const msg = event.data; + if (msg.type !== 'data') { + return; + } + const ack: MessageAck = { type: 'ack', id: msg.id }; + try { + listener(msg.payload as T); + } catch (e) { + ack.error = e; + } + localPort.postMessage(ack); + }; + + localPort.start(); + ipcRenderer.postMessage(channel, undefined, [transferablePort]); + + return { + close: () => { + localPort.onmessage = undefined; + localPort.close(); + transferablePort.close(); + }, + }; +} From 97780ad4484207b28cdaf6f0907c6ca2fbd7981c Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 22 Oct 2025 12:49:30 +0200 Subject: [PATCH 03/15] Move cluster state to main process (#59643) * Create `ClusterStore` that manages cluster state * Fix tests that mocked tshd directly * Remove IPC to notify the main process about cluster list changes * Load immer plugins in `MainProcess` * Improve comments * Refactor `useSender` * Get rid of unnecessary Map and try/catch around send * Get rid of `MainProcess.create` * Do not return early `c.proxyHost` is falsy * Add more context to test * Add missing logout handler in main process * Fix applying patches * Adjust `subscribeToClusterStore` to updated `startAwaitableSenderListener` * Crash window when sending state update fails * Extract WebContents navigation handlers and add tests for opening links * Improve error message * Initialize `ClusterStore` synchronously * Convert `lazyTshdClient` field to `getTshdClient` function, add docs * Remove unused eslint directive (cherry picked from commit a41d021f8a2584e4b99bf5881eebea587a15faf9) --- web/packages/teleterm/src/main.ts | 93 +------- .../awaitableSender/awaitableSender.ts | 2 +- .../clusterStore/clusterStore.test.ts | 119 ++++++++++ .../mainProcess/clusterStore/clusterStore.ts | 220 ++++++++++++++++++ .../src/mainProcess/clusterStore/index.ts | 19 ++ .../src/mainProcess/fixtures/mocks.ts | 18 +- .../teleterm/src/mainProcess/mainProcess.ts | 87 +++++-- .../src/mainProcess/mainProcessClient.ts | 26 ++- .../src/mainProcess/navigationHandler.test.ts | 120 ++++++++++ .../src/mainProcess/navigationHandler.ts | 147 ++++++++++++ .../rootClusterProxyHostAllowList.ts | 114 --------- .../teleterm/src/mainProcess/types.ts | 20 +- .../src/mainProcess/windowsManager.ts | 36 +++ web/packages/teleterm/src/ui/App.test.tsx | 15 +- .../ClusterLogin/ClusterLogin.test.tsx | 10 +- .../teleterm/src/ui/Vnet/integration.test.tsx | 18 +- web/packages/teleterm/src/ui/appContext.ts | 22 -- .../teleterm/src/ui/fixtures/mocks.ts | 16 +- .../services/clusters/clustersService.test.ts | 96 +------- .../ui/services/clusters/clustersService.ts | 127 ++++------ .../services/immutableStore/immutableStore.ts | 4 +- 21 files changed, 865 insertions(+), 464 deletions(-) create mode 100644 web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts create mode 100644 web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts create mode 100644 web/packages/teleterm/src/mainProcess/clusterStore/index.ts create mode 100644 web/packages/teleterm/src/mainProcess/navigationHandler.test.ts create mode 100644 web/packages/teleterm/src/mainProcess/navigationHandler.ts delete mode 100644 web/packages/teleterm/src/mainProcess/rootClusterProxyHostAllowList.ts diff --git a/web/packages/teleterm/src/main.ts b/web/packages/teleterm/src/main.ts index d3df7a3a37206..6ccac5e63faa3 100644 --- a/web/packages/teleterm/src/main.ts +++ b/web/packages/teleterm/src/main.ts @@ -19,7 +19,7 @@ import os from 'node:os'; import path from 'node:path'; -import { app, dialog, nativeTheme, shell } from 'electron'; +import { app, dialog, nativeTheme } from 'electron'; import { CUSTOM_PROTOCOL } from 'shared/deepLinks'; import { ensureError } from 'shared/utils/error'; @@ -27,8 +27,8 @@ import { ensureError } from 'shared/utils/error'; import { parseDeepLink } from 'teleterm/deepLinks'; import Logger from 'teleterm/logger'; import MainProcess from 'teleterm/mainProcess'; +import { registerNavigationHandlers } from 'teleterm/mainProcess/navigationHandler'; import { enableWebHandlersProtection } from 'teleterm/mainProcess/protocolHandler'; -import { manageRootClusterProxyHostAllowList } from 'teleterm/mainProcess/rootClusterProxyHostAllowList'; import { getRuntimeSettings } from 'teleterm/mainProcess/runtimeSettings'; import { WindowsManager } from 'teleterm/mainProcess/windowsManager'; import { createConfigService } from 'teleterm/services/config'; @@ -97,7 +97,7 @@ async function initializeApp(): Promise { let mainProcess: MainProcess; try { - mainProcess = MainProcess.create({ + mainProcess = new MainProcess({ settings, logger, configService, @@ -148,23 +148,6 @@ async function initializeApp(): Promise { // window before processing the listener for deep links. setUpDeepLinks(logger, windowsManager, settings); - const rootClusterProxyHostAllowList = new Set(); - - (async () => { - const { terminalService } = await mainProcess.getTshdClients(); - - manageRootClusterProxyHostAllowList({ - tshdClient: terminalService, - logger, - allowList: rootClusterProxyHostAllowList, - }); - })().catch(error => { - const message = 'Could not initialize the tshd client in the main process'; - logger.error(message, error); - showDialogWithError(message, error); - app.exit(1); - }); - app .whenReady() .then(() => { @@ -183,69 +166,13 @@ async function initializeApp(): Promise { app.exit(1); }); - // Limit navigation capabilities to reduce the attack surface. - // See TEL-Q122-19 from "Teleport Core Testing Q1 2022" security audit. - // - // See also points 12, 13 and 14 from the Electron's security tutorial. - // https://github.com/electron/electron/blob/v17.2.0/docs/tutorial/security.md#12-verify-webview-options-before-creation - app.on('web-contents-created', (_, contents) => { - contents.on('will-navigate', (event, navigationUrl) => { - // Allow reloading the renderer app in dev mode. - if (settings.dev && new URL(navigationUrl).host === 'localhost:8080') { - return; - } - logger.warn(`Navigation to ${navigationUrl} blocked by 'will-navigate'`); - event.preventDefault(); - }); - - // The usage of webview is blocked by default, but let's include the handler just in case. - // https://github.com/electron/electron/blob/v17.2.0/docs/api/webview-tag.md#enabling - contents.on('will-attach-webview', (event, _, params) => { - logger.warn( - `Opening a webview to ${params.src} blocked by 'will-attach-webview'` - ); - event.preventDefault(); - }); - - contents.setWindowOpenHandler(details => { - const url = new URL(details.url); - - function isUrlSafe(): boolean { - if (url.protocol !== 'https:') { - return false; - } - if (url.host === 'goteleport.com') { - return true; - } - if ( - url.host === 'github.com' && - url.pathname.startsWith('/gravitational/') - ) { - return true; - } - - // Allow opening links to the Web UIs of root clusters currently added in the app. - if (rootClusterProxyHostAllowList.has(url.host)) { - return true; - } - } - - // Open links to documentation and GitHub issues in the external browser. - // They need to have `target` set to `_blank`. - if (isUrlSafe()) { - shell.openExternal(url.toString()); - } else { - logger.warn( - `Opening a new window to ${url} blocked by 'setWindowOpenHandler'` - ); - dialog.showErrorBox( - 'Cannot open this link', - 'The domain does not match any of the allowed domains. Check main.log for more details.' - ); - } - - return { action: 'deny' }; - }); + app.on('web-contents-created', (_, webContents) => { + registerNavigationHandlers( + webContents, + settings, + mainProcess.clusterStore, + logger + ); }); } diff --git a/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts index 1fe081e672dc2..9214fcce431c8 100644 --- a/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts +++ b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts @@ -148,7 +148,7 @@ export class AwaitableSender { /** Error thrown when waiting for message acknowledgement confirmation was abandoned. */ export class MessageAcknowledgementError extends Error { constructor(cause?: unknown) { - super('Stopped waiting for message acknowledgment from renderer', { + super('Failed to receive message acknowledgement from the renderer', { cause, }); } diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts new file mode 100644 index 0000000000000..f59619102dc25 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts @@ -0,0 +1,119 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { enableMapSet, enablePatches } from 'immer'; + +import { MockedUnaryCall } from 'teleterm/services/tshd/cloneableClient'; +import { MockTshClient } from 'teleterm/services/tshd/fixtures/mocks'; +import { + makeLeafCluster, + makeRootCluster, +} from 'teleterm/services/tshd/testHelpers'; + +import { ClusterStore } from './clusterStore'; + +enablePatches(); +enableMapSet(); + +const cluster = makeRootCluster({ connected: false }); +const clusterWithDetails = makeRootCluster({ + ...cluster, + connected: true, + features: { + advancedAccessWorkflows: true, + isUsageBasedBilling: true, + }, +}); +const leafCluster = makeLeafCluster(); +const mockWindowsManager = { + crashWindow: async () => undefined, +}; + +test('adds cluster', async () => { + const mockClient = new MockTshClient(); + mockClient.addCluster = () => new MockedUnaryCall(cluster); + const clusterStore = new ClusterStore( + () => Promise.resolve(mockClient), + mockWindowsManager + ); + + await clusterStore.add(cluster.uri); + + const state = clusterStore.getState(); + expect(state.get(cluster.uri)).toEqual(cluster); +}); + +test('adding a cluster does not overwrite an existing one', async () => { + const mockClient = new MockTshClient(); + mockClient.addCluster = () => new MockedUnaryCall(cluster); + mockClient.getCluster = () => new MockedUnaryCall(clusterWithDetails); + const clusterStore = new ClusterStore( + () => Promise.resolve(mockClient), + mockWindowsManager + ); + + await clusterStore.sync(cluster.uri); + // addCluster call returns fewer details than getCluster, + // so clusterStore.add shouldn't overwrite details already acquired + // by clusterStore.sync. + await clusterStore.add(cluster.uri); + + const state = clusterStore.getState(); + expect(state.get(cluster.uri)).toEqual(clusterWithDetails); +}); + +test('syncs cluster', async () => { + const mockClient = new MockTshClient(); + mockClient.getCluster = () => new MockedUnaryCall(clusterWithDetails); + mockClient.listLeafClusters = () => + new MockedUnaryCall({ clusters: [leafCluster] }); + const clusterStore = new ClusterStore( + () => Promise.resolve(mockClient), + mockWindowsManager + ); + + await clusterStore.sync(cluster.uri); + + const state = clusterStore.getState(); + expect(state.get(clusterWithDetails.uri)).toStrictEqual(clusterWithDetails); + expect(state.get(leafCluster.uri)).toStrictEqual(leafCluster); +}); + +test('logs out of cluster', async () => { + const mockClient = new MockTshClient(); + mockClient.getCluster = () => new MockedUnaryCall(clusterWithDetails); + mockClient.listLeafClusters = () => + new MockedUnaryCall({ clusters: [leafCluster] }); + const logoutMock = jest.spyOn(mockClient, 'logout'); + const removeClusterMock = jest.spyOn(mockClient, 'removeCluster'); + const clusterStore = new ClusterStore( + () => Promise.resolve(mockClient), + mockWindowsManager + ); + await clusterStore.sync(cluster.uri); + + await clusterStore.logout(cluster.uri); + + expect(logoutMock).toHaveBeenCalledWith({ clusterUri: cluster.uri }); + expect(removeClusterMock).toHaveBeenCalledWith({ + clusterUri: cluster.uri, + }); + const state = clusterStore.getState(); + expect(state.get(clusterWithDetails.uri)).toBeUndefined(); + expect(state.get(leafCluster.uri)).toBeUndefined(); +}); diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts new file mode 100644 index 0000000000000..5256bfdf38871 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts @@ -0,0 +1,220 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Patch, Producer, produceWithPatches } from 'immer'; + +import { + Cluster, + ShowResources, +} from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; + +import Logger from 'teleterm/logger'; +import { TshdClient } from 'teleterm/services/tshd'; +import { ClusterUri, RootClusterUri, routing } from 'teleterm/ui/uri'; + +import { AwaitableSender } from '../awaitableSender'; +import type { WindowsManager } from '../windowsManager'; + +export type State = ReadonlyMap; + +export type ClusterStoreUpdate = + /** + * Patches allow the other side to keep reference stability + * so that not the whole state is recreated. + */ + | { kind: 'patches'; value: Patch[] } + /** The full state, useful to get the initial state on the other side. */ + | { kind: 'state'; value: State }; + +export class ClusterStore { + private senders = new Set>(); + private state: State = new Map(); + private logger = new Logger('ClusterStore'); + + constructor( + private readonly getTshdClient: () => Promise, + private readonly windowsManager: Pick + ) {} + + /** Adds a cluster. */ + async add(proxyAddress: string): Promise { + const client = await this.getTshdClient(); + const { response } = await client.addCluster({ + name: proxyAddress, + }); + + await this.update(draft => { + // Do not overwrite the existing cluster; + // otherwise we may lose properties fetched from the auth server. + // Consider separating properties read from profile and those + // fetched from the auth server at the RPC message level. + if (draft.has(response.uri)) { + return; + } + draft.set(response.uri, response); + }); + + return response; + } + + /** Logs out of the cluster and removes its profile.*/ + async logout(uri: RootClusterUri): Promise { + const client = await this.getTshdClient(); + // TODO(gzdunek): logout and removeCluster should be combined into + // a single acton in tshd. + await client.logout({ clusterUri: uri }); + await client.removeCluster({ clusterUri: uri }); + await this.update(draft => { + for (let d of draft.values()) { + if (routing.belongsToProfile(uri, d.uri)) { + draft.delete(d.uri); + } + } + }); + } + + /** + * Synchronizes the root clusters. + * Does not make a network call, only reads profiles from the disk. + */ + async syncRootClusters(): Promise { + const client = await this.getTshdClient(); + const { response } = await client.listRootClusters({}); + await this.update(draft => { + draft.clear(); + response.clusters.forEach(cluster => { + draft.set(cluster.uri, cluster); + }); + }); + } + + /** + * Synchronizes a root cluster. + * Makes network calls to get cluster details and its leaf clusters. + */ + async sync(uri: RootClusterUri): Promise { + let cluster: Cluster; + let leafs: Cluster[]; + const client = await this.getTshdClient(); + try { + const clusterAndLeafs = await getClusterAndLeafs(client, uri); + cluster = clusterAndLeafs.cluster; + leafs = clusterAndLeafs.leafs; + } catch (error) { + await this.update(draft => { + const cluster = draft.get(uri); + if (cluster) { + // TODO(gzdunek): We should rather store the cluster synchronization status, + // so the callsites could check it before reading the field. + // The workaround is to update the field in case of a failure, + // so the places that wait for showResources !== UNSPECIFIED don't get stuck indefinitely. + cluster.showResources = ShowResources.ACCESSIBLE_ONLY; + } + }); + throw error; + } + + await this.update(draft => { + draft.set(cluster.uri, cluster); + leafs.forEach(leaf => { + draft.set(leaf.uri, leaf); + }); + }); + } + + getRootClusters(): Cluster[] { + return this.state + .values() + .toArray() + .filter(c => !c.leaf); + } + + getState(): State { + return this.state; + } + + /** + * Registers an `AwaitableSender` to send updates and automatically unregisters + * it when disposed. + * + * Upon registration, the current state is sent immediately as the initial + * message to the sender. + */ + registerSender(sender: AwaitableSender): void { + this.logger.info('Sender registered'); + this.senders.add(sender); + const send = this.withErrorHandling(update => sender.send(update)); + void send({ + kind: 'state', + value: this.state, + }); + sender.whenDisposed().then(() => { + this.senders.delete(sender); + this.logger.info('Sender unregistered'); + }); + } + + private async update(producer: Producer): Promise { + const [state, patches] = produceWithPatches(this.state, producer); + this.state = state; + await Promise.all( + this.senders.values().map(sender => { + const send = this.withErrorHandling(update => sender.send(update)); + return send({ + kind: 'patches', + value: patches, + }); + }) + ); + } + + /** + * Wraps a cluster store update sender function with error handling. + * + * Any error indicates that the renderer state may be out of sync with the cluster store. + * Applying further updates may fail. + * Prompt the user to reload the window or quit the app. + */ + private withErrorHandling( + sender: (update: ClusterStoreUpdate) => Promise + ): (update: ClusterStoreUpdate) => Promise { + return async update => { + try { + await sender(update); + } catch (e) { + await this.windowsManager.crashWindow(e); + } + }; + } +} + +async function getClusterAndLeafs(tshdClient: TshdClient, uri: RootClusterUri) { + const resolved = await Promise.all([ + tshdClient.getCluster({ + clusterUri: uri, + }), + tshdClient.listLeafClusters({ + clusterUri: uri, + }), + ]); + + return { + cluster: resolved[0].response, + leafs: resolved[1].response.clusters, + }; +} diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/index.ts b/web/packages/teleterm/src/mainProcess/clusterStore/index.ts new file mode 100644 index 0000000000000..820d317cdd8b7 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/clusterStore/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './clusterStore'; diff --git a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts index a779c4f025b2e..e864bc9c191ea 100644 --- a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -25,6 +25,8 @@ import { AgentProcessState } from 'teleterm/mainProcess/types'; // Importing electron breaks the fixtures if that's done from within storybook. import { createConfigService } from 'teleterm/services/config/configService'; import { createMockFileStorage } from 'teleterm/services/fileStorage/fixtures/mocks'; +import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; +import { Cluster } from 'teleterm/services/tshd/types'; import { MainProcessClient, RuntimeSettings } from 'teleterm/types'; export class MockMainProcessClient implements MainProcessClient { @@ -172,8 +174,6 @@ export class MockMainProcessClient implements MainProcessClient { return this.frontendAppInit.promise; } - refreshClusterList() {} - async selectDirectoryForDesktopSession() { return ''; } @@ -202,6 +202,20 @@ export class MockMainProcessClient implements MainProcessClient { } { return { cleanup: () => undefined }; } + + subscribeToClusterStore(): { + cleanup: () => void; + } { + return { cleanup: () => undefined }; + } + async logoutCluster(): Promise {} + async syncCluster(): Promise {} + async addCluster(): Promise { + return makeRootCluster(); + } + async syncRootClusters(): Promise { + return []; + } } export const makeRuntimeSettings = ( diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index e5f4dc173eeb0..2f852ababba5e 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -32,6 +32,7 @@ import { nativeTheme, shell, } from 'electron'; +import { enableMapSet, enablePatches } from 'immer'; import { AbortError } from 'shared/utils/error'; @@ -77,6 +78,8 @@ import { } from '../services/config'; import { downloadAgent, FileDownloader, verifyAgent } from './agentDownloader'; import { AgentRunner } from './agentRunner'; +import { AwaitableSender } from './awaitableSender'; +import { ClusterStore } from './clusterStore'; import { subscribeToTabContextMenuEvent } from './contextMenus/tabContextMenu'; import { subscribeToTerminalContextMenuEvent } from './contextMenus/terminalContextMenu'; import { @@ -127,8 +130,18 @@ export default class MainProcess { autoUpdateService: AutoUpdateClient; }>; private readonly appUpdater: AppUpdater; + public readonly clusterStore: ClusterStore; - private constructor(opts: Options) { + /** + * Starts necessary child processes such as tsh daemon and the shared process. It also sets + * up IPC handlers and resolves the network addresses under which the child processes set up gRPC + * servers. + * + * Might throw an error if spawning a child process fails, see initTshd for more details. + */ + constructor(opts: Options) { + enablePatches(); + enableMapSet(); this.settings = opts.settings; this.logger = opts.logger; this.configService = opts.configService; @@ -151,6 +164,13 @@ export default class MainProcess { } ); + this.updateAboutPanelIfNeeded(); + this.setAppMenu(); + this.initTshd(); + this.initSharedProcess(); + this.initResolvingChildProcessAddresses(); + this.initIpc(); + const getClusterVersions = async () => { const { autoUpdateService } = await this.getTshdClients(); const { response } = await autoUpdateService.getClusterVersions({}); @@ -177,19 +197,10 @@ export default class MainProcess { }, process.env[TELEPORT_TOOLS_VERSION_ENV_VAR] ); - } - - /** - * create starts necessary child processes such as tsh daemon and the shared process. It also sets - * up IPC handlers and resolves the network addresses under which the child processes set up gRPC - * servers. - * - * create might throw an error if spawning a child process fails, see initTshd for more details. - */ - static create(opts: Options) { - const instance = new MainProcess(opts); - instance.init(); - return instance; + this.clusterStore = new ClusterStore( + () => this.getTshdClients().then(c => c.terminalService), + this.windowsManager + ); } async dispose(): Promise { @@ -209,16 +220,13 @@ export default class MainProcess { ]); } - private init() { - this.updateAboutPanelIfNeeded(); - this.setAppMenu(); - this.initTshd(); - this.initSharedProcess(); - this.initResolvingChildProcessAddresses(); - this.initIpc(); - } - - async getTshdClients(): Promise<{ + /** + * Returns the tshd client. + * + * If the client setup fails, the resulting error will propagate + * to callers of this method. + */ + private async getTshdClients(): Promise<{ terminalService: TshdClient; autoUpdateService: AutoUpdateClient; }> { @@ -334,6 +342,16 @@ export default class MainProcess { ); } + /** + * Initializes the resolution of child process addresses. + * + * Both the internal tshd client (in the main process) and the one in the renderer + * depend on this initialization promise. + * + * If the promise rejects, the error will propagate to the renderer via the IPC + * handler (causing the renderer to stop initialization and show the error) + * and also surface when attempting to access `getTshdClients()`. + */ private initResolvingChildProcessAddresses(): void { this.resolvedChildProcessAddresses = Promise.all([ resolveNetworkAddress( @@ -693,6 +711,27 @@ export default class MainProcess { this.appUpdater.quitAndInstall() ); + ipcMain.handle(MainProcessIpc.AddCluster, (ev, proxyAddress) => + this.clusterStore.add(proxyAddress) + ); + + ipcMain.handle(MainProcessIpc.SyncRootClusters, () => + this.clusterStore.syncRootClusters() + ); + + ipcMain.handle(MainProcessIpc.SyncCluster, (_, args) => + this.clusterStore.sync(args.clusterUri) + ); + + ipcMain.handle(MainProcessIpc.Logout, (_, args) => + this.clusterStore.logout(args.clusterUri) + ); + + ipcMain.on(MainProcessIpc.InitClusterStoreSubscription, ev => { + const port = ev.ports[0]; + this.clusterStore.registerSender(new AwaitableSender(port)); + }); + subscribeToTerminalContextMenuEvent(this.configService); subscribeToTabContextMenuEvent( this.settings.availableShells, diff --git a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts index 56196f08f7a64..cefd984a0d4d2 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -189,9 +189,6 @@ export default function createMainProcessClient(): MainProcessClient { signalUserInterfaceReadiness(args: { success: boolean }) { ipcRenderer.send(WindowsManagerIpc.SignalUserInterfaceReadiness, args); }, - refreshClusterList() { - ipcRenderer.send(MainProcessIpc.RefreshClusterList); - }, selectDirectoryForDesktopSession(args: { desktopUri: string; login: string; @@ -264,6 +261,28 @@ export default function createMainProcessClient(): MainProcessClient { ), }; }, + subscribeToClusterStore: listener => { + const { close } = startAwaitableSenderListener( + MainProcessIpc.InitClusterStoreSubscription, + listener + ); + + return { + cleanup: close, + }; + }, + addCluster: async (proxyAddress: string) => { + return await ipcRenderer.invoke(MainProcessIpc.AddCluster, proxyAddress); + }, + syncRootClusters: async options => { + return await ipcRenderer.invoke(MainProcessIpc.SyncRootClusters, options); + }, + syncCluster: (clusterUri: RootClusterUri) => { + return ipcRenderer.invoke(MainProcessIpc.SyncCluster, { clusterUri }); + }, + logoutCluster: (clusterUri: RootClusterUri) => { + return ipcRenderer.invoke(MainProcessIpc.Logout, { clusterUri }); + }, }; } @@ -274,7 +293,6 @@ export default function createMainProcessClient(): MainProcessClient { * The main process is expected to create an `AwaitableSender` using the received port, * enabling it to send messages that require acknowledgments from the renderer. */ -// eslint-disable-next-line unused-imports/no-unused-vars function startAwaitableSenderListener( channel: string, listener: (value: T) => void diff --git a/web/packages/teleterm/src/mainProcess/navigationHandler.test.ts b/web/packages/teleterm/src/mainProcess/navigationHandler.test.ts new file mode 100644 index 0000000000000..61498b898962a --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/navigationHandler.test.ts @@ -0,0 +1,120 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { dialog, shell, WebContents } from 'electron'; + +import Logger, { NullService } from 'teleterm/logger'; +import { makeRuntimeSettings } from 'teleterm/mainProcess/fixtures/mocks'; +import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; + +import { registerNavigationHandlers } from './navigationHandler'; + +beforeAll(() => { + Logger.init(new NullService()); +}); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +jest.mock('electron', () => ({ + dialog: { + showErrorBox: jest.fn(), + }, + shell: { + openExternal: jest.fn(), + }, +})); + +const cluster = makeRootCluster(); + +describe('opening links to', () => { + test.each([ + { + name: 'Teleport', + url: 'https://goteleport.com/', + allowed: true, + }, + { + name: 'Gravitational GitHub', + url: 'https://github.com/gravitational/', + allowed: true, + }, + { + name: 'cluster SSO host', + // comes from makeRootCluster + url: 'https://example.auth0.com/some-path', + allowed: true, + }, + { + name: 'cluster proxy host', + // comes from makeRootCluster + url: 'https://teleport-local.com:3080/some-path', + allowed: true, + }, + { + name: 'non-HTTPS URLs', + url: 'http://goteleport.com', + allowed: false, + }, + { + name: 'non-Gravitational GitHub', + url: 'https://github.com/abc', + allowed: false, + }, + { + name: 'arbitrary URLs', + url: 'https://google.com', + allowed: false, + }, + ])('$name', test => { + let handler: Parameters[0]; + registerNavigationHandlers( + { + setWindowOpenHandler: d => { + handler = d; + }, + on: jest.fn(), + }, + makeRuntimeSettings(), + { getRootClusters: () => [cluster] }, + new Logger() + ); + + const result = handler({ + url: test.url, + frameName: '', + features: '', + disposition: 'default', + referrer: undefined, + }); + + expect(result).toEqual({ action: 'deny' }); + /* eslint-disable jest/no-conditional-expect */ + if (test.allowed) { + expect(shell.openExternal).toHaveBeenCalledWith(test.url); + expect(dialog.showErrorBox).not.toHaveBeenCalled(); + } else { + expect(shell.openExternal).not.toHaveBeenCalled(); + expect(dialog.showErrorBox).toHaveBeenCalledWith( + 'Cannot open this link', + 'The domain does not match any of the allowed domains. Check main.log for more details.' + ); + } + }); +}); diff --git a/web/packages/teleterm/src/mainProcess/navigationHandler.ts b/web/packages/teleterm/src/mainProcess/navigationHandler.ts new file mode 100644 index 0000000000000..eb200c6328059 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/navigationHandler.ts @@ -0,0 +1,147 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { dialog, shell, WebContents } from 'electron'; + +import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; + +import { proxyHostToBrowserProxyHost } from 'teleterm/services/tshd/cluster'; +import { Logger, RuntimeSettings } from 'teleterm/types'; + +import { ClusterStore } from './clusterStore'; + +/** + * Limits navigation capabilities to reduce the attack surface. + * See TEL-Q122-19 from "Teleport Core Testing Q1 2022" security audit. + * + * See also points 12, 13 and 14 from the Electron's security tutorial. + * https://github.com/electron/electron/blob/v17.2.0/docs/tutorial/security.md#12-verify-webview-options-before-creation + */ +export function registerNavigationHandlers( + webContents: Pick, + settings: Pick, + clusterStore: Pick, + logger: Logger +): void { + webContents.on('will-navigate', (event, navigationUrl) => { + // Allow reloading the renderer app in dev mode. + if (settings.dev && new URL(navigationUrl).host === 'localhost:8080') { + return; + } + logger.warn(`Navigation to ${navigationUrl} blocked by 'will-navigate'`); + event.preventDefault(); + }); + + // The usage of webview is blocked by default, but let's include the handler just in case. + // https://github.com/electron/electron/blob/v17.2.0/docs/api/webview-tag.md#enabling + webContents.on('will-attach-webview', (event, _, params) => { + logger.warn( + `Opening a webview to ${params.src} blocked by 'will-attach-webview'` + ); + event.preventDefault(); + }); + + // Open links in the browser. + webContents.setWindowOpenHandler(details => { + const url = new URL(details.url); + + if (isUrlSafe(url, clusterStore, logger)) { + shell.openExternal(url.toString()); + } else { + logger.warn( + `Opening a new window to ${url} blocked by 'setWindowOpenHandler'` + ); + dialog.showErrorBox( + 'Cannot open this link', + 'The domain does not match any of the allowed domains. Check main.log for more details.' + ); + } + + return { action: 'deny' }; + }); +} + +function isUrlSafe( + url: URL, + clusterStore: Pick, + logger: Logger +): boolean { + if (url.protocol !== 'https:') { + return false; + } + if (url.host === 'goteleport.com') { + return true; + } + if (url.host === 'github.com' && url.pathname.startsWith('/gravitational/')) { + return true; + } + + const rootClusterProxyHostAllowList = makeRootClusterProxyHostAllowList( + clusterStore.getRootClusters(), + logger + ); + + // Allow opening links to the Web UIs of root clusters currently added in the app. + if (rootClusterProxyHostAllowList.has(url.host)) { + return true; + } +} + +/** + * Produces a list of proxy and SSO hosts of root clusters. This enables us to + * open links to Web UIs of clusters from within Connect. + * + * The port part of a proxy host is dropped if the port is 443. See `proxyHostToBrowserProxyHost` for + * more details. + */ +function makeRootClusterProxyHostAllowList( + clusters: Cluster[], + logger: Logger +): Set { + return new Set( + clusters + .flatMap(c => { + let browserProxyHost: string; + if (c.proxyHost) { + try { + browserProxyHost = proxyHostToBrowserProxyHost(c.proxyHost); + } catch (error) { + logger.error( + 'Ran into an error when converting proxy host to browser proxy host', + error + ); + } + } + + // Allow the SSO host for SSO login/mfa redirects. + let browserSsoHost: string; + if (c.ssoHost) { + try { + browserSsoHost = proxyHostToBrowserProxyHost(c.ssoHost); + } catch (error) { + logger.error( + 'Ran into an error when converting sso host to browser sso host', + error + ); + } + } + return [browserProxyHost, browserSsoHost]; + }) + .filter(Boolean) + ); +} diff --git a/web/packages/teleterm/src/mainProcess/rootClusterProxyHostAllowList.ts b/web/packages/teleterm/src/mainProcess/rootClusterProxyHostAllowList.ts deleted file mode 100644 index 9551237e0bbdb..0000000000000 --- a/web/packages/teleterm/src/mainProcess/rootClusterProxyHostAllowList.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { ipcMain } from 'electron'; - -import { isAbortError } from 'shared/utils/abortError'; - -import { MainProcessIpc } from 'teleterm/mainProcess/types'; -import { TshdClient } from 'teleterm/services/tshd'; -import { cloneAbortSignal } from 'teleterm/services/tshd/cloneableClient'; -import { proxyHostToBrowserProxyHost } from 'teleterm/services/tshd/cluster'; -import * as tshd from 'teleterm/services/tshd/types'; -import { Logger } from 'teleterm/types'; - -export type RootClusterProxyHostAllowList = Set; - -/** - * Refreshes the allow list whenever the renderer process notifies the main process that the list of - * clusters in ClustersService got updated. - * - * The allow list includes proxy hosts of root clusters. This enables us to open links to Web UIs of - * clusters from within Connect. - * - * The port part of a proxy host is dropped if the port is 443. See proxyHostToBrowserProxyHost for - * more details. - */ -export function manageRootClusterProxyHostAllowList({ - tshdClient, - logger, - allowList, -}: { - tshdClient: TshdClient; - logger: Logger; - allowList: RootClusterProxyHostAllowList; -}) { - let abortController: AbortController; - - const refreshAllowList = async () => { - // Allow only one call to be in progress. This ensures that on subsequent calls to - // refreshAllowList, we always store only the most recent version of the list. - abortController?.abort(); - abortController = new AbortController(); - - let rootClusters: tshd.Cluster[]; - try { - const { response } = await tshdClient.listRootClusters( - {}, - { abort: cloneAbortSignal(abortController.signal) } - ); - rootClusters = response.clusters; - } catch (error) { - if (isAbortError(error)) { - // Ignore abort errors. They will be logged by the gRPC client middleware. - return; - } - - logger.error('Could not fetch root clusters', error); - // Return instead of throwing as there's nothing else we can do with the error at this place - // in the program. - return; - } - - allowList.clear(); - for (const rootCluster of rootClusters) { - if (rootCluster.proxyHost) { - let browserProxyHost: string; - try { - browserProxyHost = proxyHostToBrowserProxyHost(rootCluster.proxyHost); - allowList.add(browserProxyHost); - } catch (error) { - logger.error( - 'Ran into an error when converting proxy host to browser proxy host', - error - ); - } - } - - // Allow the SSO host for SSO login/mfa redirects. - if (rootCluster.ssoHost) { - let browserSsoHost: string; - try { - browserSsoHost = proxyHostToBrowserProxyHost(rootCluster.ssoHost); - allowList.add(browserSsoHost); - } catch (error) { - logger.error( - 'Ran into an error when converting sso host to browser sso host', - error - ); - } - } - } - }; - - refreshAllowList(); - - ipcMain.on(MainProcessIpc.RefreshClusterList, () => { - refreshAllowList(); - }); -} diff --git a/web/packages/teleterm/src/mainProcess/types.ts b/web/packages/teleterm/src/mainProcess/types.ts index e5749c8183335..3a012542d267d 100644 --- a/web/packages/teleterm/src/mainProcess/types.ts +++ b/web/packages/teleterm/src/mainProcess/types.ts @@ -16,10 +16,14 @@ * along with this program. If not, see . */ +import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; + import { DeepLinkParseResult } from 'teleterm/deepLinks'; +import type { ClusterStoreUpdate } from 'teleterm/mainProcess/clusterStore'; import { CreateAgentConfigFileArgs } from 'teleterm/mainProcess/createAgentConfigFile'; import { AppUpdateEvent } from 'teleterm/services/appUpdater'; import { FileStorage } from 'teleterm/services/fileStorage'; +import { CloneableAbortSignal } from 'teleterm/services/tshd'; import { Document } from 'teleterm/ui/services/workspacesService'; import { RootClusterUri } from 'teleterm/ui/uri'; @@ -195,7 +199,6 @@ export type MainProcessClient = { * interacted with the relevant modals during startup and is free to use the app. */ signalUserInterfaceReadiness(args: { success: boolean }): void; - refreshClusterList(): void; /** * Opens the Electron directory picker and sends the selected path to tshd through SetSharedDirectoryForDesktopSession. * tshd then verifies whether there is an active session for the specified desktop user and attempts to open the directory. @@ -229,6 +232,15 @@ export type MainProcessClient = { ): { cleanup: () => void; }; + addCluster(proxyAddress: string): Promise; + syncCluster(clusterUri: RootClusterUri): Promise; + syncRootClusters(options: { + abortSignal: CloneableAbortSignal; + }): Promise; + logoutCluster(clusterUri: RootClusterUri): Promise; + subscribeToClusterStore(listener: (value: ClusterStoreUpdate) => void): { + cleanup: () => void; + }; }; export type ChildProcessAddresses = { @@ -342,7 +354,6 @@ export enum RendererIpc { export enum MainProcessIpc { GetRuntimeSettings = 'main-process-get-runtime-settings', TryRemoveConnectMyComputerAgentBinary = 'main-process-try-remove-connect-my-computer-agent-binary', - RefreshClusterList = 'main-process-refresh-cluster-list', DownloadConnectMyComputerAgent = 'main-process-connect-my-computer-download-agent', VerifyConnectMyComputerAgent = 'main-process-connect-my-computer-verify-agent', SaveTextToFile = 'main-process-save-text-to-file', @@ -355,6 +366,11 @@ export enum MainProcessIpc { ChangeAppUpdatesManagingCluster = 'main-process-change-app-updates-managing-cluster', MaybeRemoveAppUpdatesManagingCluster = 'main-process-maybe-remove-app-updates-managing-cluster', SupportsAppUpdates = 'main-process-supports-app-updates', + InitClusterStoreSubscription = 'main-process-init-cluster-store-subscription', + SyncCluster = 'main-process-sync-cluster', + AddCluster = 'main-process-add-cluster', + SyncRootClusters = 'main-process-sync-root-clusters', + Logout = 'main-process-logout', } export enum WindowsManagerIpc { diff --git a/web/packages/teleterm/src/mainProcess/windowsManager.ts b/web/packages/teleterm/src/mainProcess/windowsManager.ts index c68d44d24ecc5..9d5d335e0cb4b 100644 --- a/web/packages/teleterm/src/mainProcess/windowsManager.ts +++ b/web/packages/teleterm/src/mainProcess/windowsManager.ts @@ -31,6 +31,8 @@ import { screen, } from 'electron'; +import { ensureError } from 'shared/utils/error'; + import { DeepLinkParseResult } from 'teleterm/deepLinks'; import Logger from 'teleterm/logger'; import { @@ -69,6 +71,7 @@ export class WindowsManager { * by the OS (e.g. Command+H). */ private isInBackgroundMode: boolean; + private crashWindowPromise: Promise; constructor( private fileStorage: FileStorage, @@ -516,6 +519,39 @@ export class WindowsManager { return keepRunning; } + /** + * Displays an error in a system dialog and offers reloading the window + * or quitting the app. + */ + async crashWindow(error: unknown): Promise { + if (this.crashWindowPromise) { + return this.crashWindowPromise; + } + this.crashWindowPromise = this.doCrashWindow(error); + try { + await this.crashWindowPromise; + } finally { + this.crashWindowPromise = undefined; + } + } + + private async doCrashWindow(error: unknown): Promise { + this.logger.error('Window crashed', error); + const { response } = await dialog.showMessageBox(this.window, { + type: 'error', + message: 'Teleport Connect has crashed', + detail: ensureError(error).message, + buttons: ['Reload Window', 'Quit'], + defaultId: 0, + noLink: true, + }); + if (response === 0) { + this.window.reload(); + } else { + app.quit(); + } + } + private isWindowUsable(): boolean { return this.window && !this.window.isDestroyed(); } diff --git a/web/packages/teleterm/src/ui/App.test.tsx b/web/packages/teleterm/src/ui/App.test.tsx index d15b30d1b5904..e97512b8f6984 100644 --- a/web/packages/teleterm/src/ui/App.test.tsx +++ b/web/packages/teleterm/src/ui/App.test.tsx @@ -25,7 +25,6 @@ import { mockIntersectionObserver } from 'jsdom-testing-mocks'; import { render } from 'design/utils/testing'; import Logger, { NullService } from 'teleterm/logger'; -import { MockedUnaryCall } from 'teleterm/services/tshd/cloneableClient'; import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; import { App } from 'teleterm/ui/App'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; @@ -67,6 +66,9 @@ test('activating a workspace via deep link overrides the previously active works connected: false, }); const appContext = new MockAppContext(); + appContext.addRootCluster(deepLinkCluster); + appContext.addRootCluster(previouslyActiveCluster); + jest .spyOn(appContext.statePersistenceService, 'getWorkspacesState') .mockReturnValue({ @@ -88,11 +90,6 @@ test('activating a workspace via deep link overrides the previously active works 'usageReporting.enabled', false ); - jest.spyOn(appContext.tshd, 'listRootClusters').mockReturnValue( - new MockedUnaryCall({ - clusters: [deepLinkCluster, previouslyActiveCluster], - }) - ); render(); @@ -198,11 +195,7 @@ test.each<{ 'usageReporting.enabled', false ); - jest.spyOn(appContext.tshd, 'listRootClusters').mockReturnValue( - new MockedUnaryCall({ - clusters: [rootCluster], - }) - ); + appContext.addRootCluster(rootCluster); render(); diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx index 3d5cd873e40fc..1d773d5706f95 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx @@ -172,11 +172,11 @@ it('shows two separate prompt texts during SSO login', async () => { .spyOn(appContext.tshd, 'login') .mockImplementation(async () => loginPromise); - const { resolve: resolveGetClusterPromise, promise: getClusterPromise } = - Promise.withResolvers>(); + const { resolve: resolveSyncClusterPromise, promise: syncClusterPromise } = + Promise.withResolvers(); jest - .spyOn(appContext.tshd, 'getCluster') - .mockImplementation(async () => getClusterPromise); + .spyOn(appContext.mainProcessClient, 'syncCluster') + .mockImplementation(async () => syncClusterPromise); render( @@ -205,6 +205,6 @@ it('shows two separate prompt texts during SSO login', async () => { await act(async () => { // Resolve the promise to avoid leaving a hanging promise around. - resolveGetClusterPromise(new MockedUnaryCall(cluster)); + resolveSyncClusterPromise(); }); }); diff --git a/web/packages/teleterm/src/ui/Vnet/integration.test.tsx b/web/packages/teleterm/src/ui/Vnet/integration.test.tsx index 9a731ebf32836..054bcd675fd75 100644 --- a/web/packages/teleterm/src/ui/Vnet/integration.test.tsx +++ b/web/packages/teleterm/src/ui/Vnet/integration.test.tsx @@ -71,6 +71,7 @@ test.each(tests)( const user = userEvent.setup(); const ctx = new MockAppContext(); const rootCluster = makeRootCluster(); + ctx.addRootCluster(rootCluster, { noActivate: true }); ctx.configService.set('usageReporting.enabled', false); jest.spyOn(ctx.tshd, 'listUnifiedResources').mockReturnValue( @@ -84,11 +85,6 @@ test.each(tests)( ], }) ); - jest.spyOn(ctx.tshd, 'listRootClusters').mockReturnValue( - new MockedUnaryCall({ - clusters: [rootCluster], - }) - ); jest.spyOn(ctx.vnet, 'getServiceInfo').mockReturnValue( new MockedUnaryCall({ appDnsZones: [proxyHostname(rootCluster.proxyHost)], @@ -167,6 +163,7 @@ test.each(tests)( const user = userEvent.setup(); const ctx = new MockAppContext(); const rootCluster = makeRootCluster(); + ctx.addRootCluster(rootCluster, { noActivate: true }); ctx.configService.set('usageReporting.enabled', false); ctx.statePersistenceService.putState({ ...ctx.statePersistenceService.getState(), @@ -184,11 +181,6 @@ test.each(tests)( ], }) ); - jest.spyOn(ctx.tshd, 'listRootClusters').mockReturnValue( - new MockedUnaryCall({ - clusters: [rootCluster], - }) - ); jest.spyOn(ctx.vnet, 'getServiceInfo').mockReturnValue( new MockedUnaryCall({ appDnsZones: [proxyHostname(rootCluster.proxyHost)], @@ -247,13 +239,9 @@ test('launching VNet for the first time from the connections panel does not open const user = userEvent.setup(); const ctx = new MockAppContext(); const rootCluster = makeRootCluster(); + ctx.addRootCluster(rootCluster, { noActivate: true }); ctx.configService.set('usageReporting.enabled', false); - jest.spyOn(ctx.tshd, 'listRootClusters').mockReturnValue( - new MockedUnaryCall({ - clusters: [rootCluster], - }) - ); jest.spyOn(ctx.vnet, 'getServiceInfo').mockReturnValue( new MockedUnaryCall({ appDnsZones: [proxyHostname(rootCluster.proxyHost)], diff --git a/web/packages/teleterm/src/ui/appContext.ts b/web/packages/teleterm/src/ui/appContext.ts index f766cdec331ef..b4beaeebb00df 100644 --- a/web/packages/teleterm/src/ui/appContext.ts +++ b/web/packages/teleterm/src/ui/appContext.ts @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -import { debounce } from 'shared/utils/highbar'; - import { ConfigService } from 'teleterm/services/config'; import { TshdClient, VnetClient } from 'teleterm/services/tshd/createClient'; import { @@ -161,7 +159,6 @@ export default class AppContext implements IAppContext { createTshdEventsContextBridgeService(this) ); - this.notifyMainProcessAboutClusterListChanges(); void this.clustersService.syncGatewaysAndCatchErrors(); await this.clustersService.syncRootClustersAndCatchErrors(); this.workspacesService.restorePersistedState(); @@ -197,23 +194,4 @@ export default class AppContext implements IAppContext { get unexpectedVnetShutdownListener(): UnexpectedVnetShutdownListener { return this._unexpectedVnetShutdownListener; } - - private notifyMainProcessAboutClusterListChanges() { - // Debounce the notifications sent to the main process so that we don't unnecessarily send more - // than one notification per frame. The main process doesn't need to be notified absolutely - // immediately after a change in the cluster list. - // - // The clusters map in ClustersService gets updated a bunch of times during the start of the - // app. After each update, the renderer tells the main process to refresh the list. The main - // process sends a request to list root clusters and cancels any pending ones. Debouncing here - // helps to minimize those cancellations. - const refreshClusterList = debounce( - this.mainProcessClient.refreshClusterList, - 16 - ); - this.clustersService.subscribeWithSelector( - state => state.clusters, - refreshClusterList - ); - } } diff --git a/web/packages/teleterm/src/ui/fixtures/mocks.ts b/web/packages/teleterm/src/ui/fixtures/mocks.ts index 87075e10862f9..c7a7f69e7c16d 100644 --- a/web/packages/teleterm/src/ui/fixtures/mocks.ts +++ b/web/packages/teleterm/src/ui/fixtures/mocks.ts @@ -55,7 +55,8 @@ export class MockAppContext extends AppContext { addRootClusterWithDoc( cluster: Cluster, - doc: Document[] | Document | undefined + doc: Document[] | Document | undefined, + options?: AddRootClusterOptions ) { this.clustersService.setState(draftState => { draftState.clusters.set(cluster.uri, cluster); @@ -63,13 +64,20 @@ export class MockAppContext extends AppContext { const docs = Array.isArray(doc) ? doc : [doc]; this.workspacesService.addWorkspace(cluster.uri); this.workspacesService.setState(draftState => { - draftState.rootClusterUri = cluster.uri; + if (!options?.noActivate) { + draftState.rootClusterUri = cluster.uri; + } draftState.workspaces[cluster.uri].documents = docs.filter(Boolean); draftState.workspaces[cluster.uri].location = docs[0]?.uri; }); } - addRootCluster(cluster: Cluster) { - this.addRootClusterWithDoc(cluster, undefined); + addRootCluster(cluster: Cluster, options?: AddRootClusterOptions) { + this.addRootClusterWithDoc(cluster, undefined, options); } } + +interface AddRootClusterOptions { + /** Does not set the cluster as active in workspaces service. */ + noActivate?: boolean; +} diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts index 1265af5d4f494..9aa71b09cac1e 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { MainProcessClient } from 'teleterm/mainProcess/types'; +import { MockMainProcessClient } from 'teleterm/mainProcess/fixtures/mocks'; import type { TshdClient } from 'teleterm/services/tshd'; import { MockedUnaryCall } from 'teleterm/services/tshd/cloneableClient'; import { @@ -60,9 +60,7 @@ const UsageServiceMock = UsageService as jest.MockedClass; function createService(client: Partial): ClustersService { return new ClustersService( client as TshdClient, - { - removeKubeConfig: jest.fn().mockResolvedValueOnce(undefined), - } as unknown as MainProcessClient, + new MockMainProcessClient(), new NotificationsServiceMock(), new UsageServiceMock(undefined, undefined, undefined, undefined, undefined) ); @@ -93,49 +91,6 @@ function getClientMocks(): Partial { }; } -test('add cluster', async () => { - const { addCluster } = getClientMocks(); - const service = createService({ - addCluster, - }); - - await service.addRootCluster(clusterUri); - - expect(addCluster).toHaveBeenCalledWith({ name: clusterUri }); - expect(service.state.clusters).toStrictEqual( - new Map([[clusterUri, clusterMock]]) - ); -}); - -test('add cluster does not overwrite the existing cluster', async () => { - const { addCluster } = getClientMocks(); - const service = createService({ - addCluster, - }); - service.state.clusters.set(clusterUri, { - ...clusterMock, - features: { advancedAccessWorkflows: true, isUsageBasedBilling: true }, - }); - - await service.addRootCluster(clusterUri); - - expect(addCluster).toHaveBeenCalledWith({ name: clusterUri }); - expect(service.state.clusters).toStrictEqual( - new Map([ - [ - clusterUri, - { - ...clusterMock, - features: { - advancedAccessWorkflows: true, - isUsageBasedBilling: true, - }, - }, - ], - ]) - ); -}); - test('remove gateways', async () => { const { removeGateway } = getClientMocks(); const service = createService({ removeGateway }); @@ -153,10 +108,6 @@ test('remove gateways', async () => { }); service.setState(draftState => { - draftState.clusters = new Map([ - [clusterMock.uri, clusterMock], - [leafClusterMock.uri, leafClusterMock], - ]); draftState.gateways = new Map([ [gatewayFromRootCluster.uri, gatewayFromRootCluster], [gatewayFromLeafCluster.uri, gatewayFromLeafCluster], @@ -180,49 +131,6 @@ test('remove gateways', async () => { }); }); -test('sync root cluster', async () => { - const { getCluster, listLeafClusters, startHeadlessWatcher } = - getClientMocks(); - const service = createService({ - getCluster, - listLeafClusters, - startHeadlessWatcher, - }); - - await service.syncAndWatchRootClusterWithErrorHandling(clusterUri); - - expect(service.findCluster(clusterUri)).toStrictEqual(clusterMock); - expect(service.findCluster(leafClusterMock.uri)).toStrictEqual( - leafClusterMock - ); - expect(listLeafClusters).toHaveBeenCalledWith({ clusterUri }); - expect(startHeadlessWatcher).toHaveBeenCalledWith({ - rootClusterUri: clusterUri, - }); -}); - -test('logout from cluster', async () => { - const { logout, removeCluster } = getClientMocks(); - const service = createService({ - logout, - removeCluster, - getCluster: () => new MockedUnaryCall({ ...clusterMock, connected: false }), - }); - service.setState(draftState => { - draftState.clusters = new Map([ - [clusterMock.uri, clusterMock], - [leafClusterMock.uri, leafClusterMock], - ]); - }); - - await service.logout(clusterUri); - - expect(logout).toHaveBeenCalledWith({ clusterUri }); - expect(removeCluster).toHaveBeenCalledWith({ clusterUri }); - expect(service.findCluster(clusterMock.uri)).toBeUndefined(); - expect(service.findCluster(leafClusterMock.uri)).toBeUndefined(); -}); - test('create a gateway', async () => { const { createGateway } = getClientMocks(); const service = createService({ diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index b12050707912a..a124bc511f1a5 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -16,10 +16,8 @@ * along with this program. If not, see . */ -import { - Cluster, - ShowResources, -} from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { applyPatches, castDraft, enablePatches } from 'immer'; + import { Gateway } from 'gen-proto-ts/teleport/lib/teleterm/v1/gateway_pb'; import { CreateAccessRequestRequest, @@ -28,8 +26,9 @@ import { ReviewAccessRequestRequest, } from 'gen-proto-ts/teleport/lib/teleterm/v1/service_pb'; import { useStore } from 'shared/libs/stores'; -import { isAbortError } from 'shared/utils/error'; +import { AbortError, isAbortError } from 'shared/utils/error'; +import type { State as ClustersState } from 'teleterm/mainProcess/clusterStore'; import { MainProcessClient } from 'teleterm/mainProcess/types'; import { cloneAbortSignal, TshdClient } from 'teleterm/services/tshd'; import { getGatewayTargetUriKind } from 'teleterm/services/tshd/gateway'; @@ -42,10 +41,17 @@ import { ImmutableStore } from '../immutableStore'; const { routing } = uri; type ClustersServiceState = { - clusters: Map; + /** + * `clusters` is a local mirror of the `ClusterStore` state from the main process. + * This state is read-only and must not be updated manually — it is managed exclusively + * through `subscribeToClusterStore`. + */ + clusters: ClustersState; gateways: Map; }; +enablePatches(); + export function createClusterServiceState(): ClustersServiceState { return { clusters: new Map(), @@ -63,36 +69,16 @@ export class ClustersService extends ImmutableStore { private usageService: UsageService ) { super(); + this.subscribeToClusterStore(); } - async addRootCluster(addr: string) { - const { response: cluster } = await this.client.addCluster({ name: addr }); - // Do not overwrite the existing cluster; - // otherwise we may lose properties fetched from the auth server. - // Consider separating properties read from profile and those - // fetched from the auth server at the RPC message level. - if (!this.state.clusters.has(cluster.uri)) { - this.setState(draft => { - draft.clusters.set(cluster.uri, cluster); - }); - } - - return cluster; + async addRootCluster(proxyAddress: string) { + return this.mainProcessClient.addCluster(proxyAddress); } /** Logs out of the cluster. */ async logout(clusterUri: uri.RootClusterUri) { - // TODO(gzdunek): logout and removeCluster should be combined into a single acton in tshd - await this.client.logout({ clusterUri }); - await this.client.removeCluster({ clusterUri }); - - this.setState(draft => { - draft.clusters.forEach(cluster => { - if (routing.belongsToProfile(clusterUri, cluster.uri)) { - draft.clusters.delete(cluster.uri); - } - }); - }); + await this.mainProcessClient.logoutCluster(clusterUri); } async authenticateWebDevice( @@ -128,7 +114,7 @@ export class ClustersService extends ImmutableStore { clusterUri: uri.RootClusterUri ) { try { - await this.syncRootCluster(clusterUri); + await this.mainProcessClient.syncCluster(clusterUri); } catch (e) { const cluster = this.findCluster(clusterUri); const clusterName = @@ -178,10 +164,7 @@ export class ClustersService extends ImmutableStore { * errors up. */ async syncRootCluster(clusterUri: uri.RootClusterUri) { - await Promise.all([ - this.syncClusterInfo(clusterUri), - this.syncLeafClustersList(clusterUri), - ]); + await this.mainProcessClient.syncCluster(clusterUri); } /** @@ -192,14 +175,26 @@ export class ClustersService extends ImmutableStore { * its associated workspace, resulting in an invalid state. */ async syncRootClustersAndCatchErrors(abortSignal?: AbortSignal) { - let clusters: Cluster[]; - + //TODO(gzdunek): Implement passing abort signals over IPC. + // In this particular case it's fine to discard waiting for the result. + const abortPromise = + abortSignal && + new Promise((_, reject) => { + if (abortSignal.aborted) { + reject(new AbortError()); + return; + } + abortSignal.addEventListener('abort', () => reject(new AbortError()), { + once: true, + }); + }); try { - const { response } = await this.client.listRootClusters( - {}, - { abortSignal: abortSignal && cloneAbortSignal(abortSignal) } - ); - clusters = response.clusters; + await Promise.race([ + abortPromise, + await this.mainProcessClient.syncRootClusters({ + abortSignal: abortSignal && cloneAbortSignal(abortSignal), + }), + ]); } catch (error) { if (isAbortError(error)) { this.logger.info('Listing root clusters aborted'); @@ -219,12 +214,8 @@ export class ClustersService extends ImmutableStore { return; } - this.setState(draft => { - draft.clusters = new Map(clusters.map(c => [c.uri, c])); - }); - // Sync root clusters and resume headless watchers for any active login sessions. - clusters + this.getRootClusters() .filter(c => c.connected) .forEach(c => this.syncAndWatchRootClusterWithErrorHandling(c.uri)); } @@ -250,20 +241,6 @@ export class ClustersService extends ImmutableStore { } } - private async syncLeafClustersList(clusterUri: uri.RootClusterUri) { - const { response } = await this.client.listLeafClusters({ - clusterUri, - }); - - this.setState(draft => { - for (const leaf of response.clusters) { - draft.clusters.set(leaf.uri, leaf); - } - }); - - return response.clusters; - } - /** Assumes roles for the given requests. */ async assumeRoles( rootClusterUri: uri.RootClusterUri, @@ -502,27 +479,15 @@ export class ClustersService extends ImmutableStore { return useStore(this).state; } - private async syncClusterInfo(clusterUri: uri.RootClusterUri) { - try { - const { response: cluster } = await this.client.getCluster({ - clusterUri, - }); - this.setState(draft => { - draft.clusters.set(clusterUri, cluster); - }); - } catch (error) { - this.setState(draft => { - const cluster = draft.clusters.get(clusterUri); - if (cluster) { - // TODO(gzdunek): We should rather store the cluster synchronization status, - // so the callsites could check it before reading the field. - // The workaround is to update the field in case of a failure, - // so the places that wait for showResources !== UNSPECIFIED don't get stuck indefinitely. - cluster.showResources = ShowResources.ACCESSIBLE_ONLY; + private subscribeToClusterStore(): void { + this.mainProcessClient.subscribeToClusterStore(e => { + this.setState(c => { + if (e.kind === 'state') { + c.clusters = castDraft(e.value); + return; } + applyPatches(c.clusters, e.value); }); - - throw error; - } + }); } } diff --git a/web/packages/teleterm/src/ui/services/immutableStore/immutableStore.ts b/web/packages/teleterm/src/ui/services/immutableStore/immutableStore.ts index 65979a1c04712..ae466e0fefc09 100644 --- a/web/packages/teleterm/src/ui/services/immutableStore/immutableStore.ts +++ b/web/packages/teleterm/src/ui/services/immutableStore/immutableStore.ts @@ -18,7 +18,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment*/ -import { enableMapSet, produce } from 'immer'; +import { enableMapSet, produce, Producer } from 'immer'; import stateLogger from 'shared/libs/stores/logger'; import Store from 'shared/libs/stores/store'; @@ -31,7 +31,7 @@ export class ImmutableStore extends Store { protected logger = new Logger(this.constructor.name); // @ts-ignore - setState(nextState: (draftState: T) => T | void): void { + setState(nextState: Producer): void { const prevState = this.state; this.state = produce(this.state, nextState); stateLogger.logState(this.constructor.name, prevState, 'with', this.state); From e9bbccd6add02ebe1447ee49b48568709919443c Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Tue, 28 Oct 2025 14:17:55 +0100 Subject: [PATCH 04/15] Connect: make logout function idempotent (#60553) * Remove `ClusterRemove` RPC, make logging out idempotent * Move calling `removeKubeConfig` and `maybeRemoveAppUpdatesManagingCluster` to main process The main process should not depend on the renderer to clean up its own resources. * Remove cleaning up kube dir * Lint (cherry picked from commit 2d1bc7b90986a9bdc20363cafc1b68c9eb0f9cf2) --- .../go/teleport/lib/teleterm/v1/service.pb.go | 1051 ++++++++--------- .../lib/teleterm/v1/service_grpc.pb.go | 48 +- .../lib/teleterm/v1/service_pb.client.ts | 74 +- .../lib/teleterm/v1/service_pb.grpc-server.ts | 21 +- .../ts/teleport/lib/teleterm/v1/service_pb.ts | 77 +- integration/teleterm_test.go | 62 + .../apiserver/handler/handler_auth.go | 12 +- .../apiserver/handler/handler_clusters.go | 9 - lib/teleterm/daemon/daemon.go | 46 +- proto/teleport/lib/teleterm/v1/service.proto | 14 +- .../clusterStore/clusterStore.test.ts | 7 +- .../mainProcess/clusterStore/clusterStore.ts | 7 +- .../src/mainProcess/fixtures/mocks.ts | 7 +- .../teleterm/src/mainProcess/mainProcess.ts | 49 +- .../src/mainProcess/mainProcessClient.ts | 13 +- .../teleterm/src/mainProcess/types.ts | 10 +- .../src/services/tshd/fixtures/mocks.ts | 1 - .../src/ui/ClusterLogout/logoutWithCleanup.ts | 21 +- .../services/clusters/clustersService.test.ts | 1 - .../ui/services/clusters/clustersService.ts | 5 - 20 files changed, 672 insertions(+), 863 deletions(-) diff --git a/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go index e7512de53b669..983564730e5e2 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go @@ -239,51 +239,6 @@ func (*EmptyResponse) Descriptor() ([]byte, []int) { return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{0} } -// RemoveClusterRequest describes RemoveClusterRequest -type RemoveClusterRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ClusterUri string `protobuf:"bytes,1,opt,name=cluster_uri,json=clusterUri,proto3" json:"cluster_uri,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RemoveClusterRequest) Reset() { - *x = RemoveClusterRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RemoveClusterRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveClusterRequest) ProtoMessage() {} - -func (x *RemoveClusterRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveClusterRequest.ProtoReflect.Descriptor instead. -func (*RemoveClusterRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{1} -} - -func (x *RemoveClusterRequest) GetClusterUri() string { - if x != nil { - return x.ClusterUri - } - return "" -} - // GetClusterRequest describes GetClusterRequest type GetClusterRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -294,7 +249,7 @@ type GetClusterRequest struct { func (x *GetClusterRequest) Reset() { *x = GetClusterRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[2] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -306,7 +261,7 @@ func (x *GetClusterRequest) String() string { func (*GetClusterRequest) ProtoMessage() {} func (x *GetClusterRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[2] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -319,7 +274,7 @@ func (x *GetClusterRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetClusterRequest.ProtoReflect.Descriptor instead. func (*GetClusterRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{2} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{1} } func (x *GetClusterRequest) GetClusterUri() string { @@ -329,17 +284,18 @@ func (x *GetClusterRequest) GetClusterUri() string { return "" } -// LogoutRequest describes LogoutRequest type LogoutRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ClusterUri string `protobuf:"bytes,1,opt,name=cluster_uri,json=clusterUri,proto3" json:"cluster_uri,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ClusterUri string `protobuf:"bytes,1,opt,name=cluster_uri,json=clusterUri,proto3" json:"cluster_uri,omitempty"` + // Whether to remove the associated YAML profile after logout. + RemoveProfile bool `protobuf:"varint,2,opt,name=remove_profile,json=removeProfile,proto3" json:"remove_profile,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LogoutRequest) Reset() { *x = LogoutRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -351,7 +307,7 @@ func (x *LogoutRequest) String() string { func (*LogoutRequest) ProtoMessage() {} func (x *LogoutRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -364,7 +320,7 @@ func (x *LogoutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. func (*LogoutRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{3} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{2} } func (x *LogoutRequest) GetClusterUri() string { @@ -374,6 +330,13 @@ func (x *LogoutRequest) GetClusterUri() string { return "" } +func (x *LogoutRequest) GetRemoveProfile() bool { + if x != nil { + return x.RemoveProfile + } + return false +} + type StartHeadlessWatcherRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RootClusterUri string `protobuf:"bytes,1,opt,name=root_cluster_uri,json=rootClusterUri,proto3" json:"root_cluster_uri,omitempty"` @@ -383,7 +346,7 @@ type StartHeadlessWatcherRequest struct { func (x *StartHeadlessWatcherRequest) Reset() { *x = StartHeadlessWatcherRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -395,7 +358,7 @@ func (x *StartHeadlessWatcherRequest) String() string { func (*StartHeadlessWatcherRequest) ProtoMessage() {} func (x *StartHeadlessWatcherRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -408,7 +371,7 @@ func (x *StartHeadlessWatcherRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartHeadlessWatcherRequest.ProtoReflect.Descriptor instead. func (*StartHeadlessWatcherRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{4} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{3} } func (x *StartHeadlessWatcherRequest) GetRootClusterUri() string { @@ -426,7 +389,7 @@ type StartHeadlessWatcherResponse struct { func (x *StartHeadlessWatcherResponse) Reset() { *x = StartHeadlessWatcherResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -438,7 +401,7 @@ func (x *StartHeadlessWatcherResponse) String() string { func (*StartHeadlessWatcherResponse) ProtoMessage() {} func (x *StartHeadlessWatcherResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -451,7 +414,7 @@ func (x *StartHeadlessWatcherResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartHeadlessWatcherResponse.ProtoReflect.Descriptor instead. func (*StartHeadlessWatcherResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{5} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{4} } type GetAccessRequestRequest struct { @@ -465,7 +428,7 @@ type GetAccessRequestRequest struct { func (x *GetAccessRequestRequest) Reset() { *x = GetAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -477,7 +440,7 @@ func (x *GetAccessRequestRequest) String() string { func (*GetAccessRequestRequest) ProtoMessage() {} func (x *GetAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -490,7 +453,7 @@ func (x *GetAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestRequest.ProtoReflect.Descriptor instead. func (*GetAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{6} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{5} } func (x *GetAccessRequestRequest) GetClusterUri() string { @@ -517,7 +480,7 @@ type GetAccessRequestsRequest struct { func (x *GetAccessRequestsRequest) Reset() { *x = GetAccessRequestsRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -529,7 +492,7 @@ func (x *GetAccessRequestsRequest) String() string { func (*GetAccessRequestsRequest) ProtoMessage() {} func (x *GetAccessRequestsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -542,7 +505,7 @@ func (x *GetAccessRequestsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestsRequest.ProtoReflect.Descriptor instead. func (*GetAccessRequestsRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{7} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{6} } func (x *GetAccessRequestsRequest) GetClusterUri() string { @@ -561,7 +524,7 @@ type GetAccessRequestResponse struct { func (x *GetAccessRequestResponse) Reset() { *x = GetAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -573,7 +536,7 @@ func (x *GetAccessRequestResponse) String() string { func (*GetAccessRequestResponse) ProtoMessage() {} func (x *GetAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -586,7 +549,7 @@ func (x *GetAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestResponse.ProtoReflect.Descriptor instead. func (*GetAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{8} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{7} } func (x *GetAccessRequestResponse) GetRequest() *AccessRequest { @@ -605,7 +568,7 @@ type GetAccessRequestsResponse struct { func (x *GetAccessRequestsResponse) Reset() { *x = GetAccessRequestsResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -617,7 +580,7 @@ func (x *GetAccessRequestsResponse) String() string { func (*GetAccessRequestsResponse) ProtoMessage() {} func (x *GetAccessRequestsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -630,7 +593,7 @@ func (x *GetAccessRequestsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestsResponse.ProtoReflect.Descriptor instead. func (*GetAccessRequestsResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{9} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{8} } func (x *GetAccessRequestsResponse) GetRequests() []*AccessRequest { @@ -650,7 +613,7 @@ type DeleteAccessRequestRequest struct { func (x *DeleteAccessRequestRequest) Reset() { *x = DeleteAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -662,7 +625,7 @@ func (x *DeleteAccessRequestRequest) String() string { func (*DeleteAccessRequestRequest) ProtoMessage() {} func (x *DeleteAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -675,7 +638,7 @@ func (x *DeleteAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAccessRequestRequest.ProtoReflect.Descriptor instead. func (*DeleteAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{10} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{9} } func (x *DeleteAccessRequestRequest) GetRootClusterUri() string { @@ -718,7 +681,7 @@ type CreateAccessRequestRequest struct { func (x *CreateAccessRequestRequest) Reset() { *x = CreateAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -730,7 +693,7 @@ func (x *CreateAccessRequestRequest) String() string { func (*CreateAccessRequestRequest) ProtoMessage() {} func (x *CreateAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -743,7 +706,7 @@ func (x *CreateAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAccessRequestRequest.ProtoReflect.Descriptor instead. func (*CreateAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{11} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{10} } func (x *CreateAccessRequestRequest) GetRootClusterUri() string { @@ -818,7 +781,7 @@ type CreateAccessRequestResponse struct { func (x *CreateAccessRequestResponse) Reset() { *x = CreateAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -830,7 +793,7 @@ func (x *CreateAccessRequestResponse) String() string { func (*CreateAccessRequestResponse) ProtoMessage() {} func (x *CreateAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -843,7 +806,7 @@ func (x *CreateAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAccessRequestResponse.ProtoReflect.Descriptor instead. func (*CreateAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{12} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{11} } func (x *CreateAccessRequestResponse) GetRequest() *AccessRequest { @@ -864,7 +827,7 @@ type AssumeRoleRequest struct { func (x *AssumeRoleRequest) Reset() { *x = AssumeRoleRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -876,7 +839,7 @@ func (x *AssumeRoleRequest) String() string { func (*AssumeRoleRequest) ProtoMessage() {} func (x *AssumeRoleRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -889,7 +852,7 @@ func (x *AssumeRoleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AssumeRoleRequest.ProtoReflect.Descriptor instead. func (*AssumeRoleRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{13} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{12} } func (x *AssumeRoleRequest) GetRootClusterUri() string { @@ -923,7 +886,7 @@ type GetRequestableRolesRequest struct { func (x *GetRequestableRolesRequest) Reset() { *x = GetRequestableRolesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -935,7 +898,7 @@ func (x *GetRequestableRolesRequest) String() string { func (*GetRequestableRolesRequest) ProtoMessage() {} func (x *GetRequestableRolesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -948,7 +911,7 @@ func (x *GetRequestableRolesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequestableRolesRequest.ProtoReflect.Descriptor instead. func (*GetRequestableRolesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{14} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{13} } func (x *GetRequestableRolesRequest) GetClusterUri() string { @@ -975,7 +938,7 @@ type GetRequestableRolesResponse struct { func (x *GetRequestableRolesResponse) Reset() { *x = GetRequestableRolesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -987,7 +950,7 @@ func (x *GetRequestableRolesResponse) String() string { func (*GetRequestableRolesResponse) ProtoMessage() {} func (x *GetRequestableRolesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1000,7 +963,7 @@ func (x *GetRequestableRolesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequestableRolesResponse.ProtoReflect.Descriptor instead. func (*GetRequestableRolesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{15} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{14} } func (x *GetRequestableRolesResponse) GetRoles() []string { @@ -1032,7 +995,7 @@ type ReviewAccessRequestRequest struct { func (x *ReviewAccessRequestRequest) Reset() { *x = ReviewAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1044,7 +1007,7 @@ func (x *ReviewAccessRequestRequest) String() string { func (*ReviewAccessRequestRequest) ProtoMessage() {} func (x *ReviewAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1057,7 +1020,7 @@ func (x *ReviewAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReviewAccessRequestRequest.ProtoReflect.Descriptor instead. func (*ReviewAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{16} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{15} } func (x *ReviewAccessRequestRequest) GetRootClusterUri() string { @@ -1111,7 +1074,7 @@ type ReviewAccessRequestResponse struct { func (x *ReviewAccessRequestResponse) Reset() { *x = ReviewAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1123,7 +1086,7 @@ func (x *ReviewAccessRequestResponse) String() string { func (*ReviewAccessRequestResponse) ProtoMessage() {} func (x *ReviewAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1136,7 +1099,7 @@ func (x *ReviewAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReviewAccessRequestResponse.ProtoReflect.Descriptor instead. func (*ReviewAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{17} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{16} } func (x *ReviewAccessRequestResponse) GetRequest() *AccessRequest { @@ -1158,7 +1121,7 @@ type PromoteAccessRequestRequest struct { func (x *PromoteAccessRequestRequest) Reset() { *x = PromoteAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1170,7 +1133,7 @@ func (x *PromoteAccessRequestRequest) String() string { func (*PromoteAccessRequestRequest) ProtoMessage() {} func (x *PromoteAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1183,7 +1146,7 @@ func (x *PromoteAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PromoteAccessRequestRequest.ProtoReflect.Descriptor instead. func (*PromoteAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{18} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{17} } func (x *PromoteAccessRequestRequest) GetRootClusterUri() string { @@ -1223,7 +1186,7 @@ type PromoteAccessRequestResponse struct { func (x *PromoteAccessRequestResponse) Reset() { *x = PromoteAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1235,7 +1198,7 @@ func (x *PromoteAccessRequestResponse) String() string { func (*PromoteAccessRequestResponse) ProtoMessage() {} func (x *PromoteAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1248,7 +1211,7 @@ func (x *PromoteAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PromoteAccessRequestResponse.ProtoReflect.Descriptor instead. func (*PromoteAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{19} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{18} } func (x *PromoteAccessRequestResponse) GetRequest() *AccessRequest { @@ -1268,7 +1231,7 @@ type GetSuggestedAccessListsRequest struct { func (x *GetSuggestedAccessListsRequest) Reset() { *x = GetSuggestedAccessListsRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1280,7 +1243,7 @@ func (x *GetSuggestedAccessListsRequest) String() string { func (*GetSuggestedAccessListsRequest) ProtoMessage() {} func (x *GetSuggestedAccessListsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1293,7 +1256,7 @@ func (x *GetSuggestedAccessListsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSuggestedAccessListsRequest.ProtoReflect.Descriptor instead. func (*GetSuggestedAccessListsRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{20} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{19} } func (x *GetSuggestedAccessListsRequest) GetRootClusterUri() string { @@ -1319,7 +1282,7 @@ type GetSuggestedAccessListsResponse struct { func (x *GetSuggestedAccessListsResponse) Reset() { *x = GetSuggestedAccessListsResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1331,7 +1294,7 @@ func (x *GetSuggestedAccessListsResponse) String() string { func (*GetSuggestedAccessListsResponse) ProtoMessage() {} func (x *GetSuggestedAccessListsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1344,7 +1307,7 @@ func (x *GetSuggestedAccessListsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSuggestedAccessListsResponse.ProtoReflect.Descriptor instead. func (*GetSuggestedAccessListsResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{21} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{20} } func (x *GetSuggestedAccessListsResponse) GetAccessLists() []*v1.AccessList { @@ -1383,7 +1346,7 @@ type ListKubernetesResourcesRequest struct { func (x *ListKubernetesResourcesRequest) Reset() { *x = ListKubernetesResourcesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1395,7 +1358,7 @@ func (x *ListKubernetesResourcesRequest) String() string { func (*ListKubernetesResourcesRequest) ProtoMessage() {} func (x *ListKubernetesResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1408,7 +1371,7 @@ func (x *ListKubernetesResourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesResourcesRequest.ProtoReflect.Descriptor instead. func (*ListKubernetesResourcesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{22} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{21} } func (x *ListKubernetesResourcesRequest) GetClusterUri() string { @@ -1483,7 +1446,7 @@ type ListKubernetesResourcesResponse struct { func (x *ListKubernetesResourcesResponse) Reset() { *x = ListKubernetesResourcesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1495,7 +1458,7 @@ func (x *ListKubernetesResourcesResponse) String() string { func (*ListKubernetesResourcesResponse) ProtoMessage() {} func (x *ListKubernetesResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1508,7 +1471,7 @@ func (x *ListKubernetesResourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesResourcesResponse.ProtoReflect.Descriptor instead. func (*ListKubernetesResourcesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{23} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{22} } func (x *ListKubernetesResourcesResponse) GetResources() []*KubeResource { @@ -1535,7 +1498,7 @@ type ListKubernetesServersRequest struct { func (x *ListKubernetesServersRequest) Reset() { *x = ListKubernetesServersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1547,7 +1510,7 @@ func (x *ListKubernetesServersRequest) String() string { func (*ListKubernetesServersRequest) ProtoMessage() {} func (x *ListKubernetesServersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1560,7 +1523,7 @@ func (x *ListKubernetesServersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesServersRequest.ProtoReflect.Descriptor instead. func (*ListKubernetesServersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{24} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{23} } func (x *ListKubernetesServersRequest) GetPageSize() int32 { @@ -1604,7 +1567,7 @@ type ListKubernetesServersResponse struct { func (x *ListKubernetesServersResponse) Reset() { *x = ListKubernetesServersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1616,7 +1579,7 @@ func (x *ListKubernetesServersResponse) String() string { func (*ListKubernetesServersResponse) ProtoMessage() {} func (x *ListKubernetesServersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1629,7 +1592,7 @@ func (x *ListKubernetesServersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesServersResponse.ProtoReflect.Descriptor instead. func (*ListKubernetesServersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{25} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{24} } func (x *ListKubernetesServersResponse) GetResources() []*KubeServer { @@ -1656,7 +1619,7 @@ type CredentialInfo struct { func (x *CredentialInfo) Reset() { *x = CredentialInfo{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1668,7 +1631,7 @@ func (x *CredentialInfo) String() string { func (*CredentialInfo) ProtoMessage() {} func (x *CredentialInfo) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1681,7 +1644,7 @@ func (x *CredentialInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialInfo.ProtoReflect.Descriptor instead. func (*CredentialInfo) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{26} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{25} } func (x *CredentialInfo) GetUsername() string { @@ -1703,7 +1666,7 @@ type LoginPasswordlessResponse struct { func (x *LoginPasswordlessResponse) Reset() { *x = LoginPasswordlessResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1715,7 +1678,7 @@ func (x *LoginPasswordlessResponse) String() string { func (*LoginPasswordlessResponse) ProtoMessage() {} func (x *LoginPasswordlessResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1728,7 +1691,7 @@ func (x *LoginPasswordlessResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginPasswordlessResponse.ProtoReflect.Descriptor instead. func (*LoginPasswordlessResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{26} } func (x *LoginPasswordlessResponse) GetPrompt() PasswordlessPrompt { @@ -1760,7 +1723,7 @@ type LoginPasswordlessRequest struct { func (x *LoginPasswordlessRequest) Reset() { *x = LoginPasswordlessRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1772,7 +1735,7 @@ func (x *LoginPasswordlessRequest) String() string { func (*LoginPasswordlessRequest) ProtoMessage() {} func (x *LoginPasswordlessRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1785,7 +1748,7 @@ func (x *LoginPasswordlessRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginPasswordlessRequest.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27} } func (x *LoginPasswordlessRequest) GetRequest() isLoginPasswordlessRequest_Request { @@ -1861,7 +1824,7 @@ type FileTransferRequest struct { func (x *FileTransferRequest) Reset() { *x = FileTransferRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1873,7 +1836,7 @@ func (x *FileTransferRequest) String() string { func (*FileTransferRequest) ProtoMessage() {} func (x *FileTransferRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1886,7 +1849,7 @@ func (x *FileTransferRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FileTransferRequest.ProtoReflect.Descriptor instead. func (*FileTransferRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28} } func (x *FileTransferRequest) GetLogin() string { @@ -1933,7 +1896,7 @@ type FileTransferProgress struct { func (x *FileTransferProgress) Reset() { *x = FileTransferProgress{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1945,7 +1908,7 @@ func (x *FileTransferProgress) String() string { func (*FileTransferProgress) ProtoMessage() {} func (x *FileTransferProgress) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1958,7 +1921,7 @@ func (x *FileTransferProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use FileTransferProgress.ProtoReflect.Descriptor instead. func (*FileTransferProgress) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29} } func (x *FileTransferProgress) GetPercentage() uint32 { @@ -1984,7 +1947,7 @@ type LoginRequest struct { func (x *LoginRequest) Reset() { *x = LoginRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1996,7 +1959,7 @@ func (x *LoginRequest) String() string { func (*LoginRequest) ProtoMessage() {} func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2009,7 +1972,7 @@ func (x *LoginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{31} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30} } func (x *LoginRequest) GetClusterUri() string { @@ -2071,7 +2034,7 @@ type AddClusterRequest struct { func (x *AddClusterRequest) Reset() { *x = AddClusterRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2083,7 +2046,7 @@ func (x *AddClusterRequest) String() string { func (*AddClusterRequest) ProtoMessage() {} func (x *AddClusterRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2096,7 +2059,7 @@ func (x *AddClusterRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddClusterRequest.ProtoReflect.Descriptor instead. func (*AddClusterRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{32} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{31} } func (x *AddClusterRequest) GetName() string { @@ -2114,7 +2077,7 @@ type ListClustersRequest struct { func (x *ListClustersRequest) Reset() { *x = ListClustersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2126,7 +2089,7 @@ func (x *ListClustersRequest) String() string { func (*ListClustersRequest) ProtoMessage() {} func (x *ListClustersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2139,7 +2102,7 @@ func (x *ListClustersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListClustersRequest.ProtoReflect.Descriptor instead. func (*ListClustersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{33} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{32} } type ListClustersResponse struct { @@ -2151,7 +2114,7 @@ type ListClustersResponse struct { func (x *ListClustersResponse) Reset() { *x = ListClustersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2163,7 +2126,7 @@ func (x *ListClustersResponse) String() string { func (*ListClustersResponse) ProtoMessage() {} func (x *ListClustersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2176,7 +2139,7 @@ func (x *ListClustersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListClustersResponse.ProtoReflect.Descriptor instead. func (*ListClustersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{34} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{33} } func (x *ListClustersResponse) GetClusters() []*Cluster { @@ -2195,7 +2158,7 @@ type ListLeafClustersRequest struct { func (x *ListLeafClustersRequest) Reset() { *x = ListLeafClustersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2207,7 +2170,7 @@ func (x *ListLeafClustersRequest) String() string { func (*ListLeafClustersRequest) ProtoMessage() {} func (x *ListLeafClustersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2220,7 +2183,7 @@ func (x *ListLeafClustersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeafClustersRequest.ProtoReflect.Descriptor instead. func (*ListLeafClustersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{35} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{34} } func (x *ListLeafClustersRequest) GetClusterUri() string { @@ -2239,7 +2202,7 @@ type ListDatabaseUsersRequest struct { func (x *ListDatabaseUsersRequest) Reset() { *x = ListDatabaseUsersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2251,7 +2214,7 @@ func (x *ListDatabaseUsersRequest) String() string { func (*ListDatabaseUsersRequest) ProtoMessage() {} func (x *ListDatabaseUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2264,7 +2227,7 @@ func (x *ListDatabaseUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseUsersRequest.ProtoReflect.Descriptor instead. func (*ListDatabaseUsersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{36} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{35} } func (x *ListDatabaseUsersRequest) GetDbUri() string { @@ -2283,7 +2246,7 @@ type ListDatabaseUsersResponse struct { func (x *ListDatabaseUsersResponse) Reset() { *x = ListDatabaseUsersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2295,7 +2258,7 @@ func (x *ListDatabaseUsersResponse) String() string { func (*ListDatabaseUsersResponse) ProtoMessage() {} func (x *ListDatabaseUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2308,7 +2271,7 @@ func (x *ListDatabaseUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseUsersResponse.ProtoReflect.Descriptor instead. func (*ListDatabaseUsersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{37} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{36} } func (x *ListDatabaseUsersResponse) GetUsers() []string { @@ -2335,7 +2298,7 @@ type ListResourcesParams struct { func (x *ListResourcesParams) Reset() { *x = ListResourcesParams{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2347,7 +2310,7 @@ func (x *ListResourcesParams) String() string { func (*ListResourcesParams) ProtoMessage() {} func (x *ListResourcesParams) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2360,7 +2323,7 @@ func (x *ListResourcesParams) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResourcesParams.ProtoReflect.Descriptor instead. func (*ListResourcesParams) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{38} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{37} } func (x *ListResourcesParams) GetStartKey() string { @@ -2401,7 +2364,7 @@ type ListDatabaseServersRequest struct { func (x *ListDatabaseServersRequest) Reset() { *x = ListDatabaseServersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2413,7 +2376,7 @@ func (x *ListDatabaseServersRequest) String() string { func (*ListDatabaseServersRequest) ProtoMessage() {} func (x *ListDatabaseServersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2426,7 +2389,7 @@ func (x *ListDatabaseServersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseServersRequest.ProtoReflect.Descriptor instead. func (*ListDatabaseServersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{39} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{38} } func (x *ListDatabaseServersRequest) GetClusterUri() string { @@ -2453,7 +2416,7 @@ type ListDatabaseServersResponse struct { func (x *ListDatabaseServersResponse) Reset() { *x = ListDatabaseServersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2465,7 +2428,7 @@ func (x *ListDatabaseServersResponse) String() string { func (*ListDatabaseServersResponse) ProtoMessage() {} func (x *ListDatabaseServersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2478,7 +2441,7 @@ func (x *ListDatabaseServersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseServersResponse.ProtoReflect.Descriptor instead. func (*ListDatabaseServersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{40} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{39} } func (x *ListDatabaseServersResponse) GetResources() []*DatabaseServer { @@ -2507,7 +2470,7 @@ type CreateGatewayRequest struct { func (x *CreateGatewayRequest) Reset() { *x = CreateGatewayRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2519,7 +2482,7 @@ func (x *CreateGatewayRequest) String() string { func (*CreateGatewayRequest) ProtoMessage() {} func (x *CreateGatewayRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2532,7 +2495,7 @@ func (x *CreateGatewayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGatewayRequest.ProtoReflect.Descriptor instead. func (*CreateGatewayRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{41} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{40} } func (x *CreateGatewayRequest) GetTargetUri() string { @@ -2571,7 +2534,7 @@ type ListGatewaysRequest struct { func (x *ListGatewaysRequest) Reset() { *x = ListGatewaysRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2583,7 +2546,7 @@ func (x *ListGatewaysRequest) String() string { func (*ListGatewaysRequest) ProtoMessage() {} func (x *ListGatewaysRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2596,7 +2559,7 @@ func (x *ListGatewaysRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListGatewaysRequest.ProtoReflect.Descriptor instead. func (*ListGatewaysRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{42} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{41} } type ListGatewaysResponse struct { @@ -2608,7 +2571,7 @@ type ListGatewaysResponse struct { func (x *ListGatewaysResponse) Reset() { *x = ListGatewaysResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2620,7 +2583,7 @@ func (x *ListGatewaysResponse) String() string { func (*ListGatewaysResponse) ProtoMessage() {} func (x *ListGatewaysResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2633,7 +2596,7 @@ func (x *ListGatewaysResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListGatewaysResponse.ProtoReflect.Descriptor instead. func (*ListGatewaysResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{43} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{42} } func (x *ListGatewaysResponse) GetGateways() []*Gateway { @@ -2652,7 +2615,7 @@ type RemoveGatewayRequest struct { func (x *RemoveGatewayRequest) Reset() { *x = RemoveGatewayRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2664,7 +2627,7 @@ func (x *RemoveGatewayRequest) String() string { func (*RemoveGatewayRequest) ProtoMessage() {} func (x *RemoveGatewayRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2677,7 +2640,7 @@ func (x *RemoveGatewayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveGatewayRequest.ProtoReflect.Descriptor instead. func (*RemoveGatewayRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{44} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{43} } func (x *RemoveGatewayRequest) GetGatewayUri() string { @@ -2697,7 +2660,7 @@ type SetGatewayTargetSubresourceNameRequest struct { func (x *SetGatewayTargetSubresourceNameRequest) Reset() { *x = SetGatewayTargetSubresourceNameRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2709,7 +2672,7 @@ func (x *SetGatewayTargetSubresourceNameRequest) String() string { func (*SetGatewayTargetSubresourceNameRequest) ProtoMessage() {} func (x *SetGatewayTargetSubresourceNameRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2722,7 +2685,7 @@ func (x *SetGatewayTargetSubresourceNameRequest) ProtoReflect() protoreflect.Mes // Deprecated: Use SetGatewayTargetSubresourceNameRequest.ProtoReflect.Descriptor instead. func (*SetGatewayTargetSubresourceNameRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{45} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{44} } func (x *SetGatewayTargetSubresourceNameRequest) GetGatewayUri() string { @@ -2749,7 +2712,7 @@ type SetGatewayLocalPortRequest struct { func (x *SetGatewayLocalPortRequest) Reset() { *x = SetGatewayLocalPortRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2761,7 +2724,7 @@ func (x *SetGatewayLocalPortRequest) String() string { func (*SetGatewayLocalPortRequest) ProtoMessage() {} func (x *SetGatewayLocalPortRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2774,7 +2737,7 @@ func (x *SetGatewayLocalPortRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGatewayLocalPortRequest.ProtoReflect.Descriptor instead. func (*SetGatewayLocalPortRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{46} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{45} } func (x *SetGatewayLocalPortRequest) GetGatewayUri() string { @@ -2800,7 +2763,7 @@ type GetAuthSettingsRequest struct { func (x *GetAuthSettingsRequest) Reset() { *x = GetAuthSettingsRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2812,7 +2775,7 @@ func (x *GetAuthSettingsRequest) String() string { func (*GetAuthSettingsRequest) ProtoMessage() {} func (x *GetAuthSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2825,7 +2788,7 @@ func (x *GetAuthSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAuthSettingsRequest.ProtoReflect.Descriptor instead. func (*GetAuthSettingsRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{47} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{46} } func (x *GetAuthSettingsRequest) GetClusterUri() string { @@ -2844,7 +2807,7 @@ type UpdateTshdEventsServerAddressRequest struct { func (x *UpdateTshdEventsServerAddressRequest) Reset() { *x = UpdateTshdEventsServerAddressRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2856,7 +2819,7 @@ func (x *UpdateTshdEventsServerAddressRequest) String() string { func (*UpdateTshdEventsServerAddressRequest) ProtoMessage() {} func (x *UpdateTshdEventsServerAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2869,7 +2832,7 @@ func (x *UpdateTshdEventsServerAddressRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use UpdateTshdEventsServerAddressRequest.ProtoReflect.Descriptor instead. func (*UpdateTshdEventsServerAddressRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{48} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{47} } func (x *UpdateTshdEventsServerAddressRequest) GetAddress() string { @@ -2887,7 +2850,7 @@ type UpdateTshdEventsServerAddressResponse struct { func (x *UpdateTshdEventsServerAddressResponse) Reset() { *x = UpdateTshdEventsServerAddressResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2899,7 +2862,7 @@ func (x *UpdateTshdEventsServerAddressResponse) String() string { func (*UpdateTshdEventsServerAddressResponse) ProtoMessage() {} func (x *UpdateTshdEventsServerAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2912,7 +2875,7 @@ func (x *UpdateTshdEventsServerAddressResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use UpdateTshdEventsServerAddressResponse.ProtoReflect.Descriptor instead. func (*UpdateTshdEventsServerAddressResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{49} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{48} } type UpdateHeadlessAuthenticationStateRequest struct { @@ -2926,7 +2889,7 @@ type UpdateHeadlessAuthenticationStateRequest struct { func (x *UpdateHeadlessAuthenticationStateRequest) Reset() { *x = UpdateHeadlessAuthenticationStateRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2938,7 +2901,7 @@ func (x *UpdateHeadlessAuthenticationStateRequest) String() string { func (*UpdateHeadlessAuthenticationStateRequest) ProtoMessage() {} func (x *UpdateHeadlessAuthenticationStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2951,7 +2914,7 @@ func (x *UpdateHeadlessAuthenticationStateRequest) ProtoReflect() protoreflect.M // Deprecated: Use UpdateHeadlessAuthenticationStateRequest.ProtoReflect.Descriptor instead. func (*UpdateHeadlessAuthenticationStateRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{50} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{49} } func (x *UpdateHeadlessAuthenticationStateRequest) GetRootClusterUri() string { @@ -2983,7 +2946,7 @@ type UpdateHeadlessAuthenticationStateResponse struct { func (x *UpdateHeadlessAuthenticationStateResponse) Reset() { *x = UpdateHeadlessAuthenticationStateResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2995,7 +2958,7 @@ func (x *UpdateHeadlessAuthenticationStateResponse) String() string { func (*UpdateHeadlessAuthenticationStateResponse) ProtoMessage() {} func (x *UpdateHeadlessAuthenticationStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3008,7 +2971,7 @@ func (x *UpdateHeadlessAuthenticationStateResponse) ProtoReflect() protoreflect. // Deprecated: Use UpdateHeadlessAuthenticationStateResponse.ProtoReflect.Descriptor instead. func (*UpdateHeadlessAuthenticationStateResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{51} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{50} } type CreateConnectMyComputerRoleRequest struct { @@ -3020,7 +2983,7 @@ type CreateConnectMyComputerRoleRequest struct { func (x *CreateConnectMyComputerRoleRequest) Reset() { *x = CreateConnectMyComputerRoleRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3032,7 +2995,7 @@ func (x *CreateConnectMyComputerRoleRequest) String() string { func (*CreateConnectMyComputerRoleRequest) ProtoMessage() {} func (x *CreateConnectMyComputerRoleRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3045,7 +3008,7 @@ func (x *CreateConnectMyComputerRoleRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreateConnectMyComputerRoleRequest.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerRoleRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{52} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{51} } func (x *CreateConnectMyComputerRoleRequest) GetRootClusterUri() string { @@ -3066,7 +3029,7 @@ type CreateConnectMyComputerRoleResponse struct { func (x *CreateConnectMyComputerRoleResponse) Reset() { *x = CreateConnectMyComputerRoleResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3078,7 +3041,7 @@ func (x *CreateConnectMyComputerRoleResponse) String() string { func (*CreateConnectMyComputerRoleResponse) ProtoMessage() {} func (x *CreateConnectMyComputerRoleResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3091,7 +3054,7 @@ func (x *CreateConnectMyComputerRoleResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use CreateConnectMyComputerRoleResponse.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerRoleResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{53} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{52} } func (x *CreateConnectMyComputerRoleResponse) GetCertsReloaded() bool { @@ -3110,7 +3073,7 @@ type CreateConnectMyComputerNodeTokenRequest struct { func (x *CreateConnectMyComputerNodeTokenRequest) Reset() { *x = CreateConnectMyComputerNodeTokenRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3122,7 +3085,7 @@ func (x *CreateConnectMyComputerNodeTokenRequest) String() string { func (*CreateConnectMyComputerNodeTokenRequest) ProtoMessage() {} func (x *CreateConnectMyComputerNodeTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3135,7 +3098,7 @@ func (x *CreateConnectMyComputerNodeTokenRequest) ProtoReflect() protoreflect.Me // Deprecated: Use CreateConnectMyComputerNodeTokenRequest.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerNodeTokenRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{54} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{53} } func (x *CreateConnectMyComputerNodeTokenRequest) GetRootClusterUri() string { @@ -3154,7 +3117,7 @@ type CreateConnectMyComputerNodeTokenResponse struct { func (x *CreateConnectMyComputerNodeTokenResponse) Reset() { *x = CreateConnectMyComputerNodeTokenResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3166,7 +3129,7 @@ func (x *CreateConnectMyComputerNodeTokenResponse) String() string { func (*CreateConnectMyComputerNodeTokenResponse) ProtoMessage() {} func (x *CreateConnectMyComputerNodeTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3179,7 +3142,7 @@ func (x *CreateConnectMyComputerNodeTokenResponse) ProtoReflect() protoreflect.M // Deprecated: Use CreateConnectMyComputerNodeTokenResponse.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerNodeTokenResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{55} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{54} } func (x *CreateConnectMyComputerNodeTokenResponse) GetToken() string { @@ -3198,7 +3161,7 @@ type WaitForConnectMyComputerNodeJoinRequest struct { func (x *WaitForConnectMyComputerNodeJoinRequest) Reset() { *x = WaitForConnectMyComputerNodeJoinRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3210,7 +3173,7 @@ func (x *WaitForConnectMyComputerNodeJoinRequest) String() string { func (*WaitForConnectMyComputerNodeJoinRequest) ProtoMessage() {} func (x *WaitForConnectMyComputerNodeJoinRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3223,7 +3186,7 @@ func (x *WaitForConnectMyComputerNodeJoinRequest) ProtoReflect() protoreflect.Me // Deprecated: Use WaitForConnectMyComputerNodeJoinRequest.ProtoReflect.Descriptor instead. func (*WaitForConnectMyComputerNodeJoinRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{56} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{55} } func (x *WaitForConnectMyComputerNodeJoinRequest) GetRootClusterUri() string { @@ -3242,7 +3205,7 @@ type WaitForConnectMyComputerNodeJoinResponse struct { func (x *WaitForConnectMyComputerNodeJoinResponse) Reset() { *x = WaitForConnectMyComputerNodeJoinResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3254,7 +3217,7 @@ func (x *WaitForConnectMyComputerNodeJoinResponse) String() string { func (*WaitForConnectMyComputerNodeJoinResponse) ProtoMessage() {} func (x *WaitForConnectMyComputerNodeJoinResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3267,7 +3230,7 @@ func (x *WaitForConnectMyComputerNodeJoinResponse) ProtoReflect() protoreflect.M // Deprecated: Use WaitForConnectMyComputerNodeJoinResponse.ProtoReflect.Descriptor instead. func (*WaitForConnectMyComputerNodeJoinResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{57} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{56} } func (x *WaitForConnectMyComputerNodeJoinResponse) GetServer() *Server { @@ -3286,7 +3249,7 @@ type DeleteConnectMyComputerNodeRequest struct { func (x *DeleteConnectMyComputerNodeRequest) Reset() { *x = DeleteConnectMyComputerNodeRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3298,7 +3261,7 @@ func (x *DeleteConnectMyComputerNodeRequest) String() string { func (*DeleteConnectMyComputerNodeRequest) ProtoMessage() {} func (x *DeleteConnectMyComputerNodeRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3311,7 +3274,7 @@ func (x *DeleteConnectMyComputerNodeRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteConnectMyComputerNodeRequest.ProtoReflect.Descriptor instead. func (*DeleteConnectMyComputerNodeRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{58} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{57} } func (x *DeleteConnectMyComputerNodeRequest) GetRootClusterUri() string { @@ -3329,7 +3292,7 @@ type DeleteConnectMyComputerNodeResponse struct { func (x *DeleteConnectMyComputerNodeResponse) Reset() { *x = DeleteConnectMyComputerNodeResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3341,7 +3304,7 @@ func (x *DeleteConnectMyComputerNodeResponse) String() string { func (*DeleteConnectMyComputerNodeResponse) ProtoMessage() {} func (x *DeleteConnectMyComputerNodeResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3354,7 +3317,7 @@ func (x *DeleteConnectMyComputerNodeResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use DeleteConnectMyComputerNodeResponse.ProtoReflect.Descriptor instead. func (*DeleteConnectMyComputerNodeResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{59} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{58} } type GetConnectMyComputerNodeNameRequest struct { @@ -3366,7 +3329,7 @@ type GetConnectMyComputerNodeNameRequest struct { func (x *GetConnectMyComputerNodeNameRequest) Reset() { *x = GetConnectMyComputerNodeNameRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3378,7 +3341,7 @@ func (x *GetConnectMyComputerNodeNameRequest) String() string { func (*GetConnectMyComputerNodeNameRequest) ProtoMessage() {} func (x *GetConnectMyComputerNodeNameRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3391,7 +3354,7 @@ func (x *GetConnectMyComputerNodeNameRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use GetConnectMyComputerNodeNameRequest.ProtoReflect.Descriptor instead. func (*GetConnectMyComputerNodeNameRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{60} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{59} } func (x *GetConnectMyComputerNodeNameRequest) GetRootClusterUri() string { @@ -3410,7 +3373,7 @@ type GetConnectMyComputerNodeNameResponse struct { func (x *GetConnectMyComputerNodeNameResponse) Reset() { *x = GetConnectMyComputerNodeNameResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3422,7 +3385,7 @@ func (x *GetConnectMyComputerNodeNameResponse) String() string { func (*GetConnectMyComputerNodeNameResponse) ProtoMessage() {} func (x *GetConnectMyComputerNodeNameResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3435,7 +3398,7 @@ func (x *GetConnectMyComputerNodeNameResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use GetConnectMyComputerNodeNameResponse.ProtoReflect.Descriptor instead. func (*GetConnectMyComputerNodeNameResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{61} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{60} } func (x *GetConnectMyComputerNodeNameResponse) GetName() string { @@ -3475,7 +3438,7 @@ type ListUnifiedResourcesRequest struct { func (x *ListUnifiedResourcesRequest) Reset() { *x = ListUnifiedResourcesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3487,7 +3450,7 @@ func (x *ListUnifiedResourcesRequest) String() string { func (*ListUnifiedResourcesRequest) ProtoMessage() {} func (x *ListUnifiedResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3500,7 +3463,7 @@ func (x *ListUnifiedResourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnifiedResourcesRequest.ProtoReflect.Descriptor instead. func (*ListUnifiedResourcesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{62} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{61} } func (x *ListUnifiedResourcesRequest) GetClusterUri() string { @@ -3585,7 +3548,7 @@ type SortBy struct { func (x *SortBy) Reset() { *x = SortBy{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3597,7 +3560,7 @@ func (x *SortBy) String() string { func (*SortBy) ProtoMessage() {} func (x *SortBy) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3610,7 +3573,7 @@ func (x *SortBy) ProtoReflect() protoreflect.Message { // Deprecated: Use SortBy.ProtoReflect.Descriptor instead. func (*SortBy) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{63} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{62} } func (x *SortBy) GetIsDesc() bool { @@ -3639,7 +3602,7 @@ type ListUnifiedResourcesResponse struct { func (x *ListUnifiedResourcesResponse) Reset() { *x = ListUnifiedResourcesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3651,7 +3614,7 @@ func (x *ListUnifiedResourcesResponse) String() string { func (*ListUnifiedResourcesResponse) ProtoMessage() {} func (x *ListUnifiedResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3664,7 +3627,7 @@ func (x *ListUnifiedResourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnifiedResourcesResponse.ProtoReflect.Descriptor instead. func (*ListUnifiedResourcesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{64} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{63} } func (x *ListUnifiedResourcesResponse) GetResources() []*PaginatedResource { @@ -3698,7 +3661,7 @@ type PaginatedResource struct { func (x *PaginatedResource) Reset() { *x = PaginatedResource{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3710,7 +3673,7 @@ func (x *PaginatedResource) String() string { func (*PaginatedResource) ProtoMessage() {} func (x *PaginatedResource) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3723,7 +3686,7 @@ func (x *PaginatedResource) ProtoReflect() protoreflect.Message { // Deprecated: Use PaginatedResource.ProtoReflect.Descriptor instead. func (*PaginatedResource) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{65} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{64} } func (x *PaginatedResource) GetResource() isPaginatedResource_Resource { @@ -3828,7 +3791,7 @@ type GetUserPreferencesRequest struct { func (x *GetUserPreferencesRequest) Reset() { *x = GetUserPreferencesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3840,7 +3803,7 @@ func (x *GetUserPreferencesRequest) String() string { func (*GetUserPreferencesRequest) ProtoMessage() {} func (x *GetUserPreferencesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3853,7 +3816,7 @@ func (x *GetUserPreferencesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserPreferencesRequest.ProtoReflect.Descriptor instead. func (*GetUserPreferencesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{66} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{65} } func (x *GetUserPreferencesRequest) GetClusterUri() string { @@ -3872,7 +3835,7 @@ type GetUserPreferencesResponse struct { func (x *GetUserPreferencesResponse) Reset() { *x = GetUserPreferencesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3884,7 +3847,7 @@ func (x *GetUserPreferencesResponse) String() string { func (*GetUserPreferencesResponse) ProtoMessage() {} func (x *GetUserPreferencesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3897,7 +3860,7 @@ func (x *GetUserPreferencesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserPreferencesResponse.ProtoReflect.Descriptor instead. func (*GetUserPreferencesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{67} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{66} } func (x *GetUserPreferencesResponse) GetUserPreferences() *UserPreferences { @@ -3917,7 +3880,7 @@ type UpdateUserPreferencesRequest struct { func (x *UpdateUserPreferencesRequest) Reset() { *x = UpdateUserPreferencesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3929,7 +3892,7 @@ func (x *UpdateUserPreferencesRequest) String() string { func (*UpdateUserPreferencesRequest) ProtoMessage() {} func (x *UpdateUserPreferencesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3942,7 +3905,7 @@ func (x *UpdateUserPreferencesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserPreferencesRequest.ProtoReflect.Descriptor instead. func (*UpdateUserPreferencesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{68} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{67} } func (x *UpdateUserPreferencesRequest) GetClusterUri() string { @@ -3968,7 +3931,7 @@ type UpdateUserPreferencesResponse struct { func (x *UpdateUserPreferencesResponse) Reset() { *x = UpdateUserPreferencesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3980,7 +3943,7 @@ func (x *UpdateUserPreferencesResponse) String() string { func (*UpdateUserPreferencesResponse) ProtoMessage() {} func (x *UpdateUserPreferencesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3993,7 +3956,7 @@ func (x *UpdateUserPreferencesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserPreferencesResponse.ProtoReflect.Descriptor instead. func (*UpdateUserPreferencesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{69} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{68} } func (x *UpdateUserPreferencesResponse) GetUserPreferences() *UserPreferences { @@ -4015,7 +3978,7 @@ type UserPreferences struct { func (x *UserPreferences) Reset() { *x = UserPreferences{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4027,7 +3990,7 @@ func (x *UserPreferences) String() string { func (*UserPreferences) ProtoMessage() {} func (x *UserPreferences) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4040,7 +4003,7 @@ func (x *UserPreferences) ProtoReflect() protoreflect.Message { // Deprecated: Use UserPreferences.ProtoReflect.Descriptor instead. func (*UserPreferences) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{70} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{69} } func (x *UserPreferences) GetClusterPreferences() *v11.ClusterUserPreferences { @@ -4070,7 +4033,7 @@ type AuthenticateWebDeviceRequest struct { func (x *AuthenticateWebDeviceRequest) Reset() { *x = AuthenticateWebDeviceRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4082,7 +4045,7 @@ func (x *AuthenticateWebDeviceRequest) String() string { func (*AuthenticateWebDeviceRequest) ProtoMessage() {} func (x *AuthenticateWebDeviceRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4095,7 +4058,7 @@ func (x *AuthenticateWebDeviceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthenticateWebDeviceRequest.ProtoReflect.Descriptor instead. func (*AuthenticateWebDeviceRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{71} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{70} } func (x *AuthenticateWebDeviceRequest) GetDeviceWebToken() *v12.DeviceWebToken { @@ -4124,7 +4087,7 @@ type AuthenticateWebDeviceResponse struct { func (x *AuthenticateWebDeviceResponse) Reset() { *x = AuthenticateWebDeviceResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4136,7 +4099,7 @@ func (x *AuthenticateWebDeviceResponse) String() string { func (*AuthenticateWebDeviceResponse) ProtoMessage() {} func (x *AuthenticateWebDeviceResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4149,7 +4112,7 @@ func (x *AuthenticateWebDeviceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthenticateWebDeviceResponse.ProtoReflect.Descriptor instead. func (*AuthenticateWebDeviceResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{72} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{71} } func (x *AuthenticateWebDeviceResponse) GetConfirmationToken() *v12.DeviceConfirmationToken { @@ -4168,7 +4131,7 @@ type GetAppRequest struct { func (x *GetAppRequest) Reset() { *x = GetAppRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4180,7 +4143,7 @@ func (x *GetAppRequest) String() string { func (*GetAppRequest) ProtoMessage() {} func (x *GetAppRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4193,7 +4156,7 @@ func (x *GetAppRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAppRequest.ProtoReflect.Descriptor instead. func (*GetAppRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{73} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{72} } func (x *GetAppRequest) GetAppUri() string { @@ -4212,7 +4175,7 @@ type GetAppResponse struct { func (x *GetAppResponse) Reset() { *x = GetAppResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4224,7 +4187,7 @@ func (x *GetAppResponse) String() string { func (*GetAppResponse) ProtoMessage() {} func (x *GetAppResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4237,7 +4200,7 @@ func (x *GetAppResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAppResponse.ProtoReflect.Descriptor instead. func (*GetAppResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{74} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{73} } func (x *GetAppResponse) GetApp() *App { @@ -4260,7 +4223,7 @@ type TargetDesktop struct { func (x *TargetDesktop) Reset() { *x = TargetDesktop{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4272,7 +4235,7 @@ func (x *TargetDesktop) String() string { func (*TargetDesktop) ProtoMessage() {} func (x *TargetDesktop) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4285,7 +4248,7 @@ func (x *TargetDesktop) ProtoReflect() protoreflect.Message { // Deprecated: Use TargetDesktop.ProtoReflect.Descriptor instead. func (*TargetDesktop) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{75} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{74} } func (x *TargetDesktop) GetDesktopUri() string { @@ -4316,7 +4279,7 @@ type ConnectToDesktopRequest struct { func (x *ConnectToDesktopRequest) Reset() { *x = ConnectToDesktopRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4328,7 +4291,7 @@ func (x *ConnectToDesktopRequest) String() string { func (*ConnectToDesktopRequest) ProtoMessage() {} func (x *ConnectToDesktopRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4341,7 +4304,7 @@ func (x *ConnectToDesktopRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectToDesktopRequest.ProtoReflect.Descriptor instead. func (*ConnectToDesktopRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{76} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{75} } func (x *ConnectToDesktopRequest) GetData() []byte { @@ -4369,7 +4332,7 @@ type ConnectToDesktopResponse struct { func (x *ConnectToDesktopResponse) Reset() { *x = ConnectToDesktopResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4381,7 +4344,7 @@ func (x *ConnectToDesktopResponse) String() string { func (*ConnectToDesktopResponse) ProtoMessage() {} func (x *ConnectToDesktopResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4394,7 +4357,7 @@ func (x *ConnectToDesktopResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectToDesktopResponse.ProtoReflect.Descriptor instead. func (*ConnectToDesktopResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{77} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{76} } func (x *ConnectToDesktopResponse) GetData() []byte { @@ -4419,7 +4382,7 @@ type SetSharedDirectoryForDesktopSessionRequest struct { func (x *SetSharedDirectoryForDesktopSessionRequest) Reset() { *x = SetSharedDirectoryForDesktopSessionRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4431,7 +4394,7 @@ func (x *SetSharedDirectoryForDesktopSessionRequest) String() string { func (*SetSharedDirectoryForDesktopSessionRequest) ProtoMessage() {} func (x *SetSharedDirectoryForDesktopSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4444,7 +4407,7 @@ func (x *SetSharedDirectoryForDesktopSessionRequest) ProtoReflect() protoreflect // Deprecated: Use SetSharedDirectoryForDesktopSessionRequest.ProtoReflect.Descriptor instead. func (*SetSharedDirectoryForDesktopSessionRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{78} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{77} } func (x *SetSharedDirectoryForDesktopSessionRequest) GetDesktopUri() string { @@ -4477,7 +4440,7 @@ type SetSharedDirectoryForDesktopSessionResponse struct { func (x *SetSharedDirectoryForDesktopSessionResponse) Reset() { *x = SetSharedDirectoryForDesktopSessionResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4489,7 +4452,7 @@ func (x *SetSharedDirectoryForDesktopSessionResponse) String() string { func (*SetSharedDirectoryForDesktopSessionResponse) ProtoMessage() {} func (x *SetSharedDirectoryForDesktopSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4502,7 +4465,7 @@ func (x *SetSharedDirectoryForDesktopSessionResponse) ProtoReflect() protoreflec // Deprecated: Use SetSharedDirectoryForDesktopSessionResponse.ProtoReflect.Descriptor instead. func (*SetSharedDirectoryForDesktopSessionResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{79} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{78} } // LoginPasswordlessRequestInit contains fields needed to init the stream request. @@ -4516,7 +4479,7 @@ type LoginPasswordlessRequest_LoginPasswordlessRequestInit struct { func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) Reset() { *x = LoginPasswordlessRequest_LoginPasswordlessRequestInit{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4528,7 +4491,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) String() string func (*LoginPasswordlessRequest_LoginPasswordlessRequestInit) ProtoMessage() {} func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4541,7 +4504,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) ProtoReflect() p // Deprecated: Use LoginPasswordlessRequest_LoginPasswordlessRequestInit.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest_LoginPasswordlessRequestInit) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28, 0} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27, 0} } func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) GetClusterUri() string { @@ -4562,7 +4525,7 @@ type LoginPasswordlessRequest_LoginPasswordlessPINResponse struct { func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) Reset() { *x = LoginPasswordlessRequest_LoginPasswordlessPINResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4574,7 +4537,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) String() string func (*LoginPasswordlessRequest_LoginPasswordlessPINResponse) ProtoMessage() {} func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4587,7 +4550,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) ProtoReflect() p // Deprecated: Use LoginPasswordlessRequest_LoginPasswordlessPINResponse.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest_LoginPasswordlessPINResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28, 1} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27, 1} } func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) GetPin() string { @@ -4610,7 +4573,7 @@ type LoginPasswordlessRequest_LoginPasswordlessCredentialResponse struct { func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) Reset() { *x = LoginPasswordlessRequest_LoginPasswordlessCredentialResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4622,7 +4585,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) String() func (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) ProtoMessage() {} func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4635,7 +4598,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) ProtoRefl // Deprecated: Use LoginPasswordlessRequest_LoginPasswordlessCredentialResponse.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28, 2} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27, 2} } func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) GetIndex() int64 { @@ -4660,7 +4623,7 @@ type LoginRequest_LocalParams struct { func (x *LoginRequest_LocalParams) Reset() { *x = LoginRequest_LocalParams{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4672,7 +4635,7 @@ func (x *LoginRequest_LocalParams) String() string { func (*LoginRequest_LocalParams) ProtoMessage() {} func (x *LoginRequest_LocalParams) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4685,7 +4648,7 @@ func (x *LoginRequest_LocalParams) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest_LocalParams.ProtoReflect.Descriptor instead. func (*LoginRequest_LocalParams) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{31, 0} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30, 0} } func (x *LoginRequest_LocalParams) GetUser() string { @@ -4722,7 +4685,7 @@ type LoginRequest_SsoParams struct { func (x *LoginRequest_SsoParams) Reset() { *x = LoginRequest_SsoParams{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[84] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4734,7 +4697,7 @@ func (x *LoginRequest_SsoParams) String() string { func (*LoginRequest_SsoParams) ProtoMessage() {} func (x *LoginRequest_SsoParams) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[84] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4747,7 +4710,7 @@ func (x *LoginRequest_SsoParams) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest_SsoParams.ProtoReflect.Descriptor instead. func (*LoginRequest_SsoParams) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{31, 1} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30, 1} } func (x *LoginRequest_SsoParams) GetProviderType() string { @@ -4769,16 +4732,14 @@ var File_teleport_lib_teleterm_v1_service_proto protoreflect.FileDescriptor const file_teleport_lib_teleterm_v1_service_proto_rawDesc = "" + "\n" + "&teleport/lib/teleterm/v1/service.proto\x12\x18teleport.lib.teleterm.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a'teleport/accesslist/v1/accesslist.proto\x1a7teleport/devicetrust/v1/device_confirmation_token.proto\x1a.teleport/devicetrust/v1/device_web_token.proto\x1a-teleport/lib/teleterm/v1/access_request.proto\x1a\"teleport/lib/teleterm/v1/app.proto\x1a,teleport/lib/teleterm/v1/auth_settings.proto\x1a&teleport/lib/teleterm/v1/cluster.proto\x1a'teleport/lib/teleterm/v1/database.proto\x1a&teleport/lib/teleterm/v1/gateway.proto\x1a#teleport/lib/teleterm/v1/kube.proto\x1a%teleport/lib/teleterm/v1/server.proto\x1a+teleport/lib/teleterm/v1/usage_events.proto\x1a.teleport/lib/teleterm/v1/windows_desktop.proto\x1a5teleport/userpreferences/v1/cluster_preferences.proto\x1a>teleport/userpreferences/v1/unified_resource_preferences.proto\"\x0f\n" + - "\rEmptyResponse\"7\n" + - "\x14RemoveClusterRequest\x12\x1f\n" + - "\vcluster_uri\x18\x01 \x01(\tR\n" + - "clusterUri\"4\n" + + "\rEmptyResponse\"4\n" + "\x11GetClusterRequest\x12\x1f\n" + "\vcluster_uri\x18\x01 \x01(\tR\n" + - "clusterUri\"0\n" + + "clusterUri\"W\n" + "\rLogoutRequest\x12\x1f\n" + "\vcluster_uri\x18\x01 \x01(\tR\n" + - "clusterUri\"G\n" + + "clusterUri\x12%\n" + + "\x0eremove_profile\x18\x02 \x01(\bR\rremoveProfile\"G\n" + "\x1bStartHeadlessWatcherRequest\x12(\n" + "\x10root_cluster_uri\x18\x01 \x01(\tR\x0erootClusterUri\"\x1e\n" + "\x1cStartHeadlessWatcherResponse\"f\n" + @@ -5064,7 +5025,7 @@ const file_teleport_lib_teleterm_v1_service_proto_rawDesc = "" + ")HEADLESS_AUTHENTICATION_STATE_UNSPECIFIED\x10\x00\x12)\n" + "%HEADLESS_AUTHENTICATION_STATE_PENDING\x10\x01\x12(\n" + "$HEADLESS_AUTHENTICATION_STATE_DENIED\x10\x02\x12*\n" + - "&HEADLESS_AUTHENTICATION_STATE_APPROVED\x10\x032\xb4,\n" + + "&HEADLESS_AUTHENTICATION_STATE_APPROVED\x10\x032\xca+\n" + "\x0fTerminalService\x12\xa0\x01\n" + "\x1dUpdateTshdEventsServerAddress\x12>.teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest\x1a?.teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse\x12q\n" + "\x10ListRootClusters\x12-.teleport.lib.teleterm.v1.ListClustersRequest\x1a..teleport.lib.teleterm.v1.ListClustersResponse\x12u\n" + @@ -5085,8 +5046,7 @@ const file_teleport_lib_teleterm_v1_service_proto_rawDesc = "" + "\x17ListKubernetesResources\x128.teleport.lib.teleterm.v1.ListKubernetesResourcesRequest\x1a9.teleport.lib.teleterm.v1.ListKubernetesResourcesResponse\x12\x88\x01\n" + "\x15ListKubernetesServers\x126.teleport.lib.teleterm.v1.ListKubernetesServersRequest\x1a7.teleport.lib.teleterm.v1.ListKubernetesServersResponse\x12\\\n" + "\n" + - "AddCluster\x12+.teleport.lib.teleterm.v1.AddClusterRequest\x1a!.teleport.lib.teleterm.v1.Cluster\x12h\n" + - "\rRemoveCluster\x12..teleport.lib.teleterm.v1.RemoveClusterRequest\x1a'.teleport.lib.teleterm.v1.EmptyResponse\x12m\n" + + "AddCluster\x12+.teleport.lib.teleterm.v1.AddClusterRequest\x1a!.teleport.lib.teleterm.v1.Cluster\x12m\n" + "\fListGateways\x12-.teleport.lib.teleterm.v1.ListGatewaysRequest\x1a..teleport.lib.teleterm.v1.ListGatewaysResponse\x12b\n" + "\rCreateGateway\x12..teleport.lib.teleterm.v1.CreateGatewayRequest\x1a!.teleport.lib.teleterm.v1.Gateway\x12h\n" + "\rRemoveGateway\x12..teleport.lib.teleterm.v1.RemoveGatewayRequest\x1a'.teleport.lib.teleterm.v1.EmptyResponse\x12\x86\x01\n" + @@ -5127,253 +5087,250 @@ func file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP() []byte { } var file_teleport_lib_teleterm_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_teleport_lib_teleterm_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 85) +var file_teleport_lib_teleterm_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 84) var file_teleport_lib_teleterm_v1_service_proto_goTypes = []any{ (PasswordlessPrompt)(0), // 0: teleport.lib.teleterm.v1.PasswordlessPrompt (FileTransferDirection)(0), // 1: teleport.lib.teleterm.v1.FileTransferDirection (HeadlessAuthenticationState)(0), // 2: teleport.lib.teleterm.v1.HeadlessAuthenticationState (*EmptyResponse)(nil), // 3: teleport.lib.teleterm.v1.EmptyResponse - (*RemoveClusterRequest)(nil), // 4: teleport.lib.teleterm.v1.RemoveClusterRequest - (*GetClusterRequest)(nil), // 5: teleport.lib.teleterm.v1.GetClusterRequest - (*LogoutRequest)(nil), // 6: teleport.lib.teleterm.v1.LogoutRequest - (*StartHeadlessWatcherRequest)(nil), // 7: teleport.lib.teleterm.v1.StartHeadlessWatcherRequest - (*StartHeadlessWatcherResponse)(nil), // 8: teleport.lib.teleterm.v1.StartHeadlessWatcherResponse - (*GetAccessRequestRequest)(nil), // 9: teleport.lib.teleterm.v1.GetAccessRequestRequest - (*GetAccessRequestsRequest)(nil), // 10: teleport.lib.teleterm.v1.GetAccessRequestsRequest - (*GetAccessRequestResponse)(nil), // 11: teleport.lib.teleterm.v1.GetAccessRequestResponse - (*GetAccessRequestsResponse)(nil), // 12: teleport.lib.teleterm.v1.GetAccessRequestsResponse - (*DeleteAccessRequestRequest)(nil), // 13: teleport.lib.teleterm.v1.DeleteAccessRequestRequest - (*CreateAccessRequestRequest)(nil), // 14: teleport.lib.teleterm.v1.CreateAccessRequestRequest - (*CreateAccessRequestResponse)(nil), // 15: teleport.lib.teleterm.v1.CreateAccessRequestResponse - (*AssumeRoleRequest)(nil), // 16: teleport.lib.teleterm.v1.AssumeRoleRequest - (*GetRequestableRolesRequest)(nil), // 17: teleport.lib.teleterm.v1.GetRequestableRolesRequest - (*GetRequestableRolesResponse)(nil), // 18: teleport.lib.teleterm.v1.GetRequestableRolesResponse - (*ReviewAccessRequestRequest)(nil), // 19: teleport.lib.teleterm.v1.ReviewAccessRequestRequest - (*ReviewAccessRequestResponse)(nil), // 20: teleport.lib.teleterm.v1.ReviewAccessRequestResponse - (*PromoteAccessRequestRequest)(nil), // 21: teleport.lib.teleterm.v1.PromoteAccessRequestRequest - (*PromoteAccessRequestResponse)(nil), // 22: teleport.lib.teleterm.v1.PromoteAccessRequestResponse - (*GetSuggestedAccessListsRequest)(nil), // 23: teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest - (*GetSuggestedAccessListsResponse)(nil), // 24: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse - (*ListKubernetesResourcesRequest)(nil), // 25: teleport.lib.teleterm.v1.ListKubernetesResourcesRequest - (*ListKubernetesResourcesResponse)(nil), // 26: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse - (*ListKubernetesServersRequest)(nil), // 27: teleport.lib.teleterm.v1.ListKubernetesServersRequest - (*ListKubernetesServersResponse)(nil), // 28: teleport.lib.teleterm.v1.ListKubernetesServersResponse - (*CredentialInfo)(nil), // 29: teleport.lib.teleterm.v1.CredentialInfo - (*LoginPasswordlessResponse)(nil), // 30: teleport.lib.teleterm.v1.LoginPasswordlessResponse - (*LoginPasswordlessRequest)(nil), // 31: teleport.lib.teleterm.v1.LoginPasswordlessRequest - (*FileTransferRequest)(nil), // 32: teleport.lib.teleterm.v1.FileTransferRequest - (*FileTransferProgress)(nil), // 33: teleport.lib.teleterm.v1.FileTransferProgress - (*LoginRequest)(nil), // 34: teleport.lib.teleterm.v1.LoginRequest - (*AddClusterRequest)(nil), // 35: teleport.lib.teleterm.v1.AddClusterRequest - (*ListClustersRequest)(nil), // 36: teleport.lib.teleterm.v1.ListClustersRequest - (*ListClustersResponse)(nil), // 37: teleport.lib.teleterm.v1.ListClustersResponse - (*ListLeafClustersRequest)(nil), // 38: teleport.lib.teleterm.v1.ListLeafClustersRequest - (*ListDatabaseUsersRequest)(nil), // 39: teleport.lib.teleterm.v1.ListDatabaseUsersRequest - (*ListDatabaseUsersResponse)(nil), // 40: teleport.lib.teleterm.v1.ListDatabaseUsersResponse - (*ListResourcesParams)(nil), // 41: teleport.lib.teleterm.v1.ListResourcesParams - (*ListDatabaseServersRequest)(nil), // 42: teleport.lib.teleterm.v1.ListDatabaseServersRequest - (*ListDatabaseServersResponse)(nil), // 43: teleport.lib.teleterm.v1.ListDatabaseServersResponse - (*CreateGatewayRequest)(nil), // 44: teleport.lib.teleterm.v1.CreateGatewayRequest - (*ListGatewaysRequest)(nil), // 45: teleport.lib.teleterm.v1.ListGatewaysRequest - (*ListGatewaysResponse)(nil), // 46: teleport.lib.teleterm.v1.ListGatewaysResponse - (*RemoveGatewayRequest)(nil), // 47: teleport.lib.teleterm.v1.RemoveGatewayRequest - (*SetGatewayTargetSubresourceNameRequest)(nil), // 48: teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest - (*SetGatewayLocalPortRequest)(nil), // 49: teleport.lib.teleterm.v1.SetGatewayLocalPortRequest - (*GetAuthSettingsRequest)(nil), // 50: teleport.lib.teleterm.v1.GetAuthSettingsRequest - (*UpdateTshdEventsServerAddressRequest)(nil), // 51: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest - (*UpdateTshdEventsServerAddressResponse)(nil), // 52: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse - (*UpdateHeadlessAuthenticationStateRequest)(nil), // 53: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest - (*UpdateHeadlessAuthenticationStateResponse)(nil), // 54: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse - (*CreateConnectMyComputerRoleRequest)(nil), // 55: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest - (*CreateConnectMyComputerRoleResponse)(nil), // 56: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse - (*CreateConnectMyComputerNodeTokenRequest)(nil), // 57: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest - (*CreateConnectMyComputerNodeTokenResponse)(nil), // 58: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse - (*WaitForConnectMyComputerNodeJoinRequest)(nil), // 59: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest - (*WaitForConnectMyComputerNodeJoinResponse)(nil), // 60: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse - (*DeleteConnectMyComputerNodeRequest)(nil), // 61: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest - (*DeleteConnectMyComputerNodeResponse)(nil), // 62: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse - (*GetConnectMyComputerNodeNameRequest)(nil), // 63: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest - (*GetConnectMyComputerNodeNameResponse)(nil), // 64: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse - (*ListUnifiedResourcesRequest)(nil), // 65: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest - (*SortBy)(nil), // 66: teleport.lib.teleterm.v1.SortBy - (*ListUnifiedResourcesResponse)(nil), // 67: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse - (*PaginatedResource)(nil), // 68: teleport.lib.teleterm.v1.PaginatedResource - (*GetUserPreferencesRequest)(nil), // 69: teleport.lib.teleterm.v1.GetUserPreferencesRequest - (*GetUserPreferencesResponse)(nil), // 70: teleport.lib.teleterm.v1.GetUserPreferencesResponse - (*UpdateUserPreferencesRequest)(nil), // 71: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest - (*UpdateUserPreferencesResponse)(nil), // 72: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse - (*UserPreferences)(nil), // 73: teleport.lib.teleterm.v1.UserPreferences - (*AuthenticateWebDeviceRequest)(nil), // 74: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest - (*AuthenticateWebDeviceResponse)(nil), // 75: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse - (*GetAppRequest)(nil), // 76: teleport.lib.teleterm.v1.GetAppRequest - (*GetAppResponse)(nil), // 77: teleport.lib.teleterm.v1.GetAppResponse - (*TargetDesktop)(nil), // 78: teleport.lib.teleterm.v1.TargetDesktop - (*ConnectToDesktopRequest)(nil), // 79: teleport.lib.teleterm.v1.ConnectToDesktopRequest - (*ConnectToDesktopResponse)(nil), // 80: teleport.lib.teleterm.v1.ConnectToDesktopResponse - (*SetSharedDirectoryForDesktopSessionRequest)(nil), // 81: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest - (*SetSharedDirectoryForDesktopSessionResponse)(nil), // 82: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse - (*LoginPasswordlessRequest_LoginPasswordlessRequestInit)(nil), // 83: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit - (*LoginPasswordlessRequest_LoginPasswordlessPINResponse)(nil), // 84: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse - (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse)(nil), // 85: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse - (*LoginRequest_LocalParams)(nil), // 86: teleport.lib.teleterm.v1.LoginRequest.LocalParams - (*LoginRequest_SsoParams)(nil), // 87: teleport.lib.teleterm.v1.LoginRequest.SsoParams - (*AccessRequest)(nil), // 88: teleport.lib.teleterm.v1.AccessRequest - (*ResourceID)(nil), // 89: teleport.lib.teleterm.v1.ResourceID - (*timestamppb.Timestamp)(nil), // 90: google.protobuf.Timestamp - (*v1.AccessList)(nil), // 91: teleport.accesslist.v1.AccessList - (*KubeResource)(nil), // 92: teleport.lib.teleterm.v1.KubeResource - (*KubeServer)(nil), // 93: teleport.lib.teleterm.v1.KubeServer - (*Cluster)(nil), // 94: teleport.lib.teleterm.v1.Cluster - (*DatabaseServer)(nil), // 95: teleport.lib.teleterm.v1.DatabaseServer - (*Gateway)(nil), // 96: teleport.lib.teleterm.v1.Gateway - (*Server)(nil), // 97: teleport.lib.teleterm.v1.Server - (*Database)(nil), // 98: teleport.lib.teleterm.v1.Database - (*Kube)(nil), // 99: teleport.lib.teleterm.v1.Kube - (*App)(nil), // 100: teleport.lib.teleterm.v1.App - (*WindowsDesktop)(nil), // 101: teleport.lib.teleterm.v1.WindowsDesktop - (*v11.ClusterUserPreferences)(nil), // 102: teleport.userpreferences.v1.ClusterUserPreferences - (*v11.UnifiedResourcePreferences)(nil), // 103: teleport.userpreferences.v1.UnifiedResourcePreferences - (*v12.DeviceWebToken)(nil), // 104: teleport.devicetrust.v1.DeviceWebToken - (*v12.DeviceConfirmationToken)(nil), // 105: teleport.devicetrust.v1.DeviceConfirmationToken - (*ReportUsageEventRequest)(nil), // 106: teleport.lib.teleterm.v1.ReportUsageEventRequest - (*AuthSettings)(nil), // 107: teleport.lib.teleterm.v1.AuthSettings + (*GetClusterRequest)(nil), // 4: teleport.lib.teleterm.v1.GetClusterRequest + (*LogoutRequest)(nil), // 5: teleport.lib.teleterm.v1.LogoutRequest + (*StartHeadlessWatcherRequest)(nil), // 6: teleport.lib.teleterm.v1.StartHeadlessWatcherRequest + (*StartHeadlessWatcherResponse)(nil), // 7: teleport.lib.teleterm.v1.StartHeadlessWatcherResponse + (*GetAccessRequestRequest)(nil), // 8: teleport.lib.teleterm.v1.GetAccessRequestRequest + (*GetAccessRequestsRequest)(nil), // 9: teleport.lib.teleterm.v1.GetAccessRequestsRequest + (*GetAccessRequestResponse)(nil), // 10: teleport.lib.teleterm.v1.GetAccessRequestResponse + (*GetAccessRequestsResponse)(nil), // 11: teleport.lib.teleterm.v1.GetAccessRequestsResponse + (*DeleteAccessRequestRequest)(nil), // 12: teleport.lib.teleterm.v1.DeleteAccessRequestRequest + (*CreateAccessRequestRequest)(nil), // 13: teleport.lib.teleterm.v1.CreateAccessRequestRequest + (*CreateAccessRequestResponse)(nil), // 14: teleport.lib.teleterm.v1.CreateAccessRequestResponse + (*AssumeRoleRequest)(nil), // 15: teleport.lib.teleterm.v1.AssumeRoleRequest + (*GetRequestableRolesRequest)(nil), // 16: teleport.lib.teleterm.v1.GetRequestableRolesRequest + (*GetRequestableRolesResponse)(nil), // 17: teleport.lib.teleterm.v1.GetRequestableRolesResponse + (*ReviewAccessRequestRequest)(nil), // 18: teleport.lib.teleterm.v1.ReviewAccessRequestRequest + (*ReviewAccessRequestResponse)(nil), // 19: teleport.lib.teleterm.v1.ReviewAccessRequestResponse + (*PromoteAccessRequestRequest)(nil), // 20: teleport.lib.teleterm.v1.PromoteAccessRequestRequest + (*PromoteAccessRequestResponse)(nil), // 21: teleport.lib.teleterm.v1.PromoteAccessRequestResponse + (*GetSuggestedAccessListsRequest)(nil), // 22: teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest + (*GetSuggestedAccessListsResponse)(nil), // 23: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse + (*ListKubernetesResourcesRequest)(nil), // 24: teleport.lib.teleterm.v1.ListKubernetesResourcesRequest + (*ListKubernetesResourcesResponse)(nil), // 25: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse + (*ListKubernetesServersRequest)(nil), // 26: teleport.lib.teleterm.v1.ListKubernetesServersRequest + (*ListKubernetesServersResponse)(nil), // 27: teleport.lib.teleterm.v1.ListKubernetesServersResponse + (*CredentialInfo)(nil), // 28: teleport.lib.teleterm.v1.CredentialInfo + (*LoginPasswordlessResponse)(nil), // 29: teleport.lib.teleterm.v1.LoginPasswordlessResponse + (*LoginPasswordlessRequest)(nil), // 30: teleport.lib.teleterm.v1.LoginPasswordlessRequest + (*FileTransferRequest)(nil), // 31: teleport.lib.teleterm.v1.FileTransferRequest + (*FileTransferProgress)(nil), // 32: teleport.lib.teleterm.v1.FileTransferProgress + (*LoginRequest)(nil), // 33: teleport.lib.teleterm.v1.LoginRequest + (*AddClusterRequest)(nil), // 34: teleport.lib.teleterm.v1.AddClusterRequest + (*ListClustersRequest)(nil), // 35: teleport.lib.teleterm.v1.ListClustersRequest + (*ListClustersResponse)(nil), // 36: teleport.lib.teleterm.v1.ListClustersResponse + (*ListLeafClustersRequest)(nil), // 37: teleport.lib.teleterm.v1.ListLeafClustersRequest + (*ListDatabaseUsersRequest)(nil), // 38: teleport.lib.teleterm.v1.ListDatabaseUsersRequest + (*ListDatabaseUsersResponse)(nil), // 39: teleport.lib.teleterm.v1.ListDatabaseUsersResponse + (*ListResourcesParams)(nil), // 40: teleport.lib.teleterm.v1.ListResourcesParams + (*ListDatabaseServersRequest)(nil), // 41: teleport.lib.teleterm.v1.ListDatabaseServersRequest + (*ListDatabaseServersResponse)(nil), // 42: teleport.lib.teleterm.v1.ListDatabaseServersResponse + (*CreateGatewayRequest)(nil), // 43: teleport.lib.teleterm.v1.CreateGatewayRequest + (*ListGatewaysRequest)(nil), // 44: teleport.lib.teleterm.v1.ListGatewaysRequest + (*ListGatewaysResponse)(nil), // 45: teleport.lib.teleterm.v1.ListGatewaysResponse + (*RemoveGatewayRequest)(nil), // 46: teleport.lib.teleterm.v1.RemoveGatewayRequest + (*SetGatewayTargetSubresourceNameRequest)(nil), // 47: teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest + (*SetGatewayLocalPortRequest)(nil), // 48: teleport.lib.teleterm.v1.SetGatewayLocalPortRequest + (*GetAuthSettingsRequest)(nil), // 49: teleport.lib.teleterm.v1.GetAuthSettingsRequest + (*UpdateTshdEventsServerAddressRequest)(nil), // 50: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest + (*UpdateTshdEventsServerAddressResponse)(nil), // 51: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse + (*UpdateHeadlessAuthenticationStateRequest)(nil), // 52: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest + (*UpdateHeadlessAuthenticationStateResponse)(nil), // 53: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse + (*CreateConnectMyComputerRoleRequest)(nil), // 54: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest + (*CreateConnectMyComputerRoleResponse)(nil), // 55: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse + (*CreateConnectMyComputerNodeTokenRequest)(nil), // 56: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest + (*CreateConnectMyComputerNodeTokenResponse)(nil), // 57: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse + (*WaitForConnectMyComputerNodeJoinRequest)(nil), // 58: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest + (*WaitForConnectMyComputerNodeJoinResponse)(nil), // 59: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse + (*DeleteConnectMyComputerNodeRequest)(nil), // 60: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest + (*DeleteConnectMyComputerNodeResponse)(nil), // 61: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse + (*GetConnectMyComputerNodeNameRequest)(nil), // 62: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest + (*GetConnectMyComputerNodeNameResponse)(nil), // 63: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse + (*ListUnifiedResourcesRequest)(nil), // 64: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest + (*SortBy)(nil), // 65: teleport.lib.teleterm.v1.SortBy + (*ListUnifiedResourcesResponse)(nil), // 66: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse + (*PaginatedResource)(nil), // 67: teleport.lib.teleterm.v1.PaginatedResource + (*GetUserPreferencesRequest)(nil), // 68: teleport.lib.teleterm.v1.GetUserPreferencesRequest + (*GetUserPreferencesResponse)(nil), // 69: teleport.lib.teleterm.v1.GetUserPreferencesResponse + (*UpdateUserPreferencesRequest)(nil), // 70: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest + (*UpdateUserPreferencesResponse)(nil), // 71: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse + (*UserPreferences)(nil), // 72: teleport.lib.teleterm.v1.UserPreferences + (*AuthenticateWebDeviceRequest)(nil), // 73: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest + (*AuthenticateWebDeviceResponse)(nil), // 74: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse + (*GetAppRequest)(nil), // 75: teleport.lib.teleterm.v1.GetAppRequest + (*GetAppResponse)(nil), // 76: teleport.lib.teleterm.v1.GetAppResponse + (*TargetDesktop)(nil), // 77: teleport.lib.teleterm.v1.TargetDesktop + (*ConnectToDesktopRequest)(nil), // 78: teleport.lib.teleterm.v1.ConnectToDesktopRequest + (*ConnectToDesktopResponse)(nil), // 79: teleport.lib.teleterm.v1.ConnectToDesktopResponse + (*SetSharedDirectoryForDesktopSessionRequest)(nil), // 80: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest + (*SetSharedDirectoryForDesktopSessionResponse)(nil), // 81: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse + (*LoginPasswordlessRequest_LoginPasswordlessRequestInit)(nil), // 82: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit + (*LoginPasswordlessRequest_LoginPasswordlessPINResponse)(nil), // 83: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse + (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse)(nil), // 84: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse + (*LoginRequest_LocalParams)(nil), // 85: teleport.lib.teleterm.v1.LoginRequest.LocalParams + (*LoginRequest_SsoParams)(nil), // 86: teleport.lib.teleterm.v1.LoginRequest.SsoParams + (*AccessRequest)(nil), // 87: teleport.lib.teleterm.v1.AccessRequest + (*ResourceID)(nil), // 88: teleport.lib.teleterm.v1.ResourceID + (*timestamppb.Timestamp)(nil), // 89: google.protobuf.Timestamp + (*v1.AccessList)(nil), // 90: teleport.accesslist.v1.AccessList + (*KubeResource)(nil), // 91: teleport.lib.teleterm.v1.KubeResource + (*KubeServer)(nil), // 92: teleport.lib.teleterm.v1.KubeServer + (*Cluster)(nil), // 93: teleport.lib.teleterm.v1.Cluster + (*DatabaseServer)(nil), // 94: teleport.lib.teleterm.v1.DatabaseServer + (*Gateway)(nil), // 95: teleport.lib.teleterm.v1.Gateway + (*Server)(nil), // 96: teleport.lib.teleterm.v1.Server + (*Database)(nil), // 97: teleport.lib.teleterm.v1.Database + (*Kube)(nil), // 98: teleport.lib.teleterm.v1.Kube + (*App)(nil), // 99: teleport.lib.teleterm.v1.App + (*WindowsDesktop)(nil), // 100: teleport.lib.teleterm.v1.WindowsDesktop + (*v11.ClusterUserPreferences)(nil), // 101: teleport.userpreferences.v1.ClusterUserPreferences + (*v11.UnifiedResourcePreferences)(nil), // 102: teleport.userpreferences.v1.UnifiedResourcePreferences + (*v12.DeviceWebToken)(nil), // 103: teleport.devicetrust.v1.DeviceWebToken + (*v12.DeviceConfirmationToken)(nil), // 104: teleport.devicetrust.v1.DeviceConfirmationToken + (*ReportUsageEventRequest)(nil), // 105: teleport.lib.teleterm.v1.ReportUsageEventRequest + (*AuthSettings)(nil), // 106: teleport.lib.teleterm.v1.AuthSettings } var file_teleport_lib_teleterm_v1_service_proto_depIdxs = []int32{ - 88, // 0: teleport.lib.teleterm.v1.GetAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 88, // 1: teleport.lib.teleterm.v1.GetAccessRequestsResponse.requests:type_name -> teleport.lib.teleterm.v1.AccessRequest - 89, // 2: teleport.lib.teleterm.v1.CreateAccessRequestRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID - 90, // 3: teleport.lib.teleterm.v1.CreateAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp - 90, // 4: teleport.lib.teleterm.v1.CreateAccessRequestRequest.max_duration:type_name -> google.protobuf.Timestamp - 90, // 5: teleport.lib.teleterm.v1.CreateAccessRequestRequest.request_ttl:type_name -> google.protobuf.Timestamp - 88, // 6: teleport.lib.teleterm.v1.CreateAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 89, // 7: teleport.lib.teleterm.v1.GetRequestableRolesRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID - 90, // 8: teleport.lib.teleterm.v1.ReviewAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp - 88, // 9: teleport.lib.teleterm.v1.ReviewAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 88, // 10: teleport.lib.teleterm.v1.PromoteAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 91, // 11: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse.access_lists:type_name -> teleport.accesslist.v1.AccessList - 92, // 12: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeResource - 41, // 13: teleport.lib.teleterm.v1.ListKubernetesServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams - 93, // 14: teleport.lib.teleterm.v1.ListKubernetesServersResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeServer + 87, // 0: teleport.lib.teleterm.v1.GetAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 87, // 1: teleport.lib.teleterm.v1.GetAccessRequestsResponse.requests:type_name -> teleport.lib.teleterm.v1.AccessRequest + 88, // 2: teleport.lib.teleterm.v1.CreateAccessRequestRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID + 89, // 3: teleport.lib.teleterm.v1.CreateAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp + 89, // 4: teleport.lib.teleterm.v1.CreateAccessRequestRequest.max_duration:type_name -> google.protobuf.Timestamp + 89, // 5: teleport.lib.teleterm.v1.CreateAccessRequestRequest.request_ttl:type_name -> google.protobuf.Timestamp + 87, // 6: teleport.lib.teleterm.v1.CreateAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 88, // 7: teleport.lib.teleterm.v1.GetRequestableRolesRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID + 89, // 8: teleport.lib.teleterm.v1.ReviewAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp + 87, // 9: teleport.lib.teleterm.v1.ReviewAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 87, // 10: teleport.lib.teleterm.v1.PromoteAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 90, // 11: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse.access_lists:type_name -> teleport.accesslist.v1.AccessList + 91, // 12: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeResource + 40, // 13: teleport.lib.teleterm.v1.ListKubernetesServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams + 92, // 14: teleport.lib.teleterm.v1.ListKubernetesServersResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeServer 0, // 15: teleport.lib.teleterm.v1.LoginPasswordlessResponse.prompt:type_name -> teleport.lib.teleterm.v1.PasswordlessPrompt - 29, // 16: teleport.lib.teleterm.v1.LoginPasswordlessResponse.credentials:type_name -> teleport.lib.teleterm.v1.CredentialInfo - 83, // 17: teleport.lib.teleterm.v1.LoginPasswordlessRequest.init:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit - 84, // 18: teleport.lib.teleterm.v1.LoginPasswordlessRequest.pin:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse - 85, // 19: teleport.lib.teleterm.v1.LoginPasswordlessRequest.credential:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse + 28, // 16: teleport.lib.teleterm.v1.LoginPasswordlessResponse.credentials:type_name -> teleport.lib.teleterm.v1.CredentialInfo + 82, // 17: teleport.lib.teleterm.v1.LoginPasswordlessRequest.init:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit + 83, // 18: teleport.lib.teleterm.v1.LoginPasswordlessRequest.pin:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse + 84, // 19: teleport.lib.teleterm.v1.LoginPasswordlessRequest.credential:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse 1, // 20: teleport.lib.teleterm.v1.FileTransferRequest.direction:type_name -> teleport.lib.teleterm.v1.FileTransferDirection - 86, // 21: teleport.lib.teleterm.v1.LoginRequest.local:type_name -> teleport.lib.teleterm.v1.LoginRequest.LocalParams - 87, // 22: teleport.lib.teleterm.v1.LoginRequest.sso:type_name -> teleport.lib.teleterm.v1.LoginRequest.SsoParams - 94, // 23: teleport.lib.teleterm.v1.ListClustersResponse.clusters:type_name -> teleport.lib.teleterm.v1.Cluster - 41, // 24: teleport.lib.teleterm.v1.ListDatabaseServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams - 95, // 25: teleport.lib.teleterm.v1.ListDatabaseServersResponse.resources:type_name -> teleport.lib.teleterm.v1.DatabaseServer - 96, // 26: teleport.lib.teleterm.v1.ListGatewaysResponse.gateways:type_name -> teleport.lib.teleterm.v1.Gateway + 85, // 21: teleport.lib.teleterm.v1.LoginRequest.local:type_name -> teleport.lib.teleterm.v1.LoginRequest.LocalParams + 86, // 22: teleport.lib.teleterm.v1.LoginRequest.sso:type_name -> teleport.lib.teleterm.v1.LoginRequest.SsoParams + 93, // 23: teleport.lib.teleterm.v1.ListClustersResponse.clusters:type_name -> teleport.lib.teleterm.v1.Cluster + 40, // 24: teleport.lib.teleterm.v1.ListDatabaseServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams + 94, // 25: teleport.lib.teleterm.v1.ListDatabaseServersResponse.resources:type_name -> teleport.lib.teleterm.v1.DatabaseServer + 95, // 26: teleport.lib.teleterm.v1.ListGatewaysResponse.gateways:type_name -> teleport.lib.teleterm.v1.Gateway 2, // 27: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest.state:type_name -> teleport.lib.teleterm.v1.HeadlessAuthenticationState - 97, // 28: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse.server:type_name -> teleport.lib.teleterm.v1.Server - 66, // 29: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest.sort_by:type_name -> teleport.lib.teleterm.v1.SortBy - 68, // 30: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.PaginatedResource - 98, // 31: teleport.lib.teleterm.v1.PaginatedResource.database:type_name -> teleport.lib.teleterm.v1.Database - 97, // 32: teleport.lib.teleterm.v1.PaginatedResource.server:type_name -> teleport.lib.teleterm.v1.Server - 99, // 33: teleport.lib.teleterm.v1.PaginatedResource.kube:type_name -> teleport.lib.teleterm.v1.Kube - 100, // 34: teleport.lib.teleterm.v1.PaginatedResource.app:type_name -> teleport.lib.teleterm.v1.App - 101, // 35: teleport.lib.teleterm.v1.PaginatedResource.windows_desktop:type_name -> teleport.lib.teleterm.v1.WindowsDesktop - 73, // 36: teleport.lib.teleterm.v1.GetUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences - 73, // 37: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences - 73, // 38: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences - 102, // 39: teleport.lib.teleterm.v1.UserPreferences.cluster_preferences:type_name -> teleport.userpreferences.v1.ClusterUserPreferences - 103, // 40: teleport.lib.teleterm.v1.UserPreferences.unified_resource_preferences:type_name -> teleport.userpreferences.v1.UnifiedResourcePreferences - 104, // 41: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest.device_web_token:type_name -> teleport.devicetrust.v1.DeviceWebToken - 105, // 42: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse.confirmation_token:type_name -> teleport.devicetrust.v1.DeviceConfirmationToken - 100, // 43: teleport.lib.teleterm.v1.GetAppResponse.app:type_name -> teleport.lib.teleterm.v1.App - 78, // 44: teleport.lib.teleterm.v1.ConnectToDesktopRequest.target_desktop:type_name -> teleport.lib.teleterm.v1.TargetDesktop - 51, // 45: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:input_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest - 36, // 46: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:input_type -> teleport.lib.teleterm.v1.ListClustersRequest - 38, // 47: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:input_type -> teleport.lib.teleterm.v1.ListLeafClustersRequest - 7, // 48: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:input_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherRequest - 39, // 49: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:input_type -> teleport.lib.teleterm.v1.ListDatabaseUsersRequest - 42, // 50: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:input_type -> teleport.lib.teleterm.v1.ListDatabaseServersRequest - 10, // 51: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:input_type -> teleport.lib.teleterm.v1.GetAccessRequestsRequest - 9, // 52: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:input_type -> teleport.lib.teleterm.v1.GetAccessRequestRequest - 13, // 53: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:input_type -> teleport.lib.teleterm.v1.DeleteAccessRequestRequest - 14, // 54: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:input_type -> teleport.lib.teleterm.v1.CreateAccessRequestRequest - 19, // 55: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:input_type -> teleport.lib.teleterm.v1.ReviewAccessRequestRequest - 17, // 56: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:input_type -> teleport.lib.teleterm.v1.GetRequestableRolesRequest - 16, // 57: teleport.lib.teleterm.v1.TerminalService.AssumeRole:input_type -> teleport.lib.teleterm.v1.AssumeRoleRequest - 21, // 58: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:input_type -> teleport.lib.teleterm.v1.PromoteAccessRequestRequest - 23, // 59: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:input_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest - 25, // 60: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:input_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesRequest - 27, // 61: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:input_type -> teleport.lib.teleterm.v1.ListKubernetesServersRequest - 35, // 62: teleport.lib.teleterm.v1.TerminalService.AddCluster:input_type -> teleport.lib.teleterm.v1.AddClusterRequest - 4, // 63: teleport.lib.teleterm.v1.TerminalService.RemoveCluster:input_type -> teleport.lib.teleterm.v1.RemoveClusterRequest - 45, // 64: teleport.lib.teleterm.v1.TerminalService.ListGateways:input_type -> teleport.lib.teleterm.v1.ListGatewaysRequest - 44, // 65: teleport.lib.teleterm.v1.TerminalService.CreateGateway:input_type -> teleport.lib.teleterm.v1.CreateGatewayRequest - 47, // 66: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:input_type -> teleport.lib.teleterm.v1.RemoveGatewayRequest - 48, // 67: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:input_type -> teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest - 49, // 68: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:input_type -> teleport.lib.teleterm.v1.SetGatewayLocalPortRequest - 50, // 69: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:input_type -> teleport.lib.teleterm.v1.GetAuthSettingsRequest - 5, // 70: teleport.lib.teleterm.v1.TerminalService.GetCluster:input_type -> teleport.lib.teleterm.v1.GetClusterRequest - 34, // 71: teleport.lib.teleterm.v1.TerminalService.Login:input_type -> teleport.lib.teleterm.v1.LoginRequest - 31, // 72: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:input_type -> teleport.lib.teleterm.v1.LoginPasswordlessRequest - 6, // 73: teleport.lib.teleterm.v1.TerminalService.Logout:input_type -> teleport.lib.teleterm.v1.LogoutRequest - 32, // 74: teleport.lib.teleterm.v1.TerminalService.TransferFile:input_type -> teleport.lib.teleterm.v1.FileTransferRequest - 106, // 75: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:input_type -> teleport.lib.teleterm.v1.ReportUsageEventRequest - 53, // 76: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:input_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest - 55, // 77: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest - 57, // 78: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest - 59, // 79: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:input_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest - 61, // 80: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:input_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest - 63, // 81: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:input_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest - 65, // 82: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:input_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesRequest - 69, // 83: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:input_type -> teleport.lib.teleterm.v1.GetUserPreferencesRequest - 71, // 84: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:input_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesRequest - 74, // 85: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:input_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest - 76, // 86: teleport.lib.teleterm.v1.TerminalService.GetApp:input_type -> teleport.lib.teleterm.v1.GetAppRequest - 79, // 87: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:input_type -> teleport.lib.teleterm.v1.ConnectToDesktopRequest - 81, // 88: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:input_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest - 52, // 89: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:output_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse - 37, // 90: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse - 37, // 91: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse - 8, // 92: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:output_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherResponse - 40, // 93: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:output_type -> teleport.lib.teleterm.v1.ListDatabaseUsersResponse - 43, // 94: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:output_type -> teleport.lib.teleterm.v1.ListDatabaseServersResponse - 12, // 95: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:output_type -> teleport.lib.teleterm.v1.GetAccessRequestsResponse - 11, // 96: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:output_type -> teleport.lib.teleterm.v1.GetAccessRequestResponse - 3, // 97: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 15, // 98: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:output_type -> teleport.lib.teleterm.v1.CreateAccessRequestResponse - 20, // 99: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:output_type -> teleport.lib.teleterm.v1.ReviewAccessRequestResponse - 18, // 100: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:output_type -> teleport.lib.teleterm.v1.GetRequestableRolesResponse - 3, // 101: teleport.lib.teleterm.v1.TerminalService.AssumeRole:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 22, // 102: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:output_type -> teleport.lib.teleterm.v1.PromoteAccessRequestResponse - 24, // 103: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:output_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse - 26, // 104: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:output_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesResponse - 28, // 105: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:output_type -> teleport.lib.teleterm.v1.ListKubernetesServersResponse - 94, // 106: teleport.lib.teleterm.v1.TerminalService.AddCluster:output_type -> teleport.lib.teleterm.v1.Cluster - 3, // 107: teleport.lib.teleterm.v1.TerminalService.RemoveCluster:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 46, // 108: teleport.lib.teleterm.v1.TerminalService.ListGateways:output_type -> teleport.lib.teleterm.v1.ListGatewaysResponse - 96, // 109: teleport.lib.teleterm.v1.TerminalService.CreateGateway:output_type -> teleport.lib.teleterm.v1.Gateway - 3, // 110: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 96, // 111: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:output_type -> teleport.lib.teleterm.v1.Gateway - 96, // 112: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:output_type -> teleport.lib.teleterm.v1.Gateway - 107, // 113: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:output_type -> teleport.lib.teleterm.v1.AuthSettings - 94, // 114: teleport.lib.teleterm.v1.TerminalService.GetCluster:output_type -> teleport.lib.teleterm.v1.Cluster - 3, // 115: teleport.lib.teleterm.v1.TerminalService.Login:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 30, // 116: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:output_type -> teleport.lib.teleterm.v1.LoginPasswordlessResponse - 3, // 117: teleport.lib.teleterm.v1.TerminalService.Logout:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 33, // 118: teleport.lib.teleterm.v1.TerminalService.TransferFile:output_type -> teleport.lib.teleterm.v1.FileTransferProgress - 3, // 119: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 54, // 120: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:output_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse - 56, // 121: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse - 58, // 122: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse - 60, // 123: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:output_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse - 62, // 124: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:output_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse - 64, // 125: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:output_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse - 67, // 126: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:output_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesResponse - 70, // 127: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:output_type -> teleport.lib.teleterm.v1.GetUserPreferencesResponse - 72, // 128: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:output_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesResponse - 75, // 129: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:output_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse - 77, // 130: teleport.lib.teleterm.v1.TerminalService.GetApp:output_type -> teleport.lib.teleterm.v1.GetAppResponse - 80, // 131: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:output_type -> teleport.lib.teleterm.v1.ConnectToDesktopResponse - 82, // 132: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:output_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse - 89, // [89:133] is the sub-list for method output_type - 45, // [45:89] is the sub-list for method input_type + 96, // 28: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse.server:type_name -> teleport.lib.teleterm.v1.Server + 65, // 29: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest.sort_by:type_name -> teleport.lib.teleterm.v1.SortBy + 67, // 30: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.PaginatedResource + 97, // 31: teleport.lib.teleterm.v1.PaginatedResource.database:type_name -> teleport.lib.teleterm.v1.Database + 96, // 32: teleport.lib.teleterm.v1.PaginatedResource.server:type_name -> teleport.lib.teleterm.v1.Server + 98, // 33: teleport.lib.teleterm.v1.PaginatedResource.kube:type_name -> teleport.lib.teleterm.v1.Kube + 99, // 34: teleport.lib.teleterm.v1.PaginatedResource.app:type_name -> teleport.lib.teleterm.v1.App + 100, // 35: teleport.lib.teleterm.v1.PaginatedResource.windows_desktop:type_name -> teleport.lib.teleterm.v1.WindowsDesktop + 72, // 36: teleport.lib.teleterm.v1.GetUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences + 72, // 37: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences + 72, // 38: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences + 101, // 39: teleport.lib.teleterm.v1.UserPreferences.cluster_preferences:type_name -> teleport.userpreferences.v1.ClusterUserPreferences + 102, // 40: teleport.lib.teleterm.v1.UserPreferences.unified_resource_preferences:type_name -> teleport.userpreferences.v1.UnifiedResourcePreferences + 103, // 41: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest.device_web_token:type_name -> teleport.devicetrust.v1.DeviceWebToken + 104, // 42: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse.confirmation_token:type_name -> teleport.devicetrust.v1.DeviceConfirmationToken + 99, // 43: teleport.lib.teleterm.v1.GetAppResponse.app:type_name -> teleport.lib.teleterm.v1.App + 77, // 44: teleport.lib.teleterm.v1.ConnectToDesktopRequest.target_desktop:type_name -> teleport.lib.teleterm.v1.TargetDesktop + 50, // 45: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:input_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest + 35, // 46: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:input_type -> teleport.lib.teleterm.v1.ListClustersRequest + 37, // 47: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:input_type -> teleport.lib.teleterm.v1.ListLeafClustersRequest + 6, // 48: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:input_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherRequest + 38, // 49: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:input_type -> teleport.lib.teleterm.v1.ListDatabaseUsersRequest + 41, // 50: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:input_type -> teleport.lib.teleterm.v1.ListDatabaseServersRequest + 9, // 51: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:input_type -> teleport.lib.teleterm.v1.GetAccessRequestsRequest + 8, // 52: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:input_type -> teleport.lib.teleterm.v1.GetAccessRequestRequest + 12, // 53: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:input_type -> teleport.lib.teleterm.v1.DeleteAccessRequestRequest + 13, // 54: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:input_type -> teleport.lib.teleterm.v1.CreateAccessRequestRequest + 18, // 55: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:input_type -> teleport.lib.teleterm.v1.ReviewAccessRequestRequest + 16, // 56: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:input_type -> teleport.lib.teleterm.v1.GetRequestableRolesRequest + 15, // 57: teleport.lib.teleterm.v1.TerminalService.AssumeRole:input_type -> teleport.lib.teleterm.v1.AssumeRoleRequest + 20, // 58: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:input_type -> teleport.lib.teleterm.v1.PromoteAccessRequestRequest + 22, // 59: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:input_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest + 24, // 60: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:input_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesRequest + 26, // 61: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:input_type -> teleport.lib.teleterm.v1.ListKubernetesServersRequest + 34, // 62: teleport.lib.teleterm.v1.TerminalService.AddCluster:input_type -> teleport.lib.teleterm.v1.AddClusterRequest + 44, // 63: teleport.lib.teleterm.v1.TerminalService.ListGateways:input_type -> teleport.lib.teleterm.v1.ListGatewaysRequest + 43, // 64: teleport.lib.teleterm.v1.TerminalService.CreateGateway:input_type -> teleport.lib.teleterm.v1.CreateGatewayRequest + 46, // 65: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:input_type -> teleport.lib.teleterm.v1.RemoveGatewayRequest + 47, // 66: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:input_type -> teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest + 48, // 67: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:input_type -> teleport.lib.teleterm.v1.SetGatewayLocalPortRequest + 49, // 68: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:input_type -> teleport.lib.teleterm.v1.GetAuthSettingsRequest + 4, // 69: teleport.lib.teleterm.v1.TerminalService.GetCluster:input_type -> teleport.lib.teleterm.v1.GetClusterRequest + 33, // 70: teleport.lib.teleterm.v1.TerminalService.Login:input_type -> teleport.lib.teleterm.v1.LoginRequest + 30, // 71: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:input_type -> teleport.lib.teleterm.v1.LoginPasswordlessRequest + 5, // 72: teleport.lib.teleterm.v1.TerminalService.Logout:input_type -> teleport.lib.teleterm.v1.LogoutRequest + 31, // 73: teleport.lib.teleterm.v1.TerminalService.TransferFile:input_type -> teleport.lib.teleterm.v1.FileTransferRequest + 105, // 74: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:input_type -> teleport.lib.teleterm.v1.ReportUsageEventRequest + 52, // 75: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:input_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest + 54, // 76: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest + 56, // 77: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest + 58, // 78: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:input_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest + 60, // 79: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:input_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest + 62, // 80: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:input_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest + 64, // 81: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:input_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesRequest + 68, // 82: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:input_type -> teleport.lib.teleterm.v1.GetUserPreferencesRequest + 70, // 83: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:input_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesRequest + 73, // 84: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:input_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest + 75, // 85: teleport.lib.teleterm.v1.TerminalService.GetApp:input_type -> teleport.lib.teleterm.v1.GetAppRequest + 78, // 86: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:input_type -> teleport.lib.teleterm.v1.ConnectToDesktopRequest + 80, // 87: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:input_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest + 51, // 88: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:output_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse + 36, // 89: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse + 36, // 90: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse + 7, // 91: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:output_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherResponse + 39, // 92: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:output_type -> teleport.lib.teleterm.v1.ListDatabaseUsersResponse + 42, // 93: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:output_type -> teleport.lib.teleterm.v1.ListDatabaseServersResponse + 11, // 94: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:output_type -> teleport.lib.teleterm.v1.GetAccessRequestsResponse + 10, // 95: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:output_type -> teleport.lib.teleterm.v1.GetAccessRequestResponse + 3, // 96: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 14, // 97: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:output_type -> teleport.lib.teleterm.v1.CreateAccessRequestResponse + 19, // 98: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:output_type -> teleport.lib.teleterm.v1.ReviewAccessRequestResponse + 17, // 99: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:output_type -> teleport.lib.teleterm.v1.GetRequestableRolesResponse + 3, // 100: teleport.lib.teleterm.v1.TerminalService.AssumeRole:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 21, // 101: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:output_type -> teleport.lib.teleterm.v1.PromoteAccessRequestResponse + 23, // 102: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:output_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse + 25, // 103: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:output_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesResponse + 27, // 104: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:output_type -> teleport.lib.teleterm.v1.ListKubernetesServersResponse + 93, // 105: teleport.lib.teleterm.v1.TerminalService.AddCluster:output_type -> teleport.lib.teleterm.v1.Cluster + 45, // 106: teleport.lib.teleterm.v1.TerminalService.ListGateways:output_type -> teleport.lib.teleterm.v1.ListGatewaysResponse + 95, // 107: teleport.lib.teleterm.v1.TerminalService.CreateGateway:output_type -> teleport.lib.teleterm.v1.Gateway + 3, // 108: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 95, // 109: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:output_type -> teleport.lib.teleterm.v1.Gateway + 95, // 110: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:output_type -> teleport.lib.teleterm.v1.Gateway + 106, // 111: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:output_type -> teleport.lib.teleterm.v1.AuthSettings + 93, // 112: teleport.lib.teleterm.v1.TerminalService.GetCluster:output_type -> teleport.lib.teleterm.v1.Cluster + 3, // 113: teleport.lib.teleterm.v1.TerminalService.Login:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 29, // 114: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:output_type -> teleport.lib.teleterm.v1.LoginPasswordlessResponse + 3, // 115: teleport.lib.teleterm.v1.TerminalService.Logout:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 32, // 116: teleport.lib.teleterm.v1.TerminalService.TransferFile:output_type -> teleport.lib.teleterm.v1.FileTransferProgress + 3, // 117: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 53, // 118: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:output_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse + 55, // 119: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse + 57, // 120: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse + 59, // 121: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:output_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse + 61, // 122: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:output_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse + 63, // 123: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:output_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse + 66, // 124: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:output_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesResponse + 69, // 125: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:output_type -> teleport.lib.teleterm.v1.GetUserPreferencesResponse + 71, // 126: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:output_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesResponse + 74, // 127: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:output_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse + 76, // 128: teleport.lib.teleterm.v1.TerminalService.GetApp:output_type -> teleport.lib.teleterm.v1.GetAppResponse + 79, // 129: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:output_type -> teleport.lib.teleterm.v1.ConnectToDesktopResponse + 81, // 130: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:output_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse + 88, // [88:131] is the sub-list for method output_type + 45, // [45:88] is the sub-list for method input_type 45, // [45:45] is the sub-list for extension type_name 45, // [45:45] is the sub-list for extension extendee 0, // [0:45] is the sub-list for field type_name @@ -5394,16 +5351,16 @@ func file_teleport_lib_teleterm_v1_service_proto_init() { file_teleport_lib_teleterm_v1_server_proto_init() file_teleport_lib_teleterm_v1_usage_events_proto_init() file_teleport_lib_teleterm_v1_windows_desktop_proto_init() - file_teleport_lib_teleterm_v1_service_proto_msgTypes[28].OneofWrappers = []any{ + file_teleport_lib_teleterm_v1_service_proto_msgTypes[27].OneofWrappers = []any{ (*LoginPasswordlessRequest_Init)(nil), (*LoginPasswordlessRequest_Pin)(nil), (*LoginPasswordlessRequest_Credential)(nil), } - file_teleport_lib_teleterm_v1_service_proto_msgTypes[31].OneofWrappers = []any{ + file_teleport_lib_teleterm_v1_service_proto_msgTypes[30].OneofWrappers = []any{ (*LoginRequest_Local)(nil), (*LoginRequest_Sso)(nil), } - file_teleport_lib_teleterm_v1_service_proto_msgTypes[65].OneofWrappers = []any{ + file_teleport_lib_teleterm_v1_service_proto_msgTypes[64].OneofWrappers = []any{ (*PaginatedResource_Database)(nil), (*PaginatedResource_Server)(nil), (*PaginatedResource_Kube)(nil), @@ -5416,7 +5373,7 @@ func file_teleport_lib_teleterm_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_lib_teleterm_v1_service_proto_rawDesc), len(file_teleport_lib_teleterm_v1_service_proto_rawDesc)), NumEnums: 3, - NumMessages: 85, + NumMessages: 84, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go index e4c6a07f54a73..cb81fac910a62 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go @@ -54,7 +54,6 @@ const ( TerminalService_ListKubernetesResources_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/ListKubernetesResources" TerminalService_ListKubernetesServers_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/ListKubernetesServers" TerminalService_AddCluster_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/AddCluster" - TerminalService_RemoveCluster_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/RemoveCluster" TerminalService_ListGateways_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/ListGateways" TerminalService_CreateGateway_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/CreateGateway" TerminalService_RemoveGateway_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/RemoveGateway" @@ -137,8 +136,6 @@ type TerminalServiceClient interface { ListKubernetesServers(ctx context.Context, in *ListKubernetesServersRequest, opts ...grpc.CallOption) (*ListKubernetesServersResponse, error) // AddCluster adds a cluster to profile AddCluster(ctx context.Context, in *AddClusterRequest, opts ...grpc.CallOption) (*Cluster, error) - // RemoveCluster removes a cluster from profile - RemoveCluster(ctx context.Context, in *RemoveClusterRequest, opts ...grpc.CallOption) (*EmptyResponse, error) // ListGateways lists gateways ListGateways(ctx context.Context, in *ListGatewaysRequest, opts ...grpc.CallOption) (*ListGatewaysResponse, error) // CreateGateway creates a gateway @@ -177,7 +174,9 @@ type TerminalServiceClient interface { // -> Receive the index number associated with the selected credential in list // <- End LoginPasswordless(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[LoginPasswordlessRequest, LoginPasswordlessResponse], error) - // ClusterLogin logs out a user from cluster + // Logs the user out of the cluster and cleans up associated resources. + // Optionally removes the profile. + // This operation is idempotent and can be safely invoked multiple times. Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*EmptyResponse, error) // TransferFile sends a request to download/upload a file TransferFile(ctx context.Context, in *FileTransferRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileTransferProgress], error) @@ -418,16 +417,6 @@ func (c *terminalServiceClient) AddCluster(ctx context.Context, in *AddClusterRe return out, nil } -func (c *terminalServiceClient) RemoveCluster(ctx context.Context, in *RemoveClusterRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(EmptyResponse) - err := c.cc.Invoke(ctx, TerminalService_RemoveCluster_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *terminalServiceClient) ListGateways(ctx context.Context, in *ListGatewaysRequest, opts ...grpc.CallOption) (*ListGatewaysResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListGatewaysResponse) @@ -748,8 +737,6 @@ type TerminalServiceServer interface { ListKubernetesServers(context.Context, *ListKubernetesServersRequest) (*ListKubernetesServersResponse, error) // AddCluster adds a cluster to profile AddCluster(context.Context, *AddClusterRequest) (*Cluster, error) - // RemoveCluster removes a cluster from profile - RemoveCluster(context.Context, *RemoveClusterRequest) (*EmptyResponse, error) // ListGateways lists gateways ListGateways(context.Context, *ListGatewaysRequest) (*ListGatewaysResponse, error) // CreateGateway creates a gateway @@ -788,7 +775,9 @@ type TerminalServiceServer interface { // -> Receive the index number associated with the selected credential in list // <- End LoginPasswordless(grpc.BidiStreamingServer[LoginPasswordlessRequest, LoginPasswordlessResponse]) error - // ClusterLogin logs out a user from cluster + // Logs the user out of the cluster and cleans up associated resources. + // Optionally removes the profile. + // This operation is idempotent and can be safely invoked multiple times. Logout(context.Context, *LogoutRequest) (*EmptyResponse, error) // TransferFile sends a request to download/upload a file TransferFile(*FileTransferRequest, grpc.ServerStreamingServer[FileTransferProgress]) error @@ -903,9 +892,6 @@ func (UnimplementedTerminalServiceServer) ListKubernetesServers(context.Context, func (UnimplementedTerminalServiceServer) AddCluster(context.Context, *AddClusterRequest) (*Cluster, error) { return nil, status.Errorf(codes.Unimplemented, "method AddCluster not implemented") } -func (UnimplementedTerminalServiceServer) RemoveCluster(context.Context, *RemoveClusterRequest) (*EmptyResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemoveCluster not implemented") -} func (UnimplementedTerminalServiceServer) ListGateways(context.Context, *ListGatewaysRequest) (*ListGatewaysResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListGateways not implemented") } @@ -1326,24 +1312,6 @@ func _TerminalService_AddCluster_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } -func _TerminalService_RemoveCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RemoveClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TerminalServiceServer).RemoveCluster(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TerminalService_RemoveCluster_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TerminalServiceServer).RemoveCluster(ctx, req.(*RemoveClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _TerminalService_ListGateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListGatewaysRequest) if err := dec(in); err != nil { @@ -1844,10 +1812,6 @@ var TerminalService_ServiceDesc = grpc.ServiceDesc{ MethodName: "AddCluster", Handler: _TerminalService_AddCluster_Handler, }, - { - MethodName: "RemoveCluster", - Handler: _TerminalService_RemoveCluster_Handler, - }, { MethodName: "ListGateways", Handler: _TerminalService_ListGateways_Handler, diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts index 77b8ca4981ac6..7f4220520924d 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts @@ -69,7 +69,6 @@ import type { Gateway } from "./gateway_pb"; import type { CreateGatewayRequest } from "./service_pb"; import type { ListGatewaysResponse } from "./service_pb"; import type { ListGatewaysRequest } from "./service_pb"; -import type { RemoveClusterRequest } from "./service_pb"; import type { Cluster } from "./cluster_pb"; import type { AddClusterRequest } from "./service_pb"; import type { ListKubernetesServersResponse } from "./service_pb"; @@ -234,12 +233,6 @@ export interface ITerminalServiceClient { * @generated from protobuf rpc: AddCluster(teleport.lib.teleterm.v1.AddClusterRequest) returns (teleport.lib.teleterm.v1.Cluster); */ addCluster(input: AddClusterRequest, options?: RpcOptions): UnaryCall; - /** - * RemoveCluster removes a cluster from profile - * - * @generated from protobuf rpc: RemoveCluster(teleport.lib.teleterm.v1.RemoveClusterRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); - */ - removeCluster(input: RemoveClusterRequest, options?: RpcOptions): UnaryCall; /** * ListGateways lists gateways * @@ -315,7 +308,9 @@ export interface ITerminalServiceClient { */ loginPasswordless(options?: RpcOptions): DuplexStreamingCall; /** - * ClusterLogin logs out a user from cluster + * Logs the user out of the cluster and cleans up associated resources. + * Optionally removes the profile. + * This operation is idempotent and can be safely invoked multiple times. * * @generated from protobuf rpc: Logout(teleport.lib.teleterm.v1.LogoutRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ @@ -616,22 +611,13 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf const method = this.methods[17], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } - /** - * RemoveCluster removes a cluster from profile - * - * @generated from protobuf rpc: RemoveCluster(teleport.lib.teleterm.v1.RemoveClusterRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); - */ - removeCluster(input: RemoveClusterRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[18], opt = this._transport.mergeOptions(options); - return stackIntercept("unary", this._transport, method, opt, input); - } /** * ListGateways lists gateways * * @generated from protobuf rpc: ListGateways(teleport.lib.teleterm.v1.ListGatewaysRequest) returns (teleport.lib.teleterm.v1.ListGatewaysResponse); */ listGateways(input: ListGatewaysRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[19], opt = this._transport.mergeOptions(options); + const method = this.methods[18], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -640,7 +626,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: CreateGateway(teleport.lib.teleterm.v1.CreateGatewayRequest) returns (teleport.lib.teleterm.v1.Gateway); */ createGateway(input: CreateGatewayRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[20], opt = this._transport.mergeOptions(options); + const method = this.methods[19], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -649,7 +635,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: RemoveGateway(teleport.lib.teleterm.v1.RemoveGatewayRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ removeGateway(input: RemoveGatewayRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[21], opt = this._transport.mergeOptions(options); + const method = this.methods[20], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -661,7 +647,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: SetGatewayTargetSubresourceName(teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest) returns (teleport.lib.teleterm.v1.Gateway); */ setGatewayTargetSubresourceName(input: SetGatewayTargetSubresourceNameRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[22], opt = this._transport.mergeOptions(options); + const method = this.methods[21], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -671,7 +657,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: SetGatewayLocalPort(teleport.lib.teleterm.v1.SetGatewayLocalPortRequest) returns (teleport.lib.teleterm.v1.Gateway); */ setGatewayLocalPort(input: SetGatewayLocalPortRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[23], opt = this._transport.mergeOptions(options); + const method = this.methods[22], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -680,7 +666,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetAuthSettings(teleport.lib.teleterm.v1.GetAuthSettingsRequest) returns (teleport.lib.teleterm.v1.AuthSettings); */ getAuthSettings(input: GetAuthSettingsRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[24], opt = this._transport.mergeOptions(options); + const method = this.methods[23], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -690,7 +676,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetCluster(teleport.lib.teleterm.v1.GetClusterRequest) returns (teleport.lib.teleterm.v1.Cluster); */ getCluster(input: GetClusterRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[25], opt = this._transport.mergeOptions(options); + const method = this.methods[24], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -699,7 +685,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: Login(teleport.lib.teleterm.v1.LoginRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ login(input: LoginRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[26], opt = this._transport.mergeOptions(options); + const method = this.methods[25], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -723,16 +709,18 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: LoginPasswordless(stream teleport.lib.teleterm.v1.LoginPasswordlessRequest) returns (stream teleport.lib.teleterm.v1.LoginPasswordlessResponse); */ loginPasswordless(options?: RpcOptions): DuplexStreamingCall { - const method = this.methods[27], opt = this._transport.mergeOptions(options); + const method = this.methods[26], opt = this._transport.mergeOptions(options); return stackIntercept("duplex", this._transport, method, opt); } /** - * ClusterLogin logs out a user from cluster + * Logs the user out of the cluster and cleans up associated resources. + * Optionally removes the profile. + * This operation is idempotent and can be safely invoked multiple times. * * @generated from protobuf rpc: Logout(teleport.lib.teleterm.v1.LogoutRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ logout(input: LogoutRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[28], opt = this._transport.mergeOptions(options); + const method = this.methods[27], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -741,7 +729,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: TransferFile(teleport.lib.teleterm.v1.FileTransferRequest) returns (stream teleport.lib.teleterm.v1.FileTransferProgress); */ transferFile(input: FileTransferRequest, options?: RpcOptions): ServerStreamingCall { - const method = this.methods[29], opt = this._transport.mergeOptions(options); + const method = this.methods[28], opt = this._transport.mergeOptions(options); return stackIntercept("serverStreaming", this._transport, method, opt, input); } /** @@ -750,7 +738,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: ReportUsageEvent(teleport.lib.teleterm.v1.ReportUsageEventRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ reportUsageEvent(input: ReportUsageEventRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[30], opt = this._transport.mergeOptions(options); + const method = this.methods[29], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -760,7 +748,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: UpdateHeadlessAuthenticationState(teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest) returns (teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse); */ updateHeadlessAuthenticationState(input: UpdateHeadlessAuthenticationStateRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[31], opt = this._transport.mergeOptions(options); + const method = this.methods[30], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -771,7 +759,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: CreateConnectMyComputerRole(teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest) returns (teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse); */ createConnectMyComputerRole(input: CreateConnectMyComputerRoleRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[32], opt = this._transport.mergeOptions(options); + const method = this.methods[31], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -780,7 +768,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: CreateConnectMyComputerNodeToken(teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest) returns (teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse); */ createConnectMyComputerNodeToken(input: CreateConnectMyComputerNodeTokenRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[33], opt = this._transport.mergeOptions(options); + const method = this.methods[32], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -794,7 +782,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: WaitForConnectMyComputerNodeJoin(teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest) returns (teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse); */ waitForConnectMyComputerNodeJoin(input: WaitForConnectMyComputerNodeJoinRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[34], opt = this._transport.mergeOptions(options); + const method = this.methods[33], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -803,7 +791,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: DeleteConnectMyComputerNode(teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest) returns (teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse); */ deleteConnectMyComputerNode(input: DeleteConnectMyComputerNodeRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[35], opt = this._transport.mergeOptions(options); + const method = this.methods[34], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -812,7 +800,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetConnectMyComputerNodeName(teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest) returns (teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse); */ getConnectMyComputerNodeName(input: GetConnectMyComputerNodeNameRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[36], opt = this._transport.mergeOptions(options); + const method = this.methods[35], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -821,7 +809,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: ListUnifiedResources(teleport.lib.teleterm.v1.ListUnifiedResourcesRequest) returns (teleport.lib.teleterm.v1.ListUnifiedResourcesResponse); */ listUnifiedResources(input: ListUnifiedResourcesRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[37], opt = this._transport.mergeOptions(options); + const method = this.methods[36], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -830,7 +818,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetUserPreferences(teleport.lib.teleterm.v1.GetUserPreferencesRequest) returns (teleport.lib.teleterm.v1.GetUserPreferencesResponse); */ getUserPreferences(input: GetUserPreferencesRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[38], opt = this._transport.mergeOptions(options); + const method = this.methods[37], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -840,7 +828,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: UpdateUserPreferences(teleport.lib.teleterm.v1.UpdateUserPreferencesRequest) returns (teleport.lib.teleterm.v1.UpdateUserPreferencesResponse); */ updateUserPreferences(input: UpdateUserPreferencesRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[39], opt = this._transport.mergeOptions(options); + const method = this.methods[38], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -853,7 +841,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: AuthenticateWebDevice(teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest) returns (teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse); */ authenticateWebDevice(input: AuthenticateWebDeviceRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[40], opt = this._transport.mergeOptions(options); + const method = this.methods[39], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -863,7 +851,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetApp(teleport.lib.teleterm.v1.GetAppRequest) returns (teleport.lib.teleterm.v1.GetAppResponse); */ getApp(input: GetAppRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[41], opt = this._transport.mergeOptions(options); + const method = this.methods[40], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -872,7 +860,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: ConnectToDesktop(stream teleport.lib.teleterm.v1.ConnectToDesktopRequest) returns (stream teleport.lib.teleterm.v1.ConnectToDesktopResponse); */ connectToDesktop(options?: RpcOptions): DuplexStreamingCall { - const method = this.methods[42], opt = this._transport.mergeOptions(options); + const method = this.methods[41], opt = this._transport.mergeOptions(options); return stackIntercept("duplex", this._transport, method, opt); } /** @@ -886,7 +874,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: SetSharedDirectoryForDesktopSession(teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest) returns (teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse); */ setSharedDirectoryForDesktopSession(input: SetSharedDirectoryForDesktopSessionRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[43], opt = this._transport.mergeOptions(options); + const method = this.methods[42], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } } diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts index b2591363272fd..6fd5564b85a20 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts @@ -64,7 +64,6 @@ import { Gateway } from "./gateway_pb"; import { CreateGatewayRequest } from "./service_pb"; import { ListGatewaysResponse } from "./service_pb"; import { ListGatewaysRequest } from "./service_pb"; -import { RemoveClusterRequest } from "./service_pb"; import { Cluster } from "./cluster_pb"; import { AddClusterRequest } from "./service_pb"; import { ListKubernetesServersResponse } from "./service_pb"; @@ -227,12 +226,6 @@ export interface ITerminalService extends grpc.UntypedServiceImplementation { * @generated from protobuf rpc: AddCluster(teleport.lib.teleterm.v1.AddClusterRequest) returns (teleport.lib.teleterm.v1.Cluster); */ addCluster: grpc.handleUnaryCall; - /** - * RemoveCluster removes a cluster from profile - * - * @generated from protobuf rpc: RemoveCluster(teleport.lib.teleterm.v1.RemoveClusterRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); - */ - removeCluster: grpc.handleUnaryCall; /** * ListGateways lists gateways * @@ -308,7 +301,9 @@ export interface ITerminalService extends grpc.UntypedServiceImplementation { */ loginPasswordless: grpc.handleBidiStreamingCall; /** - * ClusterLogin logs out a user from cluster + * Logs the user out of the cluster and cleans up associated resources. + * Optionally removes the profile. + * This operation is idempotent and can be safely invoked multiple times. * * @generated from protobuf rpc: Logout(teleport.lib.teleterm.v1.LogoutRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ @@ -615,16 +610,6 @@ export const terminalServiceDefinition: grpc.ServiceDefinition responseSerialize: value => Buffer.from(Cluster.toBinary(value)), requestSerialize: value => Buffer.from(AddClusterRequest.toBinary(value)) }, - removeCluster: { - path: "/teleport.lib.teleterm.v1.TerminalService/RemoveCluster", - originalName: "RemoveCluster", - requestStream: false, - responseStream: false, - responseDeserialize: bytes => EmptyResponse.fromBinary(bytes), - requestDeserialize: bytes => RemoveClusterRequest.fromBinary(bytes), - responseSerialize: value => Buffer.from(EmptyResponse.toBinary(value)), - requestSerialize: value => Buffer.from(RemoveClusterRequest.toBinary(value)) - }, listGateways: { path: "/teleport.lib.teleterm.v1.TerminalService/ListGateways", originalName: "ListGateways", diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts index ac602477a0994..b93089cf73be4 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts @@ -56,17 +56,6 @@ import { AccessRequest } from "./access_request_pb"; */ export interface EmptyResponse { } -/** - * RemoveClusterRequest describes RemoveClusterRequest - * - * @generated from protobuf message teleport.lib.teleterm.v1.RemoveClusterRequest - */ -export interface RemoveClusterRequest { - /** - * @generated from protobuf field: string cluster_uri = 1; - */ - clusterUri: string; -} /** * GetClusterRequest describes GetClusterRequest * @@ -79,8 +68,6 @@ export interface GetClusterRequest { clusterUri: string; } /** - * LogoutRequest describes LogoutRequest - * * @generated from protobuf message teleport.lib.teleterm.v1.LogoutRequest */ export interface LogoutRequest { @@ -88,6 +75,12 @@ export interface LogoutRequest { * @generated from protobuf field: string cluster_uri = 1; */ clusterUri: string; + /** + * Whether to remove the associated YAML profile after logout. + * + * @generated from protobuf field: bool remove_profile = 2; + */ + removeProfile: boolean; } /** * @generated from protobuf message teleport.lib.teleterm.v1.StartHeadlessWatcherRequest @@ -1442,53 +1435,6 @@ class EmptyResponse$Type extends MessageType { */ export const EmptyResponse = new EmptyResponse$Type(); // @generated message type with reflection information, may provide speed optimized methods -class RemoveClusterRequest$Type extends MessageType { - constructor() { - super("teleport.lib.teleterm.v1.RemoveClusterRequest", [ - { no: 1, name: "cluster_uri", kind: "scalar", T: 9 /*ScalarType.STRING*/ } - ]); - } - create(value?: PartialMessage): RemoveClusterRequest { - const message = globalThis.Object.create((this.messagePrototype!)); - message.clusterUri = ""; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: RemoveClusterRequest): RemoveClusterRequest { - let message = target ?? this.create(), end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string cluster_uri */ 1: - message.clusterUri = reader.string(); - break; - default: - let u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); - } - } - return message; - } - internalBinaryWrite(message: RemoveClusterRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* string cluster_uri = 1; */ - if (message.clusterUri !== "") - writer.tag(1, WireType.LengthDelimited).string(message.clusterUri); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); - return writer; - } -} -/** - * @generated MessageType for protobuf message teleport.lib.teleterm.v1.RemoveClusterRequest - */ -export const RemoveClusterRequest = new RemoveClusterRequest$Type(); -// @generated message type with reflection information, may provide speed optimized methods class GetClusterRequest$Type extends MessageType { constructor() { super("teleport.lib.teleterm.v1.GetClusterRequest", [ @@ -1539,12 +1485,14 @@ export const GetClusterRequest = new GetClusterRequest$Type(); class LogoutRequest$Type extends MessageType { constructor() { super("teleport.lib.teleterm.v1.LogoutRequest", [ - { no: 1, name: "cluster_uri", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + { no: 1, name: "cluster_uri", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "remove_profile", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): LogoutRequest { const message = globalThis.Object.create((this.messagePrototype!)); message.clusterUri = ""; + message.removeProfile = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1557,6 +1505,9 @@ class LogoutRequest$Type extends MessageType { case /* string cluster_uri */ 1: message.clusterUri = reader.string(); break; + case /* bool remove_profile */ 2: + message.removeProfile = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -1572,6 +1523,9 @@ class LogoutRequest$Type extends MessageType { /* string cluster_uri = 1; */ if (message.clusterUri !== "") writer.tag(1, WireType.LengthDelimited).string(message.clusterUri); + /* bool remove_profile = 2; */ + if (message.removeProfile !== false) + writer.tag(2, WireType.Varint).bool(message.removeProfile); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -5915,7 +5869,6 @@ export const TerminalService = new ServiceType("teleport.lib.teleterm.v1.Termina { name: "ListKubernetesResources", options: {}, I: ListKubernetesResourcesRequest, O: ListKubernetesResourcesResponse }, { name: "ListKubernetesServers", options: {}, I: ListKubernetesServersRequest, O: ListKubernetesServersResponse }, { name: "AddCluster", options: {}, I: AddClusterRequest, O: Cluster }, - { name: "RemoveCluster", options: {}, I: RemoveClusterRequest, O: EmptyResponse }, { name: "ListGateways", options: {}, I: ListGatewaysRequest, O: ListGatewaysResponse }, { name: "CreateGateway", options: {}, I: CreateGatewayRequest, O: Gateway }, { name: "RemoveGateway", options: {}, I: RemoveGatewayRequest, O: EmptyResponse }, diff --git a/integration/teleterm_test.go b/integration/teleterm_test.go index dcd45599ba37f..2c5c0d351c6c1 100644 --- a/integration/teleterm_test.go +++ b/integration/teleterm_test.go @@ -134,6 +134,11 @@ func TestTeleterm(t *testing.T) { testClientCache(t, pack, creds) }) + t.Run("logging out", func(t *testing.T) { + t.Parallel() + testLogout(t, pack, creds) + }) + t.Run("ListDatabaseUsers", func(t *testing.T) { // ListDatabaseUsers cannot be run in parallel as it modifies the default roles of users set up // through the test pack. @@ -549,6 +554,63 @@ func testClientCache(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers. require.NotEqual(t, secondCallForClient, thirdCallForClient) } +func testLogout(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.UserCreds) { + ctx := context.Background() + + tc := mustLogin(t, pack.Root.User.GetName(), pack, creds) + + storageFakeClock := clockwork.NewFakeClockAt(time.Now()) + + storage, err := clusters.NewStorage(clusters.Config{ + ClientStore: tc.ClientStore, + Clock: storageFakeClock, + InsecureSkipVerify: tc.InsecureSkipVerify, + }) + require.NoError(t, err) + + cluster, _, err := storage.Add(ctx, tc.WebProxyAddr) + require.NoError(t, err) + + tshdEventsClient := daemon.NewTshdEventsClient(func() (grpc.DialOption, error) { + return grpc.WithTransportCredentials(insecure.NewCredentials()), nil + }) + + daemonService, err := daemon.New(daemon.Config{ + Storage: storage, + TshdEventsClient: tshdEventsClient, + KubeconfigsDir: t.TempDir(), + AgentsDir: t.TempDir(), + }) + require.NoError(t, err) + t.Cleanup(func() { + daemonService.Stop() + }) + + // Ensure there is a cluster. + rootClusters, err := daemonService.ListRootClusters(ctx) + require.NoError(t, err) + require.Len(t, rootClusters, 1) + + // Log out without removing the profile. + err = daemonService.ClusterLogout(ctx, cluster.URI, false) + require.NoError(t, err) + rootClusters, err = daemonService.ListRootClusters(ctx) + require.NoError(t, err) + require.Len(t, rootClusters, 1) + require.Empty(t, rootClusters[0].GetLoggedInUser().Name) + + // Log out again, now also remove the profile. + err = daemonService.ClusterLogout(ctx, cluster.URI, true) + require.NoError(t, err) + rootClusters, err = daemonService.ListRootClusters(ctx) + require.NoError(t, err) + require.Empty(t, rootClusters) + + // Log out again, the operation should be idempotent. + err = daemonService.ClusterLogout(ctx, cluster.URI, true) + require.NoError(t, err) +} + func testCreateConnectMyComputerRole(t *testing.T, pack *dbhelpers.DatabasePack) { systemUser, err := user.Current() require.NoError(t, err) diff --git a/lib/teleterm/apiserver/handler/handler_auth.go b/lib/teleterm/apiserver/handler/handler_auth.go index 5193e5821dfdd..32d51d54fe9f8 100644 --- a/lib/teleterm/apiserver/handler/handler_auth.go +++ b/lib/teleterm/apiserver/handler/handler_auth.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/teleterm/api/uri" "github.com/gravitational/teleport/lib/utils" ) @@ -106,9 +107,16 @@ func (s *Handler) LoginPasswordless(stream api.TerminalService_LoginPasswordless return trace.Wrap(err) } -// Logout logs a user out from a cluster +// Logout logs the user out of the cluster and cleans up associated resources. +// Optionally removes the profile. +// This operation is idempotent and can be safely invoked multiple times. func (s *Handler) Logout(ctx context.Context, req *api.LogoutRequest) (*api.EmptyResponse, error) { - if err := s.DaemonService.ClusterLogout(ctx, req.ClusterUri); err != nil { + clusterURI, err := uri.Parse(req.GetClusterUri()) + if err != nil { + return nil, trace.Wrap(err) + } + + if err = s.DaemonService.ClusterLogout(ctx, clusterURI, req.RemoveProfile); err != nil { return nil, trace.Wrap(err) } diff --git a/lib/teleterm/apiserver/handler/handler_clusters.go b/lib/teleterm/apiserver/handler/handler_clusters.go index 68769b0194473..1acc3b9be9648 100644 --- a/lib/teleterm/apiserver/handler/handler_clusters.go +++ b/lib/teleterm/apiserver/handler/handler_clusters.go @@ -70,15 +70,6 @@ func (s *Handler) AddCluster(ctx context.Context, req *api.AddClusterRequest) (* return newAPIRootCluster(cluster), nil } -// RemoveCluster removes a cluster from local system -func (s *Handler) RemoveCluster(ctx context.Context, req *api.RemoveClusterRequest) (*api.EmptyResponse, error) { - if err := s.DaemonService.RemoveCluster(ctx, req.ClusterUri); err != nil { - return nil, trace.Wrap(err) - } - - return &api.EmptyResponse{}, nil -} - // GetCluster returns a cluster func (s *Handler) GetCluster(ctx context.Context, req *api.GetClusterRequest) (*api.Cluster, error) { cluster, _, err := s.DaemonService.ResolveClusterWithDetails(ctx, req.ClusterUri) diff --git a/lib/teleterm/daemon/daemon.go b/lib/teleterm/daemon/daemon.go index f99fdcb272107..908e3855332dd 100644 --- a/lib/teleterm/daemon/daemon.go +++ b/lib/teleterm/daemon/daemon.go @@ -267,26 +267,6 @@ func (s *Service) newDesktopSession(desktopURI uri.ResourceURI, login string) (* return session, cleanup, nil } -// RemoveCluster removes cluster -func (s *Service) RemoveCluster(ctx context.Context, uri string) error { - cluster, _, err := s.ResolveCluster(uri) - if err != nil { - return trace.Wrap(err) - } - - if cluster.Connected() { - if err := cluster.Logout(ctx); err != nil { - return trace.Wrap(err) - } - } - - if err := s.cfg.Storage.Remove(ctx, cluster.ProfileName); err != nil { - return trace.Wrap(err) - } - - return nil -} - // NewClusterClient is a wrapper on ResolveClusterURI that can be passed as an argument to // s.cfg.CreateClientCacheFunc. func (s *Service) NewClusterClient(ctx context.Context, profileName, leafClusterName string) (*client.TeleportClient, error) { @@ -349,22 +329,30 @@ func (s *Service) ResolveClusterWithDetails(ctx context.Context, uri string) (*c return withDetails, clusterClient, nil } -// ClusterLogout logs a user out from the cluster -func (s *Service) ClusterLogout(ctx context.Context, uri string) error { - cluster, _, err := s.ResolveCluster(uri) - if err != nil { +// ClusterLogout logs the user out of the cluster and cleans up associated resources. +// Optionally removes the profile. +// This operation is idempotent and can be safely invoked multiple times. +func (s *Service) ClusterLogout(ctx context.Context, uri uri.ResourceURI, removeProfile bool) error { + cluster, _, err := s.ResolveClusterURI(uri) + if err != nil && !trace.IsNotFound(err) { return trace.Wrap(err) } - - if err := cluster.Logout(ctx); err != nil { - return trace.Wrap(err) + if err == nil { + if err = cluster.Logout(ctx); err != nil { + return trace.Wrap(err) + } + if removeProfile { + if err = s.cfg.Storage.Remove(ctx, cluster.ProfileName); err != nil { + return trace.Wrap(err) + } + } } - if err := s.StopHeadlessWatcher(uri); err != nil && !trace.IsNotFound(err) { + if err = s.StopHeadlessWatcher(uri.String()); err != nil && !trace.IsNotFound(err) { return trace.Wrap(err) } - return trace.Wrap(s.ClearCachedClientsForRoot(cluster.URI)) + return trace.Wrap(s.ClearCachedClientsForRoot(uri)) } // CreateGateway creates a gateway to given targetURI diff --git a/proto/teleport/lib/teleterm/v1/service.proto b/proto/teleport/lib/teleterm/v1/service.proto index 13092ecba95ea..510ba471a9288 100644 --- a/proto/teleport/lib/teleterm/v1/service.proto +++ b/proto/teleport/lib/teleterm/v1/service.proto @@ -91,8 +91,6 @@ service TerminalService { rpc ListKubernetesServers(ListKubernetesServersRequest) returns (ListKubernetesServersResponse); // AddCluster adds a cluster to profile rpc AddCluster(AddClusterRequest) returns (Cluster); - // RemoveCluster removes a cluster from profile - rpc RemoveCluster(RemoveClusterRequest) returns (EmptyResponse); // ListGateways lists gateways rpc ListGateways(ListGatewaysRequest) returns (ListGatewaysResponse); @@ -133,7 +131,9 @@ service TerminalService { // -> Receive the index number associated with the selected credential in list // <- End rpc LoginPasswordless(stream LoginPasswordlessRequest) returns (stream LoginPasswordlessResponse); - // ClusterLogin logs out a user from cluster + // Logs the user out of the cluster and cleans up associated resources. + // Optionally removes the profile. + // This operation is idempotent and can be safely invoked multiple times. rpc Logout(LogoutRequest) returns (EmptyResponse); // TransferFile sends a request to download/upload a file rpc TransferFile(FileTransferRequest) returns (stream FileTransferProgress); @@ -193,19 +193,15 @@ service TerminalService { message EmptyResponse {} -// RemoveClusterRequest describes RemoveClusterRequest -message RemoveClusterRequest { - string cluster_uri = 1; -} - // GetClusterRequest describes GetClusterRequest message GetClusterRequest { string cluster_uri = 1; } -// LogoutRequest describes LogoutRequest message LogoutRequest { string cluster_uri = 1; + // Whether to remove the associated YAML profile after logout. + bool remove_profile = 2; } message StartHeadlessWatcherRequest { diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts index f59619102dc25..642a3dccf2ba8 100644 --- a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts +++ b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.test.ts @@ -100,18 +100,17 @@ test('logs out of cluster', async () => { mockClient.listLeafClusters = () => new MockedUnaryCall({ clusters: [leafCluster] }); const logoutMock = jest.spyOn(mockClient, 'logout'); - const removeClusterMock = jest.spyOn(mockClient, 'removeCluster'); const clusterStore = new ClusterStore( () => Promise.resolve(mockClient), mockWindowsManager ); await clusterStore.sync(cluster.uri); - await clusterStore.logout(cluster.uri); + await clusterStore.logoutAndRemove(cluster.uri); - expect(logoutMock).toHaveBeenCalledWith({ clusterUri: cluster.uri }); - expect(removeClusterMock).toHaveBeenCalledWith({ + expect(logoutMock).toHaveBeenCalledWith({ clusterUri: cluster.uri, + removeProfile: true, }); const state = clusterStore.getState(); expect(state.get(clusterWithDetails.uri)).toBeUndefined(); diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts index 5256bfdf38871..f0eda5cc9414f 100644 --- a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts +++ b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts @@ -73,12 +73,9 @@ export class ClusterStore { } /** Logs out of the cluster and removes its profile.*/ - async logout(uri: RootClusterUri): Promise { + async logoutAndRemove(uri: RootClusterUri): Promise { const client = await this.getTshdClient(); - // TODO(gzdunek): logout and removeCluster should be combined into - // a single acton in tshd. - await client.logout({ clusterUri: uri }); - await client.removeCluster({ clusterUri: uri }); + await client.logout({ clusterUri: uri, removeProfile: true }); await this.update(draft => { for (let d of draft.values()) { if (routing.belongsToProfile(uri, d.uri)) { diff --git a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts index e864bc9c191ea..6900a3da258af 100644 --- a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -107,10 +107,6 @@ export class MockMainProcessClient implements MainProcessClient { fileStorage = createMockFileStorage(); - removeKubeConfig(): Promise { - return Promise.resolve(undefined); - } - async forceFocusWindow() {} async symlinkTshMacOs() { @@ -182,7 +178,6 @@ export class MockMainProcessClient implements MainProcessClient { return true; } async changeAppUpdatesManagingCluster() {} - async maybeRemoveAppUpdatesManagingCluster() {} async checkForAppUpdates() {} async downloadAppUpdate() {} async cancelAppUpdateDownload() {} @@ -208,7 +203,7 @@ export class MockMainProcessClient implements MainProcessClient { } { return { cleanup: () => undefined }; } - async logoutCluster(): Promise {} + async logout(): Promise {} async syncCluster(): Promise {} async addCluster(): Promise { return makeRootCluster(); diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index 2f852ababba5e..cf09c69931b91 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -394,33 +394,6 @@ export default class MainProcess { return this.resolvedChildProcessAddresses; }); - // the handler can remove a single kube config file or entire directory for given cluster - ipcMain.handle( - 'main-process-remove-kube-config', - ( - _, - options: { - relativePath: string; - isDirectory?: boolean; - } - ) => { - const { kubeConfigsDir } = this.settings; - const filePath = path.join(kubeConfigsDir, options.relativePath); - const isOutOfRoot = filePath.indexOf(kubeConfigsDir) !== 0; - - if (isOutOfRoot) { - return Promise.reject('Invalid path'); - } - return fs - .rm(filePath, { recursive: !!options.isDirectory }) - .catch(error => { - if (error.code !== 'ENOENT') { - throw error; - } - }); - } - ); - ipcMain.handle('main-process-show-file-save-dialog', (_, filePath) => dialog.showSaveDialog({ defaultPath: path.basename(filePath), @@ -689,16 +662,6 @@ export default class MainProcess { ) => this.appUpdater.changeManagingCluster(args.clusterUri) ); - ipcMain.handle( - MainProcessIpc.MaybeRemoveAppUpdatesManagingCluster, - ( - event, - args: { - clusterUri: RootClusterUri; - } - ) => this.appUpdater.maybeRemoveManagingCluster(args.clusterUri) - ); - ipcMain.handle(MainProcessIpc.DownloadAppUpdate, () => this.appUpdater.download() ); @@ -723,9 +686,15 @@ export default class MainProcess { this.clusterStore.sync(args.clusterUri) ); - ipcMain.handle(MainProcessIpc.Logout, (_, args) => - this.clusterStore.logout(args.clusterUri) - ); + ipcMain.handle(MainProcessIpc.Logout, async (_, args) => { + // This function checks for updates, do not wait for it. + this.appUpdater + .maybeRemoveManagingCluster(args.clusterUri) + .catch(error => { + this.logger.error('Failed to remove managing cluster', error); + }); + await this.clusterStore.logoutAndRemove(args.clusterUri); + }); ipcMain.on(MainProcessIpc.InitClusterStoreSubscription, ev => { const port = ev.ports[0]; diff --git a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts index cefd984a0d4d2..fd3fbaa5522d7 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -112,9 +112,6 @@ export default function createMainProcessClient(): MainProcessClient { openTabContextMenu, configService: createConfigServiceClient(), fileStorage: createFileStorageClient(), - removeKubeConfig(options) { - return ipcRenderer.invoke('main-process-remove-kube-config', options); - }, forceFocusWindow(args) { return ipcRenderer.invoke(MainProcessIpc.ForceFocusWindow, args); }, @@ -221,14 +218,6 @@ export default function createMainProcessClient(): MainProcessClient { } ); }, - maybeRemoveAppUpdatesManagingCluster(clusterUri) { - return ipcRenderer.invoke( - MainProcessIpc.MaybeRemoveAppUpdatesManagingCluster, - { - clusterUri, - } - ); - }, subscribeToAppUpdateEvents: listener => { const ipcListener = (_, updateEvent: AppUpdateEvent) => { listener(updateEvent); @@ -280,7 +269,7 @@ export default function createMainProcessClient(): MainProcessClient { syncCluster: (clusterUri: RootClusterUri) => { return ipcRenderer.invoke(MainProcessIpc.SyncCluster, { clusterUri }); }, - logoutCluster: (clusterUri: RootClusterUri) => { + logout: (clusterUri: RootClusterUri) => { return ipcRenderer.invoke(MainProcessIpc.Logout, { clusterUri }); }, }; diff --git a/web/packages/teleterm/src/mainProcess/types.ts b/web/packages/teleterm/src/mainProcess/types.ts index 3a012542d267d..e2ded8b44eb76 100644 --- a/web/packages/teleterm/src/mainProcess/types.ts +++ b/web/packages/teleterm/src/mainProcess/types.ts @@ -143,10 +143,6 @@ export type MainProcessClient = { }>; configService: ConfigService; fileStorage: FileStorage; - removeKubeConfig(options: { - relativePath: string; - isDirectory?: boolean; - }): Promise; /** * Tells the OS to focus the window. If wait is true, polls periodically for window status and * resolves when it's focused or after a short timeout. @@ -213,9 +209,6 @@ export type MainProcessClient = { changeAppUpdatesManagingCluster( clusterUri: RootClusterUri | undefined ): Promise; - maybeRemoveAppUpdatesManagingCluster( - clusterUri: RootClusterUri - ): Promise; supportsAppUpdates(): boolean; checkForAppUpdates(): Promise; downloadAppUpdate(): Promise; @@ -237,7 +230,7 @@ export type MainProcessClient = { syncRootClusters(options: { abortSignal: CloneableAbortSignal; }): Promise; - logoutCluster(clusterUri: RootClusterUri): Promise; + logout(clusterUri: RootClusterUri): Promise; subscribeToClusterStore(listener: (value: ClusterStoreUpdate) => void): { cleanup: () => void; }; @@ -364,7 +357,6 @@ export enum MainProcessIpc { CancelAppUpdateDownload = 'main-process-cancel-app-update-download', QuiteAndInstallAppUpdate = 'main-process-quit-and-install-app-update', ChangeAppUpdatesManagingCluster = 'main-process-change-app-updates-managing-cluster', - MaybeRemoveAppUpdatesManagingCluster = 'main-process-maybe-remove-app-updates-managing-cluster', SupportsAppUpdates = 'main-process-supports-app-updates', InitClusterStoreSubscription = 'main-process-init-cluster-store-subscription', SyncCluster = 'main-process-sync-cluster', diff --git a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts index 76be4dbf51c57..0b9651694b649 100644 --- a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts +++ b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts @@ -71,7 +71,6 @@ export class MockTshClient implements TshdClient { localConnectorName: '', clientVersionStatus: ClientVersionStatus.OK, }); - removeCluster = () => new MockedUnaryCall({}); login = () => new MockedUnaryCall({}); loginPasswordless = undefined; logout = () => new MockedUnaryCall({}); diff --git a/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts b/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts index 8b8b95769ef52..aeffa5c0d8dac 100644 --- a/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts +++ b/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts @@ -16,23 +16,14 @@ * along with this program. If not, see . */ -import Logger from 'teleterm/logger'; import { IAppContext } from 'teleterm/ui/types'; -import { RootClusterUri, routing } from 'teleterm/ui/uri'; +import { RootClusterUri } from 'teleterm/ui/uri'; /** Disposes cluster-related resources and then logs out. */ export async function logoutWithCleanup( ctx: IAppContext, clusterUri: RootClusterUri ): Promise { - const logger = new Logger('logoutWithCleanup'); - // This function checks for updates, do not wait for it. - ctx.mainProcessClient - .maybeRemoveAppUpdatesManagingCluster(clusterUri) - .catch(err => { - logger.error('Failed to remove managing cluster', err); - }); - if (ctx.workspacesService.getRootClusterUri() === clusterUri) { const [firstConnectedWorkspace] = ctx.workspacesService .getConnectedWorkspacesClustersUri() @@ -56,14 +47,6 @@ export async function logoutWithCleanup( await ctx.clustersService.removeClusterGateways(clusterUri); - const { - params: { rootClusterId }, - } = routing.parseClusterUri(clusterUri); - await ctx.mainProcessClient.removeKubeConfig({ - relativePath: rootClusterId, - isDirectory: true, - }); - // Remove the cluster, it does not depend on anything. - await ctx.clustersService.logout(clusterUri); + await ctx.mainProcessClient.logout(clusterUri); } diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts index 9aa71b09cac1e..1ff01a388b48b 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts @@ -71,7 +71,6 @@ function getClientMocks(): Partial { login: jest.fn().mockReturnValueOnce(new MockedUnaryCall({})), logout: jest.fn().mockReturnValueOnce(new MockedUnaryCall({})), addCluster: jest.fn().mockReturnValueOnce(new MockedUnaryCall(clusterMock)), - removeCluster: jest.fn().mockReturnValueOnce(new MockedUnaryCall({})), getCluster: jest.fn().mockReturnValueOnce(new MockedUnaryCall(clusterMock)), listLeafClusters: jest .fn() diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index a124bc511f1a5..e897ce365c775 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -76,11 +76,6 @@ export class ClustersService extends ImmutableStore { return this.mainProcessClient.addCluster(proxyAddress); } - /** Logs out of the cluster. */ - async logout(clusterUri: uri.RootClusterUri) { - await this.mainProcessClient.logoutCluster(clusterUri); - } - async authenticateWebDevice( rootClusterUri: uri.RootClusterUri, { From e0ca09e189bed4ed4e602e4f4b67e67666c53dd1 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Mon, 3 Nov 2025 19:08:35 +0100 Subject: [PATCH 05/15] Connect: add profile watcher (#60622) * Add profile watcher * Move `makeClusterWithOnlyProfileProperties` to `profileWatcher.ts`, improve test * Handle watched directory removal * Improve comments * Make tests faster, pass abort signal everywhere * Improve docs * Make `removing tsh directory does not break watcher` easier to understand * Make test dir per test * Improve timing in tests * Add a limit of how many events can be emitted by `fs.watch` (to break the endless stream of events on Windows when watched dir is removed), go into the polling mode only when it's expected that the watched dir was removed * Use `expect().rejects.toThrow` correctly * Deflake 'max file system events count is restricted' * Replace `makeClusterWithOnlyProfileProperties` with `mergeClusterProfileWithDetails`, move it back to `cluster.ts` * Attempt to fix tests * Clarify comment (cherry picked from commit d4e6f1974e7034598cbbd00d573a96f5da1b6d61) --- lib/teleterm/clusters/storage.go | 4 +- .../src/mainProcess/profileWatcher/index.ts | 19 + .../profileWatcher/profileWatcher.test.ts | 369 ++++++++++++++++++ .../profileWatcher/profileWatcher.ts | 282 +++++++++++++ .../teleterm/src/services/tshd/cluster.ts | 49 +++ 5 files changed, 722 insertions(+), 1 deletion(-) create mode 100644 web/packages/teleterm/src/mainProcess/profileWatcher/index.ts create mode 100644 web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts create mode 100644 web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts diff --git a/lib/teleterm/clusters/storage.go b/lib/teleterm/clusters/storage.go index 02617ffc76585..12901915bca99 100644 --- a/lib/teleterm/clusters/storage.go +++ b/lib/teleterm/clusters/storage.go @@ -120,7 +120,9 @@ func (s *Storage) Remove(ctx context.Context, profileName string) error { // https://github.com/gravitational/teleport/issues/13278 func (s *Storage) Add(ctx context.Context, webProxyAddress string) (*Cluster, *client.TeleportClient, error) { profiles, err := s.ListProfileNames() - if err != nil { + // If the tsh directory does not exist, [client.ProfileStore.SaveProfile] will + // create it. + if err != nil && !trace.IsNotFound(err) { return nil, nil, trace.Wrap(err) } diff --git a/web/packages/teleterm/src/mainProcess/profileWatcher/index.ts b/web/packages/teleterm/src/mainProcess/profileWatcher/index.ts new file mode 100644 index 0000000000000..db5e0897a9dc7 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/profileWatcher/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './profileWatcher'; diff --git a/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts new file mode 100644 index 0000000000000..404d875fcbbc5 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.test.ts @@ -0,0 +1,369 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { wait } from 'shared/utils/wait'; + +import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; +import { RootClusterUri, routing } from 'teleterm/ui/uri'; + +import { watchProfiles } from './profileWatcher'; + +let tempDir: string; + +beforeAll(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'profile-watcher-test')); +}); + +afterAll(async () => { + if (tempDir) { + await fs.rm(tempDir, { recursive: true, force: true }); + } +}); + +// Ensure the watcher is stopped when a test fails. +let abortController: AbortController; +beforeEach(() => { + abortController = new AbortController(); +}); +afterEach(() => { + abortController.abort(); +}); + +function makePerTestDir() { + return fs.mkdtemp(path.join(tempDir, 'test')); +} + +const testDebounceMs = 10; + +async function mockTshClient(tshDir: string, initial: { clusters: Cluster[] }) { + const listRootClusters = async () => { + let paths: string[] = []; + try { + paths = await fs.readdir(tshDir); + } catch (err) { + if (err.code === 'ENOENT') { + throw { + name: 'TshdRpcError', + code: 'NOT_FOUND', + }; + } + throw err; + } + return Promise.all( + paths.map(async singlePath => { + const file = await fs.readFile( + path.join(tshDir, singlePath, 'cluster.json'), + { + encoding: 'utf-8', + } + ); + return Cluster.fromJsonString(file); + }) + ); + }; + + const insertOrUpdateCluster = async (cluster: Cluster) => { + const profileDir = path.join(tshDir, routing.parseClusterName(cluster.uri)); + + await fs.mkdir(profileDir, { recursive: true }); + await fs.writeFile( + path.join(profileDir, 'cluster.json'), + Cluster.toJsonString(cluster), + { encoding: 'utf-8' } + ); + }; + + const removeCluster = async (uri: RootClusterUri) => { + const profileDir = path.join(tshDir, routing.parseClusterName(uri)); + + await fs.rm(profileDir, { + recursive: true, + }); + }; + + // Set initial state. + await Promise.all( + initial.clusters.map(cluster => insertOrUpdateCluster(cluster)) + ); + + return { + listRootClusters, + insertOrUpdateCluster, + removeCluster, + }; +} + +function mockClusterStore(initial: { clusters: Cluster[] }) { + return { + getRootClusters: () => initial.clusters, + clearAll: () => { + initial.clusters = []; + }, + }; +} + +test('yields an "added" change when new cluster appears', async () => { + const tshDir = await makePerTestDir(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [] }); + const clusterStoreMock = mockClusterStore({ clusters: [] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + const cluster = makeRootCluster(); + await tshClientMock.insertOrUpdateCluster(cluster); + + const { value } = await watcher.next(); + expect(value).toEqual([{ op: 'added', cluster }]); + + await watcher.return(undefined); +}); + +test('yields a "removed" change when cluster disappears', async () => { + const tshDir = await makePerTestDir(); + const cluster = makeRootCluster(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [cluster] }); + const clusterStoreMock = mockClusterStore({ clusters: [cluster] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + void tshClientMock.removeCluster(cluster.uri); + + const { value } = await watcher.next(); + expect(value).toEqual([{ op: 'removed', cluster }]); + + await watcher.return(undefined); +}); + +test('yields a "changed" change when cluster properties differ', async () => { + const tshDir = await makePerTestDir(); + const oldCluster = makeRootCluster(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [oldCluster] }); + const clusterStoreMock = mockClusterStore({ clusters: [oldCluster] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + const newCluster: Cluster = { ...oldCluster, connected: false }; + void tshClientMock.insertOrUpdateCluster(newCluster); + + const { value } = await watcher.next(); + expect(value).toEqual([ + { op: 'changed', previous: oldCluster, next: newCluster }, + ]); + + await watcher.return(undefined); +}); + +test('does not yield when no cluster changes detected', async () => { + const tshDir = await makePerTestDir(); + const cluster = makeRootCluster(); + const tshClientMock = await mockTshClient(tshDir, { + clusters: [ + // Extend the cluster with properties loaded from the proxy to verify + // if they are properly ignored when detecting changes. + { ...cluster, authClusterId: 'some-id' }, + ], + }); + const clusterStoreMock = mockClusterStore({ clusters: [cluster] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + // Overwrite the cluster (profile properties are unchanged). + void tshClientMock.insertOrUpdateCluster(cluster); + + const race = Promise.race([ + watcher.next(), + // Wait a little longer than the debounce value. + wait(testDebounceMs + testDebounceMs / 2).then(() => 'timeout'), + ]); + + expect(await race).toBe('timeout'); + // Cancel the watcher with the abort signal, it's blocked on `watcher.next()`. + abortController.abort(); +}); + +test('file system events are debounced', async () => { + const tshDir = await makePerTestDir(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [] }); + const clusterStoreMock = mockClusterStore({ clusters: [] }); + const handler = jest.fn().mockImplementation(() => Promise.resolve()); + const testDebounceMs = 100; + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + void (async () => { + for await (let e of watcher) { + await handler(e); + } + })(); + + const cluster = makeRootCluster(); + + // Insert two rapid events within debounce interval. + await tshClientMock.insertOrUpdateCluster(cluster); + await tshClientMock.insertOrUpdateCluster(cluster); + // Wait slightly longer than debounce interval to ensure a single handler is called. + await wait(2 * testDebounceMs); + expect(handler).toHaveBeenCalledTimes(1); +}); + +test('no events are lost when handler is slow', async () => { + const tshDir = await makePerTestDir(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [] }); + const clusterStoreMock = mockClusterStore({ clusters: [] }); + const handler = jest.fn().mockImplementation(() => wait(2 * testDebounceMs)); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + const cluster = makeRootCluster(); + + void (async () => { + for await (let e of watcher) { + const handle = handler(e); + // Insert the second event while the first event is still processed. + void tshClientMock.insertOrUpdateCluster(cluster); + await handle; + } + })(); + + await tshClientMock.insertOrUpdateCluster(cluster); + await expect(() => { + return handler.mock.calls.length === 2; + }).toEventuallyBeTrue({ waitFor: 1000, tick: 10 }); +}); + +test('watcher stops when consumer throws', async () => { + const tshDir = await makePerTestDir(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [] }); + const clusterStoreMock = mockClusterStore({ clusters: [] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + await expect(watcher.throw(new Error('Consumer failure'))).rejects.toThrow( + 'Consumer failure' + ); + + await tshClientMock.insertOrUpdateCluster(makeRootCluster()); + + const race = await Promise.race([ + watcher.next(), + wait(300).then(() => 'timeout'), + ]); + + expect(race).toStrictEqual({ done: true, value: undefined }); +}); + +test('removing tsh directory does not break watcher', async () => { + const tshDir = await makePerTestDir(); + const cluster = makeRootCluster(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [] }); + const clusterStoreMock = mockClusterStore({ clusters: [cluster] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: testDebounceMs, + }); + + // Start the watcher by pulling the first value. + const firstEvent = watcher.next(); + await fs.rm(tshDir, { recursive: true }); + expect((await firstEvent).value).toEqual([{ op: 'removed', cluster }]); + // Clean up the store, so that we can detect a change. + clusterStoreMock.clearAll(); + + await fs.mkdir(tshDir); + await tshClientMock.insertOrUpdateCluster(cluster); + // The second event needs to wait for the dir to be detected. + jest.useFakeTimers(); + const secondEvent = watcher.next(); + // Polling uses 1 second interval. + jest.advanceTimersByTime(1000); + jest.useRealTimers(); + expect((await secondEvent).value).toEqual([{ op: 'added', cluster }]); +}); + +test('max file system events count is restricted', async () => { + const tshDir = await makePerTestDir(); + const cluster = makeRootCluster(); + const tshClientMock = await mockTshClient(tshDir, { clusters: [] }); + const clusterStoreMock = mockClusterStore({ clusters: [] }); + const watcher = watchProfiles({ + tshDirectory: tshDir, + tshClient: tshClientMock, + clusterStore: clusterStoreMock, + signal: abortController.signal, + debounceMs: 100, + maxFileSystemEvents: 1, + }); + + const promises = await Promise.allSettled([ + // Start the watcher by pulling the first value. + watcher.next(), + // Makes two updates. + tshClientMock.insertOrUpdateCluster(cluster), + ]); + + expect(promises[0]).toEqual({ + status: 'rejected', + reason: expect.objectContaining({ + message: + 'Exceeded file system event limit: more than 1 events detected within 100 ms', + }), + }); +}); diff --git a/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts new file mode 100644 index 0000000000000..8b09588521c7a --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/profileWatcher/profileWatcher.ts @@ -0,0 +1,282 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { watch } from 'node:fs'; +import { access } from 'node:fs/promises'; + +import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { debounce } from 'shared/utils/highbar'; +import { wait } from 'shared/utils/wait'; + +import { isTshdRpcError } from 'teleterm/services/tshd'; +import { mergeClusterProfileWithDetails } from 'teleterm/services/tshd/cluster'; +import { RootClusterUri } from 'teleterm/ui/uri'; + +interface TshClient { + listRootClusters(): Promise; +} + +interface ClusterStore { + getRootClusters(): Cluster[]; +} + +/** + * Watches the specified `tshDirectory` for profile changes. + * File system events are debounced with a default 200 ms delay. + * The watcher should be started only after the initial cluster store sync completes, + * to prevent unnecessary profile change events. + * + * When the watched directory is removed, the watcher emits a profile change + * and enters polling mode (with 1 second interval) until the directory reappears. + */ +export async function* watchProfiles({ + tshDirectory, + tshClient, + clusterStore, + debounceMs = 200, + maxFileSystemEvents = 4096, + signal, +}: { + tshDirectory: string; + tshClient: TshClient; + clusterStore: ClusterStore; + debounceMs?: number; + /** + * Maximum number of file system events that can accumulate while debouncing. + * + * Note: On Windows, removing a watched directory can trigger an infinite stream + * of events. Setting this limit helps mitigate that issue. + * + * Default 4096. + * */ + maxFileSystemEvents?: number; + signal?: AbortSignal; +}): AsyncGenerator { + while (!signal?.aborted) { + try { + // eslint-disable-next-line unused-imports/no-unused-vars + for await (const _ of debounceWatch( + tshDirectory, + debounceMs, + maxFileSystemEvents, + signal + )) { + const clusters = await tshClient.listRootClusters(); + const newClusters = new Map(clusters.map(c => [c.uri, c])); + const oldClusters = new Map( + clusterStore.getRootClusters().map(c => [c.uri, c]) + ); + + const changes = detectChanges(oldClusters, newClusters); + if (changes.length > 0) { + yield changes; + } + } + } catch (error) { + // Check if the error is caused by removing the watched directory. + // Removing that directory emits different events, depending on a platform: + // - On macOS/Linux, it emits a 'rename' event. + // - On Windows, it may throw an EPERM error, or emit thousands of events + // (so that we check FileSystemEventsOverflowError). + // To reliably detect removal on macOS/Linux, we expect tshClient.listRootClusters() + // to fail with a filesystem-related error, allowing us to catch all relevant cases here. + if ( + isTshdRpcError(error, 'NOT_FOUND') || + error instanceof FileSystemEventsOverflowError || + error?.code === 'EPERM' + ) { + const ok = await pathExists(tshDirectory); + if (!ok) { + yield clusterStore + .getRootClusters() + .map(cluster => ({ op: 'removed', cluster })); + await waitForPath(tshDirectory, signal); + continue; + } + } + throw error; + } + } +} + +class FileSystemEventsOverflowError extends Error { + constructor(maxCount: number, debounceMs: number) { + super( + `Exceeded file system event limit: more than ${maxCount} events detected within ${debounceMs} ms` + ); + } +} + +async function pathExists(dirPath: string): Promise { + try { + await access(dirPath); + return true; + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + throw error; + } +} + +/** Waits for path to exists, polling at intervals (1 second). */ +async function waitForPath( + dirPath: string, + signal?: AbortSignal +): Promise { + if (signal?.aborted) { + return; + } + + while (!signal?.aborted) { + // Start from waiting, pathExists() was invoked earlier to check the path. + await wait(1000, signal); + const exist = await pathExists(dirPath); + if (exist) { + return; + } + } +} + +export type ProfileChange = + | { + /** A cluster has been added. */ + op: 'added'; + cluster: Cluster; + } + | { + /** A cluster has been removed. */ + op: 'removed'; + cluster: Cluster; + } + | { + /** + * A cluster's properties have changed. + * Only the properties present locally in the profile are compared. + * (`listRootClusters` doesn't return cluster details from the proxy). + */ + op: 'changed'; + previous: Cluster; + next: Cluster; + }; + +export type ProfileChangeSet = ProfileChange[]; + +async function* debounceWatch( + path: string, + debounceMs: number, + maxFileSystemEvents: number, + abortSignal: AbortSignal | undefined +): AsyncGenerator { + let signal = Promise.withResolvers(); + let closed = false; + let eventsToDebounce = 0; + const scheduleYield = debounce(() => { + eventsToDebounce = 0; + signal.resolve(); + }, debounceMs); + const onEvent = () => { + ++eventsToDebounce; + if (eventsToDebounce > maxFileSystemEvents) { + signal.reject( + new FileSystemEventsOverflowError(maxFileSystemEvents, debounceMs) + ); + return; + } + scheduleYield(); + }; + + const watcher = watch( + path, + { signal: abortSignal, recursive: true }, + onEvent + ); + + const closeHandler = () => { + closed = true; + signal.resolve(); + }; + const errorHandler = (e: Error) => signal.reject(e); + watcher.on('close', closeHandler); + watcher.on('error', errorHandler); + + // The watcher might be restarted if the path disappears and then reappears. + // Begin by checking for any changes immediately. + onEvent(); + + try { + while (true) { + await signal.promise; + if (closed) { + break; + } + + // Recreate the signal so any events occurring while yielding will resolve the next promise. + signal = Promise.withResolvers(); + + yield; + } + } finally { + scheduleYield.cancel(); + watcher.close(); + watcher.off('close', closeHandler); + watcher.off('error', errorHandler); + } +} + +function detectChanges( + previousClusters: Map, + nextClusters: Map +): ProfileChange[] { + const changes: ProfileChange[] = []; + const allUris = new Set([...previousClusters.keys(), ...nextClusters.keys()]); + + for (const uri of allUris) { + const previous = previousClusters.get(uri); + const next = nextClusters.get(uri); + + if (!nextClusters.has(uri)) { + changes.push({ + op: 'removed', + cluster: previous, + }); + } else if (!previousClusters.has(uri)) { + changes.push({ op: 'added', cluster: next }); + } else if ( + // Ensure we are comparing only profile properties. + !Cluster.equals( + mergeClusterProfileWithDetails({ + profile: previous, + details: Cluster.create(), + }), + mergeClusterProfileWithDetails({ + profile: next, + details: Cluster.create(), + }) + ) + ) { + changes.push({ + op: 'changed', + previous: previous, + next: next, + }); + } + } + + return changes; +} diff --git a/web/packages/teleterm/src/services/tshd/cluster.ts b/web/packages/teleterm/src/services/tshd/cluster.ts index 4ca67b44565e6..52c36b930cb85 100644 --- a/web/packages/teleterm/src/services/tshd/cluster.ts +++ b/web/packages/teleterm/src/services/tshd/cluster.ts @@ -18,6 +18,12 @@ import * as whatwg from 'whatwg-url'; +import { TrustedDeviceRequirement } from 'gen-proto-ts/teleport/legacy/types/trusted_device_requirement_pb'; +import { + Cluster, + LoggedInUser_UserType, +} from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; + /** * Accepts a proxy host in the form of "cluster-address.example.com:3090" and returns the host as * understood by browsers. @@ -65,3 +71,46 @@ export function proxyHostname(proxyHost: string) { return whatwgURL.hostname; } + +/** + * Creates a cluster by merging properties that can be read from + * the profile from disk with properties from details. + * + * Listing all fields ensures TypeScript will force us to handle new fields + * by specifying whether they come from the profile or the details. + */ +export function mergeClusterProfileWithDetails({ + profile, + details, +}: { + profile: Cluster; + details: Cluster; +}): Cluster { + return { + uri: profile.uri, + connected: profile.connected, + leaf: profile.leaf, + profileStatusError: profile.profileStatusError, + proxyHost: profile.proxyHost, + ssoHost: profile.ssoHost, + name: details.name, + showResources: details.showResources, + features: details.features, + authClusterId: details.authClusterId, + proxyVersion: details.proxyVersion, + loggedInUser: profile.loggedInUser && { + name: profile.loggedInUser.name, + activeRequests: profile.loggedInUser.activeRequests, + roles: profile.loggedInUser.roles, + isDeviceTrusted: profile.loggedInUser.isDeviceTrusted, + userType: + details.loggedInUser?.userType || LoggedInUser_UserType.UNSPECIFIED, + trustedDeviceRequirement: + details.loggedInUser?.trustedDeviceRequirement || + TrustedDeviceRequirement.UNSPECIFIED, + requestableRoles: details.loggedInUser?.requestableRoles || [], + suggestedReviewers: details.loggedInUser?.suggestedReviewers || [], + acl: details.loggedInUser?.acl || undefined, + }, + }; +} From 2094c5a28e5123cdd50d64049bbf5719e42ec9d3 Mon Sep 17 00:00:00 2001 From: ravicious Date: Thu, 6 Nov 2025 10:04:37 +0100 Subject: [PATCH 06/15] Initialize tshdClients in MainProcess constructor (#61044) (cherry picked from commit c7a423367496618414976072f57e894dfbccd636) --- .../teleterm/src/mainProcess/mainProcess.ts | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index cf09c69931b91..b2022383852d0 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -125,6 +125,13 @@ export default class MainProcess { ) ); private readonly agentRunner: AgentRunner; + /** + * A promise responsible for initializing clients for tshd gRPC services once the addresses of + * child processes are resolved. Set in the constructor. + * + * If the client setup fails, the resulting error will propagate to callsites which use + * tshdClients. + */ private tshdClients: Promise<{ terminalService: TshdClient; autoUpdateService: AutoUpdateClient; @@ -168,16 +175,16 @@ export default class MainProcess { this.setAppMenu(); this.initTshd(); this.initSharedProcess(); - this.initResolvingChildProcessAddresses(); + this.initResolvingChildProcessAddressesAndTshdClients(); this.initIpc(); const getClusterVersions = async () => { - const { autoUpdateService } = await this.getTshdClients(); + const { autoUpdateService } = await this.tshdClients; const { response } = await autoUpdateService.getClusterVersions({}); return response; }; const getDownloadBaseUrl = async () => { - const { autoUpdateService } = await this.getTshdClients(); + const { autoUpdateService } = await this.tshdClients; const { response: { baseUrl }, } = await autoUpdateService.getDownloadBaseUrl({}); @@ -198,7 +205,7 @@ export default class MainProcess { process.env[TELEPORT_TOOLS_VERSION_ENV_VAR] ); this.clusterStore = new ClusterStore( - () => this.getTshdClients().then(c => c.terminalService), + () => this.tshdClients.then(c => c.terminalService), this.windowsManager ); } @@ -220,29 +227,6 @@ export default class MainProcess { ]); } - /** - * Returns the tshd client. - * - * If the client setup fails, the resulting error will propagate - * to callers of this method. - */ - private async getTshdClients(): Promise<{ - terminalService: TshdClient; - autoUpdateService: AutoUpdateClient; - }> { - if (!this.tshdClients) { - this.tshdClients = this.resolvedChildProcessAddresses.then( - ({ tsh: tshdAddress }) => - setUpTshdClients({ - runtimeSettings: this.settings, - tshdAddress, - }) - ); - } - - return this.tshdClients; - } - private initTshd() { const { binaryPath, homeDir } = this.settings.tshd; this.logger.info(`Starting tsh daemon from ${binaryPath}`); @@ -343,16 +327,17 @@ export default class MainProcess { } /** - * Initializes the resolution of child process addresses. + * Initializes the resolution of child process addresses and sets up tshd clients. + * On Windows, the setup of tshd clients also initialized the main process cert for mTLS. * * Both the internal tshd client (in the main process) and the one in the renderer * depend on this initialization promise. * * If the promise rejects, the error will propagate to the renderer via the IPC * handler (causing the renderer to stop initialization and show the error) - * and also surface when attempting to access `getTshdClients()`. + * and also surface when attempting to access the tshdClients property. */ - private initResolvingChildProcessAddresses(): void { + private initResolvingChildProcessAddressesAndTshdClients(): void { this.resolvedChildProcessAddresses = Promise.all([ resolveNetworkAddress( this.settings.tshd.requestedNetworkAddress, @@ -379,6 +364,19 @@ export default class MainProcess { ) ), ]).then(([tsh, shared]) => ({ tsh, shared })); + + this.tshdClients = this.resolvedChildProcessAddresses.then( + ({ tsh: tshdAddress }) => + setUpTshdClients({ + runtimeSettings: this.settings, + tshdAddress, + }) + ); + // Log the error just to avoid unhandled promise rejection if setUpTshdClients fails before + // anything reads tshdClients. + this.tshdClients.catch(error => { + this.logger.error('Could not initialize tshd clients', error); + }); } private initIpc() { @@ -633,7 +631,7 @@ export default class MainProcess { } const [dirPath] = value.filePaths; - const { terminalService } = await this.getTshdClients(); + const { terminalService } = await this.tshdClients; await terminalService.setSharedDirectoryForDesktopSession({ desktopUri: args.desktopUri, login: args.login, From bf17527c65ccbbe405b981615ee3d6083ecdb339 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 13 Nov 2025 16:00:58 +0100 Subject: [PATCH 07/15] Connect: react to tsh actions by watching tsh dir (#60884) * Add `ClusterLifecycleManager` * Register handlers for adding, removing and logging out from cluster * Provide `rootCluster` in `useWorkspaceContext` The handlers in the profile watcher will proceed with updating the cluster store, even if the renderer handlers returned errors. This check protects us from a runtime error if the renderer fails to remove the workspace. * Improve docs * Move processing queue to listener * Make `will-` operations always interrupt main process actions * Improve error messages * Do not remove managing cluster when **only** logging out The app updater displays all clusters, not just those the user is logged into. * Revert "Provide `rootCluster` in `useWorkspaceContext`" This reverts commit cf76d2ba274f48bc23da7c624561ea0cd9918129. * Rename `logoutWithCleanup` to `cleanUpBeforeLogout` * Do not pass `AbortSignal` to `this.mainProcessClient.syncRootClusters` * Lint * Fix types issues * Do not stack watcher notifications (cherry picked from commit 5fa824911a646a30ded3f617aa4f4423a782550d) --- .../clusterLifecycleManager.ts | 240 ++++++++++++++++++ .../clusterLifecycleManager/index.ts | 19 ++ .../mainProcess/clusterStore/clusterStore.ts | 16 +- .../src/mainProcess/fixtures/mocks.ts | 10 + .../teleterm/src/mainProcess/mainProcess.ts | 64 +++-- .../src/mainProcess/mainProcessClient.ts | 36 ++- .../teleterm/src/mainProcess/types.ts | 21 +- .../ClusterAdd/ClusterAdd.story.tsx | 2 +- .../ClusterConnect/ClusterAdd/ClusterAdd.tsx | 5 +- .../src/ui/ClusterLogout/ClusterLogout.tsx | 4 +- ...tWithCleanup.ts => cleanUpBeforeLogout.ts} | 16 +- web/packages/teleterm/src/ui/appContext.ts | 60 +++++ .../ui/services/clusters/clustersService.ts | 10 +- .../workspacesService/workspacesService.ts | 12 + 14 files changed, 465 insertions(+), 50 deletions(-) create mode 100644 web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts create mode 100644 web/packages/teleterm/src/mainProcess/clusterLifecycleManager/index.ts rename web/packages/teleterm/src/ui/ClusterLogout/{logoutWithCleanup.ts => cleanUpBeforeLogout.ts} (86%) diff --git a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts new file mode 100644 index 0000000000000..d44986450fd1a --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts @@ -0,0 +1,240 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; + +import Logger from 'teleterm/logger'; +import { AwaitableSender } from 'teleterm/mainProcess/awaitableSender'; +import { RendererIpc } from 'teleterm/mainProcess/types'; +import type { WindowsManager } from 'teleterm/mainProcess/windowsManager'; +import { AppUpdater } from 'teleterm/services/appUpdater'; +import { isTshdRpcError, TshdClient } from 'teleterm/services/tshd'; +import { mergeClusterProfileWithDetails } from 'teleterm/services/tshd/cluster'; +import { RootClusterUri } from 'teleterm/ui/uri'; + +import { ClusterStore } from '../clusterStore'; +import { ProfileChangeSet } from '../profileWatcher'; + +/** Describes a lifecycle event related to a cluster. */ +export interface ClusterLifecycleEvent { + uri: RootClusterUri; + /** + * The lifecycle operation type. + * + * Operations prefixed with `will-` occur before the corresponding action + * in the main process and can interrupt it when they return an error. + * + * Operations prefixed with `did-` occur after the action has already happened + * in the main process, so they cannot prevent it. + */ + op: 'did-add-cluster' | 'will-logout' | 'will-logout-and-remove'; +} + +export interface ProfileWatcherError { + error: unknown; + reason: 'processing-error' | 'exited'; +} + +/** + * Manages the lifecycle of clusters by handling both UI actions that update them + * (e.g., adding a cluster, logging out) and profile watcher events. + * + * When handling an action or event requires additional work on the renderer side + * (for example, cleaning up before logging out), a handler registered + * in `rendererEventHandler` is invoked. + * Then, the cluster store is updated (the exact order of these steps depends + * on the specific case). + * + * It is important to always call a method of `ClusterLifecycleManager` rather + * than interacting directly with `ClusterStore` whenever the action involves + * additional work on the renderer side. + */ +export class ClusterLifecycleManager { + private readonly logger = new Logger('ClusterLifecycleManager'); + private rendererEventHandler: + | AwaitableSender + | undefined; + private watcherStarted = false; + + constructor( + private readonly clusterStore: ClusterStore, + private readonly getTshdClient: () => Promise, + private readonly appUpdater: AppUpdater, + private readonly windowsManager: Pick, + private readonly profileWatcher: AsyncIterable + ) {} + + setRendererEventHandler( + handler: AwaitableSender + ): void { + if (this.rendererEventHandler) { + this.logger.error( + 'Only one renderer lifecycle event handler can be registered at a time' + ); + return; + } + + this.logger.info('Renderer lifecycle event handler registered'); + this.rendererEventHandler = handler; + this.rendererEventHandler.whenDisposed().then(() => { + this.logger.info('Renderer lifecycle event handler unregistered'); + this.rendererEventHandler = undefined; + }); + } + + async addCluster(proxyAddress: string): Promise { + const cluster = await this.clusterStore.add(proxyAddress); + await this.rendererEventHandler.send({ + op: 'did-add-cluster', + uri: cluster.uri, + }); + return cluster; + } + + async logoutAndRemoveCluster(uri: RootClusterUri): Promise { + await this.rendererEventHandler.send({ op: 'will-logout-and-remove', uri }); + this.onBeforeRemove(uri); + await this.clusterStore.logoutAndRemove(uri); + } + + async syncRootClustersAndStartProfileWatcher(): Promise { + await this.clusterStore.syncRootClusters(); + if (!this.watcherStarted) { + this.watcherStarted = true; + void this.watchProfileChanges(); + } + } + + private onBeforeRemove(uri: RootClusterUri): void { + // Do not wait for this promise to finish as we don't want to block logout + // on checking app updates. + this.appUpdater.maybeRemoveManagingCluster(uri).catch(error => { + this.logger.error('Failed to remove managing cluster', error); + }); + } + + /** + * If the cluster is connected, try to sync it to get the full profile with details. + * Otherwise, update the cluster with the profile read from disk. + */ + private async syncOrUpdateCluster(cluster: Cluster): Promise { + if (cluster.connected) { + try { + return this.clusterStore.sync(cluster.uri); + } catch (e) { + // Theoretically, the cert could just expire and result in an error + // resolvable with relogin when trying to sync the cluster. + // In that case, only update the store. + if (!(isTshdRpcError(e) && e.isResolvableWithRelogin)) { + throw e; + } + } + } + const existing = this.clusterStore.getState().get(cluster.uri); + await this.clusterStore.set( + mergeClusterProfileWithDetails({ + profile: cluster, + details: existing || Cluster.create(), + }) + ); + } + + /** + * Watches for changes in the `tsh` directory. + * + * Some file system events require notifying the renderer (e.g., to + * remove a workspace before a cluster store update is sent). + */ + private async watchProfileChanges(): Promise { + try { + for await (const changes of this.profileWatcher) { + this.logger.info('Detected profile changes', changes); + + for (const change of changes) { + try { + switch (change.op) { + case 'added': + await this.handleClusterAdded(change.cluster); + break; + case 'changed': + await this.handleClusterChanged(change.previous, change.next); + break; + case 'removed': + await this.handleClusterRemoved(change.cluster); + break; + } + } catch (error) { + this.logger.error('Error while processing cluster event', error); + this.handleWatcherError({ error, reason: 'processing-error' }); + } + } + } + } catch (error) { + this.logger.error('Profile watcher exited with error', error); + this.handleWatcherError({ error, reason: 'exited' }); + } + } + + private async handleClusterAdded(cluster: Cluster): Promise { + await this.syncOrUpdateCluster(cluster); + await this.rendererEventHandler.send({ + op: 'did-add-cluster', + uri: cluster.uri, + }); + } + + private async handleClusterChanged( + previous: Cluster, + next: Cluster + ): Promise { + const wasLoggedIn = previous.loggedInUser?.name; + const isLoggedIn = next.loggedInUser?.name; + const hasLoggedOut = wasLoggedIn && !isLoggedIn; + + if (hasLoggedOut) { + await this.handleClusterLogout(next); + } else { + await this.syncOrUpdateCluster(next); + } + } + + private async handleClusterRemoved(cluster: Cluster): Promise { + await this.rendererEventHandler.send({ + op: 'will-logout-and-remove', + uri: cluster.uri, + }); + this.onBeforeRemove(cluster.uri); + await this.clusterStore.logoutAndRemove(cluster.uri); + } + + private async handleClusterLogout(cluster: Cluster): Promise { + await this.rendererEventHandler.send({ + op: 'will-logout', + uri: cluster.uri, + }); + const client = await this.getTshdClient(); + await client.logout({ clusterUri: cluster.uri, removeProfile: false }); + await this.syncOrUpdateCluster(cluster); + } + + private handleWatcherError(error: ProfileWatcherError): void { + this.windowsManager + .getWindow() + .webContents.send(RendererIpc.ProfileWatcherError, error); + } +} diff --git a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/index.ts b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/index.ts new file mode 100644 index 0000000000000..a1e42e6988ce1 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './clusterLifecycleManager'; diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts index f0eda5cc9414f..ad5baeb93b20e 100644 --- a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts +++ b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts @@ -51,7 +51,10 @@ export class ClusterStore { private readonly windowsManager: Pick ) {} - /** Adds a cluster. */ + /** + * Adds a cluster. + * Should only be called via ClusterLifecycleManager. + */ async add(proxyAddress: string): Promise { const client = await this.getTshdClient(); const { response } = await client.addCluster({ @@ -72,7 +75,10 @@ export class ClusterStore { return response; } - /** Logs out of the cluster and removes its profile.*/ + /** + * Logs out of the cluster and removes its profile. + * Should only be called via ClusterLifecycleManager. + */ async logoutAndRemove(uri: RootClusterUri): Promise { const client = await this.getTshdClient(); await client.logout({ clusterUri: uri, removeProfile: true }); @@ -134,6 +140,12 @@ export class ClusterStore { }); } + async set(cluster: Cluster): Promise { + await this.update(draft => { + draft.set(cluster.uri, cluster); + }); + } + getRootClusters(): Cluster[] { return this.state .values() diff --git a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts index 6900a3da258af..658e81bd7af05 100644 --- a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -211,6 +211,16 @@ export class MockMainProcessClient implements MainProcessClient { async syncRootClusters(): Promise { return []; } + registerClusterLifecycleHandler(): { + cleanup: () => void; + } { + return { cleanup: () => undefined }; + } + subscribeToProfileWatcherErrors(): { + cleanup: () => void; + } { + return { cleanup: () => undefined }; + } } export const makeRuntimeSettings = ( diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index b2022383852d0..7fe83186e4f21 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -37,6 +37,8 @@ import { enableMapSet, enablePatches } from 'immer'; import { AbortError } from 'shared/utils/error'; import Logger from 'teleterm/logger'; +import { ClusterLifecycleManager } from 'teleterm/mainProcess/clusterLifecycleManager'; +import { watchProfiles } from 'teleterm/mainProcess/profileWatcher'; import { getAssetPath } from 'teleterm/mainProcess/runtimeSettings'; import { ChildProcessAddresses, @@ -114,6 +116,16 @@ export default class MainProcess { private sharedProcessLastLogs: KeepLastChunks; private appStateFileStorage: FileStorage; private configFileStorage: FileStorage; + /** + * Promise holding the resolution of child process addresses. + * + * Both the internal tshd client (in the main process) and the one in + * the renderer depend on this promise. + * + * If the promise rejects, the error will propagate to the renderer via the IPC + * handler (causing the renderer to stop initialization and show the error) + * and also surface when attempting to access the tshdClients property. + */ private resolvedChildProcessAddresses: Promise; private windowsManager: WindowsManager; // this function can be safely called concurrently @@ -130,7 +142,7 @@ export default class MainProcess { * child processes are resolved. Set in the constructor. * * If the client setup fails, the resulting error will propagate to callsites which use - * tshdClients. + * tshdClients, including preload of the frontend app, causing its initialization to stop. */ private tshdClients: Promise<{ terminalService: TshdClient; @@ -138,6 +150,8 @@ export default class MainProcess { }>; private readonly appUpdater: AppUpdater; public readonly clusterStore: ClusterStore; + private clusterLifecycleManager: ClusterLifecycleManager; + private disposeAbortController = new AbortController(); /** * Starts necessary child processes such as tsh daemon and the shared process. It also sets @@ -208,9 +222,29 @@ export default class MainProcess { () => this.tshdClients.then(c => c.terminalService), this.windowsManager ); + const watcher = watchProfiles({ + tshDirectory: this.settings.tshd.homeDir, + tshClient: { + listRootClusters: async () => { + const { terminalService } = await this.tshdClients; + const { response } = await terminalService.listRootClusters({}); + return response.clusters; + }, + }, + clusterStore: this.clusterStore, + signal: this.disposeAbortController.signal, + }); + this.clusterLifecycleManager = new ClusterLifecycleManager( + this.clusterStore, + () => this.tshdClients.then(c => c.terminalService), + this.appUpdater, + this.windowsManager, + watcher + ); } async dispose(): Promise { + this.disposeAbortController.abort(); this.windowsManager.dispose(); await Promise.all([ this.appUpdater.dispose(), @@ -329,13 +363,6 @@ export default class MainProcess { /** * Initializes the resolution of child process addresses and sets up tshd clients. * On Windows, the setup of tshd clients also initialized the main process cert for mTLS. - * - * Both the internal tshd client (in the main process) and the one in the renderer - * depend on this initialization promise. - * - * If the promise rejects, the error will propagate to the renderer via the IPC - * handler (causing the renderer to stop initialization and show the error) - * and also surface when attempting to access the tshdClients property. */ private initResolvingChildProcessAddressesAndTshdClients(): void { this.resolvedChildProcessAddresses = Promise.all([ @@ -673,11 +700,11 @@ export default class MainProcess { ); ipcMain.handle(MainProcessIpc.AddCluster, (ev, proxyAddress) => - this.clusterStore.add(proxyAddress) + this.clusterLifecycleManager.addCluster(proxyAddress) ); ipcMain.handle(MainProcessIpc.SyncRootClusters, () => - this.clusterStore.syncRootClusters() + this.clusterLifecycleManager.syncRootClustersAndStartProfileWatcher() ); ipcMain.handle(MainProcessIpc.SyncCluster, (_, args) => @@ -685,13 +712,9 @@ export default class MainProcess { ); ipcMain.handle(MainProcessIpc.Logout, async (_, args) => { - // This function checks for updates, do not wait for it. - this.appUpdater - .maybeRemoveManagingCluster(args.clusterUri) - .catch(error => { - this.logger.error('Failed to remove managing cluster', error); - }); - await this.clusterStore.logoutAndRemove(args.clusterUri); + await this.clusterLifecycleManager.logoutAndRemoveCluster( + args.clusterUri + ); }); ipcMain.on(MainProcessIpc.InitClusterStoreSubscription, ev => { @@ -699,6 +722,13 @@ export default class MainProcess { this.clusterStore.registerSender(new AwaitableSender(port)); }); + ipcMain.on(MainProcessIpc.RegisterClusterLifecycleHandler, ev => { + const port = ev.ports[0]; + this.clusterLifecycleManager.setRendererEventHandler( + new AwaitableSender(port) + ); + }); + subscribeToTerminalContextMenuEvent(this.configService); subscribeToTabContextMenuEvent( this.settings.availableShells, diff --git a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts index fd3fbaa5522d7..39922f62fd62a 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -263,8 +263,8 @@ export default function createMainProcessClient(): MainProcessClient { addCluster: async (proxyAddress: string) => { return await ipcRenderer.invoke(MainProcessIpc.AddCluster, proxyAddress); }, - syncRootClusters: async options => { - return await ipcRenderer.invoke(MainProcessIpc.SyncRootClusters, options); + syncRootClusters: async () => { + return await ipcRenderer.invoke(MainProcessIpc.SyncRootClusters); }, syncCluster: (clusterUri: RootClusterUri) => { return ipcRenderer.invoke(MainProcessIpc.SyncCluster, { clusterUri }); @@ -272,6 +272,30 @@ export default function createMainProcessClient(): MainProcessClient { logout: (clusterUri: RootClusterUri) => { return ipcRenderer.invoke(MainProcessIpc.Logout, { clusterUri }); }, + registerClusterLifecycleHandler(listener): { + cleanup: () => void; + } { + const { close } = startAwaitableSenderListener( + MainProcessIpc.RegisterClusterLifecycleHandler, + listener + ); + + return { cleanup: close }; + }, + subscribeToProfileWatcherErrors: listener => { + const ipcListener = (_, error) => { + listener(error); + }; + + ipcRenderer.addListener(RendererIpc.ProfileWatcherError, ipcListener); + return { + cleanup: () => + ipcRenderer.removeListener( + RendererIpc.ProfileWatcherError, + ipcListener + ), + }; + }, }; } @@ -284,23 +308,25 @@ export default function createMainProcessClient(): MainProcessClient { */ function startAwaitableSenderListener( channel: string, - listener: (value: T) => void + listener: (value: T) => void | Promise ): { close: () => void; } { const { port1: localPort, port2: transferablePort } = new MessageChannel(); - localPort.onmessage = (event: MessageEvent) => { + localPort.onmessage = async (event: MessageEvent) => { const msg = event.data; if (msg.type !== 'data') { return; } const ack: MessageAck = { type: 'ack', id: msg.id }; + try { - listener(msg.payload as T); + await listener(msg.payload as T); } catch (e) { ack.error = e; } + localPort.postMessage(ack); }; diff --git a/web/packages/teleterm/src/mainProcess/types.ts b/web/packages/teleterm/src/mainProcess/types.ts index e2ded8b44eb76..1513a8a9187ea 100644 --- a/web/packages/teleterm/src/mainProcess/types.ts +++ b/web/packages/teleterm/src/mainProcess/types.ts @@ -19,11 +19,14 @@ import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; import { DeepLinkParseResult } from 'teleterm/deepLinks'; +import type { + ClusterLifecycleEvent, + ProfileWatcherError, +} from 'teleterm/mainProcess/clusterLifecycleManager'; import type { ClusterStoreUpdate } from 'teleterm/mainProcess/clusterStore'; import { CreateAgentConfigFileArgs } from 'teleterm/mainProcess/createAgentConfigFile'; import { AppUpdateEvent } from 'teleterm/services/appUpdater'; import { FileStorage } from 'teleterm/services/fileStorage'; -import { CloneableAbortSignal } from 'teleterm/services/tshd'; import { Document } from 'teleterm/ui/services/workspacesService'; import { RootClusterUri } from 'teleterm/ui/uri'; @@ -227,13 +230,21 @@ export type MainProcessClient = { }; addCluster(proxyAddress: string): Promise; syncCluster(clusterUri: RootClusterUri): Promise; - syncRootClusters(options: { - abortSignal: CloneableAbortSignal; - }): Promise; + syncRootClusters(): Promise; logout(clusterUri: RootClusterUri): Promise; subscribeToClusterStore(listener: (value: ClusterStoreUpdate) => void): { cleanup: () => void; }; + registerClusterLifecycleHandler( + listener: (event: ClusterLifecycleEvent) => Promise + ): { + cleanup: () => void; + }; + subscribeToProfileWatcherErrors( + listener: (args: ProfileWatcherError) => void + ): { + cleanup: () => void; + }; }; export type ChildProcessAddresses = { @@ -342,6 +353,7 @@ export enum RendererIpc { OpenAppUpdateDialog = 'renderer-open-app-update-dialog', AppUpdateEvent = 'renderer-app-update-event', IsInBackgroundMode = 'renderer-is-in-background-mode', + ProfileWatcherError = 'renderer-profile-watcher-error', } export enum MainProcessIpc { @@ -363,6 +375,7 @@ export enum MainProcessIpc { AddCluster = 'main-process-add-cluster', SyncRootClusters = 'main-process-sync-root-clusters', Logout = 'main-process-logout', + RegisterClusterLifecycleHandler = 'main-process-register-cluster-lifecycle-handler', } export enum WindowsManagerIpc { diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx index d1b0d6a5d379f..a4b1563b20eca 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx @@ -85,7 +85,7 @@ function getMockAppContext( } = {} ) { const appContext = new MockAppContext(); - appContext.clustersService.addRootCluster = + appContext.mockMainProcessClient.addCluster = args.addRootCluster || (() => Promise.resolve(makeRootCluster())); return appContext; } diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx index e2d6303bb7313..7c62b012a0b52 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx @@ -35,12 +35,11 @@ export function ClusterAdd(props: { onSuccess(clusterUri: string): void; prefill: { clusterAddress: string }; }) { - const { clustersService, workspacesService } = useAppContext(); + const { mainProcessClient } = useAppContext(); const [{ status, statusText }, addCluster] = useAsync( async (addr: string) => { const proxyAddr = parseClusterProxyWebAddr(addr); - const cluster = await clustersService.addRootCluster(proxyAddr); - workspacesService.addWorkspace(cluster.uri); + const cluster = await mainProcessClient.addCluster(proxyAddr); return props.onSuccess(cluster.uri); } ); diff --git a/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx b/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx index 3a5aa35af6544..9326f1276dbb3 100644 --- a/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx +++ b/web/packages/teleterm/src/ui/ClusterLogout/ClusterLogout.tsx @@ -30,8 +30,6 @@ import { useAsync } from 'shared/hooks/useAsync'; import { useAppContext } from 'teleterm/ui/appContextProvider'; import { RootClusterUri, routing } from 'teleterm/ui/uri'; -import { logoutWithCleanup } from './logoutWithCleanup'; - export function ClusterLogout({ clusterUri, onClose, @@ -43,7 +41,7 @@ export function ClusterLogout({ }) { const ctx = useAppContext(); const [{ status, statusText }, removeCluster] = useAsync(() => - logoutWithCleanup(ctx, clusterUri) + ctx.mainProcessClient.logout(clusterUri) ); async function removeClusterAndClose(): Promise { diff --git a/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts b/web/packages/teleterm/src/ui/ClusterLogout/cleanUpBeforeLogout.ts similarity index 86% rename from web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts rename to web/packages/teleterm/src/ui/ClusterLogout/cleanUpBeforeLogout.ts index aeffa5c0d8dac..b84c196d7b914 100644 --- a/web/packages/teleterm/src/ui/ClusterLogout/logoutWithCleanup.ts +++ b/web/packages/teleterm/src/ui/ClusterLogout/cleanUpBeforeLogout.ts @@ -19,10 +19,11 @@ import { IAppContext } from 'teleterm/ui/types'; import { RootClusterUri } from 'teleterm/ui/uri'; -/** Disposes cluster-related resources and then logs out. */ -export async function logoutWithCleanup( +/** Disposes cluster-related resources. */ +export async function cleanUpBeforeLogout( ctx: IAppContext, - clusterUri: RootClusterUri + clusterUri: RootClusterUri, + opts: { removeWorkspace: boolean } ): Promise { if (ctx.workspacesService.getRootClusterUri() === clusterUri) { const [firstConnectedWorkspace] = ctx.workspacesService @@ -38,7 +39,11 @@ export async function logoutWithCleanup( // Remove connections first, they depend both on the cluster and the workspace. ctx.connectionTracker.removeItemsBelongingToRootCluster(clusterUri); // Remove the workspace next, because it depends on the cluster. - ctx.workspacesService.removeWorkspace(clusterUri); + if (opts.removeWorkspace) { + ctx.workspacesService.removeWorkspace(clusterUri); + } else { + ctx.workspacesService.clearWorkspace(clusterUri); + } // If there are active ssh connections to the agent, killing it will take a few seconds. To work // around this, kill the agent only after removing the workspace. Removing the workspace closes @@ -46,7 +51,4 @@ export async function logoutWithCleanup( await ctx.connectMyComputerService.killAgentAndRemoveData(clusterUri); await ctx.clustersService.removeClusterGateways(clusterUri); - - // Remove the cluster, it does not depend on anything. - await ctx.mainProcessClient.logout(clusterUri); } diff --git a/web/packages/teleterm/src/ui/appContext.ts b/web/packages/teleterm/src/ui/appContext.ts index b4beaeebb00df..25962f39e4ca2 100644 --- a/web/packages/teleterm/src/ui/appContext.ts +++ b/web/packages/teleterm/src/ui/appContext.ts @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import { getErrorMessage } from 'shared/utils/error'; + import { ConfigService } from 'teleterm/services/config'; import { TshdClient, VnetClient } from 'teleterm/services/tshd/createClient'; import { @@ -23,6 +25,7 @@ import { MainProcessClient, TshdEventContextBridgeService, } from 'teleterm/types'; +import { cleanUpBeforeLogout } from 'teleterm/ui/ClusterLogout/cleanUpBeforeLogout'; import { ClustersService } from 'teleterm/ui/services/clusters'; import { ConnectionTrackerService } from 'teleterm/ui/services/connectionTracker'; import { ConnectMyComputerService } from 'teleterm/ui/services/connectMyComputer'; @@ -152,6 +155,9 @@ export default class AppContext implements IAppContext { tshClient, this.configService ); + + this.registerClusterLifecycleHandler(); + this.subscribeToProfileWatcherErrors(); } async pullInitialState(): Promise { @@ -194,4 +200,58 @@ export default class AppContext implements IAppContext { get unexpectedVnetShutdownListener(): UnexpectedVnetShutdownListener { return this._unexpectedVnetShutdownListener; } + + private registerClusterLifecycleHandler(): void { + // Queue chain ensures sequential processing. + let processingQueue = Promise.resolve(); + + this.mainProcessClient.registerClusterLifecycleHandler(({ uri, op }) => { + // Chain onto the queue and catch errors so it keeps processing + const task = processingQueue.then(async () => { + switch (op) { + case 'did-add-cluster': + return this.workspacesService.addWorkspace(uri); + case 'will-logout': + return cleanUpBeforeLogout(this, uri, { removeWorkspace: false }); + case 'will-logout-and-remove': + return cleanUpBeforeLogout(this, uri, { removeWorkspace: true }); + default: + op satisfies never; + } + }); + + // Update the queue so the next event waits for this one. + // Catch errors, they will be returned below. + processingQueue = task.catch(() => {}); + + return task; + }); + } + + private subscribeToProfileWatcherErrors(): void { + let notificationId: string | undefined; + this.mainProcessClient.subscribeToProfileWatcherErrors( + ({ error, reason }) => { + let title: string; + switch (reason) { + case 'processing-error': + title = + 'Failed to process the detected profile update. Changes made through tsh may not be reflected in the app.'; + break; + case 'exited': + title = + "Stopped monitoring profiles. Changes made through tsh won't be reflected in the app."; + break; + } + + if (notificationId) { + this.notificationsService.removeNotification(notificationId); + } + notificationId = this.notificationsService.notifyError({ + title, + description: getErrorMessage(error), + }); + } + ); + } } diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index e897ce365c775..5cd44a42d4c03 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -30,7 +30,7 @@ import { AbortError, isAbortError } from 'shared/utils/error'; import type { State as ClustersState } from 'teleterm/mainProcess/clusterStore'; import { MainProcessClient } from 'teleterm/mainProcess/types'; -import { cloneAbortSignal, TshdClient } from 'teleterm/services/tshd'; +import { TshdClient } from 'teleterm/services/tshd'; import { getGatewayTargetUriKind } from 'teleterm/services/tshd/gateway'; import { NotificationsService } from 'teleterm/ui/services/notifications'; import { UsageService } from 'teleterm/ui/services/usage'; @@ -72,10 +72,6 @@ export class ClustersService extends ImmutableStore { this.subscribeToClusterStore(); } - async addRootCluster(proxyAddress: string) { - return this.mainProcessClient.addCluster(proxyAddress); - } - async authenticateWebDevice( rootClusterUri: uri.RootClusterUri, { @@ -186,9 +182,7 @@ export class ClustersService extends ImmutableStore { try { await Promise.race([ abortPromise, - await this.mainProcessClient.syncRootClusters({ - abortSignal: abortSignal && cloneAbortSignal(abortSignal), - }), + await this.mainProcessClient.syncRootClusters(), ]); } catch (error) { if (isAbortError(error)) { diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index b5eb013b50995..7a6c37b4a3808 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -466,6 +466,18 @@ export class WorkspacesService extends ImmutableStore { }); } + clearWorkspace(clusterUri: RootClusterUri): void { + this.setState(draftState => { + draftState.workspaces[clusterUri] = getWorkspaceDefaultState( + clusterUri, + draftState.workspaces + ); + }); + this.restoredState = produce(this.restoredState, draftState => { + delete draftState.workspaces[clusterUri]; + }); + } + getConnectedWorkspacesClustersUri() { return (Object.keys(this.state.workspaces) as RootClusterUri[]).filter( clusterUri => this.clustersService.findCluster(clusterUri)?.connected From 0910e18014b0b0ede24e444a69858a83eedf7810 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Mon, 17 Nov 2025 12:02:40 +0100 Subject: [PATCH 08/15] Connect: close cluster clients when profile changes (#61090) * Include expiration time in `LoggedInUser` This will allow the profile watcher to detect when the user relogged. * Display expiration time in UI * Add `ClearStaleClusterClients` RPC * Implement `ClearStaleClusterClients` * Clear stale clients when profile changes * Improve session expiration component * Move refresh button back to top * `ClearCachedStaleClientsForRoot` -> `ClearStaleCachedClientsForRoot` * `unchanged` -> `stale` * Make "closing stale clients" a subtest * Add `clientcache` test * Remove `getProfile` error wrapping * Improve comment * Convert story to controls (cherry picked from commit 6615e42eccd90c42c4ecefefb57a4f246e66ccaf) --- api/profile/profile.go | 8 +- .../go/teleport/lib/teleterm/v1/cluster.pb.go | 60 +- .../go/teleport/lib/teleterm/v1/service.pb.go | 1048 +++++++++-------- .../lib/teleterm/v1/service_grpc.pb.go | 40 + .../ts/teleport/lib/teleterm/v1/cluster_pb.ts | 16 +- .../lib/teleterm/v1/service_pb.client.ts | 47 +- .../lib/teleterm/v1/service_pb.grpc-server.ts | 18 + .../ts/teleport/lib/teleterm/v1/service_pb.ts | 87 ++ integration/teleterm_test.go | 62 + lib/client/clientcache/clientcache.go | 105 +- lib/client/clientcache/clientcache_test.go | 180 +++ .../apiserver/handler/handler_clusters.go | 14 + lib/teleterm/clusters/cluster.go | 4 + lib/teleterm/daemon/config.go | 2 +- lib/teleterm/daemon/daemon.go | 9 + proto/teleport/lib/teleterm/v1/cluster.proto | 3 + proto/teleport/lib/teleterm/v1/service.proto | 8 + .../clusterLifecycleManager.ts | 6 + .../teleterm/src/services/tshd/cluster.ts | 1 + .../src/services/tshd/fixtures/mocks.ts | 1 + .../teleterm/src/services/tshd/testHelpers.ts | 5 +- .../src/ui/TopBar/Identity/Identity.story.tsx | 282 +++-- .../Identity/IdentityList/IdentityList.tsx | 46 +- 23 files changed, 1392 insertions(+), 660 deletions(-) create mode 100644 lib/client/clientcache/clientcache_test.go diff --git a/api/profile/profile.go b/api/profile/profile.go index 5045a71265875..752c2243644d3 100644 --- a/api/profile/profile.go +++ b/api/profile/profile.go @@ -177,7 +177,7 @@ func (p *Profile) TLSConfig() (*tls.Config, error) { // Expiry returns the credential expiry. func (p *Profile) Expiry() (time.Time, bool) { - certPEMBlock, err := os.ReadFile(p.TLSCertPath()) + certPEMBlock, err := p.TLSCert() if err != nil { return time.Time{}, false } @@ -188,6 +188,12 @@ func (p *Profile) Expiry() (time.Time, bool) { return cert.NotAfter, true } +// TLSCert returns the profile's TLS certificate. +func (p *Profile) TLSCert() ([]byte, error) { + certPEMBlock, err := os.ReadFile(p.TLSCertPath()) + return certPEMBlock, trace.Wrap(err) +} + // RequireKubeLocalProxy returns true if this profile indicates a local proxy // is required for kube access. func (p *Profile) RequireKubeLocalProxy() bool { diff --git a/gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go index 785dffa38870d..9183e559219e2 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go @@ -27,6 +27,7 @@ import ( types "github.com/gravitational/teleport/api/types" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -328,8 +329,10 @@ type LoggedInUser struct { IsDeviceTrusted bool `protobuf:"varint,9,opt,name=is_device_trusted,json=isDeviceTrusted,proto3" json:"is_device_trusted,omitempty"` // Indicates whether access may be hindered by the lack of a trusted device. TrustedDeviceRequirement types.TrustedDeviceRequirement `protobuf:"varint,10,opt,name=trusted_device_requirement,json=trustedDeviceRequirement,proto3,enum=types.TrustedDeviceRequirement" json:"trusted_device_requirement,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Expiration time of the certificate. + ValidUntil *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=valid_until,json=validUntil,proto3" json:"valid_until,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LoggedInUser) Reset() { @@ -425,6 +428,13 @@ func (x *LoggedInUser) GetTrustedDeviceRequirement() types.TrustedDeviceRequirem return types.TrustedDeviceRequirement(0) } +func (x *LoggedInUser) GetValidUntil() *timestamppb.Timestamp { + if x != nil { + return x.ValidUntil + } + return nil +} + // ACL is the access control list of the user type ACL struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -756,7 +766,7 @@ var File_teleport_lib_teleterm_v1_cluster_proto protoreflect.FileDescriptor const file_teleport_lib_teleterm_v1_cluster_proto_rawDesc = "" + "\n" + - "&teleport/lib/teleterm/v1/cluster.proto\x12\x18teleport.lib.teleterm.v1\x1a6teleport/legacy/types/trusted_device_requirement.proto\"\xf8\x03\n" + + "&teleport/lib/teleterm/v1/cluster.proto\x12\x18teleport.lib.teleterm.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a6teleport/legacy/types/trusted_device_requirement.proto\"\xf8\x03\n" + "\aCluster\x12\x10\n" + "\x03uri\x18\x01 \x01(\tR\x03uri\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1d\n" + @@ -771,7 +781,7 @@ const file_teleport_lib_teleterm_v1_cluster_proto_rawDesc = "" + " \x01(\tR\fproxyVersion\x12N\n" + "\x0eshow_resources\x18\v \x01(\x0e2'.teleport.lib.teleterm.v1.ShowResourcesR\rshowResources\x120\n" + "\x14profile_status_error\x18\f \x01(\tR\x12profileStatusError\x12\x19\n" + - "\bsso_host\x18\r \x01(\tR\assoHost\"\xaa\x04\n" + + "\bsso_host\x18\r \x01(\tR\assoHost\"\xe7\x04\n" + "\fLoggedInUser\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05roles\x18\x02 \x03(\tR\x05roles\x12/\n" + @@ -782,7 +792,9 @@ const file_teleport_lib_teleterm_v1_cluster_proto_rawDesc = "" + "\tuser_type\x18\b \x01(\x0e2/.teleport.lib.teleterm.v1.LoggedInUser.UserTypeR\buserType\x12*\n" + "\x11is_device_trusted\x18\t \x01(\bR\x0fisDeviceTrusted\x12]\n" + "\x1atrusted_device_requirement\x18\n" + - " \x01(\x0e2\x1f.types.TrustedDeviceRequirementR\x18trustedDeviceRequirement\"M\n" + + " \x01(\x0e2\x1f.types.TrustedDeviceRequirementR\x18trustedDeviceRequirement\x12;\n" + + "\vvalid_until\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "validUntil\"M\n" + "\bUserType\x12\x19\n" + "\x15USER_TYPE_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fUSER_TYPE_LOCAL\x10\x01\x12\x11\n" + @@ -844,6 +856,7 @@ var file_teleport_lib_teleterm_v1_cluster_proto_goTypes = []any{ (*ResourceAccess)(nil), // 5: teleport.lib.teleterm.v1.ResourceAccess (*Features)(nil), // 6: teleport.lib.teleterm.v1.Features (types.TrustedDeviceRequirement)(0), // 7: types.TrustedDeviceRequirement + (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp } var file_teleport_lib_teleterm_v1_cluster_proto_depIdxs = []int32{ 3, // 0: teleport.lib.teleterm.v1.Cluster.logged_in_user:type_name -> teleport.lib.teleterm.v1.LoggedInUser @@ -852,24 +865,25 @@ var file_teleport_lib_teleterm_v1_cluster_proto_depIdxs = []int32{ 4, // 3: teleport.lib.teleterm.v1.LoggedInUser.acl:type_name -> teleport.lib.teleterm.v1.ACL 1, // 4: teleport.lib.teleterm.v1.LoggedInUser.user_type:type_name -> teleport.lib.teleterm.v1.LoggedInUser.UserType 7, // 5: teleport.lib.teleterm.v1.LoggedInUser.trusted_device_requirement:type_name -> types.TrustedDeviceRequirement - 5, // 6: teleport.lib.teleterm.v1.ACL.auth_connectors:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 7: teleport.lib.teleterm.v1.ACL.roles:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 8: teleport.lib.teleterm.v1.ACL.users:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 9: teleport.lib.teleterm.v1.ACL.trusted_clusters:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 10: teleport.lib.teleterm.v1.ACL.events:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 11: teleport.lib.teleterm.v1.ACL.tokens:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 12: teleport.lib.teleterm.v1.ACL.servers:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 13: teleport.lib.teleterm.v1.ACL.apps:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 14: teleport.lib.teleterm.v1.ACL.dbs:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 15: teleport.lib.teleterm.v1.ACL.kubeservers:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 16: teleport.lib.teleterm.v1.ACL.access_requests:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 17: teleport.lib.teleterm.v1.ACL.recorded_sessions:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 5, // 18: teleport.lib.teleterm.v1.ACL.active_sessions:type_name -> teleport.lib.teleterm.v1.ResourceAccess - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 8, // 6: teleport.lib.teleterm.v1.LoggedInUser.valid_until:type_name -> google.protobuf.Timestamp + 5, // 7: teleport.lib.teleterm.v1.ACL.auth_connectors:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 8: teleport.lib.teleterm.v1.ACL.roles:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 9: teleport.lib.teleterm.v1.ACL.users:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 10: teleport.lib.teleterm.v1.ACL.trusted_clusters:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 11: teleport.lib.teleterm.v1.ACL.events:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 12: teleport.lib.teleterm.v1.ACL.tokens:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 13: teleport.lib.teleterm.v1.ACL.servers:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 14: teleport.lib.teleterm.v1.ACL.apps:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 15: teleport.lib.teleterm.v1.ACL.dbs:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 16: teleport.lib.teleterm.v1.ACL.kubeservers:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 17: teleport.lib.teleterm.v1.ACL.access_requests:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 18: teleport.lib.teleterm.v1.ACL.recorded_sessions:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 5, // 19: teleport.lib.teleterm.v1.ACL.active_sessions:type_name -> teleport.lib.teleterm.v1.ResourceAccess + 20, // [20:20] is the sub-list for method output_type + 20, // [20:20] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_teleport_lib_teleterm_v1_cluster_proto_init() } diff --git a/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go index 983564730e5e2..7703b7d64d913 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/service.pb.go @@ -337,6 +337,86 @@ func (x *LogoutRequest) GetRemoveProfile() bool { return false } +type ClearStaleClusterClientsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RootClusterUri string `protobuf:"bytes,1,opt,name=root_cluster_uri,json=rootClusterUri,proto3" json:"root_cluster_uri,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClearStaleClusterClientsRequest) Reset() { + *x = ClearStaleClusterClientsRequest{} + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClearStaleClusterClientsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearStaleClusterClientsRequest) ProtoMessage() {} + +func (x *ClearStaleClusterClientsRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearStaleClusterClientsRequest.ProtoReflect.Descriptor instead. +func (*ClearStaleClusterClientsRequest) Descriptor() ([]byte, []int) { + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ClearStaleClusterClientsRequest) GetRootClusterUri() string { + if x != nil { + return x.RootClusterUri + } + return "" +} + +type ClearStaleClusterClientsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClearStaleClusterClientsResponse) Reset() { + *x = ClearStaleClusterClientsResponse{} + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClearStaleClusterClientsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearStaleClusterClientsResponse) ProtoMessage() {} + +func (x *ClearStaleClusterClientsResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearStaleClusterClientsResponse.ProtoReflect.Descriptor instead. +func (*ClearStaleClusterClientsResponse) Descriptor() ([]byte, []int) { + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{4} +} + type StartHeadlessWatcherRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RootClusterUri string `protobuf:"bytes,1,opt,name=root_cluster_uri,json=rootClusterUri,proto3" json:"root_cluster_uri,omitempty"` @@ -346,7 +426,7 @@ type StartHeadlessWatcherRequest struct { func (x *StartHeadlessWatcherRequest) Reset() { *x = StartHeadlessWatcherRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -358,7 +438,7 @@ func (x *StartHeadlessWatcherRequest) String() string { func (*StartHeadlessWatcherRequest) ProtoMessage() {} func (x *StartHeadlessWatcherRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[3] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -371,7 +451,7 @@ func (x *StartHeadlessWatcherRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartHeadlessWatcherRequest.ProtoReflect.Descriptor instead. func (*StartHeadlessWatcherRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{3} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{5} } func (x *StartHeadlessWatcherRequest) GetRootClusterUri() string { @@ -389,7 +469,7 @@ type StartHeadlessWatcherResponse struct { func (x *StartHeadlessWatcherResponse) Reset() { *x = StartHeadlessWatcherResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -401,7 +481,7 @@ func (x *StartHeadlessWatcherResponse) String() string { func (*StartHeadlessWatcherResponse) ProtoMessage() {} func (x *StartHeadlessWatcherResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[4] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -414,7 +494,7 @@ func (x *StartHeadlessWatcherResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartHeadlessWatcherResponse.ProtoReflect.Descriptor instead. func (*StartHeadlessWatcherResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{4} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{6} } type GetAccessRequestRequest struct { @@ -428,7 +508,7 @@ type GetAccessRequestRequest struct { func (x *GetAccessRequestRequest) Reset() { *x = GetAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -440,7 +520,7 @@ func (x *GetAccessRequestRequest) String() string { func (*GetAccessRequestRequest) ProtoMessage() {} func (x *GetAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[5] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -453,7 +533,7 @@ func (x *GetAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestRequest.ProtoReflect.Descriptor instead. func (*GetAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{5} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{7} } func (x *GetAccessRequestRequest) GetClusterUri() string { @@ -480,7 +560,7 @@ type GetAccessRequestsRequest struct { func (x *GetAccessRequestsRequest) Reset() { *x = GetAccessRequestsRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -492,7 +572,7 @@ func (x *GetAccessRequestsRequest) String() string { func (*GetAccessRequestsRequest) ProtoMessage() {} func (x *GetAccessRequestsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[6] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -505,7 +585,7 @@ func (x *GetAccessRequestsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestsRequest.ProtoReflect.Descriptor instead. func (*GetAccessRequestsRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{6} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{8} } func (x *GetAccessRequestsRequest) GetClusterUri() string { @@ -524,7 +604,7 @@ type GetAccessRequestResponse struct { func (x *GetAccessRequestResponse) Reset() { *x = GetAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -536,7 +616,7 @@ func (x *GetAccessRequestResponse) String() string { func (*GetAccessRequestResponse) ProtoMessage() {} func (x *GetAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[7] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -549,7 +629,7 @@ func (x *GetAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestResponse.ProtoReflect.Descriptor instead. func (*GetAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{7} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{9} } func (x *GetAccessRequestResponse) GetRequest() *AccessRequest { @@ -568,7 +648,7 @@ type GetAccessRequestsResponse struct { func (x *GetAccessRequestsResponse) Reset() { *x = GetAccessRequestsResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -580,7 +660,7 @@ func (x *GetAccessRequestsResponse) String() string { func (*GetAccessRequestsResponse) ProtoMessage() {} func (x *GetAccessRequestsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[8] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -593,7 +673,7 @@ func (x *GetAccessRequestsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccessRequestsResponse.ProtoReflect.Descriptor instead. func (*GetAccessRequestsResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{8} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{10} } func (x *GetAccessRequestsResponse) GetRequests() []*AccessRequest { @@ -613,7 +693,7 @@ type DeleteAccessRequestRequest struct { func (x *DeleteAccessRequestRequest) Reset() { *x = DeleteAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -625,7 +705,7 @@ func (x *DeleteAccessRequestRequest) String() string { func (*DeleteAccessRequestRequest) ProtoMessage() {} func (x *DeleteAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[9] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -638,7 +718,7 @@ func (x *DeleteAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAccessRequestRequest.ProtoReflect.Descriptor instead. func (*DeleteAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{9} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{11} } func (x *DeleteAccessRequestRequest) GetRootClusterUri() string { @@ -681,7 +761,7 @@ type CreateAccessRequestRequest struct { func (x *CreateAccessRequestRequest) Reset() { *x = CreateAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -693,7 +773,7 @@ func (x *CreateAccessRequestRequest) String() string { func (*CreateAccessRequestRequest) ProtoMessage() {} func (x *CreateAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[10] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -706,7 +786,7 @@ func (x *CreateAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAccessRequestRequest.ProtoReflect.Descriptor instead. func (*CreateAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{10} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{12} } func (x *CreateAccessRequestRequest) GetRootClusterUri() string { @@ -781,7 +861,7 @@ type CreateAccessRequestResponse struct { func (x *CreateAccessRequestResponse) Reset() { *x = CreateAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -793,7 +873,7 @@ func (x *CreateAccessRequestResponse) String() string { func (*CreateAccessRequestResponse) ProtoMessage() {} func (x *CreateAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[11] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -806,7 +886,7 @@ func (x *CreateAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAccessRequestResponse.ProtoReflect.Descriptor instead. func (*CreateAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{11} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{13} } func (x *CreateAccessRequestResponse) GetRequest() *AccessRequest { @@ -827,7 +907,7 @@ type AssumeRoleRequest struct { func (x *AssumeRoleRequest) Reset() { *x = AssumeRoleRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -839,7 +919,7 @@ func (x *AssumeRoleRequest) String() string { func (*AssumeRoleRequest) ProtoMessage() {} func (x *AssumeRoleRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[12] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -852,7 +932,7 @@ func (x *AssumeRoleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AssumeRoleRequest.ProtoReflect.Descriptor instead. func (*AssumeRoleRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{12} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{14} } func (x *AssumeRoleRequest) GetRootClusterUri() string { @@ -886,7 +966,7 @@ type GetRequestableRolesRequest struct { func (x *GetRequestableRolesRequest) Reset() { *x = GetRequestableRolesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -898,7 +978,7 @@ func (x *GetRequestableRolesRequest) String() string { func (*GetRequestableRolesRequest) ProtoMessage() {} func (x *GetRequestableRolesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[13] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -911,7 +991,7 @@ func (x *GetRequestableRolesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequestableRolesRequest.ProtoReflect.Descriptor instead. func (*GetRequestableRolesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{13} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{15} } func (x *GetRequestableRolesRequest) GetClusterUri() string { @@ -938,7 +1018,7 @@ type GetRequestableRolesResponse struct { func (x *GetRequestableRolesResponse) Reset() { *x = GetRequestableRolesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -950,7 +1030,7 @@ func (x *GetRequestableRolesResponse) String() string { func (*GetRequestableRolesResponse) ProtoMessage() {} func (x *GetRequestableRolesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[14] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -963,7 +1043,7 @@ func (x *GetRequestableRolesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequestableRolesResponse.ProtoReflect.Descriptor instead. func (*GetRequestableRolesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{14} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{16} } func (x *GetRequestableRolesResponse) GetRoles() []string { @@ -995,7 +1075,7 @@ type ReviewAccessRequestRequest struct { func (x *ReviewAccessRequestRequest) Reset() { *x = ReviewAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1007,7 +1087,7 @@ func (x *ReviewAccessRequestRequest) String() string { func (*ReviewAccessRequestRequest) ProtoMessage() {} func (x *ReviewAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[15] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1020,7 +1100,7 @@ func (x *ReviewAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReviewAccessRequestRequest.ProtoReflect.Descriptor instead. func (*ReviewAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{15} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{17} } func (x *ReviewAccessRequestRequest) GetRootClusterUri() string { @@ -1074,7 +1154,7 @@ type ReviewAccessRequestResponse struct { func (x *ReviewAccessRequestResponse) Reset() { *x = ReviewAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1086,7 +1166,7 @@ func (x *ReviewAccessRequestResponse) String() string { func (*ReviewAccessRequestResponse) ProtoMessage() {} func (x *ReviewAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[16] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1099,7 +1179,7 @@ func (x *ReviewAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReviewAccessRequestResponse.ProtoReflect.Descriptor instead. func (*ReviewAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{16} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{18} } func (x *ReviewAccessRequestResponse) GetRequest() *AccessRequest { @@ -1121,7 +1201,7 @@ type PromoteAccessRequestRequest struct { func (x *PromoteAccessRequestRequest) Reset() { *x = PromoteAccessRequestRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1133,7 +1213,7 @@ func (x *PromoteAccessRequestRequest) String() string { func (*PromoteAccessRequestRequest) ProtoMessage() {} func (x *PromoteAccessRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[17] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1146,7 +1226,7 @@ func (x *PromoteAccessRequestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PromoteAccessRequestRequest.ProtoReflect.Descriptor instead. func (*PromoteAccessRequestRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{17} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{19} } func (x *PromoteAccessRequestRequest) GetRootClusterUri() string { @@ -1186,7 +1266,7 @@ type PromoteAccessRequestResponse struct { func (x *PromoteAccessRequestResponse) Reset() { *x = PromoteAccessRequestResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1198,7 +1278,7 @@ func (x *PromoteAccessRequestResponse) String() string { func (*PromoteAccessRequestResponse) ProtoMessage() {} func (x *PromoteAccessRequestResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[18] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1211,7 +1291,7 @@ func (x *PromoteAccessRequestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PromoteAccessRequestResponse.ProtoReflect.Descriptor instead. func (*PromoteAccessRequestResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{18} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{20} } func (x *PromoteAccessRequestResponse) GetRequest() *AccessRequest { @@ -1231,7 +1311,7 @@ type GetSuggestedAccessListsRequest struct { func (x *GetSuggestedAccessListsRequest) Reset() { *x = GetSuggestedAccessListsRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1243,7 +1323,7 @@ func (x *GetSuggestedAccessListsRequest) String() string { func (*GetSuggestedAccessListsRequest) ProtoMessage() {} func (x *GetSuggestedAccessListsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[19] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1256,7 +1336,7 @@ func (x *GetSuggestedAccessListsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSuggestedAccessListsRequest.ProtoReflect.Descriptor instead. func (*GetSuggestedAccessListsRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{19} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{21} } func (x *GetSuggestedAccessListsRequest) GetRootClusterUri() string { @@ -1282,7 +1362,7 @@ type GetSuggestedAccessListsResponse struct { func (x *GetSuggestedAccessListsResponse) Reset() { *x = GetSuggestedAccessListsResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1294,7 +1374,7 @@ func (x *GetSuggestedAccessListsResponse) String() string { func (*GetSuggestedAccessListsResponse) ProtoMessage() {} func (x *GetSuggestedAccessListsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[20] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1307,7 +1387,7 @@ func (x *GetSuggestedAccessListsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSuggestedAccessListsResponse.ProtoReflect.Descriptor instead. func (*GetSuggestedAccessListsResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{20} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{22} } func (x *GetSuggestedAccessListsResponse) GetAccessLists() []*v1.AccessList { @@ -1346,7 +1426,7 @@ type ListKubernetesResourcesRequest struct { func (x *ListKubernetesResourcesRequest) Reset() { *x = ListKubernetesResourcesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1358,7 +1438,7 @@ func (x *ListKubernetesResourcesRequest) String() string { func (*ListKubernetesResourcesRequest) ProtoMessage() {} func (x *ListKubernetesResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[21] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1371,7 +1451,7 @@ func (x *ListKubernetesResourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesResourcesRequest.ProtoReflect.Descriptor instead. func (*ListKubernetesResourcesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{21} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{23} } func (x *ListKubernetesResourcesRequest) GetClusterUri() string { @@ -1446,7 +1526,7 @@ type ListKubernetesResourcesResponse struct { func (x *ListKubernetesResourcesResponse) Reset() { *x = ListKubernetesResourcesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1458,7 +1538,7 @@ func (x *ListKubernetesResourcesResponse) String() string { func (*ListKubernetesResourcesResponse) ProtoMessage() {} func (x *ListKubernetesResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[22] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1471,7 +1551,7 @@ func (x *ListKubernetesResourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesResourcesResponse.ProtoReflect.Descriptor instead. func (*ListKubernetesResourcesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{22} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{24} } func (x *ListKubernetesResourcesResponse) GetResources() []*KubeResource { @@ -1498,7 +1578,7 @@ type ListKubernetesServersRequest struct { func (x *ListKubernetesServersRequest) Reset() { *x = ListKubernetesServersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1510,7 +1590,7 @@ func (x *ListKubernetesServersRequest) String() string { func (*ListKubernetesServersRequest) ProtoMessage() {} func (x *ListKubernetesServersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[23] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1523,7 +1603,7 @@ func (x *ListKubernetesServersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesServersRequest.ProtoReflect.Descriptor instead. func (*ListKubernetesServersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{23} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{25} } func (x *ListKubernetesServersRequest) GetPageSize() int32 { @@ -1567,7 +1647,7 @@ type ListKubernetesServersResponse struct { func (x *ListKubernetesServersResponse) Reset() { *x = ListKubernetesServersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1579,7 +1659,7 @@ func (x *ListKubernetesServersResponse) String() string { func (*ListKubernetesServersResponse) ProtoMessage() {} func (x *ListKubernetesServersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[24] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1592,7 +1672,7 @@ func (x *ListKubernetesServersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListKubernetesServersResponse.ProtoReflect.Descriptor instead. func (*ListKubernetesServersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{24} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{26} } func (x *ListKubernetesServersResponse) GetResources() []*KubeServer { @@ -1619,7 +1699,7 @@ type CredentialInfo struct { func (x *CredentialInfo) Reset() { *x = CredentialInfo{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1631,7 +1711,7 @@ func (x *CredentialInfo) String() string { func (*CredentialInfo) ProtoMessage() {} func (x *CredentialInfo) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[25] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1644,7 +1724,7 @@ func (x *CredentialInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialInfo.ProtoReflect.Descriptor instead. func (*CredentialInfo) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{25} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27} } func (x *CredentialInfo) GetUsername() string { @@ -1666,7 +1746,7 @@ type LoginPasswordlessResponse struct { func (x *LoginPasswordlessResponse) Reset() { *x = LoginPasswordlessResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1678,7 +1758,7 @@ func (x *LoginPasswordlessResponse) String() string { func (*LoginPasswordlessResponse) ProtoMessage() {} func (x *LoginPasswordlessResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[26] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1691,7 +1771,7 @@ func (x *LoginPasswordlessResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginPasswordlessResponse.ProtoReflect.Descriptor instead. func (*LoginPasswordlessResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{26} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28} } func (x *LoginPasswordlessResponse) GetPrompt() PasswordlessPrompt { @@ -1723,7 +1803,7 @@ type LoginPasswordlessRequest struct { func (x *LoginPasswordlessRequest) Reset() { *x = LoginPasswordlessRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1735,7 +1815,7 @@ func (x *LoginPasswordlessRequest) String() string { func (*LoginPasswordlessRequest) ProtoMessage() {} func (x *LoginPasswordlessRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[27] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1748,7 +1828,7 @@ func (x *LoginPasswordlessRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginPasswordlessRequest.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29} } func (x *LoginPasswordlessRequest) GetRequest() isLoginPasswordlessRequest_Request { @@ -1824,7 +1904,7 @@ type FileTransferRequest struct { func (x *FileTransferRequest) Reset() { *x = FileTransferRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1836,7 +1916,7 @@ func (x *FileTransferRequest) String() string { func (*FileTransferRequest) ProtoMessage() {} func (x *FileTransferRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[28] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1849,7 +1929,7 @@ func (x *FileTransferRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FileTransferRequest.ProtoReflect.Descriptor instead. func (*FileTransferRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{28} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30} } func (x *FileTransferRequest) GetLogin() string { @@ -1896,7 +1976,7 @@ type FileTransferProgress struct { func (x *FileTransferProgress) Reset() { *x = FileTransferProgress{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1908,7 +1988,7 @@ func (x *FileTransferProgress) String() string { func (*FileTransferProgress) ProtoMessage() {} func (x *FileTransferProgress) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[29] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1921,7 +2001,7 @@ func (x *FileTransferProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use FileTransferProgress.ProtoReflect.Descriptor instead. func (*FileTransferProgress) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{31} } func (x *FileTransferProgress) GetPercentage() uint32 { @@ -1947,7 +2027,7 @@ type LoginRequest struct { func (x *LoginRequest) Reset() { *x = LoginRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1959,7 +2039,7 @@ func (x *LoginRequest) String() string { func (*LoginRequest) ProtoMessage() {} func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[30] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1972,7 +2052,7 @@ func (x *LoginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{32} } func (x *LoginRequest) GetClusterUri() string { @@ -2034,7 +2114,7 @@ type AddClusterRequest struct { func (x *AddClusterRequest) Reset() { *x = AddClusterRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2046,7 +2126,7 @@ func (x *AddClusterRequest) String() string { func (*AddClusterRequest) ProtoMessage() {} func (x *AddClusterRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[31] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2059,7 +2139,7 @@ func (x *AddClusterRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddClusterRequest.ProtoReflect.Descriptor instead. func (*AddClusterRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{31} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{33} } func (x *AddClusterRequest) GetName() string { @@ -2077,7 +2157,7 @@ type ListClustersRequest struct { func (x *ListClustersRequest) Reset() { *x = ListClustersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2089,7 +2169,7 @@ func (x *ListClustersRequest) String() string { func (*ListClustersRequest) ProtoMessage() {} func (x *ListClustersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[32] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2102,7 +2182,7 @@ func (x *ListClustersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListClustersRequest.ProtoReflect.Descriptor instead. func (*ListClustersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{32} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{34} } type ListClustersResponse struct { @@ -2114,7 +2194,7 @@ type ListClustersResponse struct { func (x *ListClustersResponse) Reset() { *x = ListClustersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2126,7 +2206,7 @@ func (x *ListClustersResponse) String() string { func (*ListClustersResponse) ProtoMessage() {} func (x *ListClustersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[33] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2139,7 +2219,7 @@ func (x *ListClustersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListClustersResponse.ProtoReflect.Descriptor instead. func (*ListClustersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{33} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{35} } func (x *ListClustersResponse) GetClusters() []*Cluster { @@ -2158,7 +2238,7 @@ type ListLeafClustersRequest struct { func (x *ListLeafClustersRequest) Reset() { *x = ListLeafClustersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2170,7 +2250,7 @@ func (x *ListLeafClustersRequest) String() string { func (*ListLeafClustersRequest) ProtoMessage() {} func (x *ListLeafClustersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[34] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2183,7 +2263,7 @@ func (x *ListLeafClustersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeafClustersRequest.ProtoReflect.Descriptor instead. func (*ListLeafClustersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{34} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{36} } func (x *ListLeafClustersRequest) GetClusterUri() string { @@ -2202,7 +2282,7 @@ type ListDatabaseUsersRequest struct { func (x *ListDatabaseUsersRequest) Reset() { *x = ListDatabaseUsersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2214,7 +2294,7 @@ func (x *ListDatabaseUsersRequest) String() string { func (*ListDatabaseUsersRequest) ProtoMessage() {} func (x *ListDatabaseUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[35] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2227,7 +2307,7 @@ func (x *ListDatabaseUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseUsersRequest.ProtoReflect.Descriptor instead. func (*ListDatabaseUsersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{35} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{37} } func (x *ListDatabaseUsersRequest) GetDbUri() string { @@ -2246,7 +2326,7 @@ type ListDatabaseUsersResponse struct { func (x *ListDatabaseUsersResponse) Reset() { *x = ListDatabaseUsersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2258,7 +2338,7 @@ func (x *ListDatabaseUsersResponse) String() string { func (*ListDatabaseUsersResponse) ProtoMessage() {} func (x *ListDatabaseUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[36] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2271,7 +2351,7 @@ func (x *ListDatabaseUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseUsersResponse.ProtoReflect.Descriptor instead. func (*ListDatabaseUsersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{36} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{38} } func (x *ListDatabaseUsersResponse) GetUsers() []string { @@ -2298,7 +2378,7 @@ type ListResourcesParams struct { func (x *ListResourcesParams) Reset() { *x = ListResourcesParams{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2310,7 +2390,7 @@ func (x *ListResourcesParams) String() string { func (*ListResourcesParams) ProtoMessage() {} func (x *ListResourcesParams) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[37] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2323,7 +2403,7 @@ func (x *ListResourcesParams) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResourcesParams.ProtoReflect.Descriptor instead. func (*ListResourcesParams) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{37} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{39} } func (x *ListResourcesParams) GetStartKey() string { @@ -2364,7 +2444,7 @@ type ListDatabaseServersRequest struct { func (x *ListDatabaseServersRequest) Reset() { *x = ListDatabaseServersRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2376,7 +2456,7 @@ func (x *ListDatabaseServersRequest) String() string { func (*ListDatabaseServersRequest) ProtoMessage() {} func (x *ListDatabaseServersRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[38] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2389,7 +2469,7 @@ func (x *ListDatabaseServersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseServersRequest.ProtoReflect.Descriptor instead. func (*ListDatabaseServersRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{38} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{40} } func (x *ListDatabaseServersRequest) GetClusterUri() string { @@ -2416,7 +2496,7 @@ type ListDatabaseServersResponse struct { func (x *ListDatabaseServersResponse) Reset() { *x = ListDatabaseServersResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2428,7 +2508,7 @@ func (x *ListDatabaseServersResponse) String() string { func (*ListDatabaseServersResponse) ProtoMessage() {} func (x *ListDatabaseServersResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[39] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2441,7 +2521,7 @@ func (x *ListDatabaseServersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDatabaseServersResponse.ProtoReflect.Descriptor instead. func (*ListDatabaseServersResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{39} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{41} } func (x *ListDatabaseServersResponse) GetResources() []*DatabaseServer { @@ -2470,7 +2550,7 @@ type CreateGatewayRequest struct { func (x *CreateGatewayRequest) Reset() { *x = CreateGatewayRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2482,7 +2562,7 @@ func (x *CreateGatewayRequest) String() string { func (*CreateGatewayRequest) ProtoMessage() {} func (x *CreateGatewayRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[40] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2495,7 +2575,7 @@ func (x *CreateGatewayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGatewayRequest.ProtoReflect.Descriptor instead. func (*CreateGatewayRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{40} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{42} } func (x *CreateGatewayRequest) GetTargetUri() string { @@ -2534,7 +2614,7 @@ type ListGatewaysRequest struct { func (x *ListGatewaysRequest) Reset() { *x = ListGatewaysRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2546,7 +2626,7 @@ func (x *ListGatewaysRequest) String() string { func (*ListGatewaysRequest) ProtoMessage() {} func (x *ListGatewaysRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[41] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2559,7 +2639,7 @@ func (x *ListGatewaysRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListGatewaysRequest.ProtoReflect.Descriptor instead. func (*ListGatewaysRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{41} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{43} } type ListGatewaysResponse struct { @@ -2571,7 +2651,7 @@ type ListGatewaysResponse struct { func (x *ListGatewaysResponse) Reset() { *x = ListGatewaysResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2583,7 +2663,7 @@ func (x *ListGatewaysResponse) String() string { func (*ListGatewaysResponse) ProtoMessage() {} func (x *ListGatewaysResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[42] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2596,7 +2676,7 @@ func (x *ListGatewaysResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListGatewaysResponse.ProtoReflect.Descriptor instead. func (*ListGatewaysResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{42} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{44} } func (x *ListGatewaysResponse) GetGateways() []*Gateway { @@ -2615,7 +2695,7 @@ type RemoveGatewayRequest struct { func (x *RemoveGatewayRequest) Reset() { *x = RemoveGatewayRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2627,7 +2707,7 @@ func (x *RemoveGatewayRequest) String() string { func (*RemoveGatewayRequest) ProtoMessage() {} func (x *RemoveGatewayRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[43] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2640,7 +2720,7 @@ func (x *RemoveGatewayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveGatewayRequest.ProtoReflect.Descriptor instead. func (*RemoveGatewayRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{43} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{45} } func (x *RemoveGatewayRequest) GetGatewayUri() string { @@ -2660,7 +2740,7 @@ type SetGatewayTargetSubresourceNameRequest struct { func (x *SetGatewayTargetSubresourceNameRequest) Reset() { *x = SetGatewayTargetSubresourceNameRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2672,7 +2752,7 @@ func (x *SetGatewayTargetSubresourceNameRequest) String() string { func (*SetGatewayTargetSubresourceNameRequest) ProtoMessage() {} func (x *SetGatewayTargetSubresourceNameRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[44] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2685,7 +2765,7 @@ func (x *SetGatewayTargetSubresourceNameRequest) ProtoReflect() protoreflect.Mes // Deprecated: Use SetGatewayTargetSubresourceNameRequest.ProtoReflect.Descriptor instead. func (*SetGatewayTargetSubresourceNameRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{44} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{46} } func (x *SetGatewayTargetSubresourceNameRequest) GetGatewayUri() string { @@ -2712,7 +2792,7 @@ type SetGatewayLocalPortRequest struct { func (x *SetGatewayLocalPortRequest) Reset() { *x = SetGatewayLocalPortRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2724,7 +2804,7 @@ func (x *SetGatewayLocalPortRequest) String() string { func (*SetGatewayLocalPortRequest) ProtoMessage() {} func (x *SetGatewayLocalPortRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[45] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2737,7 +2817,7 @@ func (x *SetGatewayLocalPortRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGatewayLocalPortRequest.ProtoReflect.Descriptor instead. func (*SetGatewayLocalPortRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{45} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{47} } func (x *SetGatewayLocalPortRequest) GetGatewayUri() string { @@ -2763,7 +2843,7 @@ type GetAuthSettingsRequest struct { func (x *GetAuthSettingsRequest) Reset() { *x = GetAuthSettingsRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2775,7 +2855,7 @@ func (x *GetAuthSettingsRequest) String() string { func (*GetAuthSettingsRequest) ProtoMessage() {} func (x *GetAuthSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[46] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2788,7 +2868,7 @@ func (x *GetAuthSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAuthSettingsRequest.ProtoReflect.Descriptor instead. func (*GetAuthSettingsRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{46} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{48} } func (x *GetAuthSettingsRequest) GetClusterUri() string { @@ -2807,7 +2887,7 @@ type UpdateTshdEventsServerAddressRequest struct { func (x *UpdateTshdEventsServerAddressRequest) Reset() { *x = UpdateTshdEventsServerAddressRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2819,7 +2899,7 @@ func (x *UpdateTshdEventsServerAddressRequest) String() string { func (*UpdateTshdEventsServerAddressRequest) ProtoMessage() {} func (x *UpdateTshdEventsServerAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[47] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2832,7 +2912,7 @@ func (x *UpdateTshdEventsServerAddressRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use UpdateTshdEventsServerAddressRequest.ProtoReflect.Descriptor instead. func (*UpdateTshdEventsServerAddressRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{47} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{49} } func (x *UpdateTshdEventsServerAddressRequest) GetAddress() string { @@ -2850,7 +2930,7 @@ type UpdateTshdEventsServerAddressResponse struct { func (x *UpdateTshdEventsServerAddressResponse) Reset() { *x = UpdateTshdEventsServerAddressResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2862,7 +2942,7 @@ func (x *UpdateTshdEventsServerAddressResponse) String() string { func (*UpdateTshdEventsServerAddressResponse) ProtoMessage() {} func (x *UpdateTshdEventsServerAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[48] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2875,7 +2955,7 @@ func (x *UpdateTshdEventsServerAddressResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use UpdateTshdEventsServerAddressResponse.ProtoReflect.Descriptor instead. func (*UpdateTshdEventsServerAddressResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{48} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{50} } type UpdateHeadlessAuthenticationStateRequest struct { @@ -2889,7 +2969,7 @@ type UpdateHeadlessAuthenticationStateRequest struct { func (x *UpdateHeadlessAuthenticationStateRequest) Reset() { *x = UpdateHeadlessAuthenticationStateRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2901,7 +2981,7 @@ func (x *UpdateHeadlessAuthenticationStateRequest) String() string { func (*UpdateHeadlessAuthenticationStateRequest) ProtoMessage() {} func (x *UpdateHeadlessAuthenticationStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[49] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2914,7 +2994,7 @@ func (x *UpdateHeadlessAuthenticationStateRequest) ProtoReflect() protoreflect.M // Deprecated: Use UpdateHeadlessAuthenticationStateRequest.ProtoReflect.Descriptor instead. func (*UpdateHeadlessAuthenticationStateRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{49} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{51} } func (x *UpdateHeadlessAuthenticationStateRequest) GetRootClusterUri() string { @@ -2946,7 +3026,7 @@ type UpdateHeadlessAuthenticationStateResponse struct { func (x *UpdateHeadlessAuthenticationStateResponse) Reset() { *x = UpdateHeadlessAuthenticationStateResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2958,7 +3038,7 @@ func (x *UpdateHeadlessAuthenticationStateResponse) String() string { func (*UpdateHeadlessAuthenticationStateResponse) ProtoMessage() {} func (x *UpdateHeadlessAuthenticationStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[50] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2971,7 +3051,7 @@ func (x *UpdateHeadlessAuthenticationStateResponse) ProtoReflect() protoreflect. // Deprecated: Use UpdateHeadlessAuthenticationStateResponse.ProtoReflect.Descriptor instead. func (*UpdateHeadlessAuthenticationStateResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{50} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{52} } type CreateConnectMyComputerRoleRequest struct { @@ -2983,7 +3063,7 @@ type CreateConnectMyComputerRoleRequest struct { func (x *CreateConnectMyComputerRoleRequest) Reset() { *x = CreateConnectMyComputerRoleRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2995,7 +3075,7 @@ func (x *CreateConnectMyComputerRoleRequest) String() string { func (*CreateConnectMyComputerRoleRequest) ProtoMessage() {} func (x *CreateConnectMyComputerRoleRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[51] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3008,7 +3088,7 @@ func (x *CreateConnectMyComputerRoleRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreateConnectMyComputerRoleRequest.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerRoleRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{51} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{53} } func (x *CreateConnectMyComputerRoleRequest) GetRootClusterUri() string { @@ -3029,7 +3109,7 @@ type CreateConnectMyComputerRoleResponse struct { func (x *CreateConnectMyComputerRoleResponse) Reset() { *x = CreateConnectMyComputerRoleResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3041,7 +3121,7 @@ func (x *CreateConnectMyComputerRoleResponse) String() string { func (*CreateConnectMyComputerRoleResponse) ProtoMessage() {} func (x *CreateConnectMyComputerRoleResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[52] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3054,7 +3134,7 @@ func (x *CreateConnectMyComputerRoleResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use CreateConnectMyComputerRoleResponse.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerRoleResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{52} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{54} } func (x *CreateConnectMyComputerRoleResponse) GetCertsReloaded() bool { @@ -3073,7 +3153,7 @@ type CreateConnectMyComputerNodeTokenRequest struct { func (x *CreateConnectMyComputerNodeTokenRequest) Reset() { *x = CreateConnectMyComputerNodeTokenRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3085,7 +3165,7 @@ func (x *CreateConnectMyComputerNodeTokenRequest) String() string { func (*CreateConnectMyComputerNodeTokenRequest) ProtoMessage() {} func (x *CreateConnectMyComputerNodeTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[53] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3098,7 +3178,7 @@ func (x *CreateConnectMyComputerNodeTokenRequest) ProtoReflect() protoreflect.Me // Deprecated: Use CreateConnectMyComputerNodeTokenRequest.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerNodeTokenRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{53} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{55} } func (x *CreateConnectMyComputerNodeTokenRequest) GetRootClusterUri() string { @@ -3117,7 +3197,7 @@ type CreateConnectMyComputerNodeTokenResponse struct { func (x *CreateConnectMyComputerNodeTokenResponse) Reset() { *x = CreateConnectMyComputerNodeTokenResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3129,7 +3209,7 @@ func (x *CreateConnectMyComputerNodeTokenResponse) String() string { func (*CreateConnectMyComputerNodeTokenResponse) ProtoMessage() {} func (x *CreateConnectMyComputerNodeTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[54] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3142,7 +3222,7 @@ func (x *CreateConnectMyComputerNodeTokenResponse) ProtoReflect() protoreflect.M // Deprecated: Use CreateConnectMyComputerNodeTokenResponse.ProtoReflect.Descriptor instead. func (*CreateConnectMyComputerNodeTokenResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{54} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{56} } func (x *CreateConnectMyComputerNodeTokenResponse) GetToken() string { @@ -3161,7 +3241,7 @@ type WaitForConnectMyComputerNodeJoinRequest struct { func (x *WaitForConnectMyComputerNodeJoinRequest) Reset() { *x = WaitForConnectMyComputerNodeJoinRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3173,7 +3253,7 @@ func (x *WaitForConnectMyComputerNodeJoinRequest) String() string { func (*WaitForConnectMyComputerNodeJoinRequest) ProtoMessage() {} func (x *WaitForConnectMyComputerNodeJoinRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[55] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3186,7 +3266,7 @@ func (x *WaitForConnectMyComputerNodeJoinRequest) ProtoReflect() protoreflect.Me // Deprecated: Use WaitForConnectMyComputerNodeJoinRequest.ProtoReflect.Descriptor instead. func (*WaitForConnectMyComputerNodeJoinRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{55} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{57} } func (x *WaitForConnectMyComputerNodeJoinRequest) GetRootClusterUri() string { @@ -3205,7 +3285,7 @@ type WaitForConnectMyComputerNodeJoinResponse struct { func (x *WaitForConnectMyComputerNodeJoinResponse) Reset() { *x = WaitForConnectMyComputerNodeJoinResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3217,7 +3297,7 @@ func (x *WaitForConnectMyComputerNodeJoinResponse) String() string { func (*WaitForConnectMyComputerNodeJoinResponse) ProtoMessage() {} func (x *WaitForConnectMyComputerNodeJoinResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[56] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3230,7 +3310,7 @@ func (x *WaitForConnectMyComputerNodeJoinResponse) ProtoReflect() protoreflect.M // Deprecated: Use WaitForConnectMyComputerNodeJoinResponse.ProtoReflect.Descriptor instead. func (*WaitForConnectMyComputerNodeJoinResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{56} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{58} } func (x *WaitForConnectMyComputerNodeJoinResponse) GetServer() *Server { @@ -3249,7 +3329,7 @@ type DeleteConnectMyComputerNodeRequest struct { func (x *DeleteConnectMyComputerNodeRequest) Reset() { *x = DeleteConnectMyComputerNodeRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3261,7 +3341,7 @@ func (x *DeleteConnectMyComputerNodeRequest) String() string { func (*DeleteConnectMyComputerNodeRequest) ProtoMessage() {} func (x *DeleteConnectMyComputerNodeRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[57] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3274,7 +3354,7 @@ func (x *DeleteConnectMyComputerNodeRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteConnectMyComputerNodeRequest.ProtoReflect.Descriptor instead. func (*DeleteConnectMyComputerNodeRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{57} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{59} } func (x *DeleteConnectMyComputerNodeRequest) GetRootClusterUri() string { @@ -3292,7 +3372,7 @@ type DeleteConnectMyComputerNodeResponse struct { func (x *DeleteConnectMyComputerNodeResponse) Reset() { *x = DeleteConnectMyComputerNodeResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3304,7 +3384,7 @@ func (x *DeleteConnectMyComputerNodeResponse) String() string { func (*DeleteConnectMyComputerNodeResponse) ProtoMessage() {} func (x *DeleteConnectMyComputerNodeResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[58] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3317,7 +3397,7 @@ func (x *DeleteConnectMyComputerNodeResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use DeleteConnectMyComputerNodeResponse.ProtoReflect.Descriptor instead. func (*DeleteConnectMyComputerNodeResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{58} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{60} } type GetConnectMyComputerNodeNameRequest struct { @@ -3329,7 +3409,7 @@ type GetConnectMyComputerNodeNameRequest struct { func (x *GetConnectMyComputerNodeNameRequest) Reset() { *x = GetConnectMyComputerNodeNameRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3341,7 +3421,7 @@ func (x *GetConnectMyComputerNodeNameRequest) String() string { func (*GetConnectMyComputerNodeNameRequest) ProtoMessage() {} func (x *GetConnectMyComputerNodeNameRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[59] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3354,7 +3434,7 @@ func (x *GetConnectMyComputerNodeNameRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use GetConnectMyComputerNodeNameRequest.ProtoReflect.Descriptor instead. func (*GetConnectMyComputerNodeNameRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{59} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{61} } func (x *GetConnectMyComputerNodeNameRequest) GetRootClusterUri() string { @@ -3373,7 +3453,7 @@ type GetConnectMyComputerNodeNameResponse struct { func (x *GetConnectMyComputerNodeNameResponse) Reset() { *x = GetConnectMyComputerNodeNameResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3385,7 +3465,7 @@ func (x *GetConnectMyComputerNodeNameResponse) String() string { func (*GetConnectMyComputerNodeNameResponse) ProtoMessage() {} func (x *GetConnectMyComputerNodeNameResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[60] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3398,7 +3478,7 @@ func (x *GetConnectMyComputerNodeNameResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use GetConnectMyComputerNodeNameResponse.ProtoReflect.Descriptor instead. func (*GetConnectMyComputerNodeNameResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{60} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{62} } func (x *GetConnectMyComputerNodeNameResponse) GetName() string { @@ -3438,7 +3518,7 @@ type ListUnifiedResourcesRequest struct { func (x *ListUnifiedResourcesRequest) Reset() { *x = ListUnifiedResourcesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3450,7 +3530,7 @@ func (x *ListUnifiedResourcesRequest) String() string { func (*ListUnifiedResourcesRequest) ProtoMessage() {} func (x *ListUnifiedResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[61] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3463,7 +3543,7 @@ func (x *ListUnifiedResourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnifiedResourcesRequest.ProtoReflect.Descriptor instead. func (*ListUnifiedResourcesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{61} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{63} } func (x *ListUnifiedResourcesRequest) GetClusterUri() string { @@ -3548,7 +3628,7 @@ type SortBy struct { func (x *SortBy) Reset() { *x = SortBy{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3560,7 +3640,7 @@ func (x *SortBy) String() string { func (*SortBy) ProtoMessage() {} func (x *SortBy) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[62] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3573,7 +3653,7 @@ func (x *SortBy) ProtoReflect() protoreflect.Message { // Deprecated: Use SortBy.ProtoReflect.Descriptor instead. func (*SortBy) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{62} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{64} } func (x *SortBy) GetIsDesc() bool { @@ -3602,7 +3682,7 @@ type ListUnifiedResourcesResponse struct { func (x *ListUnifiedResourcesResponse) Reset() { *x = ListUnifiedResourcesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3614,7 +3694,7 @@ func (x *ListUnifiedResourcesResponse) String() string { func (*ListUnifiedResourcesResponse) ProtoMessage() {} func (x *ListUnifiedResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[63] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3627,7 +3707,7 @@ func (x *ListUnifiedResourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnifiedResourcesResponse.ProtoReflect.Descriptor instead. func (*ListUnifiedResourcesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{63} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{65} } func (x *ListUnifiedResourcesResponse) GetResources() []*PaginatedResource { @@ -3661,7 +3741,7 @@ type PaginatedResource struct { func (x *PaginatedResource) Reset() { *x = PaginatedResource{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3673,7 +3753,7 @@ func (x *PaginatedResource) String() string { func (*PaginatedResource) ProtoMessage() {} func (x *PaginatedResource) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[64] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3686,7 +3766,7 @@ func (x *PaginatedResource) ProtoReflect() protoreflect.Message { // Deprecated: Use PaginatedResource.ProtoReflect.Descriptor instead. func (*PaginatedResource) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{64} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{66} } func (x *PaginatedResource) GetResource() isPaginatedResource_Resource { @@ -3791,7 +3871,7 @@ type GetUserPreferencesRequest struct { func (x *GetUserPreferencesRequest) Reset() { *x = GetUserPreferencesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3803,7 +3883,7 @@ func (x *GetUserPreferencesRequest) String() string { func (*GetUserPreferencesRequest) ProtoMessage() {} func (x *GetUserPreferencesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[65] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3816,7 +3896,7 @@ func (x *GetUserPreferencesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserPreferencesRequest.ProtoReflect.Descriptor instead. func (*GetUserPreferencesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{65} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{67} } func (x *GetUserPreferencesRequest) GetClusterUri() string { @@ -3835,7 +3915,7 @@ type GetUserPreferencesResponse struct { func (x *GetUserPreferencesResponse) Reset() { *x = GetUserPreferencesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3847,7 +3927,7 @@ func (x *GetUserPreferencesResponse) String() string { func (*GetUserPreferencesResponse) ProtoMessage() {} func (x *GetUserPreferencesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[66] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3860,7 +3940,7 @@ func (x *GetUserPreferencesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserPreferencesResponse.ProtoReflect.Descriptor instead. func (*GetUserPreferencesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{66} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{68} } func (x *GetUserPreferencesResponse) GetUserPreferences() *UserPreferences { @@ -3880,7 +3960,7 @@ type UpdateUserPreferencesRequest struct { func (x *UpdateUserPreferencesRequest) Reset() { *x = UpdateUserPreferencesRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3892,7 +3972,7 @@ func (x *UpdateUserPreferencesRequest) String() string { func (*UpdateUserPreferencesRequest) ProtoMessage() {} func (x *UpdateUserPreferencesRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[67] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3905,7 +3985,7 @@ func (x *UpdateUserPreferencesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserPreferencesRequest.ProtoReflect.Descriptor instead. func (*UpdateUserPreferencesRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{67} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{69} } func (x *UpdateUserPreferencesRequest) GetClusterUri() string { @@ -3931,7 +4011,7 @@ type UpdateUserPreferencesResponse struct { func (x *UpdateUserPreferencesResponse) Reset() { *x = UpdateUserPreferencesResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3943,7 +4023,7 @@ func (x *UpdateUserPreferencesResponse) String() string { func (*UpdateUserPreferencesResponse) ProtoMessage() {} func (x *UpdateUserPreferencesResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[68] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3956,7 +4036,7 @@ func (x *UpdateUserPreferencesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserPreferencesResponse.ProtoReflect.Descriptor instead. func (*UpdateUserPreferencesResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{68} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{70} } func (x *UpdateUserPreferencesResponse) GetUserPreferences() *UserPreferences { @@ -3978,7 +4058,7 @@ type UserPreferences struct { func (x *UserPreferences) Reset() { *x = UserPreferences{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3990,7 +4070,7 @@ func (x *UserPreferences) String() string { func (*UserPreferences) ProtoMessage() {} func (x *UserPreferences) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[69] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4003,7 +4083,7 @@ func (x *UserPreferences) ProtoReflect() protoreflect.Message { // Deprecated: Use UserPreferences.ProtoReflect.Descriptor instead. func (*UserPreferences) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{69} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{71} } func (x *UserPreferences) GetClusterPreferences() *v11.ClusterUserPreferences { @@ -4033,7 +4113,7 @@ type AuthenticateWebDeviceRequest struct { func (x *AuthenticateWebDeviceRequest) Reset() { *x = AuthenticateWebDeviceRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4045,7 +4125,7 @@ func (x *AuthenticateWebDeviceRequest) String() string { func (*AuthenticateWebDeviceRequest) ProtoMessage() {} func (x *AuthenticateWebDeviceRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[70] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4058,7 +4138,7 @@ func (x *AuthenticateWebDeviceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthenticateWebDeviceRequest.ProtoReflect.Descriptor instead. func (*AuthenticateWebDeviceRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{70} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{72} } func (x *AuthenticateWebDeviceRequest) GetDeviceWebToken() *v12.DeviceWebToken { @@ -4087,7 +4167,7 @@ type AuthenticateWebDeviceResponse struct { func (x *AuthenticateWebDeviceResponse) Reset() { *x = AuthenticateWebDeviceResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4099,7 +4179,7 @@ func (x *AuthenticateWebDeviceResponse) String() string { func (*AuthenticateWebDeviceResponse) ProtoMessage() {} func (x *AuthenticateWebDeviceResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[71] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4112,7 +4192,7 @@ func (x *AuthenticateWebDeviceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthenticateWebDeviceResponse.ProtoReflect.Descriptor instead. func (*AuthenticateWebDeviceResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{71} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{73} } func (x *AuthenticateWebDeviceResponse) GetConfirmationToken() *v12.DeviceConfirmationToken { @@ -4131,7 +4211,7 @@ type GetAppRequest struct { func (x *GetAppRequest) Reset() { *x = GetAppRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4143,7 +4223,7 @@ func (x *GetAppRequest) String() string { func (*GetAppRequest) ProtoMessage() {} func (x *GetAppRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[72] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4156,7 +4236,7 @@ func (x *GetAppRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAppRequest.ProtoReflect.Descriptor instead. func (*GetAppRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{72} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{74} } func (x *GetAppRequest) GetAppUri() string { @@ -4175,7 +4255,7 @@ type GetAppResponse struct { func (x *GetAppResponse) Reset() { *x = GetAppResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4187,7 +4267,7 @@ func (x *GetAppResponse) String() string { func (*GetAppResponse) ProtoMessage() {} func (x *GetAppResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[73] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4200,7 +4280,7 @@ func (x *GetAppResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAppResponse.ProtoReflect.Descriptor instead. func (*GetAppResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{73} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{75} } func (x *GetAppResponse) GetApp() *App { @@ -4223,7 +4303,7 @@ type TargetDesktop struct { func (x *TargetDesktop) Reset() { *x = TargetDesktop{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4235,7 +4315,7 @@ func (x *TargetDesktop) String() string { func (*TargetDesktop) ProtoMessage() {} func (x *TargetDesktop) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[74] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4248,7 +4328,7 @@ func (x *TargetDesktop) ProtoReflect() protoreflect.Message { // Deprecated: Use TargetDesktop.ProtoReflect.Descriptor instead. func (*TargetDesktop) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{74} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{76} } func (x *TargetDesktop) GetDesktopUri() string { @@ -4279,7 +4359,7 @@ type ConnectToDesktopRequest struct { func (x *ConnectToDesktopRequest) Reset() { *x = ConnectToDesktopRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4291,7 +4371,7 @@ func (x *ConnectToDesktopRequest) String() string { func (*ConnectToDesktopRequest) ProtoMessage() {} func (x *ConnectToDesktopRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[75] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4304,7 +4384,7 @@ func (x *ConnectToDesktopRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectToDesktopRequest.ProtoReflect.Descriptor instead. func (*ConnectToDesktopRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{75} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{77} } func (x *ConnectToDesktopRequest) GetData() []byte { @@ -4332,7 +4412,7 @@ type ConnectToDesktopResponse struct { func (x *ConnectToDesktopResponse) Reset() { *x = ConnectToDesktopResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4344,7 +4424,7 @@ func (x *ConnectToDesktopResponse) String() string { func (*ConnectToDesktopResponse) ProtoMessage() {} func (x *ConnectToDesktopResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[76] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4357,7 +4437,7 @@ func (x *ConnectToDesktopResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectToDesktopResponse.ProtoReflect.Descriptor instead. func (*ConnectToDesktopResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{76} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{78} } func (x *ConnectToDesktopResponse) GetData() []byte { @@ -4382,7 +4462,7 @@ type SetSharedDirectoryForDesktopSessionRequest struct { func (x *SetSharedDirectoryForDesktopSessionRequest) Reset() { *x = SetSharedDirectoryForDesktopSessionRequest{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4394,7 +4474,7 @@ func (x *SetSharedDirectoryForDesktopSessionRequest) String() string { func (*SetSharedDirectoryForDesktopSessionRequest) ProtoMessage() {} func (x *SetSharedDirectoryForDesktopSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[77] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4407,7 +4487,7 @@ func (x *SetSharedDirectoryForDesktopSessionRequest) ProtoReflect() protoreflect // Deprecated: Use SetSharedDirectoryForDesktopSessionRequest.ProtoReflect.Descriptor instead. func (*SetSharedDirectoryForDesktopSessionRequest) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{77} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{79} } func (x *SetSharedDirectoryForDesktopSessionRequest) GetDesktopUri() string { @@ -4440,7 +4520,7 @@ type SetSharedDirectoryForDesktopSessionResponse struct { func (x *SetSharedDirectoryForDesktopSessionResponse) Reset() { *x = SetSharedDirectoryForDesktopSessionResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4452,7 +4532,7 @@ func (x *SetSharedDirectoryForDesktopSessionResponse) String() string { func (*SetSharedDirectoryForDesktopSessionResponse) ProtoMessage() {} func (x *SetSharedDirectoryForDesktopSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[78] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4465,7 +4545,7 @@ func (x *SetSharedDirectoryForDesktopSessionResponse) ProtoReflect() protoreflec // Deprecated: Use SetSharedDirectoryForDesktopSessionResponse.ProtoReflect.Descriptor instead. func (*SetSharedDirectoryForDesktopSessionResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{78} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{80} } // LoginPasswordlessRequestInit contains fields needed to init the stream request. @@ -4479,7 +4559,7 @@ type LoginPasswordlessRequest_LoginPasswordlessRequestInit struct { func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) Reset() { *x = LoginPasswordlessRequest_LoginPasswordlessRequestInit{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4491,7 +4571,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) String() string func (*LoginPasswordlessRequest_LoginPasswordlessRequestInit) ProtoMessage() {} func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[79] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4504,7 +4584,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) ProtoReflect() p // Deprecated: Use LoginPasswordlessRequest_LoginPasswordlessRequestInit.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest_LoginPasswordlessRequestInit) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27, 0} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29, 0} } func (x *LoginPasswordlessRequest_LoginPasswordlessRequestInit) GetClusterUri() string { @@ -4525,7 +4605,7 @@ type LoginPasswordlessRequest_LoginPasswordlessPINResponse struct { func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) Reset() { *x = LoginPasswordlessRequest_LoginPasswordlessPINResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4537,7 +4617,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) String() string func (*LoginPasswordlessRequest_LoginPasswordlessPINResponse) ProtoMessage() {} func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[80] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4550,7 +4630,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) ProtoReflect() p // Deprecated: Use LoginPasswordlessRequest_LoginPasswordlessPINResponse.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest_LoginPasswordlessPINResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27, 1} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29, 1} } func (x *LoginPasswordlessRequest_LoginPasswordlessPINResponse) GetPin() string { @@ -4573,7 +4653,7 @@ type LoginPasswordlessRequest_LoginPasswordlessCredentialResponse struct { func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) Reset() { *x = LoginPasswordlessRequest_LoginPasswordlessCredentialResponse{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4585,7 +4665,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) String() func (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) ProtoMessage() {} func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[81] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4598,7 +4678,7 @@ func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) ProtoRefl // Deprecated: Use LoginPasswordlessRequest_LoginPasswordlessCredentialResponse.ProtoReflect.Descriptor instead. func (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{27, 2} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{29, 2} } func (x *LoginPasswordlessRequest_LoginPasswordlessCredentialResponse) GetIndex() int64 { @@ -4623,7 +4703,7 @@ type LoginRequest_LocalParams struct { func (x *LoginRequest_LocalParams) Reset() { *x = LoginRequest_LocalParams{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4635,7 +4715,7 @@ func (x *LoginRequest_LocalParams) String() string { func (*LoginRequest_LocalParams) ProtoMessage() {} func (x *LoginRequest_LocalParams) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[82] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4648,7 +4728,7 @@ func (x *LoginRequest_LocalParams) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest_LocalParams.ProtoReflect.Descriptor instead. func (*LoginRequest_LocalParams) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30, 0} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{32, 0} } func (x *LoginRequest_LocalParams) GetUser() string { @@ -4685,7 +4765,7 @@ type LoginRequest_SsoParams struct { func (x *LoginRequest_SsoParams) Reset() { *x = LoginRequest_SsoParams{} - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4697,7 +4777,7 @@ func (x *LoginRequest_SsoParams) String() string { func (*LoginRequest_SsoParams) ProtoMessage() {} func (x *LoginRequest_SsoParams) ProtoReflect() protoreflect.Message { - mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[83] + mi := &file_teleport_lib_teleterm_v1_service_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4710,7 +4790,7 @@ func (x *LoginRequest_SsoParams) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest_SsoParams.ProtoReflect.Descriptor instead. func (*LoginRequest_SsoParams) Descriptor() ([]byte, []int) { - return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{30, 1} + return file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP(), []int{32, 1} } func (x *LoginRequest_SsoParams) GetProviderType() string { @@ -4739,7 +4819,10 @@ const file_teleport_lib_teleterm_v1_service_proto_rawDesc = "" + "\rLogoutRequest\x12\x1f\n" + "\vcluster_uri\x18\x01 \x01(\tR\n" + "clusterUri\x12%\n" + - "\x0eremove_profile\x18\x02 \x01(\bR\rremoveProfile\"G\n" + + "\x0eremove_profile\x18\x02 \x01(\bR\rremoveProfile\"K\n" + + "\x1fClearStaleClusterClientsRequest\x12(\n" + + "\x10root_cluster_uri\x18\x01 \x01(\tR\x0erootClusterUri\"\"\n" + + " ClearStaleClusterClientsResponse\"G\n" + "\x1bStartHeadlessWatcherRequest\x12(\n" + "\x10root_cluster_uri\x18\x01 \x01(\tR\x0erootClusterUri\"\x1e\n" + "\x1cStartHeadlessWatcherResponse\"f\n" + @@ -5025,7 +5108,7 @@ const file_teleport_lib_teleterm_v1_service_proto_rawDesc = "" + ")HEADLESS_AUTHENTICATION_STATE_UNSPECIFIED\x10\x00\x12)\n" + "%HEADLESS_AUTHENTICATION_STATE_PENDING\x10\x01\x12(\n" + "$HEADLESS_AUTHENTICATION_STATE_DENIED\x10\x02\x12*\n" + - "&HEADLESS_AUTHENTICATION_STATE_APPROVED\x10\x032\xca+\n" + + "&HEADLESS_AUTHENTICATION_STATE_APPROVED\x10\x032\xde,\n" + "\x0fTerminalService\x12\xa0\x01\n" + "\x1dUpdateTshdEventsServerAddress\x12>.teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest\x1a?.teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse\x12q\n" + "\x10ListRootClusters\x12-.teleport.lib.teleterm.v1.ListClustersRequest\x1a..teleport.lib.teleterm.v1.ListClustersResponse\x12u\n" + @@ -5057,7 +5140,8 @@ const file_teleport_lib_teleterm_v1_service_proto_rawDesc = "" + "GetCluster\x12+.teleport.lib.teleterm.v1.GetClusterRequest\x1a!.teleport.lib.teleterm.v1.Cluster\x12X\n" + "\x05Login\x12&.teleport.lib.teleterm.v1.LoginRequest\x1a'.teleport.lib.teleterm.v1.EmptyResponse\x12\x80\x01\n" + "\x11LoginPasswordless\x122.teleport.lib.teleterm.v1.LoginPasswordlessRequest\x1a3.teleport.lib.teleterm.v1.LoginPasswordlessResponse(\x010\x01\x12Z\n" + - "\x06Logout\x12'.teleport.lib.teleterm.v1.LogoutRequest\x1a'.teleport.lib.teleterm.v1.EmptyResponse\x12o\n" + + "\x06Logout\x12'.teleport.lib.teleterm.v1.LogoutRequest\x1a'.teleport.lib.teleterm.v1.EmptyResponse\x12\x91\x01\n" + + "\x18ClearStaleClusterClients\x129.teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest\x1a:.teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse\x12o\n" + "\fTransferFile\x12-.teleport.lib.teleterm.v1.FileTransferRequest\x1a..teleport.lib.teleterm.v1.FileTransferProgress0\x01\x12n\n" + "\x10ReportUsageEvent\x121.teleport.lib.teleterm.v1.ReportUsageEventRequest\x1a'.teleport.lib.teleterm.v1.EmptyResponse\x12\xac\x01\n" + "!UpdateHeadlessAuthenticationState\x12B.teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest\x1aC.teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse\x12\x9a\x01\n" + @@ -5087,7 +5171,7 @@ func file_teleport_lib_teleterm_v1_service_proto_rawDescGZIP() []byte { } var file_teleport_lib_teleterm_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_teleport_lib_teleterm_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 84) +var file_teleport_lib_teleterm_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 86) var file_teleport_lib_teleterm_v1_service_proto_goTypes = []any{ (PasswordlessPrompt)(0), // 0: teleport.lib.teleterm.v1.PasswordlessPrompt (FileTransferDirection)(0), // 1: teleport.lib.teleterm.v1.FileTransferDirection @@ -5095,242 +5179,246 @@ var file_teleport_lib_teleterm_v1_service_proto_goTypes = []any{ (*EmptyResponse)(nil), // 3: teleport.lib.teleterm.v1.EmptyResponse (*GetClusterRequest)(nil), // 4: teleport.lib.teleterm.v1.GetClusterRequest (*LogoutRequest)(nil), // 5: teleport.lib.teleterm.v1.LogoutRequest - (*StartHeadlessWatcherRequest)(nil), // 6: teleport.lib.teleterm.v1.StartHeadlessWatcherRequest - (*StartHeadlessWatcherResponse)(nil), // 7: teleport.lib.teleterm.v1.StartHeadlessWatcherResponse - (*GetAccessRequestRequest)(nil), // 8: teleport.lib.teleterm.v1.GetAccessRequestRequest - (*GetAccessRequestsRequest)(nil), // 9: teleport.lib.teleterm.v1.GetAccessRequestsRequest - (*GetAccessRequestResponse)(nil), // 10: teleport.lib.teleterm.v1.GetAccessRequestResponse - (*GetAccessRequestsResponse)(nil), // 11: teleport.lib.teleterm.v1.GetAccessRequestsResponse - (*DeleteAccessRequestRequest)(nil), // 12: teleport.lib.teleterm.v1.DeleteAccessRequestRequest - (*CreateAccessRequestRequest)(nil), // 13: teleport.lib.teleterm.v1.CreateAccessRequestRequest - (*CreateAccessRequestResponse)(nil), // 14: teleport.lib.teleterm.v1.CreateAccessRequestResponse - (*AssumeRoleRequest)(nil), // 15: teleport.lib.teleterm.v1.AssumeRoleRequest - (*GetRequestableRolesRequest)(nil), // 16: teleport.lib.teleterm.v1.GetRequestableRolesRequest - (*GetRequestableRolesResponse)(nil), // 17: teleport.lib.teleterm.v1.GetRequestableRolesResponse - (*ReviewAccessRequestRequest)(nil), // 18: teleport.lib.teleterm.v1.ReviewAccessRequestRequest - (*ReviewAccessRequestResponse)(nil), // 19: teleport.lib.teleterm.v1.ReviewAccessRequestResponse - (*PromoteAccessRequestRequest)(nil), // 20: teleport.lib.teleterm.v1.PromoteAccessRequestRequest - (*PromoteAccessRequestResponse)(nil), // 21: teleport.lib.teleterm.v1.PromoteAccessRequestResponse - (*GetSuggestedAccessListsRequest)(nil), // 22: teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest - (*GetSuggestedAccessListsResponse)(nil), // 23: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse - (*ListKubernetesResourcesRequest)(nil), // 24: teleport.lib.teleterm.v1.ListKubernetesResourcesRequest - (*ListKubernetesResourcesResponse)(nil), // 25: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse - (*ListKubernetesServersRequest)(nil), // 26: teleport.lib.teleterm.v1.ListKubernetesServersRequest - (*ListKubernetesServersResponse)(nil), // 27: teleport.lib.teleterm.v1.ListKubernetesServersResponse - (*CredentialInfo)(nil), // 28: teleport.lib.teleterm.v1.CredentialInfo - (*LoginPasswordlessResponse)(nil), // 29: teleport.lib.teleterm.v1.LoginPasswordlessResponse - (*LoginPasswordlessRequest)(nil), // 30: teleport.lib.teleterm.v1.LoginPasswordlessRequest - (*FileTransferRequest)(nil), // 31: teleport.lib.teleterm.v1.FileTransferRequest - (*FileTransferProgress)(nil), // 32: teleport.lib.teleterm.v1.FileTransferProgress - (*LoginRequest)(nil), // 33: teleport.lib.teleterm.v1.LoginRequest - (*AddClusterRequest)(nil), // 34: teleport.lib.teleterm.v1.AddClusterRequest - (*ListClustersRequest)(nil), // 35: teleport.lib.teleterm.v1.ListClustersRequest - (*ListClustersResponse)(nil), // 36: teleport.lib.teleterm.v1.ListClustersResponse - (*ListLeafClustersRequest)(nil), // 37: teleport.lib.teleterm.v1.ListLeafClustersRequest - (*ListDatabaseUsersRequest)(nil), // 38: teleport.lib.teleterm.v1.ListDatabaseUsersRequest - (*ListDatabaseUsersResponse)(nil), // 39: teleport.lib.teleterm.v1.ListDatabaseUsersResponse - (*ListResourcesParams)(nil), // 40: teleport.lib.teleterm.v1.ListResourcesParams - (*ListDatabaseServersRequest)(nil), // 41: teleport.lib.teleterm.v1.ListDatabaseServersRequest - (*ListDatabaseServersResponse)(nil), // 42: teleport.lib.teleterm.v1.ListDatabaseServersResponse - (*CreateGatewayRequest)(nil), // 43: teleport.lib.teleterm.v1.CreateGatewayRequest - (*ListGatewaysRequest)(nil), // 44: teleport.lib.teleterm.v1.ListGatewaysRequest - (*ListGatewaysResponse)(nil), // 45: teleport.lib.teleterm.v1.ListGatewaysResponse - (*RemoveGatewayRequest)(nil), // 46: teleport.lib.teleterm.v1.RemoveGatewayRequest - (*SetGatewayTargetSubresourceNameRequest)(nil), // 47: teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest - (*SetGatewayLocalPortRequest)(nil), // 48: teleport.lib.teleterm.v1.SetGatewayLocalPortRequest - (*GetAuthSettingsRequest)(nil), // 49: teleport.lib.teleterm.v1.GetAuthSettingsRequest - (*UpdateTshdEventsServerAddressRequest)(nil), // 50: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest - (*UpdateTshdEventsServerAddressResponse)(nil), // 51: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse - (*UpdateHeadlessAuthenticationStateRequest)(nil), // 52: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest - (*UpdateHeadlessAuthenticationStateResponse)(nil), // 53: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse - (*CreateConnectMyComputerRoleRequest)(nil), // 54: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest - (*CreateConnectMyComputerRoleResponse)(nil), // 55: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse - (*CreateConnectMyComputerNodeTokenRequest)(nil), // 56: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest - (*CreateConnectMyComputerNodeTokenResponse)(nil), // 57: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse - (*WaitForConnectMyComputerNodeJoinRequest)(nil), // 58: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest - (*WaitForConnectMyComputerNodeJoinResponse)(nil), // 59: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse - (*DeleteConnectMyComputerNodeRequest)(nil), // 60: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest - (*DeleteConnectMyComputerNodeResponse)(nil), // 61: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse - (*GetConnectMyComputerNodeNameRequest)(nil), // 62: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest - (*GetConnectMyComputerNodeNameResponse)(nil), // 63: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse - (*ListUnifiedResourcesRequest)(nil), // 64: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest - (*SortBy)(nil), // 65: teleport.lib.teleterm.v1.SortBy - (*ListUnifiedResourcesResponse)(nil), // 66: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse - (*PaginatedResource)(nil), // 67: teleport.lib.teleterm.v1.PaginatedResource - (*GetUserPreferencesRequest)(nil), // 68: teleport.lib.teleterm.v1.GetUserPreferencesRequest - (*GetUserPreferencesResponse)(nil), // 69: teleport.lib.teleterm.v1.GetUserPreferencesResponse - (*UpdateUserPreferencesRequest)(nil), // 70: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest - (*UpdateUserPreferencesResponse)(nil), // 71: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse - (*UserPreferences)(nil), // 72: teleport.lib.teleterm.v1.UserPreferences - (*AuthenticateWebDeviceRequest)(nil), // 73: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest - (*AuthenticateWebDeviceResponse)(nil), // 74: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse - (*GetAppRequest)(nil), // 75: teleport.lib.teleterm.v1.GetAppRequest - (*GetAppResponse)(nil), // 76: teleport.lib.teleterm.v1.GetAppResponse - (*TargetDesktop)(nil), // 77: teleport.lib.teleterm.v1.TargetDesktop - (*ConnectToDesktopRequest)(nil), // 78: teleport.lib.teleterm.v1.ConnectToDesktopRequest - (*ConnectToDesktopResponse)(nil), // 79: teleport.lib.teleterm.v1.ConnectToDesktopResponse - (*SetSharedDirectoryForDesktopSessionRequest)(nil), // 80: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest - (*SetSharedDirectoryForDesktopSessionResponse)(nil), // 81: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse - (*LoginPasswordlessRequest_LoginPasswordlessRequestInit)(nil), // 82: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit - (*LoginPasswordlessRequest_LoginPasswordlessPINResponse)(nil), // 83: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse - (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse)(nil), // 84: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse - (*LoginRequest_LocalParams)(nil), // 85: teleport.lib.teleterm.v1.LoginRequest.LocalParams - (*LoginRequest_SsoParams)(nil), // 86: teleport.lib.teleterm.v1.LoginRequest.SsoParams - (*AccessRequest)(nil), // 87: teleport.lib.teleterm.v1.AccessRequest - (*ResourceID)(nil), // 88: teleport.lib.teleterm.v1.ResourceID - (*timestamppb.Timestamp)(nil), // 89: google.protobuf.Timestamp - (*v1.AccessList)(nil), // 90: teleport.accesslist.v1.AccessList - (*KubeResource)(nil), // 91: teleport.lib.teleterm.v1.KubeResource - (*KubeServer)(nil), // 92: teleport.lib.teleterm.v1.KubeServer - (*Cluster)(nil), // 93: teleport.lib.teleterm.v1.Cluster - (*DatabaseServer)(nil), // 94: teleport.lib.teleterm.v1.DatabaseServer - (*Gateway)(nil), // 95: teleport.lib.teleterm.v1.Gateway - (*Server)(nil), // 96: teleport.lib.teleterm.v1.Server - (*Database)(nil), // 97: teleport.lib.teleterm.v1.Database - (*Kube)(nil), // 98: teleport.lib.teleterm.v1.Kube - (*App)(nil), // 99: teleport.lib.teleterm.v1.App - (*WindowsDesktop)(nil), // 100: teleport.lib.teleterm.v1.WindowsDesktop - (*v11.ClusterUserPreferences)(nil), // 101: teleport.userpreferences.v1.ClusterUserPreferences - (*v11.UnifiedResourcePreferences)(nil), // 102: teleport.userpreferences.v1.UnifiedResourcePreferences - (*v12.DeviceWebToken)(nil), // 103: teleport.devicetrust.v1.DeviceWebToken - (*v12.DeviceConfirmationToken)(nil), // 104: teleport.devicetrust.v1.DeviceConfirmationToken - (*ReportUsageEventRequest)(nil), // 105: teleport.lib.teleterm.v1.ReportUsageEventRequest - (*AuthSettings)(nil), // 106: teleport.lib.teleterm.v1.AuthSettings + (*ClearStaleClusterClientsRequest)(nil), // 6: teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest + (*ClearStaleClusterClientsResponse)(nil), // 7: teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse + (*StartHeadlessWatcherRequest)(nil), // 8: teleport.lib.teleterm.v1.StartHeadlessWatcherRequest + (*StartHeadlessWatcherResponse)(nil), // 9: teleport.lib.teleterm.v1.StartHeadlessWatcherResponse + (*GetAccessRequestRequest)(nil), // 10: teleport.lib.teleterm.v1.GetAccessRequestRequest + (*GetAccessRequestsRequest)(nil), // 11: teleport.lib.teleterm.v1.GetAccessRequestsRequest + (*GetAccessRequestResponse)(nil), // 12: teleport.lib.teleterm.v1.GetAccessRequestResponse + (*GetAccessRequestsResponse)(nil), // 13: teleport.lib.teleterm.v1.GetAccessRequestsResponse + (*DeleteAccessRequestRequest)(nil), // 14: teleport.lib.teleterm.v1.DeleteAccessRequestRequest + (*CreateAccessRequestRequest)(nil), // 15: teleport.lib.teleterm.v1.CreateAccessRequestRequest + (*CreateAccessRequestResponse)(nil), // 16: teleport.lib.teleterm.v1.CreateAccessRequestResponse + (*AssumeRoleRequest)(nil), // 17: teleport.lib.teleterm.v1.AssumeRoleRequest + (*GetRequestableRolesRequest)(nil), // 18: teleport.lib.teleterm.v1.GetRequestableRolesRequest + (*GetRequestableRolesResponse)(nil), // 19: teleport.lib.teleterm.v1.GetRequestableRolesResponse + (*ReviewAccessRequestRequest)(nil), // 20: teleport.lib.teleterm.v1.ReviewAccessRequestRequest + (*ReviewAccessRequestResponse)(nil), // 21: teleport.lib.teleterm.v1.ReviewAccessRequestResponse + (*PromoteAccessRequestRequest)(nil), // 22: teleport.lib.teleterm.v1.PromoteAccessRequestRequest + (*PromoteAccessRequestResponse)(nil), // 23: teleport.lib.teleterm.v1.PromoteAccessRequestResponse + (*GetSuggestedAccessListsRequest)(nil), // 24: teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest + (*GetSuggestedAccessListsResponse)(nil), // 25: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse + (*ListKubernetesResourcesRequest)(nil), // 26: teleport.lib.teleterm.v1.ListKubernetesResourcesRequest + (*ListKubernetesResourcesResponse)(nil), // 27: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse + (*ListKubernetesServersRequest)(nil), // 28: teleport.lib.teleterm.v1.ListKubernetesServersRequest + (*ListKubernetesServersResponse)(nil), // 29: teleport.lib.teleterm.v1.ListKubernetesServersResponse + (*CredentialInfo)(nil), // 30: teleport.lib.teleterm.v1.CredentialInfo + (*LoginPasswordlessResponse)(nil), // 31: teleport.lib.teleterm.v1.LoginPasswordlessResponse + (*LoginPasswordlessRequest)(nil), // 32: teleport.lib.teleterm.v1.LoginPasswordlessRequest + (*FileTransferRequest)(nil), // 33: teleport.lib.teleterm.v1.FileTransferRequest + (*FileTransferProgress)(nil), // 34: teleport.lib.teleterm.v1.FileTransferProgress + (*LoginRequest)(nil), // 35: teleport.lib.teleterm.v1.LoginRequest + (*AddClusterRequest)(nil), // 36: teleport.lib.teleterm.v1.AddClusterRequest + (*ListClustersRequest)(nil), // 37: teleport.lib.teleterm.v1.ListClustersRequest + (*ListClustersResponse)(nil), // 38: teleport.lib.teleterm.v1.ListClustersResponse + (*ListLeafClustersRequest)(nil), // 39: teleport.lib.teleterm.v1.ListLeafClustersRequest + (*ListDatabaseUsersRequest)(nil), // 40: teleport.lib.teleterm.v1.ListDatabaseUsersRequest + (*ListDatabaseUsersResponse)(nil), // 41: teleport.lib.teleterm.v1.ListDatabaseUsersResponse + (*ListResourcesParams)(nil), // 42: teleport.lib.teleterm.v1.ListResourcesParams + (*ListDatabaseServersRequest)(nil), // 43: teleport.lib.teleterm.v1.ListDatabaseServersRequest + (*ListDatabaseServersResponse)(nil), // 44: teleport.lib.teleterm.v1.ListDatabaseServersResponse + (*CreateGatewayRequest)(nil), // 45: teleport.lib.teleterm.v1.CreateGatewayRequest + (*ListGatewaysRequest)(nil), // 46: teleport.lib.teleterm.v1.ListGatewaysRequest + (*ListGatewaysResponse)(nil), // 47: teleport.lib.teleterm.v1.ListGatewaysResponse + (*RemoveGatewayRequest)(nil), // 48: teleport.lib.teleterm.v1.RemoveGatewayRequest + (*SetGatewayTargetSubresourceNameRequest)(nil), // 49: teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest + (*SetGatewayLocalPortRequest)(nil), // 50: teleport.lib.teleterm.v1.SetGatewayLocalPortRequest + (*GetAuthSettingsRequest)(nil), // 51: teleport.lib.teleterm.v1.GetAuthSettingsRequest + (*UpdateTshdEventsServerAddressRequest)(nil), // 52: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest + (*UpdateTshdEventsServerAddressResponse)(nil), // 53: teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse + (*UpdateHeadlessAuthenticationStateRequest)(nil), // 54: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest + (*UpdateHeadlessAuthenticationStateResponse)(nil), // 55: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse + (*CreateConnectMyComputerRoleRequest)(nil), // 56: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest + (*CreateConnectMyComputerRoleResponse)(nil), // 57: teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse + (*CreateConnectMyComputerNodeTokenRequest)(nil), // 58: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest + (*CreateConnectMyComputerNodeTokenResponse)(nil), // 59: teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse + (*WaitForConnectMyComputerNodeJoinRequest)(nil), // 60: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest + (*WaitForConnectMyComputerNodeJoinResponse)(nil), // 61: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse + (*DeleteConnectMyComputerNodeRequest)(nil), // 62: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest + (*DeleteConnectMyComputerNodeResponse)(nil), // 63: teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse + (*GetConnectMyComputerNodeNameRequest)(nil), // 64: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest + (*GetConnectMyComputerNodeNameResponse)(nil), // 65: teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse + (*ListUnifiedResourcesRequest)(nil), // 66: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest + (*SortBy)(nil), // 67: teleport.lib.teleterm.v1.SortBy + (*ListUnifiedResourcesResponse)(nil), // 68: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse + (*PaginatedResource)(nil), // 69: teleport.lib.teleterm.v1.PaginatedResource + (*GetUserPreferencesRequest)(nil), // 70: teleport.lib.teleterm.v1.GetUserPreferencesRequest + (*GetUserPreferencesResponse)(nil), // 71: teleport.lib.teleterm.v1.GetUserPreferencesResponse + (*UpdateUserPreferencesRequest)(nil), // 72: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest + (*UpdateUserPreferencesResponse)(nil), // 73: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse + (*UserPreferences)(nil), // 74: teleport.lib.teleterm.v1.UserPreferences + (*AuthenticateWebDeviceRequest)(nil), // 75: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest + (*AuthenticateWebDeviceResponse)(nil), // 76: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse + (*GetAppRequest)(nil), // 77: teleport.lib.teleterm.v1.GetAppRequest + (*GetAppResponse)(nil), // 78: teleport.lib.teleterm.v1.GetAppResponse + (*TargetDesktop)(nil), // 79: teleport.lib.teleterm.v1.TargetDesktop + (*ConnectToDesktopRequest)(nil), // 80: teleport.lib.teleterm.v1.ConnectToDesktopRequest + (*ConnectToDesktopResponse)(nil), // 81: teleport.lib.teleterm.v1.ConnectToDesktopResponse + (*SetSharedDirectoryForDesktopSessionRequest)(nil), // 82: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest + (*SetSharedDirectoryForDesktopSessionResponse)(nil), // 83: teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse + (*LoginPasswordlessRequest_LoginPasswordlessRequestInit)(nil), // 84: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit + (*LoginPasswordlessRequest_LoginPasswordlessPINResponse)(nil), // 85: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse + (*LoginPasswordlessRequest_LoginPasswordlessCredentialResponse)(nil), // 86: teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse + (*LoginRequest_LocalParams)(nil), // 87: teleport.lib.teleterm.v1.LoginRequest.LocalParams + (*LoginRequest_SsoParams)(nil), // 88: teleport.lib.teleterm.v1.LoginRequest.SsoParams + (*AccessRequest)(nil), // 89: teleport.lib.teleterm.v1.AccessRequest + (*ResourceID)(nil), // 90: teleport.lib.teleterm.v1.ResourceID + (*timestamppb.Timestamp)(nil), // 91: google.protobuf.Timestamp + (*v1.AccessList)(nil), // 92: teleport.accesslist.v1.AccessList + (*KubeResource)(nil), // 93: teleport.lib.teleterm.v1.KubeResource + (*KubeServer)(nil), // 94: teleport.lib.teleterm.v1.KubeServer + (*Cluster)(nil), // 95: teleport.lib.teleterm.v1.Cluster + (*DatabaseServer)(nil), // 96: teleport.lib.teleterm.v1.DatabaseServer + (*Gateway)(nil), // 97: teleport.lib.teleterm.v1.Gateway + (*Server)(nil), // 98: teleport.lib.teleterm.v1.Server + (*Database)(nil), // 99: teleport.lib.teleterm.v1.Database + (*Kube)(nil), // 100: teleport.lib.teleterm.v1.Kube + (*App)(nil), // 101: teleport.lib.teleterm.v1.App + (*WindowsDesktop)(nil), // 102: teleport.lib.teleterm.v1.WindowsDesktop + (*v11.ClusterUserPreferences)(nil), // 103: teleport.userpreferences.v1.ClusterUserPreferences + (*v11.UnifiedResourcePreferences)(nil), // 104: teleport.userpreferences.v1.UnifiedResourcePreferences + (*v12.DeviceWebToken)(nil), // 105: teleport.devicetrust.v1.DeviceWebToken + (*v12.DeviceConfirmationToken)(nil), // 106: teleport.devicetrust.v1.DeviceConfirmationToken + (*ReportUsageEventRequest)(nil), // 107: teleport.lib.teleterm.v1.ReportUsageEventRequest + (*AuthSettings)(nil), // 108: teleport.lib.teleterm.v1.AuthSettings } var file_teleport_lib_teleterm_v1_service_proto_depIdxs = []int32{ - 87, // 0: teleport.lib.teleterm.v1.GetAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 87, // 1: teleport.lib.teleterm.v1.GetAccessRequestsResponse.requests:type_name -> teleport.lib.teleterm.v1.AccessRequest - 88, // 2: teleport.lib.teleterm.v1.CreateAccessRequestRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID - 89, // 3: teleport.lib.teleterm.v1.CreateAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp - 89, // 4: teleport.lib.teleterm.v1.CreateAccessRequestRequest.max_duration:type_name -> google.protobuf.Timestamp - 89, // 5: teleport.lib.teleterm.v1.CreateAccessRequestRequest.request_ttl:type_name -> google.protobuf.Timestamp - 87, // 6: teleport.lib.teleterm.v1.CreateAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 88, // 7: teleport.lib.teleterm.v1.GetRequestableRolesRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID - 89, // 8: teleport.lib.teleterm.v1.ReviewAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp - 87, // 9: teleport.lib.teleterm.v1.ReviewAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 87, // 10: teleport.lib.teleterm.v1.PromoteAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest - 90, // 11: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse.access_lists:type_name -> teleport.accesslist.v1.AccessList - 91, // 12: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeResource - 40, // 13: teleport.lib.teleterm.v1.ListKubernetesServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams - 92, // 14: teleport.lib.teleterm.v1.ListKubernetesServersResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeServer + 89, // 0: teleport.lib.teleterm.v1.GetAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 89, // 1: teleport.lib.teleterm.v1.GetAccessRequestsResponse.requests:type_name -> teleport.lib.teleterm.v1.AccessRequest + 90, // 2: teleport.lib.teleterm.v1.CreateAccessRequestRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID + 91, // 3: teleport.lib.teleterm.v1.CreateAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp + 91, // 4: teleport.lib.teleterm.v1.CreateAccessRequestRequest.max_duration:type_name -> google.protobuf.Timestamp + 91, // 5: teleport.lib.teleterm.v1.CreateAccessRequestRequest.request_ttl:type_name -> google.protobuf.Timestamp + 89, // 6: teleport.lib.teleterm.v1.CreateAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 90, // 7: teleport.lib.teleterm.v1.GetRequestableRolesRequest.resource_ids:type_name -> teleport.lib.teleterm.v1.ResourceID + 91, // 8: teleport.lib.teleterm.v1.ReviewAccessRequestRequest.assume_start_time:type_name -> google.protobuf.Timestamp + 89, // 9: teleport.lib.teleterm.v1.ReviewAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 89, // 10: teleport.lib.teleterm.v1.PromoteAccessRequestResponse.request:type_name -> teleport.lib.teleterm.v1.AccessRequest + 92, // 11: teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse.access_lists:type_name -> teleport.accesslist.v1.AccessList + 93, // 12: teleport.lib.teleterm.v1.ListKubernetesResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeResource + 42, // 13: teleport.lib.teleterm.v1.ListKubernetesServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams + 94, // 14: teleport.lib.teleterm.v1.ListKubernetesServersResponse.resources:type_name -> teleport.lib.teleterm.v1.KubeServer 0, // 15: teleport.lib.teleterm.v1.LoginPasswordlessResponse.prompt:type_name -> teleport.lib.teleterm.v1.PasswordlessPrompt - 28, // 16: teleport.lib.teleterm.v1.LoginPasswordlessResponse.credentials:type_name -> teleport.lib.teleterm.v1.CredentialInfo - 82, // 17: teleport.lib.teleterm.v1.LoginPasswordlessRequest.init:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit - 83, // 18: teleport.lib.teleterm.v1.LoginPasswordlessRequest.pin:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse - 84, // 19: teleport.lib.teleterm.v1.LoginPasswordlessRequest.credential:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse + 30, // 16: teleport.lib.teleterm.v1.LoginPasswordlessResponse.credentials:type_name -> teleport.lib.teleterm.v1.CredentialInfo + 84, // 17: teleport.lib.teleterm.v1.LoginPasswordlessRequest.init:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessRequestInit + 85, // 18: teleport.lib.teleterm.v1.LoginPasswordlessRequest.pin:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessPINResponse + 86, // 19: teleport.lib.teleterm.v1.LoginPasswordlessRequest.credential:type_name -> teleport.lib.teleterm.v1.LoginPasswordlessRequest.LoginPasswordlessCredentialResponse 1, // 20: teleport.lib.teleterm.v1.FileTransferRequest.direction:type_name -> teleport.lib.teleterm.v1.FileTransferDirection - 85, // 21: teleport.lib.teleterm.v1.LoginRequest.local:type_name -> teleport.lib.teleterm.v1.LoginRequest.LocalParams - 86, // 22: teleport.lib.teleterm.v1.LoginRequest.sso:type_name -> teleport.lib.teleterm.v1.LoginRequest.SsoParams - 93, // 23: teleport.lib.teleterm.v1.ListClustersResponse.clusters:type_name -> teleport.lib.teleterm.v1.Cluster - 40, // 24: teleport.lib.teleterm.v1.ListDatabaseServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams - 94, // 25: teleport.lib.teleterm.v1.ListDatabaseServersResponse.resources:type_name -> teleport.lib.teleterm.v1.DatabaseServer - 95, // 26: teleport.lib.teleterm.v1.ListGatewaysResponse.gateways:type_name -> teleport.lib.teleterm.v1.Gateway + 87, // 21: teleport.lib.teleterm.v1.LoginRequest.local:type_name -> teleport.lib.teleterm.v1.LoginRequest.LocalParams + 88, // 22: teleport.lib.teleterm.v1.LoginRequest.sso:type_name -> teleport.lib.teleterm.v1.LoginRequest.SsoParams + 95, // 23: teleport.lib.teleterm.v1.ListClustersResponse.clusters:type_name -> teleport.lib.teleterm.v1.Cluster + 42, // 24: teleport.lib.teleterm.v1.ListDatabaseServersRequest.params:type_name -> teleport.lib.teleterm.v1.ListResourcesParams + 96, // 25: teleport.lib.teleterm.v1.ListDatabaseServersResponse.resources:type_name -> teleport.lib.teleterm.v1.DatabaseServer + 97, // 26: teleport.lib.teleterm.v1.ListGatewaysResponse.gateways:type_name -> teleport.lib.teleterm.v1.Gateway 2, // 27: teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest.state:type_name -> teleport.lib.teleterm.v1.HeadlessAuthenticationState - 96, // 28: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse.server:type_name -> teleport.lib.teleterm.v1.Server - 65, // 29: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest.sort_by:type_name -> teleport.lib.teleterm.v1.SortBy - 67, // 30: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.PaginatedResource - 97, // 31: teleport.lib.teleterm.v1.PaginatedResource.database:type_name -> teleport.lib.teleterm.v1.Database - 96, // 32: teleport.lib.teleterm.v1.PaginatedResource.server:type_name -> teleport.lib.teleterm.v1.Server - 98, // 33: teleport.lib.teleterm.v1.PaginatedResource.kube:type_name -> teleport.lib.teleterm.v1.Kube - 99, // 34: teleport.lib.teleterm.v1.PaginatedResource.app:type_name -> teleport.lib.teleterm.v1.App - 100, // 35: teleport.lib.teleterm.v1.PaginatedResource.windows_desktop:type_name -> teleport.lib.teleterm.v1.WindowsDesktop - 72, // 36: teleport.lib.teleterm.v1.GetUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences - 72, // 37: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences - 72, // 38: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences - 101, // 39: teleport.lib.teleterm.v1.UserPreferences.cluster_preferences:type_name -> teleport.userpreferences.v1.ClusterUserPreferences - 102, // 40: teleport.lib.teleterm.v1.UserPreferences.unified_resource_preferences:type_name -> teleport.userpreferences.v1.UnifiedResourcePreferences - 103, // 41: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest.device_web_token:type_name -> teleport.devicetrust.v1.DeviceWebToken - 104, // 42: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse.confirmation_token:type_name -> teleport.devicetrust.v1.DeviceConfirmationToken - 99, // 43: teleport.lib.teleterm.v1.GetAppResponse.app:type_name -> teleport.lib.teleterm.v1.App - 77, // 44: teleport.lib.teleterm.v1.ConnectToDesktopRequest.target_desktop:type_name -> teleport.lib.teleterm.v1.TargetDesktop - 50, // 45: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:input_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest - 35, // 46: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:input_type -> teleport.lib.teleterm.v1.ListClustersRequest - 37, // 47: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:input_type -> teleport.lib.teleterm.v1.ListLeafClustersRequest - 6, // 48: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:input_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherRequest - 38, // 49: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:input_type -> teleport.lib.teleterm.v1.ListDatabaseUsersRequest - 41, // 50: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:input_type -> teleport.lib.teleterm.v1.ListDatabaseServersRequest - 9, // 51: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:input_type -> teleport.lib.teleterm.v1.GetAccessRequestsRequest - 8, // 52: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:input_type -> teleport.lib.teleterm.v1.GetAccessRequestRequest - 12, // 53: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:input_type -> teleport.lib.teleterm.v1.DeleteAccessRequestRequest - 13, // 54: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:input_type -> teleport.lib.teleterm.v1.CreateAccessRequestRequest - 18, // 55: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:input_type -> teleport.lib.teleterm.v1.ReviewAccessRequestRequest - 16, // 56: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:input_type -> teleport.lib.teleterm.v1.GetRequestableRolesRequest - 15, // 57: teleport.lib.teleterm.v1.TerminalService.AssumeRole:input_type -> teleport.lib.teleterm.v1.AssumeRoleRequest - 20, // 58: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:input_type -> teleport.lib.teleterm.v1.PromoteAccessRequestRequest - 22, // 59: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:input_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest - 24, // 60: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:input_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesRequest - 26, // 61: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:input_type -> teleport.lib.teleterm.v1.ListKubernetesServersRequest - 34, // 62: teleport.lib.teleterm.v1.TerminalService.AddCluster:input_type -> teleport.lib.teleterm.v1.AddClusterRequest - 44, // 63: teleport.lib.teleterm.v1.TerminalService.ListGateways:input_type -> teleport.lib.teleterm.v1.ListGatewaysRequest - 43, // 64: teleport.lib.teleterm.v1.TerminalService.CreateGateway:input_type -> teleport.lib.teleterm.v1.CreateGatewayRequest - 46, // 65: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:input_type -> teleport.lib.teleterm.v1.RemoveGatewayRequest - 47, // 66: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:input_type -> teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest - 48, // 67: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:input_type -> teleport.lib.teleterm.v1.SetGatewayLocalPortRequest - 49, // 68: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:input_type -> teleport.lib.teleterm.v1.GetAuthSettingsRequest + 98, // 28: teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse.server:type_name -> teleport.lib.teleterm.v1.Server + 67, // 29: teleport.lib.teleterm.v1.ListUnifiedResourcesRequest.sort_by:type_name -> teleport.lib.teleterm.v1.SortBy + 69, // 30: teleport.lib.teleterm.v1.ListUnifiedResourcesResponse.resources:type_name -> teleport.lib.teleterm.v1.PaginatedResource + 99, // 31: teleport.lib.teleterm.v1.PaginatedResource.database:type_name -> teleport.lib.teleterm.v1.Database + 98, // 32: teleport.lib.teleterm.v1.PaginatedResource.server:type_name -> teleport.lib.teleterm.v1.Server + 100, // 33: teleport.lib.teleterm.v1.PaginatedResource.kube:type_name -> teleport.lib.teleterm.v1.Kube + 101, // 34: teleport.lib.teleterm.v1.PaginatedResource.app:type_name -> teleport.lib.teleterm.v1.App + 102, // 35: teleport.lib.teleterm.v1.PaginatedResource.windows_desktop:type_name -> teleport.lib.teleterm.v1.WindowsDesktop + 74, // 36: teleport.lib.teleterm.v1.GetUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences + 74, // 37: teleport.lib.teleterm.v1.UpdateUserPreferencesRequest.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences + 74, // 38: teleport.lib.teleterm.v1.UpdateUserPreferencesResponse.user_preferences:type_name -> teleport.lib.teleterm.v1.UserPreferences + 103, // 39: teleport.lib.teleterm.v1.UserPreferences.cluster_preferences:type_name -> teleport.userpreferences.v1.ClusterUserPreferences + 104, // 40: teleport.lib.teleterm.v1.UserPreferences.unified_resource_preferences:type_name -> teleport.userpreferences.v1.UnifiedResourcePreferences + 105, // 41: teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest.device_web_token:type_name -> teleport.devicetrust.v1.DeviceWebToken + 106, // 42: teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse.confirmation_token:type_name -> teleport.devicetrust.v1.DeviceConfirmationToken + 101, // 43: teleport.lib.teleterm.v1.GetAppResponse.app:type_name -> teleport.lib.teleterm.v1.App + 79, // 44: teleport.lib.teleterm.v1.ConnectToDesktopRequest.target_desktop:type_name -> teleport.lib.teleterm.v1.TargetDesktop + 52, // 45: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:input_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressRequest + 37, // 46: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:input_type -> teleport.lib.teleterm.v1.ListClustersRequest + 39, // 47: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:input_type -> teleport.lib.teleterm.v1.ListLeafClustersRequest + 8, // 48: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:input_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherRequest + 40, // 49: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:input_type -> teleport.lib.teleterm.v1.ListDatabaseUsersRequest + 43, // 50: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:input_type -> teleport.lib.teleterm.v1.ListDatabaseServersRequest + 11, // 51: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:input_type -> teleport.lib.teleterm.v1.GetAccessRequestsRequest + 10, // 52: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:input_type -> teleport.lib.teleterm.v1.GetAccessRequestRequest + 14, // 53: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:input_type -> teleport.lib.teleterm.v1.DeleteAccessRequestRequest + 15, // 54: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:input_type -> teleport.lib.teleterm.v1.CreateAccessRequestRequest + 20, // 55: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:input_type -> teleport.lib.teleterm.v1.ReviewAccessRequestRequest + 18, // 56: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:input_type -> teleport.lib.teleterm.v1.GetRequestableRolesRequest + 17, // 57: teleport.lib.teleterm.v1.TerminalService.AssumeRole:input_type -> teleport.lib.teleterm.v1.AssumeRoleRequest + 22, // 58: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:input_type -> teleport.lib.teleterm.v1.PromoteAccessRequestRequest + 24, // 59: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:input_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsRequest + 26, // 60: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:input_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesRequest + 28, // 61: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:input_type -> teleport.lib.teleterm.v1.ListKubernetesServersRequest + 36, // 62: teleport.lib.teleterm.v1.TerminalService.AddCluster:input_type -> teleport.lib.teleterm.v1.AddClusterRequest + 46, // 63: teleport.lib.teleterm.v1.TerminalService.ListGateways:input_type -> teleport.lib.teleterm.v1.ListGatewaysRequest + 45, // 64: teleport.lib.teleterm.v1.TerminalService.CreateGateway:input_type -> teleport.lib.teleterm.v1.CreateGatewayRequest + 48, // 65: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:input_type -> teleport.lib.teleterm.v1.RemoveGatewayRequest + 49, // 66: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:input_type -> teleport.lib.teleterm.v1.SetGatewayTargetSubresourceNameRequest + 50, // 67: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:input_type -> teleport.lib.teleterm.v1.SetGatewayLocalPortRequest + 51, // 68: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:input_type -> teleport.lib.teleterm.v1.GetAuthSettingsRequest 4, // 69: teleport.lib.teleterm.v1.TerminalService.GetCluster:input_type -> teleport.lib.teleterm.v1.GetClusterRequest - 33, // 70: teleport.lib.teleterm.v1.TerminalService.Login:input_type -> teleport.lib.teleterm.v1.LoginRequest - 30, // 71: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:input_type -> teleport.lib.teleterm.v1.LoginPasswordlessRequest + 35, // 70: teleport.lib.teleterm.v1.TerminalService.Login:input_type -> teleport.lib.teleterm.v1.LoginRequest + 32, // 71: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:input_type -> teleport.lib.teleterm.v1.LoginPasswordlessRequest 5, // 72: teleport.lib.teleterm.v1.TerminalService.Logout:input_type -> teleport.lib.teleterm.v1.LogoutRequest - 31, // 73: teleport.lib.teleterm.v1.TerminalService.TransferFile:input_type -> teleport.lib.teleterm.v1.FileTransferRequest - 105, // 74: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:input_type -> teleport.lib.teleterm.v1.ReportUsageEventRequest - 52, // 75: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:input_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest - 54, // 76: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest - 56, // 77: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest - 58, // 78: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:input_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest - 60, // 79: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:input_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest - 62, // 80: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:input_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest - 64, // 81: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:input_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesRequest - 68, // 82: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:input_type -> teleport.lib.teleterm.v1.GetUserPreferencesRequest - 70, // 83: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:input_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesRequest - 73, // 84: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:input_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest - 75, // 85: teleport.lib.teleterm.v1.TerminalService.GetApp:input_type -> teleport.lib.teleterm.v1.GetAppRequest - 78, // 86: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:input_type -> teleport.lib.teleterm.v1.ConnectToDesktopRequest - 80, // 87: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:input_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest - 51, // 88: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:output_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse - 36, // 89: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse - 36, // 90: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse - 7, // 91: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:output_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherResponse - 39, // 92: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:output_type -> teleport.lib.teleterm.v1.ListDatabaseUsersResponse - 42, // 93: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:output_type -> teleport.lib.teleterm.v1.ListDatabaseServersResponse - 11, // 94: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:output_type -> teleport.lib.teleterm.v1.GetAccessRequestsResponse - 10, // 95: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:output_type -> teleport.lib.teleterm.v1.GetAccessRequestResponse - 3, // 96: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 14, // 97: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:output_type -> teleport.lib.teleterm.v1.CreateAccessRequestResponse - 19, // 98: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:output_type -> teleport.lib.teleterm.v1.ReviewAccessRequestResponse - 17, // 99: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:output_type -> teleport.lib.teleterm.v1.GetRequestableRolesResponse - 3, // 100: teleport.lib.teleterm.v1.TerminalService.AssumeRole:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 21, // 101: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:output_type -> teleport.lib.teleterm.v1.PromoteAccessRequestResponse - 23, // 102: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:output_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse - 25, // 103: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:output_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesResponse - 27, // 104: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:output_type -> teleport.lib.teleterm.v1.ListKubernetesServersResponse - 93, // 105: teleport.lib.teleterm.v1.TerminalService.AddCluster:output_type -> teleport.lib.teleterm.v1.Cluster - 45, // 106: teleport.lib.teleterm.v1.TerminalService.ListGateways:output_type -> teleport.lib.teleterm.v1.ListGatewaysResponse - 95, // 107: teleport.lib.teleterm.v1.TerminalService.CreateGateway:output_type -> teleport.lib.teleterm.v1.Gateway - 3, // 108: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 95, // 109: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:output_type -> teleport.lib.teleterm.v1.Gateway - 95, // 110: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:output_type -> teleport.lib.teleterm.v1.Gateway - 106, // 111: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:output_type -> teleport.lib.teleterm.v1.AuthSettings - 93, // 112: teleport.lib.teleterm.v1.TerminalService.GetCluster:output_type -> teleport.lib.teleterm.v1.Cluster - 3, // 113: teleport.lib.teleterm.v1.TerminalService.Login:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 29, // 114: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:output_type -> teleport.lib.teleterm.v1.LoginPasswordlessResponse - 3, // 115: teleport.lib.teleterm.v1.TerminalService.Logout:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 32, // 116: teleport.lib.teleterm.v1.TerminalService.TransferFile:output_type -> teleport.lib.teleterm.v1.FileTransferProgress - 3, // 117: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:output_type -> teleport.lib.teleterm.v1.EmptyResponse - 53, // 118: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:output_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse - 55, // 119: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse - 57, // 120: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse - 59, // 121: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:output_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse - 61, // 122: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:output_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse - 63, // 123: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:output_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse - 66, // 124: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:output_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesResponse - 69, // 125: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:output_type -> teleport.lib.teleterm.v1.GetUserPreferencesResponse - 71, // 126: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:output_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesResponse - 74, // 127: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:output_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse - 76, // 128: teleport.lib.teleterm.v1.TerminalService.GetApp:output_type -> teleport.lib.teleterm.v1.GetAppResponse - 79, // 129: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:output_type -> teleport.lib.teleterm.v1.ConnectToDesktopResponse - 81, // 130: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:output_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse - 88, // [88:131] is the sub-list for method output_type - 45, // [45:88] is the sub-list for method input_type + 6, // 73: teleport.lib.teleterm.v1.TerminalService.ClearStaleClusterClients:input_type -> teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest + 33, // 74: teleport.lib.teleterm.v1.TerminalService.TransferFile:input_type -> teleport.lib.teleterm.v1.FileTransferRequest + 107, // 75: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:input_type -> teleport.lib.teleterm.v1.ReportUsageEventRequest + 54, // 76: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:input_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest + 56, // 77: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest + 58, // 78: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:input_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest + 60, // 79: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:input_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest + 62, // 80: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:input_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest + 64, // 81: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:input_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest + 66, // 82: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:input_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesRequest + 70, // 83: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:input_type -> teleport.lib.teleterm.v1.GetUserPreferencesRequest + 72, // 84: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:input_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesRequest + 75, // 85: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:input_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest + 77, // 86: teleport.lib.teleterm.v1.TerminalService.GetApp:input_type -> teleport.lib.teleterm.v1.GetAppRequest + 80, // 87: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:input_type -> teleport.lib.teleterm.v1.ConnectToDesktopRequest + 82, // 88: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:input_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest + 53, // 89: teleport.lib.teleterm.v1.TerminalService.UpdateTshdEventsServerAddress:output_type -> teleport.lib.teleterm.v1.UpdateTshdEventsServerAddressResponse + 38, // 90: teleport.lib.teleterm.v1.TerminalService.ListRootClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse + 38, // 91: teleport.lib.teleterm.v1.TerminalService.ListLeafClusters:output_type -> teleport.lib.teleterm.v1.ListClustersResponse + 9, // 92: teleport.lib.teleterm.v1.TerminalService.StartHeadlessWatcher:output_type -> teleport.lib.teleterm.v1.StartHeadlessWatcherResponse + 41, // 93: teleport.lib.teleterm.v1.TerminalService.ListDatabaseUsers:output_type -> teleport.lib.teleterm.v1.ListDatabaseUsersResponse + 44, // 94: teleport.lib.teleterm.v1.TerminalService.ListDatabaseServers:output_type -> teleport.lib.teleterm.v1.ListDatabaseServersResponse + 13, // 95: teleport.lib.teleterm.v1.TerminalService.GetAccessRequests:output_type -> teleport.lib.teleterm.v1.GetAccessRequestsResponse + 12, // 96: teleport.lib.teleterm.v1.TerminalService.GetAccessRequest:output_type -> teleport.lib.teleterm.v1.GetAccessRequestResponse + 3, // 97: teleport.lib.teleterm.v1.TerminalService.DeleteAccessRequest:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 16, // 98: teleport.lib.teleterm.v1.TerminalService.CreateAccessRequest:output_type -> teleport.lib.teleterm.v1.CreateAccessRequestResponse + 21, // 99: teleport.lib.teleterm.v1.TerminalService.ReviewAccessRequest:output_type -> teleport.lib.teleterm.v1.ReviewAccessRequestResponse + 19, // 100: teleport.lib.teleterm.v1.TerminalService.GetRequestableRoles:output_type -> teleport.lib.teleterm.v1.GetRequestableRolesResponse + 3, // 101: teleport.lib.teleterm.v1.TerminalService.AssumeRole:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 23, // 102: teleport.lib.teleterm.v1.TerminalService.PromoteAccessRequest:output_type -> teleport.lib.teleterm.v1.PromoteAccessRequestResponse + 25, // 103: teleport.lib.teleterm.v1.TerminalService.GetSuggestedAccessLists:output_type -> teleport.lib.teleterm.v1.GetSuggestedAccessListsResponse + 27, // 104: teleport.lib.teleterm.v1.TerminalService.ListKubernetesResources:output_type -> teleport.lib.teleterm.v1.ListKubernetesResourcesResponse + 29, // 105: teleport.lib.teleterm.v1.TerminalService.ListKubernetesServers:output_type -> teleport.lib.teleterm.v1.ListKubernetesServersResponse + 95, // 106: teleport.lib.teleterm.v1.TerminalService.AddCluster:output_type -> teleport.lib.teleterm.v1.Cluster + 47, // 107: teleport.lib.teleterm.v1.TerminalService.ListGateways:output_type -> teleport.lib.teleterm.v1.ListGatewaysResponse + 97, // 108: teleport.lib.teleterm.v1.TerminalService.CreateGateway:output_type -> teleport.lib.teleterm.v1.Gateway + 3, // 109: teleport.lib.teleterm.v1.TerminalService.RemoveGateway:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 97, // 110: teleport.lib.teleterm.v1.TerminalService.SetGatewayTargetSubresourceName:output_type -> teleport.lib.teleterm.v1.Gateway + 97, // 111: teleport.lib.teleterm.v1.TerminalService.SetGatewayLocalPort:output_type -> teleport.lib.teleterm.v1.Gateway + 108, // 112: teleport.lib.teleterm.v1.TerminalService.GetAuthSettings:output_type -> teleport.lib.teleterm.v1.AuthSettings + 95, // 113: teleport.lib.teleterm.v1.TerminalService.GetCluster:output_type -> teleport.lib.teleterm.v1.Cluster + 3, // 114: teleport.lib.teleterm.v1.TerminalService.Login:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 31, // 115: teleport.lib.teleterm.v1.TerminalService.LoginPasswordless:output_type -> teleport.lib.teleterm.v1.LoginPasswordlessResponse + 3, // 116: teleport.lib.teleterm.v1.TerminalService.Logout:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 7, // 117: teleport.lib.teleterm.v1.TerminalService.ClearStaleClusterClients:output_type -> teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse + 34, // 118: teleport.lib.teleterm.v1.TerminalService.TransferFile:output_type -> teleport.lib.teleterm.v1.FileTransferProgress + 3, // 119: teleport.lib.teleterm.v1.TerminalService.ReportUsageEvent:output_type -> teleport.lib.teleterm.v1.EmptyResponse + 55, // 120: teleport.lib.teleterm.v1.TerminalService.UpdateHeadlessAuthenticationState:output_type -> teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse + 57, // 121: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerRole:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse + 59, // 122: teleport.lib.teleterm.v1.TerminalService.CreateConnectMyComputerNodeToken:output_type -> teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse + 61, // 123: teleport.lib.teleterm.v1.TerminalService.WaitForConnectMyComputerNodeJoin:output_type -> teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse + 63, // 124: teleport.lib.teleterm.v1.TerminalService.DeleteConnectMyComputerNode:output_type -> teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse + 65, // 125: teleport.lib.teleterm.v1.TerminalService.GetConnectMyComputerNodeName:output_type -> teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse + 68, // 126: teleport.lib.teleterm.v1.TerminalService.ListUnifiedResources:output_type -> teleport.lib.teleterm.v1.ListUnifiedResourcesResponse + 71, // 127: teleport.lib.teleterm.v1.TerminalService.GetUserPreferences:output_type -> teleport.lib.teleterm.v1.GetUserPreferencesResponse + 73, // 128: teleport.lib.teleterm.v1.TerminalService.UpdateUserPreferences:output_type -> teleport.lib.teleterm.v1.UpdateUserPreferencesResponse + 76, // 129: teleport.lib.teleterm.v1.TerminalService.AuthenticateWebDevice:output_type -> teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse + 78, // 130: teleport.lib.teleterm.v1.TerminalService.GetApp:output_type -> teleport.lib.teleterm.v1.GetAppResponse + 81, // 131: teleport.lib.teleterm.v1.TerminalService.ConnectToDesktop:output_type -> teleport.lib.teleterm.v1.ConnectToDesktopResponse + 83, // 132: teleport.lib.teleterm.v1.TerminalService.SetSharedDirectoryForDesktopSession:output_type -> teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse + 89, // [89:133] is the sub-list for method output_type + 45, // [45:89] is the sub-list for method input_type 45, // [45:45] is the sub-list for extension type_name 45, // [45:45] is the sub-list for extension extendee 0, // [0:45] is the sub-list for field type_name @@ -5351,16 +5439,16 @@ func file_teleport_lib_teleterm_v1_service_proto_init() { file_teleport_lib_teleterm_v1_server_proto_init() file_teleport_lib_teleterm_v1_usage_events_proto_init() file_teleport_lib_teleterm_v1_windows_desktop_proto_init() - file_teleport_lib_teleterm_v1_service_proto_msgTypes[27].OneofWrappers = []any{ + file_teleport_lib_teleterm_v1_service_proto_msgTypes[29].OneofWrappers = []any{ (*LoginPasswordlessRequest_Init)(nil), (*LoginPasswordlessRequest_Pin)(nil), (*LoginPasswordlessRequest_Credential)(nil), } - file_teleport_lib_teleterm_v1_service_proto_msgTypes[30].OneofWrappers = []any{ + file_teleport_lib_teleterm_v1_service_proto_msgTypes[32].OneofWrappers = []any{ (*LoginRequest_Local)(nil), (*LoginRequest_Sso)(nil), } - file_teleport_lib_teleterm_v1_service_proto_msgTypes[64].OneofWrappers = []any{ + file_teleport_lib_teleterm_v1_service_proto_msgTypes[66].OneofWrappers = []any{ (*PaginatedResource_Database)(nil), (*PaginatedResource_Server)(nil), (*PaginatedResource_Kube)(nil), @@ -5373,7 +5461,7 @@ func file_teleport_lib_teleterm_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_lib_teleterm_v1_service_proto_rawDesc), len(file_teleport_lib_teleterm_v1_service_proto_rawDesc)), NumEnums: 3, - NumMessages: 84, + NumMessages: 86, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go index cb81fac910a62..977818c1c9a68 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/service_grpc.pb.go @@ -64,6 +64,7 @@ const ( TerminalService_Login_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/Login" TerminalService_LoginPasswordless_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/LoginPasswordless" TerminalService_Logout_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/Logout" + TerminalService_ClearStaleClusterClients_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/ClearStaleClusterClients" TerminalService_TransferFile_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/TransferFile" TerminalService_ReportUsageEvent_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/ReportUsageEvent" TerminalService_UpdateHeadlessAuthenticationState_FullMethodName = "/teleport.lib.teleterm.v1.TerminalService/UpdateHeadlessAuthenticationState" @@ -178,6 +179,8 @@ type TerminalServiceClient interface { // Optionally removes the profile. // This operation is idempotent and can be safely invoked multiple times. Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*EmptyResponse, error) + // Closes root and leaf cluster clients that use outdated TLS certificates. + ClearStaleClusterClients(ctx context.Context, in *ClearStaleClusterClientsRequest, opts ...grpc.CallOption) (*ClearStaleClusterClientsResponse, error) // TransferFile sends a request to download/upload a file TransferFile(ctx context.Context, in *FileTransferRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileTransferProgress], error) // ReportUsageEvent allows to send usage events that are then anonymized and forwarded to prehog @@ -520,6 +523,16 @@ func (c *terminalServiceClient) Logout(ctx context.Context, in *LogoutRequest, o return out, nil } +func (c *terminalServiceClient) ClearStaleClusterClients(ctx context.Context, in *ClearStaleClusterClientsRequest, opts ...grpc.CallOption) (*ClearStaleClusterClientsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClearStaleClusterClientsResponse) + err := c.cc.Invoke(ctx, TerminalService_ClearStaleClusterClients_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *terminalServiceClient) TransferFile(ctx context.Context, in *FileTransferRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileTransferProgress], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &TerminalService_ServiceDesc.Streams[1], TerminalService_TransferFile_FullMethodName, cOpts...) @@ -779,6 +792,8 @@ type TerminalServiceServer interface { // Optionally removes the profile. // This operation is idempotent and can be safely invoked multiple times. Logout(context.Context, *LogoutRequest) (*EmptyResponse, error) + // Closes root and leaf cluster clients that use outdated TLS certificates. + ClearStaleClusterClients(context.Context, *ClearStaleClusterClientsRequest) (*ClearStaleClusterClientsResponse, error) // TransferFile sends a request to download/upload a file TransferFile(*FileTransferRequest, grpc.ServerStreamingServer[FileTransferProgress]) error // ReportUsageEvent allows to send usage events that are then anonymized and forwarded to prehog @@ -922,6 +937,9 @@ func (UnimplementedTerminalServiceServer) LoginPasswordless(grpc.BidiStreamingSe func (UnimplementedTerminalServiceServer) Logout(context.Context, *LogoutRequest) (*EmptyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") } +func (UnimplementedTerminalServiceServer) ClearStaleClusterClients(context.Context, *ClearStaleClusterClientsRequest) (*ClearStaleClusterClientsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClearStaleClusterClients not implemented") +} func (UnimplementedTerminalServiceServer) TransferFile(*FileTransferRequest, grpc.ServerStreamingServer[FileTransferProgress]) error { return status.Errorf(codes.Unimplemented, "method TransferFile not implemented") } @@ -1481,6 +1499,24 @@ func _TerminalService_Logout_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _TerminalService_ClearStaleClusterClients_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClearStaleClusterClientsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TerminalServiceServer).ClearStaleClusterClients(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TerminalService_ClearStaleClusterClients_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TerminalServiceServer).ClearStaleClusterClients(ctx, req.(*ClearStaleClusterClientsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _TerminalService_TransferFile_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(FileTransferRequest) if err := stream.RecvMsg(m); err != nil { @@ -1848,6 +1884,10 @@ var TerminalService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Logout", Handler: _TerminalService_Logout_Handler, }, + { + MethodName: "ClearStaleClusterClients", + Handler: _TerminalService_ClearStaleClusterClients_Handler, + }, { MethodName: "ReportUsageEvent", Handler: _TerminalService_ReportUsageEvent_Handler, diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/cluster_pb.ts b/gen/proto/ts/teleport/lib/teleterm/v1/cluster_pb.ts index d9ddda213e5cd..e570f209d1abd 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/cluster_pb.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/cluster_pb.ts @@ -30,6 +30,7 @@ import { UnknownFieldHandler } from "@protobuf-ts/runtime"; import type { PartialMessage } from "@protobuf-ts/runtime"; import { reflectionMergePartial } from "@protobuf-ts/runtime"; import { MessageType } from "@protobuf-ts/runtime"; +import { Timestamp } from "../../../../google/protobuf/timestamp_pb"; import { TrustedDeviceRequirement } from "../../../legacy/types/trusted_device_requirement_pb"; /** * Cluster describes cluster fields. @@ -191,6 +192,12 @@ export interface LoggedInUser { * @generated from protobuf field: types.TrustedDeviceRequirement trusted_device_requirement = 10; */ trustedDeviceRequirement: TrustedDeviceRequirement; + /** + * Expiration time of the certificate. + * + * @generated from protobuf field: google.protobuf.Timestamp valid_until = 11; + */ + validUntil?: Timestamp; } /** * UserType indicates whether the user was created through an SSO provider or in Teleport itself. @@ -541,7 +548,8 @@ class LoggedInUser$Type extends MessageType { { no: 7, name: "requestable_roles", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, { no: 8, name: "user_type", kind: "enum", T: () => ["teleport.lib.teleterm.v1.LoggedInUser.UserType", LoggedInUser_UserType, "USER_TYPE_"] }, { no: 9, name: "is_device_trusted", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, - { no: 10, name: "trusted_device_requirement", kind: "enum", T: () => ["types.TrustedDeviceRequirement", TrustedDeviceRequirement, "TRUSTED_DEVICE_REQUIREMENT_"] } + { no: 10, name: "trusted_device_requirement", kind: "enum", T: () => ["types.TrustedDeviceRequirement", TrustedDeviceRequirement, "TRUSTED_DEVICE_REQUIREMENT_"] }, + { no: 11, name: "valid_until", kind: "message", T: () => Timestamp } ]); } create(value?: PartialMessage): LoggedInUser { @@ -590,6 +598,9 @@ class LoggedInUser$Type extends MessageType { case /* types.TrustedDeviceRequirement trusted_device_requirement */ 10: message.trustedDeviceRequirement = reader.int32(); break; + case /* google.protobuf.Timestamp valid_until */ 11: + message.validUntil = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.validUntil); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -629,6 +640,9 @@ class LoggedInUser$Type extends MessageType { /* types.TrustedDeviceRequirement trusted_device_requirement = 10; */ if (message.trustedDeviceRequirement !== 0) writer.tag(10, WireType.Varint).int32(message.trustedDeviceRequirement); + /* google.protobuf.Timestamp valid_until = 11; */ + if (message.validUntil) + Timestamp.internalBinaryWrite(message.validUntil, writer.tag(11, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts index 7f4220520924d..f31ee81bb3625 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.client.ts @@ -54,6 +54,8 @@ import type { ReportUsageEventRequest } from "./usage_events_pb"; import type { FileTransferProgress } from "./service_pb"; import type { FileTransferRequest } from "./service_pb"; import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc"; +import type { ClearStaleClusterClientsResponse } from "./service_pb"; +import type { ClearStaleClusterClientsRequest } from "./service_pb"; import type { LogoutRequest } from "./service_pb"; import type { LoginPasswordlessResponse } from "./service_pb"; import type { LoginPasswordlessRequest } from "./service_pb"; @@ -315,6 +317,12 @@ export interface ITerminalServiceClient { * @generated from protobuf rpc: Logout(teleport.lib.teleterm.v1.LogoutRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ logout(input: LogoutRequest, options?: RpcOptions): UnaryCall; + /** + * Closes root and leaf cluster clients that use outdated TLS certificates. + * + * @generated from protobuf rpc: ClearStaleClusterClients(teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest) returns (teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse); + */ + clearStaleClusterClients(input: ClearStaleClusterClientsRequest, options?: RpcOptions): UnaryCall; /** * TransferFile sends a request to download/upload a file * @@ -723,13 +731,22 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf const method = this.methods[27], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } + /** + * Closes root and leaf cluster clients that use outdated TLS certificates. + * + * @generated from protobuf rpc: ClearStaleClusterClients(teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest) returns (teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse); + */ + clearStaleClusterClients(input: ClearStaleClusterClientsRequest, options?: RpcOptions): UnaryCall { + const method = this.methods[28], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } /** * TransferFile sends a request to download/upload a file * * @generated from protobuf rpc: TransferFile(teleport.lib.teleterm.v1.FileTransferRequest) returns (stream teleport.lib.teleterm.v1.FileTransferProgress); */ transferFile(input: FileTransferRequest, options?: RpcOptions): ServerStreamingCall { - const method = this.methods[28], opt = this._transport.mergeOptions(options); + const method = this.methods[29], opt = this._transport.mergeOptions(options); return stackIntercept("serverStreaming", this._transport, method, opt, input); } /** @@ -738,7 +755,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: ReportUsageEvent(teleport.lib.teleterm.v1.ReportUsageEventRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ reportUsageEvent(input: ReportUsageEventRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[29], opt = this._transport.mergeOptions(options); + const method = this.methods[30], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -748,7 +765,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: UpdateHeadlessAuthenticationState(teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateRequest) returns (teleport.lib.teleterm.v1.UpdateHeadlessAuthenticationStateResponse); */ updateHeadlessAuthenticationState(input: UpdateHeadlessAuthenticationStateRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[30], opt = this._transport.mergeOptions(options); + const method = this.methods[31], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -759,7 +776,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: CreateConnectMyComputerRole(teleport.lib.teleterm.v1.CreateConnectMyComputerRoleRequest) returns (teleport.lib.teleterm.v1.CreateConnectMyComputerRoleResponse); */ createConnectMyComputerRole(input: CreateConnectMyComputerRoleRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[31], opt = this._transport.mergeOptions(options); + const method = this.methods[32], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -768,7 +785,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: CreateConnectMyComputerNodeToken(teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenRequest) returns (teleport.lib.teleterm.v1.CreateConnectMyComputerNodeTokenResponse); */ createConnectMyComputerNodeToken(input: CreateConnectMyComputerNodeTokenRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[32], opt = this._transport.mergeOptions(options); + const method = this.methods[33], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -782,7 +799,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: WaitForConnectMyComputerNodeJoin(teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinRequest) returns (teleport.lib.teleterm.v1.WaitForConnectMyComputerNodeJoinResponse); */ waitForConnectMyComputerNodeJoin(input: WaitForConnectMyComputerNodeJoinRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[33], opt = this._transport.mergeOptions(options); + const method = this.methods[34], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -791,7 +808,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: DeleteConnectMyComputerNode(teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeRequest) returns (teleport.lib.teleterm.v1.DeleteConnectMyComputerNodeResponse); */ deleteConnectMyComputerNode(input: DeleteConnectMyComputerNodeRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[34], opt = this._transport.mergeOptions(options); + const method = this.methods[35], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -800,7 +817,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetConnectMyComputerNodeName(teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameRequest) returns (teleport.lib.teleterm.v1.GetConnectMyComputerNodeNameResponse); */ getConnectMyComputerNodeName(input: GetConnectMyComputerNodeNameRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[35], opt = this._transport.mergeOptions(options); + const method = this.methods[36], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -809,7 +826,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: ListUnifiedResources(teleport.lib.teleterm.v1.ListUnifiedResourcesRequest) returns (teleport.lib.teleterm.v1.ListUnifiedResourcesResponse); */ listUnifiedResources(input: ListUnifiedResourcesRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[36], opt = this._transport.mergeOptions(options); + const method = this.methods[37], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -818,7 +835,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetUserPreferences(teleport.lib.teleterm.v1.GetUserPreferencesRequest) returns (teleport.lib.teleterm.v1.GetUserPreferencesResponse); */ getUserPreferences(input: GetUserPreferencesRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[37], opt = this._transport.mergeOptions(options); + const method = this.methods[38], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -828,7 +845,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: UpdateUserPreferences(teleport.lib.teleterm.v1.UpdateUserPreferencesRequest) returns (teleport.lib.teleterm.v1.UpdateUserPreferencesResponse); */ updateUserPreferences(input: UpdateUserPreferencesRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[38], opt = this._transport.mergeOptions(options); + const method = this.methods[39], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -841,7 +858,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: AuthenticateWebDevice(teleport.lib.teleterm.v1.AuthenticateWebDeviceRequest) returns (teleport.lib.teleterm.v1.AuthenticateWebDeviceResponse); */ authenticateWebDevice(input: AuthenticateWebDeviceRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[39], opt = this._transport.mergeOptions(options); + const method = this.methods[40], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -851,7 +868,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: GetApp(teleport.lib.teleterm.v1.GetAppRequest) returns (teleport.lib.teleterm.v1.GetAppResponse); */ getApp(input: GetAppRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[40], opt = this._transport.mergeOptions(options); + const method = this.methods[41], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -860,7 +877,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: ConnectToDesktop(stream teleport.lib.teleterm.v1.ConnectToDesktopRequest) returns (stream teleport.lib.teleterm.v1.ConnectToDesktopResponse); */ connectToDesktop(options?: RpcOptions): DuplexStreamingCall { - const method = this.methods[41], opt = this._transport.mergeOptions(options); + const method = this.methods[42], opt = this._transport.mergeOptions(options); return stackIntercept("duplex", this._transport, method, opt); } /** @@ -874,7 +891,7 @@ export class TerminalServiceClient implements ITerminalServiceClient, ServiceInf * @generated from protobuf rpc: SetSharedDirectoryForDesktopSession(teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionRequest) returns (teleport.lib.teleterm.v1.SetSharedDirectoryForDesktopSessionResponse); */ setSharedDirectoryForDesktopSession(input: SetSharedDirectoryForDesktopSessionRequest, options?: RpcOptions): UnaryCall { - const method = this.methods[42], opt = this._transport.mergeOptions(options); + const method = this.methods[43], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } } diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts index 6fd5564b85a20..f2d9f31ee3bfb 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.grpc-server.ts @@ -50,6 +50,8 @@ import { UpdateHeadlessAuthenticationStateRequest } from "./service_pb"; import { ReportUsageEventRequest } from "./usage_events_pb"; import { FileTransferProgress } from "./service_pb"; import { FileTransferRequest } from "./service_pb"; +import { ClearStaleClusterClientsResponse } from "./service_pb"; +import { ClearStaleClusterClientsRequest } from "./service_pb"; import { LogoutRequest } from "./service_pb"; import { LoginPasswordlessResponse } from "./service_pb"; import { LoginPasswordlessRequest } from "./service_pb"; @@ -308,6 +310,12 @@ export interface ITerminalService extends grpc.UntypedServiceImplementation { * @generated from protobuf rpc: Logout(teleport.lib.teleterm.v1.LogoutRequest) returns (teleport.lib.teleterm.v1.EmptyResponse); */ logout: grpc.handleUnaryCall; + /** + * Closes root and leaf cluster clients that use outdated TLS certificates. + * + * @generated from protobuf rpc: ClearStaleClusterClients(teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest) returns (teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse); + */ + clearStaleClusterClients: grpc.handleUnaryCall; /** * TransferFile sends a request to download/upload a file * @@ -710,6 +718,16 @@ export const terminalServiceDefinition: grpc.ServiceDefinition responseSerialize: value => Buffer.from(EmptyResponse.toBinary(value)), requestSerialize: value => Buffer.from(LogoutRequest.toBinary(value)) }, + clearStaleClusterClients: { + path: "/teleport.lib.teleterm.v1.TerminalService/ClearStaleClusterClients", + originalName: "ClearStaleClusterClients", + requestStream: false, + responseStream: false, + responseDeserialize: bytes => ClearStaleClusterClientsResponse.fromBinary(bytes), + requestDeserialize: bytes => ClearStaleClusterClientsRequest.fromBinary(bytes), + responseSerialize: value => Buffer.from(ClearStaleClusterClientsResponse.toBinary(value)), + requestSerialize: value => Buffer.from(ClearStaleClusterClientsRequest.toBinary(value)) + }, transferFile: { path: "/teleport.lib.teleterm.v1.TerminalService/TransferFile", originalName: "TransferFile", diff --git a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts index b93089cf73be4..29d50da9529e9 100644 --- a/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts +++ b/gen/proto/ts/teleport/lib/teleterm/v1/service_pb.ts @@ -82,6 +82,20 @@ export interface LogoutRequest { */ removeProfile: boolean; } +/** + * @generated from protobuf message teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest + */ +export interface ClearStaleClusterClientsRequest { + /** + * @generated from protobuf field: string root_cluster_uri = 1; + */ + rootClusterUri: string; +} +/** + * @generated from protobuf message teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse + */ +export interface ClearStaleClusterClientsResponse { +} /** * @generated from protobuf message teleport.lib.teleterm.v1.StartHeadlessWatcherRequest */ @@ -1537,6 +1551,78 @@ class LogoutRequest$Type extends MessageType { */ export const LogoutRequest = new LogoutRequest$Type(); // @generated message type with reflection information, may provide speed optimized methods +class ClearStaleClusterClientsRequest$Type extends MessageType { + constructor() { + super("teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest", [ + { no: 1, name: "root_cluster_uri", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): ClearStaleClusterClientsRequest { + const message = globalThis.Object.create((this.messagePrototype!)); + message.rootClusterUri = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ClearStaleClusterClientsRequest): ClearStaleClusterClientsRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string root_cluster_uri */ 1: + message.rootClusterUri = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ClearStaleClusterClientsRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string root_cluster_uri = 1; */ + if (message.rootClusterUri !== "") + writer.tag(1, WireType.LengthDelimited).string(message.rootClusterUri); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message teleport.lib.teleterm.v1.ClearStaleClusterClientsRequest + */ +export const ClearStaleClusterClientsRequest = new ClearStaleClusterClientsRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ClearStaleClusterClientsResponse$Type extends MessageType { + constructor() { + super("teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse", []); + } + create(value?: PartialMessage): ClearStaleClusterClientsResponse { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ClearStaleClusterClientsResponse): ClearStaleClusterClientsResponse { + return target ?? this.create(); + } + internalBinaryWrite(message: ClearStaleClusterClientsResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message teleport.lib.teleterm.v1.ClearStaleClusterClientsResponse + */ +export const ClearStaleClusterClientsResponse = new ClearStaleClusterClientsResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods class StartHeadlessWatcherRequest$Type extends MessageType { constructor() { super("teleport.lib.teleterm.v1.StartHeadlessWatcherRequest", [ @@ -5879,6 +5965,7 @@ export const TerminalService = new ServiceType("teleport.lib.teleterm.v1.Termina { name: "Login", options: {}, I: LoginRequest, O: EmptyResponse }, { name: "LoginPasswordless", serverStreaming: true, clientStreaming: true, options: {}, I: LoginPasswordlessRequest, O: LoginPasswordlessResponse }, { name: "Logout", options: {}, I: LogoutRequest, O: EmptyResponse }, + { name: "ClearStaleClusterClients", options: {}, I: ClearStaleClusterClientsRequest, O: ClearStaleClusterClientsResponse }, { name: "TransferFile", serverStreaming: true, options: {}, I: FileTransferRequest, O: FileTransferProgress }, { name: "ReportUsageEvent", options: {}, I: ReportUsageEventRequest, O: EmptyResponse }, { name: "UpdateHeadlessAuthenticationState", options: {}, I: UpdateHeadlessAuthenticationStateRequest, O: UpdateHeadlessAuthenticationStateResponse }, diff --git a/integration/teleterm_test.go b/integration/teleterm_test.go index 2c5c0d351c6c1..1bdbb7ad252c8 100644 --- a/integration/teleterm_test.go +++ b/integration/teleterm_test.go @@ -134,6 +134,12 @@ func TestTeleterm(t *testing.T) { testClientCache(t, pack, creds) }) + t.Run("clearing stale cached clients", func(t *testing.T) { + t.Parallel() + + testClearingStaleCachedClients(t, pack, creds) + }) + t.Run("logging out", func(t *testing.T) { t.Parallel() testLogout(t, pack, creds) @@ -554,6 +560,62 @@ func testClientCache(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers. require.NotEqual(t, secondCallForClient, thirdCallForClient) } +func testClearingStaleCachedClients(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.UserCreds) { + ctx := context.Background() + + tc := mustLogin(t, pack.Root.User.GetName(), pack, creds) + + storageFakeClock := clockwork.NewFakeClockAt(time.Now()) + + storage, err := clusters.NewStorage(clusters.Config{ + ClientStore: tc.ClientStore, + Clock: storageFakeClock, + InsecureSkipVerify: tc.InsecureSkipVerify, + }) + require.NoError(t, err) + + cluster, _, err := storage.Add(ctx, tc.WebProxyAddr) + require.NoError(t, err) + + tshdEventsClient := daemon.NewTshdEventsClient(func() (grpc.DialOption, error) { + return grpc.WithTransportCredentials(insecure.NewCredentials()), nil + }) + + daemonService, err := daemon.New(daemon.Config{ + Storage: storage, + TshdEventsClient: tshdEventsClient, + KubeconfigsDir: t.TempDir(), + AgentsDir: t.TempDir(), + }) + require.NoError(t, err) + t.Cleanup(func() { + daemonService.Stop() + }) + + firstCallForClient, err := daemonService.GetCachedClient(ctx, cluster.URI) + require.NoError(t, err) + err = daemonService.ClearStaleCachedClientsForRoot(cluster.URI) + require.NoError(t, err) + // Ensure the client wasn't closed. + secondCallForClient, err := daemonService.GetCachedClient(ctx, cluster.URI) + require.NoError(t, err) + require.Equal(t, firstCallForClient, secondCallForClient) + // Reissue user certs by assuming a role with a bogus ID in DropAccessRequests. + accessRequest := &api.AssumeRoleRequest{ + RootClusterUri: cluster.URI.String(), + DropRequestIds: []string{"does-not-matter"}, + } + err = cluster.AssumeRole(ctx, firstCallForClient, accessRequest) + require.NoError(t, err) + // The cert has changed, so after clearing stale clients, + // GetCachedClient should return a new client. + err = daemonService.ClearStaleCachedClientsForRoot(cluster.URI) + require.NoError(t, err) + thirdCallForClient, err := daemonService.GetCachedClient(ctx, cluster.URI) + require.NoError(t, err) + require.NotEqual(t, secondCallForClient, thirdCallForClient) +} + func testLogout(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.UserCreds) { ctx := context.Background() diff --git a/lib/client/clientcache/clientcache.go b/lib/client/clientcache/clientcache.go index f5e8f44aafdf9..c8f92b9718050 100644 --- a/lib/client/clientcache/clientcache.go +++ b/lib/client/clientcache/clientcache.go @@ -17,6 +17,7 @@ package clientcache import ( + "bytes" "context" "log/slog" "slices" @@ -35,12 +36,47 @@ import ( type Cache struct { cfg Config mu sync.RWMutex - // clients keeps a mapping from key (profile name and leaf cluster name) to cluster client. - clients map[key]*client.ClusterClient + // clients keeps a mapping from key (profile name and leaf cluster name) to client. + clients map[key]*clientWithMetadata // group prevents duplicate requests to create clients for a given cluster. group singleflight.Group } +type clientWithMetadata struct { + client *client.ClusterClient + // tlsCert is the certificate that was loaded when the client was created. + tlsCert []byte + // getProfile reads the fresh profile for the client from disk. + getProfile func() (profile, error) +} + +type profile interface { + // TLSCert returns the current TLS cert, stored in the agent. + TLSCert() ([]byte, error) +} + +// isTLSCertStale checks if the cached client uses the current TLS cert +// (read from the agent). If not, the TLS cert is considered stale. +func (c *clientWithMetadata) isTLSCertStale() (bool, error) { + pr, err := c.getProfile() + if err != nil { + if trace.IsNotFound(err) { + return true, nil + } + return false, trace.Wrap(err) + } + + tlsCert, err := pr.TLSCert() + if err != nil { + if trace.IsNotFound(err) { + return true, nil + } + return false, trace.Wrap(err) + } + + return !bytes.Equal(c.tlsCert, tlsCert), nil +} + // NewClientFunc is a function that will return a new [*client.TeleportClient] for a given profile and leaf // cluster. [leafClusterName] may be empty, in which case implementations should return a client for the root cluster. type NewClientFunc func(ctx context.Context, profileName, leafClusterName string) (*client.TeleportClient, error) @@ -89,7 +125,7 @@ func New(c Config) (*Cache, error) { return &Cache{ cfg: c, - clients: make(map[key]*client.ClusterClient), + clients: make(map[key]*clientWithMetadata), }, nil } @@ -100,7 +136,7 @@ func (c *Cache) Get(ctx context.Context, profileName, leafClusterName string) (* groupClt, err, _ := c.group.Do(k.String(), func() (any, error) { if fromCache := c.getFromCache(k); fromCache != nil { c.cfg.Logger.DebugContext(ctx, "Retrieved client from cache", "cluster", k) - return fromCache, nil + return fromCache.client, nil } tc, err := c.cfg.NewClientFunc(ctx, profileName, leafClusterName) @@ -120,8 +156,19 @@ func (c *Cache) Get(ctx context.Context, profileName, leafClusterName string) (* return nil, trace.Wrap(err) } + keyRing, err := tc.LocalAgent().GetCoreKeyRing() + if err != nil { + return nil, trace.Wrap(err) + } + // Save the client in the cache, so we don't have to build a new connection next time. - c.addToCache(k, newClient) + c.addToCache(k, &clientWithMetadata{ + client: newClient, + tlsCert: keyRing.TLSCert, + getProfile: func() (profile, error) { + return tc.GetProfile(tc.WebProxyAddr) + }, + }) c.cfg.Logger.InfoContext(ctx, "Added client to cache", "cluster", k) @@ -139,8 +186,25 @@ func (c *Cache) Get(ctx context.Context, profileName, leafClusterName string) (* return clt, nil } +// ClearOption configures ClearForRoot behavior. +type ClearOption func(*clearConfig) + +type clearConfig struct { + onlyClearClientsWithStaleCert bool +} + +// WithClearingOnlyClientsWithStaleCert closes only clients that use outdated certs. +func WithClearingOnlyClientsWithStaleCert() ClearOption { + return func(c *clearConfig) { c.onlyClearClientsWithStaleCert = true } +} + // ClearForRoot closes and removes clients from the cache for the root cluster and its leaf clusters. -func (c *Cache) ClearForRoot(profileName string) error { +func (c *Cache) ClearForRoot(profileName string, opts ...ClearOption) error { + cfg := &clearConfig{} + for _, o := range opts { + o(cfg) + } + c.mu.Lock() defer c.mu.Unlock() @@ -150,13 +214,23 @@ func (c *Cache) ClearForRoot(profileName string) error { ) for k, clt := range c.clients { - if k.profile == profileName { - if err := clt.Close(); err != nil { + if k.profile != profileName { + continue + } + if cfg.onlyClearClientsWithStaleCert { + stale, err := clt.isTLSCertStale() + // If an error occurs, close the client as well. + if err != nil { errors = append(errors, err) + } else if !stale { + continue } - deleted = append(deleted, k.String()) - delete(c.clients, k) } + if err := clt.client.Close(); err != nil { + errors = append(errors, err) + } + deleted = append(deleted, k.String()) + delete(c.clients, k) } c.cfg.Logger.InfoContext(context.Background(), "Invalidated cached clients for root cluster", @@ -165,7 +239,6 @@ func (c *Cache) ClearForRoot(profileName string) error { ) return trace.NewAggregate(errors...) - } // Clear closes and removes all clients. @@ -175,7 +248,7 @@ func (c *Cache) Clear() error { var errors []error for _, clt := range c.clients { - if err := clt.Close(); err != nil { + if err := clt.client.Close(); err != nil { errors = append(errors, err) } } @@ -184,14 +257,14 @@ func (c *Cache) Clear() error { return trace.NewAggregate(errors...) } -func (c *Cache) addToCache(k key, clusterClient *client.ClusterClient) { +func (c *Cache) addToCache(k key, clt *clientWithMetadata) { c.mu.Lock() defer c.mu.Unlock() - c.clients[k] = clusterClient + c.clients[k] = clt } -func (c *Cache) getFromCache(k key) *client.ClusterClient { +func (c *Cache) getFromCache(k key) *clientWithMetadata { c.mu.RLock() defer c.mu.RUnlock() @@ -241,7 +314,7 @@ func (c *NoCache) Get(ctx context.Context, profileName, leafClusterName string) return newClient, nil } -func (c *NoCache) ClearForRoot(profileName string) error { +func (c *NoCache) ClearForRoot(profileName string, _ ...ClearOption) error { c.mu.Lock() defer c.mu.Unlock() diff --git a/lib/client/clientcache/clientcache_test.go b/lib/client/clientcache/clientcache_test.go new file mode 100644 index 0000000000000..4e1aa962bb364 --- /dev/null +++ b/lib/client/clientcache/clientcache_test.go @@ -0,0 +1,180 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package clientcache + +import ( + "context" + "crypto/x509/pkix" + "log/slog" + "testing" + + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" + + apiprofile "github.com/gravitational/teleport/api/profile" + "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/lib/auth/testauthority" + "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/fixtures" + "github.com/gravitational/teleport/lib/observability/tracing" + "github.com/gravitational/teleport/lib/sshca" + "github.com/gravitational/teleport/lib/tlsca" +) + +func TestClearingClientsWithStaleCert(t *testing.T) { + privateKey, err := keys.ParsePrivateKey(fixtures.PEMBytes["rsa"]) + require.NoError(t, err) + tlsCert, sshCert, err := makeCerts(privateKey) + require.NoError(t, err) + + keyRing := client.NewKeyRing(privateKey, privateKey) + keyRing.KeyRingIndex = client.KeyRingIndex{ + ProxyHost: "localhost", + Username: "testuser", + ClusterName: "root", + } + keyRing.Cert = sshCert + keyRing.TLSCert = tlsCert + + profile := &apiprofile.Profile{ + WebProxyAddr: keyRing.ProxyHost, + Username: keyRing.Username, + SiteName: keyRing.ClusterName, + } + clientStore := client.NewFSClientStore(t.TempDir()) + err = clientStore.SaveProfile(profile, true) + require.NoError(t, err) + err = clientStore.AddKeyRing(keyRing) + require.NoError(t, err) + + cache, err := New(Config{ + NewClientFunc: func(ctx context.Context, profileName, leafClusterName string) (*client.TeleportClient, error) { + config := &client.Config{ + ClientStore: clientStore, + SSHProxyAddr: "localhost:3080", + WebProxyAddr: "localhost:3080", + Username: "testuser", + Tracer: tracing.NoopProvider().Tracer("test"), + SiteName: "root", + } + if leafClusterName != "" { + config.SiteName = leafClusterName + } + tc, err := client.NewClient(config) + + return tc, err + }, + RetryWithReloginFunc: func(ctx context.Context, tc *client.TeleportClient, fn func() error, opts ...client.RetryWithReloginOption) error { + return fn() + }, + Logger: slog.New(slog.DiscardHandler), + }) + require.NoError(t, err) + + // Get clients. + rootClient, err := cache.Get(t.Context(), "root", "") + require.NoError(t, err) + leaf1Client, err := cache.Get(t.Context(), "root", "leaf1") + require.NoError(t, err) + + // Update the TLS cert. + tlsCert, _, err = makeCerts(privateKey) + require.NoError(t, err) + keyRing.TLSCert = tlsCert + err = clientStore.AddKeyRing(keyRing) + require.NoError(t, err) + + // Get the client for a new leaf after the cert has been updated. + leaf2Client, err := cache.Get(t.Context(), "root", "leaf2") + require.NoError(t, err) + + // Clear stale clients. + err = cache.ClearForRoot("root", WithClearingOnlyClientsWithStaleCert()) + require.NoError(t, err) + + newRootClient, err := cache.Get(t.Context(), "root", "") + require.NoError(t, err) + newLeaf1Client, err := cache.Get(t.Context(), "root", "leaf1") + require.NoError(t, err) + newLeaf2Client, err := cache.Get(t.Context(), "root", "leaf2") + require.NoError(t, err) + // Clients opened before updating the cert should be reopened. + require.NotEqual(t, newRootClient, rootClient) + require.NotEqual(t, newLeaf1Client, leaf1Client) + // The client opened after updating the cert should be untouched. + require.Equal(t, newLeaf2Client, leaf2Client) +} + +// makeCerts makes TSL and SSH certs. +func makeCerts(privateKey *keys.PrivateKey) ([]byte, []byte, error) { + cert, err := tlsca.GenerateSelfSignedCAWithSigner(privateKey, pkix.Name{ + CommonName: "root", + Organization: []string{"localhost"}, + }, nil, defaults.CATTL) + if err != nil { + return nil, nil, trace.Wrap(err) + } + ca, err := tlsca.FromCertAndSigner(cert, privateKey) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + keygen := testauthority.New() + + clock := clockwork.NewRealClock() + identity := tlsca.Identity{ + Username: "testuser", + } + + subject, err := identity.Subject() + if err != nil { + return nil, nil, trace.Wrap(err) + } + + tlsCert, err := ca.GenerateCertificate(tlsca.CertificateRequest{ + Clock: clock, + PublicKey: privateKey.Public(), + Subject: subject, + NotAfter: clock.Now().UTC().Add(defaults.CATTL), + }) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + signer, err := keys.ParsePrivateKey([]byte(fixtures.SSHCAPrivateKey)) + if err != nil { + return nil, nil, trace.Wrap(err) + } + caSigner, err := ssh.NewSignerFromKey(signer) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + sshCert, err := keygen.GenerateUserCert(sshca.UserCertificateRequest{ + CASigner: caSigner, + PublicUserKey: ssh.MarshalAuthorizedKey(privateKey.SSHPublicKey()), + Identity: sshca.Identity{ + Username: "testuser", + Principals: []string{"testuser"}, + }, + }) + + return tlsCert, sshCert, trace.Wrap(err) +} diff --git a/lib/teleterm/apiserver/handler/handler_clusters.go b/lib/teleterm/apiserver/handler/handler_clusters.go index 1acc3b9be9648..8d25d1a5c1297 100644 --- a/lib/teleterm/apiserver/handler/handler_clusters.go +++ b/lib/teleterm/apiserver/handler/handler_clusters.go @@ -22,9 +22,11 @@ import ( "context" "github.com/gravitational/trace" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api/constants" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" + "github.com/gravitational/teleport/lib/teleterm/api/uri" "github.com/gravitational/teleport/lib/teleterm/clusters" ) @@ -82,6 +84,17 @@ func (s *Handler) GetCluster(ctx context.Context, req *api.GetClusterRequest) (* return apiRootClusterWithDetails, trace.Wrap(err) } +// ClearStaleClusterClients closes root and leaf cluster clients that use outdated TLS certificates. +func (s *Handler) ClearStaleClusterClients(_ context.Context, req *api.ClearStaleClusterClientsRequest) (*api.ClearStaleClusterClientsResponse, error) { + parsed, err := uri.Parse(req.RootClusterUri) + if err != nil { + return &api.ClearStaleClusterClientsResponse{}, trace.Wrap(err) + } + + err = s.DaemonService.ClearStaleCachedClientsForRoot(parsed) + return &api.ClearStaleClusterClientsResponse{}, trace.Wrap(err) +} + func newAPIRootCluster(cluster *clusters.Cluster) *api.Cluster { loggedInUser := cluster.GetLoggedInUser() @@ -95,6 +108,7 @@ func newAPIRootCluster(cluster *clusters.Cluster) *api.Cluster { Roles: loggedInUser.Roles, ActiveRequests: loggedInUser.ActiveRequests, IsDeviceTrusted: cluster.HasDeviceTrustExtensions(), + ValidUntil: timestamppb.New(loggedInUser.ValidUntil), }, SsoHost: cluster.SSOHost, } diff --git a/lib/teleterm/clusters/cluster.go b/lib/teleterm/clusters/cluster.go index 326ad470fe5d2..c8ae7ff6428f7 100644 --- a/lib/teleterm/clusters/cluster.go +++ b/lib/teleterm/clusters/cluster.go @@ -21,6 +21,7 @@ package clusters import ( "context" "log/slog" + "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -312,6 +313,7 @@ func (c *Cluster) GetLoggedInUser() LoggedInUser { SSHLogins: c.status.Logins, Roles: c.status.Roles, ActiveRequests: c.status.ActiveRequests, + ValidUntil: c.status.ValidUntil, } } @@ -353,6 +355,8 @@ type LoggedInUser struct { Roles []string // ActiveRequests is the user active requests ActiveRequests []string + // ValidUntil is expiration time of the certificate. + ValidUntil time.Time } // AddMetadataToRetryableError is Connect's equivalent of client.RetryWithRelogin. By adding the diff --git a/lib/teleterm/daemon/config.go b/lib/teleterm/daemon/config.go index a8b87de4af022..ee758d72e8cff 100644 --- a/lib/teleterm/daemon/config.go +++ b/lib/teleterm/daemon/config.go @@ -95,7 +95,7 @@ type ClientCache interface { Get(ctx context.Context, profileName, leafClusterName string) (*client.ClusterClient, error) // ClearForRoot closes and removes clients from the cache // for the root cluster and its leaf clusters. - ClearForRoot(profileName string) error + ClearForRoot(profileName string, opts ...clientcache.ClearOption) error // Clear closes and removes all clients. Clear() error } diff --git a/lib/teleterm/daemon/daemon.go b/lib/teleterm/daemon/daemon.go index 908e3855332dd..83c8757b4f711 100644 --- a/lib/teleterm/daemon/daemon.go +++ b/lib/teleterm/daemon/daemon.go @@ -40,6 +40,7 @@ import ( api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/client/clientcache" "github.com/gravitational/teleport/lib/client/sso" dtauthn "github.com/gravitational/teleport/lib/devicetrust/authn" "github.com/gravitational/teleport/lib/teleterm/api/uri" @@ -1283,6 +1284,14 @@ func (s *Service) ClearCachedClientsForRoot(clusterURI uri.ResourceURI) error { return trace.Wrap(s.clientCache.ClearForRoot(profileName)) } +// ClearStaleCachedClientsForRoot closes and removes clients from the cache +// for the root cluster and its leaf clusters, if their cert is outdated. +func (s *Service) ClearStaleCachedClientsForRoot(clusterURI uri.ResourceURI) error { + profileName := clusterURI.GetProfileName() + err := s.clientCache.ClearForRoot(profileName, clientcache.WithClearingOnlyClientsWithStaleCert()) + return trace.Wrap(err) +} + // SetSharedDirectoryForDesktopSession opens a directory for a desktop session and enables file system operations for it. // If there is no active desktop session associated with the specified desktop_uri and login, // an error is returned. diff --git a/proto/teleport/lib/teleterm/v1/cluster.proto b/proto/teleport/lib/teleterm/v1/cluster.proto index 6a9a3bdd92615..09170fa61af0b 100644 --- a/proto/teleport/lib/teleterm/v1/cluster.proto +++ b/proto/teleport/lib/teleterm/v1/cluster.proto @@ -20,6 +20,7 @@ syntax = "proto3"; package teleport.lib.teleterm.v1; +import "google/protobuf/timestamp.proto"; import "teleport/legacy/types/trusted_device_requirement.proto"; option go_package = "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1;teletermv1"; @@ -111,6 +112,8 @@ message LoggedInUser { bool is_device_trusted = 9; // Indicates whether access may be hindered by the lack of a trusted device. types.TrustedDeviceRequirement trusted_device_requirement = 10; + // Expiration time of the certificate. + google.protobuf.Timestamp valid_until = 11; } // ACL is the access control list of the user diff --git a/proto/teleport/lib/teleterm/v1/service.proto b/proto/teleport/lib/teleterm/v1/service.proto index 510ba471a9288..353433c59d6d8 100644 --- a/proto/teleport/lib/teleterm/v1/service.proto +++ b/proto/teleport/lib/teleterm/v1/service.proto @@ -135,6 +135,8 @@ service TerminalService { // Optionally removes the profile. // This operation is idempotent and can be safely invoked multiple times. rpc Logout(LogoutRequest) returns (EmptyResponse); + // Closes root and leaf cluster clients that use outdated TLS certificates. + rpc ClearStaleClusterClients(ClearStaleClusterClientsRequest) returns (ClearStaleClusterClientsResponse); // TransferFile sends a request to download/upload a file rpc TransferFile(FileTransferRequest) returns (stream FileTransferProgress); // ReportUsageEvent allows to send usage events that are then anonymized and forwarded to prehog @@ -204,6 +206,12 @@ message LogoutRequest { bool remove_profile = 2; } +message ClearStaleClusterClientsRequest { + string root_cluster_uri = 1; +} + +message ClearStaleClusterClientsResponse {} + message StartHeadlessWatcherRequest { string root_cluster_uri = 1; } diff --git a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts index d44986450fd1a..28a0b8aea954f 100644 --- a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts +++ b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts @@ -209,6 +209,12 @@ export class ClusterLifecycleManager { if (hasLoggedOut) { await this.handleClusterLogout(next); } else { + const client = await this.getTshdClient(); + // Only clear clients with outdated certificates. + // The watcher 'changed' event may be emitted right after the user logs in + // or assumes a role via Connect (which already closes all clients + // for the profile), so we avoid closing them again if they're already up to date. + await client.clearStaleClusterClients({ rootClusterUri: next.uri }); await this.syncOrUpdateCluster(next); } } diff --git a/web/packages/teleterm/src/services/tshd/cluster.ts b/web/packages/teleterm/src/services/tshd/cluster.ts index 52c36b930cb85..0a78c04011603 100644 --- a/web/packages/teleterm/src/services/tshd/cluster.ts +++ b/web/packages/teleterm/src/services/tshd/cluster.ts @@ -103,6 +103,7 @@ export function mergeClusterProfileWithDetails({ activeRequests: profile.loggedInUser.activeRequests, roles: profile.loggedInUser.roles, isDeviceTrusted: profile.loggedInUser.isDeviceTrusted, + validUntil: profile.loggedInUser.validUntil, userType: details.loggedInUser?.userType || LoggedInUser_UserType.UNSPECIFIED, trustedDeviceRequirement: diff --git a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts index 0b9651694b649..382f7bb109d2a 100644 --- a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts +++ b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts @@ -74,6 +74,7 @@ export class MockTshClient implements TshdClient { login = () => new MockedUnaryCall({}); loginPasswordless = undefined; logout = () => new MockedUnaryCall({}); + clearStaleClusterClients = () => new MockedUnaryCall({}); transferFile = undefined; reportUsageEvent = () => new MockedUnaryCall({}); createConnectMyComputerRole = () => diff --git a/web/packages/teleterm/src/services/tshd/testHelpers.ts b/web/packages/teleterm/src/services/tshd/testHelpers.ts index 476c992ee5f35..9cfb2fe025e2e 100644 --- a/web/packages/teleterm/src/services/tshd/testHelpers.ts +++ b/web/packages/teleterm/src/services/tshd/testHelpers.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Timestamp } from 'gen-proto-ts/google/protobuf/timestamp_pb'; import { TrustedDeviceRequirement } from 'gen-proto-ts/teleport/legacy/types/trusted_device_requirement_pb'; import { App } from 'gen-proto-ts/teleport/lib/teleterm/v1/app_pb'; import { @@ -24,6 +25,7 @@ import { } from 'gen-proto-ts/teleport/lib/teleterm/v1/auth_settings_pb'; import { ACL, + LoggedInUser_UserType, ShowResources, } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; import { WindowsDesktop } from 'gen-proto-ts/teleport/lib/teleterm/v1/windows_desktop_pb'; @@ -259,7 +261,8 @@ export const makeLoggedInUser = ( roles: [], requestableRoles: [], suggestedReviewers: [], - userType: tsh.LoggedInUser_UserType.LOCAL, + userType: LoggedInUser_UserType.LOCAL, + validUntil: Timestamp.fromDate(new Date()), ...props, }); diff --git a/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx b/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx index 4a4bcca868a7c..c854438d793dc 100644 --- a/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx @@ -16,9 +16,11 @@ * along with this program. If not, see . */ +import { Meta, StoryObj } from '@storybook/react-vite'; import { useLayoutEffect } from 'react'; import Flex from 'design/Flex'; +import { Timestamp } from 'gen-proto-ts/google/protobuf/timestamp_pb'; import { TrustedDeviceRequirement } from 'gen-proto-ts/teleport/legacy/types/trusted_device_requirement_pb'; import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; @@ -32,21 +34,122 @@ import { RootClusterUri } from 'teleterm/ui/uri'; import { IdentityContainer } from './Identity'; -export default { +interface StoryProps { + clusters: ('violet' | 'orange' | 'green')[]; + activeCluster: boolean; + activeClusterExpired: boolean; + deviceTrust: 'enrolled' | 'required-not-enrolled' | 'not-enrolled'; + showProfileErrors: boolean; +} + +const meta: Meta = { title: 'Teleterm/Identity', + component: props => { + const hasOrange = props.clusters.includes('orange'); + const hasViolet = props.clusters.includes('violet'); + const hasGreen = props.clusters.includes('green'); + const clusters = [ + hasOrange && + makeRootCluster({ + ...clusterOrange, + profileStatusError: props.showProfileErrors ? profileStatusError : '', + }), + hasViolet && + makeRootCluster({ + ...clusterViolet, + profileStatusError: props.showProfileErrors ? profileStatusError : '', + }), + hasGreen && + makeRootCluster({ + ...clusterGreen, + profileStatusError: props.showProfileErrors ? profileStatusError : '', + }), + ].filter(Boolean); + + const hasClusterWithLoggedInUser = + props.activeCluster && (hasOrange || hasViolet); + if (hasClusterWithLoggedInUser) { + clusters[0].loggedInUser = makeLoggedInUser({ + ...clusters[0].loggedInUser, + validUntil: Timestamp.fromDate( + props.activeClusterExpired + ? new Date() + : new Date(Date.now() + 24 * 60 * 60 * 1000) + ), + isDeviceTrusted: props.deviceTrust === 'enrolled', + trustedDeviceRequirement: + props.deviceTrust === 'required-not-enrolled' + ? TrustedDeviceRequirement.REQUIRED + : TrustedDeviceRequirement.NOT_REQUIRED, + }); + } + + return ( + + ); + }, + argTypes: { + clusters: { + control: { type: 'check' }, + options: ['violet', 'orange', 'green'], + description: 'List of clusters to show.', + }, + activeCluster: { + control: { type: 'boolean' }, + description: 'Makes "violet" or "orange" an active cluster.', + }, + deviceTrust: { + control: { type: 'radio' }, + options: ['enrolled', 'required-not-enrolled', 'not-enrolled'], + description: 'Controls device trust requirement.', + }, + activeClusterExpired: { + control: { type: 'boolean' }, + description: 'Whether the active cluster has expired cert.', + }, + showProfileErrors: { + control: { type: 'boolean' }, + description: 'Shows profile errors for all clusters.', + }, + }, + args: { + clusters: ['violet', 'orange', 'green'], + activeCluster: true, + deviceTrust: 'not-enrolled', + activeClusterExpired: false, + showProfileErrors: false, + }, }; +export default meta; + const clusterOrange = makeRootCluster({ - name: 'orange', + name: 'orange-psv-eindhoven-eredivisie-production-lorem-ipsum', loggedInUser: makeLoggedInUser({ - name: 'bob', - roles: ['access', 'editor'], + name: 'ruud-van-nistelrooy-van-der-sar', + roles: [ + 'circle-mark-app-access', + 'grafana-lite-app-access', + 'grafana-gold-app-access', + 'release-lion-app-access', + 'release-fox-app-access', + 'sales-center-lorem-app-access', + 'sales-center-ipsum-db-access', + 'sales-center-shop-app-access', + 'sales-center-floor-db-access', + ], }), uri: '/clusters/orange', }); const clusterViolet = makeRootCluster({ name: 'violet', - loggedInUser: makeLoggedInUser({ name: 'sammy' }), + loggedInUser: makeLoggedInUser({ + name: 'sammy', + roles: ['access', 'editor'], + }), uri: '/clusters/violet', }); const clusterGreen = makeRootCluster({ @@ -66,8 +169,14 @@ const OpenIdentityPopover = (props: { props.clusters.forEach(c => { ctx.addRootCluster(c); }); + ctx.workspacesService.addWorkspace(clusterGreen.uri); + ctx.workspacesService.addWorkspace(clusterViolet.uri); + ctx.workspacesService.addWorkspace(clusterOrange.uri); ctx.workspacesService.setState(draftState => { draftState.rootClusterUri = props.activeClusterUri; + draftState.workspaces[clusterGreen.uri].color = 'green'; + draftState.workspaces[clusterViolet.uri].color = 'purple'; + draftState.workspaces[clusterOrange.uri].color = 'yellow'; }); useOpenPopover(); @@ -98,125 +207,60 @@ const useOpenPopover = () => { }, []); }; -export function NoRootClusters() { - return ; -} - -export function OneClusterWithNoActiveCluster() { - return ( - - ); -} +export const NoRootClusters: StoryObj = { + args: { + clusters: [], + }, +}; -export function OneClusterWithActiveCluster() { - const cluster = makeRootCluster({ - loggedInUser: makeLoggedInUser({ - name: 'alice', - roles: ['access', 'editor'], - }), - }); +export const OneClusterWithNoActiveCluster: StoryObj = { + args: { + clusters: ['orange'], + activeCluster: false, + }, +}; - return ( - - ); -} +export const OneClusterWithActiveCluster: StoryObj = { + args: { + clusters: ['violet'], + }, +}; -export function ManyClustersWithNoActiveCluster() { - return ( - - ); -} +export const ManyClustersWithNoActiveCluster: StoryObj = { + args: { + clusters: ['orange', 'green', 'violet'], + activeCluster: false, + }, +}; -export function ManyClustersWithActiveCluster() { - return ( - - ); -} +export const ManyClustersWithActiveCluster: StoryObj = { + args: { + clusters: ['orange', 'green', 'violet'], + }, +}; -export function ManyClustersWithProfileErrorsAndActiveCluster() { - return ( - - ); -} +export const ManyClustersWithProfileErrorsAndActiveCluster: StoryObj = + { + args: { + clusters: ['orange', 'green', 'violet'], + showProfileErrors: true, + }, + }; -export function LongNamesWithManyRoles() { - return ( - - ); -} +export const TrustedDeviceEnrolled: StoryObj = { + args: { + deviceTrust: 'enrolled', + }, +}; -export function TrustedDeviceEnrolled() { - return ( - - ); -} +export const TrustedDeviceRequiredButNotEnrolled: StoryObj = { + args: { + deviceTrust: 'required-not-enrolled', + }, +}; -export function TrustedDeviceRequiredButNotEnrolled() { - return ( - - ); -} +export const ActiveClusterExpired: StoryObj = { + args: { + activeClusterExpired: true, + }, +}; diff --git a/web/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx b/web/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx index de66e52681542..5e52acd2965b1 100644 --- a/web/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx @@ -16,12 +16,20 @@ * along with this program. If not, see . */ +import { formatDistanceToNowStrict, isPast } from 'date-fns'; import { JSX } from 'react'; import styled from 'styled-components'; -import { ButtonText, Flex, Label, P3 } from 'design'; -import { Logout, Refresh, ShieldCheck, ShieldWarning } from 'design/Icon'; +import { ButtonText, Flex, Label, P3, Stack } from 'design'; +import { + Clock, + Logout, + Refresh, + ShieldCheck, + ShieldWarning, +} from 'design/Icon'; import Link from 'design/Link'; +import { Timestamp } from 'gen-proto-ts/google/protobuf/timestamp_pb'; import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; import { ProfileStatusError } from 'teleterm/ui/components/ProfileStatusError'; @@ -46,6 +54,10 @@ export function ActiveCluster(props: { onLogout(): void; }) { const clusterName = routing.parseClusterName(props.activeCluster.uri); + const validUntil = + props.activeCluster.loggedInUser?.validUntil && + Timestamp.toDate(props.activeCluster.loggedInUser.validUntil); + return ( <> @@ -103,7 +115,35 @@ export function ActiveCluster(props: { ))} - + + {validUntil && ( + + + + {isPast(validUntil) ? ( + 'Session expired.' + ) : ( + <> + Session expires{' '} + + {formatDistanceToNowStrict(validUntil, { + addSuffix: true, + })} + . + + + )} + + + )} + + From b5c5c0ac3170f3e984b27553e91ce66cf573ac90 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Tue, 18 Nov 2025 08:36:29 +0100 Subject: [PATCH 09/15] Gracefully handle missing `current-profile` and respect `TELEPORT_PROXY` in `tsh status` (#61295) * Respect `TELEPORT_PROXY` env var in `tsh status` * Enable listing profiles if there is no active profile * Add test * Define `err` within the block where it's actually used * Handle missing current profile in `tsh logout` * Make check more explicit * Revert mistakenly commited change (cherry picked from commit 95bec3a0c143374609772abf9759e6dbd2b6de2b) --- lib/client/client_store.go | 45 +++++++++---- lib/client/client_store_test.go | 9 ++- tool/tsh/common/tsh.go | 112 ++++++++++++++++++-------------- tool/tsh/common/tsh_test.go | 34 ++++++++++ 4 files changed, 140 insertions(+), 60 deletions(-) diff --git a/lib/client/client_store.go b/lib/client/client_store.go index d45037d5a0c94..ed6db939a8b09 100644 --- a/lib/client/client_store.go +++ b/lib/client/client_store.go @@ -238,16 +238,17 @@ func (s *Store) AddTrustedHostKeys(proxyHost string, clusterName string, hostKey // ReadProfileStatus returns the profile status for the given profile name. // If no profile name is provided, return the current profile. -func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) { +func (s *Store) ReadProfileStatus(proxyAddressOrProfile string) (*ProfileStatus, error) { var err error - if profileName == "" { + var profileName string + if proxyAddressOrProfile == "" { profileName, err = s.CurrentProfile() if err != nil { return nil, trace.Wrap(err) } } else { // remove ports from proxy host, because profile name is stored by host name - profileName, err = utils.Host(profileName) + profileName, err = utils.Host(proxyAddressOrProfile) if err != nil { return nil, trace.Wrap(err) } @@ -309,17 +310,37 @@ func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) { }) } -// FullProfileStatus returns the name of the current profile with a -// a list of all profile statuses. -func (s *Store) FullProfileStatus() (*ProfileStatus, []*ProfileStatus, error) { - currentProfileName, err := s.CurrentProfile() - if err != nil { - return nil, nil, trace.Wrap(err) +// FullProfileStatus returns the status of the active profile along with the +// statuses of all profiles. +// +// The active profile status is determined from the provided profile if given; +// otherwise, it is read from the current profile. +// +// The active profile status is nil if there is no active profile. +func (s *Store) FullProfileStatus(proxyAddressOrProfile string) (*ProfileStatus, []*ProfileStatus, error) { + var currentProfileName string + if proxyAddressOrProfile == "" { + profileName, err := s.CurrentProfile() + if err != nil && !trace.IsNotFound(err) { + return nil, nil, trace.Wrap(err) + } + currentProfileName = profileName + } else { + // Remove ports from proxy host, profile name is stored by host name. + profileName, err := utils.Host(proxyAddressOrProfile) + if err != nil { + return nil, nil, trace.Wrap(err) + } + currentProfileName = profileName } - currentProfile, err := s.ReadProfileStatus(currentProfileName) - if err != nil { - return nil, nil, trace.Wrap(err) + var currentProfile *ProfileStatus + if currentProfileName != "" { + profileStatus, err := s.ReadProfileStatus(currentProfileName) + if err != nil { + return nil, nil, trace.Wrap(err) + } + currentProfile = profileStatus } profileNames, err := s.ListProfiles() diff --git a/lib/client/client_store_test.go b/lib/client/client_store_test.go index c996e2a04189f..e4c44284e571c 100644 --- a/lib/client/client_store_test.go +++ b/lib/client/client_store_test.go @@ -304,11 +304,18 @@ func TestClientStore(t *testing.T) { }) require.NoError(t, err) - currentStatus, otherStatuses, err := clientStore.FullProfileStatus() + currentStatus, otherStatuses, err := clientStore.FullProfileStatus("") require.NoError(t, err) require.Equal(t, expectStatus, currentStatus) require.Len(t, otherStatuses, 1) require.Equal(t, expectOtherStatus, otherStatuses[0]) + + // Test passing the active profile in the parameter. + currentStatus, otherStatuses, err = clientStore.FullProfileStatus("other.example.com") + require.NoError(t, err) + require.Equal(t, expectOtherStatus, currentStatus) + require.Len(t, otherStatuses, 1) + require.Equal(t, expectStatus, otherStatuses[0]) }) }) } diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 5ef1184147978..dfa45070840c7 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -2656,7 +2656,22 @@ func onLogout(cf *CLIConf) error { fmt.Fprintf(cf.Stdout(), "Logged out %v from %v.\n", cf.Username, proxyHost) // Remove all keys. case proxyHost == "" && cf.Username == "": - tc, err := makeClient(cf) + proxy, err := cf.getClientStore().CurrentProfile() + if err != nil && !trace.IsNotFound(err) { + return trace.Wrap(err) + } + + if proxy == "" { + // If there's no current profile, try to use the first profile. + if len(profiles) > 0 { + proxy = profiles[0].ProxyURL.Host + } else { + fmt.Printf("All users logged out.\n") + return nil + } + } + + tc, err := makeClientForProxy(cf, proxy) if err != nil { return trace.Wrap(err) } @@ -5100,7 +5115,7 @@ func (c *CLIConf) ProfileStatus() (*client.ProfileStatus, error) { } func (c *CLIConf) FullProfileStatus() (*client.ProfileStatus, []*client.ProfileStatus, error) { - currentProfile, profiles, err := c.getClientStore().FullProfileStatus() + currentProfile, profiles, err := c.getClientStore().FullProfileStatus(c.Proxy) if err != nil { return nil, nil, trace.Wrap(err) } @@ -5326,7 +5341,7 @@ func humanFriendlyValidUntilDuration(validUntil time.Time, clock clockwork.Clock } // printStatus prints the status of the profile. -func printStatus(debug bool, p *profileInfo, env map[string]string, isActive bool) { +func printStatus(w io.Writer, debug bool, p *profileInfo, env map[string]string, isActive bool) { clock := clockwork.NewRealClock() var prefix string proxyURL := p.getProxyURLLine(isActive, env) @@ -5338,40 +5353,40 @@ func printStatus(debug bool, p *profileInfo, env map[string]string, isActive boo prefix = " " } - fmt.Printf("%vProfile URL: %v\n", prefix, proxyURL) + fmt.Fprintf(w, "%vProfile URL: %v\n", prefix, proxyURL) if debug { switch { case p.RelayAddr == "" && p.DefaultRelayAddr != "": - fmt.Printf(" Relay address: %v (default)\n", p.DefaultRelayAddr) + fmt.Fprintf(w, " Relay address: %v (default)\n", p.DefaultRelayAddr) case p.RelayAddr != "" && p.DefaultRelayAddr != "": - fmt.Printf(" Relay address: %v (default: %v)\n", p.RelayAddr, p.DefaultRelayAddr) + fmt.Fprintf(w, " Relay address: %v (default: %v)\n", p.RelayAddr, p.DefaultRelayAddr) case p.RelayAddr != "" && p.DefaultRelayAddr == "": - fmt.Printf(" Relay address: %v (no default)\n", p.RelayAddr) + fmt.Fprintf(w, " Relay address: %v (no default)\n", p.RelayAddr) default: - fmt.Printf(" Relay address: (none)\n") + fmt.Fprintf(w, " Relay address: (none)\n") } } else if relayAddr := cmp.Or(p.RelayAddr, p.DefaultRelayAddr); relayAddr != "" { - fmt.Printf(" Relay address: %v\n", relayAddr) + fmt.Fprintf(w, " Relay address: %v\n", relayAddr) } - fmt.Printf(" Logged in as: %v\n", p.Username) + fmt.Fprintf(w, " Logged in as: %v\n", p.Username) if len(p.ActiveRequests) != 0 { - fmt.Printf(" Active requests: %v\n", strings.Join(p.ActiveRequests, ", ")) + fmt.Fprintf(w, " Active requests: %v\n", strings.Join(p.ActiveRequests, ", ")) } if cluster != "" { - fmt.Printf(" Cluster: %v\n", cluster) + fmt.Fprintf(w, " Cluster: %v\n", cluster) } if p.Scope != "" { - fmt.Printf(" Scope: %v\n", p.Scope) - fmt.Printf(" Scoped Roles:\n") + fmt.Fprintf(w, " Scope: %v\n", p.Scope) + fmt.Fprintf(w, " Scoped Roles:\n") assignedScopes := slices.Collect(maps.Keys(p.ScopedRoles)) slices.Sort(assignedScopes) for _, scope := range assignedScopes { - fmt.Printf(" %s: %v\n", scope, rolesToString(debug, p.ScopedRoles[scope])) + fmt.Fprintf(w, " %s: %v\n", scope, rolesToString(debug, p.ScopedRoles[scope])) } } else { - fmt.Printf(" Roles: %v\n", rolesToString(debug, p.Roles)) + fmt.Fprintf(w, " Roles: %v\n", rolesToString(debug, p.Roles)) } if debug { var count int @@ -5380,60 +5395,60 @@ func printStatus(debug bool, p *profileInfo, env map[string]string, isActive boo continue } if count == 0 { - fmt.Printf(" Traits: %v: %v\n", k, v) + fmt.Fprintf(w, " Traits: %v: %v\n", k, v) } else { - fmt.Printf(" %v: %v\n", k, v) + fmt.Fprintf(w, " %v: %v\n", k, v) } count = count + 1 } } if len(p.Logins) > 0 { - fmt.Printf(" Logins: %v\n", strings.Join(p.Logins, ", ")) + fmt.Fprintf(w, " Logins: %v\n", strings.Join(p.Logins, ", ")) } if p.KubernetesEnabled { - fmt.Printf(" Kubernetes: enabled\n") + fmt.Fprintf(w, " Kubernetes: enabled\n") if kubeCluster != "" { - fmt.Printf(" Kubernetes cluster: %q\n", kubeCluster) + fmt.Fprintf(w, " Kubernetes cluster: %q\n", kubeCluster) } if len(p.KubernetesUsers) > 0 { - fmt.Printf(" Kubernetes users: %v\n", strings.Join(p.KubernetesUsers, ", ")) + fmt.Fprintf(w, " Kubernetes users: %v\n", strings.Join(p.KubernetesUsers, ", ")) } if len(p.KubernetesGroups) > 0 { - fmt.Printf(" Kubernetes groups: %v\n", strings.Join(p.KubernetesGroups, ", ")) + fmt.Fprintf(w, " Kubernetes groups: %v\n", strings.Join(p.KubernetesGroups, ", ")) } } else { - fmt.Printf(" Kubernetes: disabled\n") + fmt.Fprintf(w, " Kubernetes: disabled\n") } if len(p.Databases) != 0 { - fmt.Printf(" Databases: %v\n", strings.Join(p.Databases, ", ")) + fmt.Fprintf(w, " Databases: %v\n", strings.Join(p.Databases, ", ")) } if len(p.AllowedResourceIDs) > 0 { allowedResourcesStr, err := types.ResourceIDsToString(p.AllowedResourceIDs) if err != nil { logger.WarnContext(context.Background(), "failed to marshal allowed resource IDs to string", "error", err) } else { - fmt.Printf(" Allowed Resources: %s\n", allowedResourcesStr) + fmt.Fprintf(w, " Allowed Resources: %s\n", allowedResourcesStr) } } if p.GitHubIdentity != nil { - fmt.Printf(" GitHub username: %s\n", p.GitHubIdentity.Username) + fmt.Fprintf(w, " GitHub username: %s\n", p.GitHubIdentity.Username) } - fmt.Printf(" Valid until: %v [%v]\n", p.ValidUntil, humanFriendlyValidUntilDuration(p.ValidUntil, clock)) - fmt.Printf(" Extensions: %v\n", strings.Join(p.Extensions, ", ")) + fmt.Fprintf(w, " Valid until: %v [%v]\n", p.ValidUntil, humanFriendlyValidUntilDuration(p.ValidUntil, clock)) + fmt.Fprintf(w, " Extensions: %v\n", strings.Join(p.Extensions, ", ")) if debug { first := true for k, v := range p.CriticalOptions { if first { - fmt.Printf(" Critical options: %v: %v\n", k, v) + fmt.Fprintf(w, " Critical options: %v: %v\n", k, v) } else { - fmt.Printf(" %v: %v\n", k, v) + fmt.Fprintf(w, " %v: %v\n", k, v) } first = false } } - fmt.Printf("\n") + fmt.Fprintf(w, "\n") } func isOktaRole(role string) bool { @@ -5485,12 +5500,12 @@ func printLoginInformation(cf *CLIConf, profile *client.ProfileStatus, profiles // Print the active profile. if profile != nil { - printStatus(cf.Debug, active, env, true) + printStatus(cf.Stdout(), cf.Debug, active, env, true) } // Print all other profiles. for _, p := range others { - printStatus(cf.Debug, p, env, false) + printStatus(cf.Stdout(), cf.Debug, p, env, false) } // Print relevant active env vars, if they are set. @@ -5521,25 +5536,16 @@ func onStatus(cf *CLIConf) error { } return trace.Wrap(err) } - - // make the teleport client and retrieve the certificate from the proxy: - tc, err := makeClient(cf) - if err != nil { - logger.WarnContext(cf.Context, "Failed to make client for retrieving cluster alerts", "error", err) - return trace.Wrap(err) + if profile == nil && len(profiles) == 0 { + return trace.NotFound("Not logged in.") } - // `tsh status` should run without requiring user interaction. - // To achieve this, we avoid remote calls that might prompt for - // hardware key touch or require a PIN. - hardwareKeyInteractionRequired := tc.PrivateKeyPolicy.MFAVerified() - - if err := printLoginInformation(cf, profile, profiles); err != nil { + if err = printLoginInformation(cf, profile, profiles); err != nil { return trace.Wrap(err) } if profile == nil { - return trace.NotFound("Not logged in.") + return trace.NotFound("No active profile.") } duration := time.Until(profile.ValidUntil) @@ -5547,6 +5553,18 @@ func onStatus(cf *CLIConf) error { return trace.NotFound("Active profile expired.") } + // make the teleport client and retrieve the certificate from the proxy: + tc, err := makeClient(cf) + if err != nil { + logger.WarnContext(cf.Context, "Failed to make client for retrieving cluster alerts", "error", err) + return trace.Wrap(err) + } + + // `tsh status` should run without requiring user interaction. + // To achieve this, we avoid remote calls that might prompt for + // hardware key touch or require a PIN. + hardwareKeyInteractionRequired := tc.PrivateKeyPolicy.MFAVerified() + if hardwareKeyInteractionRequired { logger.DebugContext(cf.Context, "Skipping cluster alerts due to Hardware Key PIN/Touch requirement") } else { diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index caf01c525656b..5e1bf854ca16b 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -6107,6 +6107,13 @@ func TestLogout(t *testing.T) { require.NoError(t, err) }, }, + { + name: "current profile missing", + modifyKeyDir: func(t *testing.T, homePath string) { + currentProfileFilePath := keypaths.CurrentProfileFilePath(homePath) + require.NoError(t, os.Remove(currentProfileFilePath)) + }, + }, } { t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -6663,6 +6670,33 @@ func testListingResources[T any](t *testing.T, pack listPack[T], unmarshalFunc f } } +func TestStatusPrintsProfilesIfNoActiveProfile(t *testing.T) { + t.Parallel() + + buf := bytes.NewBuffer([]byte{}) + + err := Run(context.Background(), []string{ + "status", + }, setHomePath(t.TempDir()), func(c *CLIConf) error { + c.OverrideStdout = buf + profile := &profile.Profile{ + WebProxyAddr: "proxy:3080", + Username: "testuser", + } + // setCurrent is false, so there is no active profile. + err := c.getClientStore().SaveProfile(profile, false) + require.NoError(t, err) + return nil + }) + + require.Contains(t, buf.String(), + ` Profile URL: https://proxy:3080 + Logged in as: testuser + Cluster: proxy`) + require.True(t, trace.IsNotFound(err)) + require.ErrorContains(t, err, "No active profile.") +} + // TestProxyTemplates verifies proxy templates apply properly to client config. func TestProxyTemplatesMakeClient(t *testing.T) { t.Parallel() From b6750d9e830cb5fbf22c69de14075257beca6c5d Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Tue, 18 Nov 2025 08:48:03 +0100 Subject: [PATCH 10/15] Connect: switch tsh home directory to ~/.tsh (#61352) * Switch tsh home directory to ~/.tsh * Migrate old tsh home to new location, disallow updating fields outside the `state` key in app_state.json from the renderer process * Show banner about migrated tsh home * `promoteMigratedTshHome` -> `showTshHomeMigrationBanner` * `MigratedTshHomeBanner` -> `TshHomeMigrationBanner` * 'Profiles are' -> 'Profiles are now', remove unnecessary space * Fix assigning colors for new workspaces * Improve logs (cherry picked from commit 54b5f6cade849935bbeaa73fc9abbd1b3fdfa3cc) --- web/packages/teleterm/src/main.ts | 95 ++++++++++++++++++- .../src/mainProcess/fixtures/mocks.ts | 2 +- .../teleterm/src/mainProcess/mainProcess.ts | 6 +- .../src/mainProcess/runtimeSettings.ts | 10 +- .../teleterm/src/mainProcess/types.ts | 2 +- .../src/services/config/appConfigSchema.ts | 4 + .../services/fileStorage/fileStorageClient.ts | 11 ++- .../pty/ptyHost/buildPtyOptions.test.ts | 9 ++ .../services/pty/ptyHost/buildPtyOptions.ts | 3 +- .../teleterm/src/services/pty/ptyService.ts | 1 + .../ClusterConnectPanel.story.tsx | 12 ++- .../ClusterConnectPanel.tsx | 9 ++ .../src/ui/TopBar/Identity/Identity.story.tsx | 4 + .../src/ui/TopBar/Identity/Identity.tsx | 64 ++++++++++++- .../teleterm/src/ui/TopBar/Identity/index.ts | 5 +- .../statePersistenceService.ts | 2 + .../workspacesService.test.ts | 45 ++------- .../workspacesService/workspacesService.ts | 8 ++ 18 files changed, 232 insertions(+), 60 deletions(-) diff --git a/web/packages/teleterm/src/main.ts b/web/packages/teleterm/src/main.ts index 6ccac5e63faa3..d531e38c40762 100644 --- a/web/packages/teleterm/src/main.ts +++ b/web/packages/teleterm/src/main.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; @@ -32,9 +33,10 @@ import { enableWebHandlersProtection } from 'teleterm/mainProcess/protocolHandle import { getRuntimeSettings } from 'teleterm/mainProcess/runtimeSettings'; import { WindowsManager } from 'teleterm/mainProcess/windowsManager'; import { createConfigService } from 'teleterm/services/config'; -import { createFileStorage } from 'teleterm/services/fileStorage'; +import { createFileStorage, FileStorage } from 'teleterm/services/fileStorage'; import { createFileLoggerService, LoggerColor } from 'teleterm/services/logger'; import * as types from 'teleterm/types'; +import type { StatePersistenceState } from 'teleterm/ui/services/statePersistence'; import { assertUnreachable } from 'teleterm/ui/utils'; import { setTray } from './tray'; @@ -82,6 +84,16 @@ async function initializeApp(): Promise { settings, }); + const tshHome = configService.get('tshHome').value; + // Ensure the tsh directory exist. + await fs.mkdir(tshHome, { + recursive: true, + }); + + // TODO(gzdunek): DELETE IN 20.0.0. Users should already migrate to the new location. + // Also remove TshHomeMigrationBanner component and relevant properties from app_state.json. + await migrateOldTshHomeOnce(logger, tshHome, appStateFileStorage); + nativeTheme.themeSource = configService.get('theme').value; const windowsManager = new WindowsManager( appStateFileStorage, @@ -337,3 +349,84 @@ function showDialogWithError(title: string, unknownError: unknown) { const content = error.stack || error.message; dialog.showErrorBox(title, content); } + +/** + * Migrates the old "Teleport Connect/tsh" directory to the new location + * ("~/.tsh" by default) by copying all files recursively. + * Any failure in the migration process causes an early exit and marks it as processed. + * Retrying on the next launch could be harmful, since the user likely already + * re-added their profiles. + */ +async function migrateOldTshHomeOnce( + logger: Logger, + tshHome: string, + appStorage: FileStorage +): Promise { + const oldTshHome = path.resolve(app.getPath('userData'), 'tsh'); + const tshHomeMigrationKey = 'tshHomeMigration'; + const tshMigration: TshHomeMigration = + appStorage.get()?.[tshHomeMigrationKey]; + if (tshMigration?.processed) { + return; + } + + const markMigrationAsProcessed = (opts?: { noOldTshHome?: boolean }) => { + const migrationProcessed: TshHomeMigration = { processed: true }; + // The properties are separated, because `tshHomeMigration` should only + // be updated from the main process. + // The renderer can only update properties in the `state` key. + appStorage.put(tshHomeMigrationKey, migrationProcessed); + // Do not promote the shared tsh directory if there was nothing to migrate. + if (!opts?.noOldTshHome) { + // TODO(gzdunek): We need a better way to manage the app state. + const appState = (appStorage.get('state') || {}) as StatePersistenceState; + appState.showTshHomeMigrationBanner = true; + appStorage.put('state', appState); + } + }; + + // Check if the old directory exists. + try { + await fs.stat(oldTshHome); + } catch (err) { + if (err.code === 'ENOENT') { + logger.info( + 'Old tsh directory does not exist, marking migration as processed' + ); + markMigrationAsProcessed({ noOldTshHome: true }); + return; + } + logger.error('Failed to read old tsh directory', err); + markMigrationAsProcessed(); + return; + } + + // Perform the migration. + // The dereference option allows the source and the target to be symlinks. + // + // It may happen that the user already symlinked the global tsh home to the + // Electron's the home. + // In that case, the copy will fail with ERR_FS_CP_EINVAL error. + try { + await fs.cp(oldTshHome, tshHome, { + recursive: true, + force: true, + dereference: true, + }); + logger.info( + `Successfully copied tsh home directory from ${oldTshHome} to ${tshHome}` + ); + } catch (err) { + logger.error('Failed to copy tsh directory', err); + } finally { + markMigrationAsProcessed(); + } +} + +interface TshHomeMigration { + /** + * Indicates whether the old `tsh` directory has been migrated to the new location. + * `true` means the migration was attempted (successfully or not) and should not be retried. + */ + processed?: boolean; +} diff --git a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts index 658e81bd7af05..41b7471326b72 100644 --- a/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/web/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -245,7 +245,7 @@ export const makeRuntimeSettings = ( tshd: { requestedNetworkAddress: '', binaryPath: '', - homeDir: '', + defaultHomeDir: '', }, sharedProcess: { requestedNetworkAddress: '', diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index 7fe83186e4f21..578508c100593 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -223,7 +223,7 @@ export default class MainProcess { this.windowsManager ); const watcher = watchProfiles({ - tshDirectory: this.settings.tshd.homeDir, + tshDirectory: this.configService.get('tshHome').value, tshClient: { listRootClusters: async () => { const { terminalService } = await this.tshdClients; @@ -262,7 +262,7 @@ export default class MainProcess { } private initTshd() { - const { binaryPath, homeDir } = this.settings.tshd; + const { binaryPath } = this.settings.tshd; this.logger.info(`Starting tsh daemon from ${binaryPath}`); // spawn might either fail immediately by throwing an error or cause the error event to be emitted @@ -282,7 +282,7 @@ export default class MainProcess { windowsHide: true, env: { ...process.env, - TELEPORT_HOME: homeDir, + TELEPORT_HOME: this.configService.get('tshHome').value, [TSH_AUTOUPDATE_ENV_VAR]: TSH_AUTOUPDATE_OFF, }, } diff --git a/web/packages/teleterm/src/mainProcess/runtimeSettings.ts b/web/packages/teleterm/src/mainProcess/runtimeSettings.ts index e159d551e2edc..843d6c76ebe15 100644 --- a/web/packages/teleterm/src/mainProcess/runtimeSettings.ts +++ b/web/packages/teleterm/src/mainProcess/runtimeSettings.ts @@ -79,7 +79,7 @@ export async function getRuntimeSettings(): Promise { const tshd = { binaryPath: tshBinPath, - homeDir: getTshHomeDir(), + defaultHomeDir: path.resolve(app.getPath('home'), '.tsh'), requestedNetworkAddress: tshAddress, }; const sharedProcess = { @@ -147,14 +147,6 @@ function getKubeConfigsDir(): string { return kubeConfigsPath; } -function getTshHomeDir() { - const tshPath = path.resolve(app.getPath('userData'), 'tsh'); - if (!fs.existsSync(tshPath)) { - fs.mkdirSync(tshPath); - } - return tshPath; -} - // binDir is used in the packaged version to add tsh to PATH. // tshBinPath is used by Connect to call tsh directly. function getBinaryPaths(): { binDir?: string; tshBinPath: string } { diff --git a/web/packages/teleterm/src/mainProcess/types.ts b/web/packages/teleterm/src/mainProcess/types.ts index 1513a8a9187ea..439ea54ab21bd 100644 --- a/web/packages/teleterm/src/mainProcess/types.ts +++ b/web/packages/teleterm/src/mainProcess/types.ts @@ -76,7 +76,7 @@ export type RuntimeSettings = { tshd: { requestedNetworkAddress: string; binaryPath: string; - homeDir: string; + defaultHomeDir: string; }; sharedProcess: { requestedNetworkAddress: string; diff --git a/web/packages/teleterm/src/services/config/appConfigSchema.ts b/web/packages/teleterm/src/services/config/appConfigSchema.ts index d453dbdd8422f..e7600ae054886 100644 --- a/web/packages/teleterm/src/services/config/appConfigSchema.ts +++ b/web/packages/teleterm/src/services/config/appConfigSchema.ts @@ -70,6 +70,10 @@ export const createAppConfigSchema = (settings: RuntimeSettings) => { .describe( 'Keeps the app running in the menu bar/system tray even when the main window is closed. On Linux, displaying the system tray icon may require installing shell extensions.' ), + tshHome: z + .string() + .default(settings.tshd.defaultHomeDir) + .describe('Home location for tsh configuration and data.'), /** * This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated * in a styled component or used in CSS, as it may inject malicious CSS code. diff --git a/web/packages/teleterm/src/services/fileStorage/fileStorageClient.ts b/web/packages/teleterm/src/services/fileStorage/fileStorageClient.ts index c9820a6cb4bbc..c39f15808c75e 100644 --- a/web/packages/teleterm/src/services/fileStorage/fileStorageClient.ts +++ b/web/packages/teleterm/src/services/fileStorage/fileStorageClient.ts @@ -24,6 +24,8 @@ import { } from '../../mainProcess/types'; import { FileStorage } from './fileStorage'; +const APP_STATE_FIELDS_MODIFIABLE_FROM_RENDERER = ['state']; + // TODO(ravicious): The main process should not expose the whole interface of FileStorage to the // renderer, only what's absolutely needed by the renderer. FileStorage at the moment includes a // bunch of functions that are used only in the main process (and should be used only there). @@ -36,11 +38,18 @@ export function subscribeToFileStorageEvents(configService: FileStorage): void { case FileStorageEventType.Get: return (event.returnValue = configService.get(item.key)); case FileStorageEventType.Put: + if (!APP_STATE_FIELDS_MODIFIABLE_FROM_RENDERER.includes(item.key)) { + throw new Error( + `Could not update "${item.key}". This field is readonly in the renderer process.` + ); + } return configService.put(item.key, item.json); case FileStorageEventType.Write: return configService.write(); case FileStorageEventType.Replace: - return configService.replace(item.json); + throw new Error( + 'Replacing state is not allowed in the renderer process.' + ); case FileStorageEventType.GetFilePath: return configService.getFilePath(); case FileStorageEventType.GetFileName: diff --git a/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.test.ts b/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.test.ts index 4dbab055667d2..a58d1b37c9af9 100644 --- a/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.test.ts +++ b/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.test.ts @@ -67,6 +67,7 @@ describe('getPtyProcessOptions', () => { customShellPath: '', ssh: makeSshOptions(), windowsPty: { useConpty: true }, + tshHome: '', }, cmd: cmd, env: processEnv, @@ -102,6 +103,7 @@ describe('getPtyProcessOptions', () => { customShellPath: '', ssh: makeSshOptions(), windowsPty: { useConpty: true }, + tshHome: '', }, cmd: cmd, env: processEnv, @@ -134,6 +136,7 @@ describe('getPtyProcessOptions', () => { customShellPath: '', ssh: makeSshOptions({ noResume: true }), windowsPty: { useConpty: true }, + tshHome: '', }, cmd: cmd, env: processEnv, @@ -164,6 +167,7 @@ describe('getPtyProcessOptions', () => { customShellPath: '', ssh: makeSshOptions({ forwardAgent: true }), windowsPty: { useConpty: true }, + tshHome: '', }, cmd: cmd, env: processEnv, @@ -194,6 +198,7 @@ describe('getPtyProcessOptions', () => { customShellPath: '', ssh: makeSshOptions({ forwardAgent: false }), windowsPty: { useConpty: true }, + tshHome: '', }, cmd: cmd, env: processEnv, @@ -229,6 +234,7 @@ describe('buildPtyOptions', () => { customShellPath: '', ssh: makeSshOptions(), windowsPty: { useConpty: true }, + tshHome: '', }, cmd, }); @@ -256,6 +262,7 @@ describe('buildPtyOptions', () => { customShellPath: '/custom/shell/path/better-shell', ssh: makeSshOptions(), windowsPty: { useConpty: true }, + tshHome: '', }, cmd, }); @@ -283,6 +290,7 @@ describe('buildPtyOptions', () => { customShellPath: '', ssh: makeSshOptions(), windowsPty: { useConpty: true }, + tshHome: '', }, cmd, }); @@ -320,6 +328,7 @@ describe('buildPtyOptions', () => { customShellPath: '', ssh: makeSshOptions(), windowsPty: { useConpty: true }, + tshHome: '', }, cmd, processEnv: { diff --git a/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts b/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts index 90077b8359727..a8d71a5444b7d 100644 --- a/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts +++ b/web/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts @@ -44,6 +44,7 @@ type PtyOptions = { ssh: SshOptions; windowsPty: Pick; customShellPath: string; + tshHome: string; }; const WSLENV_VAR = 'WSLENV'; @@ -101,7 +102,7 @@ export async function buildPtyOptions({ ...shellEnv, TERM_PROGRAM: 'Teleport_Connect', TERM_PROGRAM_VERSION: settings.appVersion, - TELEPORT_HOME: settings.tshd.homeDir, + TELEPORT_HOME: options.tshHome, TELEPORT_CLUSTER: cmd.clusterName, TELEPORT_PROXY: cmd.proxyHost, [TSH_AUTOUPDATE_ENV_VAR]: TSH_AUTOUPDATE_OFF, diff --git a/web/packages/teleterm/src/services/pty/ptyService.ts b/web/packages/teleterm/src/services/pty/ptyService.ts index 6fadcfe1fe305..1e25c31d584d2 100644 --- a/web/packages/teleterm/src/services/pty/ptyService.ts +++ b/web/packages/teleterm/src/services/pty/ptyService.ts @@ -48,6 +48,7 @@ export function createPtyService( forwardAgent: configService.get('ssh.forwardAgent').value, }, customShellPath: configService.get('terminal.customShell').value, + tshHome: configService.get('tshHome').value, windowsPty, }, cmd: command, diff --git a/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.story.tsx b/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.story.tsx index a142f2855aa06..29b62365e72c5 100644 --- a/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.story.tsx +++ b/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.story.tsx @@ -57,10 +57,14 @@ export const WithClusters = () => { const ctx = new MockAppContext(); ctx.addRootCluster(clusterOrange); ctx.addRootCluster(clusterViolet); + ctx.statePersistenceService.putState({ + ...ctx.statePersistenceService.getState(), + showTshHomeMigrationBanner: true, + }); return ( - ; + ); }; @@ -74,9 +78,13 @@ export const WithErrors = () => { }) ); ctx.addRootCluster(clusterViolet); + ctx.statePersistenceService.putState({ + ...ctx.statePersistenceService.getState(), + showTshHomeMigrationBanner: true, + }); return ( - ; + ); }; diff --git a/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.tsx b/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.tsx index 9890f52be0355..3654ee2715efb 100644 --- a/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.tsx +++ b/web/packages/teleterm/src/ui/TabHost/ClusterConnectPanel/ClusterConnectPanel.tsx @@ -33,6 +33,7 @@ import { import { useAppContext } from 'teleterm/ui/appContextProvider'; import { NullKeyboardArrowsNavigation } from 'teleterm/ui/components/KeyboardArrowsNavigation/KeyboardArrowsNavigation'; import { useStoreSelector } from 'teleterm/ui/hooks/useStoreSelector'; +import { TshHomeMigrationBanner } from 'teleterm/ui/TopBar/Identity'; import { ClusterList } from 'teleterm/ui/TopBar/Identity/IdentityList/IdentityList'; import { RootClusterUri } from 'teleterm/ui/uri'; @@ -77,6 +78,14 @@ export function ClusterConnectPanel() { Log in to a cluster to use Teleport Connect. + {/* Apply the same styling as used for the cluster items below. */} + p.theme.space[1]}px; + border-radius: ${p => p.theme.radii[2]}px; + padding: ${p => p.theme.space[2]}px; + `} + /> {/*Disable arrows navigation, it doesn't work well here,*/} {/*since it requires the container to be focused.*/} {/*The user can navigate with Tab.*/} diff --git a/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx b/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx index c854438d793dc..e2d88090d4d10 100644 --- a/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Identity/Identity.story.tsx @@ -166,6 +166,10 @@ const OpenIdentityPopover = (props: { activeClusterUri: RootClusterUri | undefined; }) => { const ctx = new MockAppContext(); + ctx.statePersistenceService.putState({ + ...ctx.statePersistenceService.getState(), + showTshHomeMigrationBanner: true, + }); props.clusters.forEach(c => { ctx.addRootCluster(c); }); diff --git a/web/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx b/web/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx index 2cd6d874bd43a..2a49f42af6dc5 100644 --- a/web/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx @@ -19,12 +19,14 @@ import { useCallback, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; -import { Box } from 'design'; +import { Box, ButtonText, Flex, Link, P3 } from 'design'; +import { Cross, Stars } from 'design/Icon'; import Popover from 'design/Popover'; import { TrustedDeviceRequirement } from 'gen-proto-ts/teleport/legacy/types/trusted_device_requirement_pb'; import * as tshd from 'teleterm/services/tshd/types'; import { KeyboardArrowsNavigation } from 'teleterm/ui/components/KeyboardArrowsNavigation'; +import { usePersistedState } from 'teleterm/ui/hooks/usePersistedState'; import { useStoreSelector } from 'teleterm/ui/hooks/useStoreSelector'; import { useKeyboardShortcutFormatters, @@ -119,6 +121,7 @@ export function IdentityContainer() { deviceTrustStatus={deviceTrustStatus} /> )} + {focusGrabber} ); + +export function TshHomeMigrationBanner(props: { className?: string }) { + const [state, setState] = usePersistedState( + 'showTshHomeMigrationBanner', + false + ); + if (!state) { + return; + } + + return ( + + + + {/*This max width value prevents the banner from being too wide in ClusterConnectPanel. */} + + {/*Matches the look of a subtitle in TitleAndSubtitle. */} + + Profiles are now{' '} + + automatically synced + {' '} + between Teleport Connect and the tsh command-line tool. + + + setState(false)} + > + + + + + ); +} diff --git a/web/packages/teleterm/src/ui/TopBar/Identity/index.ts b/web/packages/teleterm/src/ui/TopBar/Identity/index.ts index 3297d80580132..0814cfe2f9f32 100644 --- a/web/packages/teleterm/src/ui/TopBar/Identity/index.ts +++ b/web/packages/teleterm/src/ui/TopBar/Identity/index.ts @@ -16,4 +16,7 @@ * along with this program. If not, see . */ -export { IdentityContainer as Identity } from './Identity'; +export { + IdentityContainer as Identity, + TshHomeMigrationBanner, +} from './Identity'; diff --git a/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts b/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts index 2828ffc4c93b9..6c59b2c387946 100644 --- a/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts +++ b/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts @@ -64,6 +64,8 @@ export interface StatePersistenceState { */ hasEverStarted: boolean; }; + /** Shows a banner above the cluster list to notify that a new tsh home dir is used. */ + showTshHomeMigrationBanner: boolean; } // Before adding new methods to this service, consider using usePersistedState instead. diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts index 3f1c67beaf7c4..3f80718dd3a7f 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts @@ -154,52 +154,25 @@ describe('restoring workspace', () => { }; const clusterBar = makeRootCluster({ uri: '/clusters/bar' }); const workspaceBar: PersistedWorkspace = { + color: 'purple', localClusterUri: clusterBar.uri, documents: [], location: undefined, }; const clusterBaz = makeRootCluster({ uri: '/clusters/baz' }); - const workspaceBaz: PersistedWorkspace = { - localClusterUri: clusterBaz.uri, - documents: [], - location: undefined, - }; const clusterQux = makeRootCluster({ uri: '/clusters/qux' }); - const workspaceQux: PersistedWorkspace = { - localClusterUri: clusterQux.uri, - documents: [], - location: undefined, - }; const clusterWaldo = makeRootCluster({ uri: '/clusters/waldo' }); - const workspaceWaldo: PersistedWorkspace = { - localClusterUri: clusterWaldo.uri, - documents: [], - location: undefined, - }; const clusterFred = makeRootCluster({ uri: '/clusters/fred' }); - const workspaceFred: PersistedWorkspace = { - localClusterUri: clusterFred.uri, - documents: [], - location: undefined, - }; const clusterGrault = makeRootCluster({ uri: '/clusters/grault' }); - const workspaceGrault: PersistedWorkspace = { - localClusterUri: clusterGrault.uri, - documents: [], - location: undefined, - }; const clusterPlugh = makeRootCluster({ uri: '/clusters/plugh' }); - const workspacePlugh: PersistedWorkspace = { - localClusterUri: clusterPlugh.uri, - documents: [], - location: undefined, - }; const { workspacesService } = getTestSetup({ cluster: [ clusterFoo, - clusterBar, + // The workspace for clusterBaz has no assigned color, but clusterBar's workspace does. + // Return clusterBaz first to verify that it receives a new, unused color. clusterBaz, + clusterBar, clusterQux, clusterWaldo, clusterFred, @@ -209,20 +182,14 @@ describe('restoring workspace', () => { persistedWorkspaces: { [clusterFoo.uri]: workspaceFoo, [clusterBar.uri]: workspaceBar, - [clusterBaz.uri]: workspaceBaz, - [clusterQux.uri]: workspaceQux, - [clusterWaldo.uri]: workspaceWaldo, - [clusterFred.uri]: workspaceFred, - [clusterGrault.uri]: workspaceGrault, - [clusterPlugh.uri]: workspacePlugh, }, }); workspacesService.restorePersistedState(); expect(workspacesService.getWorkspace(clusterFoo.uri).color).toBe('blue'); // read from disk - expect(workspacesService.getWorkspace(clusterBar.uri).color).toBe('purple'); // the first unused color - expect(workspacesService.getWorkspace(clusterBaz.uri).color).toBe('green'); + expect(workspacesService.getWorkspace(clusterBar.uri).color).toBe('purple'); // read from disk + expect(workspacesService.getWorkspace(clusterBaz.uri).color).toBe('green'); // the first unused color expect(workspacesService.getWorkspace(clusterQux.uri).color).toBe('yellow'); expect(workspacesService.getWorkspace(clusterWaldo.uri).color).toBe('red'); expect(workspacesService.getWorkspace(clusterFred.uri).color).toBe('cyan'); diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index 7a6c37b4a3808..636dc7f69aaa9 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -526,6 +526,14 @@ export class WorkspacesService extends ImmutableStore { this.restoredState = produce(restoredState, () => {}); const restoredWorkspaces = this.clustersService .getRootClusters() + // Start restoring clusters from the ones that already have a workspace. + // The algorithm that assigns a color in getWorkspaceDefaultState needs + // to know all used colors. + .toSorted((a, b) => { + const hasA = !!this.restoredState.workspaces[a.uri]; + const hasB = !!this.restoredState.workspaces[b.uri]; + return hasB === hasA ? 0 : hasA ? -1 : 1; + }) .reduce((workspaces, cluster) => { const restoredWorkspace = this.restoredState.workspaces[cluster.uri]; workspaces[cluster.uri] = getWorkspaceDefaultState( From 03da2f4cb449bf3338922f8bd43849427463e315 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 21 Nov 2025 09:11:04 +0100 Subject: [PATCH 11/15] Connect: refresh resources when access changes and add tests for `ClusterLifecycleManager` (#61479) * Detect when user's access changes * Refresh resources in UI when `did-change-access` is received * Add tests for `ClusterLifecycleManager` * Add better docs for ClusterLifecycleEvent * Test assuming requests too * Improve test names (cherry picked from commit 4b005206f18f510f3dad234aedd32807e43cebca) --- .../awaitableSender/awaitableSender.ts | 7 +- .../clusterLifecycleManager.test.ts | 352 ++++++++++++++++++ .../clusterLifecycleManager.ts | 104 +++++- .../mainProcess/clusterStore/clusterStore.ts | 9 +- .../teleterm/src/mainProcess/mainProcess.ts | 2 +- .../ui/AccessRequests/SelectorMenu.test.tsx | 51 ++- .../src/ui/AccessRequests/SelectorMenu.tsx | 3 - .../DocumentAccessRequests/useAssumeAccess.ts | 2 - .../DocumentCluster/resourcesContext.test.tsx | 9 +- .../ui/DocumentCluster/resourcesContext.tsx | 9 + web/packages/teleterm/src/ui/appContext.ts | 27 +- .../ui/services/clusters/clustersService.ts | 12 +- web/packages/teleterm/src/ui/types.ts | 8 + 13 files changed, 536 insertions(+), 59 deletions(-) create mode 100644 web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.test.ts diff --git a/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts index 9214fcce431c8..089534fd162f3 100644 --- a/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts +++ b/web/packages/teleterm/src/mainProcess/awaitableSender/awaitableSender.ts @@ -40,6 +40,11 @@ function isMessageAck(v: unknown): v is MessageAck { return typeof v === 'object' && 'type' in v && v.type === 'ack'; } +export interface IAwaitableSender { + send(payload: T, options?: { signal?: AbortSignal }): Promise; + whenDisposed(): Promise; +} + /** * Enables sending messages from the main process to the renderer * and awaiting delivery confirmation. @@ -48,7 +53,7 @@ function isMessageAck(v: unknown): v is MessageAck { * `AwaitableSender` is pull-based — the renderer must explicitly subscribe * to receive messages. */ -export class AwaitableSender { +export class AwaitableSender implements IAwaitableSender { private messages = new Map< string, { resolve(): void; reject(reason: unknown): void } diff --git a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.test.ts b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.test.ts new file mode 100644 index 0000000000000..fe6036c2a03f6 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.test.ts @@ -0,0 +1,352 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { enablePatches } from 'immer'; + +import Logger, { NullService } from 'teleterm/logger'; +import { IAwaitableSender } from 'teleterm/mainProcess/awaitableSender'; +import { ClusterStore } from 'teleterm/mainProcess/clusterStore'; +import { ProfileChangeSet } from 'teleterm/mainProcess/profileWatcher'; +import { RendererIpc } from 'teleterm/mainProcess/types'; +import { MockedUnaryCall } from 'teleterm/services/tshd/cloneableClient'; +import { MockTshClient } from 'teleterm/services/tshd/fixtures/mocks'; +import { + makeLoggedInUser, + makeRootCluster, +} from 'teleterm/services/tshd/testHelpers'; + +import { + ClusterLifecycleEvent, + ClusterLifecycleManager, +} from './clusterLifecycleManager'; + +beforeAll(() => { + Logger.init(new NullService()); +}); + +enablePatches(); + +const cluster = makeRootCluster(); + +const tests: { + name: string; + setup(opts: { tshdClient: MockTshClient }): Promise<{ + profileWatcher: () => AsyncGenerator; + throwInRendererHandler?: boolean; + }>; + expect(opts: { + clusterStore: ClusterStore; + tshdClient: MockTshClient; + rendererHandler: IAwaitableSender; + globalErrorHandler: jest.Mock; + }): void; +}[] = [ + { + name: 'when cluster is added, it updates state and notifies renderer', + setup: async () => { + return { + profileWatcher: makeWatcher([{ op: 'added', cluster }]), + }; + }, + expect: ({ clusterStore, rendererHandler }) => { + expect(clusterStore.getState().get(cluster.uri)).toBeDefined(); + expect(rendererHandler.send).toHaveBeenCalledWith({ + op: 'did-add-cluster', + uri: cluster.uri, + }); + }, + }, + { + name: 'when cluster is added and renderer fails, it updates state and reports an error', + setup: async () => { + return { + throwInRendererHandler: true, + profileWatcher: makeWatcher([{ op: 'added', cluster }]), + }; + }, + expect: ({ clusterStore, globalErrorHandler }) => { + expect(clusterStore.getState().get(cluster.uri)).toBeDefined(); + expect(globalErrorHandler).toHaveBeenCalledWith( + RendererIpc.ProfileWatcherError, + { + error: new Error('Error in renderer'), + reason: 'processing-error', + } + ); + }, + }, + { + name: 'when cluster is removed, it updates state and notifies renderer', + setup: async ({ tshdClient }) => { + jest.spyOn(tshdClient, 'logout'); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + return { + profileWatcher: makeWatcher([{ op: 'removed', cluster }]), + }; + }, + expect: ({ clusterStore, tshdClient, rendererHandler }) => { + expect(clusterStore.getState().get(cluster.uri)).toBeUndefined(); + expect(tshdClient.logout).toHaveBeenCalledWith({ + clusterUri: cluster.uri, + removeProfile: true, + }); + expect(rendererHandler.send).toHaveBeenCalledWith({ + op: 'will-logout-and-remove', + uri: cluster.uri, + }); + }, + }, + { + name: 'when cluster is removed and renderer fails, it keeps the cluster and reports an error', + setup: async ({ tshdClient }) => { + jest.spyOn(tshdClient, 'logout'); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + return { + throwInRendererHandler: true, + profileWatcher: makeWatcher([{ op: 'removed', cluster }]), + }; + }, + expect: ({ clusterStore, tshdClient, globalErrorHandler }) => { + expect(clusterStore.getState().get(cluster.uri)).toBeDefined(); + expect(tshdClient.logout).not.toHaveBeenCalled(); + expect(globalErrorHandler).toHaveBeenCalledWith( + RendererIpc.ProfileWatcherError, + { + error: new Error('Error in renderer'), + reason: 'processing-error', + } + ); + }, + }, + { + name: 'when cluster becomes logged-out, it updates state and notifies renderer', + setup: async ({ tshdClient }) => { + const next = makeRootCluster({ + connected: false, + loggedInUser: makeLoggedInUser({ name: '' }), + }); + jest.spyOn(tshdClient, 'logout'); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + return { + profileWatcher: makeWatcher([ + { op: 'changed', next, previous: cluster }, + ]), + }; + }, + expect: ({ clusterStore, tshdClient, rendererHandler }) => { + expect(clusterStore.getState().get(cluster.uri).loggedInUser.name).toBe( + '' + ); + expect(tshdClient.logout).toHaveBeenCalledWith({ + clusterUri: cluster.uri, + removeProfile: false, + }); + expect(rendererHandler.send).toHaveBeenCalledWith({ + op: 'will-logout', + uri: cluster.uri, + }); + }, + }, + { + name: 'when cluster becomes logged-out and renderer fails, it keeps logged-in state and reports an error', + setup: async ({ tshdClient }) => { + const next = makeRootCluster({ + connected: false, + loggedInUser: makeLoggedInUser({ name: '' }), + }); + jest.spyOn(tshdClient, 'logout'); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + return { + throwInRendererHandler: true, + profileWatcher: makeWatcher([ + { op: 'changed', next, previous: cluster }, + ]), + }; + }, + expect: ({ clusterStore, tshdClient, globalErrorHandler }) => { + expect(clusterStore.getState().get(cluster.uri).loggedInUser.name).toBe( + cluster.loggedInUser.name + ); + expect(tshdClient.logout).not.toHaveBeenCalled(); + expect(globalErrorHandler).toHaveBeenCalledWith( + RendererIpc.ProfileWatcherError, + { + error: new Error('Error in renderer'), + reason: 'processing-error', + } + ); + }, + }, + { + name: 'when cluster changes, it updates state and clears stale clients', + setup: async ({ tshdClient }) => { + const next = makeRootCluster({ + connected: false, + }); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + jest.spyOn(tshdClient, 'clearStaleClusterClients'); + return { + profileWatcher: makeWatcher([ + { op: 'changed', next, previous: cluster }, + ]), + }; + }, + expect: ({ clusterStore, tshdClient, rendererHandler }) => { + expect(clusterStore.getState().get(cluster.uri).connected).toBe(false); + expect(tshdClient.clearStaleClusterClients).toHaveBeenCalledWith({ + rootClusterUri: cluster.uri, + }); + expect(rendererHandler.send).not.toHaveBeenCalled(); + }, + }, + { + name: 'when access of logged in user changes, it updates state, clears stale clients, and notifies renderer', + setup: async ({ tshdClient }) => { + const next = makeRootCluster({ + loggedInUser: makeLoggedInUser({ activeRequests: ['abcd'] }), + }); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + jest.spyOn(tshdClient, 'clearStaleClusterClients'); + return { + profileWatcher: makeWatcher([ + { op: 'changed', next, previous: cluster }, + ]), + }; + }, + expect: ({ clusterStore, tshdClient, rendererHandler }) => { + expect( + clusterStore.getState().get(cluster.uri).loggedInUser.activeRequests + ).toEqual(['abcd']); + expect(tshdClient.clearStaleClusterClients).toHaveBeenCalledWith({ + rootClusterUri: cluster.uri, + }); + expect(rendererHandler.send).toHaveBeenCalledWith({ + op: 'did-change-access', + uri: cluster.uri, + }); + }, + }, + { + name: 'when access of logged in user changes and renderer fails, it updates state, clears stale clients and reports error', + setup: async ({ tshdClient }) => { + const next = makeRootCluster({ + loggedInUser: makeLoggedInUser({ activeRequests: ['abcd'] }), + }); + jest + .spyOn(tshdClient, 'listRootClusters') + .mockResolvedValue(new MockedUnaryCall({ clusters: [cluster] })); + jest.spyOn(tshdClient, 'clearStaleClusterClients'); + return { + throwInRendererHandler: true, + profileWatcher: makeWatcher([ + { op: 'changed', next, previous: cluster }, + ]), + }; + }, + expect: ({ clusterStore, tshdClient, globalErrorHandler }) => { + expect( + clusterStore.getState().get(cluster.uri).loggedInUser.activeRequests + ).toEqual(['abcd']); + expect(tshdClient.clearStaleClusterClients).toHaveBeenCalledWith({ + rootClusterUri: cluster.uri, + }); + expect(globalErrorHandler).toHaveBeenCalledWith( + RendererIpc.ProfileWatcherError, + { + error: new Error('Error in renderer'), + reason: 'processing-error', + } + ); + }, + }, +]; + +// eslint-disable-next-line jest/expect-expect +test.each(tests)('$name', async ({ setup, expect: testExpect }) => { + const mockTshdClient = new MockTshClient(); + const mockAppUpdater = { + maybeRemoveManagingCluster: jest.fn().mockResolvedValue(undefined), + }; + const clusterStore = new ClusterStore(async () => mockTshdClient, { + crashWindow: async () => {}, + }); + const globalErrorHandler = jest.fn(); + const windowsManager = { + getWindow: () => ({ + webContents: { + send: globalErrorHandler, + }, + }), + }; + const { profileWatcher, throwInRendererHandler } = await setup({ + tshdClient: mockTshdClient, + }); + + const done = Promise.withResolvers(); + const consumer = (async function* () { + try { + for await (const value of profileWatcher()) { + yield value; + } + } finally { + done.resolve(); + } + })(); + + const manager = new ClusterLifecycleManager( + clusterStore, + async () => mockTshdClient, + mockAppUpdater, + windowsManager, + consumer + ); + const mockRendererHandler = { + send: throwInRendererHandler + ? jest.fn().mockRejectedValue(new Error('Error in renderer')) + : jest.fn().mockResolvedValue(undefined), + whenDisposed: () => new Promise(() => {}), + }; + manager.setRendererEventHandler(mockRendererHandler); + await manager.syncRootClustersAndStartProfileWatcher(); + await done.promise; + + testExpect({ + globalErrorHandler, + clusterStore, + tshdClient: mockTshdClient, + rendererHandler: mockRendererHandler, + }); +}); + +function makeWatcher(...events: ProfileChangeSet[]) { + return async function* () { + yield* events; + }; +} diff --git a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts index 28a0b8aea954f..2301dc5b96d53 100644 --- a/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts +++ b/web/packages/teleterm/src/mainProcess/clusterLifecycleManager/clusterLifecycleManager.ts @@ -16,12 +16,14 @@ * along with this program. If not, see . */ -import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { + Cluster, + LoggedInUser, +} from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; import Logger from 'teleterm/logger'; -import { AwaitableSender } from 'teleterm/mainProcess/awaitableSender'; +import type { IAwaitableSender } from 'teleterm/mainProcess/awaitableSender'; import { RendererIpc } from 'teleterm/mainProcess/types'; -import type { WindowsManager } from 'teleterm/mainProcess/windowsManager'; import { AppUpdater } from 'teleterm/services/appUpdater'; import { isTshdRpcError, TshdClient } from 'teleterm/services/tshd'; import { mergeClusterProfileWithDetails } from 'teleterm/services/tshd/cluster'; @@ -41,8 +43,20 @@ export interface ClusterLifecycleEvent { * * Operations prefixed with `did-` occur after the action has already happened * in the main process, so they cannot prevent it. + * + * Operation meanings: + * * did-add-cluster - A cluster has been successfully added. + * * did-change-access - The logged-in user has changed, or their roles, + * or access requests have been updated. + * * will-logout - The user is about to be logged out. + * * will-logout-and-remove - The user is about to be logged out + * and their profile (cluster) will be removed. */ - op: 'did-add-cluster' | 'will-logout' | 'will-logout-and-remove'; + op: + | 'did-add-cluster' + | 'did-change-access' + | 'will-logout' + | 'will-logout-and-remove'; } export interface ProfileWatcherError { @@ -50,6 +64,12 @@ export interface ProfileWatcherError { reason: 'processing-error' | 'exited'; } +interface WindowsManager { + getWindow(): { + webContents: { send(channel: string, ...args: any[]): void }; + }; +} + /** * Manages the lifecycle of clusters by handling both UI actions that update them * (e.g., adding a cluster, logging out) and profile watcher events. @@ -67,20 +87,20 @@ export interface ProfileWatcherError { export class ClusterLifecycleManager { private readonly logger = new Logger('ClusterLifecycleManager'); private rendererEventHandler: - | AwaitableSender + | IAwaitableSender | undefined; private watcherStarted = false; constructor( private readonly clusterStore: ClusterStore, private readonly getTshdClient: () => Promise, - private readonly appUpdater: AppUpdater, - private readonly windowsManager: Pick, + private readonly appUpdater: Pick, + private readonly windowsManager: WindowsManager, private readonly profileWatcher: AsyncIterable ) {} setRendererEventHandler( - handler: AwaitableSender + handler: IAwaitableSender ): void { if (this.rendererEventHandler) { this.logger.error( @@ -112,6 +132,18 @@ export class ClusterLifecycleManager { await this.clusterStore.logoutAndRemove(uri); } + async syncCluster(uri: RootClusterUri): Promise { + const { previous, next } = await this.clusterStore.sync(uri); + if (!hasAccessChanged(previous.loggedInUser, next.loggedInUser)) { + return; + } + + await this.rendererEventHandler.send({ + op: 'did-change-access', + uri: next.uri, + }); + } + async syncRootClustersAndStartProfileWatcher(): Promise { await this.clusterStore.syncRootClusters(); if (!this.watcherStarted) { @@ -135,7 +167,7 @@ export class ClusterLifecycleManager { private async syncOrUpdateCluster(cluster: Cluster): Promise { if (cluster.connected) { try { - return this.clusterStore.sync(cluster.uri); + await this.clusterStore.sync(cluster.uri); } catch (e) { // Theoretically, the cert could just expire and result in an error // resolvable with relogin when trying to sync the cluster. @@ -208,15 +240,24 @@ export class ClusterLifecycleManager { if (hasLoggedOut) { await this.handleClusterLogout(next); - } else { - const client = await this.getTshdClient(); - // Only clear clients with outdated certificates. - // The watcher 'changed' event may be emitted right after the user logs in - // or assumes a role via Connect (which already closes all clients - // for the profile), so we avoid closing them again if they're already up to date. - await client.clearStaleClusterClients({ rootClusterUri: next.uri }); - await this.syncOrUpdateCluster(next); + return; } + + const client = await this.getTshdClient(); + // Only clear clients with outdated certificates. + // The watcher 'changed' event may be emitted right after the user logs in + // or assumes a role via Connect (which already closes all clients + // for the profile), so we avoid closing them again if they're already up to date. + await client.clearStaleClusterClients({ rootClusterUri: next.uri }); + await this.syncOrUpdateCluster(next); + + if (!hasAccessChanged(previous.loggedInUser, next.loggedInUser)) { + return; + } + await this.rendererEventHandler.send({ + op: 'did-change-access', + uri: next.uri, + }); } private async handleClusterRemoved(cluster: Cluster): Promise { @@ -244,3 +285,32 @@ export class ClusterLifecycleManager { .webContents.send(RendererIpc.ProfileWatcherError, error); } } + +/** + * Checks if the username, roles or active requests changed. + * If yes, then probably the user has access to different resources. + */ +function hasAccessChanged( + previousUser: LoggedInUser | undefined, + nextUser: LoggedInUser | undefined +): boolean { + // No user, we don't know if access changed. + if (!(previousUser?.name && nextUser?.name)) { + return false; + } + + const hasChangedUsername = previousUser.name !== nextUser.name; + const hasChangedRoles = !areArraysEqual(previousUser.roles, nextUser.roles); + const hasChangedActiveRequests = !areArraysEqual( + previousUser.activeRequests, + nextUser.activeRequests + ); + + return hasChangedUsername || hasChangedRoles || hasChangedActiveRequests; +} + +function areArraysEqual(a: string[], b: string[]): boolean { + const aSet = new Set(a); + const bSet = new Set(b); + return aSet.size === bSet.size && aSet.isSubsetOf(bSet); +} diff --git a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts index ad5baeb93b20e..83d5ef65c5ccf 100644 --- a/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts +++ b/web/packages/teleterm/src/mainProcess/clusterStore/clusterStore.ts @@ -107,10 +107,13 @@ export class ClusterStore { } /** - * Synchronizes a root cluster. + * Synchronizes the root cluster and returns its state before and after the update. * Makes network calls to get cluster details and its leaf clusters. + * Should only be called via ClusterLifecycleManager. */ - async sync(uri: RootClusterUri): Promise { + async sync( + uri: RootClusterUri + ): Promise<{ previous: Cluster | undefined; next: Cluster }> { let cluster: Cluster; let leafs: Cluster[]; const client = await this.getTshdClient(); @@ -132,12 +135,14 @@ export class ClusterStore { throw error; } + const previous = this.state.get(uri); await this.update(draft => { draft.set(cluster.uri, cluster); leafs.forEach(leaf => { draft.set(leaf.uri, leaf); }); }); + return { previous, next: cluster }; } async set(cluster: Cluster): Promise { diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index 578508c100593..7b9ef31a1501e 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -708,7 +708,7 @@ export default class MainProcess { ); ipcMain.handle(MainProcessIpc.SyncCluster, (_, args) => - this.clusterStore.sync(args.clusterUri) + this.clusterLifecycleManager.syncCluster(args.clusterUri) ); ipcMain.handle(MainProcessIpc.Logout, async (_, args) => { diff --git a/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.test.tsx b/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.test.tsx index 9193a52a10b9e..1af297ba33312 100644 --- a/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.test.tsx +++ b/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.test.tsx @@ -18,7 +18,6 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useEffect } from 'react'; import { render } from 'design/utils/testing'; @@ -29,27 +28,35 @@ import { makeRootCluster, } from 'teleterm/services/tshd/testHelpers'; import { SelectorMenu } from 'teleterm/ui/AccessRequests/SelectorMenu'; -import { - ResourcesContextProvider, - useResourcesContext, -} from 'teleterm/ui/DocumentCluster/resourcesContext'; +import { ResourcesContextProvider } from 'teleterm/ui/DocumentCluster/resourcesContext'; import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; import { MockWorkspaceContextProvider } from 'teleterm/ui/fixtures/MockWorkspaceContextProvider'; -import { RootClusterUri } from 'teleterm/ui/uri'; import { AccessRequestsContextProvider } from './AccessRequestsContext'; -test('assuming or dropping a request refreshes resources', async () => { +test('assuming or dropping a request calls API', async () => { const appContext = new MockAppContext(); const accessRequest = makeAccessRequest(); const cluster = makeRootCluster({ features: { advancedAccessWorkflows: true, isUsageBasedBilling: false }, - loggedInUser: makeLoggedInUser({ activeRequests: [accessRequest.id] }), }); appContext.addRootCluster(cluster); jest.spyOn(appContext.clustersService, 'dropRoles'); - const refreshListener = jest.fn(); + jest + .spyOn(appContext.clustersService, 'assumeRoles') + .mockImplementation(async () => { + appContext.clustersService.setState(state => { + state.clusters.get(cluster.uri).loggedInUser.activeRequests = [ + accessRequest.id, + ]; + }); + }); + appContext.tshd.getAccessRequests = () => { + return new MockedUnaryCall({ + requests: [accessRequest], + }); + }; appContext.tshd.getAccessRequest = () => { return new MockedUnaryCall({ request: accessRequest, @@ -61,10 +68,6 @@ test('assuming or dropping a request refreshes resources', async () => { - @@ -77,12 +80,18 @@ test('assuming or dropping a request refreshes resources', async () => { const item = await screen.findByText(accessRequest.resources.at(0).id.name); await userEvent.click(item); + expect(appContext.clustersService.assumeRoles).toHaveBeenCalledTimes(1); + expect(appContext.clustersService.assumeRoles).toHaveBeenCalledWith( + cluster.uri, + [accessRequest.id] + ); + expect(await screen.findByText(/access assumed/i)).toBeInTheDocument(); + await userEvent.click(item); expect(appContext.clustersService.dropRoles).toHaveBeenCalledTimes(1); expect(appContext.clustersService.dropRoles).toHaveBeenCalledWith( cluster.uri, [accessRequest.id] ); - expect(refreshListener).toHaveBeenCalledTimes(1); }); test('assumed request are always visible, even if getAccessRequests no longer returns them', async () => { @@ -139,17 +148,3 @@ test('assumed request are always visible, even if getAccessRequests no longer re await screen.findByText('request-with-details-not-available') ).toBeInTheDocument(); }); - -function RequestRefreshSubscriber(props: { - rootClusterUri: RootClusterUri; - onResourcesRefreshRequest: () => void; -}) { - const { onResourcesRefreshRequest } = useResourcesContext( - props.rootClusterUri - ); - useEffect(() => { - onResourcesRefreshRequest(props.onResourcesRefreshRequest); - }, [onResourcesRefreshRequest, props.onResourcesRefreshRequest]); - - return null; -} diff --git a/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.tsx b/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.tsx index ffd9c1a72d5af..96d4a2b55246e 100644 --- a/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.tsx +++ b/web/packages/teleterm/src/ui/AccessRequests/SelectorMenu.tsx @@ -44,7 +44,6 @@ import { MenuListItem, Separator, } from 'teleterm/ui/components/Menu'; -import { useResourcesContext } from 'teleterm/ui/DocumentCluster/resourcesContext'; import { useWorkspaceContext } from 'teleterm/ui/Documents'; import { useLoggedInUser } from 'teleterm/ui/hooks/useLoggedInUser'; import { TopBarButton } from 'teleterm/ui/TopBar/TopBarButton'; @@ -83,7 +82,6 @@ export function SelectorMenu() { const ctx = useAppContext(); const { clustersService } = ctx; const selectorRef = useRef(null); - const { requestResourcesRefresh } = useResourcesContext(rootClusterUri); const loggedInUser = useLoggedInUser(); const username = loggedInUser?.name; @@ -210,7 +208,6 @@ export function SelectorMenu() { await clustersService.assumeRoles(rootClusterUri, [requestId]); } }); - requestResourcesRefresh(); } const isResourceRequestAssumed = sortedRequests diff --git a/web/packages/teleterm/src/ui/DocumentAccessRequests/useAssumeAccess.ts b/web/packages/teleterm/src/ui/DocumentAccessRequests/useAssumeAccess.ts index ed4742889ada8..63639d475b3b7 100644 --- a/web/packages/teleterm/src/ui/DocumentAccessRequests/useAssumeAccess.ts +++ b/web/packages/teleterm/src/ui/DocumentAccessRequests/useAssumeAccess.ts @@ -35,8 +35,6 @@ export function useAssumeAccess() { const [assumeRoleAttempt, runAssumeRole] = useAsync((requestId: string) => retryWithRelogin(ctx, clusterUri, async () => { await ctx.clustersService.assumeRoles(rootClusterUri, [requestId]); - // Refresh the current resource tabs in the workspace. - requestResourcesRefresh(); }) ); diff --git a/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.test.tsx b/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.test.tsx index 12c220e43632b..3c653eb549f15 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.test.tsx +++ b/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.test.tsx @@ -19,6 +19,7 @@ import { renderHook } from '@testing-library/react'; import { rootClusterUri } from 'teleterm/services/tshd/testHelpers'; +import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; import { ResourcesContextProvider, @@ -28,7 +29,9 @@ import { describe('requestResourcesRefresh', () => { it('calls listener registered with onResourcesRefreshRequest', () => { const wrapper = ({ children }) => ( - {children} + + {children} + ); const { result } = renderHook( () => ({ @@ -58,7 +61,9 @@ describe('requestResourcesRefresh', () => { describe('onResourcesRefreshRequest cleanup function', () => { it('removes the listener', () => { const wrapper = ({ children }) => ( - {children} + + {children} + ); const { result } = renderHook(() => useResourcesContext(rootClusterUri), { wrapper, diff --git a/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.tsx b/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.tsx index 13c8964127ea8..e5bb664dc8ac5 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.tsx +++ b/web/packages/teleterm/src/ui/DocumentCluster/resourcesContext.tsx @@ -24,9 +24,11 @@ import { PropsWithChildren, useCallback, useContext, + useEffect, useRef, } from 'react'; +import { useAppContext } from 'teleterm/ui/appContextProvider'; import { RootClusterUri } from 'teleterm/ui/uri'; export interface ResourcesContext { @@ -54,6 +56,7 @@ export interface ResourcesContext { const ResourcesContext = createContext(null); export const ResourcesContextProvider: FC = props => { + const appCtx = useAppContext(); const emitterRef = useRef(undefined); if (!emitterRef.current) { emitterRef.current = new EventEmitter(); @@ -86,6 +89,12 @@ export const ResourcesContextProvider: FC = props => { [] ); + useEffect(() => { + return appCtx.addResourceRefreshListener(uri => { + requestResourcesRefresh(uri); + }); + }, [appCtx, requestResourcesRefresh]); + return ( void { + this._resourceRefreshListener = listener; + + return () => { + this._resourceRefreshListener = undefined; + }; + } + + /** Gets called when `ClusterLifecycleManager` requests resource refresh. */ + get resourceRefreshListener(): ResourceRefreshListener { + return this._resourceRefreshListener; + } + private registerClusterLifecycleHandler(): void { // Queue chain ensures sequential processing. let processingQueue = Promise.resolve(); @@ -211,6 +230,12 @@ export default class AppContext implements IAppContext { switch (op) { case 'did-add-cluster': return this.workspacesService.addWorkspace(uri); + case 'did-change-access': + if (!this.clustersService.findCluster(uri).connected) { + // Only refresh resources when the cluster is connected. + return; + } + return this.resourceRefreshListener(uri); case 'will-logout': return cleanUpBeforeLogout(this, uri, { removeWorkspace: false }); case 'will-logout-and-remove': diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index 5cd44a42d4c03..19806324102a3 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -230,7 +230,11 @@ export class ClustersService extends ImmutableStore { } } - /** Assumes roles for the given requests. */ + /** + * Assumes roles for the given requests. + * After it's done, resources refresh is requested in + * ClusterLifecycleManager.syncCluster. + */ async assumeRoles( rootClusterUri: uri.RootClusterUri, requestIds: string[] @@ -244,7 +248,11 @@ export class ClustersService extends ImmutableStore { await this.syncRootCluster(rootClusterUri); } - /** Drops roles for the given requests. */ + /** + * Drops roles for the given requests. + * After it's done, resources refresh is requested in + * ClusterLifecycleManager.syncCluster. + */ async dropRoles( rootClusterUri: uri.RootClusterUri, requestIds: string[] diff --git a/web/packages/teleterm/src/ui/types.ts b/web/packages/teleterm/src/ui/types.ts index ed00bfb246c14..09ec13cfc14f5 100644 --- a/web/packages/teleterm/src/ui/types.ts +++ b/web/packages/teleterm/src/ui/types.ts @@ -39,6 +39,7 @@ import { TerminalsService } from 'teleterm/ui/services/terminals'; import { TshdNotificationsService } from 'teleterm/ui/services/tshdNotifications/tshdNotificationService'; import { UsageService } from 'teleterm/ui/services/usage'; import { WorkspacesService } from 'teleterm/ui/services/workspacesService'; +import { RootClusterUri } from 'teleterm/ui/uri'; export interface IAppContext { clustersService: ClustersService; @@ -83,8 +84,15 @@ export interface IAppContext { * process (renderer). */ unexpectedVnetShutdownListener: UnexpectedVnetShutdownListener | undefined; + + /** Sets the listener and returns a cleanup function which removes the listener. */ + addResourceRefreshListener: (listener: ResourceRefreshListener) => () => void; + /** Gets called when `ClusterLifecycleManager` requests resource refresh. */ + resourceRefreshListener: ResourceRefreshListener | undefined; } export type UnexpectedVnetShutdownListener = ( request: tshdEventsApi.ReportUnexpectedVnetShutdownRequest ) => void; + +export type ResourceRefreshListener = (uri: RootClusterUri) => void; From 572e0ebcdfcbbde16a3619945ffa781f4bfc4a2e Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Tue, 25 Nov 2025 12:49:55 +0100 Subject: [PATCH 12/15] Set up deep links as soon as possible (#61668) (cherry picked from commit 0b5ab6b81fd44871b45749dade5ef8f6be1fb246) --- web/packages/teleterm/src/main.ts | 84 +++++++++++++++++-------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/web/packages/teleterm/src/main.ts b/web/packages/teleterm/src/main.ts index d531e38c40762..ec6c9efad5899 100644 --- a/web/packages/teleterm/src/main.ts +++ b/web/packages/teleterm/src/main.ts @@ -71,7 +71,15 @@ async function initializeApp(): Promise { updateSessionDataPath(); const settings = await getRuntimeSettings(); const logger = initMainLogger(settings); + + process.on('uncaughtException', (error, origin) => { + logger.error('Uncaught exception', origin, error); + showDialogWithError(`Uncaught exception (${origin} origin)`, error); + app.exit(1); + }); + logger.info(`Starting ${app.getName()} version ${app.getVersion()}`); + const { appStateFileStorage, configFileStorage, @@ -84,6 +92,36 @@ async function initializeApp(): Promise { settings, }); + nativeTheme.themeSource = configService.get('theme').value; + const windowsManager = new WindowsManager( + appStateFileStorage, + settings, + configService + ); + + // On Windows/Linux: Re-launching the app while it's already running + // triggers 'second-instance' (because of app.requestSingleInstanceLock()). + // + // On macOS: Re-launching the app (from places like Finder, Spotlight, or Dock) + // does not trigger 'second-instance'. Instead, the system emits 'activate'. + // However, launching the app outside the desktop manager (e.g., from the command + // line) does trigger 'second-instance'. + app.on('second-instance', () => { + windowsManager.focusWindow(); + }); + app.on('activate', () => { + windowsManager.focusWindow(); + }); + + // Since setUpDeepLinks adds another listener for second-instance, it's important to call it after + // the listener which calls windowsManager.focusWindow. This way the focus will be brought to the + // window before processing the listener for deep links. + // + // This must be called as early as possible, before an async code. + // Otherwise, if the app is launched via a macOS deep link, the 'open-url' event may be emitted + // before a handler is registered, causing the link to be lost. + setUpDeepLinks(logger, windowsManager, settings); + const tshHome = configService.get('tshHome').value; // Ensure the tsh directory exist. await fs.mkdir(tshHome, { @@ -94,19 +132,6 @@ async function initializeApp(): Promise { // Also remove TshHomeMigrationBanner component and relevant properties from app_state.json. await migrateOldTshHomeOnce(logger, tshHome, appStateFileStorage); - nativeTheme.themeSource = configService.get('theme').value; - const windowsManager = new WindowsManager( - appStateFileStorage, - settings, - configService - ); - - process.on('uncaughtException', (error, origin) => { - logger.error('Uncaught exception', origin, error); - showDialogWithError(`Uncaught exception (${origin} origin)`, error); - app.exit(1); - }); - let mainProcess: MainProcess; try { mainProcess = new MainProcess({ @@ -141,25 +166,15 @@ async function initializeApp(): Promise { app.exit(); }); - // On Windows/Linux: Re-launching the app while it's already running - // triggers 'second-instance' (because of app.requestSingleInstanceLock()). - // - // On macOS: Re-launching the app (from places like Finder, Spotlight, or Dock) - // does not trigger 'second-instance'. Instead, the system emits 'activate'. - // However, launching the app outside the desktop manager (e.g., from the command - // line) does trigger 'second-instance'. - app.on('second-instance', () => { - windowsManager.focusWindow(); - }); - app.on('activate', () => { - windowsManager.focusWindow(); + app.on('web-contents-created', (_, webContents) => { + registerNavigationHandlers( + webContents, + settings, + mainProcess.clusterStore, + logger + ); }); - // Since setUpDeepLinks adds another listener for second-instance, it's important to call it after - // the listener which calls windowsManager.focusWindow. This way the focus will be brought to the - // window before processing the listener for deep links. - setUpDeepLinks(logger, windowsManager, settings); - app .whenReady() .then(() => { @@ -177,15 +192,6 @@ async function initializeApp(): Promise { showDialogWithError(message, error); app.exit(1); }); - - app.on('web-contents-created', (_, webContents) => { - registerNavigationHandlers( - webContents, - settings, - mainProcess.clusterStore, - logger - ); - }); } /** From d0e0b16ef65b70decbc65736aa33b88f7f58187b Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Tue, 25 Nov 2025 16:54:25 +0100 Subject: [PATCH 13/15] Serialize IPC errors (#61665) * Serialize all enumerable error fields * Add wrappers around `ipcMain.handle` and `ipcRenderer.invoke` * Fix `Method Error.prototype.toString called on incompatible receiver undefined` * Improve docs * Lint (cherry picked from commit a1f2ae0bf9cfd8b295c89dd84fdf1756c09a22e5) --- .../src/mainProcess/ipcSerializer.test.ts | 68 ++++++++++++++ .../teleterm/src/mainProcess/ipcSerializer.ts | 34 +++++-- .../teleterm/src/mainProcess/mainProcess.ts | 73 +++++++++------ .../src/mainProcess/mainProcessClient.ts | 92 ++++++++++--------- 4 files changed, 189 insertions(+), 78 deletions(-) create mode 100644 web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts diff --git a/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts b/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts new file mode 100644 index 0000000000000..5b3ab156b29c5 --- /dev/null +++ b/web/packages/teleterm/src/mainProcess/ipcSerializer.test.ts @@ -0,0 +1,68 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { TshdRpcError } from 'teleterm/services/tshd/cloneableClient'; + +import { deserializeError, serializeError } from './ipcSerializer'; + +test('serializes and deserializes regular error', () => { + const err = new Error('Boom'); + err.name = 'CustomError'; + + const serialized = serializeError(err); + expect(serialized instanceof Error).toBe(false); + expect(serialized.message).toBe('Boom'); + expect(serialized.name).toBe('CustomError'); + expect(serialized.stack).toBe(err.stack); + expect(serialized.cause).toBe(err.cause); + + const cloned = structuredClone(serialized); + const deserialized = deserializeError(cloned); + expect(deserialized instanceof Error).toBe(true); + expect(deserialized.message).toBe('Boom'); + expect(deserialized.name).toBe('CustomError'); + expect(deserialized.stack).toBe(err.stack); + expect(deserialized.cause).toBe(err.cause); +}); + +test('serializes and deserializes tshd error', () => { + const err: TshdRpcError = { + name: 'TshdRpcError', + message: 'Could not found', + toString: () => 'Could not found', + cause: '', + stack: '', + code: 'NOT_FOUND', + isResolvableWithRelogin: false, + }; + + const serialized = serializeError(err); + expect(serialized instanceof Error).toBe(false); + expect(serialized.message).toBe('Could not found'); + expect(serialized.name).toBe('TshdRpcError'); + expect(serialized['isResolvableWithRelogin']).toBe(false); + expect(serialized['code']).toBe('NOT_FOUND'); + + const cloned = structuredClone(serialized); + const deserialized = deserializeError(cloned); + expect(deserialized instanceof Error).toBe(true); + expect(deserialized.message).toBe('Could not found'); + expect(deserialized.name).toBe('TshdRpcError'); + expect(deserialized['isResolvableWithRelogin']).toBe(false); + expect(deserialized['code']).toBe('NOT_FOUND'); +}); diff --git a/web/packages/teleterm/src/mainProcess/ipcSerializer.ts b/web/packages/teleterm/src/mainProcess/ipcSerializer.ts index 42555704147c5..7011f6b5b47e8 100644 --- a/web/packages/teleterm/src/mainProcess/ipcSerializer.ts +++ b/web/packages/teleterm/src/mainProcess/ipcSerializer.ts @@ -21,23 +21,41 @@ export type SerializedError = { message: string; stack?: string; cause?: unknown; + toStringResult?: string; }; /** Serializes an Error into a plain object for transport through Electron IPC. */ export function serializeError(error: Error): SerializedError { + const { + name, + cause, + stack, + message, + // functions must be skipped, otherwise structuredClone will fail to clone the object + // eslint-disable-next-line unused-imports/no-unused-vars + toString, + ...enumerableFields + } = error; return { - name: error.name, - message: error.message, - cause: error.cause, - stack: error.stack, + name, + message, + cause, + stack, + // Calling the destructured function directly could result in the following error: + // Method Error.prototype.toString called on incompatible receiver undefined + toStringResult: error.toString?.(), + ...enumerableFields, }; } /** Deserializes a plain object back into an Error instance. */ export function deserializeError(serialized: SerializedError): Error { - const error = new Error(serialized.message); - error.name = serialized.name; - error.cause = serialized.cause; - error.stack = serialized.stack; + const { name, cause, stack, message, toStringResult, ...rest } = serialized; + const error = new Error(message); + error.name = name; + error.cause = cause; + error.stack = stack; + error.toString = () => toStringResult; + Object.assign(error, rest); return error; } diff --git a/web/packages/teleterm/src/mainProcess/mainProcess.ts b/web/packages/teleterm/src/mainProcess/mainProcess.ts index 7b9ef31a1501e..21cd4177e1e41 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcess.ts @@ -27,6 +27,7 @@ import { app, dialog, ipcMain, + IpcMainInvokeEvent, Menu, MenuItemConstructorOptions, nativeTheme, @@ -415,17 +416,17 @@ export default class MainProcess { event.returnValue = nativeTheme.shouldUseDarkColors; }); - ipcMain.handle('main-process-get-resolved-child-process-addresses', () => { + ipcHandle('main-process-get-resolved-child-process-addresses', () => { return this.resolvedChildProcessAddresses; }); - ipcMain.handle('main-process-show-file-save-dialog', (_, filePath) => + ipcHandle('main-process-show-file-save-dialog', (_, filePath) => dialog.showSaveDialog({ defaultPath: path.basename(filePath), }) ); - ipcMain.handle( + ipcHandle( MainProcessIpc.SaveTextToFile, async ( _, @@ -464,7 +465,7 @@ export default class MainProcess { } ); - ipcMain.handle( + ipcHandle( MainProcessIpc.ForceFocusWindow, async ( _, @@ -485,7 +486,7 @@ export default class MainProcess { // Used in the `tsh install` command on macOS to make the bundled tsh available in PATH. // Returns true if tsh got successfully installed, false if the user closed the osascript // prompt. Throws an error when osascript fails. - ipcMain.handle('main-process-symlink-tsh-macos', async () => { + ipcHandle('main-process-symlink-tsh-macos', async () => { const source = this.settings.tshd.binaryPath; const target = '/usr/local/bin/tsh'; const prompt = @@ -507,7 +508,7 @@ export default class MainProcess { } }); - ipcMain.handle('main-process-remove-tsh-symlink-macos', async () => { + ipcHandle('main-process-remove-tsh-symlink-macos', async () => { const target = '/usr/local/bin/tsh'; const prompt = 'Teleport Connect wants to remove a symlink for tsh from /usr/local/bin.'; @@ -528,21 +529,21 @@ export default class MainProcess { } }); - ipcMain.handle('main-process-open-config-file', async () => { + ipcHandle('main-process-open-config-file', async () => { const path = this.configFileStorage.getFilePath(); await shell.openPath(path); return path; }); - ipcMain.handle(MainProcessIpc.DownloadConnectMyComputerAgent, () => + ipcHandle(MainProcessIpc.DownloadConnectMyComputerAgent, () => this.downloadAgentShared() ); - ipcMain.handle(MainProcessIpc.VerifyConnectMyComputerAgent, async () => { + ipcHandle(MainProcessIpc.VerifyConnectMyComputerAgent, async () => { await verifyAgent(this.settings.agentBinaryPath); }); - ipcMain.handle( + ipcHandle( 'main-process-connect-my-computer-create-agent-config-file', (_, args: CreateAgentConfigFileArgs) => createAgentConfigFile(this.settings, { @@ -553,7 +554,7 @@ export default class MainProcess { }) ); - ipcMain.handle( + ipcHandle( 'main-process-connect-my-computer-is-agent-config-file-created', async ( _, @@ -563,7 +564,7 @@ export default class MainProcess { ) => isAgentConfigFileCreated(this.settings, args.rootClusterUri) ); - ipcMain.handle( + ipcHandle( 'main-process-connect-my-computer-kill-agent', async ( _, @@ -575,7 +576,7 @@ export default class MainProcess { } ); - ipcMain.handle( + ipcHandle( 'main-process-connect-my-computer-remove-agent-directory', ( _, @@ -585,11 +586,11 @@ export default class MainProcess { ) => removeAgentDirectory(this.settings, args.rootClusterUri) ); - ipcMain.handle(MainProcessIpc.TryRemoveConnectMyComputerAgentBinary, () => + ipcHandle(MainProcessIpc.TryRemoveConnectMyComputerAgentBinary, () => this.agentRunner.tryRemoveAgentBinary() ); - ipcMain.handle( + ipcHandle( 'main-process-connect-my-computer-run-agent', async ( _, @@ -625,7 +626,7 @@ export default class MainProcess { } ); - ipcMain.handle( + ipcHandle( 'main-process-open-agent-logs-directory', async ( _, @@ -644,7 +645,7 @@ export default class MainProcess { } ); - ipcMain.handle( + ipcHandle( MainProcessIpc.SelectDirectoryForDesktopSession, async (_, args: { desktopUri: string; login: string }) => { const value = await dialog.showOpenDialog({ @@ -673,11 +674,11 @@ export default class MainProcess { event.returnValue = this.appUpdater.supportsUpdates(); }); - ipcMain.handle(MainProcessIpc.CheckForAppUpdates, () => + ipcHandle(MainProcessIpc.CheckForAppUpdates, () => this.appUpdater.checkForUpdates() ); - ipcMain.handle( + ipcHandle( MainProcessIpc.ChangeAppUpdatesManagingCluster, ( event, @@ -687,31 +688,31 @@ export default class MainProcess { ) => this.appUpdater.changeManagingCluster(args.clusterUri) ); - ipcMain.handle(MainProcessIpc.DownloadAppUpdate, () => + ipcHandle(MainProcessIpc.DownloadAppUpdate, () => this.appUpdater.download() ); - ipcMain.handle(MainProcessIpc.CancelAppUpdateDownload, () => + ipcHandle(MainProcessIpc.CancelAppUpdateDownload, () => this.appUpdater.cancelDownload() ); - ipcMain.handle(MainProcessIpc.QuiteAndInstallAppUpdate, () => + ipcHandle(MainProcessIpc.QuiteAndInstallAppUpdate, () => this.appUpdater.quitAndInstall() ); - ipcMain.handle(MainProcessIpc.AddCluster, (ev, proxyAddress) => + ipcHandle(MainProcessIpc.AddCluster, (ev, proxyAddress) => this.clusterLifecycleManager.addCluster(proxyAddress) ); - ipcMain.handle(MainProcessIpc.SyncRootClusters, () => + ipcHandle(MainProcessIpc.SyncRootClusters, () => this.clusterLifecycleManager.syncRootClustersAndStartProfileWatcher() ); - ipcMain.handle(MainProcessIpc.SyncCluster, (_, args) => + ipcHandle(MainProcessIpc.SyncCluster, (_, args) => this.clusterLifecycleManager.syncCluster(args.clusterUri) ); - ipcMain.handle(MainProcessIpc.Logout, async (_, args) => { + ipcHandle(MainProcessIpc.Logout, async (_, args) => { await this.clusterLifecycleManager.logoutAndRemoveCluster( args.clusterUri ); @@ -1010,3 +1011,23 @@ function makeAppUpdaterStorage(fs: FileStorage): AppUpdaterStorage { }, }; } + +/** + * Handles requests sent via `ipcInvoke`. + * The renderer must send requests using `ipcInvoke` (not `ipcRenderer.invoke`). + * + * Use this instead of `ipcMain.handle`. It ensures full error serialization + * and prevents Electron from adding the generic message "Error invoking remote method". + */ +function ipcHandle( + channel: string, + listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise | any +): void { + ipcMain.handle(channel, async (...args) => { + try { + return { result: await Promise.try(listener, ...args) }; + } catch (e) { + return { error: serializeError(e) }; + } + }); +} diff --git a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts index 39922f62fd62a..44eb7726442ce 100644 --- a/web/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/web/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -18,8 +18,10 @@ import { ipcRenderer } from 'electron'; +import Logger from 'teleterm/logger'; import type { Message, MessageAck } from 'teleterm/mainProcess/awaitableSender'; import { CreateAgentConfigFileArgs } from 'teleterm/mainProcess/createAgentConfigFile'; +import { deserializeError } from 'teleterm/mainProcess/ipcSerializer'; import { AppUpdateEvent } from 'teleterm/services/appUpdater'; import { createFileStorageClient } from 'teleterm/services/fileStorage'; import { RootClusterUri } from 'teleterm/ui/uri'; @@ -36,6 +38,8 @@ import { WindowsManagerIpc, } from './types'; +const logger = new Logger('MainProcessClient'); + export default function createMainProcessClient(): MainProcessClient { return { /* @@ -98,78 +102,68 @@ export default function createMainProcessClient(): MainProcessClient { // TODO(ravicious): Convert the rest of IPC channels to use enums defined in types.ts such as // MainProcessIpc rather than hardcoded strings. getResolvedChildProcessAddresses(): Promise { - return ipcRenderer.invoke( - 'main-process-get-resolved-child-process-addresses' - ); + return ipcInvoke('main-process-get-resolved-child-process-addresses'); }, showFileSaveDialog(filePath: string) { - return ipcRenderer.invoke('main-process-show-file-save-dialog', filePath); + return ipcInvoke('main-process-show-file-save-dialog', filePath); }, saveTextToFile(args) { - return ipcRenderer.invoke(MainProcessIpc.SaveTextToFile, args); + return ipcInvoke(MainProcessIpc.SaveTextToFile, args); }, openTerminalContextMenu, openTabContextMenu, configService: createConfigServiceClient(), fileStorage: createFileStorageClient(), forceFocusWindow(args) { - return ipcRenderer.invoke(MainProcessIpc.ForceFocusWindow, args); + return ipcInvoke(MainProcessIpc.ForceFocusWindow, args); }, symlinkTshMacOs() { - return ipcRenderer.invoke('main-process-symlink-tsh-macos'); + return ipcInvoke('main-process-symlink-tsh-macos'); }, removeTshSymlinkMacOs() { - return ipcRenderer.invoke('main-process-remove-tsh-symlink-macos'); + return ipcInvoke('main-process-remove-tsh-symlink-macos'); }, openConfigFile() { - return ipcRenderer.invoke('main-process-open-config-file'); + return ipcInvoke('main-process-open-config-file'); }, shouldUseDarkColors() { return ipcRenderer.sendSync('main-process-should-use-dark-colors'); }, downloadAgent() { - return ipcRenderer.invoke(MainProcessIpc.DownloadConnectMyComputerAgent); + return ipcInvoke(MainProcessIpc.DownloadConnectMyComputerAgent); }, verifyAgent() { - return ipcRenderer.invoke(MainProcessIpc.VerifyConnectMyComputerAgent); + return ipcInvoke(MainProcessIpc.VerifyConnectMyComputerAgent); }, createAgentConfigFile(args: CreateAgentConfigFileArgs) { - return ipcRenderer.invoke( + return ipcInvoke( 'main-process-connect-my-computer-create-agent-config-file', args ); }, isAgentConfigFileCreated(args: { rootClusterUri: RootClusterUri }) { - return ipcRenderer.invoke( + return ipcInvoke( 'main-process-connect-my-computer-is-agent-config-file-created', args ); }, removeAgentDirectory(args: { rootClusterUri: RootClusterUri }) { - return ipcRenderer.invoke( + return ipcInvoke( 'main-process-connect-my-computer-remove-agent-directory', args ); }, tryRemoveConnectMyComputerAgentBinary() { - return ipcRenderer.invoke( - MainProcessIpc.TryRemoveConnectMyComputerAgentBinary - ); + return ipcInvoke(MainProcessIpc.TryRemoveConnectMyComputerAgentBinary); }, openAgentLogsDirectory(args: { rootClusterUri: RootClusterUri }) { - return ipcRenderer.invoke('main-process-open-agent-logs-directory', args); + return ipcInvoke('main-process-open-agent-logs-directory', args); }, killAgent(args: { rootClusterUri: RootClusterUri }) { - return ipcRenderer.invoke( - 'main-process-connect-my-computer-kill-agent', - args - ); + return ipcInvoke('main-process-connect-my-computer-kill-agent', args); }, runAgent(args: { rootClusterUri: RootClusterUri }) { - return ipcRenderer.invoke( - 'main-process-connect-my-computer-run-agent', - args - ); + return ipcInvoke('main-process-connect-my-computer-run-agent', args); }, getAgentState(args: { rootClusterUri: RootClusterUri }) { return ipcRenderer.sendSync( @@ -190,33 +184,27 @@ export default function createMainProcessClient(): MainProcessClient { desktopUri: string; login: string; }) { - return ipcRenderer.invoke( - MainProcessIpc.SelectDirectoryForDesktopSession, - args - ); + return ipcInvoke(MainProcessIpc.SelectDirectoryForDesktopSession, args); }, supportsAppUpdates() { return ipcRenderer.sendSync(MainProcessIpc.SupportsAppUpdates); }, checkForAppUpdates() { - return ipcRenderer.invoke(MainProcessIpc.CheckForAppUpdates); + return ipcInvoke(MainProcessIpc.CheckForAppUpdates); }, downloadAppUpdate() { - return ipcRenderer.invoke(MainProcessIpc.DownloadAppUpdate); + return ipcInvoke(MainProcessIpc.DownloadAppUpdate); }, cancelAppUpdateDownload() { - return ipcRenderer.invoke(MainProcessIpc.CancelAppUpdateDownload); + return ipcInvoke(MainProcessIpc.CancelAppUpdateDownload); }, quitAndInstallAppUpdate() { - return ipcRenderer.invoke(MainProcessIpc.QuiteAndInstallAppUpdate); + return ipcInvoke(MainProcessIpc.QuiteAndInstallAppUpdate); }, changeAppUpdatesManagingCluster(clusterUri) { - return ipcRenderer.invoke( - MainProcessIpc.ChangeAppUpdatesManagingCluster, - { - clusterUri, - } - ); + return ipcInvoke(MainProcessIpc.ChangeAppUpdatesManagingCluster, { + clusterUri, + }); }, subscribeToAppUpdateEvents: listener => { const ipcListener = (_, updateEvent: AppUpdateEvent) => { @@ -261,16 +249,16 @@ export default function createMainProcessClient(): MainProcessClient { }; }, addCluster: async (proxyAddress: string) => { - return await ipcRenderer.invoke(MainProcessIpc.AddCluster, proxyAddress); + return await ipcInvoke(MainProcessIpc.AddCluster, proxyAddress); }, syncRootClusters: async () => { - return await ipcRenderer.invoke(MainProcessIpc.SyncRootClusters); + return await ipcInvoke(MainProcessIpc.SyncRootClusters); }, syncCluster: (clusterUri: RootClusterUri) => { - return ipcRenderer.invoke(MainProcessIpc.SyncCluster, { clusterUri }); + return ipcInvoke(MainProcessIpc.SyncCluster, { clusterUri }); }, logout: (clusterUri: RootClusterUri) => { - return ipcRenderer.invoke(MainProcessIpc.Logout, { clusterUri }); + return ipcInvoke(MainProcessIpc.Logout, { clusterUri }); }, registerClusterLifecycleHandler(listener): { cleanup: () => void; @@ -341,3 +329,19 @@ function startAwaitableSenderListener( }, }; } + +/** + * Resolves with the response from the main process. + * The main process must register the handler using `ipcHandle` (not `ipcMain.handle`). + * + * Use this instead of `ipcRenderer.invoke`. + */ +async function ipcInvoke(channel: string, ...args: any[]): Promise { + const { error, result } = await ipcRenderer.invoke(channel, ...args); + if (error) { + const deserialized = deserializeError(error); + logger.error(`Error invoking remote method ${channel}`, deserialized); + throw deserialized; + } + return result; +} From d17a2be57fd7964e47f11587c3545af4d3903170 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 28 Nov 2025 11:12:24 +0100 Subject: [PATCH 14/15] Fix unrecoverable ssh cert errors in tsh/Connect (#61322) * Initialize default Username/HostLogin only in tsh * Move `Username()` from `api.go` to `tsh.go` * Remove wrong `Profile.SiteName` default * Remove resetting `SiteName` Not sure why it was needed. Perhaps to clear the default that we just removed? But even if add the default back and remove this fix, everything works. * Gracefully handle missing SSH/TLS certs * Remove unused `TeleportClient.LoadKeyForClusterWithReissue` * Revert "Move `Username()` from `api.go` to `tsh.go`" This reverts commit f7ff0ff6d1a1dc5675ff6317c2d3abe5007d1edb. * Revert "Initialize default Username/HostLogin only in tsh" This reverts commit ed38babf5e7709db1ddd97c20059a925ed1e7d21. * When any of SSH/TLS cert is missing, return partial profile * Only log non-nil errors * Revert "Remove wrong `Profile.SiteName` default" * Revert "Remove resetting `SiteName`" This reverts commit f54ab3f22acebaa671672f0ae068167c7303b594. * Set `SiteName` when adding cluster * Improve comments * Add test * Fix test * Add myself to TODO * Add test for logging out with missing SSH cert * Lint (cherry picked from commit cd3c8f84ee2ebd08b8a3d6fc6729a7cc66d8386e) --- api/profile/profile.go | 4 ++ integration/teleterm_test.go | 58 +++++++++++++++++++ lib/client/api.go | 28 +-------- lib/client/client_store.go | 49 +++++++++------- lib/teleterm/clusters/cluster.go | 18 ++++++ .../clusters/cluster_access_requests.go | 2 +- lib/teleterm/clusters/cluster_auth.go | 2 +- lib/teleterm/clusters/storage.go | 42 ++++++++++++-- tool/tsh/common/tsh_test.go | 12 ++++ 9 files changed, 160 insertions(+), 55 deletions(-) diff --git a/api/profile/profile.go b/api/profile/profile.go index 752c2243644d3..c01ccad841fa9 100644 --- a/api/profile/profile.go +++ b/api/profile/profile.go @@ -392,6 +392,10 @@ func profileFromFile(filePath string) (*Profile, error) { // Older versions of tsh did not always store the cluster name in the // profile. If no cluster name is found, fallback to the name of the profile // for backward compatibility. + // + // TODO(gzdunek): A profile name is not the same thing as a site name, and they differ when the proxy hostname is different + // from the cluster name. + // Instead, tsh should be able to handle an empty site name, or this default should be changed. if p.SiteName == "" { p.SiteName = p.Name() } diff --git a/integration/teleterm_test.go b/integration/teleterm_test.go index 1bdbb7ad252c8..b3097cb62d683 100644 --- a/integration/teleterm_test.go +++ b/integration/teleterm_test.go @@ -145,6 +145,11 @@ func TestTeleterm(t *testing.T) { testLogout(t, pack, creds) }) + t.Run("setting site name", func(t *testing.T) { + t.Parallel() + testSettingSiteName(t, pack, creds) + }) + t.Run("ListDatabaseUsers", func(t *testing.T) { // ListDatabaseUsers cannot be run in parallel as it modifies the default roles of users set up // through the test pack. @@ -673,6 +678,59 @@ func testLogout(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.UserC require.NoError(t, err) } +func testSettingSiteName(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.UserCreds) { + ctx := context.Background() + + tc, err := pack.Root.Cluster.NewClient(helpers.ClientConfig{ + TeleportUser: pack.Root.User.GetName(), + Cluster: "root.example.com", + }) + require.NoError(t, err) + + storageFakeClock := clockwork.NewFakeClockAt(time.Now()) + + storage, err := clusters.NewStorage(clusters.Config{ + ClientStore: tc.ClientStore, + Clock: storageFakeClock, + InsecureSkipVerify: tc.InsecureSkipVerify, + }) + require.NoError(t, err) + + // Add a cluster. + cluster, clusterClient, err := storage.Add(ctx, tc.WebProxyAddr) + require.NoError(t, err) + require.Equal(t, "root.example.com", clusterClient.SiteName) + require.Equal(t, "root.example.com", cluster.Name) + // Adding a cluster should set the site name in the profile. + profile, err := clusterClient.GetProfile(tc.WebProxyAddr) + require.NoError(t, err) + require.Equal(t, "root.example.com", profile.SiteName) + + // Simulate logging into a leaf cluster, which changes the profile's site name. + clusterClient.SiteName = "leaf.example.com" + err = clusterClient.SaveProfile(false) + require.NoError(t, err) + + // The URI should always resolve to the target cluster, even if the profile's site name points to a different cluster. + cluster, clusterClient, err = storage.ResolveCluster(cluster.URI) + require.NoError(t, err) + // These are empty because the user is not logged in, so there's no cert to retrieve the root cluster name. + require.Empty(t, clusterClient.SiteName) + require.Empty(t, cluster.Name) + // SiteName in the profile should still point to the leaf. + profile, err = clusterClient.GetProfile(tc.WebProxyAddr) + require.NoError(t, err) + require.Equal(t, "leaf.example.com", profile.SiteName) + + // Saving the profile with SaveProfileAndPreserveSiteName doesn't overwrite the profile + // with the current clusterClient.SiteName. + err = clusters.SaveProfileAndPreserveSiteName(clusterClient, false) + require.NoError(t, err) + profile, err = clusterClient.GetProfile(tc.WebProxyAddr) + require.NoError(t, err) + require.Equal(t, "leaf.example.com", profile.SiteName) +} + func testCreateConnectMyComputerRole(t *testing.T, pack *dbhelpers.DatabasePack) { systemUser, err := user.Current() require.NoError(t, err) diff --git a/lib/client/api.go b/lib/client/api.go index 76df566e80ad6..5c820555bfd33 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -1398,38 +1398,12 @@ func (tc *TeleportClient) LoadKeyForCluster(ctx context.Context, clusterName str return nil } -// LoadKeyForClusterWithReissue fetches a cluster-specific SSH key and loads it into the -// SSH agent. If the key is not found, it is requested to be reissued. -func (tc *TeleportClient) LoadKeyForClusterWithReissue(ctx context.Context, clusterName string) error { - ctx, span := tc.Tracer.Start( - ctx, - "teleportClient/LoadKeyForClusterWithReissue", - oteltrace.WithSpanKind(oteltrace.SpanKindClient), - oteltrace.WithAttributes(attribute.String("cluster", clusterName)), - ) - defer span.End() - - err := tc.LoadKeyForCluster(ctx, clusterName) - if err == nil { - return nil - } - if !trace.IsNotFound(err) { - return trace.Wrap(err) - } - // Reissuing also loads the new key. - err = tc.ReissueUserCerts(ctx, CertCacheKeep, ReissueParams{RouteToCluster: clusterName}) - if err != nil { - return trace.Wrap(err) - } - return nil -} - // SignersForClusterWithReissue fetches cluster-specific signers from stored certificates. // If the cluster certificates are not found, it is requested to be reissued. func (tc *TeleportClient) SignersForClusterWithReissue(ctx context.Context, clusterName string) ([]ssh.Signer, error) { ctx, span := tc.Tracer.Start( ctx, - "teleportClient/LoadKeyForClusterWithReissue", + "teleportClient/SignersForClusterWithReissue", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes(attribute.String("cluster", clusterName)), ) diff --git a/lib/client/client_store.go b/lib/client/client_store.go index ed6db939a8b09..00fb18c45f832 100644 --- a/lib/client/client_store.go +++ b/lib/client/client_store.go @@ -266,35 +266,38 @@ func (s *Store) ReadProfileStatus(proxyAddressOrProfile string) (*ProfileStatus, ClusterName: profile.SiteName, Username: profile.Username, } + + // If we can't find a keyRing to match the profile, connect to the keyRing (hardware key), + // or read the full profile status, return a partial status. + // This is used for some superficial functions `tsh logout` and `tsh status`. + partialStatus := &ProfileStatus{ + Name: profileName, + Dir: profile.Dir, + ProxyURL: url.URL{ + Scheme: "https", + Host: profile.WebProxyAddr, + }, + Username: profile.Username, + Cluster: profile.SiteName, + KubeEnabled: profile.KubeProxyAddr != "", + // Set ValidUntil to now and GetKeyRingError to show that the keys are not available. + ValidUntil: time.Now(), + SAMLSingleLogoutEnabled: profile.SAMLSingleLogoutEnabled, + SSOHost: profile.SSOHost, + } + keyRing, err := s.GetKeyRing(idx, WithAllCerts...) if err != nil { if trace.IsNotFound(err) || trace.IsConnectionProblem(err) { - // If we can't find a keyRing to match the profile, or can't connect to - // the keyRing (hardware key), return a partial status. This is used for - // some superficial functions `tsh logout` and `tsh status`. - return &ProfileStatus{ - Name: profileName, - Dir: profile.Dir, - ProxyURL: url.URL{ - Scheme: "https", - Host: profile.WebProxyAddr, - }, - Username: profile.Username, - Cluster: profile.SiteName, - KubeEnabled: profile.KubeProxyAddr != "", - // Set ValidUntil to now and GetKeyRingError to show that the keys are not available. - ValidUntil: time.Now(), - GetKeyRingError: err, - SAMLSingleLogoutEnabled: profile.SAMLSingleLogoutEnabled, - SSOHost: profile.SSOHost, - }, nil + partialStatus.GetKeyRingError = err + return partialStatus, nil } return nil, trace.Wrap(err) } _, onDisk := s.KeyStore.(*FSKeyStore) - return profileStatusFromKeyRing(keyRing, profileOptions{ + profileStatus, err := profileStatusFromKeyRing(keyRing, profileOptions{ ProfileName: profileName, ProfileDir: profile.Dir, WebProxyAddr: profile.WebProxyAddr, @@ -308,6 +311,12 @@ func (s *Store) ReadProfileStatus(proxyAddressOrProfile string) (*ProfileStatus, IsVirtual: !onDisk, TLSRoutingEnabled: profile.TLSRoutingEnabled, }) + if trace.IsNotFound(err) { + return partialStatus, nil + } else if err != nil { + return nil, trace.Wrap(err) + } + return profileStatus, nil } // FullProfileStatus returns the status of the active profile along with the diff --git a/lib/teleterm/clusters/cluster.go b/lib/teleterm/clusters/cluster.go index c8ae7ff6428f7..3085bbe343536 100644 --- a/lib/teleterm/clusters/cluster.go +++ b/lib/teleterm/clusters/cluster.go @@ -401,3 +401,21 @@ type Server struct { types.Server } + +// SaveProfileAndPreserveSiteName wraps [client.TeleportClient.SaveProfile]. It restores the original site name before +// saving the profile. +// +// It's needed because teleterm/clusters.Storage.loadProfileStatusAndClusterKey overwrites the profile's site name so that +// the cluster client is created for the cluster specified in the URI rather than the one stored in the profile. +// This adjustment is only relevant for Connect and should not be persisted. +func SaveProfileAndPreserveSiteName(clusterClient *client.TeleportClient, makeCurrent bool) error { + profile, err := clusterClient.GetProfile(clusterClient.WebProxyAddr) + if err != nil && !trace.IsNotFound(err) { + return trace.Wrap(err) + } + if profile != nil { + clusterClient.SiteName = profile.SiteName + } + err = clusterClient.SaveProfile(makeCurrent) + return trace.Wrap(err) +} diff --git a/lib/teleterm/clusters/cluster_access_requests.go b/lib/teleterm/clusters/cluster_access_requests.go index de054ebd9b59b..dc90d6e3a0ad3 100644 --- a/lib/teleterm/clusters/cluster_access_requests.go +++ b/lib/teleterm/clusters/cluster_access_requests.go @@ -234,7 +234,7 @@ func (c *Cluster) AssumeRole(ctx context.Context, rootClient *client.ClusterClie return trace.Wrap(err) } - err = c.clusterClient.SaveProfile(true) + err = SaveProfileAndPreserveSiteName(c.clusterClient, true) return trace.Wrap(err) } diff --git a/lib/teleterm/clusters/cluster_auth.go b/lib/teleterm/clusters/cluster_auth.go index ebbb1e271ddbe..9f576e31b3bb8 100644 --- a/lib/teleterm/clusters/cluster_auth.go +++ b/lib/teleterm/clusters/cluster_auth.go @@ -53,7 +53,7 @@ func (c *Cluster) SyncAuthPreference(ctx context.Context) (*webclient.WebConfigA c.Logger.DebugContext(ctx, "Got ping response", "response", string(pingResponseJSON)) } - if err := c.clusterClient.SaveProfile(false); err != nil { + if err := SaveProfileAndPreserveSiteName(c.clusterClient, false); err != nil { return nil, nil, trace.Wrap(err) } diff --git a/lib/teleterm/clusters/storage.go b/lib/teleterm/clusters/storage.go index 12901915bca99..af45d283d87d1 100644 --- a/lib/teleterm/clusters/storage.go +++ b/lib/teleterm/clusters/storage.go @@ -171,6 +171,13 @@ func (s *Storage) addCluster(ctx context.Context, webProxyAddress string) (*Clus return nil, nil, trace.Wrap(err) } + // There's an incorrect default in api/profile.profileFromFile - an empty SiteName is replaced with the profile name. + // A profile name is not the same thing as a site name, and they differ when the proxy hostname is different + // from the cluster name. + // Using this incorrect site name causes login failures in `tsh`, so we proactively set SiteName to the root cluster + // name instead. + clusterClient.SiteName = pingResponse.ClusterName + clusterLog := s.Logger.With("cluster", clusterURI) pingResponseJSON, err := json.Marshal(pingResponse) @@ -274,15 +281,38 @@ func (s *Storage) loadProfileStatusAndClusterKey(clusterClient *client.TeleportC clusterClient.SiteName = rootClusterName } - if err == nil && clusterClient.Username != "" { - status, err = clusterClient.ProfileStatus() - if err != nil { - return nil, trace.Wrap(err) - } + // TODO(gzdunek): If the key doesn't exist, we should still try to read + // the profile status. + // This creates an inconsistency in how the profile is interpreted after running + //`tsh logout --proxy=... --user=...` by `tsh status` versus Connect. + // + // tsh will still show a profile that includes the username, while Connect + // receives an empty profile status and therefore has no username. + // Fixing this requires updating how ClusterLifecycleManager detects logouts. + // Right now it assumes that a logout results in an empty username. + // After the fix, the username would still be present, so we'll need to rely on + // a different field of LoggedInUser (or introduce a new one) to determine logout + // state reliably. + if err != nil || clusterClient.Username == "" { + return status, nil + } - if err := clusterClient.LoadKeyForCluster(context.Background(), status.Cluster); err != nil { + status, err = clusterClient.ProfileStatus() + if err != nil { + return nil, trace.Wrap(err) + } + + // Load SSH key for the cluster indicated in the profile. + // Skip if the profile is empty, the key cannot be found, or the key isn't supported as an agent key. + err = clusterClient.LoadKeyForCluster(context.Background(), status.Cluster) + if err != nil { + if !trace.IsNotFound(err) && !trace.IsConnectionProblem(err) && !trace.IsCompareFailed(err) { return nil, trace.Wrap(err) } + s.Logger.InfoContext(context.Background(), "Could not load key for cluster into the local agent", + "cluster", status.Cluster, + "error", err, + ) } return status, nil diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 5e1bf854ca16b..8af5acb130107 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -87,6 +87,7 @@ import ( "github.com/gravitational/teleport/lib/cryptosuites" "github.com/gravitational/teleport/lib/cryptosuites/cryptosuitestest" "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/fixtures" "github.com/gravitational/teleport/lib/kube/kubeconfig" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/modules/modulestest" @@ -6087,6 +6088,17 @@ func TestLogout(t *testing.T) { require.NoError(t, os.Remove(privKeyPath)) }, }, + { + name: "TLS cert is present but SSH cert is missing", + modifyKeyDir: func(t *testing.T, homePath string) { + tlsCertPath := keypaths.TLSCertPath(homePath, clientKeyRing.ProxyHost, clientKeyRing.Username) + err := os.WriteFile(tlsCertPath, []byte(fixtures.TLSCACertPEM), 0o600) + require.NoError(t, err) + sshCertPath := keypaths.SSHCertPath(homePath, clientKeyRing.ProxyHost, clientKeyRing.Username, clientKeyRing.ClusterName) + _, err = os.ReadFile(sshCertPath) + require.ErrorIs(t, err, os.ErrNotExist) + }, + }, { name: "TLS private key missing", modifyKeyDir: func(t *testing.T, homePath string) { From a549d68736cf8794f3c919b19dcd515ac4da2b6e Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 20 Nov 2025 11:26:29 +0100 Subject: [PATCH 15/15] Connect: update docs for sharing ~/.tsh directory (#61467) * Update docs for sharing ~/.tsh directory * Review comments * Lint (cherry picked from commit 19533bf51a0122acdd47f39091f477746c6ebf7b) --- .../connect-your-client/teleport-connect.mdx | 52 ++++++++++++++----- web/packages/teleterm/src/main.ts | 3 +- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/docs/pages/connect-your-client/teleport-connect.mdx b/docs/pages/connect-your-client/teleport-connect.mdx index f52793c2d132d..d8fcb6de98a72 100644 --- a/docs/pages/connect-your-client/teleport-connect.mdx +++ b/docs/pages/connect-your-client/teleport-connect.mdx @@ -565,11 +565,17 @@ $ tctl lock --server-id=SERVER_UUID --message="Using Connect My Computer is forb Teleport Connect ships with its own bundled version of tsh. Teleport Connect will always use this version of tsh for any actions performed within the app. -Teleport Connect makes tsh available to use in your terminal of choice as well. Please note that at -the moment tsh and Teleport Connect operate on different sets of profiles, as Teleport Connect sets -a custom home location through [the `TELEPORT_HOME` environment -variable](../reference/cli/tsh.mdx#tsh-environment-variables). For example, logging in to a new cluster -through tsh will not make that cluster show up in Teleport Connect. +The bundled tsh tool is also available to use directly in your terminal. By default, Teleport Connect and tsh share +the same home directory, so any profiles you create or update in one tool are immediately reflected in the other. +If you prefer a different directory, you can configure it via the `tshHome` property in [the configuration](#configuration). + +Teleport Connect actively monitors the tsh directory, so updates made with tsh (such as logging in, logging out, +or adding new clusters) will automatically appear in the app. + + +Older versions of Teleport Connect used a separate set of profiles stored in an internal tsh directory. +On the first launch, the app will automatically migrate your profiles to the new location. + @@ -630,6 +636,7 @@ Below is the list of the supported config properties. | `theme` | `system` | Color theme for the app. Available modes: `light`, `dark`, `system`. | | `skipVersionCheck` | `false` | Skips the version check and hides the version compatibility warning when logging in to a cluster. | | `runInBackground` | `true` on macOS/Windows, `false` on Linux | Keeps the app running in the menu bar/system tray even when the main window is closed. On Linux, displaying the system tray icon may require installing shell extensions. | +| `tshHome` | `~/.tsh` | Home location for tsh configuration and data. | | `terminal.fontFamily` | `Menlo, Monaco, monospace` on macOS `Consolas, monospace` on Windows `'Droid Sans Mono', monospace` on Linux | Font family for the terminal. | | `terminal.fontSize` | `15` | Font size for the terminal. | | `terminal.windowsBackend` | `auto` | `auto` uses modern [ConPTY](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/) system if available, which requires Windows 10 (19H1) or above. Set to `winpty` to use winpty even if ConPTY is available. | @@ -722,27 +729,30 @@ To reset the state related to a particular cluster: 1. Close Teleport Connect. 1. Open Teleport Connect, then log back in to the cluster. -To completely wipe all app state: +To completely wipe all app state, close Teleport Connect and run the following commands: -1. Close Teleport Connect. -1. Remove the internal `tsh` folder and the `app_state.json` file to log out of all clusters and - clear all remembered tabs and connections. +{/* TODO: DELETE IN 20.0.0: */} +{/* Remove cleaning up the internal Teleport Connect/tsh directory from the commands. */} +{/* Also remove the tsh home migration in Connect's main.ts */} ```code $ rm -rf ~/Library/Application\ Support/Teleport\ Connect/{tsh,app_state.json} +$ rm -rf ~/.tsh ``` ```code $ rm -rf ~/.config/Teleport\ Connect/{tsh,app_state.json} +$ rm -rf ~/.tsh ``` ```code -$ rmdir /s /q C:\Users\%UserName%\AppData\Roaming\"Teleport Connect"\tsh $ del C:\Users\%UserName%\AppData\Roaming\"Teleport Connect"\app_state.json +$ rmdir /s /q C:\Users\%UserName%\AppData\Roaming\"Teleport Connect"\tsh +$ rmdir /s /q C:\Users\%UserName%\.tsh ``` @@ -839,23 +849,35 @@ Remove Teleport Connect for macOS from the Applications directory with this comm $ sudo rm -rf /Applications/Teleport\ Connect.app ``` -To remove the local user data directory: +To remove the local user data directory which holds app configuration, state, and logs: ```code $ rm -rf ~/Library/Application\ Support/Teleport\ Connect ``` +To remove the tsh directory which holds cluster credentials (note: this directory is also used by the tsh tool): + +```code +$ rm -rf ~/.tsh +``` + (!docs/pages/includes/uninstall-teleport-connect-windows.mdx!) -To remove the local user data directory: +To remove the local user data directory which holds app configuration, state, and logs: ```powershell $ rmdir /s /q "%APPDATA%\Teleport Connect" ``` +To remove the tsh directory which holds cluster credentials (note: this directory is also used by the tsh tool): + +```code +$ rmdir /s /q C:\Users\%UserName%\.tsh +``` + @@ -871,6 +893,12 @@ For RPM installations uninstall Teleport Connect using YUM: $ sudo yum remove teleport-connect ``` +To remove the tsh directory which holds cluster credentials (note: this directory is also used by the tsh tool): + +```code +$ rm -rf ~/.tsh +``` + Installs based on a tarball should remove the `teleport-connect` directory and any copied/linked executables. diff --git a/web/packages/teleterm/src/main.ts b/web/packages/teleterm/src/main.ts index ec6c9efad5899..8fc2912cc041b 100644 --- a/web/packages/teleterm/src/main.ts +++ b/web/packages/teleterm/src/main.ts @@ -129,7 +129,8 @@ async function initializeApp(): Promise { }); // TODO(gzdunek): DELETE IN 20.0.0. Users should already migrate to the new location. - // Also remove TshHomeMigrationBanner component and relevant properties from app_state.json. + // Also remove TshHomeMigrationBanner component, relevant properties from app_state.json, + // and address the TODO in teleport-connect.mdx > ##Troubleshooting. await migrateOldTshHomeOnce(logger, tshHome, appStateFileStorage); let mainProcess: MainProcess;