when ServiceAccount Strategy is used and no serviceAccountToken has been provided, the Kubernetes proxy is able to get the token and CA from the pod instance
Signed-off-by: Andres Mauricio Gomez P <andmagom@outlook.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Fixed bug at Kubernetes proxy, now when ServiceAccount Strategy is used and no serviceAccountToken has been provided, the Kubernetes Proxy assumes backstage is running on k8s, so it is able to get the token 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