Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ const mockEntityWithoutAnnotations = {
},
};

const mockPipelineScanV1 = {
resultId: 'result-pipeline-123',
imageId: 'sha256:deadbeef1234',
pullString: 'ghcr.io/sysdiglabs/sample-app:latest',
policyEvaluationResult: 'failed',
createdAt: new Date('2024-01-22T08:51:46Z'),
vulnTotalBySeverity: { critical: 1, high: 3, medium: 7, low: 2, negligible: 0 },
};

const mockSysdigApi = {
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }),
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }),
Expand Down Expand Up @@ -92,4 +101,48 @@ describe('SysdigVMPipelineFetchComponent', () => {
const message = await screen.findByText(/missing annotation/i);
expect(message).toBeInTheDocument();
});

it('renders rows using v1 policyEvaluationResult field (without s)', async () => {
const apiWithData = {
...mockSysdigApi,
fetchVulnPipeline: jest.fn().mockResolvedValue({ data: [mockPipelineScanV1] }),
};

await renderInTestApp(
<TestApiProvider apis={[
[sysdigApiRef, apiWithData],
[configApiRef, mockConfig],
]}>
<EntityProvider entity={mockEntity}>
<SysdigVMPipelineFetchComponent />
</EntityProvider>
</TestApiProvider>
);

expect(await screen.findByText('ghcr.io/sysdiglabs/sample-app:latest')).toBeInTheDocument();
expect(screen.getByText('failed')).toBeInTheDocument();
});

