Adding tests on KubernetesFetcher and KubernetesProxy for x509 client cert authentication

Signed-off-by: Andres Mauricio Gomez P <andmagom@outlook.com>
This commit is contained in:
Andres Mauricio Gomez P
2023-12-14 14:19:24 -05:00
parent f180cba319
commit 3cc0d26206
3 changed files with 209 additions and 8 deletions
@@ -812,6 +812,129 @@ describe('KubernetesFetcher', () => {
const [[{ agent }]] = httpsRequest.mock.calls;
expect(agent.options.rejectUnauthorized).toBe(false);
});
it('should use a x509 client cert authentication strategy to consume kubeapi when backstage-kubernetes-auth field is not provided and the authStrategy enables x509 client cert authentication - fetchObjectsForService', async () => {
worker.use(
rest.get('https://localhost:9999/api/v1/pods', (req, res, ctx) =>
res(
checkToken(req, ctx, 'token'),
withLabels(req, ctx, {
items: [{ metadata: { name: 'pod-name' } }],
}),
),
),
);
const myCert = 'MOCKCert';
const myKey = 'MOCKKey';
const result = sut.fetchObjectsForService({
serviceId: 'some-service',
clusterDetails: {
name: 'cluster1',
url: 'https://localhost:9999',
authMetadata: {},
caData: 'MOCKCA',
},
credential: {
type: 'x509 client certificate',
cert: myCert,
key: myKey,
},
objectTypesToFetch: new Set<ObjectToFetch>([
{
group: '',
apiVersion: 'v1',
plural: 'pods',
objectType: 'pods',
},
]),
labelSelector: '',
customResources: [],
});
await expect(result).rejects.toThrow(/PEM/);
expect(httpsRequest).toHaveBeenCalledTimes(1);
const [[{ agent }]] = httpsRequest.mock.calls;
expect(agent.options.ca.toString('base64')).toMatch('MOCKCA');
expect(agent.options.cert).toEqual(myCert);
expect(agent.options.key).toEqual(myKey);
});
it('should use a x509 client cert authentication strategy to consume kubeapi when backstage-kubernetes-auth field is not provided and the authStrategy enables x509 client cert authentication - fetchPodMetricsByNamespaces', async () => {
worker.use(
rest.get(
'https://localhost:9999/api/v1/namespaces/:namespace/pods',
(req, res, ctx) =>
res(
withLabels(req, ctx, {
items: [
{
metadata: { name: 'pod-name' },
spec: {
containers: [
{
name: 'container-name',
resources: {
requests: { cpu: '500m', memory: '512M' },
limits: { cpu: '1000m', memory: '1G' },
},
},
],
},
},
],
}),
),
),
rest.get(
'https://localhost:9999/apis/metrics.k8s.io/v1beta1/namespaces/:namespace/pods',
(req, res, ctx) =>
res(
withLabels(req, ctx, {
items: [
{
metadata: { name: 'pod-name' },
containers: [
{
name: 'container-name',
usage: { cpu: '0', memory: '0' },
},
],
},
],
}),
),
),
);
const myCert = 'MOCKCert';
const myKey = 'MOCKKey';
const result = sut.fetchPodMetricsByNamespaces(
{
name: 'cluster1',
url: 'https://localhost:9999',
authMetadata: {},
caData: 'MOCKCA',
},
{
type: 'x509 client certificate',
cert: myCert,
key: myKey,
},
new Set(['ns-a']),
);
await expect(result).rejects.toThrow(/PEM/);
expect(httpsRequest).toHaveBeenCalledTimes(2);
const [[{ agent }]] = httpsRequest.mock.calls;
expect(agent.options.ca.toString('base64')).toMatch('MOCKCA');
expect(agent.options.cert).toEqual(myCert);
expect(agent.options.key).toEqual(myKey);
});
});
it('should use namespace if provided', async () => {
@@ -216,16 +216,11 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
let requestInit: RequestInit;
const authProvider =
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];
if (
authProvider === 'serviceAccount' &&
!clusterDetails.authMetadata.serviceAccountToken &&
fs.pathExistsSync(Config.SERVICEACCOUNT_CA_PATH)
) {
if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
[url, requestInit] = this.fetchArgsInCluster(credential);
} else if (
credential.type === 'bearer token' ||
credential.type === 'x509 client certificate' ||
authProvider === 'localKubectlProxy'
this.isTokenOrClientCertificateAuthentication(authProvider, credential)
) {
[url, requestInit] = this.fetchArgs(clusterDetails, credential);
} else {
@@ -249,6 +244,28 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
return fetch(url, requestInit);
}
private isServiceAccountAuthentication(
authProvider: string,
clusterDetails: ClusterDetails,
) {
return (
authProvider === 'serviceAccount' &&
!clusterDetails.authMetadata.serviceAccountToken &&
fs.pathExistsSync(Config.SERVICEACCOUNT_CA_PATH)
);
}
private isTokenOrClientCertificateAuthentication(
authProvider: string,
credential: KubernetesCredential,
) {
return (
credential.type === 'bearer token' ||
credential.type === 'x509 client certificate' ||
authProvider === 'localKubectlProxy'
);
}
private fetchArgs(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
@@ -820,6 +820,67 @@ describe('KubernetesProxy', () => {
expect(ca).toMatch('MOCKCA');
});
});
it('should use a x509 client cert authentication strategy to consume kubeapi when backstage-kubernetes-auth field is not provided and the authStrategy enables x509 client cert authentication', async () => {
worker.use(
rest.get(
'https://localhost:9999/api/v1/namespaces',
(req: any, res: any, ctx: any) => {
if (req.headers.get('Authorization')) {
return res(ctx.status(403));
}
return res(
ctx.status(200),
ctx.json({
kind: 'NamespaceList',
apiVersion: 'v1',
items: [],
}),
);
},
),
);
clusterSupplier.getClusters.mockResolvedValue([
{
name: 'cluster1',
url: 'https://localhost:9999',
authMetadata: {},
},
]);
const myCert = 'MOCKCert';
const myKey = 'MOCKKey';
authStrategy.getCredential.mockResolvedValue({
type: 'x509 client certificate',
cert: myCert,
key: myKey,
});
const requestPromise = setupProxyPromise({
proxyPath: '/mountpath',
requestPath: '/api/v1/namespaces',
headers: { [HEADER_KUBERNETES_CLUSTER]: 'cluster1' },
});
const response = await requestPromise;
expect(authStrategy.getCredential).toHaveBeenCalledTimes(1);
expect(authStrategy.getCredential).toHaveBeenCalledWith(
expect.anything(),
{},
);
const [[{ key, cert }]] = httpsRequest.mock.calls;
expect(cert).toEqual(myCert);
expect(key).toEqual(myKey);
// 500 Since the key and cert are fake
expect(response.status).toEqual(500);
});
});
describe('WebSocket', () => {