GitLab Tokens and Bots

The credential model for GitLab in the lab — operator PAT (gitlab-md-pat), deploy tokens, runner registration tokens, project access tokens, the secret-variable rules, and the platform-gitops MR mechanics via the GitLab API.

GitLab has a sprawling token model. The lab keeps the surface deliberately small and uses each token type for what it’s designed for. This page documents the token taxonomy in use, the credential-custody pattern, and the variable rules.

Token taxonomy

Token typePurposeCustody
Operator PAT (gitlab-md-pat)API access for the bootstrap operator; create groups/projects/shares; MR mechanics against project ID 12; scoped fine-grained per ADR directiongitlab-md-pat file in the local-only secrets directory (Git-ignored, mode 0700)
Deploy token (read-only)Project clone access for Jenkins / Argo CD / runnersJenkins credential store (gitlab-<project>-read); per-project
Deploy token (read-write)Image-registry push token if GitLab Container Registry is usedNot in scope today — Nexus is the registry
Project access tokenPer-project token, scoped to one projectPer-job custody
Group access tokenPer-group token, scoped to one groupReserved for future federated CI flows
Runner registration tokenOne per runner class, used at runner startupsecrets/gitlab-runner-<class>/
CI/CD variablesPer-project / per-group masked + protected variablesGitLab’s own variable store
OIDC tokens (planned)Federation against Keycloak when that landsNot yet

The bootstrap admin user zahid is the only user with broad GitLab API access today. Move-to-named-users + deploy-tokens is a roadmap item per the operator guide.

Operator PAT (gitlab-md-pat)

The operator’s Personal Access Token is the main bootstrap credential. It is held in a single file named gitlab-md-pat under the local-only secrets directory, and it is the credential that drives every platform-gitops API call.

GITLAB_URL=http://30.30.30.5
GITLAB_PAT="$(cat <secrets-dir>/gitlab-md-pat)"
curl -fsS --header "PRIVATE-TOKEN: $GITLAB_PAT" \
  "$GITLAB_URL/api/v4/version"
unset GITLAB_PAT

Custody discipline:

  • The PAT lives only in <secrets-dir>/gitlab-md-pat. The directory is Git-ignored and mode-restricted (0700); the file is mode 0600.
  • The PAT is never printed, copied into issues, pasted into wiki, or echoed in shell history.
  • The PAT scope is operator-driven (currently api scope for the operator workspace). Future hardening: narrow to per-task scopes.
  • The PAT is the only credential authorized for platform-gitops MR work today — see the MR mechanics section below.

platform-gitops MR mechanics (GitLab API, not gh)

The platform GitOps repository is a GitLab project (project ID 12), not a GitHub repo. That means merge requests go via the GitLab REST API with the operator PAT — not the gh CLI. The shape of an MR-creating call:

GITLAB_URL=http://30.30.30.5
GITLAB_PAT="$(cat <secrets-dir>/gitlab-md-pat)"
PROJECT_ID=12

curl -fsS \
  --header "PRIVATE-TOKEN: $GITLAB_PAT" \
  --request POST \
  --data "source_branch=<branch>" \
  --data "target_branch=main" \
  --data "title=<title>" \
  --data "description=<body>" \
  "$GITLAB_URL/api/v4/projects/$PROJECT_ID/merge_requests"
unset GITLAB_PAT

The repeatable working-copy convention is:

ConventionValue
Local clone/home/ze/claude-agent/clones/platform-gitops (worktree-of-main)
Remotehttp://30.30.30.5/comptech-platform/openshift-ops/openshift-platform-gitops.git
Sync-wave for new operator installs10 (operator-install pattern)
Project ID12
API basehttp://30.30.30.5/api/v4

Critical reminders:

  • Don’t use gh for platform-gitops work. gh talks to GitHub, not GitLab; the platform GitOps repo lives only in GitLab.
  • Don’t use the public route (gitlab.apps.sub.comptech-lab.com) for git push or MR API calls. Smart-HTTP clone over the public route is non-functional by design; automation uses the LAN endpoint.
  • Branch first, MR second. Never push to main; main is protected, force-push is disabled, and only the bootstrap maintainer can merge.
  • Don’t --no-edit rebase — interactive rebase isn’t supported in the agent shell and changes to existing commits should be opened as new commits.

Deploy tokens (read-only)

For Jenkins jobs and Argo CD repo-server, deploy tokens are the right primitive:

  • Scoped to one project.
  • Read-only.
  • Can be revoked without affecting any user account.
  • Username + password pair, suitable for HTTP Basic auth in git clone URLs.

Current Jenkins credentials of this type:

IDProject
gitlab-openliberty-readiness-probe-readdivisions/sandbox/openliberty-readiness-probe
gitlab-demo-smoke-readdivisions/sandbox/demo-smoke

Each app gets its own deploy token, so revoking one app’s CI token doesn’t affect any other.

Project access tokens

Project access tokens are per-project bot identities; useful for narrow automation that doesn’t belong to a human:

  • Webhook delivery from Jenkins → GitLab (commit-status updates).
  • Per-project notifications / integrations.

