5

I have begrudgingly been updating our purchased wildcard certs manually for years and I've had enough now that I've got over 100 web and client servers. The servers are mostly independent and spread across a few different domains, but there are a couple farms running which might make this easier for at least those few.

All OS's are Windows Server 2016 and 2019.

My current process at the end of every certificate validity period is to purchase a new (renew) cert and complete the CSR from my in-house management box, then export the cert in .pfx format, and install it manually on each server in the Personal store. On web servers (IIS) I modify the bindings manually as well.

I know if I were using certs generated in the domain I could simply push them out using AD CA in each domain, but based on my research I cannot find a way to roll-out the cert we purchased from the vendor.

I also see that a GPO might at least be the answer here for getting the cert on the servers - it won't be hard for me to setup item-level targeting, or put all the web-servers in a group or OU in each domain. The only issue I am having with this is I cannot find a way to use this method to place the cert in the Personal store, which is a requirement.

There is likely to be more than 1 "right" answer here, but I'd like to know how you guys tackle this process every year so feel free to chime in. Apologies if this has been answered in this community before, but my search did not bear fruit.

Charlie Echo
  • 81
  • 1
  • 8
  • 1
    If you'd like to stop having to do all that work, AND stop having to pay for the certs, check out https://github.com/win-acme/win-acme and letsencrypt. I now have completely automated, publicly trusted certs on all my servers! – Grant May 24 '21 at 22:38

2 Answers2

5

PowerShell is your friend.

https://docs.microsoft.com/en-us/powershell/module/pki/import-pfxcertificate
https://docs.microsoft.com/en-us/powershell/module/iisadministration/remove-iissitebinding
https://docs.microsoft.com/en-us/powershell/module/iisadministration/new-iissitebinding

You can directly pull the PFX file from a network path (\\server\share\filename.pfx) if you have the required permissions; if you need to specify credentials, use New-PSDrive.

You can put everything together in a script block and run it on remote servers using Invoke-Command; you can specify credentials here too, if required.

This can of course be done in a loop on a list of servers, such as

$serverlist = "server1","server2","server3"

foreach ($server in $serverlist)
{
    Invoke-Command -ComputerName $server -ScriptBlock
    {
        Import-PfxCertificate [...]
        Remove-IISSiteBinding [...]
        New-IISSiteBinding [...]
    }
}

If you need to create PSCredential objects, have a look here.

Last but not least, you can take in everything required (such as server names, credentials, web site names, etc) from a CSV file using Import-Csv.

Massimo
  • 70,200
  • 57
  • 200
  • 323
  • 1
    Thank you for your response and example. Just feels good to have some validation to my own research, trail, and error as PowerShell was not part of the typical answer on the web for this. I will give this a try tomorrow and let you know how it goes. – Charlie Echo May 24 '21 at 21:27
  • 1
    @CharlieEcho Please keep us updated please. I ended up on this question when researching the same subject for myself. I've got a similar situation coming up in a few weeks when our devops systems need new certs. I'm certainly interested. No point in re-inventing the wheel. – Tonny May 25 '21 at 09:34
  • 1
    Will do. Had a fire come up so I will have to push this, but when I circle back I'll make sure to update here. – Charlie Echo May 26 '21 at 06:52
  • @Tonny - I know you're not keen on PowerShell, but I think this one is gonna make your life easier. See below – Charlie Echo May 27 '21 at 14:39
3

Hello dudes and dudettes,

Seems I've wrangled another one here with the 'ol PowerShell. Credit to @Massimo for getting me going in the right direction.

The PS script below has worked perfectly for me so far. I left comments within the script so things should be very clear for even the most novice cowboys/cowgirls.

Items to note:

  1. This was ran as a domain admin from a management machine that's subnet has VLAN access to all other segments. (I know that sounds like a security nightmare, but it is subject to full auditing and access logging as well as remains offline when not in use.)
  2. This has only been tested on Windows Server 2016 and 2019 - would be surprised if it worked for anything below 2016.
  3. Windows Server 2016 defaults to IISAdmin module 1.0, but 1.1.0.0 is required for this script so I built in the download/install from the remote repo. Feel free to remove for your own use, or use it at your own discretion.
  4. I chose to copy the .pfx file to each machine rather than install it from the network share because it simplified the process (authentication, looping, redundancy, blahblahblah). At the end of the script the .pfx is removed though. If your file has a complex password this should be fine, but do so at your own discretion.
  5. This solution applies the new cert and binding for IIS as well as enables TLS for local SMTP.
