security: retire unsafe legacy LDAP lifecycle callers
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
This commit is contained in:
parent
276b95f6ba
commit
84c5dbeec8
21 changed files with 390 additions and 1023 deletions
|
|
@ -1259,24 +1259,25 @@ def print_cleanup_evidence_template() -> None:
|
|||
|
||||
|
||||
def lifecycle_flow_template() -> dict[str, Any]:
|
||||
"""Blank evidence worksheet; every assurance requires actual owner evidence."""
|
||||
return {
|
||||
"flow_version": "v1",
|
||||
"operator": "platform-custodian",
|
||||
"implemented_as": "security-bootstrap-console guidance + existing sso-mfa/k8s scripts (lldap/create-user.sh, lldap/break-glass.sh, privacyidea/check-user-mfa-state.sh, privacyidea/repair-realm-live.sh) + direct LLDAP GraphQL for lock/offboard + console preview of effective access",
|
||||
"doc_reference": "docs/security-bootstrap-user-lifecycle.md + NET-WP-0017 workplan T05",
|
||||
"review_date": "2026-06-03",
|
||||
"effective_access_model": "Actor class + scope determines groups/roles in LLDAP + privacyIDEA + KeyCape claims. Non-root users get net-kingdom-users (or tenant-specific); fabric admins get scoped groups without net-kingdom-admins or platform policies. Previews list exact groups, no OpenBao root, no king credential.",
|
||||
"non_root_guardrail": "Creation and modification paths explicitly block granting net-kingdom-admins or platform-root equivalent unless actor=king (which is excluded for normal flows).",
|
||||
"audit_event_model": "All lifecycle actions produce non-secret progress/audit records via State Hub events or local evidence; no secrets in logs. LLDAP/PI changes are auditable via their own logs + NetKingdom progress.",
|
||||
"onboard_user_supported": True,
|
||||
"temporary_lock_supported": True,
|
||||
"permanent_offboard_supported": True,
|
||||
"credential_review_supported": True,
|
||||
"fabric_admin_supported": True,
|
||||
"shows_effective_access_before_save": True,
|
||||
"privileged_roles_require_mfa": True,
|
||||
"prevents_platform_root_grant": True,
|
||||
"no_secret_material_recorded": True,
|
||||
"flow_version": "v2",
|
||||
"operator": "",
|
||||
"implemented_as": "",
|
||||
"doc_reference": "docs/legacy-lldap-lifecycle-retirement.md",
|
||||
"review_date": "",
|
||||
"effective_access_model": "",
|
||||
"non_root_guardrail": "",
|
||||
"audit_event_model": "",
|
||||
"onboard_user_supported": False,
|
||||
"temporary_lock_supported": False,
|
||||
"permanent_offboard_supported": False,
|
||||
"credential_review_supported": False,
|
||||
"fabric_admin_supported": False,
|
||||
"shows_effective_access_before_save": False,
|
||||
"privileged_roles_require_mfa": False,
|
||||
"prevents_platform_root_grant": False,
|
||||
"no_secret_material_recorded": False,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1285,34 +1286,30 @@ def print_lifecycle_flow_template() -> None:
|
|||
|
||||
|
||||
def onboarding_dry_run_template() -> dict[str, Any]:
|
||||
"""Non-secret evidence template for NET-WP-0017-T06 onboarding dry run.
|
||||
Use after running the T05 lifecycle flow for a scoped non-root subject.
|
||||
Fill with actual results from LLDAP queries, create-user output, GraphQL
|
||||
lock/offboard, check scripts, KeyCape verify, etc. Must not contain secrets.
|
||||
"""
|
||||
"""Blank evidence worksheet; every assurance requires actual owner evidence."""
|
||||
return {
|
||||
"dry_run_date": "YYYY-MM-DD",
|
||||
"operator": "platform-custodian",
|
||||
"subject_reference": "dryrun-username (dryrun@example)",
|
||||
"actor_class": "user", # or "tenant-admin" / "fabric-admin". MUST NOT be "king credential"
|
||||
"tenant_scope": "none (or e.g. 'tenant:foo' for scoped)",
|
||||
"effective_access_summary": "LLDAP: only net-kingdom-users (or scoped). MFA self-enroll available. KeyCape OIDC claims: groups + email + sub (no platform-admin). No OpenBao root or platform policies. Lock/offboard exercised via group removal + delete.",
|
||||
"audit_progress_reference": "State Hub /progress/ id for T06 + this evidence file + LLDAP audit if enabled",
|
||||
"lock_offboard_result": "lock: removeUserFromGroup succeeded; offboard: deleteUser succeeded; post state: user absent, LLDAP clean",
|
||||
"post_dry_run_disposition": "Test subject fully removed with no residual access or groups. LLDAP users back to [admin, platform-root]. No plaintext left.",
|
||||
"lldap_identity_verified": True,
|
||||
"groups_verified": True,
|
||||
"mfa_enrollment_verified": True,
|
||||
"keycape_oidc_claims_verified": True,
|
||||
"expected_scope_verified": True,
|
||||
"no_platform_root_authority": True,
|
||||
"no_openbao_root_authority": True,
|
||||
"lock_path_exercised_or_simulated": True,
|
||||
"offboard_path_exercised_or_simulated": True,
|
||||
"credentials_reviewed": True,
|
||||
"audit_progress_recorded": True,
|
||||
"no_secret_material_recorded": True,
|
||||
"groups": ["net-kingdom-users"], # membership during the dry-run lifetime (before lock/offboard)
|
||||
"dry_run_date": "",
|
||||
"operator": "",
|
||||
"subject_reference": "",
|
||||
"actor_class": "",
|
||||
"tenant_scope": "",
|
||||
"effective_access_summary": "",
|
||||
"audit_progress_reference": "",
|
||||
"lock_offboard_result": "",
|
||||
"post_dry_run_disposition": "",
|
||||
"lldap_identity_verified": False,
|
||||
"groups_verified": False,
|
||||
"mfa_enrollment_verified": False,
|
||||
"keycape_oidc_claims_verified": False,
|
||||
"expected_scope_verified": False,
|
||||
"no_platform_root_authority": False,
|
||||
"no_openbao_root_authority": False,
|
||||
"lock_path_exercised_or_simulated": False,
|
||||
"offboard_path_exercised_or_simulated": False,
|
||||
"credentials_reviewed": False,
|
||||
"audit_progress_recorded": False,
|
||||
"no_secret_material_recorded": False,
|
||||
"groups": [],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1320,120 +1317,24 @@ def print_onboarding_dry_run_template() -> None:
|
|||
print(json.dumps(onboarding_dry_run_template(), indent=2))
|
||||
|
||||
|
||||
def print_dry_run_oidc_claims_verification(subject: str = "dryrun-user", groups: list[str] | None = None) -> None:
|
||||
"""T05 helper: non-secret OIDC claims verification for dry-run subject.
|
||||
Computes expected claims based on LLDAP groups (no secrets).
|
||||
Used by orchestrator and console 'onboarding-dry-run-claims' .
|
||||
"""
|
||||
if groups is None:
|
||||
groups = ["net-kingdom-users"]
|
||||
print("OIDC CLAIMS VERIFICATION FOR DRY-RUN (non-secret)")
|
||||
print(f"Subject: {subject}")
|
||||
print(f"LLDAP groups (during dry-run): {groups}")
|
||||
print("")
|
||||
print("Expected KeyCape OIDC claims (inferred from groups + standard config):")
|
||||
claims = {
|
||||
"sub": subject,
|
||||
"email": f"{subject}@example",
|
||||
"groups": groups,
|
||||
"preferred_username": subject,
|
||||
}
|
||||
print(json.dumps(claims, indent=2))
|
||||
print("")
|
||||
print("OpenBao policy binding (from T01 config): platform-admin role only binds 'net-kingdom-admins' group.")
|
||||
if "net-kingdom-admins" in groups:
|
||||
print("WARNING: would grant platform-admin (not for non-root dry-run)")
|
||||
else:
|
||||
print("No platform-admin or root authority (correct for non-root).")
|
||||
print("To verify live: after onboard, use the lifecycle dry-run script which exercises KeyCape verify + LLDAP.")
|
||||
def print_dry_run_oidc_claims_verification(subject: str = "dryrun-user", groups: list[str] | None = None) -> int:
|
||||
"""Compatibility refusal: inferred directory groups cannot verify issued claims."""
|
||||
print("Retired: inferred groups are not verified OIDC claims.")
|
||||
print("See docs/legacy-lldap-lifecycle-retirement.md; retain actual issuer and consumer evidence.")
|
||||
return 2
|
||||
|
||||
|
||||
def print_lifecycle_guide() -> None:
|
||||
print("NET-WP-0017-T05 LIFECYCLE OPERATOR FLOW GUIDE")
|
||||
print("")
|
||||
print("This implements the first practical operator flow per docs/security-bootstrap-user-lifecycle.md")
|
||||
print("All actions are non-secret. Secrets stay in password-safe / k8s secrets / operator memory.")
|
||||
print("Every privileged flow shows EFFECTIVE ACCESS PREVIEW before any create/save.")
|
||||
print("")
|
||||
print("=== 1. ONBOARD SCOPED NON-ROOT USER ===")
|
||||
print("Preview effective (do this mentally or via future UI card):")
|
||||
print(" - actor_class=platform-admin or tenant-admin or user")
|
||||
print(" - scope= (for tenant: e.g. 'tenant:foo')")
|
||||
print(" - groups: net-kingdom-users + optional scoped; NEVER net-kingdom-admins for non-root")
|
||||
print(" - MFA: always required for privileged; self-enroll at pink-account")
|
||||
print(" - KeyCape OIDC claims will include groups + email; no platform-root policy")
|
||||
print(" - OpenBao: no root, only if policy granted via group")
|
||||
print("")
|
||||
print("Command (run in operator shell; pass never echoed to console):")
|
||||
print(" cd sso-mfa/k8s/lldap")
|
||||
print(" KUBECTL=/home/worsch/.local/bin/kubectl ./create-user.sh \\")
|
||||
print(" <username> <email> \"Display Name\" # add --admin ONLY for platform admins")
|
||||
print(" # Then: user self-enrolls TOTP; verify with:")
|
||||
print(" cd ../privacyidea; KUBECTL=... ./check-user-mfa-state.sh <username>")
|
||||
print("")
|
||||
print("Blocked if: actor requests net-kingdom-admins without explicit king-credential path.")
|
||||
print("")
|
||||
print("=== 2. TEMPORARILY LOCK USER ===")
|
||||
print(" # Use LLDAP admin UI or GraphQL to remove from active groups temporarily")
|
||||
print(" # Or privacyIDEA: disable token for user (preserves history)")
|
||||
print(" # Preview: shows current groups, tokens, sessions before lock")
|
||||
print(" # Record: State Hub note or evidence with unlock date")
|
||||
print(" # Reversible by re-adding group / enabling token")
|
||||
print("")
|
||||
print("=== 3. PERMANENTLY OFFBOARD ===")
|
||||
print(" # 1. Select user, require reason+date")
|
||||
print(" # 2. Transfer resources (manual for now)")
|
||||
print(" # 3. Revoke: use LLDAP to remove groups; privacyIDEA disable+delete tokens if needed")
|
||||
print(" # 4. For KeyCape/Authelia sessions: rely on token expiry + group removal")
|
||||
print(" # Record non-secret offboard evidence (see T06 validator)")
|
||||
print(" # Platform-admin offboard needs second human confirm")
|
||||
print("")
|
||||
print("=== 4. REVIEW CREDENTIALS / MFA STATE ===")
|
||||
print(" cd sso-mfa/k8s/privacyidea")
|
||||
print(" ./check-user-mfa-state.sh <username>")
|
||||
print(" # Also: LLDAP GraphQL query for groups/members; manual review of owned principals")
|
||||
print(" # Actions: rotate (creds-rotate make), change groups via create-user or LLDAP UI")
|
||||
print("")
|
||||
print("=== 5. CREATE FABRIC/TENANT ADMIN (no platform-root) ===")
|
||||
print(" # Same as onboard, but:")
|
||||
print(" # - actor_class = tenant-admin or fabric-admin")
|
||||
print(" # - scope = tenant:xxx or fabric:yyy")
|
||||
print(" # - groups: scoped group only (create via LLDAP if needed)")
|
||||
print(" # - NO --admin flag")
|
||||
print(" # - Preview explicitly: 'will NOT be in net-kingdom-admins; no OpenBao platform/ policy'")
|
||||
print(" # Handover: produce checklist (name fabric, assign admin, audit expectations)")
|
||||
print("")
|
||||
print("All flows record non-secret audit via: console evidence, State Hub /progress/, or k8s audit.")
|
||||
print("See also: create-user.sh --help , break-glass.sh for recovery accounts (king only).")
|
||||
print("Next gate: T06 dry-run using this flow + evidence.")
|
||||
print("")
|
||||
|
||||
|
||||
print("=== T06 DRY-RUN EXECUTION (adjacent polish) ===")
|
||||
print("Use after the above onboard/lock/offboard steps for a scoped non-root subject.")
|
||||
print("This makes the T06 gate repeatable and less manual. See docs/smooth-bootstrap-guide.md Step 7 and NET-WP-0019.")
|
||||
print("")
|
||||
print("Preferred: one-command via make (handles /tmp safe workspace + trap + k8s fallback for secrets, never persistent bootstrap/secrets for dry-runs):")
|
||||
print(" make security-bootstrap-onboarding-dry-run SUBJECT=<username> EMAIL=<email> DISPLAY=\"<name>\"")
|
||||
print("")
|
||||
print("Or direct script:")
|
||||
print(" cd sso-mfa/k8s/lldap")
|
||||
print(" ./dry-run-nonroot-user.sh <username> <email> \"Display Name\" [--actor user] [--scope none]")
|
||||
print(" # It will: safe secret extract (/tmp + trap), create --test, verifs (MFA/KeyCape), optional lock/offboard, emit /tmp/.../evidence.json, cleanup.")
|
||||
print("")
|
||||
print("Validate evidence:")
|
||||
print(" make security-bootstrap-validate-onboarding-dry-run")
|
||||
print("")
|
||||
print("Claims verification (non-secret, infers from LLDAP groups + T01 role):")
|
||||
print(" python3 tools/security-bootstrap-console/security_bootstrap_console.py onboarding-dry-run-claims --subject <username> --groups net-kingdom-users")
|
||||
print("")
|
||||
print("Cleanup pattern-matched test users:")
|
||||
print(" make security-bootstrap-lifecycle-cleanup-dryrun-users PATTERN=t06-*")
|
||||
print(" # Or console 'lifecycle-cleanup-dryrun-users --pattern t06-*'")
|
||||
print("")
|
||||
print("All steps leave LLDAP clean, no plaintext secrets on disk, evidence with required bools (effective before save, no root grant, verified identity/claims, actor_class != king, etc.). See console lifecycle-flow-template / onboarding-dry-run-template and smooth-bootstrap-guide.md.")
|
||||
print("See workplan T06/0019 notes for the 2026-06-03 execution.")
|
||||
print("")
|
||||
print("USER LIFECYCLE OWNER ROUTES")
|
||||
print("Ordinary onboarding: use User Engine registration for an admitted client and tenant.")
|
||||
print("Identity provisioning and single-use password setup stay with the IAM provider.")
|
||||
print("User Engine owns account lifecycle and product memberships; IAM owns coarse authentication groups.")
|
||||
print("Tenant lifecycle changes must preserve other tenants and global identity state.")
|
||||
print("Platform-root bootstrap and offline recovery require their separate owner-reviewed ceremony.")
|
||||
print("Legacy account creation, pattern cleanup and inferred-claims commands are retired.")
|
||||
print("Record actual identity, issued claims, effective access, denial and exact cleanup receipts.")
|
||||
print("Blank templates are unverified; structural validation alone is not native acceptance.")
|
||||
print("See docs/legacy-lldap-lifecycle-retirement.md for contracts and remaining recovery work.")
|
||||
|
||||
|
||||
def compact_command_output(text: str) -> str:
|
||||
|
|
@ -3003,12 +2904,12 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
|||
),
|
||||
{
|
||||
"name": "User lifecycle dry-run (T06)",
|
||||
"description": "Execute non-root onboarding dry-run (create/verify/lock/offboard), check LLDAP/groups/MFA/KeyCape claims, produce evidence. See NET-WP-0019 and dry-run-nonroot-user.sh .",
|
||||
"subsystem": "LLDAP / privacyIDEA / KeyCape",
|
||||
"description": "Retired: use User Engine and IAM owner flows with actual consumer evidence; no pattern deletion or inferred-claims verification.",
|
||||
"subsystem": "User Engine / IAM",
|
||||
"responsibility": "platform-custodian",
|
||||
"email": role_email(data, "role_platform_custodian_email"),
|
||||
"location": "sso-mfa/k8s/lldap/dry-run-nonroot-user.sh ; make security-bootstrap-onboarding-dry-run ; console 'onboarding-dry-run' ; NET-WP-0019",
|
||||
"state": "template",
|
||||
"location": "docs/legacy-lldap-lifecycle-retirement.md",
|
||||
"state": "retired",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -5123,12 +5024,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
sub.add_parser("custody-roster-template", help="Print non-secret custody roster JSON template.")
|
||||
sub.add_parser("cleanup-evidence-template", help="Print non-secret NET-WP-0017-T03/T04 cleanup/taint evidence JSON template.")
|
||||
sub.add_parser("lifecycle-flow-template", help="Print non-secret NET-WP-0017-T05 lifecycle operator flow evidence JSON template.")
|
||||
sub.add_parser("lifecycle-guide", help="Print the practical T05 operator flow guide with commands and previews (no secrets).")
|
||||
sub.add_parser("lifecycle-guide", help="Print current User Engine and IAM lifecycle owner guidance.")
|
||||
sub.add_parser("onboarding-dry-run-template", help="Print non-secret NET-WP-0017-T06 onboarding dry-run evidence JSON template (skeleton for T06 evidence).")
|
||||
sub.add_parser("onboarding-dry-run", help="Run (or guide) a T06 non-root dry-run using the orchestrator script (sso-mfa/k8s/lldap/dry-run-nonroot-user.sh). See NET-WP-0019.")
|
||||
cl = sub.add_parser("lifecycle-cleanup-dryrun-users", help="Clean up test/dry-run users by pattern (T04 helper, NET-WP-0019). Example: --pattern t06-*")
|
||||
cl.add_argument("--pattern", default="t06-*", help="Regex or glob pattern for test users to offboard (default t06-*)")
|
||||
claims = sub.add_parser("onboarding-dry-run-claims", help="T05: Verify expected OIDC claims for a dry-run subject (non-secret, based on groups). See NET-WP-0019-T05.")
|
||||
sub.add_parser("onboarding-dry-run", help="Retired; prints current lifecycle owner guidance and exits with failure.")
|
||||
cl = sub.add_parser("lifecycle-cleanup-dryrun-users", help="Retired; prints current lifecycle owner guidance and exits with failure.")
|
||||
cl.add_argument("--pattern", default="t06-*", help="Ignored compatibility argument; pattern deletion is retired")
|
||||
claims = sub.add_parser("onboarding-dry-run-claims", help="Retired; prints current lifecycle owner guidance and exits with failure.")
|
||||
claims.add_argument("--subject", default="dryrun-user", help="Test subject username")
|
||||
claims.add_argument("--groups", default="net-kingdom-users", help="Comma-separated groups during dry-run")
|
||||
sub.add_parser("handover-checklist", help="Print handover and cleanup checklist.")
|
||||
|
|
@ -5191,6 +5092,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
if args.command in {"onboarding-dry-run", "lifecycle-cleanup-dryrun-users", "onboarding-dry-run-claims"}:
|
||||
print("Retired: legacy LLDAP lifecycle automation and inferred-claims verification.")
|
||||
print("See docs/legacy-lldap-lifecycle-retirement.md for supported owner routes.")
|
||||
return 2
|
||||
metadata_commands = {
|
||||
"status",
|
||||
"validate-king-kit",
|
||||
|
|
@ -5255,37 +5160,6 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if args.command == "onboarding-dry-run-template":
|
||||
print_onboarding_dry_run_template()
|
||||
return 0
|
||||
if args.command == "onboarding-dry-run":
|
||||
print("NET-WP-0019 / T06 Dry-Run (orchestrator)")
|
||||
print("Run the script directly for full automation:")
|
||||
print(" cd sso-mfa/k8s/lldap")
|
||||
print(" ./dry-run-nonroot-user.sh <username> <email> \"Display Name\" [--actor user] [--scope none]")
|
||||
print("")
|
||||
print("It will:")
|
||||
print(" - safely extract admin pass into /tmp (trap cleanup)")
|
||||
print(" - create the user (non-root)")
|
||||
print(" - verify LLDAP/groups, MFA readiness, KeyCape OIDC path")
|
||||
print(" - exercise lock (remove group) + offboard (delete)")
|
||||
print(" - emit /tmp/netkingdom-onboarding-dry-run/evidence.json (from template)")
|
||||
print(" - clean up")
|
||||
print("")
|
||||
print("Then validate:")
|
||||
print(" make security-bootstrap-validate-onboarding-dry-run")
|
||||
print("")
|
||||
print("See also: make security-bootstrap-lifecycle-guide (T06 section) and the NET-WP-0019 workplan.")
|
||||
return 0
|
||||
if args.command == "lifecycle-cleanup-dryrun-users":
|
||||
pat = getattr(args, "pattern", "t06-*") if hasattr(args, "pattern") else "t06-*"
|
||||
print("Delegating cleanup for pattern", pat, "to orchestrator...")
|
||||
import subprocess, os
|
||||
script = os.path.join(os.path.dirname(__file__), "..", "..", "sso-mfa", "k8s", "lldap", "dry-run-nonroot-user.sh")
|
||||
script = os.path.abspath(script)
|
||||
subprocess.call(["bash", script, "--cleanup-only", pat])
|
||||
return 0
|
||||
if args.command == "onboarding-dry-run-claims":
|
||||
groups = getattr(args, "groups", "net-kingdom-users").split(",")
|
||||
print_dry_run_oidc_claims_verification(getattr(args, "subject", "dryrun-user"), groups)
|
||||
return 0
|
||||
if args.command == "handover-checklist":
|
||||
print_handover_checklist()
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue