Import-AzureKeyVaultCertificate fails with Forbidden error - azure

I am trying to use Powershell to upload a certificate PFX file to Key Vault using the command below:
Import-AzureKeyVaultCertificate -VaultName $kvName -Name $kvCertName -FilePath $aseCertPFXFile -Password $SecureStringPassword
When I run this command, I get the error below:
Import-AzureKeyVaultCertificate : Operation returned an invalid status code 'Forbidden'
I can manually upload this certificate using the portal with no issues and the account I am using to run the PowerShell script is the same one I use when doing it via the portal.

The problem wound up being that there was a residual context from a Service Principal that did not have the necessary permissions to perform the actions. Once I removed that context, it forced a new login where I used my user account and the import worked.

Related

Automate connecting to Azure for checking and creating resources without prompt

I am required to write a PowerShell script, which can connect to my company's Azure account, check and create Azure resources (eg. Service Bus namespace, Service Bus topic, and Service Bus subscriptions). Everything worked well until I tried to deploy my script as a step in my project's on-premise TeamCity. I keep getting this error message
Exception calling "ShouldContinue" with "2" argument(s): "Windows
PowerShell is in NonInteractive mode. Read and Prompt functionality is
not available."
I investigated and found out that the problem is in this line
Connect-AzAccount
If I run the script manually, it will pop up a prompt asking me to login to Azure. I believe that's what went wrong. Because my project's on-premise TeamCity does not seem have an option to open a prompt for PowerShell command. I have read some workarounds, even on this website, but none of them is applicable to my case. Even a solution like https://stackoverflow.com/a/61099568/8213536 gave me these errors
WARNING: Unable to acquire token for tenant 'organizations' with error
'UsernamePasswordCredential authentication failed: There was an error
parsing WS-Trust response from the endpoint. This may occur if there
is an issue with your ADFS configuration. See
https://aka.ms/msal-net-iwa-troubleshooting for more details. Error
Message: Object reference not set to an instance of an object. See the
troubleshooting guide for more information.
https://aka.ms/azsdk/net/identity/usernamepasswordcredential/troubleshoot'
Connect-AzAccount : UsernamePasswordCredential authentication failed:
There was an error parsing WS-Trust response from the endpoint. This
may occur if there is an issue with your ADFS configuration. See
https://aka.ms/msal-net-iwa-troubleshooting for more details. Error
Message: Object reference not set to an instance of an object. See the
troubleshooting guide for more information.
https://aka.ms/azsdk/net/identity/usernamepasswordcredential/troubleshoot
At line:1 char:1
Connect-AzAccount -Credential $creds
+ CategoryInfo : CloseError: (:) [Connect-AzAccount], AuthenticationFailedException
+ FullyQualifiedErrorId : Microsoft.Azure.Commands.Profile.ConnectAzureRmAccountCommand
One of the other solutions https://stackoverflow.com/a/52014189/8213536 requires an application's principal id, which is not applicable for my scenario either, as I am not creating a new application. I just need to be able to automatically connect to Azure (without prompt), check and create SB Namespace, SB Topic and SB Subscription.
Could someone please help me on this? Thanks.
As promised, I would like to post my solution. First I created a service principal with a client secret key. Then I asked my company's cloud engineer to assign it to the Azure subscription of my company and to the resource group that I intended to group all my necessary resources into. Finally in my code, I implemented something similar to https://stackoverflow.com/a/61099568/8213536
$applicationId = $azServicePrincipalId
Write-Host "Connecting to Azure using principal $applicationId"
$securePassword = $azServicePrincipalPw | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $applicationId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $azureTenantId -Credential $credential
$azServicePrincipalId and $azServicePrincipalPw came from the Service Principal itself, while $azureTenantId came from my company's Azure subscription.
It is now working as expected.

How to create AzureProfile.json file using AzModule command in PowerShell?

