Chapter 14Lesson 02220–290 min

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.

Raw labPath policyRolesDirect vs groupAuthorization tests

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?

Why is delete omitted from the team roles?

What should a failed Team A write to /team-b/ cause you to inspect first?

Why use Raw in this chapter?

What does Content Selector Preview prove?

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

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.

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