Framework/Helpers/ActiveDirectoryHelper.ps1
Set-StrictMode -Version Latest class ActiveDirectoryHelper { static [PSObject] GetADAppServicePrincipalByAppId($ApplicationId) { $TenantId = ([ContextHelper]::GetCurrentRMContext()).Tenant.Id $ApiVersion = "1.6" $GraphUri = [WebRequestHelper]::GetGraphUrl() $GraphApiUrl = $GraphUri + $TenantId + "/servicePrincipals/{0}?api-version=$ApiVersion" $uri = [string]::Format($GraphApiUrl + "&`$filter=(appId eq '{1}')", [string]::Empty , $ApplicationId); $resultObject = [WebRequestHelper]::InvokeGetWebRequest($uri); #this returns array of objects. Actual object is present at 1st index if($resultObject) { return $resultObject[0] } else { return $null } } static [void] UpdateADAppServicePrincipalCredential( $ApplicationID, [System.Security.Cryptography.X509Certificates.X509Certificate2] $PublicCert, [System.DateTime] $NotBefore = (Get-Date).AddDays(-1), [System.DateTime] $NotAfter = $NotBefore.AddMonths(6), [string] $Delete = "False" ) { #Initialization $TenantId = ([ContextHelper]::GetCurrentRMContext()).Tenant.Id $ApiVersion = "1.6" $GraphUri = [WebRequestHelper]::GetGraphUrl() $GraphApiUrl = $GraphUri + $TenantId + "/servicePrincipals/{0}?api-version=$ApiVersion" $addMode = $False; $startDateString = $NotBefore.ToString("O"); $endDateString = $NotAfter.ToString("O"); if($Delete -eq "False") { if(-not $PublicCert) { throw "Public Certificate cannot be null" } } $servicePrincipal = [ActiveDirectoryHelper]::GetADAppServicePrincipalByAppId($ApplicationID) if($Delete -eq "False") { if($null -eq $servicePrincipal) { $addMode = $True; $servicePrincipal = New-Object -TypeName PSObject; $servicePrincipal | Add-Member -MemberType NoteProperty -Name appId -Value $ApplicationID -PassThru } $publicCertString = [System.Convert]::ToBase64String($PublicCert.GetRawCertData()); $credentialObject = New-Object -TypeName PSObject $credentialObject | Add-Member -MemberType NoteProperty -Name endDate -Value $endDateString -PassThru ` | Add-Member -MemberType NoteProperty -Name startDate -Value $startDateString -PassThru $credentialObject | Add-Member -MemberType NoteProperty -Name type -Value "AsymmetricX509Cert" -PassThru ` | Add-Member -MemberType NoteProperty -Name usage -Value "Verify" -PassThru ` | Add-Member -MemberType NoteProperty -Name value -Value $publicCertString if ([bool](Get-Member -InputObject $servicePrincipal -Name "keyCredentials")) { [System.Collections.ArrayList]$keys = $servicePrincipal.keyCredentials $credentialList = $keys.Add($credentialObject) $servicePrincipal.keyCredentials = $keys } else { $servicePrincipal | Add-Member -MemberType NoteProperty -Name keyCredentials -Value @($credentialObject) } } elseif($Delete -eq "True") { $servicePrincipal.keyCredentials = $servicePrincipal.keyCredentials | Where-Object { [System.DateTime]::Parse($_.startDate).ToUniversalTime() -ne $NotBefore.ToUniversalTime() ` -and [System.DateTime]::Parse($_.endDate).ToUniversalTime() -ne $NotAfter.ToUniversalTime() ` } } elseif($Delete -eq "All") { $servicePrincipal.keyCredentials = @() } $servicePrincipal = $servicePrincipal | Select-Object * -ExcludeProperty "requiredResourceAccess" $body = ConvertTo-Json -InputObject $servicePrincipal $operation = [string]::Empty; $requestUri = [string]::Empty; $ResourceAppIdURI = [WebRequestHelper]::GetGraphUrl() $GraphAPIAccessToken = Get-AzSKAccessToken -ResourceAppIdURI $ResourceAppIdURI; if($addMode) { $operation = "POST"; $requestUri = [string]::Format($GraphApiUrl, [string]::Empty); } else { $operation = "PATCH"; $requestUri = [string]::Format($GraphApiUrl, $servicePrincipal.objectId); } $updateResult = Invoke-RestMethod ` -Method $operation ` -Uri $requestUri ` -Headers @{ Authorization = "Bearer " + $GraphAPIAccessToken } ` -ContentType "application/json" ` -Body $body ` -UseBasicParsing if($null -eq $updateResult) { Throw "There was a problem while updating the service principal with new certificate" } } static [PSObject] GetADAppByAppId($ApplicationId) { $TenantId = ([ContextHelper]::GetCurrentRMContext()).Tenant.Id $ApiVersion = "1.6" $GraphUri = [WebRequestHelper]::GetGraphUrl() $GraphApiUrl = $GraphUri + $TenantId + "/applications/{0}?api-version=$ApiVersion" $uri = [string]::Format($GraphApiUrl + "&`$filter=(appId eq '{1}')", [string]::Empty , $ApplicationId); $resultObject = [WebRequestHelper]::InvokeGetWebRequest($uri); #this returns array of objects. Actual object is present at 1st index if($resultObject) { return $resultObject[0] } else { return $null } } static [void] UpdateADAppCredential( $ApplicationID, [System.Security.Cryptography.X509Certificates.X509Certificate2] $PublicCert, [System.DateTime] $NotBefore = (Get-Date).AddDays(-1), [System.DateTime] $NotAfter = $NotBefore.AddMonths(6), [string] $Delete = "False" ) { #Initialization $TenantId = ([ContextHelper]::GetCurrentRMContext()).Tenant.Id $ApiVersion = "1.6" $GraphUri = [WebRequestHelper]::GetGraphUrl() $GraphApiUrl = $GraphUri + $TenantId + "/applications/{0}?api-version=$ApiVersion" $startDateString = $NotBefore.ToString("O"); $endDateString = $NotAfter.ToString("O"); if($Delete -eq "False") { if(-not $PublicCert) { throw "Public Certificate cannot be null" } } $ADApplication = [ActiveDirectoryHelper]::GetADAppByAppId($ApplicationID) if($Delete -eq "False") { $publicCertString = [System.Convert]::ToBase64String($PublicCert.GetRawCertData()); $credentialObject = New-Object -TypeName PSObject $credentialObject | Add-Member -MemberType NoteProperty -Name endDate -Value $endDateString -PassThru ` | Add-Member -MemberType NoteProperty -Name startDate -Value $startDateString -PassThru $credentialObject | Add-Member -MemberType NoteProperty -Name type -Value "AsymmetricX509Cert" -PassThru ` | Add-Member -MemberType NoteProperty -Name usage -Value "Verify" -PassThru ` | Add-Member -MemberType NoteProperty -Name value -Value $publicCertString if ([bool](Get-Member -InputObject $ADApplication -Name "keyCredentials")) { [System.Collections.ArrayList]$keys = $ADApplication.keyCredentials $keys.Add($credentialObject) $ADApplication.keyCredentials = $keys } else { $ADApplication | Add-Member -MemberType NoteProperty -Name keyCredentials -Value @($credentialObject) } } elseif($Delete -eq "True") { $ADApplication.keyCredentials = $ADApplication.keyCredentials | Where-Object { [System.DateTime]::Parse($_.startDate).ToUniversalTime() -ne $NotBefore.ToUniversalTime() ` -and [System.DateTime]::Parse($_.endDate).ToUniversalTime() -ne $NotAfter.ToUniversalTime() } } elseif($Delete -eq "All") { $ADApplication.keyCredentials = @() } elseif($Delete -eq "DeleteSelected") { # Collecting all Certificates -> Old + Latest Certificates [System.Collections.ArrayList]$AllCerts = $ADApplication.keyCredentials # Filtering out the latest Certificate among all Certificates [System.Collections.ArrayList]$latestCert = @($AllCerts | Where-Object { [System.DateTime]::Parse($_.startDate).ToUniversalTime() -eq $NotBefore.ToUniversalTime() ` -and [System.DateTime]::Parse($_.endDate).ToUniversalTime() -eq $NotAfter.ToUniversalTime() }) # Filtering out the Older Certificates associated with CA SPN [System.Collections.ArrayList]$OldCerts = @($AllCerts | Where-Object { $latestCert -notcontains $_ }) if($OldCerts.count -gt 0) { Write-host "We found the following older credentials associated with [$($ADApplication.displayname)]:" -ForegroundColor Yellow # Displaying older Certificates in form of table $display= $OldCerts|Format-Table -Property @{name="Index";expression={$OldCerts.IndexOf($_)}},@{name="Thumbprint";expression={$_.customKeyIdentifier}},@{name="EndDate(MM-dd-yyyy)";expression={([datetime] $_.endDate).ToString("MM/dd/yyyy")}} | Out-String Write-Host $display Write-host "Before deleting make sure that these certificates are not used anywhere else!" -ForegroundColor Yellow Write-Host "Please select an action from below: `n[A]: Delete all`n[N]: Delete none`n[S]: Delete selected" -ForegroundColor Cyan # Initializing an empty array list to add certificates for deletion [System.Collections.ArrayList] $CertificatesToRemove = @() $userChoice="" while($userChoice -ne 'A' -and $userChoice -ne 'N' -and $userChoice -ne 'S') { $userChoice = Read-Host "User choice" if(-not [string]::IsNullOrWhiteSpace($userChoice)) { $userChoice = $userChoice.Trim(); } } # Variable used for taking confirmation for any/all Certificate deletion. $confirmation="" switch ($userChoice.ToUpper()) { "A" #DeleteAll { while($confirmation.ToUpper() -ne 'Y' -and $confirmation.ToUpper() -ne 'N') { $confirmation = Read-Host "Do you want to delete all certificates ? (Y/N)" if(-not [string]::IsNullOrWhiteSpace($confirmation)) { $confirmation = $confirmation.Trim(); } } if($confirmation.ToUpper() -eq 'Y') { Write-Host "Deleting all certificates. This may take few min..." -ForegroundColor Yellow $ADApplication.keyCredentials = $latestCert } else { Write-Host "No certificates were deleted." -ForegroundColor Yellow } break } "N" #None { Write-Host "No certificates were deleted." -ForegroundColor Yellow break } "S" #Select { do{ # flag used for validating the indexes entered by user. $validIndexFlag=$true $invalidindexes="" $indexs=Read-Host "Enter comma separated index(es) from the above table" $indexs = $indexs.Trim(); if([string]::IsNullOrWhiteSpace($indexs) -or $indexs -eq ',') { Write-Host "You have entered blank values, please enter a valid index." -ForegroundColor Yellow $validIndexFlag=$false } else { $indexArray = $indexs.Split(',',[System.StringSplitOptions]::RemoveEmptyEntries).Trim() $indexArray | ForEach-Object{ $currentIndex=$_ try { #Using Array index property to validate whether the index is valid or not. if($OldCerts[$currentIndex]){ } } catch { $validIndexFlag = $false # Collecting all invalid indexes and making a comma separated string like '1,2,' # so that same string can be displayed in case of invalid indexes $invalidindexes += $currentIndex+"," } } if($validIndexFlag) { # All indexes are valid. $OldCerts | Where-Object { if($indexArray -contains $OldCerts.IndexOf($_)) { $CertificatesToRemove.add($OldCerts[$OldCerts.IndexOf($_)]) } } Write-Host "Certificates selected for deletion: " -ForegroundColor Cyan $output=$CertificatesToRemove|Format-Table -Property @{name="Thumbprint(s)";expression={$_.customKeyIdentifier}} | Out-String Write-Host $output while($confirmation.ToUpper() -ne 'Y' -and $confirmation.ToUpper() -ne 'N') { $confirmation = Read-Host "Do you want to delete the selected certificates ? (Y/N)" if(-not [string]::IsNullOrWhiteSpace($confirmation)) { $confirmation = $confirmation.Trim(); } } if($confirmation.ToUpper() -eq 'Y') { $ADApplication.keyCredentials = $AllCerts | Where-Object { $CertificatesToRemove -notcontains $_ } $ADApplication.keyCredentials=[System.Collections.ArrayList]@($ADApplication.keyCredentials) Write-Host "Deleting selected certificates." -ForegroundColor Yellow } else { Write-Host "No certificates were deleted." -ForegroundColor Yellow } } else { # All/ Any index is/are invalid Write-Host "Please provide valid index(es) from above table." -ForegroundColor Yellow #Checking the count of invalid indexes so that valid message ( for 1 or many invalid indexes) can be displayed. if($invalidindexes.Split(',',[System.StringSplitOptions]::RemoveEmptyEntries).count -eq 1) { # Printing invalidindexes string without last comma => 1, => 1 Write-Host " $(-join$invalidindexes[0..($invalidindexes.Length-2)]) is not a valid index. " } else { # Printing invalidindexes string without last comma => 1,2, => 1,2 Write-Host " $(-join$invalidindexes[0..($invalidindexes.Length-2)]) are not valid indexes. " } Write-Host "No certificates were deleted." -ForegroundColor Yellow } } }while(-not($validIndexFlag)) break } Default { Write-Host "You have entered incorrect choice. Please enter valid choice." -ForegroundColor Yellow } } } else { Write-Host "There are no old credentials associated with [$($ADApplication.displayname)]" -ForegroundColor Yellow Write-Host "Skipping the process for deleting old certificates." -ForegroundColor Yellow } } $finalCredsObject = $ADApplication | Select-Object -Property keyCredentials $body = ConvertTo-Json -InputObject $finalCredsObject $operation = [string]::Empty; $requestUri = [string]::Empty; $ResourceAppIdURI = [WebRequestHelper]::GetGraphUrl() $GraphAPIAccessToken = Get-AzSKAccessToken -ResourceAppIdURI $ResourceAppIdURI; $operation = "PATCH"; $requestUri = [string]::Format($GraphApiUrl, $ADApplication.objectId); $updateResult = Invoke-RestMethod ` -Method $operation ` -Uri $requestUri ` -Headers @{ Authorization = "Bearer " + $GraphAPIAccessToken } ` -ContentType "application/json" ` -Body $body ` -UseBasicParsing if($null -eq $updateResult) { Throw "There was a problem while updating the service principal with new certificate" } } static [PSObject] NewSelfSignedCertificate($AppName,$CertStartDate,$CertEndDate,$Provider) { $newCertificate = New-SelfSignedCertificate -DnsName $AppName ` -Subject "CN=$AppName" ` -CertStoreLocation Cert:\CurrentUser\My ` -KeyExportPolicy Exportable ` -NotBefore $CertStartDate ` -NotAfter $CertEndDate ` -Type DocumentEncryptionCert ` -KeyUsage DataEncipherment ` -KeySpec KeyExchange ` -KeyUsageProperty Decrypt ` -Provider $Provider ` -ErrorAction Stop return $newCertificate } } |