Place SGU role groups in their user OUs

This commit is contained in:
2026-09-08 16:12:58 -06:00
parent a24c25a3fb
commit 8baa47fe1e
7 changed files with 51 additions and 24 deletions
+5 -5
View File
@@ -55,11 +55,11 @@ Operational documentation:
- [Domain monitoring, usage reports, and six-month retention](docs/monitoring.md) - [Domain monitoring, usage reports, and six-month retention](docs/monitoring.md)
- [Decision: do not persist password verifiers in Redis](docs/decisions/0001-no-password-cache.md) - [Decision: do not persist password verifiers in Redis](docs/decisions/0001-no-password-cache.md)
| Prefix | Role | Default OU | | Prefix | Role | Default OU | Security group in the same OU |
|---|---|---| |---|---|---|---|
| `DO` | Professor / docente | `OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` | | `DO` | Professor / docente | `OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` | `SGU-Docentes` |
| `AL` | Student / alumno | `OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` | | `AL` | Student / alumno | `OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` | `SGU-Alumnos` |
| `AD` | Administrative | `OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` | | `AD` | Administrative | `OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` | `SGU-Administrativos` |
If the broker or institutional NTLM authority is unavailable, the provider If the broker or institutional NTLM authority is unavailable, the provider
submits the unchanged credentials to Windows for normal AD/cached-domain submits the unchanged credentials to Windows for normal AD/cached-domain
+5 -2
View File
@@ -76,8 +76,11 @@ password outcome.
Every synchronization also enforces one idempotent security-group membership Every synchronization also enforces one idempotent security-group membership
from the classified institutional prefix: `AL` to `SGU-Alumnos`, `AD` to from the classified institutional prefix: `AL` to `SGU-Alumnos`, `AD` to
`SGU-Administrativos`, and `DO` to `SGU-Docentes`. This happens synchronously `SGU-Administrativos`, and `DO` to `SGU-Docentes`. Each role group is stored
inside the broker before the institutional password is written to AD. A missing inside its corresponding user OU. During an upgrade,
the bootstrap moves a legacy group from the `Usuarios-SGU` root while preserving
its SID and memberships instead of creating a duplicate. Membership enforcement
happens synchronously inside the broker before the institutional password is written to AD. A missing
or inaccessible role group therefore fails provisioning instead of leaving a or inaccessible role group therefore fails provisioning instead of leaving a
new usable account without its authorization classification. Existing accounts new usable account without its authorization classification. Existing accounts
are repaired automatically on their next successful SGU authentication. are repaired automatically on their next successful SGU authentication.
+31 -7
View File
@@ -76,16 +76,23 @@ if (-not $serverCertificate.Verify()) {
} }
Import-Module ActiveDirectory -ErrorAction Stop Import-Module ActiveDirectory -ErrorAction Stop
function ConvertTo-LdapFilterValue {
param([Parameter(Mandatory)][string]$Value)
return $Value.Replace('\', '\5c').Replace('*', '\2a').Replace('(', '\28').Replace(')', '\29').Replace(([string][char]0), '\00')
}
$usersOuName = 'Usuarios-SGU' $usersOuName = 'Usuarios-SGU'
$usersOuDn = "OU=$usersOuName,$BaseDn" $usersOuDn = "OU=$usersOuName,$BaseDn"
if ([string]::IsNullOrWhiteSpace($ProfessorGroupDn)) { if ([string]::IsNullOrWhiteSpace($ProfessorGroupDn)) {
$ProfessorGroupDn = "CN=SGU-Docentes,$usersOuDn" $ProfessorGroupDn = "CN=SGU-Docentes,OU=Docentes,$usersOuDn"
} }
if ([string]::IsNullOrWhiteSpace($StudentGroupDn)) { if ([string]::IsNullOrWhiteSpace($StudentGroupDn)) {
$StudentGroupDn = "CN=SGU-Alumnos,$usersOuDn" $StudentGroupDn = "CN=SGU-Alumnos,OU=Alumnos,$usersOuDn"
} }
if ([string]::IsNullOrWhiteSpace($AdministrativeGroupDn)) { if ([string]::IsNullOrWhiteSpace($AdministrativeGroupDn)) {
$AdministrativeGroupDn = "CN=SGU-Administrativos,$usersOuDn" $AdministrativeGroupDn = "CN=SGU-Administrativos,OU=Administrativos,$usersOuDn"
} }
if ($CreateMissingOus) { if ($CreateMissingOus) {
@@ -152,13 +159,30 @@ foreach ($definition in $roleGroupDefinitions) {
throw "$($definition.Role)GroupDn must start with a simple CN component." throw "$($definition.Role)GroupDn must start with a simple CN component."
} }
$groupName = $groupDnMatch.Groups['Name'].Value $groupName = $groupDnMatch.Groups['Name'].Value
$groupPath = $groupDnMatch.Groups['Path'].Value
if ($groupName.Length -gt 20) { if ($groupName.Length -gt 20) {
throw "$($definition.Role) group name exceeds the 20-character sAMAccountName limit." throw "$($definition.Role) group name exceeds the 20-character sAMAccountName limit."
} }
New-ADGroup -Name $groupName -SamAccountName $groupName `
-GroupCategory Security -GroupScope Global ` $matchingGroups = @(Get-ADGroup `
-Path $groupDnMatch.Groups['Path'].Value ` -LDAPFilter "(sAMAccountName=$(ConvertTo-LdapFilterValue -Value $groupName))" `
-Description $definition.Description -Server $LdapHost | Out-Null -SearchBase $BaseDn -SearchScope Subtree -Server $LdapHost -ErrorAction Stop)
if ($matchingGroups.Count -gt 1) {
throw "More than one Active Directory group uses sAMAccountName $groupName; the bootstrap cannot select one safely."
}
if ($matchingGroups.Count -eq 1) {
if ($matchingGroups[0].GroupCategory -ne 'Security') {
throw "$($definition.Role)GroupDn must identify a security group."
}
Move-ADObject -Identity $matchingGroups[0].DistinguishedName `
-TargetPath $groupPath -Server $LdapHost -Confirm:$false -ErrorAction Stop
}
else {
New-ADGroup -Name $groupName -SamAccountName $groupName `
-GroupCategory Security -GroupScope Global `
-Path $groupPath `
-Description $definition.Description -Server $LdapHost | Out-Null
}
$roleGroup = Get-ADGroup -Identity $definition.Dn -Server $LdapHost -ErrorAction Stop $roleGroup = Get-ADGroup -Identity $definition.Dn -Server $LdapHost -ErrorAction Stop
} }
if (-not $roleGroup) { if (-not $roleGroup) {
+1 -1
View File
@@ -116,7 +116,7 @@ Bootstrap reproducible para el laboratorio SGU.
- `sgu-azure-infrastructure-$Version.zip`: despliega mediante Bicep una VM Windows Server 2025, red privada, IP pública protegida por NSG y Azure VPN Gateway P2S; también genera certificados por equipo y descarga el perfil de cliente. - `sgu-azure-infrastructure-$Version.zip`: despliega mediante Bicep una VM Windows Server 2025, red privada, IP pública protegida por NSG y Azure VPN Gateway P2S; también genera certificados por equipo y descarga el perfil de cliente.
- El bootstrap Azure conserva la IP privada administrada por la NIC de Azure, autoriza el pool P2S en los firewalls SGU y nunca publica LDAP, Kerberos, SMB, RPC, WinRM ni el Auth Broker directamente a Internet. - El bootstrap Azure conserva la IP privada administrada por la NIC de Azure, autoriza el pool P2S en los firewalls SGU y nunca publica LDAP, Kerberos, SMB, RPC, WinRM ni el Auth Broker directamente a Internet.
- Los Windows 11 Pro pueden instalar un perfil IKEv2 de todos los usuarios con certificado de máquina, DNS dividido para `lci.lasalle.mx` y ejecutarlo desde la pantalla de inicio de sesión antes de autenticar una cuenta de dominio nueva. - Los Windows 11 Pro pueden instalar un perfil IKEv2 de todos los usuarios con certificado de máquina, DNS dividido para `lci.lasalle.mx` y ejecutarlo desde la pantalla de inicio de sesión antes de autenticar una cuenta de dominio nueva.
- El Auth Broker clasifica sin tareas programadas cada cuenta autenticada: `AL` se agrega a `SGU-Alumnos`, `AD` a `SGU-Administrativos` y `DO` a `SGU-Docentes`; el bootstrap crea estos grupos de seguridad de forma idempotente. - El Auth Broker clasifica sin tareas programadas cada cuenta autenticada: `AL` se agrega a `SGU-Alumnos`, `AD` a `SGU-Administrativos` y `DO` a `SGU-Docentes`; el bootstrap crea cada grupo dentro de la OU de su rol y migra idempotentemente cualquier grupo heredado sin cambiar su SID.
- El enriquecimiento obtiene el sexo de los módulos SGU de personal/alumnos, lo conserva como la línea administrada `SGU-Gender: Male|Female` en Notas de AD y adapta el fondo de Windows/Linux; cuando falta utiliza redacción neutral. - El enriquecimiento obtiene el sexo de los módulos SGU de personal/alumnos, lo conserva como la línea administrada `SGU-Gender: Male|Female` en Notas de AD y adapta el fondo de Windows/Linux; cuando falta utiliza redacción neutral.
- El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker. - El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker.
- Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host. - Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host.
+3 -3
View File
@@ -176,11 +176,11 @@ public sealed class ActiveDirectoryOptions
public string AdministrativeOuDn { get; init; } = "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx"; public string AdministrativeOuDn { get; init; } = "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string ProfessorGroupDn { get; init; } = "CN=SGU-Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx"; public string ProfessorGroupDn { get; init; } = "CN=SGU-Docentes,OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string StudentGroupDn { get; init; } = "CN=SGU-Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx"; public string StudentGroupDn { get; init; } = "CN=SGU-Alumnos,OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string AdministrativeGroupDn { get; init; } = "CN=SGU-Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx"; public string AdministrativeGroupDn { get; init; } = "CN=SGU-Administrativos,OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string RemoteDesktopGroupDn { get; init; } = string.Empty; public string RemoteDesktopGroupDn { get; init; } = string.Empty;
+3 -3
View File
@@ -50,9 +50,9 @@
"ProfessorOuDn": "OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx", "ProfessorOuDn": "OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"StudentOuDn": "OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx", "StudentOuDn": "OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"AdministrativeOuDn": "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx", "AdministrativeOuDn": "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"ProfessorGroupDn": "CN=SGU-Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx", "ProfessorGroupDn": "CN=SGU-Docentes,OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"StudentGroupDn": "CN=SGU-Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx", "StudentGroupDn": "CN=SGU-Alumnos,OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"AdministrativeGroupDn": "CN=SGU-Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx", "AdministrativeGroupDn": "CN=SGU-Administrativos,OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"RemoteDesktopGroupDn": "", "RemoteDesktopGroupDn": "",
"DefaultCompany": "La Salle", "DefaultCompany": "La Salle",
"CreateMissingOus": false "CreateMissingOus": false
@@ -31,9 +31,9 @@ public sealed class BrokerOptionsTests
} }
[Theory] [Theory]
[InlineData(InstitutionalRole.Student, "CN=SGU-Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx")] [InlineData(InstitutionalRole.Student, "CN=SGU-Alumnos,OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx")]
[InlineData(InstitutionalRole.Administrative, "CN=SGU-Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx")] [InlineData(InstitutionalRole.Administrative, "CN=SGU-Administrativos,OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx")]
[InlineData(InstitutionalRole.Professor, "CN=SGU-Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx")] [InlineData(InstitutionalRole.Professor, "CN=SGU-Docentes,OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx")]
public void DefaultRoleGroupMappingsMatchInstitutionalPrefixes(InstitutionalRole role, string expectedGroupDn) public void DefaultRoleGroupMappingsMatchInstitutionalPrefixes(InstitutionalRole role, string expectedGroupDn)
{ {
ActiveDirectoryOptions options = new(); ActiveDirectoryOptions options = new();