Create an Azure AD application with KeyVault & Azure PowerShell Certificate authentication - azure

I was trying to Create a Application in Azure AD with Azure PowerShell Certificate authentication, below is the Powershell snippet:
Login-AzureRmAccount
$certPassword = ConvertTo-SecureString $CertPassword -AsPlainText -Force
$x509 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certPath,$certPassword
$credValue = [System.Convert]::ToBase64String($x509.GetRawCertData())
$adapp = New-AzureRmADApplication -DisplayName $ApplicationName -HomePage $URL -IdentifierUris $URL -CertValue $credValue -StartDate $startDate -EndDate $endDate
$sp = New-AzureRmADServicePrincipal -ApplicationId $adapp.ApplicationId
Set-AzureRmKeyVaultAccessPolicy -VaultName $VaultName -ServicePrincipalName $sp.ServicePrincipalNames[1] -PermissionsToKeys all –PermissionsToSecrets all -ResourceGroupName $ResourceGroupName
The Azure AD application was created successfully, however for Azure AD application with Certificate Authentication, the customKeyIdentifier and value of in the keyCredentials is null after creation, this is the portion of manifest of my application I downloaded from Azure portal:
"keyCredentials": [{
"customKeyIdentifier": null,
"endDate": "2018-01-25T11:55:35.7680698Z",
"keyId": "ca1e536c-2220-478b-af73-1198d125bb5f",
"startDate": "2017-01-25T11:55:35.7680698Z",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": null
} ]
The certificate is a self signed certificate created using makecert command generated locally.
I am using Powershell Version of 2.0.1
C# Code to retrieve the token with Application Id & Thumbprint
public static async Task GetAccessToken(string authority,
string resource, string scope) {
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, AssertionCert);
return result.AccessToken; }
This Code errors out at var result with "Keyset does not exists"
Is there any way to resolve this issue?
Thank you :)

Did you look at the answer here?
Create a Application in Azure AD with Azure PowerShell Certificate authentication
In the comments he mentions that CustomKeyIdentifier being null does not matter for authentication.
Did you try authenticating regardless of the null value?\
EDIT:
If you want to generate a thumbprint for a public certificate you own, you can do so using the following powershell cmdlets:
$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cer.Import(“mycer.cer”)
$bin = $cer.GetCertHash()
$base64Thumbprint = [System.Convert]::ToBase64String($bin)
I hope this helps.

Related

Can't Connect to Exchange Online with a private certificate at Poweshell

I'm trying to create powershell script with the next flow.
Login to Azure Active Directory via Application.
Create Private Certificate.
Upload Certificate to Azure AD Application Certificates.
Connect to ExchangeOnline.
For this I created the next sсript according to the steps:
1st Step:
$clientId = 'xxx'
$tenantId = 'xxx'
$clientSecret = 'xxx'
$org = 'xxx.onmicrosoft.com'
$clientSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
$credObject = New-Object System.Management.Automation.PSCredential ($clientId, $clientSecret)
Connect-AzAccount -Credential $credObject -Tenant $tenantId -ServicePrincipal
2nd and 3d Step:
$cert = New-SelfSignedCertificate -DnsName $org -NotAfter (Get-Date).AddYears(1) -KeySpec KeyExchange
$binCert = $cert.GetRawCertData()
$credValue = [System.Convert]::ToBase64String($binCert)
$validFrom = [datetime]::Parse($cert.GetEffectiveDateString())
$validTo = [datetime]::Parse($pfx.GetExpirationDateString())
$validTo = $validTo.AddDays(-1);
New-AzADAppCredential -ApplicationId $clientId -CertValue $credValue -StartDate $validFrom -EndDate $validTo
And up to now all is going fine. I can see this certificate at certificates list of Application.
But when I'm going to connect to MS Exchange Online with this command:
Connect-ExchangeOnline -Certificate $cert -AppID $clientId -Organization $org
i getting the next issue:
{
"error":"invalid_client",
"error_description":"xxx: Client assertion contains an invalid signature. [Reason - The key used is expired., Thumbprint of key used by client: 'xxx', Found key 'Start=03/11/2021 14:59:26, End=03/11/2022 13:09:26', Please visit the Azure Portal, Graph Explorer or directly use MS Graph to see configured keys for app Id 'xxx'. Review the documentation at https://learn.microsoft.com/en-us/graph/deployments to determine the corresponding service endpoint and https://learn.microsoft.com/en-us/graph/api/application-get?view=graph-rest-1.0&tabs=http to build a query request URL, such as 'https://graph.microsoft.com/beta/applications/xxx']\r\nTrace ID: xxxx\r\nCorrelation ID: xxx\r\nTimestamp: 2021-03-11 13:15:28Z",
"error_codes":[
700027
],
"timestamp":"2021-03-11 13:15:28Z",
"trace_id":"xxx",
"correlation_id":"xxx",
"error_uri":"https://login.microsoftonline.com/error?code=700027"
}
But this newly created certificate could not expire. I'll be glad to see any idea. Stacked with this for few days.
EDIT:
Also i admitted that if i uploading newly created certificate with UI but not with this command:
New-AzADAppCredential -ApplicationId $clientId -CertValue $credValue -StartDate $validFrom -EndDate $validTo
Then i can to exchange online with newly created certificate
The issue lies on $validTo = $validTo.AddDays(-1);. As a result, $validTo is earlier than $cert.NotAfter.
Please modify the script like this:
$cert = New-SelfSignedCertificate -DnsName $org -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(1) -KeySpec KeyExchange
$binCert = $cert.GetRawCertData()
$credValue = [System.Convert]::ToBase64String($binCert)
New-AzADAppCredential -ApplicationId $clientId -CertValue $credValue -StartDate $cert.NotBefore -EndDate $cert.NotAfter