The current notifyCommit Jenkins access tokens are per-job tokens, custody under secrets/jenkins/git-notifycommit-<job>-token.json. Same hygiene: never echoed, never committed.

Runner registration tokens

Each runner class (per the protected runner model) registers with its own token. The token is not a long-lived API credential — it’s a one-shot bootstrap that grants the runner an internal identity. After registration, the runner uses its internal credential, not the registration token, for subsequent communication with the GitLab server.

Custody per class:

secrets/gitlab-runner-validation/registration-token
secrets/gitlab-runner-build/registration-token
secrets/gitlab-runner-security/registration-token
secrets/gitlab-runner-ops/registration-token
secrets/gitlab-runner-deploy/registration-token

After registration, rotate the registration token to prevent reuse.

CI/CD variables

GitLab CI/CD variables (set under Project Settings → CI/CD → Variables or at group level) carry credentials available to pipelines. Rules:

  • Mask all values that are credentials. GitLab will redact them from job logs.
  • Mark as protected if they should only be available to protected-branch pipelines.
  • Scope to environment when applicable (different creds for dev vs prod).
  • Document variable names and purpose, not values, in MRs and runbooks.
  • Validate the pipeline can use the variable without printing itecho $TOKEN is a CI lint failure.

Allowed credential sources

Per the operator guide:

Allowed:

  • Vault-backed secret delivery (planned; not all secrets there yet).
  • Protected and masked GitLab variables for CI bootstrap credentials.
  • External Secrets references by name.
  • Local-only secret custody during lab bootstrap (secrets/).
  • Jenkins credential store for current CI jobs until Vault migration.

Forbidden:

  • Plaintext secrets in Git.
  • Credentials in issues, wiki pages, MR text, or reports.
  • Kubeconfigs in repos.
  • Runner registration tokens in repos.
  • Nexus/Jenkins/GitLab/Vault tokens in unprotected or unmasked variables.
  • Rendered Kubernetes Secret manifests in GitOps repos.

Token-creation checklist

Before creating any new GitLab variable or token:

  1. Confirm the target project/group and environment scope.
  2. Use masked + protected flags where possible.
  3. Prefer project-specific or group-specific least privilege.
  4. Record variable names and purpose in the repo’s README or docs/, not values.
  5. Validate the pipeline can use the variable without printing it.
  6. Document the rotation plan (who rotates, on what cadence, where the new value goes).

Break-glass and rotation

Break-glass examples in the GitLab context (per operator guide):

  • GitOps cannot read the platform repo and a temporary credential repair is required.
  • Branch protection accidentally locks out all maintainers.
  • A runner token must be revoked after suspected leakage.
  • A project share must be removed immediately.

Every break-glass action must record:

  • Reason
  • Approver
  • Actor
  • Time
  • Target group/project/user/runner
  • Exact API/UI action summary
  • Validation result
  • Rollback path
  • Git backport commit or expiry date

Failure modes

Symptom: an operator PAT is leaked

Root cause. Pasted into a chat, an issue, an MR comment, or a screenshot.

Fix. Revoke the PAT immediately in GitLab UI. Create a new PAT. Update <secrets-dir>/gitlab-md-pat. Audit any tools that may have cached the old value.

Prevention. Never paste tokens into anything. Use the runbook pattern: load from custody, unset after use.

Symptom: a runner is doing too much

Root cause. Tag misconfiguration; runner accepts untagged jobs or jobs tagged for a different class.

Fix. Update runner config to require specific tags; refuse untagged jobs.

Prevention. Negative access tests at runner setup time.

Symptom: a CI/CD variable was used in an MR pipeline and the value got echoed in job logs

Root cause. Variable not masked, or a pipeline step printed it directly (e.g., set -x debug mode).

Fix. Rotate the credential immediately. Update the pipeline to not print the variable. Mask the variable in GitLab.

Prevention. Default to masked + protected on every variable; CI lint checks for set -x / set -v in jobs that touch creds.

Symptom: a project access token is stale (forgotten in some old credential store)

Root cause. Project access tokens don’t auto-expire by default; an old one can linger.

Fix. Rotate; revoke the old one.

Prevention. Set token expiry on creation; review token list periodically.

Roadmap

  • Migrate operator PAT use to named users + scoped tokens. The bootstrap pattern should not be the production pattern.
  • Wire Vault as the credential source. Move from secrets/ custody to Vault-delivered values for tokens that benefit from rotation.
  • Add automated audit logging. GitLab CE has limited audit support; supplement with operator-driven evidence.

References

  • opp-full-plat/connection-details/gitlab-operator-guide.md — secret and variable policy.
  • opp-full-plat/adr/0023-federated-gitlab-group-repo-ownership.md — ownership boundary the tokens enforce; sister to the FG-1 readiness gate.
  • Deployment & Runners — endpoint table, FG-1 acceptance criteria.
  • Protected runner model — runner-token specifics.
  • opp-full-plat/runbooks/secrets-custody-drift-check.md — periodic drift check across credential custody.

Last reviewed: 2026-05-12