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, the duo run --help golden snapshot, and the --output-format json check) 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 facing stig::verify-creds there 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:

  1. 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 old if: plus && $GITLAB_DUO_TOKEN, with the changes: block unchanged. No other job's rules or needs: differ from master, and the job set gains nothing except the hidden anchor.
  2. 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.
  3. Negative control. The same simulation with test_aggregator left in place fails validation with undefined need: test_batch_1. This confirms the previous check has power rather than passing vacuously, and that gating test_aggregator was 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

  1. Delete the dead unprotected GITLAB_DUO_TOKEN variable. This turns merge request pipelines and master green, because the gated jobs stop being created.
  2. Add the real credential as a protected variable when it is available. Merge request pipelines keep skipping the suite; master runs it and must pass.
  3. 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 a v* 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.

References

  • Epic: &1
  • #7, the credential tracking issue, covers steps 1 and 2 above.
  • #1, the entitlement issue, is the failure this red was reporting.

Merge request reports

Loading
Loading