Chapter 16Lesson 02220–300 min

LDAP, SAML, OIDC, External Identity, Credential Rotation, and Authentication Troubleshooting: Guided Hands-On Workflow and Core Operations

Inspect a disposable Nexus instance, model LDAP/SAML/OIDC identities with local fixtures, prove group-to-role mapping, and rehearse credential and certificate rotation without requiring an enterprise IdP.

Identity fixturesRole mappingCredential rotationCertificatesCommunity lab

Learning objectives

  • Inspect edition and external-realm availability without changing it.
  • Create synthetic LDAP, SAML, and OIDC identity fixtures safely.
  • Map external groups/claims to narrow Nexus role IDs.
  • Simulate bind-secret and certificate rotation with no live enterprise dependency.
  • Interpret failed-login evidence and choose the smallest correction.

Safety invariant: the local recovery path remains untouched throughout the mandatory lab; the real Nexus external-realm configuration is inspected read-only while LDAP, SAML, and OIDC behavior is modeled with synthetic fixtures.

Lab scope. Mandatory work is Community/free-compatible. The running Nexus instance is used read-only for identity-state inspection. LDAP/SAML/OIDC records are local synthetic fixtures under a temporary directory. No corporate directory, IdP, DNS, TLS, database, blob store, or production realm is modified.

1. Preflight and assumptions

Reference baseline: self-hosted Nexus Repository 3.95.0, Java 21, loopback access, disposable H2 for a learning instance only. Production deployments should follow current Sonatype PostgreSQL guidance. Record the actual version/edition first.

export NX_URL="http://127.0.0.1:8081"
export LAB="$(mktemp -d)"
mkdir -p "$LAB/evidence" "$LAB/fixtures" "$LAB/certs"
chmod 700 "$LAB"
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
' "$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/realms/active" > "$LAB/evidence/realms-before.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/security/roles" > "$LAB/evidence/roles-before.json"

2. Inspect LDAP/SAML/OIDC availability before assuming it

Open Settings → Security → Realms. On Community, LDAP should be an available supported external identity source; SAML/OIDC self-hosted configuration is a Pro boundary. On a Pro disposable instance, SAML appears under Security and OIDC/OAuth2 configuration is available on supported versions.

Use the feature matrix and the running license state as evidence. Do not infer entitlement merely because a documentation page or REST path exists. For OIDC, current Security Management API documentation lists GET/PUT/DELETE /service/rest/v1/security/oauth2 for self-hosted Pro.

3. Build one identity story in three protocol shapes

The same person and group can appear differently in each protocol. Our synthetic principal is ada; the external group is nexus-academy-readers; the intended Nexus role ID is academy-ch16-reader.

cat > "$LAB/fixtures/ldap.json" <<'JSON'
{
  "bind_dn": "cn=nexus-reader,ou=svc,dc=example,dc=invalid",
  "user_search_base": "ou=people,dc=example,dc=invalid",
  "user": {
    "dn": "uid=ada,ou=people,dc=example,dc=invalid",
    "uid": "ada",
    "mail": "ada@example.invalid",
    "groups": ["nexus-academy-readers"]
  }
}
JSON
cat > "$LAB/fixtures/saml.json" <<'JSON'
{
  "subject": "ada",
  "audience": "https://nexus.example.invalid/service/rest/v1/security/saml/metadata",
  "acs": "https://nexus.example.invalid/saml",
  "attributes": {"groups": ["nexus-academy-readers"]}
}
JSON
cat > "$LAB/fixtures/oidc.json" <<'JSON'
{
  "iss": "https://id.example.invalid/realms/academy",
  "preferred_username": "ada",
  "email": "ada@example.invalid",
  "groups": ["nexus-academy-readers"]
}
JSON
cat > "$LAB/fixtures/mapping.json" <<'JSON'
{
  "external_group": "nexus-academy-readers",
  "nexus_role": "academy-ch16-reader",
  "privileges": ["nx-repository-view-raw-academy-ch16-fixture-browse", "nx-repository-view-raw-academy-ch16-fixture-read"]
}
JSON

These files contain no live secret or token. They teach protocol shape: LDAP group membership is discovered through directory search; SAML carries attributes inside an assertion; OIDC carries configured claims inside signed tokens/user info.

4. Validate mapping deterministically

python - <<'PY'
import json, os
from pathlib import Path
root = Path(os.environ['LAB']) / 'fixtures'
ldap = json.loads((root/'ldap.json').read_text())
saml = json.loads((root/'saml.json').read_text())
oidc = json.loads((root/'oidc.json').read_text())
map_ = json.loads((root/'mapping.json').read_text())
want = map_['external_group']
rows = {
  'LDAP': ldap['user']['groups'],
  'SAML': saml['attributes']['groups'],
  'OIDC': oidc['groups'],
}
for proto, groups in rows.items():
    mapped = map_['nexus_role'] if want in groups else '<none>'
    print(f'{proto}: user=ada groups={groups} -> role={mapped}')
PY

Real Nexus external role mapping performs the same conceptual join: the exact external group/role name must match the configured Nexus mapping. Case, naming, and provider output matter. The mapping grants authorization only after authentication has produced that group.

5. Model LDAP bind → search → groups

LDAP: connection is only the first checkpoint
flowchart TD
N[Nexus LDAP realm] --> B[Bind as configured identity]
B --> U[Search user base and filter]
U --> G[Resolve group membership]
G --> M[Map external group to Nexus role]
M --> P[Evaluate repository privilege]

