feat(kubernetes-plugin): add secrets rendering (#31415)
* feat: mask secret datas Signed-off-by: 김병준 <kingbj0429@gmail.com> * feat: add secrets accordion Signed-off-by: 김병준 <kingbj0429@gmail.com> * feat: add test code for secrets accordion Signed-off-by: 김병준 <kingbj0429@gmail.com> * feat: add test code for secrets fetch Signed-off-by: 김병준 <kingbj0429@gmail.com> * chore: changeset Signed-off-by: 김병준 <kingbj0429@gmail.com> * chore: yarn build:api-reports Signed-off-by: 김병준 <kingbj0429@gmail.com> * chore: remove secrets from default object and rollback test code Signed-off-by: 김병준 <kingbj0429@gmail.com> * chore: extract to helper function Signed-off-by: 김병준 <kingbj0429@gmail.com> * chore: add test code for helper function Signed-off-by: 김병준 <kingbj0429@gmail.com> --------- Signed-off-by: 김병준 <kingbj0429@gmail.com>
This commit is contained in:
@@ -1263,5 +1263,160 @@ describe('KubernetesFetcher', () => {
|
||||
]);
|
||||
expect(result.responses).toMatchObject(POD_METRICS_FIXTURE);
|
||||
});
|
||||
it('should mask secret data values with ***', async () => {
|
||||
worker.use(
|
||||
rest.get('http://localhost:9999/api/v1/secrets', (req, res, ctx) =>
|
||||
res(
|
||||
checkToken(req, ctx, 'token'),
|
||||
withLabels(req, ctx, {
|
||||
items: [
|
||||
{
|
||||
metadata: { name: 'secret-name' },
|
||||
data: {
|
||||
username: 'dXNlcm5hbWU=',
|
||||
password: 'cGFzc3dvcmQ=',
|
||||
apiKey: 'YXBpS2V5',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const result = await sut.fetchObjectsForService({
|
||||
serviceId: 'some-service',
|
||||
clusterDetails: {
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:9999',
|
||||
authMetadata: {},
|
||||
},
|
||||
credential: { type: 'bearer token', token: 'token' },
|
||||
objectTypesToFetch: new Set([
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'secrets',
|
||||
objectType: 'secrets',
|
||||
},
|
||||
]),
|
||||
labelSelector: '',
|
||||
customResources: [],
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
errors: [],
|
||||
responses: [
|
||||
{
|
||||
type: 'secrets',
|
||||
resources: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'secret-name',
|
||||
labels: {},
|
||||
},
|
||||
data: {
|
||||
username: '***',
|
||||
password: '***',
|
||||
apiKey: '***',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle secrets without data field', async () => {
|
||||
worker.use(
|
||||
rest.get('http://localhost:9999/api/v1/secrets', (req, res, ctx) =>
|
||||
res(
|
||||
checkToken(req, ctx, 'token'),
|
||||
withLabels(req, ctx, {
|
||||
items: [
|
||||
{
|
||||
metadata: { name: 'secret-without-data' },
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const result = await sut.fetchObjectsForService({
|
||||
serviceId: 'some-service',
|
||||
clusterDetails: {
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:9999',
|
||||
authMetadata: {},
|
||||
},
|
||||
credential: { type: 'bearer token', token: 'token' },
|
||||
objectTypesToFetch: new Set([
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'secrets',
|
||||
objectType: 'secrets',
|
||||
},
|
||||
]),
|
||||
labelSelector: '',
|
||||
customResources: [],
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
errors: [],
|
||||
responses: [
|
||||
{
|
||||
type: 'secrets',
|
||||
resources: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'secret-without-data',
|
||||
labels: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformResources', () => {
|
||||
let fetcher: KubernetesClientBasedFetcher;
|
||||
|
||||
beforeEach(() => {
|
||||
fetcher = new KubernetesClientBasedFetcher({
|
||||
logger: mockServices.logger.mock(),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes List suffix from custom resource kinds', () => {
|
||||
const result = (fetcher as any).transformResources(
|
||||
'customresources',
|
||||
'HelloWorldList',
|
||||
[{ name: 'foo' }],
|
||||
);
|
||||
expect(result[0].kind).toBe('HelloWorld');
|
||||
});
|
||||
|
||||
it('masks secret data values', () => {
|
||||
const result = (fetcher as any).transformResources(
|
||||
'secrets',
|
||||
'SecretList',
|
||||
[{ data: { password: 'secret123' } }],
|
||||
);
|
||||
expect(result[0].data.password).toBe('***');
|
||||
});
|
||||
|
||||
it('returns other types unchanged', () => {
|
||||
const items = [{ name: 'pod' }];
|
||||
const result = (fetcher as any).transformResources(
|
||||
'pods',
|
||||
'PodList',
|
||||
items,
|
||||
);
|
||||
expect(result).toBe(items);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,13 +103,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
? r.json().then(
|
||||
({ kind, items }): FetchResponse => ({
|
||||
type: objectType,
|
||||
resources:
|
||||
objectType === 'customresources'
|
||||
? items.map((item: JsonObject) => ({
|
||||
...item,
|
||||
kind: kind.replace(/(List)$/, ''),
|
||||
}))
|
||||
: items,
|
||||
resources: this.transformResources(objectType, kind, items),
|
||||
}),
|
||||
)
|
||||
: this.handleUnsuccessfulResponse(params.clusterDetails.name, r),
|
||||
@@ -320,4 +314,33 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
}
|
||||
return [url, requestInit];
|
||||
}
|
||||
|
||||
private transformResources(
|
||||
objectType: string,
|
||||
kind: string,
|
||||
items: JsonObject[],
|
||||
): JsonObject[] {
|
||||
if (objectType === 'customresources') {
|
||||
return items.map((item: JsonObject) => ({
|
||||
...item,
|
||||
kind: kind.replace(/(List)$/, ''),
|
||||
}));
|
||||
}
|
||||
|
||||
if (objectType === 'secrets') {
|
||||
return items.map((item: JsonObject) => {
|
||||
if (item.data && typeof item.data === 'object') {
|
||||
return {
|
||||
...item,
|
||||
data: Object.fromEntries(
|
||||
Object.keys(item.data).map(key => [key, '***']),
|
||||
),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user