Secretos y certificados de apps por vencer
Revisa todos los registros de aplicaciones y te avisa qué secretos y certificados ya vencieron o vencen pronto. Evita que una integración se caiga sin aviso.
Ejemplo en México
Logística en Monterrey, 220 personas
El conector entre el sistema de rastreo y Microsoft 365 dejó de funcionar un lunes porque venció un secreto que nadie tenía en el calendario.
.\get-secretos-apps-por-vencer.ps1 -Dias 60 -RutaCsv .\credenciales.csvVe dos secretos vencidos y cuatro que vencen en menos de 60 días. Agenda la rotación de cada uno y repite el reporte cada mes.
Cómo implementarlo
- Abre PowerShell 7 con el módulo Microsoft.Graph.Authentication instalado.
- Corre el script con -Dias y -RutaCsv e inicia sesión con una cuenta con el rol Lector global.
- Ordena el CSV por DiasRestantes para ver primero lo más urgente.
- Avisa al dueño de cada aplicación, rota la credencial y borra la vieja cuando la nueva ya funcione.
Ver el código completo de get-secretos-apps-por-vencer.ps1
<#
.SYNOPSIS
¿Qué secretos y certificados de app registrations están vencidos o por vencer?
.DESCRIPTION
Recorre los registros de aplicaciones y revisa la fecha de vencimiento de cada secreto y certificado.
Una credencial vencida suele tumbar una integración sin aviso, así que esto te da tiempo de rotarla.
No lee ni exporta el valor de ningún secreto.
Solo lectura: no cambia nada en el tenant.
.PARAMETER RutaCsv
Ruta del CSV de salida. Si se omite, solo se muestra en pantalla.
.PARAMETER Dias
Días hacia adelante para considerar una credencial por vencer. Por defecto, 30.
.PARAMETER ZonaHoraria
Zona horaria para mostrar las fechas. Por defecto, Central Standard Time (Mexico).
.EXAMPLE
.\get-secretos-apps-por-vencer.ps1 -Dias 60 -RutaCsv .\credenciales.csv
.NOTES
Permisos: Application.Read.All
Rol mínimo: Lector global (Global Reader)
Licencia: Microsoft Entra ID Free
Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas.
#>
[CmdletBinding()]
param(
[string]$RutaCsv,
[ValidateRange(1, 36500)][int]$Dias = 30,
[string]$ZonaHoraria = 'Central Standard Time (Mexico)'
)
$ErrorActionPreference = 'Stop'
$permisos = @('Application.Read.All')
if (-not (Get-Command Connect-MgGraph -ErrorAction SilentlyContinue)) {
throw 'Falta el módulo. Instálalo con: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser'
}
$zona = [TimeZoneInfo]::FindSystemTimeZoneById($ZonaHoraria)
Connect-MgGraph -Scopes $permisos -NoWelcome
function ConvertTo-FechaUtc {
param($Valor)
# Graph puede dar texto o fecha; un secreto puede no traer vencimiento.
if (-not $Valor) { return $null }
if ($Valor -is [datetimeoffset]) { return $Valor.UtcDateTime }
if ($Valor -is [datetime]) { if ($Valor.Kind -eq 'Unspecified') { return [datetime]::SpecifyKind($Valor, 'Utc') } else { return $Valor.ToUniversalTime() } }
[datetimeoffset]::Parse("$Valor", [cultureinfo]::InvariantCulture).UtcDateTime
}
$ahora = [datetime]::UtcNow
$limite = $ahora.AddDays($Dias)
$uri = 'https://graph.microsoft.com/v1.0/applications?$select=id,appId,displayName,passwordCredentials,keyCredentials&$top=999'
$apps = while ($uri) {
$pagina = Invoke-MgGraphRequest -Method GET -Uri $uri
$pagina.value
$uri = $pagina.'@odata.nextLink'
}
$filas = foreach ($app in $apps) {
$credenciales = @(
foreach ($c in $app.passwordCredentials) { @{ Tipo = 'Secreto'; Dato = $c } }
foreach ($c in $app.keyCredentials) { @{ Tipo = 'Certificado'; Dato = $c } }
)
foreach ($credencial in $credenciales) {
$vence = ConvertTo-FechaUtc $credencial.Dato.endDateTime
if (-not $vence -or $vence -gt $limite) { continue }
[pscustomobject]@{
Aplicacion = $app.displayName
AppId = $app.appId
Tipo = $credencial.Tipo
Credencial = $credencial.Dato.displayName
KeyId = $credencial.Dato.keyId
Estado = if ($vence -lt $ahora) { 'Vencido' } else { 'Por vencer' }
Vence = [TimeZoneInfo]::ConvertTimeFromUtc($vence, $zona).ToString('yyyy-MM-dd HH:mm', [cultureinfo]::InvariantCulture)
DiasRestantes = [int][math]::Floor(($vence - $ahora).TotalDays)
}
}
}
$filas = $filas | Sort-Object DiasRestantes
Write-Host "Credenciales vencidas o que vencen en $Dias días: $(@($filas).Count)"
if ($RutaCsv) {
# Excel ejecutaría como fórmula un nombre que empiece con estos caracteres.
foreach ($fila in $filas) { foreach ($p in $fila.PSObject.Properties) { if ($p.Value -is [string] -and $p.Value -match '^[=+\-@]') { $p.Value = "'" + $p.Value } } }
# UTF-8 con BOM para que Excel en español muestre bien los acentos.
$filas | Export-Csv -Path $RutaCsv -NoTypeInformation -Encoding utf8BOM
Write-Host "CSV guardado en $RutaCsv"
} else {
$filas | Format-Table -AutoSize
}