ci: gate the Duo lane on GITLAB_DUO_TOKEN presence
What this does
The nine GitLab Duo dependent jobs in this project's own pipeline currently run whenever the build and test lane runs, and they fail on the known Duo entitlement gap. That red carries no information: it reports the same missing entitlement on every ref, on every merge request, every time.
This gives the Duo suite a configured-off state. A new
.duo_gated_pipeline_rules anchor repeats the six conditions of the existing
.full_pipeline_rules anchor, with $GITLAB_DUO_TOKEN added as a conjunct to
each one. The Duo dependent jobs use that anchor instead of the ungated one.
Semantics
- Variable absent: the Duo suite is configured off. None of the gated jobs is created, and the pipeline does not report them at all.
- Variable present: every gated job is created, runs, and must pass. The gate decides whether the lane exists, not whether it succeeds.
Presence is not entitlement. A token that authenticates and is then refused by
the Duo Agent Platform still fails loudly: stig::verify-creds keeps no
allow_failure and no in-script fallback. Nothing about the failure path
changes.
Jobs gated
| Job | How it was combined |
|---|---|
stig::verify-creds |
six .full_pipeline_rules rows, each with && $GITLAB_DUO_TOKEN |
test_ai_connectivity |
same |
test_with_demo_app |
same |
test_gkg_duo_mcp |
same |
test_batch_1, test_batch_2, test_batch_3 |
same, inherited through .test_batch_template |
test_parallel_with_limit |
same |
test_aggregator |
same, gated by dependency closure rather than by calling Duo |
Every gated row is its previous row plus one conjunct. A conjunct can only
remove matches, so no gated row can broaden. No job's tag, web, dev merge
request, or changes: condition is rewritten.
test_aggregator is included even though it never calls Duo itself. Its only
inputs are the batch jobs' artifacts, and a needs: naming a job that rules
excluded from the pipeline is a pipeline creation error rather than a skip.
Leaving it ungated would have made every token-absent pipeline fail to start.
Deliberately not gated
duo_cli_canary. The script already checks the variable itself and skips only its live assertions. Its offline assertions (the pinned version, theduo run --helpgolden snapshot, and the--output-format jsoncheck) catch upstream command line interface drift with no token, and a rules gate would delete that coverage in exactly the state where the rest of the Duo suite is absent. Its log also states whether the variable was present, which is what tells a reader of a green pipeline whether the Duo suite passed or was never configured.test_duo_parser,test_rate_limiter,test_gkg_install,test_pipeline_generator. These have Duo in their names or subject matter but make no Duo call, so they keep running unconditionally.templates/stig-compliance-analysis.yml. Untouched. The consumer facingstig::verify-credsthere must keep failing loudly for a consumer who supplies a bad credential. This change gates only this project's own pipeline.
Why a presence gate is the right mechanism
GITLAB_DUO_TOKEN is currently an unprotected variable scoped to *, so it is
defined on every ref. The replacement credential is a protected personal access
token. master is this project's only protected branch, and a protected
variable reaches a merge request pipeline only when both the source and the
target branch are protected. No merge request pipeline in this project can
therefore ever receive it. Absent variable is the steady state for every merge
request, and this gate is what turns that into a skipped suite instead of a red
one.
This supersedes the decision recorded in .gitlab-ci.yml and the changelog that
a rules gate could not fix the red. That reasoning was correct about the YAML
and still is: while the variable stays unprotected and defined on every ref,
this gate is inert. What changes is the credential plan around it, not the
conclusion about the YAML.
The gate is also not interchangeable with simply deleting the variable.
verify_duo_creds.sh hard-fails on an absent token by design, because a
consumer who forgot to set it must be told. Deleting the variable without this
gate would only change the red's message from an entitlement rejection to a
missing variable. Skipping the job is the only thing that makes absence quiet.
Verification
glab ci lint passes. Beyond that:
- Row by row structural check. Each of the nine jobs' six merged rules
rows was compared against the same job's rows on
master. Every row is exactly the oldif:plus&& $GITLAB_DUO_TOKEN, with thechanges:block unchanged. No other job's rules orneeds:differ frommaster, and the job set gains nothing except the hidden anchor. - Token-absent simulation. The nine gated jobs were deleted from the
merged configuration and the remaining 37 job pipeline was validated. It is
valid, so no surviving job has a
needs:pointing at a gated job. - Negative control. The same simulation with
test_aggregatorleft in place fails validation withundefined need: test_batch_1. This confirms the previous check has power rather than passing vacuously, and that gatingtest_aggregatorwas required.
With the Duo suite off, 37 jobs remain, 29 of them in the test stage, so the
stage does not become empty or vacuous.
No branch pipeline could be observed, for a pre-existing reason unrelated to
this change. This branch's commit touches CHANGELOG.md, and workflow:rules
carries a documentation skip row that sets when: never for a branch push
whose changes: include *.md. A pipeline triggered through the API is
refused for the same reason, since only web is exempted, not api. Merge
request pipelines are unaffected: the merge_request_event row that matches
**/* with when: always precedes the documentation skip row, so the merge
request pipeline for this branch will run.
Note also that the change is inert until the follow-up below. The variable still exists and is still unprotected, so on any pipeline created today the gated jobs are created and still fail on entitlement. That is expected and does not contradict the change.
Follow-up
- Delete the dead unprotected
GITLAB_DUO_TOKENvariable. This turns merge request pipelines andmastergreen, because the gated jobs stop being created. - Add the real credential as a protected variable when it is available.
Merge request pipelines keep skipping the suite;
masterruns it and must pass. - Decide on protected tags. Protected variables reach protected tags as well
as protected branches, and this project defines no protected tag pattern. As
things stand, once the replacement token is protected the Duo suite will not
run on
v*release pipelines. Add av*protected tag alongside the variable if a release is meant to exercise the Duo lane. This is called out in a comment at the anchor.