Chapter 15Lesson 02225–300 min

Users, Roles, Privileges, Realms, Anonymous Access, User Tokens, and Least-Privilege Design: Guided Hands-On Workflow and Core Operations

Create disposable local identities and roles, disable anonymous access on a dedicated lab instance, test least-privilege reads and writes, rotate a service credential, and preserve secret-safe evidence.

Local usersRolesService accountCredential rotationAuthorization tests

Learning objectives

  • Create disposable local users and roles without using shared administrator credentials for client work.
  • Temporarily disable anonymous access on a dedicated lab instance and restore it afterward.
  • Prove read/write/delete behavior with positive and negative HTTP tests.
  • Rotate a service-account password without printing it or placing it in command history.
  • Capture identity and authorization evidence without storing secrets.

Lab boundary. Run this only on a disposable Nexus instance dedicated to the academy. Changing global anonymous access affects every repository on that instance. Capture the pre-lab anonymous configuration and restore it during cleanup. Do not perform this exercise on a shared or employer Nexus server.

1. Scenario and state model

You will create one Raw hosted repository, academy-ch15-hosted, and three local identities:

Identity Role intent Allowed Denied
ch15-reader Consumer Browse/read lab repository Add/edit/delete and security administration
ch15-publisher Human publisher Browse/read/add/edit lab repository Delete and administration
ch15-svc-ci Automation publisher Same narrow repository content actions required by CI Delete, user/role changes, repository administration

All three authenticate through the local realm. The publisher and service account share a role definition, but remain separate principals so their credentials can be rotated, revoked, and audited independently.

Shell note. Command blocks in this lab use POSIX/Bash syntax. On Windows PowerShell, use $env:TEMP or New-TemporaryFile for temporary files, Get-FileHash -Algorithm SHA256 for checksums, and curl.exe or Invoke-WebRequest with credentials supplied from a protected variable/credential object. Never place passwords directly in a command line or committed script.

2. Preflight: prove version, edition, and current security state

export NX_URL="http://127.0.0.1:8081"
export LAB="$(mktemp -d)"
mkdir -p "$LAB/evidence" "$LAB/payload"
printf 'chapter-15 identity lab\n' > "$LAB/payload/release.txt"
sha256sum "$LAB/payload/release.txt" > "$LAB/evidence/payload.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-active.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/users" > "$LAB/evidence/users-before.json"

Record the actual running version. The reference baseline is official self-hosted 3.95.0 with Java 21. If your instance reports another supported version, use its own Swagger UI to confirm endpoint schemas before copying any API mutation.

3. Disable anonymous access only on the disposable instance

Open Settings → Security → Anonymous Access. Record the current values from anonymous-before.json, then disable anonymous access and save. Verify through GET /service/rest/v1/security/anonymous and with an unauthenticated request after the repository exists.

Why use the UI for this mutation in the lab? The REST endpoint is documented, but the exact request model can evolve. The lab's core lesson is the security state transition, not memorizing one payload shape. Chapter 20 will focus on API provisioning.

Rollback requirement. If the instance was intentionally configured for anonymous access before this lab, the cleanup must restore the exact previous setting. Do not assume the desired post-lab value is “disabled.”

4. Create the disposable repository

Create academy-ch15-hosted as a Raw hosted repository using the default lab blob store, strict content-type validation, and an ordinary write policy appropriate for the disposable lab. Then inspect it:

curl -fsS --netrc-file "$NX_AUTH_FILE"   "$NX_URL/service/rest/v1/repositories/academy-ch15-hosted"   > "$LAB/evidence/repository.json"

The repository configuration is not identity state. It establishes the resource that privileges will reference.

5. Create narrow roles

Use Settings → Security → Roles. Create:

Role Privileges
academy-ch15-reader nx-repository-view-raw-academy-ch15-hosted-browse and ...-read
academy-ch15-publisher Reader privileges plus ...-add and ...-edit; no delete

Do not add nx-admin, nx-all, repository-admin privileges, user-management privileges, or wildcard repository privileges. A role name such as “publisher” has no security effect by itself; the applied privilege list is the policy.

