Checks/Applications/ARG.APP.005.ps1
|
Register-ArgusCheck @{ Id = 'ARG.APP.005' Title = 'Certificates expiring within {DaysAhead} days' Area = 'Applications' Category = 'Credential hygiene' Severity = 'Medium' Description = 'Certificates (`keyCredentials` with usage **Verify**, used by the app to authenticate) that expire within the next **{DaysAhead}** days. A certificate counts as **replaced** when the same app already has another certificate valid beyond that window; the check fails only for certificates without a replacement.' Rationale = 'An expired certificate stops app-only integrations (automation, connectors, scripts). Planned renewal avoids outages.' Remediation = @{ Steps = "1. Ask the application owner which service uses the certificate.`n2. Issue a new certificate (ideally from Azure Key Vault with automatic renewal), upload it to the app registration and deploy it to the service.`n3. Remove the old certificate once nothing uses it." PortalUrl = 'https://entra.microsoft.com/' } References = @( @{ Title = 'Add and manage app credentials in Microsoft Entra ID'; Url = 'https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-credentials' } @{ Title = 'Enforce secret and certificate standards with application management policies'; Url = 'https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/tutorial-enforce-secret-standards' } ) Requires = @{ Services = @('Graph') GraphScopes = @('Application.Read.All') DataSets = @('Applications') } Parameters = @{ DaysAhead = 30 } Tags = @('Applications', 'Credentials') Test = { param($Context) $window = $Context.Now.AddDays([int]$Context.Parameters.DaysAhead) $valid = @(Get-ArgusAppCredential -Application @($Context.Data['Applications']) -Type Certificate -Usage Verify | Where-Object { $_.End -and $_.End -gt $Context.Now }) $findings = @($valid | Where-Object { $_.End -le $window } | ForEach-Object { $credential = $_ $replaced = [bool]($valid | Where-Object { $_.ObjectId -eq $credential.ObjectId -and $_.KeyId -ne $credential.KeyId -and $_.End -gt $window }) [pscustomobject]@{ AppName = $credential.AppName AppId = $credential.AppId CertificateName = $credential.CredentialName Expires = Format-ArgusDate $credential.End DaysLeft = [int][math]::Floor(($credential.End - $Context.Now).TotalDays) Replaced = if ($replaced) { 'Yes' } else { 'No' } KeyId = $credential.KeyId ObjectId = $credential.ObjectId } } | Sort-Object DaysLeft) $open = @($findings | Where-Object Replaced -EQ 'No').Count if ($findings.Count -eq 0) { return New-ArgusResult -Status Passed -EvaluatedCount $valid.Count -Summary 'No certificate expires within {DaysAhead} days ({EvaluatedCount} valid certificates evaluated).' } $status = if ($open -gt 0) { 'Failed' } else { 'Passed' } New-ArgusResult -Status $status -Findings $findings -EvaluatedCount $valid.Count ` -Summary ("{AffectedCount} of {EvaluatedCount} certificates expire within {DaysAhead} days; $open without a replacement certificate.") } } |