Chapter 23Lesson 05~190 minutes

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.

Web APIWebhooksAutomationPolicy as codeGovernance

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:

  1. inventory the exact Community Build/API baseline;
  2. reconcile the private project and one HMAC-protected project webhook;
  3. run reconciliation twice and prove the second run is safe;
  4. trigger a local analysis and correlate ceTaskId with the webhook event;
  5. preserve one deliberate failure—either blind duplicate create, wrong webhook secret, or one-page-only inventory—and repair only the causal layer;
  6. 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

Minimum contents
  • assumptions.md and 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

  1. Export webhook/project evidence first.
  2. Delete the owned webhook by exact webhook key.
  3. Delete the disposable project by exact project key only if this checkpoint created it.
  4. Revoke API User token and project-analysis token; verify later use fails.
  5. Stop the local receiver and remove temporary payload files.
  6. Remove the temporary key-pattern permission template and restore global permission changes.
  7. 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?

Why keep the duplicate-create error after the fix?

What does a matching ceTaskId and webhook task ID prove?

Why is the real webhook secret absent from the negative HMAC test?

What is the correct response when a future release documents a V2 replacement for one used endpoint?

Next lesson — Next chapter

Monorepos, Multi-Module Builds, Generated Code, and Complex Repository Layouts

Chapter 24 applies governed analysis and automation boundaries to monorepos, modules, generated sources, and complex layouts.

Official references and version notes

Version and 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.

Automation evidence rule. Preserve endpoint generation/path/method, sanitized request intent, HTTP status/body, pagination completion, token owner/type/expiration (never the value), before/after policy state, revision, 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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.