I want to create the AzureProfile.json file using the Az Module command in PowerShell, so that I can import the json file anytime I want to login to Azure to start\stop my VM's.
Below is the command\script I am using to create it but it is giving me error as shown in the screenshot.
I tried using the below code as well but it gives same error.
Connect-AzAccount -UseDeviceAuthentication
Save-AzProfile -path "$PSScriptRoot\AzureProfile.json"
WARNING: Unable to acquire token for tenant 'organizations'
Connect-AzAccount : DeviceCodeCredential authentication failed: Retry
failed after 4 tries.
From the error message, it looks like the authentication of your account is failing in the Browser pop-up that comes after Login-AzAccount.
I ran the first set of commands and it ran successfully.
Powershell:
Created File:
Note that my PowerShell version is 5.7.0.18831.
You have tried both :
Connect-AzAccount -UseDeviceAuthentication
Login-AzAccount
Both Interactive and non-interactive mode of login.
The error is occurring while trying you communicate to azure. I am suspecting it could be an issue at a machine level or network level (proxy/firewall)
The above issue usually occurs (reproducible at my end) when there are connection issues with the Microsoft Services - blocked by proxy /firewall/GPO etc...
For a quick test you could run the below PowerShell command :
Invoke-WebRequest -Uri "https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode" -Body "client_id=1950a258-227b-4e31-a9cf-717495945fc2&scope=https%3A%2F%2Fmanagement.core.windows.net%2F%2F.default+offline_access+profile+openid" -Method Post
Sample Success Response :
Why the above command ?
The above end point https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode is hit when you use Connect-AzAccount -UseDeviceAuthentication by powershell.
If you encounter a timeout/Name unresolvable issue - the issue is with the network/machine config.

Azure Devops: value cannot be null, parameter token

I am running an Azure Powershell task in Azure DevOps.
Inside the script I use the following command
Get-AzureRmADUser -UserPrincipalName $adusernameNewPermission
But my Release pipeline fails with following error code
##[error]Value cannot be null.
Parameter name: token
First I thought that the command didn't have the right context or enough permission so I've added the defaultprofile.
Get-AzureRmADUser -DefaultProfile (Get-AzureRmContext) -UserPrincipalName $adusernameNewPermission
The GetAzureRmContext did go to the right context if I print the output of that command.
The command itself didn't had any problem when running locally (with my own user account) So the only reason I think its heading is that the service connection doesn't have the right to perform that action. But my user account has the least permissions on the tenant whilst the service connnection in azure devops has much more permissions.
It's driving me crazy where the problem lays with this one. Which token does it mean ? No reasonable error message :(
Does someone encounter the same problem or knows what I am missing ?
PS: the $adusernameNewPermission variable works like I said the exact same code runs perfectly on my local machine with the only difference being the user that is logged in.
Did you try using Get-azureaduser instead? This is a command that requires the function to be authenticated against Azure AD.
$azurePassword = ConvertTo-SecureString $env:client_secret -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($env:clientid , $azurePassword)
Connect-AzAccount -Credential $psCred -TenantId $env:tenantid -ServicePrincipal
If you're using the Script Type of "Script File Path," check that you're not trying to pass in any arguments (such as -token).
I'd try 2 things to get back to basics. This will let you know if it is actually the AzDO Service Principal or not and the type of object to use in the pipeline.
Test the functionality of the command in its simplest form and run it with the account as a string:
Get-AzureRmADUser -UserPrincipalName "achahbar#stankoverflow.com"
#OR
$UPN = "achahbar#stankoverflow.com"
Get-AzureRmADUser -UserPrincipalName $UPN
Assuming this works and depending on what your variable contains you simply need to pass the the UPN/Email object into the command:
Get-AzureRmADUser -UserPrincipalName $adusernameNewPermission.UPN
If this doesn't work pdate your task to Version 4 (Preview) and update your commands to Get-AzADUser and test step 1. again
I fixed this issue by changing the service connection in Azure DevOps that was created with a managed identity. I created a new service connection with a service principal and this doesn't gave me any errors about the value token. Hope any person who looks for this issue in the future got an answer by this.

How to use from a powershell a *.pfx certificate used on build pipeline with the download secure file task

