Private/CACrlShare.ps1
|
<#
.SYNOPSIS CA Manager - menu 7 (2026-09-10 renumber - was menu 6). Creates the CRL distribution SMB share (+ its ACLs and the infra AD group) for a split-tier / segregated-proxy deployment. Gated on CA_CrlShareMode. .DESCRIPTION Single-tier co-located CA (CA_CrlShareMode = None): NOT needed - the CA writes its own CRL to %windir%\...\CertEnroll and IIS (menu 6) serves it directly. Menu 7 is a no-op there. Split-tier (RootCrlPublish) or segregated App Proxy box (SegregatedProxy): the CA publishes its CRL/AIA to a share the proxy/IIS box can read. Reproduces a real client's hand-built C:\CRLShare (from a captured CA inventory, section G): - SMB share "CRLShare" -> C:\CRLShare - share ACL: <WriteGroup> = Full (the only non-default entry) - NTFS: <WriteGroup> = FullControl, plus ALL APPLICATION PACKAGES = ReadAndExecute (that last is what lets the co-located IIS / App Proxy worker read the .crl/.crt files) - infra AD group (default CA_CRLPublishers) whose member is the publishing CA's machine account Get-CACrlSharePlan is PURE. New-CACrlShare routes every mutation through Invoke-CAStep. After menu 7, run menu 8 - Get-CAPublicationUrlPlan already appends the file://<share>/... CDP entry when CA_CrlSharePath is set. #> # --------------------------------------------------------------------------- function Get-CACrlSharePlan { <# .SYNOPSIS PURE. Turns the CA_CrlShare* answers into the share/ACL/AD-group plan. No I/O. #> param( [Parameter(Mandatory)]$CAAnswers, [string]$CAMachineName = $env:COMPUTERNAME ) $mode = if ($CAAnswers.CA_CrlShareMode) { "$($CAAnswers.CA_CrlShareMode)" } else { 'None' } if ($mode -notin @('RootCrlPublish', 'SegregatedProxy')) { return [pscustomobject]@{ Mode = $mode; Applicable = $false Reason = "CA_CrlShareMode is '$mode' - a single-tier co-located CA serves its CRL from its own CertEnroll dir via IIS (menu 6). Nothing to do here." } } $sharePath = if (-not [string]::IsNullOrWhiteSpace($CAAnswers.CA_CrlSharePath)) { ($CAAnswers.CA_CrlSharePath).TrimEnd('\', '/') } else { 'C:\CRLShare' } # UNC form: if the answer is already \\server\share keep it; else \\<thisbox>\<leaf> $isUnc = $sharePath -match '^\\\\' $shareName = if ($isUnc) { ($sharePath -split '\\')[-1] } else { Split-Path $sharePath -Leaf } $localPath = if ($isUnc) { $null } else { $sharePath } $uncPath = if ($isUnc) { $sharePath } else { "\\$CAMachineName\$shareName" } $writeGroup = if (-not [string]::IsNullOrWhiteSpace($CAAnswers.CA_CrlShareWriteGroup)) { "$($CAAnswers.CA_CrlShareWriteGroup)" } else { 'CA_CRLPublishers' } [pscustomobject]@{ Mode = $mode Applicable = $true ShareName = $shareName LocalPath = $localPath # $null when the answer was a bare UNC (share lives on another box) UncPath = $uncPath WriteGroup = $writeGroup ShareAccess = [pscustomobject]@{ Account = $writeGroup; Right = 'Full' } NtfsAces = @( [pscustomobject]@{ Account = $writeGroup; Right = '(OI)(CI)F' } # FullControl, inherited to files+dirs [pscustomobject]@{ Account = 'ALL APPLICATION PACKAGES'; Right = '(OI)(CI)(RX)' } # IIS / App Proxy worker read ) InfraGroupName = $writeGroup InfraGroupSamName = ($writeGroup -replace '[^A-Za-z0-9_\-]', '') InfraGroupMember = "$CAMachineName`$" # the publishing CA's machine account Notes = "Transcribed from a real client's C:\CRLShare. After this, run menu 8 - it appends the file://<share>/%3%8%9.crl CDP entry when CA_CrlSharePath is set." } } # --------------------------------------------------------------------------- function Show-CACrlSharePlan { param([Parameter(Mandatory)]$Plan) if (-not $Plan.Applicable) { Write-Host " $($Plan.Reason)" -ForegroundColor Gray return } Write-Host (" Mode : {0}" -f $Plan.Mode) -ForegroundColor White Write-Host (" Share : {0} -> {1}" -f $Plan.ShareName, $(if ($Plan.LocalPath) { $Plan.LocalPath } else { '(on another host - ' + $Plan.UncPath + ')' })) -ForegroundColor Gray Write-Host (" Share ACL : {0} = {1}" -f $Plan.ShareAccess.Account, $Plan.ShareAccess.Right) -ForegroundColor Gray foreach ($a in $Plan.NtfsAces) { Write-Host (" NTFS ACE : {0} : {1}" -f $a.Account, $a.Right) -ForegroundColor Gray } Write-Host (" Infra group : {0} (member: {1})" -f $Plan.InfraGroupName, $Plan.InfraGroupMember) -ForegroundColor Gray Write-Host (" UNC : {0}" -f $Plan.UncPath) -ForegroundColor Gray Write-Host (" Then : run menu 8 (it adds the file://<share> CDP entry from CA_CrlSharePath)") -ForegroundColor DarkGray } # --------------------------------------------------------------------------- function New-CACrlShare { <# .SYNOPSIS ENGINE. Creates the CRL share directory + SMB share + ACLs + the infra AD group from a Get-CACrlSharePlan. Every mutation via Invoke-CAStep. Tolerates each piece already existing. #> param([Parameter(Mandatory)]$Plan) if (-not $Plan.Applicable) { Write-Host " $($Plan.Reason)" -ForegroundColor Gray return } # 1. AD infra group + the CA machine account Invoke-CAStep -Description "Ensure AD group '$($Plan.InfraGroupSamName)' exists and holds $($Plan.InfraGroupMember)" ` -Commands @( "New-ADGroup -Name '$($Plan.InfraGroupSamName)' -GroupScope Global -GroupCategory Security" "Add-ADGroupMember -Identity '$($Plan.InfraGroupSamName)' -Members (Get-ADComputer '$env:COMPUTERNAME')" ) ` -Action { if (-not (Get-Command Get-ADGroup -ErrorAction SilentlyContinue)) { throw "ActiveDirectory module not available - run menu 1, or create '$($Plan.InfraGroupSamName)' by hand." } $g = Get-ADGroup -LDAPFilter "(sAMAccountName=$($Plan.InfraGroupSamName))" -ErrorAction SilentlyContinue if (-not $g) { New-ADGroup -Name $Plan.InfraGroupSamName -SamAccountName $Plan.InfraGroupSamName -GroupScope Global -GroupCategory Security -Description 'CRL/AIA publish identity for a split-tier CA (CA-Manager menu 7)' } $comp = Get-ADComputer -Identity $env:COMPUTERNAME -ErrorAction Stop $members = @(Get-ADGroupMember -Identity $Plan.InfraGroupSamName -ErrorAction SilentlyContinue | ForEach-Object { $_.SID.Value }) if ($members -notcontains $comp.SID.Value) { Add-ADGroupMember -Identity $Plan.InfraGroupSamName -Members $comp } } -ContinueOnError | Out-Null if (-not $Plan.LocalPath) { Write-Host " CA_CrlSharePath is a bare UNC ($($Plan.UncPath)) - the share lives on another host; create the directory / share / ACLs there. Group step above still applies." -ForegroundColor Yellow return } # 2. directory Invoke-CAStep -Description "Create the CRL share directory $($Plan.LocalPath)" ` -Commands @("New-Item -ItemType Directory -Path '$($Plan.LocalPath)' -Force") ` -Action { if (-not (Test-Path $Plan.LocalPath)) { New-Item -ItemType Directory -Path $Plan.LocalPath -Force | Out-Null } } | Out-Null # 3. SMB share + share-level ACL Invoke-CAStep -Description "Create SMB share '$($Plan.ShareName)' -> $($Plan.LocalPath) and grant $($Plan.ShareAccess.Account) Full" ` -Commands @( "New-SmbShare -Name '$($Plan.ShareName)' -Path '$($Plan.LocalPath)' -FullAccess '$($Plan.ShareAccess.Account)'" "Grant-SmbShareAccess -Name '$($Plan.ShareName)' -AccountName '$($Plan.ShareAccess.Account)' -AccessRight Full -Force" ) ` -Action { $existing = Get-SmbShare -Name $Plan.ShareName -ErrorAction SilentlyContinue if (-not $existing) { New-SmbShare -Name $Plan.ShareName -Path $Plan.LocalPath -FullAccess $Plan.ShareAccess.Account | Out-Null } else { Grant-SmbShareAccess -Name $Plan.ShareName -AccountName $Plan.ShareAccess.Account -AccessRight Full -Force | Out-Null } } -ContinueOnError | Out-Null # 4. NTFS ACEs (icacls - deterministic + previewable) foreach ($ace in $Plan.NtfsAces) { Invoke-CAStep -Description "NTFS: grant '$($ace.Account)' '$($ace.Right)' on $($Plan.LocalPath)" ` -Commands @("icacls `"$($Plan.LocalPath)`" /grant `"$($ace.Account):$($ace.Right)`"") ` -Action { $o = & icacls.exe $Plan.LocalPath /grant "$($ace.Account):$($ace.Right)" 2>&1 | Out-String if ($LASTEXITCODE -ne 0) { throw "icacls failed (exit $LASTEXITCODE): $o" } $o } -ContinueOnError | Out-Null } Write-Host "" Write-Host " CRL share ready. Next: run menu 8 so the CA publishes to file://$($Plan.LocalPath -replace '\\','/')/... too" -ForegroundColor Cyan Write-Host " (Get-CAPublicationUrlPlan adds that entry when CA_CrlSharePath is set)." -ForegroundColor DarkGray } # --------------------------------------------------------------------------- function Invoke-CAMenuCrlShare { <# .SYNOPSIS Menu 7 (2026-09-10 renumber - was menu 6). Creates the CRL distribution share for a split-tier deployment. No-op for a single-tier co-located CA (CA_CrlShareMode = None). #> param( $CAAnswers, $Status ) Write-CAHeader "CRL distribution share + AD group" if (-not $CAAnswers) { $CAAnswers = [pscustomobject]@{} } $plan = Get-CACrlSharePlan -CAAnswers $CAAnswers Show-CACrlSharePlan -Plan $plan Write-Host "" if (-not $plan.Applicable) { Read-Host "Press Enter to return to the menu" | Out-Null return } if (Read-CAConfirm -Prompt "Create this share + AD group?") { New-CACrlShare -Plan $plan } Read-Host "`nPress Enter to return to the menu" | Out-Null } |