Skip to content

Commit

Permalink
feat(tekton): add CVE summary and signed badge (#1028)
Browse files Browse the repository at this point in the history
feat(tekton): Add CVE summary and signed badge
  • Loading branch information
jeff-phillips-18 authored Dec 22, 2023
1 parent 94bda1c commit effdef0
Show file tree
Hide file tree
Showing 17 changed files with 497 additions and 12 deletions.
3 changes: 2 additions & 1 deletion plugins/shared-react/src/types/pipeline/pipelineRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export type PipelineRunStatus = {
skippedTasks?: {
name: string;
}[];
pipelineResults?: TektonResultsRun[];
pipelineResults?: TektonResultsRun[]; // in tekton v1 pipelineResults is renamed to results
results?: TektonResultsRun[];
};

export type PipelineRunKind = {
Expand Down
1 change: 1 addition & 0 deletions plugins/tekton/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@patternfly/react-charts": "^7.1.1",
"@patternfly/react-core": "^5.1.1",
"@patternfly/react-icons": "^5.1.1",
"@patternfly/react-tokens": "^5.1.2",
"@patternfly/react-topology": "^5.1.0",
"classnames": "^2.3.2",
"dagre": "^0.8.5",
Expand Down
16 changes: 16 additions & 0 deletions plugins/tekton/src/__fixtures__/1-pipelinesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export const mockKubernetesPlrResponse = {
kind: 'PipelineRun',
metadata: {
annotations: {
'chains.tekton.dev/signed': 'true',
'pipeline.openshift.io/started-by': 'kube:admin',
},
creationTimestamp: new Date('2023-03-30T07:03:04Z'),
Expand Down Expand Up @@ -319,6 +320,13 @@ export const mockKubernetesPlrResponse = {
],
workspaces: [],
},
pipelineResults: [
{
name: 'MY_SCAN_OUTPUT',
value:
'{"vulnerabilities":{\n"critical": 1,\n"high": 9,\n"medium": 20,\n"low": 1,\n"unknown": 0},\n"unpatched_vulnerabilities": {\n"critical": 0,\n"high": 1,\n"medium": 0,\n"low":1}\n}\n',
},
],
startTime: '2023-03-30T07:03:04Z',
},
},
Expand All @@ -328,6 +336,7 @@ export const mockKubernetesPlrResponse = {
metadata: {
annotations: {
'pipeline.openshift.io/started-by': 'kube-admin',
'chains.tekton.dev/signed': 'false',
},
labels: {
'backstage.io/kubernetes-id': 'test-backstage',
Expand Down Expand Up @@ -406,6 +415,13 @@ export const mockKubernetesPlrResponse = {
startTime: '2023-04-11T06:48:50Z',
},
startTime: '2023-04-11T05:49:05Z',
results: [
{
name: 'SCAN_OUTPUT',
value:
'{"vulnerabilities":{\n"critical": 13,\n"high": 29,\n"medium": 32,\n"low": 3,\n"unknown": 0},\n"unpatched_vulnerabilities": {\n"critical": 0,\n"high": 1,\n"medium": 0,\n"low":1}\n}\n',
},
],
},
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { TableColumn } from '@backstage/core-components';

export const PipelineRunColumnHeader: TableColumn[] = [
{},
{
id: 'expander',
},
{
id: 'name',
title: 'NAME',
field: 'metadata.name',
},
{
id: 'vulnerabilities',
title: 'VULNERABILITIES',
field: 'status.results',
},
{
id: 'status',
title: 'STATUS',
Expand Down
32 changes: 30 additions & 2 deletions plugins/tekton/src/components/PipelineRunList/PipelineRunRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ import {
} from '@material-ui/core';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight';
import { Timestamp } from '@patternfly/react-core';
import { Timestamp, Tooltip } from '@patternfly/react-core';

import { PipelineRunKind } from '@janus-idp/shared-react';

import { TEKTON_SIGNED_ANNOTATION } from '../../consts/tekton-const';
import { OpenRowStatus, tektonGroupColor } from '../../types/types';
import { signedBadgeSrc } from '../../utils/image-utils';
import { pipelineRunDuration } from '../../utils/tekton-utils';
import { PipelineRunVisualization } from '../pipeline-topology';
import PipelineRunRowActions from './PipelineRunRowActions';
import PipelineRunTaskStatus from './PipelineRunTaskStatus';
import PipelineRunVulnerabilities from './PipelineRunVulnerabilities';
import PlrStatus from './PlrStatus';
import ResourceBadge from './ResourceBadge';

Expand All @@ -34,6 +37,12 @@ const useStyles = makeStyles((theme: Theme) => ({
plrVisRow: {
borderBottom: `1px solid ${theme.palette.grey.A100}`,
},
signedIndicator: {
display: 'inline-block',
width: theme.spacing(2.5),
position: 'relative',
top: theme.spacing(0.5),
},
}));

type PipelineRunRowProps = {
Expand All @@ -47,12 +56,28 @@ type PipelineRunRowProps = {
type PipelineRunNameProps = { row: PipelineRunKind };

const PipelineRunName = ({ row }: PipelineRunNameProps) => {
const classes = useStyles();
const name = row.metadata?.name;
const signed =
row?.metadata?.annotations?.[TEKTON_SIGNED_ANNOTATION] === 'true';

return (
<div>
{name ? (
<ResourceBadge color={tektonGroupColor} abbr="PLR" name={name || ''} />
<ResourceBadge
color={tektonGroupColor}
abbr="PLR"
name={name || ''}
suffix={
signed ? (
<Tooltip content="Signed">
<div className={classes.signedIndicator}>
<img src={signedBadgeSrc} alt="Signed" />
</div>
</Tooltip>
) : null
}
/>
) : (
'-'
)}
Expand Down Expand Up @@ -105,6 +130,9 @@ export const PipelineRunRow = ({
<TableCell align="left">
<PipelineRunName row={row} />
</TableCell>
<TableCell align="left">
<PipelineRunVulnerabilities pipelineRun={row} condensed />
</TableCell>
<TableCell align="left">
<PlrStatus obj={row} />
</TableCell>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import * as React from 'react';

import { makeStyles, Theme } from '@material-ui/core';
import { Tooltip } from '@patternfly/react-core';
import {
AngleDoubleDownIcon,
AngleDoubleUpIcon,
CriticalRiskIcon,
EqualsIcon,
} from '@patternfly/react-icons';
import { global_palette_blue_300 as lowColor } from '@patternfly/react-tokens/dist/js/global_palette_blue_300';
import { global_palette_gold_400 as mediumColor } from '@patternfly/react-tokens/dist/js/global_palette_gold_400';
import { global_palette_orange_300 as highColor } from '@patternfly/react-tokens/dist/js/global_palette_orange_300';
import { global_palette_red_200 as criticalColor } from '@patternfly/react-tokens/dist/js/global_palette_red_200';

import { PipelineRunKind } from '@janus-idp/shared-react';

import { usePipelineRunScanResults } from '../../hooks/usePipelineRunScanResults';

const useVStyles = makeStyles((theme: Theme) => ({
pipelineVulnerabilities: {
display: 'flex',
flexWrap: 'wrap',
gap: theme.spacing(1),
},
severityContainer: {
alignItems: 'center',
display: 'flex',
flexWrap: 'nowrap',
gap: theme.spacing(0.5),
},
severityStatus: {
alignItems: 'center',
display: 'flex',
flexWrap: 'nowrap',
gap: theme.spacing(0.5),
},
severityCount: {
fontWeight: 'bold',
},
criticalStatus: {
color: criticalColor.value,
},
highStatus: {
color: highColor.value,
},
mediumStatus: {
color: mediumColor.value,
},
lowStatus: {
color: lowColor.value,
},
}));

type PipelineRunVulnerabilitiesProps = {
pipelineRun: PipelineRunKind;
condensed?: boolean;
};

const PipelineRunVulnerabilities: React.FC<PipelineRunVulnerabilitiesProps> = ({
pipelineRun,
condensed,
}) => {
const classes = useVStyles();
const scanResults = usePipelineRunScanResults(pipelineRun);

return (
<div className={classes.pipelineVulnerabilities}>
{scanResults?.vulnerabilities ? (
<>
<div className={classes.severityContainer}>
<span className={classes.severityStatus}>
<Tooltip content="Critical">
<CriticalRiskIcon
title="Critical"
className={classes.criticalStatus}
/>
</Tooltip>
{!condensed ? 'Critical' : null}
</span>
<span className={classes.severityCount}>
{scanResults.vulnerabilities.critical || 0}
</span>
</div>
<div className={classes.severityContainer}>
<span className={classes.severityStatus}>
<Tooltip content="High">
<AngleDoubleUpIcon
title="High"
className={classes.highStatus}
/>
</Tooltip>
{!condensed ? 'High' : null}
</span>
<span className={classes.severityCount}>
{scanResults.vulnerabilities.high || 0}
</span>
</div>
<div className={classes.severityContainer}>
<span className={classes.severityStatus}>
<Tooltip content="Medium">
<EqualsIcon title="Medium" className={classes.mediumStatus} />
</Tooltip>
{!condensed ? 'Medium' : null}
</span>
<span className={classes.severityCount}>
{scanResults.vulnerabilities.medium || 0}
</span>
</div>
<div className={classes.severityContainer}>
<span className={classes.severityStatus}>
<Tooltip content="medium">
<AngleDoubleDownIcon
title="Low"
className={classes.lowStatus}
/>
</Tooltip>
{!condensed ? 'Low' : null}
</span>
<span className={classes.severityCount}>
{scanResults.vulnerabilities.low || 0}
</span>
</div>
</>
) : (
'-'
)}
</div>
);
};

export default PipelineRunVulnerabilities;
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ const ResourceBadge = ({
color,
abbr,
name,
suffix,
}: {
color: string;
abbr: string;
name: string;
suffix?: React.ReactNode;
}) => {
return (
<Split className="bs-tkn-pipeline-visualization__label">
Expand All @@ -23,6 +25,11 @@ const ResourceBadge = ({
<SplitItem>
<span>{name}</span>
</SplitItem>
{suffix ? (
<SplitItem style={{ marginLeft: 'var(--pf-v5-global--spacer--sm)' }}>
{suffix}
</SplitItem>
) : null}
</Split>
);
};
Expand Down
1 change: 1 addition & 0 deletions plugins/tekton/src/consts/tekton-const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const TEKTON_CI_ANNOTATION = 'janus-idp.io/tekton';
export const TEKTON_PIPELINE_TASK = 'tekton.dev/pipelineTask';
export const TEKTON_PIPELINE_RUN = 'tekton.dev/pipelineRun';
export const TEKTON_PIPELINE_TASKRUN = 'tekton.dev/taskRun';
export const TEKTON_SIGNED_ANNOTATION = 'chains.tekton.dev/signed';
4 changes: 4 additions & 0 deletions plugins/tekton/src/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.svg' {
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default content;
}
53 changes: 53 additions & 0 deletions plugins/tekton/src/hooks/usePipelineRunScanResults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { renderHook } from '@testing-library/react-hooks';

import { mockKubernetesPlrResponse } from '../__fixtures__/1-pipelinesData';
import { usePipelineRunScanResults } from './usePipelineRunScanResults';

jest.mock('@backstage/core-plugin-api', () => ({
...jest.requireActual('@backstage/core-plugin-api'),
useApi: jest.fn(),
}));

describe('usePipelineRunVulnerabilities', () => {
it('should return vulnerabilities when SCAN_OUTPUT is set', () => {
const { result } = renderHook(() =>
usePipelineRunScanResults(mockKubernetesPlrResponse.pipelineruns[1]),
);

expect(result.current.vulnerabilities?.critical).toEqual(13);
expect(result.current.vulnerabilities?.high).toEqual(29);
expect(result.current.vulnerabilities?.medium).toEqual(32);
expect(result.current.vulnerabilities?.low).toEqual(3);
});
it('should return vulnerabilities when the suffix SCAN_OUTPUT is set', () => {
const { result } = renderHook(() =>
usePipelineRunScanResults(mockKubernetesPlrResponse.pipelineruns[0]),
);

expect(result.current.vulnerabilities?.critical).toEqual(1);
expect(result.current.vulnerabilities?.high).toEqual(9);
expect(result.current.vulnerabilities?.medium).toEqual(20);
expect(result.current.vulnerabilities?.low).toEqual(1);
});
it('should accumulate all vulnerabilities', () => {
const { result } = renderHook(() => {
const results0 =
mockKubernetesPlrResponse.pipelineruns[0].status.pipelineResults?.[0];
const results1 =
mockKubernetesPlrResponse.pipelineruns[1].status.results?.[0];
const plr = {
...mockKubernetesPlrResponse.pipelineruns[1],
status: {
...mockKubernetesPlrResponse.pipelineruns[1].status,
results: results0 && results1 ? [results0, results1] : [],
},
};
return usePipelineRunScanResults(plr);
});

expect(result.current.vulnerabilities?.critical).toEqual(14);
expect(result.current.vulnerabilities?.high).toEqual(38);
expect(result.current.vulnerabilities?.medium).toEqual(52);
expect(result.current.vulnerabilities?.low).toEqual(4);
});
});
Loading

0 comments on commit effdef0

Please sign in to comment.