Skip to content

Add IdLE.Step.PruneEntitlements (agent-safe entitlement convergence / bulk removal) #236

@blindzero

Description

@blindzero

Problem Statement

In leaver (and some mover) workflows we must remove all entitlements (e.g., group memberships) from an identity except:

  • the provider/system default entitlement(s) that must always remain (e.g., AD Domain Users / primary group handling)
  • one or more custom “keep” entitlements (allowlist)
  • optionally additional “keep” entitlements identified by a pattern (agent-safe, wildcard-based)

With the current step model, IdLE.Step.EnsureEntitlement is intentionally atomic (one entitlement → desired state). Implementing “remove all except …” would require external host logic to:

  1. list current entitlements,
  2. compute a delta set,
  3. emit many EnsureEntitlement -State Absent steps.

This breaks portability and pushes essential lifecycle logic out of IdLE workflows.

Proposed Solution

Introduce a new provider-agnostic step type:

  • StepType: IdLE.Step.PruneEntitlements
  • Purpose: Converge the identity’s entitlements by removing all non-kept entitlements (“prune”), optionally ensuring the explicitly kept entitlements are present.

Capability gating (explicit provider opt-in)

Add a dedicated capability so providers can explicitly opt in:

  • Capability: IdLE.Entitlement.Prune

Reason: while prune can be composed from List + Revoke (+ Grant), we explicitly want providers to decide whether they support a bulk destructive operation.

Parameters (agent-safe)

  • -Provider (string, required)
  • -Identity (Idle identity reference, required)
  • -Kind (string, required)
    • entitlement kind, e.g. Group, Role, License (provider-defined)
  • -Keep (array, optional)
    • explicit entitlement references to keep (same shape as EnsureEntitlement uses for a single entitlement)
  • -KeepPattern (array of string, optional)
    • wildcard patterns only (PowerShell -like semantics), used to keep entitlements matching the pattern
    • patterns must be treated as agent-safe (no regex, no scriptblocks)
  • -EnsureKeepEntitlements (switch, optional)
    • if set, ensure entitlements from -Keep are present (grant missing)

Guardrails / Validation

  • At least one of -Keep or -KeepPattern must be provided.
  • -KeepPattern supports wildcard matching only (e.g. LEAVER-*, CN=LEAVER-*,OU=Groups,DC=...).
  • -EnsureKeepEntitlements applies only to explicit -Keep items (patterns cannot be “ensured”).
  • Provider must handle “non-removable” entitlements safely (e.g., AD primary group / Domain Users). The step must not fail the whole workflow for these; instead, it should:
    • skip with a structured warning event and continue
    • include the skipped reason in plan/export and result

Provider contract usage

The step is provider-agnostic and uses existing entitlement primitives, plus the new capability flag:

  • required provider ops:
    • ListEntitlements(identity, kind)
    • RevokeEntitlement(identity, entitlement, kind)
  • conditional provider op (only if -EnsureKeepEntitlements):
    • GrantEntitlement(identity, entitlement, kind)

Planned behavior (idempotent)

  1. List current entitlements (for Kind).
  2. Build keep-set from:
    • explicit Keep
    • any current entitlements matching any KeepPattern
    • provider/system “always keep” (e.g., AD primary group semantics)
  3. Compute remove-set = current − keep-set.
  4. Revoke each entitlement in remove-set.
  5. If EnsureKeepEntitlements is set:
    • compute missing-set = Keep − current
    • grant each entitlement in missing-set
  6. Emit structured events for:
    • plan intent (kept, pruned, ensured)
    • each revoke/grant action
    • skipped items (non-removable, permission denied, etc.)

Alternatives Considered

A) Extend IdLE.Step.EnsureEntitlement with -RemoveAllExcept / -KeepPattern

Rejected. It blurs semantics of an atomic step, increases risk of accidental destructive behavior, complicates validation/modes, and makes docs harder (“EnsureEntitlement sometimes means prune”).

B) Host-only delta computation (build many EnsureEntitlement steps externally)

Rejected. Breaks portability: the core lifecycle semantics live outside IdLE workflows and differs per host.

C) Provider-specific step (e.g., AD-only prune group memberships)

Rejected (for now). We want a generic capability and step. Providers can opt in/out via capability.

Impact

  • Backwards compatibility: No breaking changes. New step + new capability only.
  • Existing workflows: Unaffected unless users adopt the new step.
  • Provider implementations: Providers can ignore this until they opt-in by exposing IdLE.Entitlement.Prune and implementing the required primitives consistently.

Additional Context

Primary example (AD Leaver)

Remove all group memberships except:

  • Domain Users / primary group related membership behavior (provider/system keep)
  • one custom leaver group (explicit keep)
  • optionally groups matching LEAVER-*

Example intent (pseudo):

  • Kind: Group
  • Keep: [ "CN=LEAVER-RETAIN,OU=Groups,DC=contoso,DC=com" ]
  • KeepPattern: [ "CN=LEAVER-*,OU=Groups,DC=contoso,DC=com" ]
  • EnsureKeepEntitlements: $true

Acceptance Criteria

  • StepType IdLE.Step.PruneEntitlements exists and is discoverable via metadata/catalog.
  • New capability IdLE.Entitlement.Prune exists and is used for gating.
  • Parameter validation enforces agent-safe patterns (wildcards only) and requires at least one keep rule.
  • Step is idempotent and produces stable results across repeated runs.
  • Plan export includes keep-set, prune-set, and optional ensure-set.
  • Unit tests exist (Pester) for:
    • keep only
    • keep + keepPattern union
    • ensureKeepEntitlements
    • “non-removable” entitlement behavior (skip + warn)
    • validation failures (no keep rules)
  • Documentation page for the new StepType exists and follows the StepType doc template.
  • Examples added/updated to show a leaver workflow pruning group entitlements safely.

Security / Safety Notes

  • Pattern matching must use wildcard -like semantics only; no regex and no scriptblocks.
  • Step must be explicit about destructive behavior (“removes all except …”).
  • Emit clear audit events: what was removed, what was kept, what was skipped and why.

Metadata

Metadata

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions