Gitlab security jobs in Merge Request pipelines
Gitlab provides an extensive set of built-in security templates that we can add to our CI/CD pipelines in order to perform defensive checks on our code. As one of the pipelines I use started taking too long to complete (due to the number of jobs defined in it), I started refactoring it so that all security jobs would run in a separate Merge Request pipeline, while my “regular” jobs would keep running in the usual branch pipeline.
While doing so, I encountered a problem: security jobs simply wouldn’t run in the merge_request
pipeline (the pipeline wouldn’t show any job and fail). Digging into the templates’ code, I figured out that this is because of their rules
definition and that there is a not-yet-implemented improvement in Gitlab’s issue tracker to address this limitation.
Knowing that, it’s easy to change the jobs’ behaviour to fit our needs by overriding their rules
definition, as in the following YAML snippet. (note: I’m only including a couple of security templates, for demonstration purposes).
# .security.gitlab-ci.yaml
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
stages:
- test
gosec-sast:
artifacts:
reports:
sast: gl-sast-report.json
paths: [gl-sast-report.json]
rules:
- if: $CI_MERGE_REQUEST_IID
when: always
license_scanning:
rules:
- if: $CI_MERGE_REQUEST_IID
when: always
secret_detection:
rules:
- if: $CI_MERGE_REQUEST_IID
when: always
The $CI_MERGE_REQUEST_IID
is a built-in variable that is only available in merge requests, so the rules read as “only run this job when we’re in a merge request”; we unfortunately have to redefine every single job that we want to use; we save this smaller pipeline to a file named .security.gitlab-ci.yaml
(the actual name is of course up to you).
As a side note, when overriding job elements, Gitlab behaves in different ways according to the element type:
- when we override an array (such as
rules
), the whole array will be replaced, thus removing any previously-defined elements in the final job definition; - when we override a map (such as
variables
), its elements will be merged with any existing ones.
Now we only need to instruct the branch pipeline to trigger the security pipeline when we’re in a merge request (and whenever we’re in the main
branch, to be thorough in our checks), which we can do as follows:
# .gitlab-ci.yaml
stages:
- security
# - other stages as needed by your pipeline ...
variables:
SECURE_LOG_LEVEL: "info"
security:
stage: security
trigger:
include: .security.gitlab-ci.yaml
strategy: depend
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
when: always
- if: $CI_COMMIT_REF_NAME == 'main'
when: always
# other jobs ...
From now on, whenever we commit to a branch that belongs to a merge request or to the main
branch, we’ll see two pipelines being triggered at the same time.
Important note: in order to ensure that code can only be merged to main
when all pipelines (including the security pipeline) succeed, we have to remember to enable the Pipelines must succeed
option in the repository settings (General -> Merge requests -> Merge checks
, at the time of writing).