6. Create three local identities

Use Settings → Security → Users. Create ch15-reader with the reader role, ch15-publisher with the publisher role, and ch15-svc-ci with the same narrow publisher role. Set strong temporary lab passwords interactively. Do not paste them into lesson notes or evidence files.

The service identity is a separate user because automation needs independent lifecycle control. If the pipeline is deleted later, you can disable/remove ch15-svc-ci without affecting a human publisher.

User Token note. On current self-hosted Community Edition, do not enable or require User Tokens because that feature is Pro-only. If your lab is Pro-licensed, an optional extension later can generate a User Token for ch15-svc-ci; the authorization matrix must remain identical.

7. Capture client credentials without exposing them

read -r -s -p 'Temporary password for ch15-reader: ' READER_PASS; echo
export READER_AUTH="$(mktemp)"; chmod 600 "$READER_AUTH"
printf 'machine 127.0.0.1 login ch15-reader password %s\n' "$READER_PASS" > "$READER_AUTH"
unset READER_PASS

read -r -s -p 'Temporary password for ch15-publisher: ' PUBLISHER_PASS; echo
export PUBLISHER_AUTH="$(mktemp)"; chmod 600 "$PUBLISHER_AUTH"
printf 'machine 127.0.0.1 login ch15-publisher password %s\n' "$PUBLISHER_PASS" > "$PUBLISHER_AUTH"
unset PUBLISHER_PASS

read -r -s -p 'Temporary password for ch15-svc-ci: ' SVC_PASS; echo
export SVC_AUTH="$(mktemp)"; chmod 600 "$SVC_AUTH"
printf 'machine 127.0.0.1 login ch15-svc-ci password %s\n' "$SVC_PASS" > "$SVC_AUTH"
unset SVC_PASS

export REPO_URL="$NX_URL/repository/academy-ch15-hosted"

Netrc is used only as a disposable local mechanism so passwords do not appear in curl arguments. In production, use the organization's approved secret store/client credential mechanism.

8. Prove the initial authorization matrix

# Anonymous read should fail after anonymous access is disabled.
curl -sS -o "$LAB/evidence/anon-read.body" -w 'anonymous read %{http_code}\n'   "$REPO_URL/release.txt" | tee "$LAB/evidence/anon-read.status"

# Reader may read a published artifact, but should not upload.
curl -sS --netrc-file "$READER_AUTH" --upload-file "$LAB/payload/release.txt"   -o "$LAB/evidence/reader-put.body" -w 'reader PUT %{http_code}\n'   "$REPO_URL/reader-must-not-write.txt" | tee "$LAB/evidence/reader-put.status"

# Service account publishes the intended artifact.
curl -sS --netrc-file "$SVC_AUTH" --upload-file "$LAB/payload/release.txt"   -o "$LAB/evidence/svc-put.body" -w 'service PUT %{http_code}\n'   "$REPO_URL/release.txt" | tee "$LAB/evidence/svc-put.status"

curl -sS --netrc-file "$READER_AUTH"   -o "$LAB/evidence/reader-read.txt" -w 'reader GET %{http_code}\n'   "$REPO_URL/release.txt" | tee "$LAB/evidence/reader-read.status"
sha256sum "$LAB/evidence/reader-read.txt" > "$LAB/evidence/reader-read.sha256"

Do not hard-code one denial status across every proxy/client combination. Require the policy invariant: anonymous and reader writes do not create content, while the service account can publish and the reader can retrieve the resulting bytes.

9. Prove delete is absent

Discover the asset ID using the administrator's read access, then attempt deletion with the service identity. This proves that “publisher” really excludes removal:

curl -fsS --netrc-file "$NX_AUTH_FILE"   "$NX_URL/service/rest/v1/assets?repository=academy-ch15-hosted"   > "$LAB/evidence/assets-before-delete.json"

export ASSET_ID="$(python - <<'PYI'
import json, os
from pathlib import Path
p=Path(os.environ['LAB'])/'evidence/assets-before-delete.json'
for item in json.loads(p.read_text()).get('items', []):
    if item.get('path') == 'release.txt':
        print(item['id']); break
