Merge pull request #23177 from andmagom/kubernetes/FixProxyServiceAccount
Fixed bug at Kubernetes proxy when Backstage is running on K8S
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Fixed a bug in the proxy endpoint. Now when the `serviceAccount` strategy is used and no `serviceAccountToken` has been provided, the proxy endpoint assumes backstage is running on Kubernetes and gets the URL and CA from the Pod instance.
|
||||
@@ -17,13 +17,19 @@
|
||||
import 'buffer';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { errorHandler, getVoidLogger } from '@backstage/backend-common';
|
||||
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
|
||||
import {
|
||||
createMockDirectory,
|
||||
setupRequestMockHandlers,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
PermissionEvaluator,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
import {
|
||||
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
|
||||
KubernetesRequestAuth,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
import { getMockReq, getMockRes } from '@jest-mock/express';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
@@ -32,6 +38,7 @@ import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import request from 'supertest';
|
||||
import { AddressInfo, WebSocket, WebSocketServer } from 'ws';
|
||||
import { Config } from '@kubernetes/client-node';
|
||||
|
||||
import { LocalKubectlProxyClusterLocator } from '../cluster-locator/LocalKubectlProxyLocator';
|
||||
import {
|
||||
@@ -49,6 +56,12 @@ import {
|
||||
|
||||
import type { Request } from 'express';
|
||||
|
||||
const mockCertDir = createMockDirectory({
|
||||
content: {
|
||||
'ca.crt': 'MOCKCA',
|
||||
},
|
||||
});
|
||||
|
||||
describe('KubernetesProxy', () => {
|
||||
let proxy: KubernetesProxy;
|
||||
let authStrategy: jest.Mocked<AuthenticationStrategy>;
|
||||
@@ -977,4 +990,72 @@ describe('KubernetesProxy', () => {
|
||||
await closePromise;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Backstage running on k8s', () => {
|
||||
const initialHost = process.env.KUBERNETES_SERVICE_HOST;
|
||||
const initialPort = process.env.KUBERNETES_SERVICE_PORT;
|
||||
const initialCaPath = Config.SERVICEACCOUNT_CA_PATH;
|
||||
|
||||
afterEach(() => {
|
||||
process.env.KUBERNETES_SERVICE_HOST = initialHost;
|
||||
process.env.KUBERNETES_SERVICE_PORT = initialPort;
|
||||
Config.SERVICEACCOUNT_CA_PATH = initialCaPath;
|
||||
});
|
||||
|
||||
it('makes in-cluster requests when cluster details has no token', async () => {
|
||||
process.env.KUBERNETES_SERVICE_HOST = '10.10.10.10';
|
||||
process.env.KUBERNETES_SERVICE_PORT = '443';
|
||||
Config.SERVICEACCOUNT_CA_PATH = mockCertDir.resolve('ca.crt');
|
||||
|
||||
clusterSupplier.getClusters.mockResolvedValue([
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://ignored',
|
||||
authMetadata: {
|
||||
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount',
|
||||
},
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authStrategy.getCredential.mockResolvedValue({
|
||||
type: 'bearer token',
|
||||
token: 'SA_token',
|
||||
});
|
||||
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://10.10.10.10/api/v1/namespaces',
|
||||
(req: any, res: any, ctx: any) => {
|
||||
if (req.headers.get('Authorization') === 'Bearer SA_token') {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
kind: 'NamespaceList',
|
||||
apiVersion: 'v1',
|
||||
items: [],
|
||||
}),
|
||||
);
|
||||
}
|
||||
return res(ctx.status(403));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const requestPromise = setupProxyPromise({
|
||||
proxyPath: '/mountpath',
|
||||
requestPath: '/api/v1/namespaces',
|
||||
headers: {
|
||||
[HEADER_KUBERNETES_CLUSTER]: 'cluster1',
|
||||
},
|
||||
});
|
||||
|
||||
const response = await requestPromise;
|
||||
|
||||
expect(response.body).toStrictEqual({
|
||||
kind: 'NamespaceList',
|
||||
apiVersion: 'v1',
|
||||
items: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '@backstage/errors';
|
||||
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
|
||||
KubernetesRequestAuth,
|
||||
kubernetesProxyPermission,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
@@ -29,9 +30,15 @@ import {
|
||||
AuthorizeResult,
|
||||
PermissionEvaluator,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { bufferFromFileOrString } from '@kubernetes/client-node';
|
||||
import {
|
||||
Cluster,
|
||||
KubeConfig,
|
||||
bufferFromFileOrString,
|
||||
} from '@kubernetes/client-node';
|
||||
import { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware';
|
||||
import { Logger } from 'winston';
|
||||
import fs from 'fs-extra';
|
||||
import { Config } from '@kubernetes/client-node';
|
||||
|
||||
import { AuthenticationStrategy } from '../auth';
|
||||
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
|
||||
@@ -234,6 +241,25 @@ export class KubernetesProxy {
|
||||
throw new NotFoundError(`Cluster '${clusterName}' not found`);
|
||||
}
|
||||
|
||||
const authProvider =
|
||||
cluster.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
||||
|
||||
if (
|
||||
authProvider === 'serviceAccount' &&
|
||||
fs.pathExistsSync(Config.SERVICEACCOUNT_CA_PATH) &&
|
||||
!cluster.authMetadata.serviceAccountToken
|
||||
) {
|
||||
const kc = new KubeConfig();
|
||||
kc.loadFromCluster();
|
||||
const clusterFromKubeConfig = kc.getCurrentCluster() as Cluster;
|
||||
|
||||
const url = new URL(clusterFromKubeConfig.server);
|
||||
cluster.url = clusterFromKubeConfig.server;
|
||||
if (url.protocol === 'https:') {
|
||||
cluster.caFile = clusterFromKubeConfig.caFile;
|
||||
}
|
||||
}
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user