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
$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).