using packageurl-js to parse packages and groups from config
This commit is contained in:
+44
-15
@@ -1,6 +1,10 @@
|
|||||||
import {expect, jest, test} from '@jest/globals'
|
import {expect, jest, test} from '@jest/globals'
|
||||||
import {Change, Changes} from '../src/schemas'
|
import {Change, Changes} from '../src/schemas'
|
||||||
import {createTestChange} from './fixtures/create-test-change'
|
import {
|
||||||
|
createTestChange,
|
||||||
|
createTestGroupPURLs,
|
||||||
|
createTestPackagePURLs
|
||||||
|
} from './fixtures/create-test-change'
|
||||||
import {getDeniedChanges} from '../src/deny'
|
import {getDeniedChanges} from '../src/deny'
|
||||||
|
|
||||||
jest.mock('@actions/core')
|
jest.mock('@actions/core')
|
||||||
@@ -47,20 +51,24 @@ beforeEach(async () => {
|
|||||||
|
|
||||||
test('denies packages from the deny packages list', async () => {
|
test('denies packages from the deny packages list', async () => {
|
||||||
const changes: Changes = [npmChange, rubyChange]
|
const changes: Changes = [npmChange, rubyChange]
|
||||||
const deniedChanges = await getDeniedChanges(changes, [
|
const deniedPackages = createTestPackagePURLs([
|
||||||
'pkg:gem/[email protected]'
|
'pkg:gem/[email protected]'
|
||||||
])
|
])
|
||||||
|
const deniedChanges = await getDeniedChanges(changes, deniedPackages)
|
||||||
|
|
||||||
expect(deniedChanges[0]).toBe(rubyChange)
|
expect(deniedChanges[0]).toBe(rubyChange)
|
||||||
expect(deniedChanges.length).toEqual(1)
|
expect(deniedChanges.length).toEqual(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('denies packages only for the specified version from deny packages list', async () => {
|
test('denies packages only for the specified version from deny packages list', async () => {
|
||||||
const packageWithDifferentVersion = 'pkg:npm/[email protected]'
|
const deniedPackageWithDifferentVersion = createTestPackagePURLs([
|
||||||
const changes: Changes = [npmChange]
|
'pkg:npm/[email protected]'
|
||||||
const deniedChanges = await getDeniedChanges(changes, [
|
|
||||||
packageWithDifferentVersion
|
|
||||||
])
|
])
|
||||||
|
const changes: Changes = [npmChange]
|
||||||
|
const deniedChanges = await getDeniedChanges(
|
||||||
|
changes,
|
||||||
|
deniedPackageWithDifferentVersion
|
||||||
|
)
|
||||||
|
|
||||||
expect(deniedChanges.length).toEqual(0)
|
expect(deniedChanges.length).toEqual(0)
|
||||||
})
|
})
|
||||||
@@ -71,30 +79,51 @@ test('if no specified version from deny packages list, it will treat package as
|
|||||||
createTestChange({name: 'lodash', version: '4.5.6'}),
|
createTestChange({name: 'lodash', version: '4.5.6'}),
|
||||||
createTestChange({name: 'lodash', version: '7.8.9'})
|
createTestChange({name: 'lodash', version: '7.8.9'})
|
||||||
]
|
]
|
||||||
const denyAllLodashVersions = 'pkg:npm/lodash'
|
const denyAllLodashVersions = createTestPackagePURLs(['pkg:npm/lodash'])
|
||||||
const deniedChanges = await getDeniedChanges(changes, [denyAllLodashVersions])
|
const deniedChanges = await getDeniedChanges(changes, denyAllLodashVersions)
|
||||||
|
|
||||||
expect(deniedChanges.length).toEqual(3)
|
expect(deniedChanges.length).toEqual(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('denies packages from the deny group list', async () => {
|
test('denies packages from the deny group list', async () => {
|
||||||
const changes: Changes = [mvnChange, rubyChange]
|
const changes: Changes = [mvnChange, rubyChange]
|
||||||
const deniedChanges = await getDeniedChanges(
|
const deniedGroups = createTestGroupPURLs([
|
||||||
changes,
|
'pkg:maven/org.apache.logging.log4j/'
|
||||||
[],
|
])
|
||||||
['pkg:maven/org.apache.logging.log4j']
|
const deniedChanges = await getDeniedChanges(changes, [], deniedGroups)
|
||||||
)
|
|
||||||
|
|
||||||
expect(deniedChanges[0]).toBe(mvnChange)
|
expect(deniedChanges[0]).toBe(mvnChange)
|
||||||
expect(deniedChanges.length).toEqual(1)
|
expect(deniedChanges.length).toEqual(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('denies packages that match the deny group list exactly', async () => {
|
||||||
|
const changes: Changes = [
|
||||||
|
createTestChange({
|
||||||
|
package_url: 'pkg:npm/org.test.pass/[email protected]',
|
||||||
|
ecosystem: 'npm'
|
||||||
|
}),
|
||||||
|
createTestChange({
|
||||||
|
package_url: 'pkg:npm/org.test/[email protected]',
|
||||||
|
ecosystem: 'npm'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
const deniedGroups = createTestGroupPURLs(['pkg:npm/org.test/'])
|
||||||
|
const deniedChanges = await getDeniedChanges(changes, [], deniedGroups)
|
||||||
|
|
||||||
|
expect(deniedChanges.length).toEqual(1)
|
||||||
|
expect(deniedChanges[0]).toBe(changes[1])
|
||||||
|
})
|
||||||
|
|
||||||
test('allows packages not defined in the deny packages and groups list', async () => {
|
test('allows packages not defined in the deny packages and groups list', async () => {
|
||||||
const changes: Changes = [npmChange, pipChange]
|
const changes: Changes = [npmChange, pipChange]
|
||||||
|
const deniedPackages = createTestPackagePURLs([
|
||||||
|
'pkg:gem/[email protected]'
|
||||||
|
])
|
||||||
|
const deniedGroups = createTestGroupPURLs(['pkg:maven/group.not.in.changes/'])
|
||||||
const deniedChanges = await getDeniedChanges(
|
const deniedChanges = await getDeniedChanges(
|
||||||
changes,
|
changes,
|
||||||
['pkg:gem/[email protected]'],
|
deniedPackages,
|
||||||
['pkg:maven:org.apache.logging.not-in-list']
|
deniedGroups
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(deniedChanges.length).toEqual(0)
|
expect(deniedChanges.length).toEqual(0)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import {PackageURL} from 'packageurl-js'
|
||||||
import {Change} from '../../src/schemas'
|
import {Change} from '../../src/schemas'
|
||||||
import {createTestVulnerability} from './create-test-vulnerability'
|
import {createTestVulnerability} from './create-test-vulnerability'
|
||||||
|
import {parseGroupPURL} from '../../src/utils'
|
||||||
|
|
||||||
const defaultNpmChange: Change = {
|
const defaultNpmChange: Change = {
|
||||||
change_type: 'added',
|
change_type: 'added',
|
||||||
@@ -116,4 +118,18 @@ const createTestChange = (overwrites: Partial<Change> = {}): Change => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {createTestChange}
|
const createTestPackagePURLs = (list: string[]): PackageURL[] => {
|
||||||
|
return list.map(purl => {
|
||||||
|
return PackageURL.fromString(purl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTestGroupPURLs = (list: string[]): PackageURL[] => {
|
||||||
|
return list
|
||||||
|
.map(purl => {
|
||||||
|
return parseGroupPURL(purl)
|
||||||
|
})
|
||||||
|
.filter((purl): purl is PackageURL => purl !== undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {createTestChange, createTestPackagePURLs, createTestGroupPURLs}
|
||||||
|
|||||||
+20
-3
@@ -4,7 +4,7 @@ import YAML from 'yaml'
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
||||||
import {isSPDXValid, octokitClient} from './utils'
|
import {isSPDXValid, octokitClient, parseGroupPURL} from './utils'
|
||||||
import {PackageURL} from 'packageurl-js'
|
import {PackageURL} from 'packageurl-js'
|
||||||
|
|
||||||
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
||||||
@@ -33,8 +33,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
|||||||
const allow_dependencies_licenses = parseList(
|
const allow_dependencies_licenses = parseList(
|
||||||
getOptionalInput('allow-dependencies-licenses')
|
getOptionalInput('allow-dependencies-licenses')
|
||||||
)
|
)
|
||||||
const deny_packages = parseList(getOptionalInput('deny-packages'))
|
const deny_packages = getPackagePURLs(getOptionalInput('deny-packages'))
|
||||||
const deny_groups = parseList(getOptionalInput('deny-groups'))
|
const deny_groups = getGroupPURLs(getOptionalInput('deny-groups'))
|
||||||
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
|
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
|
||||||
const license_check = getOptionalBoolean('license-check')
|
const license_check = getOptionalBoolean('license-check')
|
||||||
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
||||||
@@ -107,6 +107,23 @@ function parseList(list: string | undefined): string[] | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPackagePURLs(list: string | undefined): PackageURL[] | undefined {
|
||||||
|
if (list) {
|
||||||
|
return list
|
||||||
|
.split(',')
|
||||||
|
.map(packageString => PackageURL.fromString(packageString.trim()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupPURLs(list: string | undefined): PackageURL[] | undefined {
|
||||||
|
if (list) {
|
||||||
|
return list
|
||||||
|
.split(',')
|
||||||
|
.map(packageString => parseGroupPURL(packageString.trim()))
|
||||||
|
.filter((purl): purl is PackageURL => purl !== undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateLicenses(
|
function validateLicenses(
|
||||||
key: 'allow-licenses' | 'deny-licenses',
|
key: 'allow-licenses' | 'deny-licenses',
|
||||||
licenses: string[] | undefined
|
licenses: string[] | undefined
|
||||||
|
|||||||
+11
-10
@@ -1,24 +1,22 @@
|
|||||||
import {Change} from './schemas'
|
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import {Change} from './schemas'
|
||||||
|
import {PackageURL} from 'packageurl-js'
|
||||||
|
|
||||||
export async function getDeniedChanges(
|
export async function getDeniedChanges(
|
||||||
changes: Change[],
|
changes: Change[],
|
||||||
deniedPackages: string[] = [],
|
deniedPackages: PackageURL[] = [],
|
||||||
deniedGroups: string[] = []
|
deniedGroups: PackageURL[] = []
|
||||||
): Promise<Change[]> {
|
): Promise<Change[]> {
|
||||||
const changesDenied: Change[] = []
|
const changesDenied: Change[] = []
|
||||||
|
|
||||||
let hasDeniedPackage = false
|
let hasDeniedPackage = false
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const [packageName, packageVersion] = change.package_url
|
const changedPackage = PackageURL.fromString(change.package_url)
|
||||||
.toLowerCase()
|
|
||||||
.split('@')
|
|
||||||
|
|
||||||
for (const denied of deniedPackages) {
|
for (const denied of deniedPackages) {
|
||||||
const [deniedName, deniedVersion] = denied.toLowerCase().split('@')
|
|
||||||
if (
|
if (
|
||||||
(!deniedVersion || packageVersion === deniedVersion) &&
|
(!denied.version || changedPackage.version === denied.version) &&
|
||||||
packageName === deniedName
|
changedPackage.name === denied.name
|
||||||
) {
|
) {
|
||||||
changesDenied.push(change)
|
changesDenied.push(change)
|
||||||
hasDeniedPackage = true
|
hasDeniedPackage = true
|
||||||
@@ -26,7 +24,10 @@ export async function getDeniedChanges(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const denied of deniedGroups) {
|
for (const denied of deniedGroups) {
|
||||||
if (packageName.startsWith(denied.toLowerCase())) {
|
if (
|
||||||
|
changedPackage.namespace &&
|
||||||
|
changedPackage.namespace === denied.namespace
|
||||||
|
) {
|
||||||
changesDenied.push(change)
|
changesDenied.push(change)
|
||||||
hasDeniedPackage = true
|
hasDeniedPackage = true
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -5,6 +5,15 @@ export const SCOPES = ['unknown', 'runtime', 'development'] as const
|
|||||||
|
|
||||||
export const SeveritySchema = z.enum(SEVERITIES).default('low')
|
export const SeveritySchema = z.enum(SEVERITIES).default('low')
|
||||||
|
|
||||||
|
export const PackageURLSchema = z.object({
|
||||||
|
type: z.string(),
|
||||||
|
namespace: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
version: z.string(),
|
||||||
|
qualifiers: z.record(z.string()).nullable(),
|
||||||
|
subpath: z.string().nullable()
|
||||||
|
})
|
||||||
|
|
||||||
export const ChangeSchema = z.object({
|
export const ChangeSchema = z.object({
|
||||||
change_type: z.enum(['added', 'removed']),
|
change_type: z.enum(['added', 'removed']),
|
||||||
manifest: z.string(),
|
manifest: z.string(),
|
||||||
@@ -42,8 +51,8 @@ export const ConfigurationOptionsSchema = z
|
|||||||
deny_licenses: z.array(z.string()).optional(),
|
deny_licenses: z.array(z.string()).optional(),
|
||||||
allow_dependencies_licenses: z.array(z.string()).optional(),
|
allow_dependencies_licenses: z.array(z.string()).optional(),
|
||||||
allow_ghsas: z.array(z.string()).default([]),
|
allow_ghsas: z.array(z.string()).default([]),
|
||||||
deny_packages: z.array(z.string()).default([]),
|
deny_packages: z.array(PackageURLSchema).default([]),
|
||||||
deny_groups: z.array(z.string()).default([]),
|
deny_groups: z.array(PackageURLSchema).default([]),
|
||||||
license_check: z.boolean().default(true),
|
license_check: z.boolean().default(true),
|
||||||
vulnerability_check: z.boolean().default(true),
|
vulnerability_check: z.boolean().default(true),
|
||||||
config_file: z.string().optional(),
|
config_file: z.string().optional(),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {Octokit} from 'octokit'
|
import {Octokit} from 'octokit'
|
||||||
import spdxParse from 'spdx-expression-parse'
|
import spdxParse from 'spdx-expression-parse'
|
||||||
|
import {PackageURL} from 'packageurl-js'
|
||||||
import {Changes} from './schemas'
|
import {Changes} from './schemas'
|
||||||
|
|
||||||
export function groupDependenciesByManifest(
|
export function groupDependenciesByManifest(
|
||||||
@@ -68,3 +69,21 @@ export function octokitClient(token = 'repo-token', required = true): Octokit {
|
|||||||
|
|
||||||
return new Octokit(opts)
|
return new Octokit(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parseGroupPURL = (purlString: string): PackageURL | undefined => {
|
||||||
|
try {
|
||||||
|
return PackageURL.fromString(purlString)
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
(error as Error).message ===
|
||||||
|
`purl is missing the required "name" component.`
|
||||||
|
) {
|
||||||
|
//package-url-js does not support empty names, so will manually override it for deny-groups
|
||||||
|
//https://github.com/package-url/packageurl-js/blob/master/src/package-url.js#L216
|
||||||
|
const purl = PackageURL.fromString(`${purlString}TEMP_NAME`)
|
||||||
|
purl.name = ''
|
||||||
|
return purl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user