Checkpoint Lab — Web API, Webhooks, Automation, Provisioning, and Policy as Code
Produce an idempotent Community Build policy client plus HMAC-verifying local webhook receiver, prove rerun safety and first-failure handling, and record endpoint/version assumptions and rollback evidence.
Learning objectives
- Produce a policy-as-code client whose second run is demonstrably safe.
- Receive and validate an HMAC-protected local SonarQube webhook.
- Prove one deliberate API/reconciliation failure and causal repair without discarding first-failure evidence.
-
Correlate revision, scanner,
ceTaskId, Compute Engine, gate, webhook, and downstream receiver state. - Record Web API V1/V2 assumptions, token expiration/permissions, endpoint ownership, and cleanup evidence.
1. Checkpoint scenario
You own a disposable automation user and a desired-state file for
sq-ch23-policy-lab. Your job is to:
- inventory the exact Community Build/API baseline;
- reconcile the private project and one HMAC-protected project webhook;
- run reconciliation twice and prove the second run is safe;
-
trigger a local analysis and correlate
ceTaskIdwith the webhook event; - preserve one deliberate failure—either blind duplicate create, wrong webhook secret, or one-page-only inventory—and repair only the causal layer;
- revoke credentials and remove only the lab-owned objects.
2. Exact assumptions and preflight
| Assumption | Evidence required |
|---|---|
| SonarQube | Community Build 26.9.0.129388 from downloads/system/version evidence |
| Scanner | SonarScanner CLI 8.1.0.6389; runtime recorded by scanner |
| Web API |
Existing Web API + emerging V2; exact used endpoints
verified in target /web_api
|
| API credential |
Short-lived User token owned by ch23-bot; no
administrator token in automation
|
| Scanner credential |
Separate project-analysis token for
sq-ch23-policy-lab
|
| Webhook | Project-level webhook with HMAC secret; receiver local/sandbox only |
| Database/plugins/CI/IdP | No database, plugin, CI provider, or external IdP mutation required |
export SONAR_HOST_URL="http://localhost:9000"
export PROJECT_KEY="sq-ch23-policy-lab"
sonar-scanner --version
curl -fsS "$SONAR_HOST_URL/api/server/version"
# Review this instance's endpoint docs before the checkpoint:
# $SONAR_HOST_URL/web_api
3. Predictions before action
Write at least four predictions before running anything:
- Prediction A: first reconciliation creates missing owned state; second reconciliation creates nothing.
- Prediction B: a valid HMAC receiver accepts SonarQube’s analysis webhook, while the same payload signed with a different secret is rejected.
- Prediction C: scanner exit/report upload does not by itself prove Compute Engine success or Quality Gate pass.
- Prediction D: revoking the API User token causes authenticated API calls to fail without changing project state.
4. Desired-state manifest
{
"project": {
"key": "sq-ch23-policy-lab",
"name": "SQ Chapter 23 Policy Lab",
"visibility": "private"
},
"webhook": {
"name": "ch23-local-receiver",
"urlFromEnvironment": "CH23_WEBHOOK_URL",
"secretFromEnvironment": "CH23_WEBHOOK_SECRET"
},
"ownership": {
"deleteUnmanagedObjects": false,
"ambiguousMatchPolicy": "fail-closed"
}
}
Secret values are references, not policy-file content.
5. Start receiver and perform a negative HMAC test
Use the Lesson 2 receiver. Before SonarQube sends anything, prove the receiver rejects a fake request signed with the wrong secret. Keep the real secret only in the receiver/SonarQube webhook configuration.
export CH23_WEBHOOK_SECRET="$(python -c 'import secrets; print(secrets.token_hex(32))')"
export CH23_WEBHOOK_URL="http://127.0.0.1:9123/sonar" # host-process SonarQube
python receiver.py &
RECEIVER_PID=$!
# Negative test: do not use the real secret.
printf '%s' '{"project":{"key":"sq-ch23-policy-lab"}}' > /tmp/ch23-fake.json
BAD_SIG="$(python - <<'PY'
import hashlib,hmac
body=open('/tmp/ch23-fake.json','rb').read()
print(hmac.new(b'wrong-secret', body, hashlib.sha256).hexdigest())
PY
)"
curl -sS -o evidence/bad-hmac-body.txt -w '%{http_code}\n' \
-H "X-Sonar-Webhook-HMAC-SHA256: $BAD_SIG" \
-H 'Content-Type: application/json' \
--data-binary @/tmp/ch23-fake.json \
http://127.0.0.1:9123/sonar \
| tee evidence/bad-hmac-status.txt
Expected status: 401. This verifies receiver behavior without exposing or testing the real secret.
6. Run the policy client twice
python reconcile.py | tee evidence/reconcile-1.txt
python reconcile.py | tee evidence/reconcile-2.txt
# Capture sanitized current state.
curl --fail-with-body -sS \
-H "Authorization: Bearer $SONAR_API_TOKEN" \
"$SONAR_HOST_URL/api/webhooks/list?project=$PROJECT_KEY" \
> evidence/webhooks.json
Verification: exactly one owned project key and one owned webhook; second run reports present/compliant state rather than another create.
7. Deliberate API fault: run the old create-only logic once
Now preserve a real deterministic failure by calling create on the already-created project. Do not delete/recreate the project to “make the command work.”
set +e
curl --fail-with-body -sS -X POST \
-H "Authorization: Bearer $SONAR_API_TOKEN" \
--data-urlencode "project=$PROJECT_KEY" \
--data-urlencode "name=Duplicate Attempt" \
"$SONAR_HOST_URL/api/projects/create" \
> evidence/duplicate-create.out 2>&1
CREATE_RC=$?
set -e
echo "$CREATE_RC" | tee evidence/duplicate-create-exit.txt
Repair: rerun reconcile.py. It reads
first, recognizes the existing exact key, and makes no destructive
change. The broken response remains in the packet.
8. Run analysis and preserve asynchronous evidence
git rev-parse HEAD | tee evidence/revision.txt
sonar-scanner -X 2>&1 | tee evidence/scanner.log
cp .scannerwork/report-task.txt evidence/report-task.txt
CE_TASK_ID="$(sed -n 's/^ceTaskId=//p' .scannerwork/report-task.txt)"
echo "$CE_TASK_ID" | tee evidence/ce-task-id.txt
curl --fail-with-body -sS \
-H "Authorization: Bearer $SONAR_API_TOKEN" \
"$SONAR_HOST_URL/api/ce/task?id=$CE_TASK_ID" \
| tee evidence/ce-task.json
Poll with bounded delay until the task is terminal if necessary. Never infer completion from scanner exit code.
9. Prove event correlation and receiver state
cat evidence/webhook-events.jsonl | tee evidence/webhook-events-copy.txt
python - <<'PY'
import json
ce = open('evidence/ce-task-id.txt').read().strip()
events = [json.loads(x) for x in open('evidence/webhook-events.jsonl') if x.strip()]
matched = [e for e in events if e.get('taskId') == ce]
assert matched, f'no validated webhook event matched ceTaskId={ce}'
print(json.dumps(matched[-1], indent=2, sort_keys=True))
PY
Record task status and Quality Gate status separately. A matching event proves correlation, not that the gate passed.
10. API-version assumptions record
server: Community Build 26.9.0.129388
web_api_policy: use only endpoints documented by this target instance
existing_api_endpoints_used:
- GET /api/server/version
- GET /api/components/search
- POST /api/projects/create
- GET /api/webhooks/list
- POST /api/webhooks/create|update|delete
- GET /api/ce/task
v2_statement: migration is gradual; no mechanical prefix conversion
content_type_for_post: application/x-www-form-urlencoded unless endpoint docs say otherwise
auth: Bearer User token for API; project-analysis token for scanner
expiration_header: SonarQube-Authentication-Token-Expiration
webhook_signature: X-Sonar-Webhook-HMAC-SHA256 over raw body
11. Verification checklist
- ☐ Exact server/scanner versions are recorded.
- ☐ API token owner/type/permissions/expiration are recorded without token value.
- ☐ Endpoint methods/paths/content types are documented from the target instance.
- ☐ Pagination logic is exercised with a small page size.
- ☐ First reconcile creates missing state; second reconcile produces no duplicate objects.
- ☐ Duplicate-create failure is preserved and repaired by read-before-create logic.
- ☐ HMAC-negative test returns 401 without using the real secret.
-
☐ Actual webhook event matches scanner
ceTaskId. - ☐ Compute Engine task status and Quality Gate status are recorded independently.
- ☐ No bearer/scanner/webhook secret appears in source or evidence.
12. Required evidence packet
-
assumptions.mdand API-version assumptions record. - sanitized token owner/type/expiration/permission record.
- desired-state manifest and controller version/hash.
- reconcile run 1 and run 2 logs.
- paginated inventory samples.
- preserved duplicate-create error body/exit status.
-
revision, scanner log,
report-task.txt,ceTaskId, Compute Engine response. - webhook configuration metadata (no secret), negative HMAC result, validated event record.
- Quality Gate result and limitations note.
- cleanup/revocation verification.
13. Cleanup and rollback
- Export webhook/project evidence first.
- Delete the owned webhook by exact webhook key.
- Delete the disposable project by exact project key only if this checkpoint created it.
- Revoke API User token and project-analysis token; verify later use fails.
- Stop the local receiver and remove temporary payload files.
- Remove the temporary key-pattern permission template and restore global permission changes.
- Unset all token/secret environment variables.
14. What this adds to the governed operating model
Chapter 23 adds an observable automation control plane: identities are scoped; endpoints are versioned/documented; reads precede mutations; pagination is complete; reruns converge; asynchronous task identity is retained; webhook authenticity is verified; and destructive cleanup requires exact ownership evidence.
Chapter 24 continues with Monorepos, Multi-Module Builds, Generated Code, and Complex Repository Layouts, applying these control principles to repositories where scope and project boundaries are substantially more complex.
Knowledge check
What proves the policy client is idempotent?
The second run reads the same desired/current state and performs no duplicate creation or unnecessary mutation, with independent reread evidence.
Why keep the duplicate-create error after the fix?
It is first-failure evidence proving the old create-only design was non-idempotent and that the repair addressed the actual cause.
What does a matching ceTaskId and webhook task ID
prove?
The scanner/server task and validated event refer to the same asynchronous processing job; it does not by itself prove the Quality Gate passed.
Why is the real webhook secret absent from the negative HMAC test?
The goal is to prove invalid signatures are rejected without exposing or unnecessarily using the real shared secret.
What is the correct response when a future release documents a V2 replacement for one used endpoint?
Update that endpoint adapter and its contract tests against the documented replacement; do not mechanically rewrite unrelated API paths.
Official references and version notes
- Community Build — Web API — bearer authentication, token-expiration header, POST form-data guidance, and gradual Web API V2 transition.
- Community Build — managing tokens — User/project/global token semantics, expiration and revocation.
-
Community Build — automating project creation/import
— local project creation through
POST /api/projects/createand examples of current V2 DevOps-platform endpoints. - SonarQube — webhooks — event payload, project/global webhooks, delivery monitoring and HMAC protection.
- SonarQube public Web API reference — webhooks — create/list/update/delete/delivery endpoint family and permission requirements.
- SonarQube public Web API reference — components — documented component discovery/read operations.
- SonarQube downloads — current Community Build/Server/LTA release identities used in the dated compatibility note.
Rechecked 2026-09-08. Mandatory examples target
Community Build 26.9.0.129388 and
SonarScanner CLI 8.1.0.6389. Current Community
Build documentation states that Web API V2 is gradually replacing
the existing Web API endpoint by endpoint; do not infer a
mechanical /api/v2 rewrite. Bearer authentication
with a User token is recommended for Web API calls, and
authenticated responses normally expose
SonarQube-Authentication-Token-Expiration for
rotation planning. Unless an endpoint documents otherwise,
traditional POST Web API calls should use form data /
application/x-www-form-urlencoded. Project creation
remains documented at POST /api/projects/create,
while some newer capabilities already use V2 resources. SonarQube
webhooks can be configured at project/global scope and can be
protected with HMAC-SHA256 in
X-Sonar-Webhook-HMAC-SHA256; validate the raw body
before trusting a payload. Webhook payloads include background
task and Quality Gate evidence, but delivery success is not
equivalent to Compute Engine success or gate pass. Always recheck
the exact target instance’s /web_api documentation,
endpoint changelog, permission requirement, pagination/error
schema, and V2 replacement status before production automation.
ceTaskId, Compute
Engine result, webhook HMAC outcome, task/gate state, controller
version, ownership record, and cleanup/revocation evidence separately.
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.