Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ If anything in this file conflicts with those, the more specific document wins.
## Documentation rules

- Do not edit generated references under `docs/reference/` by hand.
- Regenerate via the repository tools as documented in `CONTRIBUTING.md`.
- **Always regenerate** after changing public cmdlets or step implementations:
- `./tools/Generate-IdleCmdletReference.ps1` - after cmdlet/help changes
- `./tools/Generate-IdleStepReference.ps1` - after step changes
- See `CONTRIBUTING.md` for complete instructions.
- CI will fail if generated docs are out of date.

## Git / PR conventions

Expand Down
121 changes: 116 additions & 5 deletions docs/reference/provider-ad.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,83 @@ $plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Provider
}
```

### With Explicit Credentials
### AuthSessionBroker-based Authentication

Use an AuthSessionBroker to manage authentication centrally and enable multi-role scenarios.

**Simple approach with New-IdleAuthSessionBroker:**

```powershell
$credential = Get-Credential
$provider = New-IdleADIdentityProvider -Credential $credential
# Assuming you have credentials available (e.g., from a secure vault or credential manager)
$tier0Credential = Get-Credential -Message "Enter Tier0 admin credentials"
$adminCredential = Get-Credential -Message "Enter regular admin credentials"

# Create provider
$provider = New-IdleADIdentityProvider

# Create broker with role-based credential mapping
$broker = New-IdleAuthSessionBroker -SessionMap @{
@{ Role = 'Tier0' } = $tier0Credential
@{ Role = 'Admin' } = $adminCredential
} -DefaultCredential $adminCredential

# Use provider with broker
$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{
Identity = $provider
AuthSessionBroker = $broker
}
```

**Custom broker for advanced scenarios:**

For advanced scenarios (vault integration, MFA, dynamic credential retrieval), implement a custom broker:

```powershell
$broker = [pscustomobject]@{}
$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value {
param($Name, $Options)
# Custom logic: retrieve from vault, prompt for MFA, etc.
if ($Options.Role -eq 'Tier0') {
return Get-SecretFromVault -Name 'AD-Tier0'
}
return Get-SecretFromVault -Name 'AD-Admin'
}
```

In workflow definitions, steps specify which auth context to use via `AuthSessionOptions`:

```powershell
@{
Type = 'IdLE.Step.EnsureAttribute'
Name = 'SetPrivilegedAttribute'
With = @{
IdentityKey = 'user@domain.com'
Name = 'AdminCount'
Value = 1
AuthSessionName = 'ActiveDirectory'
AuthSessionOptions = @{ Role = 'Tier0' } # Broker returns Tier0 credential
}
}

@{
Type = 'IdLE.Step.EnsureAttribute'
Name = 'SetDepartment'
With = @{
IdentityKey = 'user@domain.com'
Name = 'Department'
Value = 'IT'
AuthSessionName = 'ActiveDirectory'
AuthSessionOptions = @{ Role = 'Admin' } # Broker returns Admin credential
}
}
```

**Key points:**
- The `Role` key (or any other key) is **defined by you** - it's not a built-in keyword
- Your broker implementation decides how to interpret `AuthSessionOptions`
- The broker can use any logic you want: hashtable lookups, vault APIs, interactive prompts, etc.
- `AuthSessionOptions` must be data-only (no ScriptBlocks) for security

### With Delete Capability (Opt-in)

By default, the Delete capability is **not** advertised for safety. Enable it explicitly:
Expand All @@ -114,13 +184,54 @@ $provider = New-IdleADIdentityProvider -AllowDelete

### Multi-Provider Scenarios

For scenarios with multiple AD forests or domains, use provider aliases with the AuthSessionBroker:

```powershell
$sourceAD = New-IdleADIdentityProvider -Credential $sourceCred
$targetAD = New-IdleADIdentityProvider -Credential $targetCred -AllowDelete
# Assuming you have credentials for each domain
$sourceCred = Get-Credential -Message "Enter Source AD admin credentials"
$targetCred = Get-Credential -Message "Enter Target AD admin credentials"

# Create providers for different AD environments
$sourceAD = New-IdleADIdentityProvider
$targetAD = New-IdleADIdentityProvider -AllowDelete

# Use New-IdleAuthSessionBroker for domain-based credential routing
$broker = New-IdleAuthSessionBroker -SessionMap @{
@{ Domain = 'Source' } = $sourceCred
@{ Domain = 'Target' } = $targetCred
}

$plan = New-IdlePlan -WorkflowPath './migration.psd1' -Request $request -Providers @{
SourceAD = $sourceAD
TargetAD = $targetAD
AuthSessionBroker = $broker
}
```

Workflow steps specify which domain to authenticate against:

```powershell
@{
Type = 'IdLE.Step.GetIdentity'
Name = 'ReadSource'
With = @{
IdentityKey = 'user@source.com'
Provider = 'SourceAD'
AuthSessionName = 'ActiveDirectory'
AuthSessionOptions = @{ Domain = 'Source' }
}
}

