move in-cluster token handling to strategy
Signed-off-by: Jamie Klassen <jklassen@vmware.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ServiceAccountStrategy } from './ServiceAccountStrategy';
|
||||
import mockFs from 'mock-fs';
|
||||
|
||||
describe('ServiceAccountStrategy', () => {
|
||||
describe('#getCredential', () => {
|
||||
it('reads bearer token from config', async () => {
|
||||
const strategy = new ServiceAccountStrategy();
|
||||
|
||||
const credential = await strategy.getCredential({
|
||||
name: '',
|
||||
url: '',
|
||||
authMetadata: { serviceAccountToken: 'from config' },
|
||||
});
|
||||
|
||||
expect(credential).toStrictEqual({
|
||||
type: 'bearer token',
|
||||
token: 'from config',
|
||||
});
|
||||
});
|
||||
describe('when serviceAccountToken is absent from config', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('reads in-cluster token', async () => {
|
||||
mockFs({
|
||||
'/var/run/secrets/kubernetes.io/serviceaccount/token':
|
||||
'in-cluster-token',
|
||||
});
|
||||
const strategy = new ServiceAccountStrategy();
|
||||
|
||||
const credential = await strategy.getCredential({
|
||||
name: '',
|
||||
url: '',
|
||||
authMetadata: {},
|
||||
});
|
||||
|
||||
expect(credential).toStrictEqual({
|
||||
type: 'bearer token',
|
||||
token: 'in-cluster-token',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { KubeConfig, User } from '@kubernetes/client-node';
|
||||
import fs from 'fs-extra';
|
||||
import { AuthenticationStrategy, KubernetesCredential } from './types';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
|
||||
@@ -26,7 +28,18 @@ export class ServiceAccountStrategy implements AuthenticationStrategy {
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<KubernetesCredential> {
|
||||
const token = clusterDetails.authMetadata.serviceAccountToken;
|
||||
return token ? { type: 'bearer token', token } : { type: 'anonymous' };
|
||||
if (token) {
|
||||
return { type: 'bearer token', token };
|
||||
}
|
||||
const kc = new KubeConfig();
|
||||
kc.loadFromCluster();
|
||||
// loadFromCluster is guaranteed to populate the user
|
||||
const user = kc.getCurrentUser() as User;
|
||||
|
||||
return {
|
||||
type: 'bearer token',
|
||||
token: fs.readFileSync(user.authProvider.config.tokenFile).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
public validate() {}
|
||||
|
||||
@@ -203,6 +203,7 @@ describe('KubernetesBuilder', () => {
|
||||
url: 'https://localhost:1234',
|
||||
authMetadata: {
|
||||
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount',
|
||||
serviceAccountToken: 'placeholder-token',
|
||||
},
|
||||
};
|
||||
const clusters: ClusterDetails[] = [
|
||||
|
||||
@@ -909,8 +909,6 @@ describe('KubernetesFetcher', () => {
|
||||
process.env.KUBERNETES_SERVICE_PORT = '443';
|
||||
mockFs({
|
||||
'/var/run/secrets/kubernetes.io/serviceaccount/ca.crt': '',
|
||||
'/var/run/secrets/kubernetes.io/serviceaccount/token':
|
||||
'allowed-token',
|
||||
});
|
||||
worker.use(
|
||||
rest.get('https://10.10.10.10/api/v1/pods', (req, res, ctx) =>
|
||||
@@ -928,9 +926,11 @@ describe('KubernetesFetcher', () => {
|
||||
clusterDetails: {
|
||||
name: 'overridden-to-in-cluster',
|
||||
url: 'http://ignored',
|
||||
authMetadata: {},
|
||||
authMetadata: {
|
||||
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount',
|
||||
},
|
||||
},
|
||||
credential: { type: 'anonymous' },
|
||||
credential: { type: 'bearer token', token: 'allowed-token' },
|
||||
objectTypesToFetch: new Set<ObjectToFetch>([
|
||||
{
|
||||
group: '',
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
CoreV1Api,
|
||||
KubeConfig,
|
||||
Metrics,
|
||||
User,
|
||||
bufferFromFileOrString,
|
||||
topPods,
|
||||
} from '@kubernetes/client-node';
|
||||
@@ -214,14 +213,19 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
|
||||
let url: URL;
|
||||
let requestInit: RequestInit;
|
||||
const authProvider =
|
||||
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
||||
if (
|
||||
authProvider === 'serviceAccount' &&
|
||||
!clusterDetails.authMetadata.serviceAccountToken &&
|
||||
fs.pathExistsSync(Config.SERVICEACCOUNT_CA_PATH)
|
||||
) {
|
||||
[url, requestInit] = this.fetchArgsInCluster(credential);
|
||||
} else if (
|
||||
credential.type === 'bearer token' ||
|
||||
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER] ===
|
||||
'localKubectlProxy'
|
||||
authProvider === 'localKubectlProxy'
|
||||
) {
|
||||
[url, requestInit] = this.fetchArgs(clusterDetails, credential);
|
||||
} else if (fs.pathExistsSync(Config.SERVICEACCOUNT_TOKEN_PATH)) {
|
||||
[url, requestInit] = this.fetchArgsInCluster();
|
||||
} else {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
@@ -271,24 +275,25 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
}
|
||||
return [url, requestInit];
|
||||
}
|
||||
private fetchArgsInCluster(): [URL, RequestInit] {
|
||||
const kc = new KubeConfig();
|
||||
kc.loadFromCluster();
|
||||
// loadFromCluster is guaranteed to populate the cluster/user/context
|
||||
const cluster = kc.getCurrentCluster() as Cluster;
|
||||
const user = kc.getCurrentUser() as User;
|
||||
|
||||
const token = fs.readFileSync(user.authProvider.config.tokenFile);
|
||||
|
||||
private fetchArgsInCluster(
|
||||
credential: KubernetesCredential,
|
||||
): [URL, RequestInit] {
|
||||
const requestInit: RequestInit = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
...(credential.type === 'bearer token' && {
|
||||
Authorization: `Bearer ${credential.token}`,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const kc = new KubeConfig();
|
||||
kc.loadFromCluster();
|
||||
// loadFromCluster is guaranteed to populate the cluster/user/context
|
||||
const cluster = kc.getCurrentCluster() as Cluster;
|
||||
|
||||
const url = new URL(cluster.server);
|
||||
if (url.protocol === 'https:') {
|
||||
requestInit.agent = new https.Agent({
|
||||
|
||||
Reference in New Issue
Block a user