Security Policies, Scan Execution, Pipeline Execution, Approval Policies, and Compliance Controls: Guided Hands-On Workflow and Core Operations
Author and evaluate realistic security-policy fixtures on a Free-compatible path, then map the same evidence to optional disposable Ultimate policy enforcement without touching real governance.
Learning objectives
- Create a disposable policy-fixture repository with realistic scan and pipeline execution policy YAML.
- Predict linking, inheritance, and policy scope before evaluating any fixture.
- Simulate one policy violation and capture a structured denial/required-action record.
- Map fixture evidence to optional Ultimate Policies, pipeline, MR, and audit surfaces.
- Perform a challenge that selects the correct GitLab control rather than copying a sequence.
1. Disposable scenario and preflight
Create a local repository named gitlab-ch27-policy-lab.
The mandatory path needs only Git, Python 3, and a text editor.
Nothing is linked to a real GitLab group, no real security policy is
changed, and no paid feature is required. If you already have an
Ultimate/trial sandbox, you may optionally reproduce a subset in a
disposable group after verifying Owner/custom-role permissions.
mkdir gitlab-ch27-policy-lab && cd gitlab-ch27-policy-lab
git init
git switch -c main
mkdir -p .gitlab/security-policies policy-ci evidence
printf 'training-policy-lab
' > README.md
2. Write the target inventory before the policy
Policy authors should name the intended objects before writing YAML.
Create evidence/scope.json:
{
"linked_unit": "training/platform",
"projects": [
{"id": 101, "path": "training/platform/payments-api", "framework": "regulated"},
{"id": 102, "path": "training/platform/docs-site", "framework": null},
{"id": 103, "path": "training/platform/legacy-worker", "framework": "exception-review"}
],
"policy_owner": "security-team",
"application_owners": ["payments-team", "docs-team", "legacy-team"]
}
Prediction 1: if a security policy project were linked at
training/platform, all descendants would be candidates
through inheritance. A policy that includes only project ID 101
should affect payments-api, not the other two.
3. Author a scan execution policy fixture
The following is a realistic policy shape for enforcing Secret
Detection on main for one synthetic project. It is
stored as code but is not applied on the Free path.
---
scan_execution_policy:
- name: Enforce default-branch secret detection
description: Training fixture - no live enforcement on Free
enabled: true
rules:
- type: pipeline
branches:
- main
actions:
- scan: secret_detection
policy_scope:
projects:
including:
- id: 101
Scan execution policies use GitLab security analyzers. They are convenient for standardized scans but intentionally less customizable than pipeline execution policies. Never put credentials in policy variables; policy YAML is plaintext Git content.
4. Author a pipeline execution policy fixture
Pipeline execution policies are better for a custom required job.
The policy below injects a version-pinned synthetic CI file and uses
the current default-safe job-name collision behavior
suffix: on_conflict.
---
pipeline_execution_policy:
- name: Enforce preflight compliance job
description: Training fixture - external CI file is synthetic
enabled: true
pipeline_config_strategy: inject_policy
content:
include:
- project: training/security-policy-ci
file: policy-ci.yml
ref: v1.0.0
suffix: on_conflict
policy_scope:
projects:
including:
- id: 101
- id: 102
policy-preflight:
stage: .pipeline-policy-pre
script:
- echo "synthetic compliance check"
- test -f policy-required.txt
Prediction 2: both project IDs 101 and 102 would receive
policy-preflight. Project 103 would not. A user who
triggers a live pipeline must be able to read the policy CI
configuration or the pipeline can fail to start; current GitLab can
grant linked projects read access to the referenced policy
configuration without exposing the full project.
5. Inspect the intended merged control before execution
On Free, inspect the fixture deterministically:
git add .
git commit -m "Add Chapter 27 policy fixtures"
git rev-parse HEAD
git show --stat --oneline HEAD
grep -nE 'scan_execution_policy|pipeline_execution_policy|policy_scope|project:|file:|ref:' .gitlab/security-policies/policy.yml
# If split across files, inspect each exact path instead of relying on a broad directory dump.
For the lab, combine the two top-level policy sections into the
single canonical
.gitlab/security-policies/policy.yml file, then keep
policy-ci.yml under policy-ci/. This
preserves the same file boundary a real security policy project
uses. It also makes separation of duties visible: policy owners
change the central policy file; application owners do not. On an
Ultimate sandbox, use the policy editor/YAML validation before
merging and record the generated merge request SHA.
6. Simulate one policy violation and preserve the exact reason
The synthetic preflight requires policy-required.txt.
Evaluate it without creating that file:
set -eu
if test -f policy-required.txt; then
printf '%s
' 'policy-preflight: PASS'
else
printf '%s
' 'policy-preflight: BLOCK - policy-required.txt missing' >&2
exit 27
fi
Record a structured evaluation object instead of replacing the cause with “policy failed”:
{
"policy_commit": "fixture-sha-27a",
"target_project_id": 101,
"target_ref": "main",
"pipeline_id": 2701,
"result": "blocked",
"reason": "policy-preflight failed: policy-required.txt missing",
"required_action": "add the required marker in this disposable project or approve a documented training exception"
}
On an Ultimate live pipeline, the equivalent evidence is the policy-originated job, its reserved stage, exact log, target commit SHA, and policy commit. For approval policies, preserve the MR policy-violation/required-approval message and scanner-report evidence.
7. Apply the least-destructive repair
Because this is a disposable functional requirement, the correct repair is to satisfy it in the target project, not disable the central policy.
printf 'chapter-27-training
' > policy-required.txt
git add policy-required.txt
git commit -m "Satisfy synthetic policy preflight"
test -f policy-required.txt && echo 'policy-preflight: PASS'
If a legitimate project cannot meet a real requirement, the next step is not an undocumented bypass. Open an exception record with owner, rationale, affected scope, expiry/review date, compensating control, and evidence. GitLab bypass mechanisms should be used only when their semantics match the approved exception.
8. Optional Ultimate live mapping
Only in a disposable Ultimate/trial group:
- Inspect Secure → Policies and current security policy project.
- Create a disabled/test policy or narrow it to one disposable project first.
-
Use Configure with a merge request; review the
generated
policy.ymldiff. - Merge and record the policy commit SHA.
- Trigger one tiny target pipeline/MR and verify the policy-originated job or approval requirement.
- Inspect audit events where available; do not dump access tokens.
- Unlink/delete only after capturing cleanup evidence and confirming no other disposable project depends on the policy.
9. Control-selection challenge
Choose the smallest correct control for each requirement before revealing the answers:
| Requirement | Your choice |
|---|---|
| Run GitLab Secret Detection on every default-branch pipeline | ? |
| Run an organization-specific artifact-signature verifier before project jobs | ? |
| Require two approvals only when completed security scan evidence violates policy | ? |
| Run a compliance script weekly even without commits | ? |
Reveal recommended controls
Use scan execution policy; pipeline execution policy; merge request approval policy; scheduled pipeline execution policy, respectively. The choice follows the enforcement object and evaluation timing—not the UI menu name.
10. Cleanup and verify
git status --short
git log --oneline --decorate -3
# Keep only sanitized evidence if needed, then remove the local disposable repository from its parent directory.
# If you used an Ultimate sandbox, verify policy unlink/deletion through UI/API before deleting target projects.
Knowledge check
Why is the policy fixture pinned to ref v1.0.0 instead of main?
A mutable branch could change enforced CI behavior without a deliberate consumer policy change; a release tag or immutable SHA improves provenance and change control.
A target pipeline cannot read the policy CI file. Is that a scanner finding?
No. It is a configuration/authorization failure in the pipeline execution policy content trust boundary.
The preflight job fails. Should the first repair be disabling the central policy?
No. Preserve the cause, decide whether the target is genuinely noncompliant or legitimately exceptional, then fix the target or process an explicit governed exception.
Which policy type creates independent scheduled pipelines with policy CI only?
Scheduled pipeline execution policy.
What must be verified after unlinking a disposable security policy project?
That the target scope no longer reports the link/enforcement and that no other disposable projects still depend on it.
Summary and next step
You now have a causal workflow: inventory scope → author policy intent → predict enforcement → preserve exact violation → least-destructive repair → verify. Lesson 3 turns those mechanics into an architecture and governance decision framework.
Primary sources and version notes
These lessons were finalized against current official GitLab documentation on 2026-08-22. Re-check tier, feature status, policy schema, permission, audit-event, and Self-Managed-version behavior before applying the same design later.
- GitLab 19.3 release
- Security policies
- Policy enforcement
- Security policy projects
- Scan execution policies
- Pipeline execution policies
- Scheduled pipeline execution policies
- Merge request approval policies
- Compliance frameworks
- Compliance and security policy groups
- Compliance and policy settings API
- Audit event types
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.