bug: fixing the passing of backstage credentials everywhere
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -63,10 +63,13 @@ export const addResourceRoutesToRouter = (
|
||||
|
||||
router.post('/resources/workloads/query', async (req, res) => {
|
||||
const entity = await getEntityByReq(req);
|
||||
const response = await objectsProvider.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: req.body.auth,
|
||||
});
|
||||
const response = await objectsProvider.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: req.body.auth,
|
||||
},
|
||||
{ credentials: await httpAuth.credentials(req) },
|
||||
);
|
||||
res.json(response);
|
||||
});
|
||||
|
||||
@@ -81,11 +84,14 @@ export const addResourceRoutesToRouter = (
|
||||
throw new InputError('at least 1 customResource is required');
|
||||
}
|
||||
|
||||
const response = await objectsProvider.getCustomResourcesByEntity({
|
||||
entity,
|
||||
customResources: req.body.customResources,
|
||||
auth: req.body.auth,
|
||||
});
|
||||
const response = await objectsProvider.getCustomResourcesByEntity(
|
||||
{
|
||||
entity,
|
||||
customResources: req.body.customResources,
|
||||
auth: req.body.auth,
|
||||
},
|
||||
{ credentials: await httpAuth.credentials(req) },
|
||||
);
|
||||
res.json(response);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export class CatalogRelationServiceLocator implements KubernetesServiceLocator {
|
||||
// As this implementation always returns all clusters serviceId is ignored here
|
||||
getClustersByEntity(
|
||||
entity: Entity,
|
||||
_requestContext: ServiceLocatorRequestContext,
|
||||
requestContext: ServiceLocatorRequestContext,
|
||||
): Promise<{ clusters: ClusterDetails[] }> {
|
||||
if (
|
||||
entity.relations &&
|
||||
@@ -41,13 +41,15 @@ export class CatalogRelationServiceLocator implements KubernetesServiceLocator {
|
||||
r => r.type === 'dependsOn' && r.targetRef.includes('resource:'),
|
||||
)
|
||||
) {
|
||||
return this.clusterSupplier.getClusters().then(clusters => {
|
||||
return {
|
||||
clusters: clusters.filter(c =>
|
||||
this.doesEntityDependOnCluster(entity, c),
|
||||
),
|
||||
};
|
||||
});
|
||||
return this.clusterSupplier
|
||||
.getClusters({ credentials: requestContext.credentials })
|
||||
.then(clusters => {
|
||||
return {
|
||||
clusters: clusters.filter(c =>
|
||||
this.doesEntityDependOnCluster(entity, c),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ clusters: [] });
|
||||
}
|
||||
|
||||
@@ -34,8 +34,10 @@ export class MultiTenantServiceLocator implements KubernetesServiceLocator {
|
||||
// As this implementation always returns all clusters serviceId is ignored here
|
||||
getClustersByEntity(
|
||||
_entity: Entity,
|
||||
_requestContext: ServiceLocatorRequestContext,
|
||||
requestContext: ServiceLocatorRequestContext,
|
||||
): Promise<{ clusters: ClusterDetails[] }> {
|
||||
return this.clusterSupplier.getClusters().then(clusters => ({ clusters }));
|
||||
return this.clusterSupplier
|
||||
.getClusters({ credentials: requestContext.credentials })
|
||||
.then(clusters => ({ clusters }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,21 +34,25 @@ export class SingleTenantServiceLocator implements KubernetesServiceLocator {
|
||||
// As this implementation always returns all clusters serviceId is ignored here
|
||||
getClustersByEntity(
|
||||
_entity: Entity,
|
||||
_requestContext: ServiceLocatorRequestContext,
|
||||
requestContext: ServiceLocatorRequestContext,
|
||||
): Promise<{ clusters: ClusterDetails[] }> {
|
||||
return this.clusterSupplier.getClusters().then(clusters => {
|
||||
if (_entity.metadata?.annotations?.['backstage.io/kubernetes-cluster']) {
|
||||
return {
|
||||
clusters: clusters.filter(
|
||||
c =>
|
||||
c.name ===
|
||||
_entity.metadata?.annotations?.[
|
||||
'backstage.io/kubernetes-cluster'
|
||||
],
|
||||
),
|
||||
};
|
||||
}
|
||||
return { clusters };
|
||||
});
|
||||
return this.clusterSupplier
|
||||
.getClusters({ credentials: requestContext.credentials })
|
||||
.then(clusters => {
|
||||
if (
|
||||
_entity.metadata?.annotations?.['backstage.io/kubernetes-cluster']
|
||||
) {
|
||||
return {
|
||||
clusters: clusters.filter(
|
||||
c =>
|
||||
c.name ===
|
||||
_entity.metadata?.annotations?.[
|
||||
'backstage.io/kubernetes-cluster'
|
||||
],
|
||||
),
|
||||
};
|
||||
}
|
||||
return { clusters };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export class KubernetesBuilder {
|
||||
|
||||
const authStrategyMap = this.getAuthStrategyMap();
|
||||
|
||||
const proxy = this.getProxy(logger, clusterSupplier);
|
||||
const proxy = this.getProxy(logger, clusterSupplier, httpAuth);
|
||||
|
||||
const serviceLocator = this.getServiceLocator();
|
||||
|
||||
@@ -350,6 +350,7 @@ export class KubernetesBuilder {
|
||||
protected buildProxy(
|
||||
logger: Logger,
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
httpAuth: HttpAuthService,
|
||||
): KubernetesProxy {
|
||||
const authStrategyMap = this.getAuthStrategyMap();
|
||||
const authStrategy = new DispatchStrategy({
|
||||
@@ -359,6 +360,7 @@ export class KubernetesBuilder {
|
||||
logger,
|
||||
clusterSupplier,
|
||||
authStrategy,
|
||||
httpAuth,
|
||||
});
|
||||
return this.proxy;
|
||||
}
|
||||
@@ -386,10 +388,13 @@ export class KubernetesBuilder {
|
||||
const serviceId = req.params.serviceId;
|
||||
const requestBody: ObjectsByEntityRequest = req.body;
|
||||
try {
|
||||
const response = await objectsProvider.getKubernetesObjectsByEntity({
|
||||
entity: requestBody.entity,
|
||||
auth: requestBody.auth || {},
|
||||
});
|
||||
const response = await objectsProvider.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity: requestBody.entity,
|
||||
auth: requestBody.auth || {},
|
||||
},
|
||||
{ credentials: await httpAuth.credentials(req) },
|
||||
);
|
||||
res.json(response);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
@@ -530,8 +535,9 @@ export class KubernetesBuilder {
|
||||
protected getProxy(
|
||||
logger: Logger,
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
httpAuth: HttpAuthService,
|
||||
) {
|
||||
return this.proxy ?? this.buildProxy(logger, clusterSupplier);
|
||||
return this.proxy ?? this.buildProxy(logger, clusterSupplier, httpAuth);
|
||||
}
|
||||
|
||||
protected getAuthStrategyMap() {
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
import { Config, ConfigReader } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
|
||||
describe('KubernetesFanOutHandler', () => {
|
||||
const fetchObjectsForService = jest.fn();
|
||||
@@ -62,6 +63,14 @@ describe('KubernetesFanOutHandler', () => {
|
||||
pod: {},
|
||||
};
|
||||
|
||||
const mockCredentials: BackstageCredentials = {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
principal: {
|
||||
userEntityRef: 'user:default/guest',
|
||||
type: 'user',
|
||||
},
|
||||
};
|
||||
|
||||
const entity = {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Component',
|
||||
@@ -372,10 +381,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(1);
|
||||
@@ -410,10 +422,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
@@ -442,10 +457,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(2);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
@@ -497,10 +515,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(2);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
@@ -559,10 +580,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -600,10 +624,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -665,10 +692,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(1);
|
||||
@@ -744,10 +774,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(1);
|
||||
@@ -779,12 +812,15 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
},
|
||||
},
|
||||
});
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(2);
|
||||
@@ -836,12 +872,15 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
},
|
||||
},
|
||||
});
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(3);
|
||||
@@ -897,12 +936,15 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
},
|
||||
},
|
||||
});
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(4);
|
||||
@@ -1015,12 +1057,15 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = await sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
const result = await sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {
|
||||
google: 'google_token_123',
|
||||
},
|
||||
},
|
||||
});
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(getClustersByEntity).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(2);
|
||||
@@ -1069,10 +1114,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
const result = sut.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
const result = sut.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
await expect(result).rejects.toThrow(nonFetchError);
|
||||
});
|
||||
|
||||
@@ -1155,10 +1203,13 @@ describe('KubernetesFanOutHandler', () => {
|
||||
});
|
||||
|
||||
const result =
|
||||
await kubernetesFanOutHandler.getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
});
|
||||
await kubernetesFanOutHandler.getKubernetesObjectsByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
const expected: ObjectsByEntityResponse = {
|
||||
items: [
|
||||
@@ -1200,17 +1251,20 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
await sut.getCustomResourcesByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
customResources: [
|
||||
{
|
||||
group: 'parameter-crd.example.com',
|
||||
apiVersion: 'v1alpha1',
|
||||
plural: 'parameter-crd',
|
||||
},
|
||||
],
|
||||
});
|
||||
await sut.getCustomResourcesByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
customResources: [
|
||||
{
|
||||
group: 'parameter-crd.example.com',
|
||||
apiVersion: 'v1alpha1',
|
||||
plural: 'parameter-crd',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(1);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
@@ -1262,17 +1316,20 @@ describe('KubernetesFanOutHandler', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await sut.getCustomResourcesByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
customResources: [
|
||||
{
|
||||
group: 'parameter-crd.example.com',
|
||||
apiVersion: 'v1alpha1',
|
||||
plural: 'parameter-crd',
|
||||
},
|
||||
],
|
||||
});
|
||||
await sut.getCustomResourcesByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
customResources: [
|
||||
{
|
||||
group: 'parameter-crd.example.com',
|
||||
apiVersion: 'v1alpha1',
|
||||
plural: 'parameter-crd',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledTimes(2);
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
@@ -1329,17 +1386,20 @@ describe('KubernetesFanOutHandler', () => {
|
||||
|
||||
sut = getKubernetesFanOutHandler([]);
|
||||
|
||||
await sut.getCustomResourcesByEntity({
|
||||
entity,
|
||||
auth: {},
|
||||
customResources: [
|
||||
{
|
||||
group: 'parameter-crd.example.com',
|
||||
apiVersion: 'v1alpha1',
|
||||
plural: 'parameter-crd',
|
||||
},
|
||||
],
|
||||
});
|
||||
await sut.getCustomResourcesByEntity(
|
||||
{
|
||||
entity,
|
||||
auth: {},
|
||||
customResources: [
|
||||
{
|
||||
group: 'parameter-crd.example.com',
|
||||
apiVersion: 'v1alpha1',
|
||||
plural: 'parameter-crd',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ credentials: mockCredentials },
|
||||
);
|
||||
|
||||
expect(fetchObjectsForService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -48,7 +48,9 @@ import {
|
||||
CustomResourcesByEntity,
|
||||
KubernetesCredential,
|
||||
KubernetesObjectsByEntity,
|
||||
KubernetesObjectsProvider,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -190,7 +192,7 @@ const toClientSafePodMetrics = (
|
||||
|
||||
type responseWithMetrics = [FetchResponseWrapper, PodStatusFetchResponse[]];
|
||||
|
||||
export class KubernetesFanOutHandler {
|
||||
export class KubernetesFanOutHandler implements KubernetesObjectsProvider {
|
||||
private readonly logger: Logger;
|
||||
private readonly fetcher: KubernetesFetcher;
|
||||
private readonly serviceLocator: KubernetesServiceLocator;
|
||||
@@ -214,30 +216,38 @@ export class KubernetesFanOutHandler {
|
||||
this.authStrategy = authStrategy;
|
||||
}
|
||||
|
||||
async getCustomResourcesByEntity({
|
||||
entity,
|
||||
auth,
|
||||
customResources,
|
||||
}: CustomResourcesByEntity): Promise<ObjectsByEntityResponse> {
|
||||
async getCustomResourcesByEntity(
|
||||
{ entity, auth, customResources }: CustomResourcesByEntity,
|
||||
options: { credentials: BackstageCredentials },
|
||||
): Promise<ObjectsByEntityResponse> {
|
||||
// Don't fetch the default object types only the provided custom resources
|
||||
return this.fanOutRequests(
|
||||
entity,
|
||||
auth,
|
||||
{ credentials: options.credentials },
|
||||
new Set<ObjectToFetch>(),
|
||||
customResources,
|
||||
);
|
||||
}
|
||||
|
||||
async getKubernetesObjectsByEntity({
|
||||
entity,
|
||||
auth,
|
||||
}: KubernetesObjectsByEntity): Promise<ObjectsByEntityResponse> {
|
||||
return this.fanOutRequests(entity, auth, this.objectTypesToFetch);
|
||||
async getKubernetesObjectsByEntity(
|
||||
{ entity, auth }: KubernetesObjectsByEntity,
|
||||
options: { credentials: BackstageCredentials },
|
||||
): Promise<ObjectsByEntityResponse> {
|
||||
return this.fanOutRequests(
|
||||
entity,
|
||||
auth,
|
||||
{
|
||||
credentials: options?.credentials,
|
||||
},
|
||||
this.objectTypesToFetch,
|
||||
);
|
||||
}
|
||||
|
||||
private async fanOutRequests(
|
||||
entity: Entity,
|
||||
auth: KubernetesRequestAuth,
|
||||
options: { credentials: BackstageCredentials },
|
||||
objectTypesToFetch: Set<ObjectToFetch>,
|
||||
customResources?: CustomResourceMatcher[],
|
||||
) {
|
||||
@@ -248,6 +258,7 @@ export class KubernetesFanOutHandler {
|
||||
const { clusters } = await this.serviceLocator.getClustersByEntity(entity, {
|
||||
objectTypesToFetch: objectTypesToFetch,
|
||||
customResources: customResources ?? [],
|
||||
credentials: options.credentials,
|
||||
});
|
||||
|
||||
this.logger.info(
|
||||
|
||||
@@ -55,6 +55,10 @@ import {
|
||||
} from './KubernetesProxy';
|
||||
|
||||
import type { Request } from 'express';
|
||||
import {
|
||||
BackstageCredentials,
|
||||
HttpAuthService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
const mockCertDir = createMockDirectory({
|
||||
content: {
|
||||
@@ -69,7 +73,10 @@ describe('KubernetesProxy', () => {
|
||||
const logger = getVoidLogger();
|
||||
|
||||
const clusterSupplier: jest.Mocked<KubernetesClustersSupplier> = {
|
||||
getClusters: jest.fn<Promise<ClusterDetails[]>, []>(),
|
||||
getClusters: jest.fn<
|
||||
Promise<ClusterDetails[]>,
|
||||
[{ credentials: BackstageCredentials }]
|
||||
>(),
|
||||
};
|
||||
|
||||
const permissionApi: jest.Mocked<PermissionEvaluator> = {
|
||||
@@ -77,6 +84,11 @@ describe('KubernetesProxy', () => {
|
||||
authorizeConditional: jest.fn(),
|
||||
};
|
||||
|
||||
const mockHttpAuth: jest.Mocked<HttpAuthService> = {
|
||||
credentials: jest.fn(),
|
||||
issueUserCookie: jest.fn(),
|
||||
};
|
||||
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
const buildMockRequest = (clusterName: any, path: string): Request => {
|
||||
@@ -146,7 +158,12 @@ describe('KubernetesProxy', () => {
|
||||
validateCluster: jest.fn(),
|
||||
presentAuthMetadata: jest.fn(),
|
||||
};
|
||||
proxy = new KubernetesProxy({ logger, clusterSupplier, authStrategy });
|
||||
proxy = new KubernetesProxy({
|
||||
logger,
|
||||
clusterSupplier,
|
||||
authStrategy,
|
||||
httpAuth: mockHttpAuth,
|
||||
});
|
||||
permissionApi.authorize.mockResolvedValue([
|
||||
{ result: AuthorizeResult.ALLOW },
|
||||
]);
|
||||
@@ -535,6 +552,7 @@ describe('KubernetesProxy', () => {
|
||||
logger: getVoidLogger(),
|
||||
clusterSupplier: clusterSupplier,
|
||||
authStrategy: strategy,
|
||||
httpAuth: mockHttpAuth,
|
||||
});
|
||||
|
||||
worker.use(
|
||||
@@ -656,6 +674,7 @@ describe('KubernetesProxy', () => {
|
||||
logger: getVoidLogger(),
|
||||
clusterSupplier: new LocalKubectlProxyClusterLocator(),
|
||||
authStrategy: new AnonymousStrategy(),
|
||||
httpAuth: mockHttpAuth,
|
||||
});
|
||||
|
||||
worker.use(
|
||||
|
||||
@@ -45,6 +45,7 @@ import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
|
||||
|
||||
import type { Request } from 'express';
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
import { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
|
||||
export const APPLICATION_JSON: string = 'application/json';
|
||||
|
||||
@@ -81,6 +82,7 @@ export type KubernetesProxyOptions = {
|
||||
logger: Logger;
|
||||
clusterSupplier: KubernetesClustersSupplier;
|
||||
authStrategy: AuthenticationStrategy;
|
||||
httpAuth: HttpAuthService;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -93,11 +95,13 @@ export class KubernetesProxy {
|
||||
private readonly logger: Logger;
|
||||
private readonly clusterSupplier: KubernetesClustersSupplier;
|
||||
private readonly authStrategy: AuthenticationStrategy;
|
||||
private readonly httpAuth: HttpAuthService;
|
||||
|
||||
constructor(options: KubernetesProxyOptions) {
|
||||
this.logger = options.logger;
|
||||
this.clusterSupplier = options.clusterSupplier;
|
||||
this.authStrategy = options.authStrategy;
|
||||
this.httpAuth = options.httpAuth;
|
||||
}
|
||||
|
||||
public createRequestHandler(
|
||||
@@ -108,6 +112,7 @@ export class KubernetesProxy {
|
||||
const authorizeResponse = await permissionApi.authorize(
|
||||
[{ permission: kubernetesProxyPermission }],
|
||||
{
|
||||
// todo: this should be updated too.
|
||||
token: getBearerTokenFromAuthorizationHeader(
|
||||
req.header('authorization'),
|
||||
),
|
||||
@@ -220,7 +225,9 @@ export class KubernetesProxy {
|
||||
|
||||
private async getClusterForRequest(req: Request): Promise<ClusterDetails> {
|
||||
const clusterName = req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];
|
||||
const clusters = await this.clusterSupplier.getClusters();
|
||||
const clusters = await this.clusterSupplier.getClusters({
|
||||
credentials: await this.httpAuth.credentials(req),
|
||||
});
|
||||
|
||||
if (!clusters || clusters.length <= 0) {
|
||||
throw new NotFoundError(`No Clusters configured`);
|
||||
|
||||
@@ -32,9 +32,15 @@ import { JsonObject } from '@backstage/types';
|
||||
export interface KubernetesObjectsProvider {
|
||||
getKubernetesObjectsByEntity(
|
||||
kubernetesObjectsByEntity: KubernetesObjectsByEntity,
|
||||
options: {
|
||||
credentials: BackstageCredentials;
|
||||
},
|
||||
): Promise<ObjectsByEntityResponse>;
|
||||
getCustomResourcesByEntity(
|
||||
customResourcesByEntity: CustomResourcesByEntity,
|
||||
options: {
|
||||
credentials: BackstageCredentials;
|
||||
},
|
||||
): Promise<ObjectsByEntityResponse>;
|
||||
}
|
||||
|
||||
@@ -135,7 +141,7 @@ export interface KubernetesClustersSupplier {
|
||||
* Implementations _should_ cache the clusters and refresh them periodically,
|
||||
* as getClusters is called whenever the list of clusters is needed.
|
||||
*/
|
||||
getClusters(options?: {
|
||||
getClusters(options: {
|
||||
credentials: BackstageCredentials;
|
||||
}): Promise<ClusterDetails[]>;
|
||||
}
|
||||
@@ -248,6 +254,7 @@ export interface KubernetesFetcher {
|
||||
export interface ServiceLocatorRequestContext {
|
||||
objectTypesToFetch: Set<ObjectToFetch>;
|
||||
customResources: CustomResourceMatcher[];
|
||||
credentials: BackstageCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user