[CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [string]$PublishPath, [Parameter(Mandatory)] [string]$ServerCertificateSubject, [Parameter(Mandatory)] [ValidatePattern('^[0-9A-Fa-f ]{40,59}$')] [string[]]$AllowedClientThumbprints, [string]$NtlmEndpoint = 'https://sgu.ulsa.edu.mx/', [string[]]$AllowedNtlmRedirectHosts = @('sgu.ulsa.edu.mx'), [ValidatePattern('^/')] [string]$AdministrativeProfilePath = '/psulsa/gadmon/capitalhumano/controlincidencias/incidencias.aspx', [ValidatePattern('^/')] [string]$MenuProfilePath = '/psulsa/menu.aspx', [ValidateRange(32768, 2097152)] [int]$MaxProfileBytes = 524288, [string]$LdapHost = 'localhost', [string]$BaseDn = 'DC=lci,DC=lasalle,DC=mx', [string]$DomainNetbios = 'LCI', [string]$UpnSuffix = 'lci.lasalle.mx', [switch]$CreateMissingOus, [switch]$DisableCertificateRevocationCheckForLab ) $ErrorActionPreference = 'Stop' $serviceName = 'SGUAuthBroker' $installPath = Join-Path $env:ProgramFiles 'SGU\AuthBroker' $normalizedClientThumbprints = @($AllowedClientThumbprints | ForEach-Object { $_ -replace ' ', '' }) if ($normalizedClientThumbprints.Where({ $_.Length -ne 40 }).Count -gt 0) { throw 'Client certificate thumbprints must contain exactly 40 hexadecimal characters.' } $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 PowerShell session on the broker server.' } $serverCertificate = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -like "*$ServerCertificateSubject*" -and $_.HasPrivateKey } | Sort-Object NotAfter -Descending | Select-Object -First 1 if (-not $serverCertificate) { throw 'The HTTPS server certificate with private key was not found in LocalMachine\My.' } if (-not $serverCertificate.Verify()) { throw 'The HTTPS server certificate chain is not trusted or is outside its validity period. Import the issuing CA chain; for a self-signed lab certificate, trust its public .cer in LocalMachine\Root.' } if ($CreateMissingOus) { Import-Module ActiveDirectory -ErrorAction Stop $usersOuName = 'Usuarios-SGU' $usersOuDn = "OU=$usersOuName,$BaseDn" if (-not (Get-ADOrganizationalUnit -LDAPFilter "(ou=$usersOuName)" -SearchBase $BaseDn -SearchScope OneLevel -Server $LdapHost -ErrorAction SilentlyContinue)) { New-ADOrganizationalUnit -Name $usersOuName -Path $BaseDn -ProtectedFromAccidentalDeletion $true -Server $LdapHost | Out-Null } foreach ($ouName in @('Docentes', 'Alumnos', 'Administrativos')) { $targetOu = Get-ADOrganizationalUnit -LDAPFilter "(ou=$ouName)" -SearchBase $usersOuDn -SearchScope OneLevel -Properties ProtectedFromAccidentalDeletion -Server $LdapHost -ErrorAction SilentlyContinue if ($targetOu) { if (-not $targetOu.ProtectedFromAccidentalDeletion) { $targetOuDn = [string]$targetOu.DistinguishedName Set-ADOrganizationalUnit -Identity $targetOuDn -ProtectedFromAccidentalDeletion $true -Server $LdapHost -Confirm:$false } continue } $legacyOu = Get-ADOrganizationalUnit -LDAPFilter "(ou=$ouName)" -SearchBase $BaseDn -SearchScope OneLevel -Properties ProtectedFromAccidentalDeletion -Server $LdapHost -ErrorAction SilentlyContinue if ($legacyOu) { $legacyOuDn = [string]$legacyOu.DistinguishedName try { if ($legacyOu.ProtectedFromAccidentalDeletion) { Set-ADOrganizationalUnit -Identity $legacyOuDn -ProtectedFromAccidentalDeletion $false -Server $LdapHost -Confirm:$false Start-Sleep -Seconds 1 } Move-ADObject -Identity $legacyOuDn -TargetPath $usersOuDn -Server $LdapHost -Confirm:$false -ErrorAction Stop } finally { $currentOu = Get-ADOrganizationalUnit -LDAPFilter "(ou=$ouName)" -SearchBase $usersOuDn -SearchScope OneLevel -Properties ProtectedFromAccidentalDeletion -Server $LdapHost -ErrorAction SilentlyContinue if (-not $currentOu) { $currentOu = Get-ADOrganizationalUnit -LDAPFilter "(ou=$ouName)" -SearchBase $BaseDn -SearchScope OneLevel -Properties ProtectedFromAccidentalDeletion -Server $LdapHost -ErrorAction SilentlyContinue } if ($currentOu) { $currentOuDn = [string]$currentOu.DistinguishedName Set-ADOrganizationalUnit -Identity $currentOuDn -ProtectedFromAccidentalDeletion $true -Server $LdapHost -Confirm:$false } } } else { New-ADOrganizationalUnit -Name $ouName -Path $usersOuDn -ProtectedFromAccidentalDeletion $true -Server $LdapHost | Out-Null } } } 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." } } $productionSettings = @{ Kestrel = @{ Endpoints = @{ Https = @{ Url = 'https://0.0.0.0:8443' Certificate = @{ Subject = $ServerCertificateSubject Store = 'My' Location = 'LocalMachine' AllowInvalid = $false } } } } Broker = @{ Tls = @{ AllowedClientThumbprints = $normalizedClientThumbprints CheckCertificateRevocation = -not $DisableCertificateRevocationCheckForLab } Ntlm = @{ Endpoint = $NtlmEndpoint Domain = '' TimeoutSeconds = 15 MaxRedirects = 5 AdministrativeProfilePath = $AdministrativeProfilePath MenuProfilePath = $MenuProfilePath MaxProfileBytes = $MaxProfileBytes AllowedRedirectHosts = $AllowedNtlmRedirectHosts } Directory = @{ LdapHost = $LdapHost BaseDn = $BaseDn DomainNetbios = $DomainNetbios UpnSuffix = $UpnSuffix ProfessorOuDn = "OU=Docentes,OU=Usuarios-SGU,$BaseDn" StudentOuDn = "OU=Alumnos,OU=Usuarios-SGU,$BaseDn" AdministrativeOuDn = "OU=Administrativos,OU=Usuarios-SGU,$BaseDn" CreateMissingOus = [bool]$CreateMissingOus } } } if ($PSCmdlet.ShouldProcess($installPath, 'Install the SGU Authentication Broker Windows service')) { if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { Stop-Service -Name $serviceName -Force } New-Item -ItemType Directory -Path $installPath -Force | Out-Null Copy-Item -Path (Join-Path $PublishPath '*') -Destination $installPath -Recurse -Force $settingsJson = $productionSettings | ConvertTo-Json -Depth 8 $utf8WithoutBom = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText( (Join-Path $installPath 'appsettings.Production.json'), $settingsJson, $utf8WithoutBom) if (-not (Get-Service -Name $serviceName -ErrorAction SilentlyContinue)) { New-Service -Name $serviceName ` -DisplayName 'SGU Authentication Broker' ` -Description 'Validates SGU NTLM credentials and synchronizes Active Directory accounts.' ` -BinaryPathName ('"{0}"' -f (Join-Path $installPath 'SGU.AuthBroker.exe')) ` -StartupType Automatic } if (-not (Get-NetFirewallRule -DisplayName 'SGU Authentication Broker (mTLS)' -ErrorAction SilentlyContinue)) { New-NetFirewallRule -DisplayName 'SGU Authentication Broker (mTLS)' ` -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8443 -Profile Domain | Out-Null } Start-Service -Name $serviceName } Get-Service -Name $serviceName | Select-Object Name, Status, StartType