A successful bind proves Nexus can authenticate its directory query identity. It does not prove the end-user base DN, user filter, group base, group object class, member attribute, or username attribute is correct. Current Nexus provides LDAP connection/user/group verification in the LDAP configuration workflow and caches authentication information.

Optional free extension: if you already have a disposable OpenLDAP container, configure it only on loopback/private networking and use synthetic DNs such as dc=example,dc=invalid. Do not connect this course lab to a corporate directory.

6. Simulate LDAP bind-secret rotation

We can model rotation without recording a real password. Store only hashes of two fake randomly generated secret values and validate the phase transition:

OLD_BIND_SECRET="$(python - <<'PY'
import secrets
print(secrets.token_urlsafe(24))
PY
)"
NEW_BIND_SECRET="$(python - <<'PY'
import secrets
print(secrets.token_urlsafe(24))
PY
)"
export OLD_BIND_HASH="$(printf '%s' "$OLD_BIND_SECRET" | sha256sum | awk '{print $1}')"
export NEW_BIND_HASH="$(printf '%s' "$NEW_BIND_SECRET" | sha256sum | awk '{print $1}')"
unset OLD_BIND_SECRET NEW_BIND_SECRET
printf 'old_hash=%s
new_hash=%s
' "$OLD_BIND_HASH" "$NEW_BIND_HASH" > "$LAB/evidence/bind-rotation-hashes.txt"

In a real rotation, change the directory-side service credential and Nexus configuration in a staged sequence that preserves a testable overlap if your directory policy allows it. Verify bind, user search, and group resolution after the cutover. The evidence packet should prove which version/phase succeeded—not contain either secret.

7. Simulate certificate rollover

openssl req -x509 -newkey rsa:2048 -nodes -days 1   -subj '/CN=id-old.example.invalid'   -keyout "$LAB/certs/old.key" -out "$LAB/certs/old.crt" >/dev/null 2>&1
openssl req -x509 -newkey rsa:2048 -nodes -days 1   -subj '/CN=id-new.example.invalid'   -keyout "$LAB/certs/new.key" -out "$LAB/certs/new.crt" >/dev/null 2>&1
openssl x509 -in "$LAB/certs/old.crt" -noout -fingerprint -sha256 > "$LAB/evidence/cert-old.txt"
openssl x509 -in "$LAB/certs/new.crt" -noout -fingerprint -sha256 > "$LAB/evidence/cert-new.txt"

For LDAPS, current Nexus can trust certificates added through the Nexus SSL Certificates UI; new certificates added there begin being used for outbound connections without a Nexus restart. For SAML, signing-key renewal requires updated IdP metadata. For OIDC, JWK rotation is normally discovered through the provider's configured key endpoint; private-CA HTTPS may require the Nexus truststore on supported Pro versions.

8. Create failure evidence, not mystery symptoms

cat > "$LAB/fixtures/failures.log" <<'LOG'
LDAP  bind=success user_search=0 base="ou=users,dc=wrong,dc=invalid" user="ada"
SAML  idp_login=success audience="https://wrong.example.invalid" acs="https://nexus.example.invalid/saml" cookie=SAML2_AUTH_REQUEST:missing
OIDC  issuer="https://id.example.invalid/realms/wrong" redirect="https://nexus.example.invalid/oidc/callback" groups_claim="roles" observed_groups=[]
HTTP  status=429 retry_after=30 auth=basic principal="ch16-fixture-user"
LOG
cp "$LAB/fixtures/failures.log" "$LAB/evidence/failures.log"

The first line is not an LDAP network failure: bind worked, then search found no user. The SAML line tells you to investigate audience/ACS/cookie state, not repository privileges. The OIDC line points at issuer and claim mapping. The 429 line is authentication throttling, not TCP outage.

9. Challenge: choose the control

A CI pipeline must publish packages noninteractively, while developers should use browser SSO for the Nexus UI. Which control should you change?

Do not broaden a browser SSO session into CI. Keep human SSO and package-client credentials separate. On Community, use a dedicated local or LDAP-backed service identity with narrow repository privileges. On Pro, a supported User Token may be appropriate for a SAML-authenticated user, but it still carries that principal's Nexus authorization.

10. Cleanup

rm -f "$NX_AUTH_FILE"
unset NX_AUTH_FILE NX_USER OLD_BIND_HASH NEW_BIND_HASH
rm -rf "$LAB"

The lab never mutated external-realm configuration, so there is no Nexus realm rollback. If you chose the optional live LDAP extension, remove only the disposable LDAP server/configuration you created and confirm the original active-realm list is restored exactly.

11. Knowledge check

Why is a synthetic identity fixture useful?

What does a successful LDAP bind fail to prove?

What should a credential-rotation evidence packet contain?

If a SAML user can sign in but cannot read a repository, which layer is next?

Why should the lab not enable SAML/OIDC on Community?

12. Next step

Lesson 3 turns the mechanics into architecture decisions: which people use centralized SSO, which service identities remain noninteractive, how group mapping affects coupling, how to fail closed, and how to preserve recovery without leaving a daily-use admin backdoor.

Official references and version notes

Version-sensitive statements were rechecked on 2026-08-26. The current official direct-download page lists Nexus Repository 3.95.0. Nexus 3.87+ on H2/PostgreSQL requires Java 21. The mandatory learning path is Community-compatible and models external identity with local fixtures; SAML and OIDC are optional Pro-only integrations for self-hosted Nexus.

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.