diff --git a/.changeset/clean-icons-wonder.md b/.changeset/clean-icons-wonder.md new file mode 100644 index 0000000000..b6beb58969 --- /dev/null +++ b/.changeset/clean-icons-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Handle Proxy WS upgrade manually for WS handshakes diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 051706bbd5..158b917a48 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -18,7 +18,7 @@ import { Logger } from 'winston'; import type { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import type { RequestHandler } from 'express'; +import { RequestHandler } from 'http-proxy-middleware'; import { TokenCredential } from '@azure/identity'; // @public (undocumented) diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index f755dc2c6b..17da706d66 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -68,6 +68,7 @@ "@jest-mock/express": "^2.0.1", "@kubernetes/client-node": "0.18.1", "@types/express": "^4.17.6", + "@types/http-proxy-middleware": "^0.19.3", "@types/luxon": "^3.0.0", "compression": "^1.7.4", "cors": "^2.8.5", @@ -88,8 +89,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/aws4": "^1.5.1", - "@types/http-proxy-middleware": "^0.19.3", - "cross-fetch": "^3.1.5", "mock-fs": "^5.2.0", "msw": "^1.0.0", "supertest": "^6.1.3", diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index a84482082f..327d580d10 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -46,7 +46,6 @@ import { HEADER_KUBERNETES_CLUSTER, KubernetesProxy, } from './KubernetesProxy'; -import fetch from 'cross-fetch'; import type { Request } from 'express'; @@ -757,7 +756,6 @@ describe('KubernetesProxy', () => { const wsProxyAddress = `ws://127.0.0.1:${proxyPort}${proxyPath}${wsPath}`; const wsAddress = `ws://localhost:${wsPort}${wsPath}`; - console.log('Ports: ', wsProxyAddress, wsAddress); // Let this request through so it reaches the express router above worker.use( @@ -769,10 +767,6 @@ describe('KubernetesProxy', () => { ), ); - // Prepopulate the proxy so the WebSocket upgrade can happen, result doesn't actually matter - const result = await fetch(wsProxyAddress.replace('ws', 'http')); - expect(result.ok).toBeFalsy(); - const webSocket = new WebSocket(wsProxyAddress); const connectMessagePromise = eventPromiseFactory(webSocket, 'message'); diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index 6e699d1220..d2a3649c27 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { ErrorResponseBody, ForwardedError, @@ -24,16 +23,18 @@ import { import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; import { kubernetesProxyPermission } from '@backstage/plugin-kubernetes-common'; import { - PermissionEvaluator, AuthorizeResult, + PermissionEvaluator, } from '@backstage/plugin-permission-common'; import { bufferFromFileOrString } from '@kubernetes/client-node'; -import type { Request, RequestHandler } from 'express'; -import { createProxyMiddleware } from 'http-proxy-middleware'; +import { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware'; import { Logger } from 'winston'; + import { AuthenticationStrategy } from '../auth'; import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +import type { Request } from 'express'; + export const APPLICATION_JSON: string = 'application/json'; /** @@ -121,7 +122,17 @@ export class KubernetesProxy { } const middleware = await this.getMiddleware(req); - middleware(req, res, next); + + // If req is an upgrade handshake, use middleware upgrade instead of http request handler https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade + if ( + req.header('connection')?.toLowerCase() === 'upgrade' && + req.header('upgrade')?.toLowerCase() === 'websocket' + ) { + // Missing the `head`, since it's optional we pass undefined to avoid type issues + middleware.upgrade!(req, req.socket, undefined); + } else { + middleware(req, res, next); + } }; } diff --git a/yarn.lock b/yarn.lock index d0c50c3999..c08f5686f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7643,7 +7643,6 @@ __metadata: "@types/luxon": ^3.0.0 compression: ^1.7.4 cors: ^2.8.5 - cross-fetch: ^3.1.5 express: ^4.17.1 express-promise-router: ^4.1.0 fs-extra: 10.1.0