Skip to content

Instantly share code, notes, and snippets.

@pharring
Created April 24, 2017 17:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save pharring/991ad7ace955ff455796a6c1009cbca5 to your computer and use it in GitHub Desktop.
Save pharring/991ad7ace955ff455796a6c1009cbca5 to your computer and use it in GitHub Desktop.
Powershell to grant NETWORK SERVICE Read access to the private key of a certificate
<#
.SYNOPSIS
Grants the NETWORK SERVICE read access to a certificate's private key.
.DESCRIPTION
In order for HTTPS to work in ASP.Net Core, an SSL certificate must be installed and readable by NETWORK SERVICE
account.
The SSL certificate is obtained from KeyVault and installed into the VM Scale Set by our ARM template. However,
the certificate is protected by a private key. The default permissions (ACLs) on the imported certificate are
insufficient for an ASP.Net Core application running in Service Fabric. This script grants the
"NT AUTHORITY\NETWORK SERVICE" account read-only access to the private key of a given certificate which is
sufficient for ASP.Net Core (Kestrel) to use it for HTTPS communication.
#>
param(
[Parameter(Mandatory=$true)][string] $Thumbprint
)
Write-Host "Updating certificate access."
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
[System.Security.Cryptography.X509Certificates.StoreName]::My,
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
)
try
{
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
try
{
$cert = $store.Certificates | where {$_.Thumbprint -eq $Thumbprint}
if ($cert -eq $null)
{
Write-Error "Cannot find the certificate with thumbprint $Thumbprint"
exit
}
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$key = $rsa.Key
# 0x44 = DACL_SECURITY_INFORMATION | NCRYPT_SILENT_FLAG
$daclPropertyOptions = [System.Security.Cryptography.CngPropertyOptions]0x44
$daclProperty = $key.GetProperty("Security Descr", $daclPropertyOptions)
$securityDescriptor = New-Object System.Security.AccessControl.RawSecurityDescriptor($daclProperty.GetValue(), 0);
# Find existing NETWORK SERVICE Access control (ACE)
$existingNetworkServiceAce = $securityDescriptor.DiscretionaryAcl | where {$_.SecurityIdentifier.IsWellKnown([System.Security.Principal.WellKnownSidType]::NetworkServiceSid)}
$desiredAccessMask = [System.Security.AccessControl.CryptoKeyRights]::GenericRead -bor
[System.Security.AccessControl.CryptoKeyRights]::Synchronize -bor
[System.Security.AccessControl.CryptoKeyRights]::ReadPermissions -bor
[System.Security.AccessControl.CryptoKeyRights]::ReadAttributes -bor
[System.Security.AccessControl.CryptoKeyRights]::ReadExtendedAttributes -bor
[System.Security.AccessControl.CryptoKeyRights]::ReadData
if ($existingNetworkServiceAce -ne $null)
{
# Verify access mask
if ($existingNetworkServiceAce.AceQualifier -ne [System.Security.AccessControl.AceQualifier]::AccessAllowed)
{
Write-Host "NETWORK SERVICE already has an entry, but it is not 'Access Allowed'."
# This would be dangerous to try and fix
exit
}
$updatedAccessMask = $existingNetworkServiceAce.AccessMask -bor $desiredAccessMask
if ($updatedAccessMask -eq $existingNetworkServiceAce.AccessMask)
{
Write-Host "NETWORK SERVICE already has read access"
exit
}
else
{
Write-Host "Adding Read access to NETWORK SERVICE"
$existingNetworkServiceAce.AccessMask = $updatedAccessMask
}
}
else
{
Write-Host "Adding NETWORK SERVICE to the access control list with Allow Read access"
# Create a new ACE
$networkServiceIdentifier = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::NetworkServiceSid, $null)
$ace = New-Object System.Security.AccessControl.CommonAce(
[System.Security.AccessControl.AceFlags]::None,
[System.Security.AccessControl.AceQualifier]::AccessAllowed,
$desiredAccessMask,
$networkServiceIdentifier,
$false,
$null)
# Add it to the DACL
$securityDescriptor.DiscretionaryAcl.InsertAce($securityDescriptor.DiscretionaryAcl.Count, $ace)
}
# Write the updated DACL back to the CNG key's security descriptor
$updatedValue = New-Object byte[] $securityDescriptor.BinaryLength
$securityDescriptor.GetBinaryForm($updatedValue, 0)
$updatedCngProperty = New-Object System.Security.Cryptography.CngProperty("Security Descr", $updatedValue, $daclPropertyOptions)
$key.SetProperty($updatedCngProperty)
Write-Host "Security descriptor updated"
}
finally
{
$store.Close()
}
}
catch [System.Security.Cryptography.CryptographicException]
{
Write-Error "Could not open the Local Machine certificate store. Are you running as administrator?"
}
@rovinbhandari
Copy link

Hi! I wanted to give this script a shot because I might have the same issue with my cert on the nodes. But,

$daclProperty = $key.GetProperty("Security Descr", $daclPropertyOptions)

throws

Exception calling "GetProperty" with "2" argument(s): "Key not valid for use in specified state.
"
At line:1 char:1
+ $key.GetProperty("Security Descr", [System.Security.Cryptography.CngP ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : CryptographicException

I also don't see a property called "Security Descr" in the $key.

@sebbrochet
Copy link

@rovinbhandari this error happens when the installed SSL certificate is not exportable (as explained here).

And unfortunately, all SSL certificates installed though a Service Fabric ARM template are not configured as exportable.

Being in the same boat than you, I finally managed the installation of the PFX using Powershell in the CustomScriptExtension and was able to use the above script from @pharring as-is (thanks!)

@4c74356b41
Copy link

4c74356b41 commented Mar 11, 2019

that doesnt really look valid, any certificate I use with this script doesnt work, self signed\real certificates. doesnt matter. imported manually\programmatically.

@4c74356b41
Copy link

yeah, it doesnt even work for a working sf cluster certificate, lol

@pharring
Copy link
Author

See https://docs.microsoft.com/azure/service-fabric/service-fabric-tutorial-dotnet-app-enable-https-endpoint#give-network-service-access-to-the-certificates-private-key

Obviously, you need to replace the hard-coded subject name with your own. (In my case, I have to figure out how to make it a parameter passed through the Setup.bat from the ExeHost in ServiceManifest.xml (because different environments have different certs).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment