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:
Andres Mauricio Gomez P
2023-12-13 10:21:56 -05:00
parent d7a1adf48b
commit f180cba319
6 changed files with 43 additions and 21 deletions
+6
View File
@@ -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(
+5
View File
@@ -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' };
/**