Update-AADToolkitApplicationCredentials.ps1
function Show-AppInfo ($appInfo){ Write-Host $appDisplay = $appInfo | Format-List -Property objectType, objectId, displayName, appId | Out-String $appCreds = $appInfo.creds | Format-Table -Property keyId, startDateTime, endDateTime, expired, credentialtype, description | Out-String Write-Host $appDisplay Write-Host $appCreds } function Show-Menu { Param($appInfo, [string[]]$MenuItems, [string] $Title ) $header = $null if (![string]::IsNullOrWhiteSpace($Title)) { $len = [math]::Max(($MenuItems | Measure-Object -Maximum -Property Length).Maximum, $Title.Length) $header = '{0}{1}{2}' -f $Title, [Environment]::NewLine, ('-' * $len) } # possible choices: didits 1 to 9, characters A to Z $choices = (49..57) + (65..90) | ForEach-Object { [char]$_ } $i = 0 $items = ($MenuItems | ForEach-Object { '[{0}] {1}' -f $choices[$i++], $_ }) -join [Environment]::NewLine # display the menu and return the chosen option while ($true) { Clear-Host if ($header) { Write-Host $header -ForegroundColor Yellow } Write-Host $items Show-AppInfo $appInfo Write-Host $answer = (Read-Host -Prompt 'Please make your choice').ToUpper() $index = $choices.IndexOf($answer[0]) if ($index -ge 0 -and $index -lt $MenuItems.Count) { return $MenuItems[$index] } else { Write-Warning "Invalid choice.. Please try again." Start-Sleep -Seconds 2 } } } function Get-CredentialInfo ($cred, $credentialType) { $expired = "No" if(Get-IsExpired -date $cred.endDateTime){ $expired = "Yes" } [pscustomobject]@{ CredentialType = $credentialType KeyId = $cred.keyId Hint = $cred.hint Description = $cred.displayName StartDateTime = $cred.startDateTime EndDateTime = $cred.endDateTime KeyType = $cred.type Usage = $cred.usage Expired = $expired } } function Invoke-CredentialRollover ($appInfo, $keyId){ $rolloverKey = $appInfo.Creds | Where-Object {$_.KeyId -eq $keyId} if($rolloverKey) { switch ($rolloverKey.CredentialType) { $credentialTypePassword { Add-Password -objectId $appInfo.objectId $appInfo.objectType Remove-Password -objectId $appInfo.objectId -objectType $appInfo.objectType -keyId $rolloverKey.keyId } $credentialTypeKey { $certFilePath = Read-Host -Prompt 'Enter the path to the certificate file' $appInfo.keyCredentials = $appInfo.keyCredentials | Where-Object {$_.keyId -ne $keyId} Add-Key -objectId $appInfo.objectId $appInfo.objectType -certFilePath $certFilePath } } } else { Write-Error "Secret or certificate with this KeyId was not found." -ErrorAction Stop } } function Get-IsExpired($date){ return (Get-Date).Subtract($date) -gt 0 } function Remove-AppCredentials($appInfo, $selection) { switch ($selection) { $menuRemoveAll { $credsToRemove = $appInfo.Creds } $menuRemoveSecrets { $credsToRemove = $appInfo.Creds | Where-Object {$_.CredentialType -eq $credentialTypePassword} } $menuRemoveCerts { $credsToRemove = $appInfo.Creds | Where-Object {$_.CredentialType -eq $credentialTypeKey} } $menuRemoveExpiredAll { $credsToRemove = $appInfo.Creds | Where-Object {Get-IsExpired -date $_.endDateTime } } $menuRemoveExpiredSecrets{ $credsToRemove = $appInfo.Creds | Where-Object {(Get-IsExpired -date $_.endDateTime) -and $_.CredentialType -eq $credentialTypePassword } } $menuRemoveExpiredCerts { $credsToRemove = $appInfo.Creds | Where-Object {(Get-IsExpired -date $_.endDateTime) -and $_.CredentialType -eq $credentialTypeKey } } } # Remove passwords foreach($cred in $credsToRemove){ if($cred.CredentialType -eq $credentialTypePassword){ Remove-Password -objectId $appInfo.objectId -objectType $appInfo.objectType -keyId $cred.keyId } } # Remove keys Remove-Keys -appInfo $appInfo -credsToRemove $credsToRemove } function Remove-Keys ($appInfo, $credsToRemove) { $keyCredsToRemove = $credsToRemove | Where-Object {$_.CredentialType -eq $credentialTypeKey} if($keyCredsToRemove.Length -gt 0){ foreach($cred in $keyCredsToRemove) { Write-Host ("Removing certificate ({0}) from {1} ({2})" -f $cred.keyId, $appInfo.ObjectType, $appInfo.ObjectId) $appInfo.keyCredentials = $appInfo.keyCredentials | Where-Object {$_.keyId -ne $cred.keyId} } if($appInfo.keyCredentials.Length -eq 0){ # Convert null to an empty array to generate a Graph compatible json $appInfo.KeyCredentials = @() } $body = @{keyCredentials = $appInfo.KeyCredentials} | ConvertTo-Json $uri = '/{0}/{1}' -f $appInfo.GraphObjectType, $appInfo.objectId Invoke-AADTGraph -uri $uri -body $body -method PATCH } } function Remove-Password($objectId, $objectType, $keyId){ Write-Host ("Removing client secret ({0}) from {1} ({2})" -f $keyId, $objectType, $objectId) switch ($objectType) { $objectTypeApplication { $graphObjectType = 'applications' } $objectTypeServicePrincipal { $graphObjectType = 'servicePrincipals' } } $uri = "/$graphObjectType/$objectId/removePassword" $body = @{keyId=$keyId} | ConvertTo-Json Invoke-AADTGraph -uri $uri -method POST -body $body } function Add-Password($objectId, $objectType){ Write-Host ("Rolling over client secret for {0} ({1})" -f $objectType, $objectId) switch ($objectType) { $objectTypeApplication { $graphObjectType = 'applications' } $objectTypeServicePrincipal { $graphObjectType = 'servicePrincipals' } } $uri = "/$graphObjectType/$objectId/addPassword" $body = @{passwordCredential = @{displayName="Rollover"; endDateTime=((Get-Date).AddYears(1).ToString('s'))} } | ConvertTo-Json Write-Host $body Invoke-AADTGraph -uri $uri -method POST -body $body } function Add-Key($objectId, $objectType, $certFilePath){ Write-Host ("Rolling over certificate for {0} ({1})" -f $objectType, $objectId) switch ($objectType) { $objectTypeApplication { $graphObjectType = 'applications' } $objectTypeServicePrincipal { $graphObjectType = 'servicePrincipals' } } $uri = "/$graphObjectType/$objectId/addKey" $cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certFilePath) $bin = $cer.GetRawCertData() $base64Value = [System.Convert]::ToBase64String($bin) $bin = $cer.GetCertHash() $base64Thumbprint = [System.Convert]::ToBase64String($bin) $keyId = [System.Guid]::NewGuid().ToString() $newKeyCredential = @{ customKeyIdentifier = $base64Thumbprint displayName = 'Rollover' keyId = $keyId type = 'AsymmetricX509Cert' usage = 'Verify' key = $base64Value } $appInfo.KeyCredentials += $newKeyCredential $body = @{keyCredentials = $appInfo.KeyCredentials} | ConvertTo-Json $uri = '/{0}/{1}' -f $appInfo.GraphObjectType, $appInfo.objectId Invoke-AADTGraph -uri $uri -body $body -method PATCH } function Get-GraphSearchResults($type, $searchObjectId){ $searchValue = "'$searchObjectId'" $uri = '/{0}?$filter=id eq {1} or id eq {1}' -f $type, $searchValue #Repear with or to avoid PowerShell Graph SDK throwing errors when there are no entries return Invoke-AADTGraph -uri $uri } function Get-GraphApp($searchObjectId){ $result = Get-GraphSearchResults -type $graphObjectTypeApplications -searchObjectId $searchObjectId if($result.value.length -eq 1){ $ObjectType = $objectTypeApplication $graphObjectType = $graphObjectTypeApplications $app = $result.value } else { Write-Host "Not an app checking ServicePrincipals" $result = Get-GraphSearchResults -type $graphObjectTypeServicePrincipals -searchObjectId $searchObjectId if($result.value.length -eq 1){ $ObjectType = $objectTypeServicePrincipal $graphObjectType = $graphObjectTypeServicePrincipals $app = $result.value } else { Write-Error "ObjectId not found" -ErrorAction Stop } } $creds = @() foreach($cred in $app.passwordCredentials) { $creds += Get-CredentialInfo -cred $cred -credentialType $credentialTypePassword } foreach($cred in $app.keyCredentials) { $creds += Get-CredentialInfo -cred $cred -credentialType $credentialTypeKey } [pscustomobject]@{ ObjectType = $objectType GraphObjectType = $graphObjectType Creds = $creds KeyCredentials = $app.keyCredentials #Until GraphAPI supports removing KeyCredentials, need to cache the KeyCredentials and use them with a PATCH ObjectId = $app.id DisplayName = $app.displayName AppId = $app.appId } } function Update-AADToolkitApplicationCredentials { $searchObjectId = Read-Host -Prompt 'Enter the ObjectId of the Application or Service Principal' $objectTypeApplication = 'Application' $objectTypeServicePrincipal = 'Service Principal' $graphObjectTypeApplications = 'applications' $graphObjectTypeServicePrincipals = 'servicePrincipals' $credentialTypePassword = 'Client secret' $credentialTypeKey = 'Certificate' $menuRemoveAll = 'Remove all certificates and secrets' $menuRemoveCerts = 'Remove all certificates' $menuRemoveSecrets = 'Remove all secrets' $menuRemoveExpiredAll = 'Remove expired certificates and secrets' $menuRemoveExpiredCerts = 'Remove expired certificates' $menuRemoveExpiredSecrets = 'Remove expired secrets' $menuRolloverCred = 'Rollover a certificate or secret' $menuQuit = 'Quit' if(![guid]::TryParse($searchObjectId, $([ref][guid]::Empty))) { Write-Error "Invalid object identifier format" -ErrorAction Stop } else { $appInfo = Get-GraphApp -searchObjectId $searchObjectId if(!$appInfo){ Write-Error "Application or ServicePrincipal with this ObjectId was not found" } Show-AppInfo $appInfo ##TODO Filter to show options applicable for the selected app $menu = $menuRemoveAll, $menuRemoveCerts, $menuRemoveSecrets, $menuRemoveExpiredAll, $menuRemoveExpiredCerts, $menuRemoveExpiredSecrets, $menuRolloverCred, $menuQuit $selection = Show-Menu -appInfo $appInfo -MenuItems $menu -Title "What would you like to do?" switch ($selection) { {$_ -in $menuRemoveAll, $menuRemoveCerts, $menuRemoveSecrets, $menuRemoveExpiredAll, $menuRemoveExpiredCerts, $menuRemoveExpiredSecrets}{ Remove-AppCredentials -appInfo $appInfo -selection $selection } $menuRolloverCred { $rolloverKeyId = Read-Host -Prompt 'Enter the KeyId of the client secret or certificate to be rolled over' Invoke-CredentialRollover -appInfo $appInfo -keyId $rolloverKeyId } } } } |