Files
ping-mee-self-service-platform/docs/test-environment/New-PortalTestEnvironment.ps1
T
Ebbe BaßandClaude Sonnet 5.5 003ce6e0c2 docs: test-environment runbook and scripts for ticket 1.2
Setup script for the test OU, scoped service account, groups and test users;
an LDAPS check script; and a runbook for the CA file, authentik, Zoraxy and
a test mailbox. Not run against a live directory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
2026-10-15 15:11:14 +02:00

173 lines
8.2 KiB
PowerShell

<#
.SYNOPSIS
Creates the Active Directory objects for testing the self-service portal.
.DESCRIPTION
Creates, under a parent OU you choose:
- a test OU with test users and two test groups,
- a separate OU with a service account that is NOT inside the test OU,
- the delegated rights the portal needs on user objects in the test OU, and nothing else.
Delegated rights (inherited to user objects in the test OU only):
write givenName, sn, displayName, mail, telephoneNumber, mobile; the "Change Password" right.
Reading is already allowed for authenticated users. The account cannot create or delete
objects, cannot reset passwords (unless -AllowResetPassword), and cannot write anything else.
Run on a domain controller, or a machine with the AD module, as a domain administrator.
Run it once with -WhatIf first. Nothing outside the two new OUs is changed.
Not tested by the author against a live directory: read the -WhatIf output before you run it.
.PARAMETER ParentDn
Distinguished name of an existing OU or container that will hold the two new OUs.
.EXAMPLE
.\New-PortalTestEnvironment.ps1 -ParentDn "OU=Organisation,DC=example,DC=local" -WhatIf
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $true)][string]$ParentDn,
[string]$TestOuName = 'PortalTest',
[string]$ServiceOuName = 'PortalTestService',
[string]$ServiceAccountName = 'svc-portal-test',
[string]$GroupPrefix = 'PortalTest',
[string]$MailDomain = 'example.test',
[switch]$AllowResetPassword
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
# --- helpers -----------------------------------------------------------------
$script:rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
function Get-RandomIndex([int]$Max) {
$bytes = New-Object byte[] 4
$script:rng.GetBytes($bytes)
return [int]([BitConverter]::ToUInt32($bytes, 0) % $Max)
}
function New-RandomPassword([int]$Length = 24) {
$sets = @('ABCDEFGHJKLMNPQRSTUVWXYZ', 'abcdefghijkmnopqrstuvwxyz', '23456789', '!#%&*+-=?@^_')
$all = -join $sets
$chars = New-Object System.Collections.Generic.List[char]
foreach ($s in $sets) { $chars.Add($s[(Get-RandomIndex $s.Length)]) }
while ($chars.Count -lt $Length) { $chars.Add($all[(Get-RandomIndex $all.Length)]) }
for ($i = $chars.Count - 1; $i -gt 0; $i--) {
$j = Get-RandomIndex ($i + 1)
$t = $chars[$i]; $chars[$i] = $chars[$j]; $chars[$j] = $t
}
return -join $chars
}
function Test-Dn([string]$Dn) {
try { Get-ADObject -Identity $Dn | Out-Null; return $true }
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { return $false }
}
function Test-UserExists([string]$Sam, [string]$SearchBase) {
if (-not (Test-Dn $SearchBase)) { return $false }
return [bool](Get-ADUser -Filter "SamAccountName -eq '$Sam'" -SearchBase $SearchBase)
}
# --- checks ------------------------------------------------------------------
if (-not (Test-Dn $ParentDn)) { throw "ParentDn does not exist: $ParentDn" }
$domain = Get-ADDomain
$netbios = $domain.NetBIOSName
$testOuDn = "OU=$TestOuName,$ParentDn"
$svcOuDn = "OU=$ServiceOuName,$ParentDn"
$real = -not $WhatIfPreference
$secrets = [ordered]@{}
$groups = @{}
$users = @()
# Everything below is wrapped so that passwords generated so far are always shown, even if a
# later step fails. Existing objects are detected with read-only calls, also under -WhatIf.
try {
# --- OUs ---------------------------------------------------------------------
foreach ($ou in @(@($TestOuName, $testOuDn), @($ServiceOuName, $svcOuDn))) {
if (Test-Dn $ou[1]) { Write-Host "OU exists: $($ou[1])"; continue }
if ($PSCmdlet.ShouldProcess($ou[1], 'Create OU')) {
New-ADOrganizationalUnit -Name $ou[0] -Path $ParentDn -ProtectedFromAccidentalDeletion $false
}
}
# --- service account (outside the test OU on purpose) ------------------------
$svcDn = "CN=$ServiceAccountName,$svcOuDn"
if (Test-Dn $svcDn) {
Write-Host "Service account exists: $svcDn (password unchanged)"
}
elseif ($PSCmdlet.ShouldProcess($svcDn, 'Create service account')) {
$pw = New-RandomPassword 32
New-ADUser -Name $ServiceAccountName -SamAccountName $ServiceAccountName `
-UserPrincipalName "$ServiceAccountName@$($domain.DNSRoot)" -Path $svcOuDn `
-AccountPassword (ConvertTo-SecureString $pw -AsPlainText -Force) -Enabled $true `
-PasswordNeverExpires $true -CannotChangePassword $true `
-Description 'Self-service portal TEST service account'
$secrets[$ServiceAccountName] = $pw
}
# --- groups ------------------------------------------------------------------
foreach ($g in @('Publishers', 'Admins')) {
$name = "$GroupPrefix-$g"
$groups[$g] = "CN=$name,$testOuDn"
if (Test-Dn $groups[$g]) { Write-Host "Group exists: $name"; continue }
if ($PSCmdlet.ShouldProcess($groups[$g], 'Create group')) {
New-ADGroup -Name $name -SamAccountName $name -GroupScope Global -GroupCategory Security -Path $testOuDn
}
}
# --- test users --------------------------------------------------------------
$users = @(
@{ Sam = 'ptest-user1'; Given = 'Test'; Sur = 'UserOne'; Group = $null; Enabled = $true; Expired = $false },
@{ Sam = 'ptest-user2'; Given = 'Test'; Sur = 'UserTwo'; Group = $null; Enabled = $true; Expired = $false },
@{ Sam = 'ptest-publisher'; Given = 'Test'; Sur = 'Publisher'; Group = 'Publishers'; Enabled = $true; Expired = $false },
@{ Sam = 'ptest-admin'; Given = 'Test'; Sur = 'Admin'; Group = 'Admins'; Enabled = $true; Expired = $false },
@{ Sam = 'ptest-disabled'; Given = 'Test'; Sur = 'Disabled'; Group = $null; Enabled = $false; Expired = $false },
@{ Sam = 'ptest-expired'; Given = 'Test'; Sur = 'MustChange'; Group = $null; Enabled = $true; Expired = $true }
)
foreach ($u in $users) {
$dn = "CN=$($u.Given) $($u.Sur),$testOuDn"
if (Test-UserExists $u.Sam $testOuDn) { Write-Host "User exists: $($u.Sam) (password unchanged)"; continue }
if ($PSCmdlet.ShouldProcess($dn, "Create test user $($u.Sam)")) {
$pw = New-RandomPassword 24
New-ADUser -Name "$($u.Given) $($u.Sur)" -SamAccountName $u.Sam -GivenName $u.Given -Surname $u.Sur `
-DisplayName "$($u.Given) $($u.Sur)" -EmailAddress "$($u.Sam)@$MailDomain" `
-UserPrincipalName "$($u.Sam)@$($domain.DNSRoot)" -Path $testOuDn `
-AccountPassword (ConvertTo-SecureString $pw -AsPlainText -Force) -Enabled $true `
-ChangePasswordAtLogon $u.Expired
if (-not $u.Enabled) { Disable-ADAccount -Identity $u.Sam }
if ($u.Group) { Add-ADGroupMember -Identity $groups[$u.Group] -Members $u.Sam }
$secrets[$u.Sam] = $pw
}
}
# --- delegation on the test OU (user objects only, inherited to children) -------
$grants = @(
'WP;givenName;user', 'WP;sn;user', 'WP;displayName;user', 'WP;mail;user',
'WP;telephoneNumber;user', 'WP;mobile;user', 'CA;Change Password;user'
)
if ($AllowResetPassword) { $grants += 'CA;Reset Password;user' }
foreach ($g in $grants) {
if ($PSCmdlet.ShouldProcess($testOuDn, "Grant $g to $netbios\$ServiceAccountName")) {
$out = & dsacls.exe $testOuDn /I:S /G "$netbios\$($ServiceAccountName):$g" 2>&1
if ($LASTEXITCODE -ne 0) { throw "dsacls failed for '$g': $out" }
}
}
}
finally {
# --- summary (always shown) ----------------------------------------------------
Write-Host ''
Write-Host '=== Values to write down (not secrets) ==='
Write-Host "Domain DNS name : $($domain.DNSRoot)"
Write-Host "Directory server (FQDN): $($domain.PDCEmulator)"
Write-Host "Test OU (base DN) : $testOuDn"
Write-Host "Service account : $ServiceAccountName@$($domain.DNSRoot) ($svcDn)"
Write-Host "Publisher group DN : $($groups['Publishers'])"
Write-Host "Admin group DN : $($groups['Admins'])"
Write-Host "Test users : $(($users | ForEach-Object { $_.Sam }) -join ', ')"
if ($secrets.Count -gt 0) {
Write-Host ''
Write-Host '=== PASSWORDS (shown once; store them in your password manager now) ===' -ForegroundColor Yellow
foreach ($k in $secrets.Keys) { Write-Host ('{0,-18} {1}' -f $k, $secrets[$k]) }
}
if (-not $real) { Write-Host "`n-WhatIf was used: nothing was changed." }
}