it('filters out rows with null policyEvaluationResult', async () => {
const scanWithNullPolicy = { ...mockPipelineScanV1, policyEvaluationResult: null as any };
const apiWithData = {
...mockSysdigApi,
fetchVulnPipeline: jest.fn().mockResolvedValue({ data: [scanWithNullPolicy, mockPipelineScanV1] }),
};

await renderInTestApp(
<TestApiProvider apis={[
[sysdigApiRef, apiWithData],
[configApiRef, mockConfig],
]}>
<EntityProvider entity={mockEntity}>
<SysdigVMPipelineFetchComponent />
</EntityProvider>
</TestApiProvider>
);

// Only the scan with a non-null policyEvaluationResult should appear
const rows = await screen.findAllByText('ghcr.io/sysdiglabs/sample-app:latest');
expect(rows).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ import { sysdigApiRef } from '../../api';
type PipelineScan = {
createdAt: Date,
imageId: string,
mainAssetName: string,
policyEvaluationsResult: string,
pullString: string,
policyEvaluationResult: string,
resultId: string,
vulnTotalBySeverity: {
critical: number,
Expand All @@ -58,8 +58,8 @@ type DenseTableProps = {
{
"createdAt": "2019-08-24T14:15:22Z",
"imageId": "string",
"mainAssetName": "string",
"policyEvaluationsResult": "passed",
"pullString": "string",
"policyEvaluationResult": "passed",
"resultId": "string",
"vulnTotalBySeverity": {
"critical": 0,
Expand All @@ -82,12 +82,12 @@ export const DenseTable = ({ pipelineScans, title }: DenseTableProps) => {
// { title: 'URL', field: "url", width: "10%" },
];

const data = pipelineScans.filter(scan => { return scan.policyEvaluationsResult !== null && scan.policyEvaluationsResult !== '' })
const data = pipelineScans.filter(scan => { return scan.policyEvaluationResult !== null && scan.policyEvaluationResult !== '' })
.flatMap(scan => {
return {
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationsResult),
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationResult),
imageId: <code>{scan.imageId}</code>,
asset: scan.mainAssetName,
asset: scan.pullString,
vulns: getChips(scan.vulnTotalBySeverity),
// convert image.lastEvaluatedAt to a date string
// lastEvaluatedAt: getDate(image.lastEvaluatedAt * 1000),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ const mockEntityWithoutAnnotations = {
},
};

const mockRegistryScanV1 = {
resultId: 'result-registry-456',
imageId: 'sha256:abc123def456',
pullString: 'harbor.example.com/library/nginx:1.25',
vulnTotalBySeverity: { critical: 0, high: 2, medium: 5, low: 1, negligible: 3 },
};

const mockSysdigApi = {
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }),
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }),
Expand Down Expand Up @@ -93,4 +100,24 @@ describe('SysdigVMRegistryFetchComponent', () => {
const message = await screen.findByText(/missing annotation/i);
expect(message).toBeInTheDocument();
});

it('renders rows using v1 pullString field (not mainAssetName)', async () => {
const apiWithData = {
...mockSysdigApi,
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [mockRegistryScanV1] }),
};

await renderInTestApp(
<TestApiProvider apis={[
[sysdigApiRef, apiWithData],
[configApiRef, mockConfig],
]}>
<EntityProvider entity={mockEntity}>
<SysdigVMRegistryFetchComponent />
</EntityProvider>
</TestApiProvider>
);

expect(await screen.findByText('harbor.example.com/library/nginx:1.25')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import { sysdigApiRef } from '../../api';

type RegistryScan = {
mainAssetName: string,
pullString: string,
imageId: string,
resultId: string,
vulnTotalBySeverity: {
Expand All @@ -57,7 +57,7 @@ type DenseTableProps = {
{
"createdAt": "2019-08-24T14:15:22Z",
"imageId": "string",
"mainAssetName": "string",
"pullString": "string",
"resultId": "string",
"vulnTotalBySeverity": {
"critical": 0,
Expand All @@ -81,7 +81,7 @@ export const DenseTable = ({ registryScans, title }: DenseTableProps) => {
.flatMap(scan => {
return {
imageId: <code>{scan.imageId}</code>,
asset: scan.mainAssetName,
asset: scan.pullString,
severity: getChips(scan.vulnTotalBySeverity)
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ const mockEntityWithoutAnnotations = {
},
};

const mockRuntimeScanV1 = {
resultId: 'result-abc123',
resourceId: 'sha256:a1b2c3d4e5f6',
mainAssetName: 'nginx:latest',
policyEvaluationResult: 'failed',
isRiskSpotlightEnabled: true,
sbomId: 'sbom-xyz',
scope: {
'asset.type': 'workload',
'kubernetes.cluster.name': 'test-cluster',
'kubernetes.namespace.name': 'test-namespace',
'kubernetes.workload.name': 'nginx',
'kubernetes.workload.type': 'deployment',
'kubernetes.pod.container.name': 'nginx',
},
vulnTotalBySeverity: { critical: 2, high: 5, medium: 10, low: 3, negligible: 1 },
runningVulnTotalBySeverity: { critical: 1, high: 2, medium: 4, low: 1, negligible: 0 },
};

const mockSysdigApi = {
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [] }),
fetchVulnRegistry: jest.fn().mockResolvedValue({ data: [] }),
Expand Down Expand Up @@ -93,4 +112,48 @@ describe('SysdigVMRuntimeFetchComponent', () => {
const message = await screen.findByText(/missing annotation/i);
expect(message).toBeInTheDocument();
});

it('renders rows using v1 policyEvaluationResult field (without s)', async () => {
const apiWithData = {
...mockSysdigApi,
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [mockRuntimeScanV1] }),
};

await renderInTestApp(
<TestApiProvider apis={[
[sysdigApiRef, apiWithData],
[configApiRef, mockConfig],
]}>
<EntityProvider entity={mockEntity}>
<SysdigVMRuntimeFetchComponent />
</EntityProvider>
</TestApiProvider>
);

expect(await screen.findByText('nginx:latest')).toBeInTheDocument();
expect(screen.getByText('failed')).toBeInTheDocument();
});

it('filters out rows with null policyEvaluationResult', async () => {
const scanWithNullPolicy = { ...mockRuntimeScanV1, policyEvaluationResult: null as any };
const apiWithData = {
...mockSysdigApi,
fetchVulnRuntime: jest.fn().mockResolvedValue({ data: [scanWithNullPolicy, mockRuntimeScanV1] }),
};

await renderInTestApp(
<TestApiProvider apis={[
[sysdigApiRef, apiWithData],
[configApiRef, mockConfig],
]}>
<EntityProvider entity={mockEntity}>
<SysdigVMRuntimeFetchComponent />
</EntityProvider>
</TestApiProvider>
);

// Only the scan with a non-null policyEvaluationResult should appear (1 row = 1 asset name)
const rows = await screen.findAllByText('nginx:latest');
expect(rows).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ import { sysdigApiRef } from '../../api';
type RuntimeScan = {
isRiskSpotlightEnabled: boolean,
mainAssetName: string,
policyEvaluationsResult: string,
policyEvaluationResult: string,
resourceId: string,
resultId: string,
runningVulnTotalBySeverity: {
critical: number,
Expand Down Expand Up @@ -120,10 +121,10 @@ export const DenseTable = ({ runtimeScans, title }: DenseTableProps) => {
// { title: 'URL', field: "url", width: "10%" },
];

const data = runtimeScans.filter(scan => { return scan.policyEvaluationsResult !== null && scan.policyEvaluationsResult !== '' })
const data = runtimeScans.filter(scan => { return scan.policyEvaluationResult !== null && scan.policyEvaluationResult !== '' })
.flatMap(scan => {
return {
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationsResult),
policyEvalStatus: getStatusColorSpan(scan.policyEvaluationResult),
asset: scan.mainAssetName,
// scope: JSON.stringify(scan.scope),
severity: getChips(scan.vulnTotalBySeverity),
Expand Down
11 changes: 10 additions & 1 deletion src/infra/api/SysdigApiClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ConfigApi, FetchApi } from "@backstage/core-plugin-api";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { SysdigApiClient } from "./SysdigApiClient";
import { API_PROXY_BASE_PATH, API_VULN_RUNTIME } from "../../lib";
import { API_PROXY_BASE_PATH, API_VULN_RUNTIME, API_VULN_REGISTRY, API_VULN_PIPELINE } from "../../lib";

describe("SysdigApiClient", () => {
const configApi: ConfigApi = new ConfigReader({
Expand Down Expand Up @@ -46,4 +46,13 @@ describe("SysdigApiClient", () => {
undefined,
);
});

it("should use v1 endpoint paths (not v1beta1)", () => {
expect(API_VULN_RUNTIME).toMatch(/\/v1\//);
expect(API_VULN_REGISTRY).toMatch(/\/v1\//);
expect(API_VULN_PIPELINE).toMatch(/\/v1\//);
expect(API_VULN_RUNTIME).not.toMatch(/v1beta1/);
expect(API_VULN_REGISTRY).not.toMatch(/v1beta1/);
expect(API_VULN_PIPELINE).not.toMatch(/v1beta1/);
});
});
8 changes: 4 additions & 4 deletions src/lib/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export const API_PROXY_BASE_PATH = "/api/proxy/sysdig";
*/

// API Endpoint for Vulnerability Management at Runtime
export const API_VULN_RUNTIME = "/secure/vulnerability/v1beta1/runtime-results";
export const API_VULN_RUNTIME = "/secure/vulnerability/v1/runtime-results";

// API Endpoint for Vulnerability Management at Registry
export const API_VULN_REGISTRY = "/secure/vulnerability/v1beta1/registry-results";
export const API_VULN_REGISTRY = "/secure/vulnerability/v1/registry-results";

// API Endpoint for Vulnerability Management at Pipeline
export const API_VULN_PIPELINE = "/secure/vulnerability/v1beta1/pipeline-results";
export const API_VULN_PIPELINE = "/secure/vulnerability/v1/pipeline-results";

// API Endpoint for Inventory (Posture)
export const API_INVENTORY = "/api/cspm/v1/inventory/resources";
Expand Down Expand Up @@ -54,4 +54,4 @@ export function getBacklink(endpoint: string | undefined, backlink: string | und
}

return backlink_base + backlink_section;
}
}
Loading