From b58183e448d677f62ab0c22c687d50460f89a600 Mon Sep 17 00:00:00 2001 From: Andrew LeFevre Date: Tue, 25 Jun 2024 20:32:34 -0400 Subject: [PATCH] check that an FQDN belongs to a known app before redirecting to it Fixes https://github.com/gravitational/teleport-private/issues/1492, updates https://github.com/gravitational/teleport-private/issues/1418. --- .../src/AppLauncher/AppLauncher.test.tsx | 20 +++++++++++++------ .../teleport/src/AppLauncher/AppLauncher.tsx | 15 ++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx b/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx index 08b211f6fbdfd..c0c5199047779 100644 --- a/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx +++ b/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx @@ -79,6 +79,9 @@ describe('app launcher path is properly formed', () => { global.fetch = jest.fn(() => Promise.resolve({})) as jest.Mock; jest.spyOn(api, 'get').mockResolvedValue({}); jest.spyOn(api, 'post').mockResolvedValue({}); + jest.spyOn(service, 'getAppFqdn').mockResolvedValue({ + fqdn: 'grafana.localhost', + }); jest.spyOn(service, 'createAppSession').mockResolvedValue({ cookieValue: 'cookie-value', subjectCookieValue: 'subject-cookie-value', @@ -115,7 +118,10 @@ describe('app launcher path is properly formed', () => { ); }); - test('arn is url decoded', () => { + test('arn is url decoded', async () => { + jest.spyOn(service, 'getAppFqdn').mockResolvedValue({ + fqdn: 'test-app.test.teleport', + }); jest.spyOn(service, 'createAppSession'); const launcherPath = @@ -132,11 +138,13 @@ describe('app launcher path is properly formed', () => { ); - expect(service.createAppSession).toHaveBeenCalledWith({ - fqdn: 'test-app.test.teleport', - clusterId: 'test.teleport', - publicAddr: 'test-app.test.teleport', - arn: 'arn:aws:iam::joe123:role/EC2FullAccess', + await waitFor(() => { + expect(service.createAppSession).toHaveBeenCalledWith({ + fqdn: 'test-app.test.teleport', + clusterId: 'test.teleport', + publicAddr: 'test-app.test.teleport', + arn: 'arn:aws:iam::joe123:role/EC2FullAccess', + }); }); }); }); diff --git a/web/packages/teleport/src/AppLauncher/AppLauncher.tsx b/web/packages/teleport/src/AppLauncher/AppLauncher.tsx index 5aa7926684cd0..f1740376b19fb 100644 --- a/web/packages/teleport/src/AppLauncher/AppLauncher.tsx +++ b/web/packages/teleport/src/AppLauncher/AppLauncher.tsx @@ -41,6 +41,21 @@ export function AppLauncher() { const port = location.port ? `:${location.port}` : ''; try { + // Attempt to resolve the fqdn of the app, if we can't then an error + // will be returned preventing a redirect to a potentially arbitrary + // address. Compare the resolved fqdn with the one that was passed, + // if they don't match then the public address was used to find the + // resolved fqdn, and the passed fdqn isn't valid. + const resolvedApp = await service.getAppFqdn({ + fqdn: params.fqdn, + clusterId: params.clusterId, + publicAddr: params.publicAddr, + arn: params.arn, + }); + if (resolvedApp.fqdn !== params.fqdn) { + throw Error(`Failed to match applications with FQDN ${params.fqdn}`); + } + let path = ''; if (queryParams.has('path')) { path = queryParams.get('path');