I have an Azure Function and it needs to connect to Graph. Typically I do this via certificates so I have the following bit of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$appCert_Secret = Get-AzKeyVaultSecret -VaultName $certsKeyVault -Name "MyCert" -AsPlainText # Create certs for connecting to Graph $appCert_Bytes = [Convert]::FromBase64String($appCert_Secret) # Add a random number to the file coz I could have multiple instances of this runbook running at the same time $appCert = "$($env:TEMP)\cert$(Get-Random -Max 10000).pfx" [System.IO.File]::WriteAllBytes("$appCertSPO", $appCert_Bytes) # Connect to Graph (SPO) try { Connect-MgGraph -ClientId $appId -Certificate $appCert -TenantId $tenantId | Out-Null } catch { $body = "Couldn't connect to Graph: $($_.Exception.Message)" Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::InternalServerError Body = $body }) exit } |
This gets the cert from my Key Vault (I get it as a secret so I have the private key). I make a file out of this in the Temp folder and then connect via Graph.
If this were an Automation account I would have used Get-AutomationCertificate -Name "MyCert"
to store the certificate in a variable ($appCert
in this case) and called Connect-MgGraph
with that. (That said, nowadays I prefer storing certs in Key Vaults with Automation Accounts too as it is easy to update and/ or use across multiple Automation Accounts so I do something similar to the above for Automation Accounts also).
Anyways, the above code gave the following error:
1 |
2021-12-01T16:44:29.919 [Error] ERROR: Cannot bind parameter 'Certificate'. Cannot convert value "C:\local\Temp\cert4477.pfx" to type "System.Security.Cryptography.X509Certificates.X509Certificate2". Error: "The system cannot find the file specified."Exception :Type : System.Management.Automation.ParameterBindingExceptionMessage : Cannot bind parameter 'Certificate'. Cannot convert value "C:\local\Temp\cert4477.pfx" to type "System.Security.Cryptography.X509Certificates.X509Certificate2". Error: "The system cannot find the file specified."ParameterName : CertificateParameterType : System.Security.Cryptography.X509Certificates.X509Certificate2TypeSpecified : stringErrorId : CannotConvertArgumentNoMessageLine : 62Offset : 50CommandInvocation :MyCommand : Connect-MgGraphBoundParameters :Comparer : System.OrdinalIgnoreCaseComparerCount : 1Keys :Length : 8Values :Length : 36SyncRoot :Comparer : |
Which is weird coz I know the Key Vault etc. works and I can see the file being created at the location… and yet the cmdlet can’t see it? Made no sense. (I couldn’t see the file via the Kudu console due to separation of TEMP folders between Kudu and the Function, but I could do a Get-ChildItem
of the folder from within the Function and see the file there).
As part of trying to find out whether the TEMP folder was special in some way I came across this wiki page. And from there this page on certs best practices. I also came across this excellent blog post on certs and .NET (has got nothing to do with Functions but is good info) and a forum post where someone else had the same issue and the solution was to create a folder within the Function’s home directory and store the certificate there. I was half tempted to do that… but it didn’t feel neat, so I spent some more time researching what I found in that certs best practices wiki. One of the things I came across from there was this page on loading certs in App Services. And turns out Functions (and App Services) have a very neat way of loading certs.
First off I have to import the cert into the Function.
If you have the cert already added to a Key Vault (like I had; easy to manage and rotate certs if they are being used by multiple services) I can even import from the Key Vault. This has the added advantage that when the certificate is rotated in the Key Vault it will be pulled into the Function. Nice, huh!
Next I have to add a Configuration Setting called WEBSITE_LOAD_CERTIFICATES
and set as its value the thumbprint of this certificate (can add multiple thumbprints, if there are multiple certificates, separated by comma).
Lastly, I can refer to this certificate by its thumbprint in the code. So everything I wrote above can be replaced with this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Connect to Graph (SPO) try { Connect-MgGraph -ClientId $appIdSPO -CertificateThumbprint "THUMBPRINT" -TenantId $tenantId | Out-Null } catch { $body = "Couldn't connect to Graph: $($_.Exception.Message)" Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::InternalServerError Body = $body }) exit } |
That’s it, so easy!