Enabling authentication to kubernetes clusters with mTLS x509 client certs
Signed-off-by: Andres Mauricio Gomez P <andmagom@outlook.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
'@backstage/plugin-kubernetes-node': patch
|
||||
---
|
||||
|
||||
Enabling authentication to kubernetes clusters with mTLS x509 client certs
|
||||
@@ -895,7 +895,7 @@ describe('KubernetesFetcher', () => {
|
||||
customResources: [],
|
||||
});
|
||||
return expect(result).rejects.toThrow(
|
||||
"no bearer token for cluster 'unauthenticated-cluster' and not running in Kubernetes",
|
||||
"no bearer token or client cert for cluster 'unauthenticated-cluster' and not running in Kubernetes",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,13 +224,14 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
[url, requestInit] = this.fetchArgsInCluster(credential);
|
||||
} else if (
|
||||
credential.type === 'bearer token' ||
|
||||
credential.type === 'x509 client certificate' ||
|
||||
authProvider === 'localKubectlProxy'
|
||||
) {
|
||||
[url, requestInit] = this.fetchArgs(clusterDetails, credential);
|
||||
} else {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`no bearer token for cluster '${clusterDetails.name}' and not running in Kubernetes`,
|
||||
`no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -272,6 +273,10 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
clusterDetails.caData,
|
||||
) ?? undefined,
|
||||
rejectUnauthorized: !clusterDetails.skipTLSVerify,
|
||||
...(credential.type === 'x509 client certificate' && {
|
||||
cert: credential.cert,
|
||||
key: credential.key,
|
||||
}),
|
||||
});
|
||||
}
|
||||
return [url, requestInit];
|
||||
|
||||
@@ -113,24 +113,6 @@ export class KubernetesProxy {
|
||||
return;
|
||||
}
|
||||
|
||||
const authHeader = req.header(HEADER_KUBERNETES_AUTH);
|
||||
if (authHeader) {
|
||||
req.headers.authorization = authHeader;
|
||||
} else {
|
||||
// Map Backstage-Kubernetes-Authorization-X-X headers to a KubernetesRequestAuth object
|
||||
const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(
|
||||
req.headers,
|
||||
);
|
||||
|
||||
const credential = await this.getClusterForRequest(req).then(cd => {
|
||||
return this.authStrategy.getCredential(cd, authObj);
|
||||
});
|
||||
|
||||
if (credential.type === 'bearer token') {
|
||||
req.headers.authorization = `Bearer ${credential.token}`;
|
||||
}
|
||||
}
|
||||
|
||||
const middleware = await this.getMiddleware(req);
|
||||
|
||||
// If req is an upgrade handshake, use middleware upgrade instead of http request handler https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
||||
@@ -173,7 +155,7 @@ export class KubernetesProxy {
|
||||
const cluster = await this.getClusterForRequest(req);
|
||||
const url = new URL(cluster.url);
|
||||
|
||||
return {
|
||||
const target: any = {
|
||||
protocol: url.protocol,
|
||||
host: url.hostname,
|
||||
port: url.port,
|
||||
@@ -182,6 +164,29 @@ export class KubernetesProxy {
|
||||
cluster.caData,
|
||||
)?.toString(),
|
||||
};
|
||||
|
||||
const authHeader = req.header(HEADER_KUBERNETES_AUTH);
|
||||
if (authHeader) {
|
||||
req.headers.authorization = authHeader;
|
||||
} else {
|
||||
// Map Backstage-Kubernetes-Authorization-X-X headers to a KubernetesRequestAuth object
|
||||
const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(
|
||||
req.headers,
|
||||
);
|
||||
|
||||
const credential = await this.getClusterForRequest(req).then(cd => {
|
||||
return this.authStrategy.getCredential(cd, authObj);
|
||||
});
|
||||
|
||||
if (credential.type === 'bearer token') {
|
||||
req.headers.authorization = `Bearer ${credential.token}`;
|
||||
} else if (credential.type === 'x509 client certificate') {
|
||||
target.key = credential.key;
|
||||
target.cert = credential.cert;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
},
|
||||
onError: (error, req, res) => {
|
||||
const wrappedError = new ForwardedError(
|
||||
|
||||
@@ -100,6 +100,11 @@ export type KubernetesCredential =
|
||||
type: 'bearer token';
|
||||
token: string;
|
||||
}
|
||||
| {
|
||||
type: 'x509 client certificate';
|
||||
cert: string;
|
||||
key: string;
|
||||
}
|
||||
| {
|
||||
type: 'anonymous';
|
||||
};
|
||||
|
||||
@@ -139,6 +139,7 @@ export interface KubernetesClustersSupplier {
|
||||
*/
|
||||
export type KubernetesCredential =
|
||||
| { type: 'bearer token'; token: string }
|
||||
| { type: 'x509 client certificate'; cert: string; key: string }
|
||||
| { type: 'anonymous' };
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user