Imported by PS script certificate has broken Private Key - azure-devops

I'm running CI integration tests in Azure DevOps, running happens on a dedicated Azure VM with installed build agent. Those tests require client SSL certificate to be installed on that VM. As a build step in CI I have a PS script that consumes the Azure KeyVault certificate and imports that into LocalMachine/My store of VM. While the cert is imported and I can see it in VM, tests from CI fail using the cert. Note that the cert, when trying to manually export in VM, has a Export with Private Key option grayed out.
When I run the same PS script manually withing VM and then run CI tests (with PS step disabled), tests successfully consumer certificate and pass.
What should I change in my PS script below, so it (being running remotely) would import a certificate with Export with Private Key option enabled?
$vaultName = "MyKeyVault-stest"
$secretName = "MyCertificate"
$kvSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $secretName
$kvSecretBytes = [System.Convert]::FromBase64String($kvSecret.SecretValueText)
$kvSecretPass = 'myPass'
#-----------------------------------------------------------------------------
$pfxCertObject=New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList #($kvSecretBytes, "", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$newcertbytes = $pfxCertObject.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $kvSecretPass)
$newCert=New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$newCert.Import($newcertbytes,$kvSecretPass,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
#-------------------------------------------------------------------------------
$certStore = Get-Item "Cert:\LocalMachine\My"
$openFlags = [System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite
$certStore.Open($openFlags)
$certStore.Add($newCert)
Write-host $env:USERNAME
Write-host $(whoami)

If you are importing a PFX to add it to a persisted store you want to specify the X509KeyStorageFlags.PersistKeySet flag. If you don't, at some undetermined point later the garbage collector notices no one cares about the key and then asks Windows to delete it... and then the version added to the X509Store can no longer find its key.
Other reading:
What is the impact of the `PersistKeySet`-StorageFlag when importing a Certificate in C#
What is the rationale for all the different X509KeyStorageFlags?

Related

TeamCity Remote Powershell Access Denied Suddenly

Disclaimer: I am not a DevOps guy so please forgive any ignorance. I'm learning this stuff to expand my understanding.
I've enabled remote Powershell on a Windows Server 2019 instance in order to stop/start scheduled tasks during deployment of files from my build server (also Windows Server 2019).
I followed the below steps in an Administrator Powershell as the Adminstrator user on the remote server:
1. Enable RSRemoting.
2. Remove existing listener.
3. Create self-signed certificate and export to crt file.
$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "<subdomain.domain.com>"
Create listener.
Create firewall rules to allow secure PSRemoting and disable unsecure connections.
Copy certificate to build server.
Import certificate on build server.
From the build server, I've tested the configuration using the following commands in Powershell:
$username = 'Administrator'
$pass = ConvertTo-SecureString -string '<password here>' -AsPlainText -Force
$cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist $username, $pass
Invoke-Command -ComputerName <subdomain.domain.com> -UseSSL -ScriptBlock {whoami} -Credential $cred
Which responds nicely with win-<some stuff>\administrator. However, when I execute a remote Powersehll command from within a TeamCity build step, I get a big ugly Connecting to remote server <subdomain.domain.com> failed with the following error message : Access is denied..
The weird part is, this worked two days ago and I have several builds that were able to complete all remote operations. From this morning, it's just stopped working - poof!
If I fudge the credentials, I do get an incorrect username/password error so it is definitely reaching the server.
Another interesting find is that if I run
[bool](Test-WSMan)
on the remote server, I get True returned, but if I run the same command with -ComputerName <subdomain.domain.com> on the build server, I get
WinRM cannot complete the operation. Verify that the specified computer name is
valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer. By default, the WinRM firewall exception for public profiles limits
access to remote computers within the same local subnet. returned.
Both the remote host and build server are logged on as the default Administrator.
Any ideas?
After more research and calling in a few favours, I was advised to tweak the TeamCity Build Agent and TeamCity Server services. These need to Log On As a User and not Local System. I can't explain how my previous settings worked. The Access is denied error I experienced has nothing to do with the Remote Powershell configuration mentioned above.

Missing cluster cert causes Add-AzServiceFabricClusterCertificate to fail: Object reference not set to an instance of an object

I'm fairly new to Service Fabric, so I'm not sure if this is an issue with the cmdlet or if this is a miss on my part. I am using Az.ServiceFabric module version 2.0.2 and the Az module version 3.8.0.
I am trying to use the Add-AzServiceFabricClusterCertificate cmdlet to add a secondary certificate that I've already created in my Azure KeyVault to my cluster. When I run the cmdlet, it fails with this error (running with Debug gave me more stack detail):
DEBUG: AzureQoSEvent: CommandName - Add-AzServiceFabricClusterCertificate; IsSuccess - False; Duration -
00:00:07.3059582;; Exception - System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Azure.Commands.ServiceFabric.Commands.ServiceFabricClusterCmdlet.GetClusterType(Cluster
clusterResource)
at Microsoft.Azure.Commands.ServiceFabric.Commands.AddAzureRmServiceFabricClusterCertificate.ExecuteCmdlet()
at Microsoft.WindowsAzure.Commands.Utilities.Common.AzurePSCmdlet.ProcessRecord();
Looking at the code for this cmdlet, I noticed that it's probably failing because the cluster resource that gets passed into GetClusterType does not have its Certificate member, so it fails when it tries to check the Certificate.Thumbprint and Certificate.ThumbprintSecondary:
internal ClusterType GetClusterType(Cluster clusterResource)
{
if (string.IsNullOrWhiteSpace(clusterResource.Certificate.Thumbprint) &&
string.IsNullOrWhiteSpace(clusterResource.Certificate.ThumbprintSecondary))
{
return ClusterType.Unsecure;
}
else
{
return ClusterType.Secure;
}
}
The cluster that gets passed into GetClusterType is retrieved in the same manner as in the Get-AzServiceFabricCluster cmdlet, so when I run that cmdlet for the cluster that I'm trying to add the certificate to, I noticed that my Certificate field is empty in the response. I'm guessing that's what's causing the NullRef exception. Here's that relevant snippet:
AzureActiveDirectory :
TenantId : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ClusterApplication : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ClientApplication : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Certificate :
CertificateCommonNames : Microsoft.Azure.Management.ServiceFabric.Models.ServerCertificateCommonNames
ClientCertificateCommonNames :
ClientCertificateThumbprints :
I'm wondering if it's expected that the Certificate field would be empty when I'm using the Get-AzServiceFabricCluster cmdlet and if that is indeed the cause of my Add-AzServiceFabricClusterCertificate cmdlet failing. When I look at the cluster's Security blade in Azure Portal, I do see the Primary Cluster Certificate with which I originally created the cluster, and this is the cert that I use when deploying and doing other cluster operations. However, I did notice that the cert thumbprint field is empty when viewing the certificate from the portal. I would expect to see this certificate when using Get-AzServiceFabricCluster, but it comes up empty. Is this certificate missing from my Get-AzServiceFabricCluster cmdlet possible to fix through the portal or with another cmdlet?
It looks like your cluster is configured to find certificates by common name, rather than thumbprint. I'm guessing this based on the fact your portal doesn't show a thumbprint against the certificate, in addition to the snippet you have posted.
If this is the case, there's no need to update your cluster configuration with a new certificate when the old certificate has expired - Instead you need to install the certificate only into your VMSS vault. Once you add the new certificate to the VMSS, Service Fabric will automatically use the later expiring certificate.
You must always ensure you have at least one valid certificate installed on your VMSS with the common name configured in your cluster.
PS to upload certificate KV and install onto VMSS:
$subscriptionId = "sub-id"
$vmssResourceGroupName = "vmss-rg-name"
$vmssName = "vmss-name"
$vaultName = "kv-name"
$primaryCertName = "kv-cert-name"
$certFilePath = "...\.pfx"
$certPassword = ConvertTo-SecureString -String "password" -AsPlainText -Force
# Sign in to your Azure account and select your subscription
Login-AzAccount -SubscriptionId $subscriptionId
# Update primary certificate within the Key Vault
$primary = Import-AzKeyVaultCertificate `
-VaultName $vaultName `
-Name $primaryCertName `
-FilePath $certFilePath `
-Password $certPassword
$certConfig = New-AzVmssVaultCertificateConfig -CertificateUrl $primary.SecretId -CertificateStore "My"
# Get VM scale set
$vmss = Get-AzVmss -ResourceGroupName $vmssResourceGroupName -VMScaleSetName $vmssName
# Add new certificate version
$vmss.VirtualMachineProfile.OsProfile.Secrets[0].VaultCertificates.Add($certConfig)
# Update the VM scale set
Update-AzVmss -ResourceGroupName $vmssResourceGroupName -Verbose `
-Name $vmssName -VirtualMachineScaleSet $vmss
For more info, I wrote a blog post on switching from thumbprint to common name.
The official docs are also a good reference.

IIS 10 - Import SSL certificate using Powershell - "A specified logon session does not exist"

Importing a .pfx-file to IIS using Powershell is pretty straight forward thanks to guidelines such as this one Use PowerShell to install SSL certificate on IIS. But I do run into an issue when trying to bind port 443 using the imported certificate:
Error: "A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520)".
This due to "...If you don't already have a cer version, or you do but it includes the private key, enable Allow this certificate to be exported..." (ref. Setup of SharePoint 2013 High-Trust On-premise Add-In Developer / Production environment)
This is how it is set in the GUI
But, looking at the following line in the code which I got from dejanstojanovic.net.
pfx.Import($certPath,$certPass,"Exportable,PersistKeySet")
it is set to Exportable. Removing PersistKeyset does not make a difference. So what could causing this?
The script is not able to set it to Exportable as in the GUI "Allow this certificate to be exported"
...I'm all out of options...
Update
I did tweak the code a bit, using constants and such, but still same issue
$certPath = "D:\ssl\cert-export-to-iis-10.pfx"
$certPass = "password"
$pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bxor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$pfx.Import($certPath,$certPass,$KeyStorageFlags)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("WebHosting","LocalMachine")
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($pfx)
$store.Close()
$store.Dispose()

Installing a .pfx Cert in TrustedRootCA WITH private key from command line in powershell on win7

My question is this: how would I go about installing a .pfx cert into TrustedRootCA with its private key from command line using powershell in windows 7?
I've got it to install the cert using this command: certutil -importpfx -p "mypasswordhere" "cert path location here"
The above installs it to the personal store of the local computer fine (With the private key intact) And also installs it to TRCA, only without the private key. This is my issue as the program requires the TRCA to have the private key.
but when I try to use the program that needs the cert it says : "It is likely that "My cert" may not have a private key that is capable of key exchange or the process may not have axxess rights for the private key. Please see inner exception for more detail.
However, I checked the TRCA store and verfied that my cert indeed does not have its private key.
So again my question is there anyway using powershell to automate this process on windows 7? Thanks in advance.
You can use the command
Import-PfxCertificate -FilePath C:\setup\TestCertImport.pfx -CertStoreLocation 'Cert:\LocalMachine\Root'
Root = Trusted root,
My = Local Machine Personal certificate folder
if the certificate has password you can add the -Password parameter and if you want it to be exportable the -Exportable

Visual studio team services deploymen/buildt certificate error

I am trying to build a click-once application using the Continuous integration and deployment feature in VSTS (Visual studio team services Online)We are trying to build this using the Hosted agent Visual studio 2015 We had difficulties signing the strong name key file with an error of
MSB3326: Cannot import the following key file: xxxx.snk. The key file may be password protected. To correct this, try to import the certificate again or import the certificate manually into the current user's personal certificate store.
And after that
MSB3321: Importing key file "xxxx.pfx" was canceled.
I have tried to both select from store and from file changed the location and made sure to commit but with no success.
Any ideas how i can overcome this errors or what am doing wrong.
Clerification on answer selected
Just wanted to make a clarification if anyone else has the same issue, in addition to the answer i had to place my certificate in my source control code and commit it. Then to select its location add a global variable on the VSTS Build
$cert.Import("$(CertPath)", $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
Where $(CertPath) would be something like $(Build.SourcesDirectory)\SharedSolutionFiles\CertificateName.pfx
You can create a PowerShell script and add a PowerShell Script step in your build definition to import the certificate file before the VSBuild step.
Build failed without PowerShell Import Certificate Step:
Build passed with PowerShell Import Certificate Step:
The PowerShell Script I used:
$pfxpath = 'pathtoees.pfx'
$password = 'password'
Add-Type -AssemblyName System.Security
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($pfxpath, $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "MY", CurrentUser
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]"ReadWrite")
$store.Add($cert)
$store.Close()
The better way is that you can setup a on premise build agent and import the certificate to certificate store, then change build agent service account to the same account.
Instead of using either an on premise build or loading the certificates on to the certificate stores on the build agent (which could be considered insecure) it is possible to overwrite the build task FileSign and construct one that uses a certificate file and password.
I have outlined the steps here:
https://stackoverflow.com/a/55313239/2068626
After failing to use methods in other answers, I found another way to use PowerShell script to import pfx certificate. my script was written for GitHub Actions but you can easily change the syntax to VSTS or Azure pipeline (using 'task: PowerShell#2' for azure pipeline for example). You may also like to update file path from github to your devops path. Also password can be replaced with a secured variable.
- name: Import certificate from the command-line
shell: pwsh
run: |
$Secure_String_Pwd = ConvertTo-SecureString "<password>" -AsPlainText -Force
Import-PfxCertificate -FilePath '${{github.workspace}}\<path>\project1_TemporaryKey.pfx' -CertStoreLocation Cert:\CurrentUser\My -Password $Secure_String_Pwd
Then build your Visual Studio ClickOnce project. The error should be gone.
After the build, you may like to remove the certificate from the machine. Here is a PowerShell example provided by Microsoft:
https://learn.microsoft.com/en-us/answers/questions/360772/powershell-commands-to-delete-personal-certificate.html
$users = "user1","user2","user3","user4","user5"
Get-ChildItem Cert:\CurrentUser\My | ForEach-Object {
$ifkeep = $false
foreach($user in $users){
if($_.Subject -match $user){
$ifkeep = $true
break
}
}
if($ifkeep -eq $false){
Remove-Item $_
}
}