Content Selectors, Repository Targets, Fine-Grained Privileges, and Path-Level Authorization: Guided Hands-On Workflow and Core Operations
Create a disposable Raw repository namespace lab, define narrow CSEL selectors, build least-privilege roles, and prove allowed and denied reads and writes through direct and group endpoints with secret-safe evidence.
Learning objectives
- Create a disposable Raw hosted/group topology for namespace-policy testing.
- Define narrow Team A and Team B selectors with current CSEL syntax.
- Create selector privileges and roles without exposing passwords in commands.
- Prove allowed and denied paths through direct and group endpoints.
- Inspect effective role composition before interpreting authorization failures.
Lab boundary. Use only a disposable local Nexus instance and synthetic names. Do not reuse production users, groups, paths, passwords, realms, or repositories. This lesson uses local Nexus users because they are Community-compatible; Pro user tokens and enterprise SSO are not required.
1. Preflight and assumptions
Assume a loopback Nexus Repository 3.95.2-compatible instance using Java 21. The exact running version is evidence, not an assumption. The database may be H2 for this disposable non-container learning instance or PostgreSQL; the policy semantics taught here do not require database inspection.
export NX_URL="http://127.0.0.1:8081"
export LAB="${TMPDIR:-/tmp}/nexus-ch14-workflow"
mkdir -p "$LAB/evidence" "$LAB/payloads"
printf 'team-a initial payload\n' > "$LAB/payloads/team-a.txt"
printf 'team-b initial payload\n' > "$LAB/payloads/team-b.txt"
read -r -p 'Disposable Nexus admin user: ' NX_USER
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/repositories" > "$LAB/evidence/repositories-before.json"
2. Create one hosted repository and one read group
Create a Raw hosted repository named
academy-ch14-hosted. For this learning exercise use the
default blob store, strict content-type validation, and
ALLOW write policy so the distinction between add and
edit can be observed. Then create a Raw group named
academy-ch14-group whose only member is the hosted
repository.
{
"name": "academy-ch14-hosted",
"online": true,
"storage": {
"blobStoreName": "default",
"strictContentTypeValidation": true,
"writePolicy": "ALLOW"
}
}
{
"name": "academy-ch14-group",
"online": true,
"storage": {
"blobStoreName": "default",
"strictContentTypeValidation": true
},
"group": {
"memberNames": ["academy-ch14-hosted"]
}
}
Use Settings → Repositories or the current repository REST endpoints. Do not deploy to the group: group deployment is a Pro-only feature and is not needed to test group reads.
3. Create and preview two selectors
| Selector | Expression | Intent |
|---|---|---|
academy-ch14-team-a |
format == "raw" and path =^ "/team-a/" |
Only Team A namespace |
academy-ch14-team-b |
format == "raw" and path =^ "/team-b/" |
Only Team B namespace |
In Settings → Repository → Content Selectors, create each selector and use Preview against the disposable hosted repository. Preview is not an authorization test; it validates which existing content matches the expression. Before content exists, it may return no matches, which is expected.
4. Bind selectors to repository actions
Create four Repository Content Selector privileges:
| Privilege | Repository | Selector | Actions |
|---|---|---|---|
academy-ch14-a-hosted-rw |
academy-ch14-hosted | academy-ch14-team-a | browse, read, add, edit |
academy-ch14-a-group-read |
academy-ch14-group | academy-ch14-team-a | browse, read |
academy-ch14-b-hosted-rw |
academy-ch14-hosted | academy-ch14-team-b | browse, read, add, edit |
academy-ch14-b-group-read |
academy-ch14-group | academy-ch14-team-b | browse, read |
Notice what is absent: delete. The first policy intentionally allows teams to publish and correct disposable content but not remove it. Also notice that the group receives its own selector privileges; the hosted-member selector is not assumed to constrain group access automatically.
5. Build roles and local users
Create local roles academy-ch14-team-a-role and
academy-ch14-team-b-role. Assign only the matching
hosted and group selector privileges. Create local users
ch14-team-a and ch14-team-b, each with
only its matching role. Use unique temporary passwords entered
interactively; do not paste a shared password into course files,
shell history, screenshots, or evidence.
After creation, inspect the role definitions through Settings →
Security → Roles or
GET /service/rest/v1/security/roles/. The effective grant should be explainable from the role contents
alone—no nx-admin, wildcard repository role, or broad
Raw group privilege.
6. Create private client credential files
read -r -s -p 'Temporary password for ch14-team-a: ' TA_PASS; echo
export TA_AUTH="$(mktemp)"; chmod 600 "$TA_AUTH"
printf 'machine 127.0.0.1 login ch14-team-a password %s\n' "$TA_PASS" > "$TA_AUTH"
unset TA_PASS
read -r -s -p 'Temporary password for ch14-team-b: ' TB_PASS; echo
export TB_AUTH="$(mktemp)"; chmod 600 "$TB_AUTH"
printf 'machine 127.0.0.1 login ch14-team-b password %s\n' "$TB_PASS" > "$TB_AUTH"
unset TB_PASS
export HOSTED_URL="$NX_URL/repository/academy-ch14-hosted"
export GROUP_URL="$NX_URL/repository/academy-ch14-group"
The password exists briefly in shell memory and then in a
permission-restricted temporary file. It never appears in a URL or
curl argument.
7. Prove direct write boundaries
curl -fsS --netrc-file "$TA_AUTH" --upload-file "$LAB/payloads/team-a.txt" "$HOSTED_URL/team-a/app/1.0.0/release.txt" -w 'team-a own write http=%{http_code}\n' -o /dev/null
curl -sS --netrc-file "$TA_AUTH" --upload-file "$LAB/payloads/team-a.txt" "$HOSTED_URL/team-b/forbidden.txt" -w 'team-a foreign write http=%{http_code}\n' -o "$LAB/evidence/team-a-foreign-write-body.txt" | tee "$LAB/evidence/team-a-foreign-write.txt"
curl -fsS --netrc-file "$TB_AUTH" --upload-file "$LAB/payloads/team-b.txt" "$HOSTED_URL/team-b/app/1.0.0/release.txt" -w 'team-b own write http=%{http_code}\n' -o /dev/null
Record the actual denied status for the cross-team write. Do not weaken the selector just to obtain a specific error code; reverse proxies and client behavior can influence the presentation while the authorization outcome remains “not allowed.”
8. Prove direct and group reads independently
for endpoint in direct group; do
base="$HOSTED_URL"; [ "$endpoint" = group ] && base="$GROUP_URL"
for path in team-a/app/1.0.0/release.txt team-b/app/1.0.0/release.txt; do
safe_name="$(printf '%s-%s' "$endpoint" "$path" | tr '/ ' '__')"
curl -sS --netrc-file "$TA_AUTH" -o "$LAB/evidence/$safe_name.body" -w "$endpoint $path http=%{http_code}\n" "$base/$path" | tee "$LAB/evidence/$safe_name.status"
done
done
Expected policy: Team A reads Team A from both endpoints and cannot read Team B from either. Team B should produce the mirror-image result. If direct and group results differ, stop and inspect the privileges bound specifically to the endpoint that behaved unexpectedly.
9. Challenge: debug a user who can browse but cannot download
Suppose a role contains only browse for the Team A
selector. The user can see matching entries in an interface but
direct GET fails. Which action is missing? Add only the required
action, then repeat one controlled request. Do not solve the problem
by granting *.
10. Cleanup for Lesson 2
If continuing to Lesson 4 or the checkpoint, keep the disposable repositories and users. Otherwise delete the local users first, then roles, then custom privileges, selectors, group, and hosted repository through supported Nexus UI/API operations. Remove local netrc files last.
rm -f "$TA_AUTH" "$TB_AUTH" "$NX_AUTH_FILE"
unset TA_AUTH TB_AUTH NX_AUTH_FILE NX_USER HOSTED_URL GROUP_URL
11. Knowledge check
Why are there separate selector privileges for hosted and group repositories?
Because group endpoint authorization is separate from direct member authorization; member restrictions are not automatically inherited by the group.
Why is delete omitted from the team roles?
Least privilege. The scenario needs publish/read/edit behavior, not artifact removal.
What should a failed Team A write to /team-b/ cause you to inspect first?
The selector expression, the privilege repository/action scope, and all roles assigned to Team A—not the blob store.
Why use Raw in this chapter?
Its path-addressed model makes namespace authorization observable without adding package-format metadata complexity.
What does Content Selector Preview prove?
Only which existing content matches the expression. It does not prove the effective authorization of a user with multiple roles.
12. Summary and next step
You built the minimum path-policy chain and tested it through two different endpoints. Lesson 3 asks when this precision is worth its operational cost and when stronger isolation—such as separate repositories—is the better design.
Official references and version notes
- Sonatype: Content Selectors — CSEL syntax, preview, supported formats, performance guidance, and upload limitations.
- Sonatype: Privileges — repository view versus content-selector privileges, actions, additive grants, and group semantics.
- Sonatype: Access Control — RBAC object model.
- Sonatype: Security Management API and API Reference.
- Sonatype: Changes During the Upgrade Process — Nexus Repository 2 repository targets become Nexus Repository 3 content selectors.
- Self-Hosted Feature Matrix — Repo Targets / Content Selectors are Community and Pro capabilities.
- Sonatype: Database Options — coordinate-based selectors are deprecated with PostgreSQL and no longer supported.
- Sonatype verified Nexus Docker image tags.
Version-sensitive statements were rechecked on 2026-08-26. Sonatype's verified container registry exposes Nexus Repository 3.95.2 as the current latest image line. The mandatory chapter path uses Community-compatible content selectors, local users/roles, Raw hosted/group repositories, and the self-hosted security APIs. It does not require Pro user tokens, SAML/OIDC, HA, staging, Repository Firewall, or deployment to a group repository.
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.