I got this problem:
I need to connect to an azure subscrition from a powershell script used on a build pipeline, but for security requirements i can't write user and password on the code, so i have a pfx certificate with the credentials.
Right now i'm using the task named dowload secure file, to put the certificate on the build. Then i'm trying to access the certificate from the powershell code.
I already test the code on my machine, but when i'm trying to use it on the build pipeline i cannot access the certificate with this
and i got an error like this
Logging in...
D:\a\1\s\Scripts\fileName.ps1 : The Script does not work :The term 'cert.secureFilePath' is not recognized
as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was
included, verify that the path is correct and try again.
$tenantId = "xxxxxxxxxxx"
$appId = "zzzzz"
$cert = %DOWNLOADSECUREFILE_SECUREFILEPATH%
$certThumbprint = $cert.Thumbprint
Write-Host "Logging in...";
Login-AzureRmAccount `
-ServicePrincipal `
-TenantId $tenantId `
-ApplicationId $appId `
-CertificateThumbprint $certThumbprint
Tasks used on the build pipeline
The full path of the downloaded Secure file is stored to the $env:DOWNLOADSECUREFILE_SECUREFILEPATH environment variable. For more information about Download Secure File task please refer to this document.
We could get the certThumbprint with following code
$CertificatePath = "$env:DOWNLOADSECUREFILE_SECUREFILEPATH"
$sSecStrPassword = "xxxxx"
$certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$certificateObject.Import($CertificatePath, $sSecStrPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
$thumbprint = $certificateObject.Thumbprint
If we don't want to use to user and password in the code directly. We could use the Azure Pipeline library. And we could reference it in the code.
If you want to encrypt and securely store the value, choose the "lock" icon at the end of the row. When you're finished adding variables, choose Save
You access the value of the variables in a linked variable group in exactly the same way as variables you define within the pipeline itself. For example, to access the value of a variable named customer in a variable group linked to the pipeline, use $(customer) in a task parameter or a script. However, secret variables (encrypted variables and key vault variables) cannot be accessed directly in scripts - instead they must be passed as arguments to a task
If I add a Variable named sSecStrPassword in the library. Then the code could be changed as following:
function GetThumbprintPFX {
param([string] $CertificatePath, [string]$Password)
$certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$certificateObject.Import($CertificatePath, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
$thumbprint = $certificateObject.Thumbprint
return $thumbprint
}
$thumbprint = GetThumbprintPFX -CertificatePath $env:DOWNLOADSECUREFILE_SECUREFILEPATH -Password '$(sSecStrPassword)'
Write-Host "$thumbprint"
Test Result:
For more information about Variable groups, please refer to this link. And Azure Key Vault is another choice for security requirements.
Update:
The following is the detail steps to use the pfx file in the Azure Devops pipeline.
prepare a .pfx file.
Add a download secure file task and upload the pfx file.
create a variable group and add a variable named sSecStrPassword
link the variable to the build
Add powershell script task and add the following script in it.
# Write your powershell commands here.
Write-Host $env:DOWNLOADSECUREFILE_SECUREFILEPATH
function GetThumbprintPFX {
param([string] $CertificatePath, [string]$Password)
$certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$certificateObject.Import($CertificatePath, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
$thumbprint = $certificateObject.Thumbprint
return $thumbprint
}
$thumbprint = GetThumbprintPFX -CertificatePath $env:DOWNLOADSECUREFILE_SECUREFILEPATH -Password '$(sSecStrPassword)'
Write-Host "$thumbprint"
queue the build and check the result.

Why does getting a certificate from Azure Key Vault require it to be stored as a secret?

Blogs like the following
https://blogs.technet.microsoft.com/neales/2017/06/26/getting-a-private-certificate-from-key-vault/
Seem to retrive the secret? Does it not matter if it's "stored" as a certificate or not?
It depends on what you are planning to do with the certificate. You could update your question with details about the expected workflow you want to support.
But basically a certificate can be stored as a file. You can see more details (C#) to get inspired on how to do that after the certificate is loaded into a variable.
Exporting a Certificate as BASE-64 encoded .cer
Update
Security considerations to take into account. If you see the certificate stored in the azure key vault as a secret and you want to limit the access to it, then you have to consider how your PowerShell scripts will store the needed credentials for authenticate against the KeyVault.
If you plan on running the script unattended / scheduled without user interaction, you will have to store some kind of credentials on the machine that needs to run the script. BetterCredentials is a great PowerShell native module for storing credentials on the local machine.
I would recommend that you create an Azure Service Principal (App Registration / Registered App), that will get only enough permissions to get the certificate from the KeyVault. The created Service Principal details should then be stored locally on the machine and you should load those credentials first and use them for connecting to the KeyVault.
Example code that should be capable of loading a Service Principal details from the BetterCredentials and sign into Azure:
BetterCredentials\Get-Credential -UserName <application ID> -Store
$azureTenantId = <tenant ID>
$Cred = BetterCredentials\Get-Credential -UserName <application ID>
Add-AzureRmAccount -Credential $Cred -TenantId $azureTenantId -ServicePrincipal

Resources