Improve SGU logon resilience and client UX

This commit is contained in:
2026-09-01 10:37:40 -06:00
parent 3d0897316d
commit da01343985
27 changed files with 869 additions and 37 deletions
+33 -1
View File
@@ -22,6 +22,9 @@ param(
[string]$BaseDn = 'DC=lci,DC=lasalle,DC=mx',
[string]$DomainNetbios = 'LCI',
[string]$UpnSuffix = 'lci.lasalle.mx',
[string]$RemoteDesktopGroupDn = '',
[ValidateRange(10, 60)]
[int]$NtlmTimeoutSeconds = 15,
[switch]$CreateMissingOus,
[switch]$DisableCertificateRevocationCheckForLab
)
@@ -96,6 +99,15 @@ if ($CreateMissingOus) {
}
}
if ($RemoteDesktopGroupDn) {
Import-Module ActiveDirectory -ErrorAction Stop
$remoteDesktopGroup = Get-ADGroup -Identity $RemoteDesktopGroupDn -Server $LdapHost -ErrorAction Stop
if ($remoteDesktopGroup.GroupCategory -ne 'Security' -or
-not $remoteDesktopGroup.DistinguishedName.EndsWith(",$BaseDn", [StringComparison]::OrdinalIgnoreCase)) {
throw 'RemoteDesktopGroupDn must identify a security group beneath BaseDn.'
}
}
foreach ($file in @('SGU.AuthBroker.exe', 'SGU.AuthBroker.dll', 'appsettings.json')) {
if (-not (Test-Path -LiteralPath (Join-Path $PublishPath $file))) {
throw "PublishPath is missing $file."
@@ -124,7 +136,7 @@ $productionSettings = @{
Ntlm = @{
Endpoint = $NtlmEndpoint
Domain = ''
TimeoutSeconds = 15
TimeoutSeconds = $NtlmTimeoutSeconds
MaxRedirects = 5
AdministrativeProfilePath = $AdministrativeProfilePath
MenuProfilePath = $MenuProfilePath
@@ -139,6 +151,7 @@ $productionSettings = @{
ProfessorOuDn = "OU=Docentes,OU=Usuarios-SGU,$BaseDn"
StudentOuDn = "OU=Alumnos,OU=Usuarios-SGU,$BaseDn"
AdministrativeOuDn = "OU=Administrativos,OU=Usuarios-SGU,$BaseDn"
RemoteDesktopGroupDn = $RemoteDesktopGroupDn
CreateMissingOus = [bool]$CreateMissingOus
}
}
@@ -147,6 +160,13 @@ $productionSettings = @{
if ($PSCmdlet.ShouldProcess($installPath, 'Install the SGU Authentication Broker Windows service')) {
if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) {
Stop-Service -Name $serviceName -Force
(Get-Service -Name $serviceName).WaitForStatus(
[System.ServiceProcess.ServiceControllerStatus]::Stopped,
[TimeSpan]::FromSeconds(15))
# A self-contained .NET process can briefly retain mapped runtime files
# after SCM reports Stopped. Give Windows time to release those handles.
Start-Sleep -Seconds 2
}
New-Item -ItemType Directory -Path $installPath -Force | Out-Null
@@ -165,6 +185,18 @@ if ($PSCmdlet.ShouldProcess($installPath, 'Install the SGU Authentication Broker
-BinaryPathName ('"{0}"' -f (Join-Path $installPath 'SGU.AuthBroker.exe')) `
-StartupType Automatic
}
else {
Set-Service -Name $serviceName -StartupType Automatic
}
& sc.exe failure $serviceName 'reset=' '86400' 'actions=' 'restart/5000/restart/15000/restart/60000' | Out-Null
if ($LASTEXITCODE -ne 0) {
throw 'Could not configure automatic recovery for SGUAuthBroker.'
}
& sc.exe failureflag $serviceName '1' | Out-Null
if ($LASTEXITCODE -ne 0) {
throw 'Could not enable recovery for non-crash SGUAuthBroker failures.'
}
if (-not (Get-NetFirewallRule -DisplayName 'SGU Authentication Broker (mTLS)' -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -DisplayName 'SGU Authentication Broker (mTLS)' `
+90
View File
@@ -0,0 +1,90 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$RemoteDesktopPrincipal = 'LCI\SG-Laboratorio-Usuarios-RDP',
[switch]$EnableAdministrativeFirewallGroups
)
$ErrorActionPreference = 'Stop'
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run this script from an elevated Windows PowerShell session.'
}
$computer = Get-CimInstance Win32_ComputerSystem
if (-not $computer.PartOfDomain) {
throw 'Join the computer to the domain before enabling domain-scoped remote access.'
}
$remoteDesktopUsersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-555')
$remoteDesktopUsersGroup = ($remoteDesktopUsersSid.Translate([Security.Principal.NTAccount]).Value -split '\\', 2)[1]
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Enable RDP and grant $RemoteDesktopPrincipal access")) {
Set-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' `
-Name fDenyTSConnections -Type DWord -Value 0
Set-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
-Name UserAuthentication -Type DWord -Value 1
Set-Service -Name TermService -StartupType Automatic
Start-Service -Name TermService
Get-NetFirewallRule -Name 'RemoteDesktop-UserMode-In-TCP','RemoteDesktop-UserMode-In-UDP' `
-ErrorAction SilentlyContinue |
Set-NetFirewallRule -Enabled True -Profile Domain
$existingMembers = @(Get-LocalGroupMember -Group $remoteDesktopUsersGroup -ErrorAction SilentlyContinue)
if ($existingMembers.Name -notcontains $RemoteDesktopPrincipal) {
Add-LocalGroupMember -Group $remoteDesktopUsersGroup -Member $RemoteDesktopPrincipal
}
# Use Windows PowerShell so both the inbox and compatible remoting endpoints
# are configured even when this helper is launched from PowerShell 7.
$enableRemoting = Start-Process -FilePath "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" `
-ArgumentList @('-NoLogo', '-NoProfile', '-NonInteractive', '-Command',
'Enable-PSRemoting -Force -SkipNetworkProfileCheck') `
-Wait -PassThru -WindowStyle Hidden
if ($enableRemoting.ExitCode -ne 0) {
throw "Enable-PSRemoting returned $($enableRemoting.ExitCode)."
}
Set-Service -Name WinRM -StartupType Automatic
Start-Service -Name WinRM
Get-NetFirewallRule -Name 'WINRM-HTTP-In-TCP','WINRM-HTTP-In-TCP-NoScope' `
-ErrorAction SilentlyContinue |
Set-NetFirewallRule -Enabled True -Profile Domain
Get-NetFirewallRule -Name 'WINRM-HTTP-In-TCP-PUBLIC' -ErrorAction SilentlyContinue |
Disable-NetFirewallRule
if ($EnableAdministrativeFirewallGroups) {
$administrativeRules = @(
'RemoteEventLogSvc-In-TCP',
'RemoteEventLogSvc-NP-In-TCP',
'RemoteEventLogSvc-RPCSS-In-TCP',
'RemoteSvcAdmin-In-TCP',
'RemoteSvcAdmin-NP-In-TCP',
'RemoteSvcAdmin-RPCSS-In-TCP',
'WMI-RPCSS-In-TCP',
'WMI-WINMGMT-In-TCP',
'WMI-ASYNC-In-TCP'
)
Get-NetFirewallRule -Name $administrativeRules -ErrorAction SilentlyContinue |
Set-NetFirewallRule -Enabled True -Profile Domain
}
}
$rdpMembers = @(Get-LocalGroupMember -Group $remoteDesktopUsersGroup -ErrorAction SilentlyContinue)
[pscustomobject]@{
ComputerName = $env:COMPUTERNAME
Domain = $computer.Domain
RemoteDesktopEnabled = (Get-ItemPropertyValue `
'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' `
-Name fDenyTSConnections) -eq 0
NetworkLevelAuthentication = (Get-ItemPropertyValue `
'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
-Name UserAuthentication) -eq 1
RemoteDesktopPrincipal = $RemoteDesktopPrincipal
PrincipalIsAuthorized = $rdpMembers.Name -contains $RemoteDesktopPrincipal
TermService = (Get-Service TermService).Status
WinRM = (Get-Service WinRM).Status
FirewallProfile = 'Domain'
}
+32 -5
View File
@@ -17,8 +17,8 @@ param(
[string]$DomainNetbios = 'LCI',
[ValidateRange(2, 30)]
[int]$TimeoutSeconds = 6,
[ValidateRange(2, 60)]
[int]$TimeoutSeconds = 20,
[switch]$InstallDotNetRuntime,
@@ -27,7 +27,7 @@ param(
$ErrorActionPreference = 'Stop'
$providerClassId = '{D789CFD8-5AD4-489F-9B83-7EB5D9D09335}'
$installPath = Join-Path $env:ProgramFiles 'SGU\CredentialProvider'
$installRoot = Join-Path $env:ProgramFiles 'SGU\CredentialProvider'
$settingsPath = Join-Path $env:ProgramData 'SGU\CredentialProvider\settings.json'
$providerRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$providerClassId"
$classRegistryPath = "HKLM:\SOFTWARE\Classes\CLSID\$providerClassId\InprocServer32"
@@ -100,6 +100,30 @@ foreach ($file in $requiredFiles) {
}
}
$resolvedPublishPath = (Resolve-Path -LiteralPath $PublishPath).Path.TrimEnd('\')
$packageManifest = Get-ChildItem -LiteralPath $resolvedPublishPath -Recurse -File |
Sort-Object FullName |
ForEach-Object {
$relativePath = $_.FullName.Substring($resolvedPublishPath.Length).TrimStart('\')
'{0}={1}' -f $relativePath, (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash
}
$manifestBytes = [Text.Encoding]::UTF8.GetBytes(($packageManifest -join "`n"))
$sha256 = [Security.Cryptography.SHA256]::Create()
try {
$packageHash = -join ($sha256.ComputeHash($manifestBytes) | ForEach-Object { $_.ToString('x2') })
}
finally {
$sha256.Dispose()
}
$versionId = $packageHash.Substring(0, 16)
$installPath = Join-Path $installRoot "versions\$versionId"
$completeMarker = Join-Path $installPath '.complete'
if ((Test-Path -LiteralPath $installPath) -and -not (Test-Path -LiteralPath $completeMarker)) {
$installPath = '{0}-{1}' -f $installPath, ([Guid]::NewGuid().ToString('N').Substring(0, 8))
$completeMarker = Join-Path $installPath '.complete'
}
$clientThumbprint = $ClientCertificateThumbprint -replace ' ', ''
$serverThumbprint = $ServerCertificateThumbprint -replace ' ', ''
if ($clientThumbprint.Length -ne 40 -or $serverThumbprint.Length -ne 40) {
@@ -121,8 +145,11 @@ if (-not $serverCertificate) {
}
if ($PSCmdlet.ShouldProcess($installPath, 'Install and register the SGU Credential Provider')) {
New-Item -ItemType Directory -Path $installPath -Force | Out-Null
Copy-Item -Path (Join-Path $PublishPath '*') -Destination $installPath -Recurse -Force
if (-not (Test-Path -LiteralPath $completeMarker)) {
New-Item -ItemType Directory -Path $installPath -Force | Out-Null
Copy-Item -Path (Join-Path $resolvedPublishPath '*') -Destination $installPath -Recurse -Force
[IO.File]::WriteAllText($completeMarker, $packageHash, [Text.UTF8Encoding]::new($false))
}
New-Item -ItemType Directory -Path (Split-Path $settingsPath -Parent) -Force | Out-Null
$settingsJson = @{