skip authorization header for server-side auth (#17306)

The backend is responsible for appending a bearer token in these cases.

Signed-off-by: Jamie Klassen <jklassen@vmware.com>
This commit is contained in:
Jamie Klassen
2023-04-10 15:12:26 -04:00
committed by GitHub
parent eeb97f6592
commit e7fb011748
7 changed files with 47 additions and 18 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
fixes a bug where an empty authorization header was provided to the proxy endpoint when a cluster had a server-side auth provider
+3 -5
View File
@@ -291,7 +291,7 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
): Promise<KubernetesRequestBody>;
// (undocumented)
getCredentials(authProvider: string): Promise<{
token: string;
token?: string;
}>;
}
@@ -306,7 +306,7 @@ export interface KubernetesAuthProvidersApi {
): Promise<KubernetesRequestBody>;
// (undocumented)
getCredentials(authProvider: string): Promise<{
token: string;
token?: string;
}>;
}
@@ -440,9 +440,7 @@ export class ServerSideKubernetesAuthProvider
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody>;
// (undocumented)
getCredentials(): Promise<{
token: string;
}>;
getCredentials(): Promise<{}>;
}
// Warning: (ae-forgotten-export) The symbol "ServicesAccordionsProps" needs to be exported by the entry point index.d.ts
@@ -395,10 +395,10 @@ describe('KubernetesBackendClient', () => {
),
),
);
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
});
it('hits the /proxy API', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({
token: 'k8-token',
});
@@ -468,7 +468,6 @@ describe('KubernetesBackendClient', () => {
});
it('/proxy API throws a 404 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({
token: 'k8-token',
});
@@ -490,8 +489,6 @@ describe('KubernetesBackendClient', () => {
});
it('throws a ERROR_NOT_FOUND if the cluster in the request is not found', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
const request = {
clusterName: 'cluster-b',
path: '/api/v1/namespaces',
@@ -501,8 +498,6 @@ describe('KubernetesBackendClient', () => {
});
it('responds with an 403 error when invalid k8 token is provided', async () => {
// when a user is signed in as a guest the result of the getCredentials() method resolves to the {} value.
identityApi.getCredentials.mockResolvedValue({});
kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({
token: 'wrong-token',
});
@@ -535,5 +530,34 @@ describe('KubernetesBackendClient', () => {
const response = await backendClient.proxy(request);
expect(response.status).toEqual(403);
});
it('skips authorization header when auth provider returns no creds', async () => {
const nsResponse = {
kind: 'Namespace',
apiVersion: 'v1',
metadata: {
name: 'new-ns',
},
};
worker.use(
rest.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces/new-ns',
(req, res, ctx) =>
res(
req.headers.get('Backstage-Kubernetes-Authorization')
? ctx.status(403)
: ctx.json(nsResponse),
),
),
);
kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({});
const response = await backendClient.proxy({
clusterName: 'cluster-a',
path: '/api/v1/namespaces/new-ns',
});
await expect(response.json()).resolves.toStrictEqual(nsResponse);
});
});
});
@@ -89,7 +89,7 @@ export class KubernetesBackendClient implements KubernetesApi {
private async getCredentials(
authProvider: string,
): Promise<{ token: string }> {
): Promise<{ token?: string }> {
return await this.kubernetesAuthProvidersApi.getCredentials(authProvider);
}
@@ -148,7 +148,9 @@ export class KubernetesBackendClient implements KubernetesApi {
const headers = {
...options.init?.headers,
[`Backstage-Kubernetes-Cluster`]: options.clusterName,
[`Backstage-Kubernetes-Authorization`]: `Bearer ${k8sToken}`,
...(k8sToken && {
[`Backstage-Kubernetes-Authorization`]: `Bearer ${k8sToken}`,
}),
...(identityResponse.token && {
Authorization: `Bearer ${identityResponse.token}`,
}),
@@ -92,7 +92,7 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
);
}
async getCredentials(authProvider: string): Promise<{ token: string }> {
async getCredentials(authProvider: string): Promise<{ token?: string }> {
const kubernetesAuthProvider: KubernetesAuthProvider | undefined =
this.kubernetesAuthProviderMap.get(authProvider);
@@ -32,7 +32,7 @@ export class ServerSideKubernetesAuthProvider
return requestBody;
}
async getCredentials(): Promise<{ token: string }> {
return { token: '' };
async getCredentials(): Promise<{}> {
return {};
}
}
@@ -21,7 +21,7 @@ export interface KubernetesAuthProvider {
decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody>;
getCredentials(): Promise<{ token: string }>;
getCredentials(): Promise<{ token?: string }>;
}
export const kubernetesAuthProvidersApiRef =
@@ -34,5 +34,5 @@ export interface KubernetesAuthProvidersApi {
authProvider: string,
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody>;
getCredentials(authProvider: string): Promise<{ token: string }>;
getCredentials(authProvider: string): Promise<{ token?: string }>;
}