Add Dependency Firewall policy enforcement feature
This commit implements phase 1 of the Dependency Firewall feature, which allows security policies to block or warn about package dependencies based on licenses and vulnerabilities.
Key additions:
Note: Remaining lint warnings are non-critical style suggestions that don't affect functionality and are consistent with existing codebase patterns.
| Before | After |
|---|---|
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #593975
Hannah Baker (7523158c) at 20 Mar 14:29
gemlock file update via bundle install
... and 8 more commits
I'm sorry I don't have scope for reviewing this MR @syarynovsky is suggested in the reviewer roulette, I hope it's ok for me to pass this on
This MR temporarily quarantines a few specs while we investigate the root cause of the failures.
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #35704
Closing this spike as the approach has been validated.
Tested the auditing behaviour against an NPM package with a warning policy in place and confirmed the expected outcome (screenshot attached above). Maven-specific testing was not performed as a warning policy for Maven packages was not available at the time, however the approach is sufficiently proven out to proceed with implementation. All outstanding questions from this spike are resolved.
Hannah Baker (6facdf27) at 19 Mar 19:52
Return multi license array from db instead of selecting .first, cle...
Before the Dependency Firewall can decide whether to block or warn on a package, it needs to know what licenses that package is distributed under. This MR wires up the license lookup so that when a firewall check is triggered, we go and fetch that information.
There are two new services:
FetchPackageLicensesService
This service takes a package's name, purl_type, and version and looks up its licenses in PMDB, backed by the pm_packages and pm_licenses tables. It uses the existing Gitlab::LicenseScanning::PackageLicenses infrastructure that the rest of the platform already relies on for license scanning.
If the package isn't in the PMDB, or there's no data for that specific version, it returns an empty array rather than an error. That's intentional β missing license data means we can't make a policy decision, not that something went wrong.
License filtering follows the same pattern as Sbom::Ingestion::LicensesFetcher: entries are excluded if the spdx_identifier is blank (nil or empty) or matches the UNKNOWN_LICENSE sentinel that PackageLicenses uses when no data is available. Results are returned as plain Ruby hashes rather than Hashie::Mash objects, so callers get predictable symbol-keyed data regardless of what the underlying infrastructure returns.
Returned licenses look like:
[{ spdx_identifier: "Apache-2.0", name: "Apache License 2.0", url: "https://spdx.org/licenses/Apache-2.0.html" }]
The service takes component data and talks to the PMDB.
EnforcementService (extended)
The enforcement service now adds more logic related to validating PURLs:
PURL_REGEXP checks that the incoming purl is well-formed per the purl-spec. The type character set ([a-zA-Z0-9.\-]) matches the ECMA-427 spec exactly.purl_type, purl_name, and purl_version extract the component parts needed to query the PMDB. Percent-encoding in the name (e.g. %40 for @ in scoped npm packages) is handled correctly.FetchPackageLicensesService and propagates any error back to the caller.Currently the service returns SUCCESS_ALLOWED and does no actual logic with the response from FetchPackageLicensesService β policy evaluation comes in a follow-up issue #593844. The point of this MR is to get the license data flowing through the right path, with the right shape, ready for that evaluation step.
Both services have full unit test coverage:
UNKNOWN_LICENSE sentinel filtered out, blank and nil spdx_identifier filtered outSUCCESS_ALLOWED
| Before | After |
|---|---|
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #593843
Hannah Baker (9519e34f) at 19 Mar 15:40
Add foundational license lookup for a given purl
Hannah Baker (44ceaa59) at 19 Mar 12:45
vibe coding response for warning
@mikeeddington please could you review this issue before I tag the Workhorse team? (I'm also not sure if the labelling is accurate)
LGTM
Thanks for the detailed explanation, really helpful! I agree that failing early in initialize makes sense here
The grouppipeline security group is working towards providing users with SLSA Level 3 Provenance Attestations. As a simplified TL;DR, in the context of GitLab, a provenance statement is a JSON document that correlates the SHA-256 sum of an artifact with the build information. A worker then performs a digital signature, called a provenance attestation, stored as a βSigstore Bundleβ blob. This is a highly sought-after feature, particularly for our GitLab Ultimate customers.
This feature is currently behind a FF, and will be opt-in once the FF is rolled out.
Currently, the SLSA L3 code generates JWT tokens in line with the documentation in Use Sigstore for keyless signing and verification | GitLab Docs. Specifically, we add an id_token attribute to the CI job that we then perform attestation for with a hardcoded variable name (SIGSTORE_ID_TOKEN), and then retrieve it from the backend job.
In Bug: token expiry prevents successful retries after 8 attempts (#588513), we describe why this approach limits our ability to retry: after 1 hour, the token expires and subsequent retries fail because of this.
In this merge request, I modify our approach to leverage the mechanism that is used within app/models/ci/build.rb to process the id_token variable, source is available here. These tokens are identical, as demonstrated in the testing section of this MR.
slsa_provenance_statement -- Roll out feature flag to publish SLSA provenance statements (#547866)I've confirmed these two approaches produce identical JWTs. More information in ADR 006: Enable the creation of SLSA Level 3 Attestations for OCI images (!17936).
I've stored a token generated using the YML id_token directive in /tmp/a and the one generated by the code above in /tmp/b.
~/code/gdk/gitlab % cat /tmp/a | awk -F '.' '{print $2}' | base64 -d
{"project_id":"19","project_path":"root/control-plane-container","namespace_id":"1","namespace_path":"root","user_id":"1","user_login":"root","user_email":"[email protected]","user_access_level":"owner","job_project_id":"19","job_project_path":"root/control-plane-container","job_namespace_id":"1","job_namespace_path":"root","pipeline_id":"626","pipeline_source":"push","job_id":"480","ref":"main","ref_type":"branch","ref_path":"refs/heads/main","ref_protected":"true","runner_id":32,"runner_environment":"self-hosted","sha":"2c9feace1082fae1e6c5aa89998a24d2b94f18b2","project_visibility":"public","ci_config_ref_uri":"gdk.test:3000/root/control-plane-container//.gitlab-ci.yml@refs/heads/main","ci_config_sha":"2c9feace1082fae1e6c5aa89998a24d2b94f18b2","jti":"c50a4360-b9b2-41b2-9322-604b60a655e4","iat":1770156518,"nbf":1770156513,"exp":1770160118,"iss":"http://gdk.test:3000","sub":"project_path:root/control-plane-container:ref_type:branch:ref:main","aud":"sigstore"}% ~/code/gdk/gitlab % cat /tmp/b | awk -F '.' '{print $2}' | base64 -d
{"project_id":"19","project_path":"root/control-plane-container","namespace_id":"1","namespace_path":"root","user_id":"1","user_login":"root","user_email":"[email protected]","user_access_level":"owner","job_project_id":"19","job_project_path":"root/control-plane-container","job_namespace_id":"1","job_namespace_path":"root","pipeline_id":"626","pipeline_source":"push","job_id":"480","ref":"main","ref_type":"branch","ref_path":"refs/heads/main","ref_protected":"true","runner_id":32,"runner_environment":"self-hosted","sha":"2c9feace1082fae1e6c5aa89998a24d2b94f18b2","project_visibility":"public","ci_config_ref_uri":"gdk.test:3000/root/control-plane-container//.gitlab-ci.yml@refs/heads/main","ci_config_sha":"2c9feace1082fae1e6c5aa89998a24d2b94f18b2","jti":"9df52247-63bd-4236-a200-025daf5b950f","iat":1770157557,"nbf":1770157552,"exp":1770161157,"iss":"http://gdk.test:3000","sub":"project_path:root/control-plane-container:ref_type:branch:ref:main","aud":"sigstore"}%
~/code/gdk/gitlab % cat /tmp/b | awk -F '.' '{print $2}' | base64 -d | jq > /tmp/b.json
~/code/gdk/gitlab % cat /tmp/a | awk -F '.' '{print $2}' | base64 -d | jq > /tmp/a.json
~/code/gdk/gitlab % diff /tmp/{a,b}.json
27,30c27,30
< "jti": "c50a4360-b9b2-41b2-9322-604b60a655e4",
< "iat": 1770156518,
< "nbf": 1770156513,
< "exp": 1770160118,
---
> "jti": "9df52247-63bd-4236-a200-025daf5b950f",
> "iat": 1770157557,
> "nbf": 1770157552,
> "exp": 1770161157,
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #588513