Add classification reason for the license_types unknown fallback

Backend half of a two-MR follow-up. Frontend half is stacked on top of this one.

What does this MR do and why?

SCA::LicenseCompliance can classify a compound SPDX expression as allowed or denied through the license_types unknown fallback introduced in !257138 (merged), but the resulting SCA::LicensePolicy carries no signal that this happened. A reviewer sees the real detected expression (for example MIT AND Apache-2.0) labeled Allowed with no indication that neither operand was individually vetted, since it was treated as unknown and happens to be allow-listed under that name.

SCA::LicensePolicy gains a classification_reason attribute, nil by default. allowed_license_expression? now returns [allowed, reason] instead of a bare boolean, setting the reason to :license_types_unknown_fallback only on the branch that actually takes the fallback. Every other path (direct matches, the licenses property's per-operand evaluation, the plus-suffix path, the flag disabled) keeps a nil reason.

Security::LicensePolicyEntity exposes the reason inside the existing classification hash, so both consumers of this data (the merge request widget and the pipeline Licenses tab) get it without any further change, since both already go through this entity.

This MR is backend-only. The widget does not read or render the new field yet; that is the stacked frontend follow-up.

How to set up and validate locally

This MR has no independent user-facing behavior; the new field is inert until the frontend follow-up reads it. Validate by inspecting the API response directly.

Preconditions: GitLab Ultimate. Merge request approval policies are an Ultimate-tier feature.

  1. Enable the feature flag on the rails console:

    Feature.enable(:license_types_unknown_expression_fallback)
  2. Create a project.

  3. Go to Secure > Policies.

  4. Select New policy.

  5. Select Merge request approval policy.

  6. Switch to .yaml mode.

  7. Paste the following policy:

    approval_policy:
      - name: Approved licenses
        enabled: true
        rules:
          - type: license_finding
            match_on_inclusion_license: false
            license_states:
              - newly_detected
            branch_type: protected
            license_types:
              - unknown
        actions:
          - type: require_approval
            approvals_required: 1
            role_approvers:
              - developer
          - type: send_bot_message
            enabled: true
  8. Merge the merge request this creates, to activate the policy.

  9. Add an empty Gemfile.lock file to the project.

  10. Add a file called gl-sbom-gem-bundler.cdx.json reporting the license expression MIT 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"
                        }
                    }
                ]
            }
        ]
    }
  11. Create a new merge request adding a .gitlab-ci.yml file with 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
  12. Wait for that merge request's pipeline to finish.

  13. Open that merge request's /-/reports/license-compliance API response (or inspect it via Reports > License compliance's network request in the browser).

  14. Verify the classification object for the MIT AND Apache-2.0 dependency now includes "reason": "license_types_unknown_fallback", alongside the existing id, name, and approval_status keys.

  15. Verify a dependency resolved by a direct policy match (not a compound expression) has "reason": null.

References

Edited by Marcos Rocha

Merge request reports

Loading
Loading