diff --git a/src/__tests__/Explore/Explore.test.js b/src/__tests__/Explore/Explore.test.js index 1e456db..6c626bc 100644 --- a/src/__tests__/Explore/Explore.test.js +++ b/src/__tests__/Explore/Explore.test.js @@ -37,7 +37,7 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: 'w', - IsSigned: false, + SignatureInfo: [], Licenses: '', Vendor: '', Labels: '', @@ -61,7 +61,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: false, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: false, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -85,7 +96,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -109,7 +131,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -133,7 +166,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -161,7 +205,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -185,7 +240,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -218,7 +284,7 @@ const filteredMockImageListWindows = () => { }; const filteredMockImageListSigned = () => { - const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.IsSigned); + const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.SignatureInfo?.length > 0); return { GlobalSearch: { Page: { TotalCount: 6, ItemCount: 6 }, @@ -266,7 +332,8 @@ describe('Explore component', () => { jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } }); render(); expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(1); - expect(await screen.findAllByTestId('verified-icon')).toHaveLength(6); + expect(await screen.findAllByTestId('untrusted-icon')).toHaveLength(2); + expect(await screen.findAllByTestId('verified-icon')).toHaveLength(10); }); it('renders vulnerability icons', async () => { diff --git a/src/__tests__/HomePage/Home.test.js b/src/__tests__/HomePage/Home.test.js index e0a3e32..0b17556 100644 --- a/src/__tests__/HomePage/Home.test.js +++ b/src/__tests__/HomePage/Home.test.js @@ -32,7 +32,7 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: 'w', - IsSigned: false, + SignatureInfo: [], Licenses: '', Vendor: '', Labels: '', @@ -49,7 +49,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -66,7 +77,18 @@ const mockImageList = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -91,7 +113,7 @@ const mockImageListRecent = { NewestImage: { Tag: 'latest', Description: 'w', - IsSigned: false, + SignatureInfo: [], Licenses: '', Vendor: '', Labels: '', @@ -108,7 +130,18 @@ const mockImageListRecent = { NewestImage: { Tag: 'latest', Description: '', - IsSigned: true, + SignatureInfo: [ + { + Tool: 'cosign', + IsTrusted: true, + Author: '' + }, + { + Tool: 'notation', + IsTrusted: true, + Author: '' + } + ], Licenses: '', Vendor: '', Labels: '', @@ -188,7 +221,7 @@ describe('Home component', () => { jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } }); render(); expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(3); - expect(await screen.findAllByTestId('verified-icon')).toHaveLength(4); + expect(await screen.findAllByTestId('verified-icon')).toHaveLength(8); }); it('renders vulnerability icons', async () => { diff --git a/src/components/Explore/Explore.jsx b/src/components/Explore/Explore.jsx index 27d663a..13c32e4 100644 --- a/src/components/Explore/Explore.jsx +++ b/src/components/Explore/Explore.jsx @@ -220,7 +220,6 @@ function Explore({ searchInputValue }) { version={item.latestVersion} description={item.description} downloads={item.downloads} - isSigned={item.isSigned} signatureInfo={item.signatureInfo} isBookmarked={item.isBookmarked} vendor={item.vendor} diff --git a/src/components/Home/Home.jsx b/src/components/Home/Home.jsx index 8330602..4d376f0 100644 --- a/src/components/Home/Home.jsx +++ b/src/components/Home/Home.jsx @@ -219,7 +219,6 @@ function Home() { version={item.latestVersion} description={item.description} downloads={item.downloads} - isSigned={item.isSigned} signatureInfo={item.signatureInfo} isBookmarked={item.isBookmarked} vendor={item.vendor} diff --git a/src/components/Repo/RepoDetails.jsx b/src/components/Repo/RepoDetails.jsx index f6d50f8..02166a8 100644 --- a/src/components/Repo/RepoDetails.jsx +++ b/src/components/Repo/RepoDetails.jsx @@ -243,6 +243,14 @@ function RepoDetails() { return lastDate; }; + const getSignatureChips = () => { + if (repoDetailData.signatureInfo?.length > 0) + return repoDetailData.signatureInfo?.map((si, index) => ( + + )); + return ; + }; + return ( <> {isLoading ? ( @@ -271,19 +279,23 @@ function RepoDetails() { - + {getSignatureChips()} {isAuthenticated() && ( - - {repoDetailData?.isBookmarked ? ( - - ) : ( - - )} - + + + {repoDetailData?.isBookmarked ? ( + + ) : ( + + )} + + )} diff --git a/src/components/Shared/PreviewCard.jsx b/src/components/Shared/PreviewCard.jsx index 919d91c..380f308 100644 --- a/src/components/Shared/PreviewCard.jsx +++ b/src/components/Shared/PreviewCard.jsx @@ -10,7 +10,7 @@ import repocube3 from '../../assets/repocube-3.png'; import repocube4 from '../../assets/repocube-4.png'; import { isEmpty } from 'lodash'; -import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; +import { VulnerabilityIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; // temporary utility to get image const randomIntFromInterval = (min, max) => { @@ -67,7 +67,7 @@ const useStyles = makeStyles(() => ({ function PreviewCard(props) { const classes = useStyles(); const navigate = useNavigate(); - const { name, isSigned, vulnerabilityData, logo } = props; + const { name, vulnerabilityData, logo } = props; const goToDetails = () => { navigate(`/image/${encodeURIComponent(name)}`); @@ -108,7 +108,6 @@ function PreviewCard(props) { - diff --git a/src/components/Shared/RepoCard.jsx b/src/components/Shared/RepoCard.jsx index f2003e4..d833581 100644 --- a/src/components/Shared/RepoCard.jsx +++ b/src/components/Shared/RepoCard.jsx @@ -28,6 +28,9 @@ import { import makeStyles from '@mui/styles/makeStyles'; import BookmarkIcon from '@mui/icons-material/Bookmark'; import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; +import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; +import { Markdown } from 'utilities/MarkdowntojsxWrapper'; +import filterConstants from 'utilities/filterConstants'; import { useTheme } from '@emotion/react'; // placeholder images @@ -36,9 +39,6 @@ import repocube2 from '../../assets/repocube-2.png'; import repocube3 from '../../assets/repocube-3.png'; import repocube4 from '../../assets/repocube-4.png'; -import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck'; -import { Markdown } from 'utilities/MarkdowntojsxWrapper'; - // temporary utility to get image const randomIntFromInterval = (min, max) => { return Math.floor(Math.random() * (max - min + 1) + min); @@ -183,7 +183,6 @@ function RepoCard(props) { platforms, description, downloads, - isSigned, signatureInfo, lastUpdated, version, @@ -260,6 +259,24 @@ function RepoCard(props) { ); }; + const getSignatureChips = () => { + const cosign = signatureInfo?.map((s) => s.tool).includes(filterConstants.signatureToolConstants.COSIGN) + ? signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN) + : null; + const notation = signatureInfo?.map((s) => s.tool).includes(filterConstants.signatureToolConstants.NOTATION) + ? signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION) + : null; + const sigArray = []; + if (cosign) sigArray.push(cosign); + if (notation) sigArray.push(notation); + if (sigArray.length === 0) return ; + return sigArray.map((sig, index) => ( +
+ +
+ )); + }; + return ( -
- -
+ {getSignatureChips()} diff --git a/src/components/Shared/SignatureTooltip.jsx b/src/components/Shared/SignatureTooltip.jsx index c4433b4..42b4996 100644 --- a/src/components/Shared/SignatureTooltip.jsx +++ b/src/components/Shared/SignatureTooltip.jsx @@ -1,19 +1,17 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Typography, Stack } from '@mui/material'; - import { isEmpty } from 'lodash'; +import { getStrongestSignature } from 'utilities/vulnerabilityAndSignatureCheck'; -function SignatureTooltip({ isSigned, signatureInfo }) { - const { tool, isTrusted, author } = !isEmpty(signatureInfo) - ? signatureInfo[0] - : { tool: 'Unknown', isTrusted: 'Unknown', author: 'Unknown' }; +function SignatureTooltip({ signatureInfo }) { + const strongestSignature = useMemo(() => getStrongestSignature(signatureInfo)); - return ( + return isEmpty(strongestSignature) ? ( + 'Not signed' + ) : ( - {isSigned ? 'Verified Signature' : 'Unverified Signature'} - Tool: {tool} - Trusted: {!isEmpty(isTrusted) ? isTrusted : 'Unknown'} - Author: {!isEmpty(author) ? author : 'Unknown'} + Tool: {strongestSignature?.tool || 'Unknown'} + Author: {strongestSignature?.author || 'Unknown'} ); } diff --git a/src/components/Tag/Tabs/DependsOn.jsx b/src/components/Tag/Tabs/DependsOn.jsx index 56b0ed5..1cfafb3 100644 --- a/src/components/Tag/Tabs/DependsOn.jsx +++ b/src/components/Tag/Tabs/DependsOn.jsx @@ -107,7 +107,7 @@ function DependsOn(props) { repoName={dependence.repoName} tag={dependence.tag} vendor={dependence.vendor} - isSigned={dependence.isSigned} + signatureInfo={dependence.signatureInfo} manifests={dependence.manifests} key={index} lastUpdated={dependence.lastUpdated} diff --git a/src/components/Tag/Tabs/IsDependentOn.jsx b/src/components/Tag/Tabs/IsDependentOn.jsx index 4afaf67..03ec2b3 100644 --- a/src/components/Tag/Tabs/IsDependentOn.jsx +++ b/src/components/Tag/Tabs/IsDependentOn.jsx @@ -107,7 +107,7 @@ function IsDependentOn(props) { repoName={dependence.repoName} tag={dependence.tag} vendor={dependence.vendor} - isSigned={dependence.isSigned} + signatureInfo={dependence.signatureInfo} manifests={dependence.manifests} key={index} lastUpdated={dependence.lastUpdated} diff --git a/src/components/Tag/TagDetails.jsx b/src/components/Tag/TagDetails.jsx index a9579bd..03f60ac 100644 --- a/src/components/Tag/TagDetails.jsx +++ b/src/components/Tag/TagDetails.jsx @@ -223,6 +223,14 @@ function TagDetails() { } }; + const getSignatureChips = () => { + if (imageDetailData.signatureInfo?.length > 0) + return imageDetailData.signatureInfo?.map((si, index) => ( + + )); + return ; + }; + return ( <> {isLoading ? ( @@ -260,10 +268,7 @@ function TagDetails() { vulnerabilitySeverity={imageDetailData.vulnerabiltySeverity} count={imageDetailData.vulnerabilityCount} /> - + {getSignatureChips()} diff --git a/src/utilities/filterConstants.js b/src/utilities/filterConstants.js index 5ae7878..3a5a3a7 100644 --- a/src/utilities/filterConstants.js +++ b/src/utilities/filterConstants.js @@ -60,6 +60,11 @@ const archFilters = [ } ]; -const filterConstants = { osFilters, imageFilters, archFilters }; +const signatureToolConstants = { + COSIGN: 'cosign', + NOTATION: 'notation' +}; + +const filterConstants = { osFilters, imageFilters, archFilters, signatureToolConstants }; export default filterConstants; diff --git a/src/utilities/objectModels.js b/src/utilities/objectModels.js index e823658..76f5457 100644 --- a/src/utilities/objectModels.js +++ b/src/utilities/objectModels.js @@ -101,12 +101,12 @@ const mapSignatureInfo = (signatureInfo) => { return signatureInfo ? { tool: signatureInfo.Tool, - isTrusted: signatureInfo.IsTrusted?.toString(), + isTrusted: signatureInfo.IsTrusted, author: signatureInfo.Author } : { tool: 'Unknown', - isTrusted: 'Unknown', + isTrusted: false, author: 'Unknown' }; }; diff --git a/src/utilities/vulnerabilityAndSignatureCheck.jsx b/src/utilities/vulnerabilityAndSignatureCheck.jsx index 5f67919..4d6d144 100644 --- a/src/utilities/vulnerabilityAndSignatureCheck.jsx +++ b/src/utilities/vulnerabilityAndSignatureCheck.jsx @@ -1,3 +1,4 @@ +import { isEmpty } from 'lodash'; import React from 'react'; import { NoneVulnerabilityIcon, @@ -17,9 +18,18 @@ import { UnknownVulnerabilityIcon, UnknownVulnerabilityChip, FailedScanIcon, - FailedScanChip + FailedScanChip, + NotTrustedSignatureIcon, + NotTrustedSignatureChip } from './vulnerabilityAndSignatureComponents'; +const getStrongestSignature = (signatureInfo) => { + if (isEmpty(signatureInfo)) return null; + const trusted = signatureInfo.find((si) => si.isTrusted); + if (!isEmpty(trusted)) return trusted; + return signatureInfo[0]; +}; + const VulnerabilityIconCheck = ({ vulnerabilitySeverity }) => { let result; let vulnerabilityStringTitle = ''; @@ -84,20 +94,24 @@ const VulnerabilityChipCheck = ({ vulnerabilitySeverity }) => { return result; }; -const SignatureIconCheck = ({ isSigned, signatureInfo }) => { - if (isSigned) { - return ; - } else { - return ; - } +const SignatureIconCheck = ({ signatureInfo }) => { + const strongestSignature = getStrongestSignature(signatureInfo); + if (strongestSignature === null) return ; + if (strongestSignature.isTrusted) return ; + return ; }; -const SignatureChipCheck = ({ isSigned }) => { - if (isSigned) { - return ; - } else { - return ; - } +const SignatureChipCheck = ({ signatureInfo }) => { + const strongestSignature = getStrongestSignature(signatureInfo); + if (strongestSignature === null) return ; + if (strongestSignature.isTrusted) return ; + return ; }; -export { VulnerabilityIconCheck, VulnerabilityChipCheck, SignatureIconCheck, SignatureChipCheck }; +export { + VulnerabilityIconCheck, + VulnerabilityChipCheck, + SignatureIconCheck, + SignatureChipCheck, + getStrongestSignature +}; diff --git a/src/utilities/vulnerabilityAndSignatureComponents.jsx b/src/utilities/vulnerabilityAndSignatureComponents.jsx index 370d608..8f8e48d 100644 --- a/src/utilities/vulnerabilityAndSignatureComponents.jsx +++ b/src/utilities/vulnerabilityAndSignatureComponents.jsx @@ -258,6 +258,28 @@ const UnverifiedSignatureIcon = ({ signatureInfo }) => { ); }; + +const NotTrustedSignatureIcon = ({ signatureInfo }) => { + return ( + } placement="top"> + + + ); +}; + const VerifiedSignatureIcon = ({ signatureInfo }) => { return ( } placement="top"> @@ -291,6 +313,21 @@ const UnverifiedSignatureChip = () => { /> ); }; + +const NotTrustedSignatureChip = () => { + return ( + { + return; + }} + deleteIcon={} + /> + ); +}; + const VerifiedSignatureChip = () => { return (