Add LicenseExpressionChecker with feature flag and denylist AND operator
What does this MR do and why?
Introduces Security::MergeRequestApprovalPolicies::LicenseExpressionChecker behind the license_expression_checker wip feature flag (disabled by default). The checker handles the denylist path for SPDX compound license expressions, starting with AND expressions and plain single-identifier licenses.
Gitlab::SPDX::ExpressionParser (copied from !235888 (merged)) produces a Node tree from an SPDX expression string. The checker recursively walks the tree so that multi-term AND expressions (e.g. MIT AND Apache-2.0 AND GPL-3.0-only) are fully evaluated — denying any single component is a violation.
SPDX IDs in expression nodes are resolved to display names via Gitlab::SPDX::Catalogue before comparison, matching the format that policies store (e.g. MIT → MIT License). The catalogue is already cached for 7 days so there is no performance concern.
UpdateLicenseApprovalsService branches between DeniedLicensesChecker and LicenseExpressionChecker based on the flag. DeniedLicensesChecker continues to handle all traffic until the flag is enabled.
How to set up and validate locally
- Enable the
license_expression_checkerfeature flag on the rails console
Feature.enable(:license_expression_checker)- Create a new project
- Go to Secure > Policies
- Click in New policy
- Select Merge request approval policy
- Create a policy to block MIT License:
approval_policy:
- name: block MIT
description: ''
enabled: true
enforcement_type: warn
rules:
- type: license_finding
match_on_inclusion_license: true
licenses:
denied:
- name: MIT License
license_states:
- newly_detected
branch_type: protected
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- developer
- type: send_bot_message
enabled: true- Add an empty
Gemfile.lockfile - Add a file called
gl-sbom-gem-bundler.cdx.jsonwith the content to report the license expressionMIT AND Apache-2.0
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:a15e529c-2113-4a11-a694-6bc3ea4e2b53",
"version": 1,
"metadata": {
"timestamp": "2022-02-23T08:02:39Z",
"tools": [
{
"vendor": "GitLab",
"name": "Gemnasium",
"version": "2.34.0"
}
],
"authors": [
{
"name": "GitLab",
"email": "support@gitlab.com"
}
],
"properties": [
{
"name": "gitlab:dependency_scanning:input_file:path",
"value": "Gemfile.lock"
},
{
"name": "gitlab:dependency_scanning:package_manager:name",
"value": "bundler"
},
{
"name": "gitlab:meta:schema_version",
"value": "1"
}
]
},
"components": [
{
"name": "sidekiq",
"version": "4.2.10",
"purl": "pkg:gem/sidekiq@4.2.10",
"type": "library",
"bom-ref": "pkg:gem/sidekiq@4.2.10",
"licenses": [
{
"license": {
"name": "MIT AND Apache-2.0"
}
}
]
}
]
}- Create a new MR adding a
.gitlab-ci.ymlwith the content
include:
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
gemnasium-dependency_scanning:
stage: test
script: 'pwd'
artifacts:
reports:
cyclonedx: gl-sbom-gem-bundler.cdx.json- Run a pipeline
- Verify the MR is blocked and required approval for the policy
References
- Closes #600032 (closed)
- Parser MR: !235888 (merged)
- POC MR: !235265 (closed)
- Epic: gitlab-org#21990