#Setup the vars below.  Yes, you gotta do some of the work...
$OldCertDirHash = 'Cert:\LocalMachine\My\1234567890QWERTYUIOPLKJHGFDSA' # Old/Expired cert's store path and hash ID
$NewCertHash = '0987654321POIUYTREWQASDFGHJKL' 
$NewCertSharePath = '\\FS01.foo.internal\ITStuff\SCENSATIVE\install\STAR.foo.com_2022.pfx' # PFX file's shared location on the network
$CertPass = Get-Credential -UserName 'Type PFX password below' -Message 'Enter password below' #Stores PXF's password obscurely
$IISSiteName = 'Default Web Site' #Website's name for new binding
$ADCPUs = Get-ADComputer -Filter * -SearchBase "OU=Testing Lab,OU=Web Servers,DC=Foo,DC=com" |  select-object -expandproperty name  #Target machines in AD using an OU's Distinguished Name
$NetworkPFXPath = 'C$\Temp\Certs' #Network path for each remote machine
$LocalPXFPath = 'C:\temp\Certs\STAR.foo.com_2022.pfx'  #Path were the .pxf file will be accessed locally on each machine


ForEach ($ADCPU in $ADCPUs)
{Write-Host Copying file $NewCertSharePath to $ADCPU $NetworkPFXPath -ForegroundColor Yellow
New-Item -Path \\$ADCPU\$NetworkPFXPath -ItemType Directory #Creates local folder on each remote machine
Copy-Item -Path $NewCertSharePath -Destination \\$ADCPU\$NetworkPFXPath # Copies pfx file from network to each remote machine's local folder
}


# ~~~ End of Setup. Magic happens below ~~~


ForEach  ($ADCPU in $ADCPUs)
  {
Write-Host $ADCPU -ForegroundColor Yellow # Highlights Server names while in process
Invoke-Command -ComputerName $ADCPU -ScriptBlock { 

#Get latest IISAdmin version from online repo with suppressed dialog
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name IISAdministration -RequiredVersion 1.1.0.0 -SkipPublisherCheck -Confirm:$False -Force

#The default IISAdmin version for server 2016 is 1.0 - below brings it up to the required version which was installed above.       
Import-Module IISAdministration -Version 1.1.0.0

#This line installs the cert from the .pfx using the password set earlier into the Computer's Personal cert store.
Import-PfxCertificate -FilePath $Using:LocalPXFPath -CertStoreLocation Cert:\LocalMachine\My -Password $Using:CertPass.Password -ErrorAction Inquire

#Use below if importing .crt rather than .pfx ~ Not in use here ~
        #Import-Certificate -FilePath "UNCPath" -CertStoreLocation Cert:\LocalMachine\My -ErrorAction Inquire

#Clear out any other active or pending IIS Server Manager tasks - prevents config file collision.        
Reset-IISServerManager -Confirm:$false

#Remove current HTTPS binding using old/expired cert
Remove-IISSiteBinding -Name 'Default Web Site' -BindingInformation '*:443:' -Protocol https -Confirm:$false

#Delete expired cert - not required but is good houskeeping
Get-ChildItem $Using:OldCertDirHash | Remove-Item -ErrorAction Ignore

#Delete Cert by subject,serialnumber,issuer,etc... ~ Not in use here. ~
#Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.FriendlyName -match '*.foo.com_2021' } | Remove-Item

#Setup https binding on 443 using new cert
New-IISSiteBinding -Name $Using:IISSiteName -BindingInformation "*:443:" -CertificateThumbPrint $Using:NewCertHash -CertStoreLocation "Cert:\LocalMachine\My" -Protocol https 

IISRESET #for obvious reasons...

Remove-Item -Path $Using:LocalPXFPath -Recurse -Verbose  #Cleanup the dir created to store the pfx file

Write-Host $ADCPU Done -ForegroundColor Green #This is more of a marker for reviewing output and would be helpful if writing to log/oputfile
    }
    
  }

Sorry for the long post, but this is a robust and thorough solution to my problem. I had myself a couple extra drinks after work for besting this one. Feel free to send me a drink if this works for you!

Charlie Echo
  • 81
  • 1
  • 8