Checkpoint Lab — REST API, CLI, Script Console, Groovy Administration, Safe Automation, and Administrative Guardrails
Build a small safe administration client that proves the Jenkins controller and exact job identity, triggers only one disposable job, captures queue and build IDs, verifies the terminal result, and refuses an ambiguous destructive target such as “latest” without ever issuing a delete request.
Learning objectives
- Build a small safe administration client around an exact disposable Jenkins job.
- Predict and verify controller/job/queue/build/agent state transitions.
- Capture request/response and run evidence without exposing the API token.
- Refuse an ambiguous destructive target before constructing a mutation request.
- Produce a cleanup/limitations record that distinguishes API acceptance from build success.
1. Checkpoint scenario
Your platform team wants a tiny automation utility that can verify a
controller, inspect admin-lab/exact-job, trigger that
one job, follow it through the queue, and report the terminal
result. A previous prototype also accepted latest for
destructive operations. Your replacement must reject that ambiguity
before any destructive HTTP request exists.
2. Baseline and assumptions
| Component | Pinned assumption |
|---|---|
| Jenkins | 2.568.3 LTS |
| Controller/agent Java | 21 (25 also supported by this LTS) |
| Script Security reference | 1422.v06869826dd9b_ |
| CLI |
jenkins-cli.jar downloaded from this
controller; WebSocket mode
|
| Controller built-in executors | 0 |
| Agent |
trusted disposable linux-ci, one executor
|
| Job | admin-lab/exact-job |
| Credential |
disposable ch28-api API token stored outside
workspace/evidence
|
3. Controller/item/identity preflight
Before the run, capture:
export JENKINS_URL='http://127.0.0.1:8080'
mkdir -p checkpoint-evidence
curl -fsS -D checkpoint-evidence/controller-headers.txt -o /dev/null "$JENKINS_URL/login"
grep -i '^X-Jenkins:' checkpoint-evidence/controller-headers.txt
Verify the disposable agent is online and built-in node has zero
executors. Verify ch28-api can read/build the exact lab
job but lacks Overall/Administer/Script Console
capability. Create the protected auth file as in Lesson 2; record
only its mode/path, never contents.
4. Predict state changes before execution
Write these predictions into
checkpoint-evidence/predictions.txt:
- Controller configuration will not change; only a queue item and one new build record will be created.
-
The trigger response will identify one queue item, which will
later resolve to one numeric build on
linux-ci. - The build result/artifact can be verified independently from the trigger response.
-
The refusal test for
latestwill terminate client-side and create no Jenkins deletion/cancellation request.
5. Safe client contract
The client accepts no arbitrary job argument in this checkpoint. Its target is an allowlisted full name. It must:
-
GET the exact job and require
fullName == admin-lab/exact-job. - Require
buildable == true. -
POST only to that job’s
/buildendpoint using API-token authentication. -
Require a queue
Locationheader and follow only that queue item. -
Require a concrete
executable.number/urlbefore build verification. - Poll the numeric build until terminal result.
- Refuse destructive aliases before any request construction.
Use the safe_admin.py implementation from Lesson 2 or
an equivalent implementation with these invariants.
6. Execute and capture API evidence
python3 safe_admin.py \
> checkpoint-evidence/client-stdout.txt \
2> checkpoint-evidence/client-stderr.txt
STATUS=$?
printf 'client_exit=%s\n' "$STATUS" | tee checkpoint-evidence/client-exit.txt
Because the script deliberately runs the refusal test after
verifying the build, a nonzero final exit is expected. Review stdout
to prove a concrete queue/build was observed first, then stderr/exit
text for REFUSE destructive target. If you separate the
refusal into a second command, record both exit codes explicitly.
7. Independently verify Jenkins state
Do not trust the client’s own summary as the only evidence. Query the exact job/build through a second read-only path:
JOB_URL="$JENKINS_URL/job/admin-lab/job/exact-job"
curl --config "$HOME/.config/ch28/curl.conf" -fsS \
"$JOB_URL/api/json?tree=fullName,lastBuild[number,url,result]" \
> checkpoint-evidence/job-after.json
java -jar "$HOME/.config/ch28/jenkins-cli.jar" \
-s "$JENKINS_URL" -webSocket -auth @"$HOME/.config/ch28/cli-auth.txt" who-am-i \
> checkpoint-evidence/cli-whoami.txt
Open the captured build URL and confirm its console shows
linux-ci/workspace identity and the archived
result.txt.
8. Prove no ambiguous destructive request was sent
The refusal logic must execute before forming a URL such as
/lastBuild/doDelete. Search your source/evidence:
grep -n "REFUSE destructive target" safe_admin.py checkpoint-evidence/client-stderr.txt || true
The client contains no code path that sends a DELETE/POST for
latest. This is stronger than sending the request and
hoping Jenkins rejects it.
9. Required evidence packet
| Evidence | What it proves |
|---|---|
| controller headers/version + base URL | target controller identity |
| automation username/auth method + auth-file mode | identity/secret-handling contract without secret value |
| exact job preflight JSON | full item path/buildable state |
| trigger status/Location (redacted) | request accepted and queue item identity |
| queue JSON | task/queue ID and executable transition |
| numeric build JSON + console/artifact observation | terminal run identity/result and execution context |
CLI who-am-i |
independent identity verification |
| safe client source/hash | guard logic actually executed |
| refusal output/exit | ambiguous destructive target blocked client-side |
| predictions/results/limitations note | causal learning and lab boundaries |
10. Cleanup and rollback
- Revoke the disposable
ch28-apitoken. -
Delete
~/.config/ch28/curl.confandcli-auth.txt; retain only non-secret digests/metadata if needed. -
Remove the synthetic
admin-labjob/folder using the same controlled lab owner that created it, after preserving desired evidence. -
Destroy the disposable agent/controller. Do not copy
JENKINS_HOMEor auth files into the evidence archive. - Verify the checkpoint created no external deployment/publication state.
11. What Chapter 28 adds to the production operating model
You can now automate Jenkins administration without treating every task as privileged Groovy: identities are scoped, exact resources are named, API/CLI contracts are bounded, CSRF behavior is understood rather than disabled, side effects are followed through queue/build state, credentials stay out of logs, and ambiguous/destructive targets are rejected before mutation.
Knowledge check
Answer before revealing the explanation.
1. What identities must the checkpoint correlate?
Controller/base URL and Jenkins version, automation user, exact job full name, queue item ID, executable build number/URL, source/build cause and agent/workspace evidence. Together they make the trigger attributable end to end.
2. Why does the client refuse an ambiguous destructive target instead of asking Jenkins to resolve it?
Refusal is the guardrail. A destructive operation must be bound to one explicit resource identity before the request is constructed; dynamic aliases such as latest are intentionally outside the accepted input contract.
3. What proves the trigger was bounded to one job?
The preflight API returns exactly the expected fullName, the POST URL contains that encoded folder/job path, the queue item points to the same task, and the resulting executable URL/number is captured before result verification.
4. What should be retained from authentication handling?
Retain the username/identity and method (API token, CLI auth file), but not the token itself. Evidence may include file permission checks and redacted command/config metadata.
5. Why is Script Console absent from the checkpoint mutation path?
The exercise is specifically about choosing the least powerful interface. REST and CLI can accomplish the bounded tasks with permission checks and exact targets; invoking Script Console would unnecessarily widen authority and reduce portability/auditability.
Official references and version notes
- Jenkins Remote Access API — REST-like resource URLs, build submission, depth control and authentication notes.
- Authenticating scripted clients — API-token authentication and Jenkins’ preemptive-auth behavior.
- CSRF Protection — crumb/session behavior and the API-token exemption.
- Jenkins CLI — controller-provided CLI jar, WebSocket/HTTP/SSH modes and recommended authentication methods.
- Script Console — in-process Groovy administration and remote execution surfaces.
- Jenkins permissions — why administrative/Script Console capability implies controller compromise-level authority.
- In-process Script Approval and Script Security plugin — sandbox/approval behavior for plugin-provided Groovy surfaces; this is not a Script Console safety wrapper.
- Jenkins LTS changelog and Java support policy.
2.568.3 LTS with Java 21; Jenkins
2.568.3 is tested with Java 21 and 25. The lab uses only Jenkins
core Remote API/CLI plus Script Security
1422.v06869826dd9b_ as the current Groovy-sandbox
reference. On modern Jenkins, the CLI client defaults to WebSocket;
HTTP mode is explicit, and file-based/environment authentication is
preferred over exposing a token as a command-line argument.
API-token-authenticated requests are exempt from CSRF crumbs;
password/session POSTs require the crumb/session flow. Re-check
endpoint/plugin/security documentation before reusing automation.
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.