227 lines
10 KiB
PowerShell
227 lines
10 KiB
PowerShell
[CmdletBinding(SupportsShouldProcess)]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$PublishPath,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$ServerCertificateSubject,
|
|
|
|
[ValidatePattern('^[0-9A-Fa-f ]{40,59}$')]
|
|
[string[]]$AllowedClientThumbprints = @(),
|
|
|
|
[string]$NtlmEndpoint = 'https://sgu.ulsa.edu.mx/',
|
|
[string[]]$AllowedNtlmRedirectHosts = @('sgu.ulsa.edu.mx'),
|
|
[ValidatePattern('^/')]
|
|
[string]$AuthenticationPath = '/psulsa/',
|
|
[ValidatePattern('^/')]
|
|
[string]$AdministrativeProfilePath = '/psulsa/gadmon/capitalhumano/controlincidencias/incidencias.aspx',
|
|
[ValidatePattern('^/')]
|
|
[string]$AdministrativePersonalProfilePath = '/psulsa/gadmon/capitalhumano/datos/personales.aspx',
|
|
[ValidatePattern('^/')]
|
|
[string]$AdministrativeLocationProfilePath = '/psulsa/gadmon/capitalhumano/datos/ubicacion.aspx',
|
|
[ValidatePattern('^/')]
|
|
[string]$StudentProfilePath = '/psulsa/alumnos/consultainformacionalumnos/consultainformacion.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',
|
|
[string]$RemoteDesktopGroupDn = '',
|
|
[ValidateLength(1, 64)]
|
|
[string]$DefaultCompany = 'La Salle',
|
|
[ValidateRange(10, 60)]
|
|
[int]$NtlmTimeoutSeconds = 20,
|
|
[ValidateRange(2, 90)]
|
|
[int]$ProfileTimeoutSeconds = 90,
|
|
[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
|
|
}
|
|
}
|
|
}
|
|
|
|
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."
|
|
}
|
|
}
|
|
|
|
$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 = $NtlmTimeoutSeconds
|
|
ProfileTimeoutSeconds = $ProfileTimeoutSeconds
|
|
MaxRedirects = 5
|
|
AuthenticationPath = $AuthenticationPath
|
|
AdministrativeProfilePath = $AdministrativeProfilePath
|
|
AdministrativePersonalProfilePath = $AdministrativePersonalProfilePath
|
|
AdministrativeLocationProfilePath = $AdministrativeLocationProfilePath
|
|
StudentProfilePath = $StudentProfilePath
|
|
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"
|
|
RemoteDesktopGroupDn = $RemoteDesktopGroupDn
|
|
DefaultCompany = $DefaultCompany
|
|
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
|
|
(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
|
|
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
|
|
}
|
|
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)' `
|
|
-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
|