@{
Type = 'IdLE.Step.CreateIdentity'
Name = 'CreateTarget'
With = @{
IdentityKey = 'user@target.com'
Attributes = @{ ... }
Provider = 'TargetAD'
AuthSessionName = 'ActiveDirectory'
AuthSessionOptions = @{ Domain = 'Target' }
}
}
```

Expand Down
56 changes: 56 additions & 0 deletions docs/reference/steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ and returns an object with properties 'IdentityKey' and 'Changed'.
The step is idempotent by design: if the identity already exists, the provider
should return Changed = $false without creating a duplicate.

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider method
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

| Key | Required |
Expand Down Expand Up @@ -62,6 +70,14 @@ IMPORTANT: This step requires the provider to advertise the IdLE.Identity.Delete
capability, which is typically opt-in for safety. The provider must be configured
to allow deletion (e.g., AllowDelete = $true for AD provider).

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider method
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

_Unknown (not detected automatically). Document required With.* keys in the step help and/or use a supported pattern._
Expand Down Expand Up @@ -89,6 +105,14 @@ and returns an object with properties 'IdentityKey' and 'Changed'.
The step is idempotent by design: if the identity is already disabled, the provider
should return Changed = $false.

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider method
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

_Unknown (not detected automatically). Document required With.* keys in the step help and/or use a supported pattern._
Expand Down Expand Up @@ -140,6 +164,14 @@ and returns an object with properties 'IdentityKey' and 'Changed'.
The step is idempotent by design: if the identity is already enabled, the provider
should return Changed = $false.

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider method
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

_Unknown (not detected automatically). Document required With.* keys in the step help and/or use a supported pattern._
Expand Down Expand Up @@ -167,6 +199,14 @@ contains a boolean property 'Changed'.

The step is idempotent by design: it converges state to the desired value.

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider method
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

| Key | Required |
Expand Down Expand Up @@ -202,6 +242,14 @@ via `Context.Providers[<ProviderAlias>]` that implements:
The step is idempotent and only calls Grant/Revoke when the assignment needs
to change.

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider methods
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

| Key | Required |
Expand Down Expand Up @@ -233,6 +281,14 @@ and returns an object with properties 'IdentityKey' and 'Changed'.
The step is idempotent by design: if the identity is already in the target container,
the provider should return Changed = $false.

Authentication:
- If With.AuthSessionName is present, the step acquires an auth session via
Context.AcquireAuthSession(Name, Options) and passes it to the provider method
if the provider supports an AuthSession parameter.
- With.AuthSessionOptions (optional, hashtable) is passed to the broker for
session selection (e.g., @{ Role = 'Tier0' }).
- ScriptBlocks in AuthSessionOptions are rejected (security boundary).

**Inputs (With.\*)**

| Key | Required |
Expand Down
3 changes: 2 additions & 1 deletion src/IdLE.Core/IdLE.Core.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ Export-ModuleMember -Function @(
'Test-IdleWorkflowDefinitionObject',
'New-IdlePlanObject',
'Invoke-IdlePlanObject',
'Export-IdlePlanObject'
'Export-IdlePlanObject',
'New-IdleAuthSessionBroker'
) -Alias @()
116 changes: 116 additions & 0 deletions src/IdLE.Core/Public/New-IdleAuthSessionBroker.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
function New-IdleAuthSessionBroker {
<#
.SYNOPSIS
Creates a simple AuthSessionBroker for use with IdLE providers.

.DESCRIPTION
Creates an AuthSessionBroker that routes authentication based on user-defined options.
The broker is used by steps to acquire credentials at runtime without embedding
secrets in workflows or provider construction.

This is a convenience function for common scenarios. For advanced scenarios
(vault integration, MFA, etc.), implement a custom broker object with an
AcquireAuthSession method.

.PARAMETER SessionMap
A hashtable that maps session configurations to credentials. Each key is a hashtable
representing the AuthSessionOptions pattern, and each value is the PSCredential to return.

Common patterns:
- @{ Role = 'Tier0' } -> $tier0Credential
- @{ Role = 'Admin' } -> $adminCredential
- @{ Domain = 'SourceAD' } -> $sourceCred
- @{ Environment = 'Production' } -> $prodCred

.PARAMETER DefaultCredential
Optional default credential to return when no session options are provided or
when the options don't match any entry in SessionMap.

.EXAMPLE
# Simple role-based broker
$broker = New-IdleAuthSessionBroker -SessionMap @{
@{ Role = 'Tier0' } = $tier0Credential
@{ Role = 'Admin' } = $adminCredential
} -DefaultCredential $adminCredential

$plan = New-IdlePlan -WorkflowPath './workflow.psd1' -Request $request -Providers @{
Identity = New-IdleADIdentityProvider
AuthSessionBroker = $broker
}

.EXAMPLE
# Domain-based broker for multi-forest scenarios
$broker = New-IdleAuthSessionBroker -SessionMap @{
@{ Domain = 'SourceAD' } = $sourceCred
@{ Domain = 'TargetAD' } = $targetCred
}

.OUTPUTS
PSCustomObject with AcquireAuthSession method
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNull()]
[hashtable] $SessionMap,

[Parameter()]
[AllowNull()]
[PSCredential] $DefaultCredential
)

$broker = [pscustomobject]@{
PSTypeName = 'IdLE.AuthSessionBroker'
SessionMap = $SessionMap
DefaultCredential = $DefaultCredential
}

$broker | Add-Member -MemberType ScriptMethod -Name AcquireAuthSession -Value {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Name,

[Parameter()]
[AllowNull()]
[hashtable] $Options
)

# If no options provided, return default
if ($null -eq $Options -or $Options.Count -eq 0) {
if ($null -ne $this.DefaultCredential) {
return $this.DefaultCredential
}
throw "No auth session options provided and no default credential configured."
}

# Find matching session in map
foreach ($entry in $this.SessionMap.GetEnumerator()) {
$pattern = $entry.Key
$credential = $entry.Value

# Check if all keys in pattern match Options
$matches = $true
foreach ($key in $pattern.Keys) {
if (-not $Options.ContainsKey($key) -or $Options[$key] -ne $pattern[$key]) {
$matches = $false
break
}
}

if ($matches) {
return $credential
}
}

# No match found
if ($null -ne $this.DefaultCredential) {
return $this.DefaultCredential
}

$optionsStr = ($Options.Keys | ForEach-Object { "$_=$($Options[$_])" }) -join ', '
throw "No matching credential found for options: $optionsStr"
} -Force

return $broker
}
Loading