Checkpoint Lab — Users, Roles, Privileges, Realms, Anonymous Access, User Tokens, and Least-Privilege Design
Implement and prove a least-privilege matrix for developer, CI publisher, consumer, and repository operator identities, rotate the CI credential, and remove every disposable security object.
Learning objectives
- Design a four-identity least-privilege matrix before implementation.
- Implement local Community-compatible users/roles on a disposable repository topology.
- Prove positive and negative authorization through direct and group endpoints.
- Rotate the CI publisher credential and prove the old secret is invalid.
- Produce an evidence packet and remove all disposable identity state.
Checkpoint scope. Use a dedicated disposable self-hosted Community instance. Reference baseline: Nexus Repository 3.95.0-07, Java 21, local H2 only for the small non-container learning instance or another supported lab database. Record the actual version/database/blob/edition. User Tokens, SAML, OIDC, HA, and other paid capabilities are not required.
1. Scenario: four responsibilities, four identities
Your artifact platform serves one small team. Developers and
consumers retrieve artifacts through a Raw group. CI publishes to a
hosted repository. A repository operator may inspect and edit the
repository configuration but should not administer users or the
entire Nexus instance. The security design must express those
responsibilities without nx-admin.
| Principal | Client endpoint | Required actions | Explicitly not required |
|---|---|---|---|
| ch15cp-developer | Group read endpoint | Browse + read | Publish, delete, repository admin, security admin |
| ch15cp-consumer | Group read endpoint | Read; optionally browse if the client/UI requires it | Publish, delete, repository admin |
| ch15cp-ci | Hosted publish endpoint + group read | Hosted browse/read/add/edit; group read | Delete, repository admin, security admin |
| ch15cp-operator | Repository configuration/API | Repository-admin browse/read/edit for disposable repos; content read as needed | User/role management, nx-admin, wildcard security privileges |
2. Preflight and before-state
export NX_URL="http://127.0.0.1:8081"
export LAB="$(mktemp -d)"
mkdir -p "$LAB/evidence" "$LAB/payload"
printf 'academy chapter 15 checkpoint\n' > "$LAB/payload/build.txt"
sha256sum "$LAB/payload/build.txt" > "$LAB/evidence/build.sha256"
export NX_USER="admin"
read -r -s -p 'Disposable Nexus admin password: ' NX_PASS; echo
export NX_AUTH_FILE="$(mktemp)"; chmod 600 "$NX_AUTH_FILE"
printf 'machine 127.0.0.1 login %s password %s\n' "$NX_USER" "$NX_PASS" > "$NX_AUTH_FILE"
unset NX_PASS
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/status" > "$LAB/evidence/status.txt"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/system/license" > "$LAB/evidence/license.json" || true
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/anonymous" > "$LAB/evidence/anonymous-before.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/realms/active" > "$LAB/evidence/realms-before.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/users" > "$LAB/evidence/users-before.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/roles" > "$LAB/evidence/roles-before.json"
3. Predictions before mutation
Write at least these predictions into
$LAB/evidence/predictions.txt before creating anything:
- An unauthenticated GET through the group will be denied while global anonymous access is disabled.
- The CI principal will be able to create/update the designated hosted asset but will be denied deleting it because no delete privilege is assigned.
- The repository operator will be able to inspect/edit the disposable repository configuration yet will be denied user-management endpoints because its role contains no security-management privileges.
- Changing the CI password will invalidate the old credential while leaving the CI role assignments unchanged.
4. Create disposable hosted and group repositories
Create academy-ch15cp-hosted as Raw hosted and
academy-ch15cp-group as Raw group with the hosted
repository as its only member. Use supported UI/REST operations and
verify the final topology before creating roles. The group is the
read endpoint; publication goes to hosted.
Capture repository configuration with the Repositories API. This evidence proves which resource names the generated repository privileges should reference.
5. Establish the anonymous baseline
On this dedicated lab instance, disable global anonymous access
using Settings → Security → Anonymous Access and verify via
GET /service/rest/v1/security/anonymous. Preserve the
before-state so cleanup can restore it exactly. Do not alter global
anonymous access on a shared Nexus server merely to satisfy this
checkpoint.
6. Build the roles from outcomes, not job titles
| Role ID | Minimum privilege intent |
|---|---|
| academy-ch15cp-developer | Group browse + read |
| academy-ch15cp-consumer |
Group read and only add browse if
the chosen client/UI needs it
|
| academy-ch15cp-ci |
Hosted browse/read/add/edit + group
read; no delete
|
| academy-ch15cp-operator |
Repository Admin privileges only for
academy-ch15cp-hosted and
academy-ch15cp-group required to inspect/edit
configuration; no security-management privileges
|
After creating the repositories, filter the Privileges view for each
exact repository name and select only the required generated
privilege IDs. Do not use wildcard * repository names
for this checkpoint.
7. Create four local users
Create ch15cp-developer, ch15cp-consumer,
ch15cp-ci, and ch15cp-operator. Treat
ch15cp-ci as the dedicated CI
service account, a separate principal with its own
credential lifecycle rather than a shared human login. Assign
exactly one corresponding role to each. Set temporary passwords
interactively and record only identity IDs/role assignments in
evidence—never the secret values.
8. Create private credential files
make_auth () {
user="$1"; var="$2"
read -r -s -p "Temporary password for $user: " pass; echo
file="$(mktemp)"; chmod 600 "$file"
printf 'machine 127.0.0.1 login %s password %s\n' "$user" "$pass" > "$file"
unset pass
export "$var=$file"
}
make_auth ch15cp-developer DEV_AUTH
make_auth ch15cp-consumer CONSUMER_AUTH
make_auth ch15cp-ci CI_AUTH
make_auth ch15cp-operator OP_AUTH
export HOSTED_URL="$NX_URL/repository/academy-ch15cp-hosted"
export GROUP_URL="$NX_URL/repository/academy-ch15cp-group"
Windows/PowerShell equivalent. Keep the same
security property: credentials belong in a temporary protected
object/file, never a command argument. Use
Get-FileHash -Algorithm SHA256 where Bash uses
sha256sum. The Nexus endpoints and authorization
expectations are unchanged.
9. CI publication and consumer reads
curl -sS --netrc-file "$CI_AUTH" --upload-file "$LAB/payload/build.txt" -o "$LAB/evidence/ci-put.body" -w 'CI PUT %{http_code}\n' "$HOSTED_URL/releases/app/1.0.0/build.txt" | tee "$LAB/evidence/ci-put.status"
curl -sS --netrc-file "$DEV_AUTH" -o "$LAB/evidence/dev-read.txt" -w 'developer GET %{http_code}\n' "$GROUP_URL/releases/app/1.0.0/build.txt" | tee "$LAB/evidence/dev-read.status"
curl -sS --netrc-file "$CONSUMER_AUTH" -o "$LAB/evidence/consumer-read.txt" -w 'consumer GET %{http_code}\n' "$GROUP_URL/releases/app/1.0.0/build.txt" | tee "$LAB/evidence/consumer-read.status"
sha256sum "$LAB/evidence/dev-read.txt" "$LAB/evidence/consumer-read.txt" > "$LAB/evidence/consumer-hashes.sha256"
Compare the retrieved hashes with build.sha256. Byte
equality proves the clients retrieved the same payload; it does not
prove trusted provenance or vulnerability safety.
10. Negative authorization matrix
# Anonymous group read must be denied.
curl -sS -o /dev/null -w 'anonymous group GET %{http_code}\n' "$GROUP_URL/releases/app/1.0.0/build.txt" | tee "$LAB/evidence/anon-group.status"
# Developer and consumer cannot publish.
for pair in "developer:$DEV_AUTH" "consumer:$CONSUMER_AUTH"; do
name="${pair%%:*}"; auth="${pair#*:}"
curl -sS --netrc-file "$auth" --upload-file "$LAB/payload/build.txt" -o "$LAB/evidence/$name-put.body" -w "$name PUT %{http_code}\n" "$HOSTED_URL/releases/$name-must-not-write.txt" | tee "$LAB/evidence/$name-put.status"
done
# CI must not administer repository configuration.
curl -sS --netrc-file "$CI_AUTH" -o "$LAB/evidence/ci-repo-admin.body" -w 'CI repository-config GET %{http_code}\n' "$NX_URL/service/rest/v1/repositories/raw/hosted/academy-ch15cp-hosted" | tee "$LAB/evidence/ci-repo-admin.status"
# Operator may inspect repository config but must not list/manage users.
curl -sS --netrc-file "$OP_AUTH" -o "$LAB/evidence/operator-repo.json" -w 'operator repository GET %{http_code}\n' "$NX_URL/service/rest/v1/repositories/raw/hosted/academy-ch15cp-hosted" | tee "$LAB/evidence/operator-repo.status"
curl -sS --netrc-file "$OP_AUTH" -o "$LAB/evidence/operator-users.body" -w 'operator users GET %{http_code}\n' "$NX_URL/service/rest/v1/security/users" | tee "$LAB/evidence/operator-users.status"
Exact status codes can vary around reverse proxies and unauthenticated challenges. The policy invariants are what matter: anonymous cannot read, developer/consumer cannot publish, CI cannot administer the repository, and the repository operator cannot administer identities.
11. Prove the CI role cannot delete
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/assets?repository=academy-ch15cp-hosted" > "$LAB/evidence/assets.json"
export ASSET_ID="$(python - <<'PYI'
import json, os
from pathlib import Path
p=Path(os.environ['LAB'])/'evidence/assets.json'
for item in json.loads(p.read_text()).get('items', []):
if item.get('path') == 'releases/app/1.0.0/build.txt':
print(item['id']); break
PYI
)"
test -n "$ASSET_ID"
curl -sS --netrc-file "$CI_AUTH" -X DELETE -o "$LAB/evidence/ci-delete.body" -w 'CI DELETE %{http_code}\n' "$NX_URL/service/rest/v1/assets/$ASSET_ID" | tee "$LAB/evidence/ci-delete.status"
curl -fsS --netrc-file "$DEV_AUTH" "$GROUP_URL/releases/app/1.0.0/build.txt" -o "$LAB/evidence/after-delete-test.txt"
If CI deletion succeeds, the checkpoint fails. Inspect the complete CI role set for an unintended delete or wildcard grant before doing anything else.
12. Rotate one credential and prove continuity
Change the password for ch15cp-ci through the Users UI.
Create a new private netrc file, test both old and new credentials,
and keep the role unchanged:
read -r -s -p 'New temporary password for ch15cp-ci: ' CI_NEW_PASS; echo
export CI_NEW_AUTH="$(mktemp)"; chmod 600 "$CI_NEW_AUTH"
printf 'machine 127.0.0.1 login ch15cp-ci password %s\n' "$CI_NEW_PASS" > "$CI_NEW_AUTH"
unset CI_NEW_PASS
curl -sS --netrc-file "$CI_AUTH" -o /dev/null -w 'old CI credential %{http_code}\n' "$GROUP_URL/releases/app/1.0.0/build.txt" | tee "$LAB/evidence/ci-old-after-rotation.status"
curl -sS --netrc-file "$CI_NEW_AUTH" -o /dev/null -w 'new CI credential %{http_code}\n' "$GROUP_URL/releases/app/1.0.0/build.txt" | tee "$LAB/evidence/ci-new-after-rotation.status"
The old secret should no longer authenticate while the new secret performs the same least-privilege job. This distinguishes credential lifecycle from authorization lifecycle.
13. Optional Pro extension: User Token without changing authorization
If—and only if—the disposable instance has a Pro license, you may
enable User Tokens, generate one for ch15cp-ci, store
its two parts in an approved temporary secret mechanism, and rerun
the same matrix. The expected authorization must not change. Then
delete/reset the token and verify it no longer works.
Do not enable “Require User Tokens for Repository Authentication” on a shared system. Enabling token expiration or resetting all tokens can break every build using those credentials.
14. Required evidence packet
Keep only non-secret evidence:
- actual Nexus version/edition/runtime/database/blob assumptions;
- anonymous settings before/during/after;
- active realm IDs;
- four user IDs and their assigned role IDs;
- role privilege lists;
- repository topology;
- positive/negative HTTP status results;
- payload SHA-256 comparisons;
- old/new CI credential outcome (status only);
- cleanup confirmation.
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/users" > "$LAB/evidence/users-final.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/roles" > "$LAB/evidence/roles-final.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/anonymous" > "$LAB/evidence/anonymous-final-before-restore.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/realms/active" > "$LAB/evidence/realms-final.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/repositories" > "$LAB/evidence/repositories-final.json"
15. Verification checklist
- Developer: group read succeeds; publish/delete/admin actions fail.
- Consumer: intended group read succeeds; write/admin actions fail.
- CI: hosted publish + group read succeed; delete/repository-admin/security-admin fail.
- Operator: intended repository configuration read/edit succeeds; user/role administration fails.
- Anonymous group read fails during lab.
- Old CI credential fails after rotation; new credential succeeds.
-
No identity has
nx-admin,nx-all, or a wildcard repository grant unless deliberately required and documented—which this checkpoint does not require.
16. Cleanup and rollback
- Delete the four disposable users using supported Security UI/API operations.
- Delete the four custom roles.
-
Delete
academy-ch15cp-groupfirst, thenacademy-ch15cp-hosted. - If the optional Pro User Token extension was used, reset/delete the lab token before deleting its user.
- Restore anonymous access exactly to the captured pre-lab state.
- Confirm the user/role/repository names are absent from APIs.
- Remove all temporary credential files and the local workspace.
rm -f "$DEV_AUTH" "$CONSUMER_AUTH" "$CI_AUTH" "$CI_NEW_AUTH" "$OP_AUTH" "$NX_AUTH_FILE"
unset DEV_AUTH CONSUMER_AUTH CI_AUTH CI_NEW_AUTH OP_AUTH NX_AUTH_FILE NX_USER HOSTED_URL GROUP_URL ASSET_ID
rm -rf "$LAB"
17. Knowledge check
Why does the operator role exclude user-management privileges?
Repository configuration maintenance and identity administration are different responsibilities; separating them limits blast radius.
What does CI password rotation prove that a role test does not?
Credential lifecycle: the old secret becomes invalid while the principal’s authorization policy remains unchanged.
Why is the group endpoint part of every read test?
Clients often consume through groups; group privileges can create exposure that direct-member tests miss.
Would a Pro User Token change what ch15cp-ci is allowed to do?
No. It changes the credential used to authenticate the same principal; roles/privileges still authorize requests.
What does Chapter 15 add to the production operating model?
A lifecycle-aware identity model covering principal source, role/privilege scope, anonymous policy, service-account ownership, credential rotation/revocation, and positive/negative authorization evidence.
18. Production operating-model addition and bridge to Chapter 16
This chapter adds an identity register and proof-driven access matrix to the artifact platform. Operators can now answer: who authenticated, through which source, which roles granted the action, which credentials are tied to automation, whether anonymous requests are intentional, and how stale identities are retired.
Chapter 16 expands the authentication side of that model into LDAP, SAML, OIDC, external identity mapping, credential rotation, and authentication troubleshooting—while preserving the least-privilege authorization model built here.
Official references and version notes
- Sonatype: Access Control — RBAC object model, default roles, additive grants, and least privilege.
- Sonatype: Users and Roles.
- Sonatype: Privileges — repository, application, selector, and wildcard privilege semantics.
- Sonatype: Realms and Authentication.
- Sonatype: Anonymous Access.
- Sonatype: User Tokens and User Tokens API.
- Sonatype: Security Management API and API Reference.
- Self-Hosted Nexus Repository Feature Matrix — User Token Support and enterprise identity boundaries.
- Sonatype: Download and 2026 self-hosted release notes.
Version-sensitive statements were rechecked on 2026-08-26. Sonatype's current official self-hosted download page exposes Nexus Repository 3.95.0 (archive build 3.95.0-07), while version-index pages may lag. The mandatory chapter path is Community-compatible: local users, roles, privileges, realms, anonymous-access settings, Raw repositories, and temporary passwords. Self-hosted User Token Support is Pro-only and appears only as an optional extension or fixture.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.