PYI
)"
test -n "$ASSET_ID"

curl -sS --netrc-file "$SVC_AUTH" -X DELETE   -o "$LAB/evidence/svc-delete.body" -w 'service DELETE %{http_code}\n'   "$NX_URL/service/rest/v1/assets/$ASSET_ID" | tee "$LAB/evidence/svc-delete.status"

curl -fsS --netrc-file "$READER_AUTH" "$REPO_URL/release.txt"   -o "$LAB/evidence/after-delete-attempt.txt"

If deletion succeeds, stop. The service account has an unintended broader privilege through another role; do not continue until the grant is identified.

10. Rotate the service credential and prove revocation

In Settings → Security → Users, change the password for ch15-svc-ci. Do not reuse the old secret. Preserve the existing netrc file temporarily as the “old credential” test, then create a second private netrc file with the new password.

# Keep the existing private SVC_AUTH only long enough for one negative test.
# Never copy credential material into the evidence directory.

read -r -s -p 'New temporary password for ch15-svc-ci: ' SVC_NEW_PASS; echo
export SVC_NEW_AUTH="$(mktemp)"; chmod 600 "$SVC_NEW_AUTH"
printf 'machine 127.0.0.1 login ch15-svc-ci password %s\n' "$SVC_NEW_PASS" > "$SVC_NEW_AUTH"
unset SVC_NEW_PASS

curl -sS --netrc-file "$SVC_AUTH" -o /dev/null -w 'old credential %{http_code}\n'   "$REPO_URL/release.txt" | tee "$LAB/evidence/old-credential.status"
curl -sS --netrc-file "$SVC_NEW_AUTH" -o /dev/null -w 'new credential %{http_code}\n'   "$REPO_URL/release.txt" | tee "$LAB/evidence/new-credential.status"

# The old netrc stays outside the evidence directory and is removed during cleanup.
# Evidence contains only the HTTP status, never the old or new credential.

Current Nexus releases invalidate sessions immediately when passwords change. The important lab result is independent credential lifecycle: the old automation secret stops working while the identity's role remains unchanged.

11. Inspect effective configuration without exposing secrets

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-during-lab.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/realms/active" > "$LAB/evidence/realms-final.json"

If you inspect request/application logs, extract only the timestamp, principal name, request path, and outcome needed to explain the test. Do not copy Authorization headers, netrc contents, passwords, tokens, or unrelated user data into the evidence packet.

12. Challenge: choose the control

A fourth identity needs to download from academy-ch15-hosted but must not browse the UI and must never publish. Should you enable anonymous access, assign the publisher role, or build a narrower role? Explain which exact privilege action is required and which should be absent. Then prove your answer with one successful and one denied request.

13. Cleanup and rollback

  1. Delete local users ch15-reader, ch15-publisher, and ch15-svc-ci through supported Security UI/API operations.
  2. Delete the two custom roles after users no longer reference them.
  3. Delete academy-ch15-hosted through the repository UI/API.
  4. Restore the exact anonymous-access configuration captured in anonymous-before.json.
  5. Delete temporary credential files and the local lab workspace.
rm -f "$READER_AUTH" "$PUBLISHER_AUTH" "$SVC_AUTH" "$SVC_NEW_AUTH" "$NX_AUTH_FILE"
unset READER_AUTH PUBLISHER_AUTH SVC_AUTH SVC_NEW_AUTH NX_AUTH_FILE NX_USER REPO_URL ASSET_ID
rm -rf "$LAB"

14. Knowledge check

Why do publisher and service account use separate users even if their roles are identical?

Why is changing global anonymous access allowed only on a disposable instance here?

What does the negative DELETE test prove?

Why preserve the old credential just long enough to test rotation?

Why does this Community lab use a password rather than a Nexus User Token?

15. Summary and next step

You converted the identity chain into observable policy: named principals, reusable roles, least-privilege actions, anonymous-access control, negative tests, and independent credential rotation. Lesson 3 evaluates when local identities are appropriate, when external identity should own lifecycle, and how to prevent role convenience from becoming privilege creep.

Official references and version 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.

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