How to Add Api Permissions to an Azure App Registration using PowerShell

I am figure out the commands in Azure PowerShell to add an the User.Read Ape Permission to my App Registration in Azure.
I can find some examples using *Azure, but would prefer one that uses the *Az commands, e.g. https://learn.microsoft.com/en-us/powershell/azure/?view=azps-2.8.0.
Wonder if anybody knows how to do this? Thanks!
This can currently only be achieved using the Azure AD PowerShell. Please note that there is a difference between Azure AD PowerShell and Azure PowerShell. The Azure AD PowerShell is not simply the old Azure PowerShell module.
Azure AD PowerShell is a separate module. There is no "AZ*" for Azure AD yet. Only couple of most commonly used commands, that have Azure Resource Provider implementation.
Azure PowerShell has a limited set of features for working with Azure AD. If you need more features, like the one you mention, you must use Azure AD PowerShell. Azure AD PowerShell is not depricated and is the officially supported PowerShell module for working with Azure AD.
You can manage these required permissions by the Set-AzureAdApplication cmdlet and passing proper -RequiredResourceAccess object.
In order to construct this object, you must first get a reference to "exposed" permissions. Because permissions are exposed by other service principals.
as I cannot upload whole file, here is a PowerShell script that creates a sample application with required permission to some MS Graph and some Power BI permissions.
Function GetToken
{
param(
[String] $authority = "https://login.microsoftonline.com/dayzure.com/oauth2/token",
[String] $clientId,
[String] $clientSecret,
[String] $resourceId = "https://graph.windows.net"
)
$scope = [System.Web.HttpUtility]::UrlEncode($resourceId)
$encSecret = [System.Web.HttpUtility]::UrlEncode($clientSecret)
$body = "grant_type=client_credentials&resource=$($scope)&client_id=$($clientId)&client_secret=$($encSecret)"
$res = Invoke-WebRequest -Uri $authority -Body $body -Method Post
$authResult = $res.Content | ConvertFrom-Json
return $authResult.access_token
}
#`
# -RequiredResourceAccess #($requiredResourceAccess)
#
Function CreateChildApp
{
param (
[string] $displayName,
[string] $tenantName
)
# create your new application
Write-Output -InputObject ('Creating App Registration {0}' -f $displayName)
if (!(Get-AzureADApplication -SearchString $displayName)) {
$app = New-AzureADApplication -DisplayName $displayName `
-Homepage "https://localhost" `
-ReplyUrls "https://localhost" `
-IdentifierUris ('https://{0}/{1}' -f $tenantName, $displayName)
# create SPN for App Registration
Write-Output -InputObject ('Creating SPN for App Registration {0}' -f $displayName)
# create a password (spn key)
$appPwd = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId
$appPwd
# create a service principal for your application
# you need this to be able to grant your application the required permission
$spForApp = New-AzureADServicePrincipal -AppId $app.AppId -PasswordCredentials #($appPwd)
}
else {
Write-Output -InputObject ('App Registration {0} already exists' -f $displayName)
$app = Get-AzureADApplication -SearchString $displayName
}
#endregion
return $app
}
Function GrantAllThePermissionsWeWant
{
param
(
[string] $targetServicePrincipalName,
$appPermissionsRequired,
$childApp,
$spForApp
)
$targetSp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$($targetServicePrincipalName)'"
# Iterate Permissions array
Write-Output -InputObject ('Retrieve Role Assignments objects')
$RoleAssignments = #()
Foreach ($AppPermission in $appPermissionsRequired) {
$RoleAssignment = $targetSp.AppRoles | Where-Object { $_.Value -eq $AppPermission}
$RoleAssignments += $RoleAssignment
}
$ResourceAccessObjects = New-Object 'System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]'
foreach ($RoleAssignment in $RoleAssignments) {
$resourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess"
$resourceAccess.Id = $RoleAssignment.Id
$resourceAccess.Type = 'Role'
$ResourceAccessObjects.Add($resourceAccess)
}
$requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$requiredResourceAccess.ResourceAppId = $targetSp.AppId
$requiredResourceAccess.ResourceAccess = $ResourceAccessObjects
# set the required resource access
Set-AzureADApplication -ObjectId $childApp.ObjectId -RequiredResourceAccess $requiredResourceAccess
Start-Sleep -s 1
# grant the required resource access
foreach ($RoleAssignment in $RoleAssignments) {
Write-Output -InputObject ('Granting admin consent for App Role: {0}' -f $($RoleAssignment.Value))
New-AzureADServiceAppRoleAssignment -ObjectId $spForApp.ObjectId -Id $RoleAssignment.Id -PrincipalId $spForApp.ObjectId -ResourceId $targetSp.ObjectId
Start-Sleep -s 1
}
}
cls
#globaladminapp
$clientID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
$key = "****"
$tenantId = "aaaaaaaa-bbbb-xxxx-yyyy-aaaaaaaaaaaa";
$TenantName = "customdomain.com";
$AppRegName = "globaladminChild-0003";
$token = GetToken -clientId $clientID -clientSecret $key
Disconnect-AzureAD
Connect-AzureAD -AadAccessToken $token -AccountId $clientID -TenantId $tenantId
$appPermissionsRequired = #('Application.ReadWrite.OwnedBy', 'Device.ReadWrite.All', 'Domain.ReadWrite.All')
$targetServicePrincipalName = 'Windows Azure Active Directory'
#$appPermissionsRequired = #('Files.ReadWrite.All','Sites.FullControl.All','Notes.ReadWrite.All')
#$targetServicePrincipalName = 'Microsoft Graph'
$app = CreateChildApp -displayName $AppRegName -tenantName $TenantName
$spForApp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$($AppRegName)'"
$appPermissionsRequired = #('Tenant.ReadWrite.All')
$targetServicePrincipalName = 'Power BI Service'
GrantAllThePermissionsWeWant -targetServicePrincipalName $targetServicePrincipalName -appPermissionsRequired $appPermissionsRequired -childApp $app -spForApp $spForApp
$appPermissionsRequired = #('Files.ReadWrite.All','Sites.FullControl.All','Notes.ReadWrite.All')
$targetServicePrincipalName = 'Microsoft Graph'
GrantAllThePermissionsWeWant -targetServicePrincipalName $targetServicePrincipalName -appPermissionsRequired $appPermissionsRequired -childApp $app -spForApp $spForApp
The interesting parts are around "apppermissionrequired" and "targetserviceprincipalname" variables.
I can't reply to Rolfo's comment directly as I don't have enough clout yet. While it's true it's not dead simple, it's possible to use both in the same session as of July 2021. Not sure this was always the case, or something was updated to allow it.
#Import modules if needed
$mList = #("AzureAD","Az.Resources","Az.Accounts")
foreach($m in $mList){if ((gmo -l $m).Count -eq 0){Install-Module -Name $m -AllowClobber -Scope CurrentUser -Force}}
#Authentication Popup
Connect-AzAccount
#Use authentication context cached from above to authenticate to AAD graph
$IDObject = Get-AzAccessToken -Resource "https://graph.windows.net"
Connect-AzureAD -AadAccessToken $IDObject.token -AccountId $IDObject.UserId
UPDATE
With the new Graph API we can use the following command to add API permissions to an App Registration/Service Principal using PowerShell. It's much simpler than the old process.
Add-AzADAppPermission -ApplicationId "$spId" -ApiId "00000009-0000-0000-c000-000000000000" -PermissionId "7504609f-c495-4c64-8542-686125a5a36f"
(This is the case for the PowerBI API)
If deploying via an Azure Devops Pipeline I often recommend using the following script to authenticate into AAD:
echo "Install Azure AD module..."
Install-Module -Name "AzureAD" -Force
Import-Module AzureAD -Force
echo "Connect Azure AD..."
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
echo $context
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
echo $graphToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken
Write-Output "Hi I'm $($context.Account.Id)"
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id -MsAccessToken $graphToken
echo "Connection ends"

Deploying azure automation account cert

I generated a pfx blob using this script :
$appId = Read-Host "Enter application ID of service principal"
$adApp = (Get-AzureRmADApplication -ApplicationId $appId)[0]
$spnId = (Get-AzureRmADServicePrincipal -ServicePrincipalName $adApp.IdentifierUris[0])[0].Id.Guid
$endDate = (Get-Date).AddYears(1)
$certSelfSigned = New-SelfSignedCertificateEx -Subject "CN=$spnId" -StoreLocation CurrentUser -NotAfter $endDate -Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider"
$publicKey = [System.Convert]::ToBase64String($certSelfSigned.GetRawCertData())
New-AzureRmADSpCredential -ServicePrincipalObjectId $spnId -CertValue $publicKey -EndDate $certSelfSigned.GetExpirationDateString()
# TEST: Login-AzureRmAccount -ServicePrincipal -CertificateThumbprint $certSelfSigned.Thumbprint -ApplicationId $appId -TenantId <guid>
$storeLocation = [Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
$storeName = [Security.Cryptography.X509Certificates.StoreName]::My
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store #($storeName, $storeLocation)
$store.Open([Security.Cryptography.X509Certificates.OpenFlags]::OpenExistingOnly)
$findType = [System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint
$cert = $store.Certificates.Find($findType, $certSelfSigned.Thumbprint, $false)
$pfxBlob = [System.Convert]::ToBase64String($cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12))
$store.Close()
"THUMBPRINT: $($certSelfSigned.Thumbprint)"
"PFX BLOB: `n$pfxBlob"
I copied the pfx blob and made a .pfx file and in azure portal when I create a new certificate for an automation account and upload the file I created before it gives me internal server error.
What is the correct format of .pfx file and what is the password that I will be using to set and how will I use a thumbprint?

Azure KeyVaultAccessForbidden - "not enabled for deployment"

I'm building a set of scripts and templates to create a Service Fabric cluster in Azure. I've got a script that creates a key vault and a self-signed certificate and successfully uploads it to the vault. Another script creates the cluster but it's hitting an error at the point that the certs are linked to the VMs. The error from the New-AzureRmResourceGroupDeployment command is:-
{
"status": "Failed",
"error": {
"code": "ResourceDeploymentFailure",
"message": "The resource operation completed with terminal provisioning state 'Failed'.",
"details": [
{
"code": "KeyVaultAccessForbidden",
"message": "Key Vault https://VAULT-NAME.vault.azure.net/secrets/clusterCert/SECRET-ID either has not been enabled for deployment or the vault id provided, /subscriptions/SUBSCRIPTION-ID/resourceGroups/jg-sf/providers/Microsoft.KeyVault/vaults/VAULTNAME, does not match the Key Vault's true resource id."
}
]
}
}
VAULT-NAME, SUBSCRIPTION-ID and SECRET-ID are all correct. The key vault has been created with the parameter "enabledForTemplateDeployment": true, as evidenced in the following screenshot.
My scripts and templates can be seen in GitHub - https://github.com/goochjs/azure-testbed.
How do I diagnose the issue?
Thanks,
Jeremy.
How do you create the key vault, I use the following script to create key vault and get CertificateURL.
New-AzureRmKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -Location $Location -sku standard -EnabledForDeployment
#Creates a new selfsigned cert and exports a pfx cert to a directory on disk
$NewCert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -DnsName $CertDNSName
Export-PfxCertificate -FilePath $CertFileFullPath -Password $SecurePassword -Cert $NewCert
Import-PfxCertificate -FilePath $CertFileFullPath -Password $SecurePassword -CertStoreLocation Cert:\LocalMachine\My
#Reads the content of the certificate and converts it into a json format
$Bytes = [System.IO.File]::ReadAllBytes($CertFileFullPath)
$Base64 = [System.Convert]::ToBase64String($Bytes)
$JSONBlob = #{
data = $Base64
dataType = 'pfx'
password = $Password
} | ConvertTo-Json
$ContentBytes = [System.Text.Encoding]::UTF8.GetBytes($JSONBlob)
$Content = [System.Convert]::ToBase64String($ContentBytes)
#Converts the json content a secure string
$SecretValue = ConvertTo-SecureString -String $Content -AsPlainText -Force
#Creates a new secret in Azure Key Vault
$NewSecret = Set-AzureKeyVaultSecret -VaultName $KeyVaultName -Name $KeyVaultSecretName -SecretValue $SecretValue -Verbose
#Writes out the information you need for creating a secure cluster
Write-Host
Write-Host "Resource Id: "$(Get-AzureRmKeyVault -VaultName $KeyVaultName).ResourceId
Write-Host "Secret URL : "$NewSecret.Id
Write-Host "Thumbprint : "$NewCert.Thumbprint
More information about this, please refer to this blog.
I suggest you could check your Resource Id format. The correct format is like /subscriptions/***************/resourceGroups/westus-mykeyvault/providers/Microsoft.KeyVault/vaults/shuisfsvault. You could create SF cluster on Azure Portal firstly.
If it still does not work, I suggest you could check your key vault, do you give enough permission to it?
Note: For test, you could give all permission to the user.

Create a Application in Azure AD with Azure PowerShell Certificate authentication

I was trying to Create a Application in Azure AD with Azure PowerShell Certificate authentication, below is the Powershell snippet:
Login-AzureRmAccount
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate("PATH_TO_CER_FILE")
$key = [System.Convert]::ToBase64String($cert.GetRawCertData())
$app = New-AzureRmADApplication -DisplayName "SetupTet4" -HomePage "http://localhost" -IdentifierUris "http://localhost" -KeyValue $key -KeyType AsymmetricX509Cert
New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId
New-AzureRmRoleAssignment -RoleDefinitionName "Owner" -ServicePrincipalName $app.ApplicationId
the Azure AD application was created successfully, however for Azure AD application with Certificate Authentication, the customKeyIdentifier and value of in the keyCredentials is null after creation, this is the portion of manifest of my application I downloaded from Azure portal:
"keyCredentials": [{
"customKeyIdentifier": null,
"endDate": "2017-02-25T20:48:35.5174541Z",
"keyId": "575580cc-ce4e-4862-ad3e-1ba5833fe7f6",
"startDate": "2016-02-25T20:48:35.5174541Z",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": null
}],
FYI the certificate is a self signed certificate I use makecert command generated locally.
Any advice, great appreciate.
James
Add a call to Set-AzureRmKeyVaultAccessPolicy to specify the access level you want the service principle to have for the key vault. See the changes in the last two lines for your script.
Login-AzureRmAccount
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate("PATH_TO_CER_FILE")
$key = [System.Convert]::ToBase64String($cert.GetRawCertData())
$app = New-AzureRmADApplication -DisplayName "SetupTet4" -HomePage "http://localhost" -IdentifierUris "http://localhost" -KeyValue $key -KeyType AsymmetricX509Cert
$sp = New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId
Set-AzureRmKeyVaultAccessPolicy -VaultName "<your-vault-name>" `
-ServicePrincipalName $sp.ServicePrincipalName `
-PermissionsToKeys all -PermissionsToSecrets all `
-ResourceGroupName "<your-resource-group-name>"

Resources