Files
redefined-designs/docs/ci/sonarqube-ci-identity.md
T
bermudalamb 261d087a9c
SonarQube Analysis / sonarqube (pull_request) Successful in 14m41s
Tests / lint (pull_request) Successful in 2m5s
Tests / backend-unit (pull_request) Successful in 40s
Tests / frontend-e2e (pull_request) Failing after 8m57s
docs(ci): record the coverage pipeline contract and the CI identity gap
Two standing documents rather than one, because they are different kinds of thing: one is a contract the pipeline must keep, the other is work not yet done.

The coverage contract names the seven requirements that keep SonarQube's number real, and what specifically breaks if each lapses. It exists because coverage does not fail loudly — it reports a smaller number, which looks exactly like tests covering less. That is the third time this project has met a tool that succeeds while measuring nothing, after #67 and #60, so the failure mode is written down alongside how to check the guard still fires.

The identity document covers CI authenticating to SonarQube as admin rather than a restricted account, raised as a "Related" note in #61 and split out so a permissions change is not buried in a CI-config commit. It spells out the revoke step explicitly, since the workflow goes green one step earlier and stopping there leaves the old credential valid.
2026-08-20 10:14:33 -05:00

3.8 KiB

CI Identity — SonarQube

Status: Not done. Tracked as its own issue. Applies to: the SONAR_TOKEN Gitea Actions secret, and the SonarQube account behind it

Current state

Gitea Actions authenticates to SonarQube using a token belonging to the admin account. This was flagged early in the project and never revisited.

The token reaches the scanner only through the SONAR_TOKEN environment variable — #67 removed the -Dsonar.login= command-line copy, so it is no longer passed on a command line where it could reach a log. That part is already fixed. What remains is whose token it is.

Why it should change

An analysis job needs one permission: submit an analysis report for one project. The admin token carries every permission the server has — creating and deleting projects, changing quality gates and profiles, managing users, reading every project including sql-utilities.

That gap matters in three ordinary situations, none of which require anyone to be malicious:

  • A leaked token is a leaked server. CI secrets end up in more places than intended: a debug run with set -x, a third-party action, a fork's workflow. The blast radius of an analysis token is one project's analysis history. The blast radius of this one is everything.
  • A misconfigured scan can destroy history. sonar.projectKey is a string in a properties file. A wrong value plus admin rights silently creates projects; other admin endpoints can delete them. A restricted token simply fails.
  • Rotation is currently painful. Rotating admin's token means finding every other place that account is used. A dedicated account can be rotated on its own.

There is also a plainer reason: when CI shows up as admin in the analysis history, the audit trail cannot distinguish an automated scan from a person making a change.

The change

  1. On SonarQube, create a user — gitea-ci — with no global permissions.
  2. Grant it Execute Analysis on the redefined-designs project only. That is the single permission a scan needs.
  3. Generate a user token for that account. Not a project or global analysis token: SonarSource's own MCP server, and other tooling, require the user type, and this server is old enough that the distinction matters.
  4. Update the SONAR_TOKEN secret in the repository's Actions settings.
  5. Run the SonarQube workflow and confirm it still passes.
  6. Revoke the admin token that CI was using. Skipping this leaves the old credential valid and the change cosmetic.

Step 6 is the one worth naming explicitly, because the workflow will already be green after step 5 and it is easy to stop there.

Also worth deciding at the same time

The scratch project redefined-designs-local, used by scripts/scan-local.sh so local scans do not overwrite CI's analysis of main, is currently written by a personal token from the developer's environment. If local scanning becomes a habit rather than an occasional check, it deserves the same treatment: its own restricted account rather than whichever token is to hand.

Constraints on this server

SonarQube here is 9.9.8 LTA, Community edition. Two consequences:

  • It has no branch analysis, so every scan overwrites the single main analysis for whichever project key it is given. This is why the scratch project exists.
  • It does not accept Authorization: Bearer — a token is supplied as the basic-auth username. Any script checking the new account's permissions must use curl -u "$TOKEN:", not a bearer header, or it will look like the permissions are wrong when the auth scheme is.
  • docs/ci/coverage-pipeline-contract.md — what the pipeline must do for coverage to stay honest
  • #61 raised this as a "Related" note; it was split out so a permissions change would not be buried inside a CI-config commit