From e9aa6575b337c52a6469221c3ffc8446fff05ad7 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Mon, 9 Jan 2023 14:33:25 -0600 Subject: [PATCH 1/5] Refactored code adding Windows cert store logic, including renaming IISU to WinIIS. --- IISU/ImplementedStoreTypes/Win/Inventory.cs | 107 +++ IISU/ImplementedStoreTypes/Win/Management.cs | 24 + .../WinIIS}/IISManager.cs | 610 +++++++++--------- .../WinIIS}/Inventory.cs | 53 +- .../WinIIS}/Management.cs | 44 +- .../WinIIS}/ReEnrollment.cs | 387 +++++------ IISU/{ => Models}/JobProperties.cs | 2 +- .../Certificate.cs} | 4 +- .../CertificateException.cs} | 16 +- .../CertificateStore.cs} | 16 +- .../CertificateStoreException.cs} | 16 +- IISU/{IISU.csproj => WindowsCertStore.csproj} | 57 +- IISU/manifest.json | 26 +- IISU.sln => WindowsCertStore.sln | 96 +-- 14 files changed, 802 insertions(+), 656 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/Win/Inventory.cs create mode 100644 IISU/ImplementedStoreTypes/Win/Management.cs rename IISU/{ => ImplementedStoreTypes/WinIIS}/IISManager.cs (96%) rename IISU/{Jobs => ImplementedStoreTypes/WinIIS}/Inventory.cs (97%) rename IISU/{Jobs => ImplementedStoreTypes/WinIIS}/Management.cs (98%) rename IISU/{Jobs => ImplementedStoreTypes/WinIIS}/ReEnrollment.cs (97%) rename IISU/{ => Models}/JobProperties.cs (95%) rename IISU/{PSCertificate.cs => PowerShellUtilities/Certificate.cs} (88%) rename IISU/{PSCertStoreException.cs => PowerShellUtilities/CertificateException.cs} (67%) rename IISU/{PowerShellCertStore.cs => PowerShellUtilities/CertificateStore.cs} (85%) rename IISU/{PowerShellCertException.cs => PowerShellUtilities/CertificateStoreException.cs} (60%) rename IISU/{IISU.csproj => WindowsCertStore.csproj} (76%) rename IISU.sln => WindowsCertStore.sln (89%) diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs new file mode 100644 index 0000000..4767c7f --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -0,0 +1,107 @@ +// Copyright 2023 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win +{ + public class Inventory : IInventoryJobExtension + { + public string ExtensionName => "Win"; + + private ILogger _logger; + private IPAMSecretResolver _resolver; + + private string ServerUserName { get; set; } + private string ServerPassword { get; set; } + + public Inventory(IPAMSecretResolver resolver) + { + _resolver = resolver; + + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + } + + public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) + { + return PerformInventory(jobConfiguration, submitInventoryUpdate); + } + + public string ResolvePamField(string name, string value) + { + _logger.LogTrace($"Attempting to resolve PAM eligible field {name}"); + return _resolver.Resolve(value); + } + + private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) + { + try + { + ServerUserName = ResolvePamField("Server UserName", config.ServerUsername); + ServerPassword = ResolvePamField("Server Password", config.ServerPassword); + + _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); + + var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + var inventoryItems = new List(); + + _logger.LogTrace("Invoking Inventory..."); + submitInventory.Invoke(inventoryItems); + _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + } + catch (CertificateStoreException psEx) + { + _logger.LogTrace(psEx.Message); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"Unable to open remote certificate store: {LogHandler.FlattenException(psEx)}" + }; + } + catch (Exception ex) + { + _logger.LogTrace(LogHandler.FlattenException(ex)); + + var failureMessage = $"Inventory job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = failureMessage + }; + } + } + } +} diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs new file mode 100644 index 0000000..78655d6 --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -0,0 +1,24 @@ +using Keyfactor.Orchestrators.Extensions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win +{ + internal class Management : IManagementJobExtension + { + public string ExtensionName => throw new NotImplementedException(); + + public Management() + { + + } + + public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) + { + WinIIS.IISManager mgr = new WinIIS.IISManager(jobConfiguration, "", ""); + + throw new NotImplementedException(); + } + } +} diff --git a/IISU/IISManager.cs b/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs similarity index 96% rename from IISU/IISManager.cs rename to IISU/ImplementedStoreTypes/WinIIS/IISManager.cs index 4a96869..36d8b58 100644 --- a/IISU/IISManager.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs @@ -1,16 +1,16 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. using System; using System.Linq; @@ -24,9 +24,9 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { - public class IISManager + public class IISManager { private ILogger Logger { get; } private string SiteName { get; set; } @@ -34,7 +34,7 @@ public class IISManager private string HostName { get; set; } private long JobHistoryId { get; set; } private string Port { get; set; } - private string SniFlag { get; set; } + private string SniFlag { get; set; } private string Path { get; set; } private string ClientMachine { get; set; } private string Protocol { get; set; } @@ -44,61 +44,61 @@ public class IISManager private string ServerPassword { get; set; } private JobProperties Properties { get; set; } private string RenewalThumbprint { get; set; } - private WSManConnectionInfo ConnectionInfo { get; set; } - - - private X509Certificate2 x509Cert; - private Runspace runSpace; - private PowerShell ps; - - #region Constructors - /// - /// Performs a Reenrollment of a certificate in IIS - /// + private WSManConnectionInfo ConnectionInfo { get; set; } + + + private X509Certificate2 x509Cert; + private Runspace runSpace; + private PowerShell ps; + + #region Constructors + /// + /// Performs a Reenrollment of a certificate in IIS + /// /// - public IISManager(ReenrollmentJobConfiguration config,string serverUserName,string serverPassword) - { - Logger = LogHandler.GetClassLogger(); - - try - { - SiteName = config.JobProperties["SiteName"].ToString(); - Port = config.JobProperties["Port"].ToString(); - HostName = config.JobProperties["HostName"]?.ToString(); - Protocol = config.JobProperties["Protocol"].ToString(); - SniFlag = config.JobProperties["SniFlag"].ToString()?.Substring(0, 1); - IpAddress = config.JobProperties["IPAddress"].ToString(); - - PrivateKeyPassword = ""; // A reenrollment does not have a PFX Password - ServerUserName = serverUserName; - ServerPassword = serverPassword; - RenewalThumbprint = ""; // A reenrollment will always be empty - ClientMachine = config.CertificateStoreDetails.ClientMachine; - Path = config.CertificateStoreDetails.StorePath; - CertContents = ""; // Not needed for a reenrollment - JobHistoryId = config.JobHistoryId; - - Properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - - ConnectionInfo = - new WSManConnectionInfo( - new Uri($"{Properties?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{Properties?.WinRmPort}/wsman")); - } - catch (Exception e) - { - throw new Exception($"Error when initiating an IIS ReEnrollment Job: {e.Message}", e.InnerException); - } + public IISManager(ReenrollmentJobConfiguration config, string serverUserName, string serverPassword) + { + Logger = LogHandler.GetClassLogger(); + + try + { + SiteName = config.JobProperties["SiteName"].ToString(); + Port = config.JobProperties["Port"].ToString(); + HostName = config.JobProperties["HostName"]?.ToString(); + Protocol = config.JobProperties["Protocol"].ToString(); + SniFlag = config.JobProperties["SniFlag"].ToString()?.Substring(0, 1); + IpAddress = config.JobProperties["IPAddress"].ToString(); + + PrivateKeyPassword = ""; // A reenrollment does not have a PFX Password + ServerUserName = serverUserName; + ServerPassword = serverPassword; + RenewalThumbprint = ""; // A reenrollment will always be empty + ClientMachine = config.CertificateStoreDetails.ClientMachine; + Path = config.CertificateStoreDetails.StorePath; + CertContents = ""; // Not needed for a reenrollment + JobHistoryId = config.JobHistoryId; + + Properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, + new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + + ConnectionInfo = + new WSManConnectionInfo( + new Uri($"{Properties?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{Properties?.WinRmPort}/wsman")); + } + catch (Exception e) + { + throw new Exception($"Error when initiating an IIS ReEnrollment Job: {e.Message}", e.InnerException); + } } - /// - /// Performs Management functions of Adding or updating certificates in IIS - /// + /// + /// Performs Management functions of Adding or updating certificates in IIS + /// /// - public IISManager(ManagementJobConfiguration config, string serverUserName, string serverPassword) - { - Logger = LogHandler.GetClassLogger(); - + public IISManager(ManagementJobConfiguration config, string serverUserName, string serverPassword) + { + Logger = LogHandler.GetClassLogger(); + try { SiteName = config.JobProperties["SiteName"].ToString(); @@ -133,52 +133,52 @@ public IISManager(ManagementJobConfiguration config, string serverUserName, stri catch (Exception e) { throw new Exception($"Error when initiating an IIS Management Job: {e.Message}", e.InnerException); - } + } } - + #endregion - public JobResult ReEnrollCertificate(X509Certificate2 certificate) - { - x509Cert = certificate; - - try - { - // Instanciate a new Powershell instance - CreatePowerShellInstance(); - - return BindCertificate(); - - } - catch (Exception e) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryId, - FailureMessage = $"Error Occurred in ReEnrollCertification {LogHandler.FlattenException(e)}" - }; + public JobResult ReEnrollCertificate(X509Certificate2 certificate) + { + x509Cert = certificate; + + try + { + // Instanciate a new Powershell instance + CreatePowerShellInstance(); + + return BindCertificate(); + + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryId, + FailureMessage = $"Error Occurred in ReEnrollCertification {LogHandler.FlattenException(e)}" + }; } } - public JobResult AddCertificate() - { - try - { - Logger.LogTrace($"Creating X509 Cert from: {CertContents}"); - x509Cert = new X509Certificate2( - Convert.FromBase64String(CertContents), - PrivateKeyPassword, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | - X509KeyStorageFlags.Exportable); - Logger.LogTrace($"X509 Cert Created With Subject: {x509Cert.SubjectName}"); - Logger.LogTrace( - $"Begin Add for Cert Store {$@"\\{ClientMachine}\{Path}"}"); - - // Instanciate a new Powershell instance - CreatePowerShellInstance(); - - // Add Certificate + public JobResult AddCertificate() + { + try + { + Logger.LogTrace($"Creating X509 Cert from: {CertContents}"); + x509Cert = new X509Certificate2( + Convert.FromBase64String(CertContents), + PrivateKeyPassword, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | + X509KeyStorageFlags.Exportable); + Logger.LogTrace($"X509 Cert Created With Subject: {x509Cert.SubjectName}"); + Logger.LogTrace( + $"Begin Add for Cert Store {$@"\\{ClientMachine}\{Path}"}"); + + // Instanciate a new Powershell instance + CreatePowerShellInstance(); + + // Add Certificate var funcScript = @" $ErrorActionPreference = ""Stop"" @@ -188,124 +188,124 @@ function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$st $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $bytes, $password, 18 <# Persist, Machine #> $certStore.Add($cert) $certStore.Close(); - }"; - - ps.AddScript(funcScript).AddStatement(); - Logger.LogTrace("InstallPfxToMachineStore Statement Added..."); - ps.AddCommand("InstallPfxToMachineStore") - .AddParameter("bytes", Convert.FromBase64String(CertContents)) - .AddParameter("password", PrivateKeyPassword) - .AddParameter("storeName", - $@"\\{ClientMachine}\{Path}"); - Logger.LogTrace("InstallPfxToMachineStore Command Added..."); - - foreach (var cmd in ps.Commands.Commands) - { - Logger.LogTrace("Logging PowerShell Command"); - Logger.LogTrace(cmd.CommandText); - } - - Logger.LogTrace("Invoking ps..."); - ps.Invoke(); - Logger.LogTrace("ps Invoked..."); - if (ps.HadErrors) - { - Logger.LogTrace("ps Has Errors"); - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryId, - FailureMessage = - $"Site {Path} on server {ClientMachine}: {psError}" - }; - } - } - - Logger.LogTrace("Clearing Commands..."); - ps.Commands.Clear(); - Logger.LogTrace("Commands Cleared.."); - - // Install the certifiacte - return BindCertificate(); - } - catch (Exception e) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryId, - FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" - }; - } + }"; + + ps.AddScript(funcScript).AddStatement(); + Logger.LogTrace("InstallPfxToMachineStore Statement Added..."); + ps.AddCommand("InstallPfxToMachineStore") + .AddParameter("bytes", Convert.FromBase64String(CertContents)) + .AddParameter("password", PrivateKeyPassword) + .AddParameter("storeName", + $@"\\{ClientMachine}\{Path}"); + Logger.LogTrace("InstallPfxToMachineStore Command Added..."); + + foreach (var cmd in ps.Commands.Commands) + { + Logger.LogTrace("Logging PowerShell Command"); + Logger.LogTrace(cmd.CommandText); + } + + Logger.LogTrace("Invoking ps..."); + ps.Invoke(); + Logger.LogTrace("ps Invoked..."); + if (ps.HadErrors) + { + Logger.LogTrace("ps Has Errors"); + var psError = ps.Streams.Error.ReadAll() + .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryId, + FailureMessage = + $"Site {Path} on server {ClientMachine}: {psError}" + }; + } + } + + Logger.LogTrace("Clearing Commands..."); + ps.Commands.Clear(); + Logger.LogTrace("Commands Cleared.."); + + // Install the certifiacte + return BindCertificate(); + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryId, + FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" + }; + } } - private void CreatePowerShellInstance() - { - Logger.LogTrace($"IncludePortInSPN: {Properties.SpnPortFlag}"); - ConnectionInfo.IncludePortInSPN = Properties.SpnPortFlag; - Logger.LogTrace($"Credentials: UserName:{ServerUserName} Password:{ServerPassword}"); - var pw = new NetworkCredential(ServerUserName, ServerPassword) - .SecurePassword; - ConnectionInfo.Credential = new PSCredential(ServerUserName, pw); - Logger.LogTrace($"PSCredential Created {pw}"); - - runSpace = RunspaceFactory.CreateRunspace(ConnectionInfo); - Logger.LogTrace("RunSpace Created"); - runSpace.Open(); - Logger.LogTrace("RunSpace Opened"); - Logger.LogTrace( - $"Creating Cert Store with ClientMachine: {ClientMachine}, JobProperties: {Path}"); - var _ = new PowerShellCertStore( - ClientMachine, Path, - runSpace); - Logger.LogTrace("Cert Store Created"); - ps = PowerShell.Create(); - Logger.LogTrace("ps created"); - ps.Runspace = runSpace; - Logger.LogTrace("RunSpace Assigned"); + private void CreatePowerShellInstance() + { + Logger.LogTrace($"IncludePortInSPN: {Properties.SpnPortFlag}"); + ConnectionInfo.IncludePortInSPN = Properties.SpnPortFlag; + Logger.LogTrace($"Credentials: UserName:{ServerUserName} Password:{ServerPassword}"); + var pw = new NetworkCredential(ServerUserName, ServerPassword) + .SecurePassword; + ConnectionInfo.Credential = new PSCredential(ServerUserName, pw); + Logger.LogTrace($"PSCredential Created {pw}"); + + runSpace = RunspaceFactory.CreateRunspace(ConnectionInfo); + Logger.LogTrace("RunSpace Created"); + runSpace.Open(); + Logger.LogTrace("RunSpace Opened"); + Logger.LogTrace( + $"Creating Cert Store with ClientMachine: {ClientMachine}, JobProperties: {Path}"); + var _ = new PowerShellUtilities.CertificateStore( + ClientMachine, Path, + runSpace); + Logger.LogTrace("Cert Store Created"); + ps = PowerShell.Create(); + Logger.LogTrace("ps created"); + ps.Runspace = runSpace; + Logger.LogTrace("RunSpace Assigned"); } - private JobResult BindCertificate() - { - try - { - //if thumbprint is there it is a renewal so we have to search all the sites for that thumbprint and renew them all - if (RenewalThumbprint?.Length > 0) - { - Logger.LogTrace($"Thumbprint Length > 0 {RenewalThumbprint}"); - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - - Logger.LogTrace("WebAdministration Imported"); - var searchScript = - "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; - ps.AddScript(searchScript).AddStatement(); - Logger.LogTrace($"Search Script: {searchScript}"); - var bindings = ps.Invoke(); - foreach (var binding in bindings) - { - if (binding.Properties["Protocol"].Value.ToString().Contains("http")) - { - Logger.LogTrace("Looping Bindings...."); - var bindingSiteName = binding.Properties["name"].Value.ToString(); - var bindingIpAddress = binding.Properties["Bindings"].Value.ToString()?.Split(':')[0]; - var bindingPort = binding.Properties["Bindings"].Value.ToString()?.Split(':')[1]; - var bindingHostName = binding.Properties["Bindings"].Value.ToString()?.Split(':')[2]; - var bindingProtocol = binding.Properties["Protocol"].Value.ToString(); - var bindingThumbprint = binding.Properties["thumbprint"].Value.ToString(); - var bindingSniFlg = binding.Properties["sniFlg"].Value.ToString(); - - Logger.LogTrace( - $"bindingSiteName: {bindingSiteName}, bindingIpAddress: {bindingIpAddress}, bindingPort: {bindingPort}, bindingHostName: {bindingHostName}, bindingProtocol: {bindingProtocol}, bindingThumbprint: {bindingThumbprint}, bindingSniFlg: {bindingSniFlg}"); - - //if the thumbprint of the renewal request matches the thumbprint of the cert in IIS, then renew it - if (RenewalThumbprint == bindingThumbprint) - { - Logger.LogTrace($"Thumbprint Match {RenewalThumbprint}={bindingThumbprint}"); + private JobResult BindCertificate() + { + try + { + //if thumbprint is there it is a renewal so we have to search all the sites for that thumbprint and renew them all + if (RenewalThumbprint?.Length > 0) + { + Logger.LogTrace($"Thumbprint Length > 0 {RenewalThumbprint}"); + ps.AddCommand("Import-Module") + .AddParameter("Name", "WebAdministration") + .AddStatement(); + + Logger.LogTrace("WebAdministration Imported"); + var searchScript = + "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; + ps.AddScript(searchScript).AddStatement(); + Logger.LogTrace($"Search Script: {searchScript}"); + var bindings = ps.Invoke(); + foreach (var binding in bindings) + { + if (binding.Properties["Protocol"].Value.ToString().Contains("http")) + { + Logger.LogTrace("Looping Bindings...."); + var bindingSiteName = binding.Properties["name"].Value.ToString(); + var bindingIpAddress = binding.Properties["Bindings"].Value.ToString()?.Split(':')[0]; + var bindingPort = binding.Properties["Bindings"].Value.ToString()?.Split(':')[1]; + var bindingHostName = binding.Properties["Bindings"].Value.ToString()?.Split(':')[2]; + var bindingProtocol = binding.Properties["Protocol"].Value.ToString(); + var bindingThumbprint = binding.Properties["thumbprint"].Value.ToString(); + var bindingSniFlg = binding.Properties["sniFlg"].Value.ToString(); + + Logger.LogTrace( + $"bindingSiteName: {bindingSiteName}, bindingIpAddress: {bindingIpAddress}, bindingPort: {bindingPort}, bindingHostName: {bindingHostName}, bindingProtocol: {bindingProtocol}, bindingThumbprint: {bindingThumbprint}, bindingSniFlg: {bindingSniFlg}"); + + //if the thumbprint of the renewal request matches the thumbprint of the cert in IIS, then renew it + if (RenewalThumbprint == bindingThumbprint) + { + Logger.LogTrace($"Thumbprint Match {RenewalThumbprint}={bindingThumbprint}"); var funcScript = string.Format(@" $ErrorActionPreference = ""Stop"" @@ -318,34 +318,34 @@ private JobResult BindCertificate() New-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" -SslFlags ""{7}"" Get-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" | ForEach-Object {{ $_.AddSslCertificate(""{5}"", ""{6}"") }} - }}", bindingSiteName, //{0} - bindingIpAddress, //{1} - bindingPort, //{2} - bindingProtocol, //{3} - bindingHostName, //{4} - x509Cert.Thumbprint, //{5} - Path, //{6} - bindingSniFlg); //{7} - - Logger.LogTrace($"funcScript {funcScript}"); - ps.AddScript(funcScript); - Logger.LogTrace("funcScript added..."); - ps.Invoke(); - Logger.LogTrace("funcScript Invoked..."); - foreach (var cmd in ps.Commands.Commands) - { - Logger.LogTrace("Logging PowerShell Command"); - Logger.LogTrace(cmd.CommandText); - } - - ps.Commands.Clear(); - Logger.LogTrace("Commands Cleared.."); - } - } - } - } - else - { + }}", bindingSiteName, //{0} + bindingIpAddress, //{1} + bindingPort, //{2} + bindingProtocol, //{3} + bindingHostName, //{4} + x509Cert.Thumbprint, //{5} + Path, //{6} + bindingSniFlg); //{7} + + Logger.LogTrace($"funcScript {funcScript}"); + ps.AddScript(funcScript); + Logger.LogTrace("funcScript added..."); + ps.Invoke(); + Logger.LogTrace("funcScript Invoked..."); + foreach (var cmd in ps.Commands.Commands) + { + Logger.LogTrace("Logging PowerShell Command"); + Logger.LogTrace(cmd.CommandText); + } + + ps.Commands.Clear(); + Logger.LogTrace("Commands Cleared.."); + } + } + } + } + else + { var funcScript = string.Format(@" $ErrorActionPreference = ""Stop"" @@ -358,62 +358,62 @@ private JobResult BindCertificate() New-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" -SslFlags ""{7}"" Get-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" | ForEach-Object {{ $_.AddSslCertificate(""{5}"", ""{6}"") }} - }}", SiteName, //{0} - IpAddress, //{1} - Port, //{2} - Protocol, //{3} - HostName, //{4} - x509Cert.Thumbprint, //{5} - Path, //{6} - Convert.ToInt16(SniFlag)); //{7} - foreach (var cmd in ps.Commands.Commands) - { - Logger.LogTrace("Logging PowerShell Command"); - Logger.LogTrace(cmd.CommandText); - } - - Logger.LogTrace($"funcScript {funcScript}"); - ps.AddScript(funcScript); - Logger.LogTrace("funcScript added..."); - ps.Invoke(); - Logger.LogTrace("funcScript Invoked..."); - } - - if (ps.HadErrors) - { - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryId, - FailureMessage = - $"Site {Path} on server {ClientMachine}: {psError}" - }; - } - } - - Logger.LogTrace("closing RunSpace..."); - runSpace.Close(); - Logger.LogTrace("RunSpace Closed..."); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryId, - FailureMessage = "" - }; - } - catch (Exception e) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryId, - FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" - }; - } + }}", SiteName, //{0} + IpAddress, //{1} + Port, //{2} + Protocol, //{3} + HostName, //{4} + x509Cert.Thumbprint, //{5} + Path, //{6} + Convert.ToInt16(SniFlag)); //{7} + foreach (var cmd in ps.Commands.Commands) + { + Logger.LogTrace("Logging PowerShell Command"); + Logger.LogTrace(cmd.CommandText); + } + + Logger.LogTrace($"funcScript {funcScript}"); + ps.AddScript(funcScript); + Logger.LogTrace("funcScript added..."); + ps.Invoke(); + Logger.LogTrace("funcScript Invoked..."); + } + + if (ps.HadErrors) + { + var psError = ps.Streams.Error.ReadAll() + .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryId, + FailureMessage = + $"Site {Path} on server {ClientMachine}: {psError}" + }; + } + } + + Logger.LogTrace("closing RunSpace..."); + runSpace.Close(); + Logger.LogTrace("RunSpace Closed..."); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = JobHistoryId, + FailureMessage = "" + }; + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryId, + FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" + }; + } } } } \ No newline at end of file diff --git a/IISU/Jobs/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs similarity index 97% rename from IISU/Jobs/Inventory.cs rename to IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index be8acb5..c845f94 100644 --- a/IISU/Jobs/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -1,16 +1,16 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. using System; using System.Collections.Generic; @@ -24,23 +24,24 @@ using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; -namespace Keyfactor.Extensions.Orchestrator.IISU.Jobs +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { public class Inventory : IInventoryJobExtension { - private ILogger _logger; - - private IPAMSecretResolver _resolver; - + private ILogger _logger; + + private IPAMSecretResolver _resolver; + private string ServerUserName { get; set; } private string ServerPassword { get; set; } public Inventory(IPAMSecretResolver resolver) { _resolver = resolver; - } - + } + private string ResolvePamField(string name, string value) { _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); @@ -50,10 +51,10 @@ private string ResolvePamField(string name, string value) private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) { try - { + { _logger = LogHandler.GetClassLogger(); - _logger.MethodEntry(); - ServerUserName = ResolvePamField("Server UserName", config.ServerUsername); + _logger.MethodEntry(); + ServerUserName = ResolvePamField("Server UserName", config.ServerUsername); ServerPassword = ResolvePamField("Server Password", config.ServerPassword); _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); @@ -78,7 +79,7 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven runSpace.Open(); _logger.LogTrace("runSpace Opened"); - var psCertStore = new PowerShellCertStore( + var psCertStore = new PowerShellUtilities.CertificateStore( config.CertificateStoreDetails.ClientMachine, config.CertificateStoreDetails.StorePath, runSpace); _logger.LogTrace("psCertStore Created"); @@ -200,7 +201,7 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven FailureMessage = "" }; } - catch (PsCertStoreException psEx) + catch (CertificateStoreException psEx) { _logger.LogTrace(psEx.Message); return new JobResult @@ -227,7 +228,7 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven } } - public string ExtensionName => "IISU"; + public string ExtensionName => "WinIIS"; public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) { return PerformInventory(jobConfiguration, submitInventoryUpdate); diff --git a/IISU/Jobs/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs similarity index 98% rename from IISU/Jobs/Management.cs rename to IISU/ImplementedStoreTypes/WinIIS/Management.cs index 492107b..53fdca1 100644 --- a/IISU/Jobs/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -1,16 +1,16 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. using System; using System.Linq; @@ -24,7 +24,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; -namespace Keyfactor.Extensions.Orchestrator.IISU.Jobs +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { public class Management : IManagementJobExtension { @@ -44,16 +44,16 @@ public Management(IPAMSecretResolver resolver) public string ExtensionName => "IISU"; - private string ResolvePamField(string name,string value) - { - _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); - return _resolver.Resolve(value); + private string ResolvePamField(string name,string value) + { + _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); + return _resolver.Resolve(value); } public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) { _logger = LogHandler.GetClassLogger(); - ServerUserName = ResolvePamField("Server UserName", jobConfiguration.ServerUsername); + ServerUserName = ResolvePamField("Server UserName", jobConfiguration.ServerUsername); ServerPassword = ResolvePamField("Server Password", jobConfiguration.ServerPassword); _logger.MethodEntry(); _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(jobConfiguration)}"); @@ -122,7 +122,7 @@ private JobResult PerformRemoval(ManagementJobConfiguration config) _logger.LogTrace("runSpace Created"); runSpace.Open(); _logger.LogTrace("runSpace Opened"); - var psCertStore = new PowerShellCertStore( + var psCertStore = new PowerShellUtilities.CertificateStore( config.CertificateStoreDetails.ClientMachine, config.CertificateStoreDetails.StorePath, runSpace); _logger.LogTrace("psCertStore Created"); @@ -258,7 +258,7 @@ private JobResult PerformAddition(ManagementJobConfiguration config) FailureMessage = failureMessage }; } - } - + } + } } \ No newline at end of file diff --git a/IISU/Jobs/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs similarity index 97% rename from IISU/Jobs/ReEnrollment.cs rename to IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs index edb7036..e197100 100644 --- a/IISU/Jobs/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs @@ -1,37 +1,38 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Text; +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -namespace Keyfactor.Extensions.Orchestrator.IISU.Jobs +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { public class ReEnrollment:IReenrollmentJobExtension { - private ILogger _logger; + private ILogger _logger; private IPAMSecretResolver _resolver; @@ -42,186 +43,186 @@ public ReEnrollment(IPAMSecretResolver resolver) public string ExtensionName => "IISU"; - private string ResolvePamField(string name, string value) - { - _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); - return _resolver.Resolve(value); - } - + private string ResolvePamField(string name, string value) + { + _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); + return _resolver.Resolve(value); + } + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReEnrollmentUpdate) { _logger = LogHandler.GetClassLogger(); _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - _logger.LogTrace($"WinRm Url: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); - - _logger.LogTrace("Entering ReEnrollment..."); - _logger.LogTrace("Before ReEnrollment..."); - return PerformReEnrollment(config, submitReEnrollmentUpdate); - - } - + _logger.LogTrace($"WinRm Url: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); + + _logger.LogTrace("Entering ReEnrollment..."); + _logger.LogTrace("Before ReEnrollment..."); + return PerformReEnrollment(config, submitReEnrollmentUpdate); + + } + private JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) { try { - _logger.MethodEntry(); - var serverUserName = ResolvePamField("Server UserName", config.ServerUsername); - var serverPassword = ResolvePamField("Server Password", config.ServerPassword); - - // Extract values necessary to create remote PS connection - JobProperties properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - - WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri($"{properties?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{properties?.WinRmPort}/wsman")); - connectionInfo.IncludePortInSPN = properties.SpnPortFlag; - var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; - _logger.LogTrace($"Credentials: UserName:{serverUserName} Password:{serverPassword}"); - - connectionInfo.Credential = new PSCredential(serverUserName, pw); - _logger.LogTrace($"PSCredential Created {pw}"); - - // Establish new remote ps session - _logger.LogTrace("Creating remote PS Workspace"); - using var runSpace = RunspaceFactory.CreateRunspace(connectionInfo); - _logger.LogTrace("Workspace created"); - runSpace.Open(); - _logger.LogTrace("Workspace opened"); - - // NEW - var ps = PowerShell.Create(); - ps.Runspace = runSpace; - - string CSR = string.Empty; - - var subjectText = config.JobProperties["subjectText"]; - var providerName = config.JobProperties["ProviderName"]; - var keyType = config.JobProperties["keyType"]; - var keySize = config.JobProperties["keySize"]; - var SAN = config.JobProperties["SAN"]; - - // If the provider name is null, default it to the Microsoft CA - if (providerName == null) providerName = "Microsoft Strong Cryptographic Provider"; - - // Create the script file - ps.AddScript("$infFilename = New-TemporaryFile"); - ps.AddScript("$csrFilename = New-TemporaryFile"); - - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); - - ps.AddScript($"Set-Content $infFilename [NewRequest]"); - ps.AddScript($"Add-Content $infFilename 'Subject = \"{subjectText}\"'"); - ps.AddScript($"Add-Content $infFilename 'ProviderName = \"{providerName}\"'"); - ps.AddScript($"Add-Content $infFilename 'MachineKeySet = True'"); - ps.AddScript($"Add-Content $infFilename 'HashAlgorithm = SHA256'"); - ps.AddScript($"Add-Content $infFilename 'KeyAlgorithm = {keyType}'"); - ps.AddScript($"Add-Content $infFilename 'KeyLength={keySize}'"); - ps.AddScript($"Add-Content $infFilename 'KeySpec = 0'"); - - if(SAN != null) - { - ps.AddScript($"Add-Content $infFilename '[Extensions]'"); - ps.AddScript(@"Add-Content $infFilename '2.5.29.17 = ""{text}""'"); - - foreach (string s in SAN.ToString().Split("&")) - { - ps.AddScript($"Add-Content $infFilename '_continue_ = \"{s + "&"}\"'"); - } - } - - // Execute the -new command - ps.AddScript($"certreq -new -q $infFilename $csrFilename"); - _logger.LogDebug($"Subject Text: {subjectText}"); - _logger.LogDebug($"SAN: {SAN}"); - _logger.LogDebug($"Provider Name: {providerName}"); - _logger.LogDebug($"Key Type: {keyType}"); - _logger.LogDebug($"Key Size: {keySize}"); - _logger.LogTrace("Attempting to create the CSR by Invoking the script."); - - Collection results = ps.Invoke(); - _logger.LogTrace("Completed the attempt in creating the CSR."); - ps.Commands.Clear(); - - try - { - ps.AddScript($"$CSR = Get-Content $csrFilename"); - _logger.LogTrace("Attempting to get the contents of the CSR file."); - results = ps.Invoke(); - _logger.LogTrace("Finished getting the CSR Contents."); - } - catch (Exception) - { - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - throw new PowerShellCertException($"Error creating CSR File. {psError}"); - } - finally - { - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - _logger.LogTrace("Attempt to delete the temporary files."); - results = ps.Invoke(); - } - - // Get the byte array - var CSRContent = ps.Runspace.SessionStateProxy.GetVariable("CSR").ToString(); - - // Sign CSR in Keyfactor - _logger.LogTrace("Get the signed CSR from KF."); - X509Certificate2 myCert = submitReenrollment.Invoke(CSRContent); - - if (myCert != null) - { - // Get the cert data into string format - string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); - - _logger.LogTrace("Creating the text version of the certificate."); - - // Write out the cert file - StringBuilder sb = new StringBuilder(); - sb.AppendLine("-----BEGIN CERTIFICATE-----"); - sb.AppendLine(csrData); - sb.AppendLine("-----END CERTIFICATE-----"); - - ps.AddScript("$cerFilename = New-TemporaryFile"); - ps.AddScript($"Set-Content $cerFilename '{sb}'"); - - results = ps.Invoke(); - ps.Commands.Clear(); - - // Accept the signed cert - _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); - ps.AddScript("certreq -accept $cerFilename"); - ps.Invoke(); - _logger.LogTrace("Successfully bound the certificate to the HSM."); - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); - _logger.LogTrace("Removing temporary files."); - results = ps.Invoke(); - - ps.Commands.Clear(); - runSpace.Close(); - - // Bind the certificate to IIS - var iisManager = new IISManager(config,serverUserName,serverPassword); - return iisManager.ReEnrollCertificate(myCert); + _logger.MethodEntry(); + var serverUserName = ResolvePamField("Server UserName", config.ServerUsername); + var serverPassword = ResolvePamField("Server Password", config.ServerPassword); + + // Extract values necessary to create remote PS connection + JobProperties properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, + new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + + WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri($"{properties?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{properties?.WinRmPort}/wsman")); + connectionInfo.IncludePortInSPN = properties.SpnPortFlag; + var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; + _logger.LogTrace($"Credentials: UserName:{serverUserName} Password:{serverPassword}"); + + connectionInfo.Credential = new PSCredential(serverUserName, pw); + _logger.LogTrace($"PSCredential Created {pw}"); + + // Establish new remote ps session + _logger.LogTrace("Creating remote PS Workspace"); + using var runSpace = RunspaceFactory.CreateRunspace(connectionInfo); + _logger.LogTrace("Workspace created"); + runSpace.Open(); + _logger.LogTrace("Workspace opened"); + + // NEW + var ps = PowerShell.Create(); + ps.Runspace = runSpace; + + string CSR = string.Empty; + + var subjectText = config.JobProperties["subjectText"]; + var providerName = config.JobProperties["ProviderName"]; + var keyType = config.JobProperties["keyType"]; + var keySize = config.JobProperties["keySize"]; + var SAN = config.JobProperties["SAN"]; + + // If the provider name is null, default it to the Microsoft CA + if (providerName == null) providerName = "Microsoft Strong Cryptographic Provider"; + + // Create the script file + ps.AddScript("$infFilename = New-TemporaryFile"); + ps.AddScript("$csrFilename = New-TemporaryFile"); + + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); + + ps.AddScript($"Set-Content $infFilename [NewRequest]"); + ps.AddScript($"Add-Content $infFilename 'Subject = \"{subjectText}\"'"); + ps.AddScript($"Add-Content $infFilename 'ProviderName = \"{providerName}\"'"); + ps.AddScript($"Add-Content $infFilename 'MachineKeySet = True'"); + ps.AddScript($"Add-Content $infFilename 'HashAlgorithm = SHA256'"); + ps.AddScript($"Add-Content $infFilename 'KeyAlgorithm = {keyType}'"); + ps.AddScript($"Add-Content $infFilename 'KeyLength={keySize}'"); + ps.AddScript($"Add-Content $infFilename 'KeySpec = 0'"); + + if(SAN != null) + { + ps.AddScript($"Add-Content $infFilename '[Extensions]'"); + ps.AddScript(@"Add-Content $infFilename '2.5.29.17 = ""{text}""'"); + + foreach (string s in SAN.ToString().Split("&")) + { + ps.AddScript($"Add-Content $infFilename '_continue_ = \"{s + "&"}\"'"); + } + } + + // Execute the -new command + ps.AddScript($"certreq -new -q $infFilename $csrFilename"); + _logger.LogDebug($"Subject Text: {subjectText}"); + _logger.LogDebug($"SAN: {SAN}"); + _logger.LogDebug($"Provider Name: {providerName}"); + _logger.LogDebug($"Key Type: {keyType}"); + _logger.LogDebug($"Key Size: {keySize}"); + _logger.LogTrace("Attempting to create the CSR by Invoking the script."); + + Collection results = ps.Invoke(); + _logger.LogTrace("Completed the attempt in creating the CSR."); + ps.Commands.Clear(); + + try + { + ps.AddScript($"$CSR = Get-Content $csrFilename"); + _logger.LogTrace("Attempting to get the contents of the CSR file."); + results = ps.Invoke(); + _logger.LogTrace("Finished getting the CSR Contents."); + } + catch (Exception) + { + var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + throw new CertificateException($"Error creating CSR File. {psError}"); } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." + finally + { + ps.Commands.Clear(); + + // Delete the temp files + ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); + _logger.LogTrace("Attempt to delete the temporary files."); + results = ps.Invoke(); + } + + // Get the byte array + var CSRContent = ps.Runspace.SessionStateProxy.GetVariable("CSR").ToString(); + + // Sign CSR in Keyfactor + _logger.LogTrace("Get the signed CSR from KF."); + X509Certificate2 myCert = submitReenrollment.Invoke(CSRContent); + + if (myCert != null) + { + // Get the cert data into string format + string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); + + _logger.LogTrace("Creating the text version of the certificate."); + + // Write out the cert file + StringBuilder sb = new StringBuilder(); + sb.AppendLine("-----BEGIN CERTIFICATE-----"); + sb.AppendLine(csrData); + sb.AppendLine("-----END CERTIFICATE-----"); + + ps.AddScript("$cerFilename = New-TemporaryFile"); + ps.AddScript($"Set-Content $cerFilename '{sb}'"); + + results = ps.Invoke(); + ps.Commands.Clear(); + + // Accept the signed cert + _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); + ps.AddScript("certreq -accept $cerFilename"); + ps.Invoke(); + _logger.LogTrace("Successfully bound the certificate to the HSM."); + ps.Commands.Clear(); + + // Delete the temp files + ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); + ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); + _logger.LogTrace("Removing temporary files."); + results = ps.Invoke(); + + ps.Commands.Clear(); + runSpace.Close(); + + // Bind the certificate to IIS + var iisManager = new IISManager(config,serverUserName,serverPassword); + return iisManager.ReEnrollCertificate(myCert); + } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." }; } - + } catch (Exception ex) { diff --git a/IISU/JobProperties.cs b/IISU/Models/JobProperties.cs similarity index 95% rename from IISU/JobProperties.cs rename to IISU/Models/JobProperties.cs index 7c78948..7e8926c 100644 --- a/IISU/JobProperties.cs +++ b/IISU/Models/JobProperties.cs @@ -15,7 +15,7 @@ using System.ComponentModel; using Newtonsoft.Json; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { internal class JobProperties { diff --git a/IISU/PSCertificate.cs b/IISU/PowerShellUtilities/Certificate.cs similarity index 88% rename from IISU/PSCertificate.cs rename to IISU/PowerShellUtilities/Certificate.cs index 5bfdf7f..905849a 100644 --- a/IISU/PSCertificate.cs +++ b/IISU/PowerShellUtilities/Certificate.cs @@ -14,9 +14,9 @@ using System; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities { - public class PsCertificate + public class Certificate { public string Thumbprint { get; set; } public byte[] RawData { get; set; } diff --git a/IISU/PSCertStoreException.cs b/IISU/PowerShellUtilities/CertificateException.cs similarity index 67% rename from IISU/PSCertStoreException.cs rename to IISU/PowerShellUtilities/CertificateException.cs index 5afc67b..793674a 100644 --- a/IISU/PSCertStoreException.cs +++ b/IISU/PowerShellUtilities/CertificateException.cs @@ -13,27 +13,29 @@ // limitations under the License. using System; +using System.Collections.Generic; using System.Runtime.Serialization; +using System.Text; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities { [Serializable] - internal class PsCertStoreException : Exception + internal class CertificateException : Exception { - public PsCertStoreException() + public CertificateException() { } - public PsCertStoreException(string message) : base(message) + public CertificateException(string message) : base(message) { } - public PsCertStoreException(string message, Exception innerException) : base(message, innerException) + public CertificateException(string message, Exception innerException) : base(message, innerException) { } - protected PsCertStoreException(SerializationInfo info, StreamingContext context) : base(info, context) + protected CertificateException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/IISU/PowerShellCertStore.cs b/IISU/PowerShellUtilities/CertificateStore.cs similarity index 85% rename from IISU/PowerShellCertStore.cs rename to IISU/PowerShellUtilities/CertificateStore.cs index ce9505a..7f79494 100644 --- a/IISU/PowerShellCertStore.cs +++ b/IISU/PowerShellUtilities/CertificateStore.cs @@ -17,11 +17,11 @@ using System.Management.Automation; using System.Management.Automation.Runspaces; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities { - internal class PowerShellCertStore + internal class CertificateStore { - public PowerShellCertStore(string serverName, string storePath, Runspace runSpace) + public CertificateStore(string serverName, string storePath, Runspace runSpace) { ServerName = serverName; StorePath = storePath; @@ -32,7 +32,7 @@ public PowerShellCertStore(string serverName, string storePath, Runspace runSpac public string ServerName { get; set; } public string StorePath { get; set; } public Runspace RunSpace { get; set; } - public List Certificates { get; set; } + public List Certificates { get; set; } public void RemoveCertificate(string thumbprint) { @@ -54,12 +54,12 @@ public void RemoveCertificate(string thumbprint) var _ = ps.Invoke(); if (ps.HadErrors) - throw new PsCertStoreException($"Error removing certificate in {StorePath} store on {ServerName}."); + throw new CertificateStoreException($"Error removing certificate in {StorePath} store on {ServerName}."); } private void Initalize() { - Certificates = new List(); + Certificates = new List(); try { using var ps = PowerShell.Create(); @@ -80,7 +80,7 @@ private void Initalize() var certs = ps.Invoke(); foreach (var c in certs) - Certificates.Add(new PsCertificate + Certificates.Add(new Certificate { Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), @@ -89,7 +89,7 @@ private void Initalize() } catch (Exception ex) { - throw new PsCertStoreException( + throw new CertificateStoreException( $"Error listing certificate in {StorePath} store on {ServerName}: {ex.Message}"); } } diff --git a/IISU/PowerShellCertException.cs b/IISU/PowerShellUtilities/CertificateStoreException.cs similarity index 60% rename from IISU/PowerShellCertException.cs rename to IISU/PowerShellUtilities/CertificateStoreException.cs index 87d915c..4d540a0 100644 --- a/IISU/PowerShellCertException.cs +++ b/IISU/PowerShellUtilities/CertificateStoreException.cs @@ -13,29 +13,27 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.Runtime.Serialization; -using System.Text; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities { [Serializable] - internal class PowerShellCertException : Exception + internal class CertificateStoreException : Exception { - public PowerShellCertException() + public CertificateStoreException() { } - public PowerShellCertException(string message) : base(message) + public CertificateStoreException(string message) : base(message) { } - public PowerShellCertException(string message, Exception innerException) : base(message, innerException) + public CertificateStoreException(string message, Exception innerException) : base(message, innerException) { } - protected PowerShellCertException(SerializationInfo info, StreamingContext context) : base(info, context) + protected CertificateStoreException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} +} \ No newline at end of file diff --git a/IISU/IISU.csproj b/IISU/WindowsCertStore.csproj similarity index 76% rename from IISU/IISU.csproj rename to IISU/WindowsCertStore.csproj index 4ad8110..df23cf8 100644 --- a/IISU/IISU.csproj +++ b/IISU/WindowsCertStore.csproj @@ -1,30 +1,35 @@ - - - - netcoreapp3.1 - Keyfactor.Extensions.Orchestrator.IISU - true - - - - none - false - - - - + + + + netcoreapp3.1 + Keyfactor.Extensions.Orchestrator.WindowsCertStore + true + + + + none + false + + + + - + - - - - - - PreserveNewest - - - - + + + + + + PreserveNewest + + + + + + + + + diff --git a/IISU/manifest.json b/IISU/manifest.json index b09b129..a3efd9b 100644 --- a/IISU/manifest.json +++ b/IISU/manifest.json @@ -1,17 +1,25 @@ { "extensions": { "Keyfactor.Orchestrators.Extensions.IOrchestratorJobExtension": { - "CertStores.IISU.Inventory": { - "assemblypath": "IISU.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.IISU.Jobs.Inventory" + "CertStores.WinIIS.Inventory": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS.Inventory" }, - "CertStores.IISU.Management": { - "assemblypath": "IISU.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.IISU.Jobs.Management" + "CertStores.WinIIS.Management": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS.Management" }, - "CertStores.IISU.ReEnrollment": { - "assemblypath": "IISU.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.IISU.Jobs.ReEnrollment" + "CertStores.WinIIS.ReEnrollment": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS.ReEnrollment" + }, + "CertStores.Win.Inventory": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.Inventory" + }, + "CertStores.Win.Management": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.Management" } } } diff --git a/IISU.sln b/WindowsCertStore.sln similarity index 89% rename from IISU.sln rename to WindowsCertStore.sln index 50f7736..7b14b13 100644 --- a/IISU.sln +++ b/WindowsCertStore.sln @@ -1,48 +1,48 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.32929.386 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISU", "IISU\IISU.csproj", "{33FBC5A1-3466-4F10-B9A6-7186F804A65A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A6C93E7-24FD-47FD-883D-EDABF5CEE4C6}" - ProjectSection(SolutionItems) = preProject - CHANGELOG.md = CHANGELOG.md - integration-manifest.json = integration-manifest.json - .github\workflows\keyfactor-extension-release.yml = .github\workflows\keyfactor-extension-release.yml - readme_source.md = readme_source.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{6302034E-DF8C-4B65-AC36-CED24C068999}" - ProjectSection(SolutionItems) = preProject - images\AddCertStore.png = images\AddCertStore.png - images\CertStoreType-c.png = images\CertStoreType-c.png - images\CertStoreType.png = images\CertStoreType.png - images\ReEnrollment1.png = images\ReEnrollment1.png - images\ReEnrollment1a.png = images\ReEnrollment1a.png - images\ReEnrollment1b.png = images\ReEnrollment1b.png - images\Screen1.png = images\Screen1.png - images\Screen2.png = images\Screen2.png - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {6302034E-DF8C-4B65-AC36-CED24C068999} = {1A6C93E7-24FD-47FD-883D-EDABF5CEE4C6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E0FA12DA-6B82-4E64-928A-BB9965E636C1} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32616.157 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsCertStore", "IISU\WindowsCertStore.csproj", "{33FBC5A1-3466-4F10-B9A6-7186F804A65A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A6C93E7-24FD-47FD-883D-EDABF5CEE4C6}" + ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + integration-manifest.json = integration-manifest.json + .github\workflows\keyfactor-extension-release.yml = .github\workflows\keyfactor-extension-release.yml + readme_source.md = readme_source.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{6302034E-DF8C-4B65-AC36-CED24C068999}" + ProjectSection(SolutionItems) = preProject + images\AddCertStore.png = images\AddCertStore.png + images\CertStoreType-c.png = images\CertStoreType-c.png + images\CertStoreType.png = images\CertStoreType.png + images\ReEnrollment1.png = images\ReEnrollment1.png + images\ReEnrollment1a.png = images\ReEnrollment1a.png + images\ReEnrollment1b.png = images\ReEnrollment1b.png + images\Screen1.png = images\Screen1.png + images\Screen2.png = images\Screen2.png + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6302034E-DF8C-4B65-AC36-CED24C068999} = {1A6C93E7-24FD-47FD-883D-EDABF5CEE4C6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E0FA12DA-6B82-4E64-928A-BB9965E636C1} + EndGlobalSection +EndGlobal From a35b0e80fd9583468466b89b9b336ef369425250 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 11 Jan 2023 16:41:50 -0600 Subject: [PATCH 2/5] Added PowerShell class to perform get-childitem from cert store --- IISU/ImplementedStoreTypes/Win/Inventory.cs | 83 ++++++++++++++++++++- IISU/PowerShellUtilities/PSCommandHelper.cs | 51 +++++++++++++ 2 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 IISU/PowerShellUtilities/PSCommandHelper.cs diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index 4767c7f..dd0df9c 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -14,7 +14,10 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using System.Security.Cryptography.X509Certificates; using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; @@ -66,9 +69,81 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); var inventoryItems = new List(); - _logger.LogTrace("Invoking Inventory..."); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); + // Setup a new connection to the client machine + var connectionInfo = new WSManConnectionInfo(new Uri($"{storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman")); + _logger.LogTrace($"WinRm URL: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); + + if (storePath != null) + { + // Set credentials object + var pw = new NetworkCredential(ServerUserName, ServerPassword).SecurePassword; + + connectionInfo.Credential = new PSCredential(ServerUserName, pw); + + // Create the PowerShell Runspace + using var runSpace = RunspaceFactory.CreateRunspace(connectionInfo); + _logger.LogTrace("runSpace Created"); + runSpace.Open(); + _logger.LogTrace("runSpace Opened"); + + using (var ps = PowerShell.Create()) + { + ps.Runspace = runSpace; + _logger.LogTrace("RunSpace Created"); + + try + { + // Call PowerShell Command to get child items (certs) + _logger.LogTrace($"Attempting to get licenses from cert path: {config.CertificateStoreDetails.StorePath})"); + List myCerts = + PSCommandHelper.GetChildItem(ps, config.CertificateStoreDetails.StorePath); + + if(myCerts.Count > 0) + { + _logger.LogTrace($"Found {myCerts.Count} certificates in path: {config.CertificateStoreDetails.StorePath}"); + foreach (X509Certificate2 thisCert in myCerts) + { + CurrentInventoryItem inventoryItem = new CurrentInventoryItem() + { + Certificates = new[] { thisCert.GetRawCertDataString().ToString() }, + Alias = thisCert.Thumbprint, + PrivateKeyEntry = thisCert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = null + }; + + inventoryItems.Add(inventoryItem); + } + + // Get Certificate info and add to list of inventory items to pass back to KF + _logger.LogTrace("Invoking Inventory..."); + submitInventory.Invoke(inventoryItems); + _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); + } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Warning, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"No certificates were found in the Certificate Store Path: {config.CertificateStoreDetails.StorePath} on server: {config.CertificateStoreDetails.ClientMachine}" + }; + } + } + catch (CertificateStoreException certEx) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: failed with Error: {certEx.Message}" + }; + } + } + } return new JobResult { diff --git a/IISU/PowerShellUtilities/PSCommandHelper.cs b/IISU/PowerShellUtilities/PSCommandHelper.cs new file mode 100644 index 0000000..fb0b3ef --- /dev/null +++ b/IISU/PowerShellUtilities/PSCommandHelper.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities +{ + public class PSCommandHelper + { + /// + /// + /// + /// + /// + /// List + /// + public static List GetChildItem(PowerShell ps, string certStorePath) + { + string output = string.Empty; + string errorMsg = string.Empty; + List certificates = new List(); + + var script = $"Get-ChildItem Cert:{certStorePath}"; + ps.AddScript(script); + + // Establish a Powershell output object + PSDataCollection outputCollection = new PSDataCollection(); + ps.Streams.Error.DataAdded += (object sender, DataAddedEventArgs e) => + { + errorMsg = ((PSDataCollection)sender)[e.Index].ToString(); + }; + + IAsyncResult result = ps.BeginInvoke(null, outputCollection); + ps.EndInvoke(result); + + foreach (var outputItem in outputCollection) + { + X509Certificate2 cert = (X509Certificate2)outputItem.BaseObject; + certificates.Add(cert); + } + + ps.Commands.Clear(); + + if (!string.IsNullOrEmpty(errorMsg)) + throw new CertificateStoreException(errorMsg); + + return certificates; + } + } +} From 119a25be3a8a04b8be4bf9f1f656fe0184d83d96 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 28 Feb 2023 20:07:07 +0000 Subject: [PATCH 3/5] Refactored code allowing multiple types of Cert Stores, including Win Cert and IIS (WebHosting) Cert Stores. --- IISU/{PowerShellUtilities => }/Certificate.cs | 2 +- IISU/CertificateStore.cs | 226 +++++++++ .../CertificateStoreException.cs | 2 +- IISU/ClientPSCertStoreInventory.cs | 74 +++ IISU/ClientPSCertStoreManager.cs | 168 +++++++ IISU/ClientPSCertStoreReEnrollment.cs | 274 +++++++++++ IISU/ClientPSIIManager.cs | 428 ++++++++++++++++++ IISU/ImplementedStoreTypes/Win/Inventory.cs | 131 ++---- IISU/ImplementedStoreTypes/Win/Management.cs | 224 ++++++++- .../ImplementedStoreTypes/Win/ReEnrollment.cs | 41 ++ .../ImplementedStoreTypes/Win/WinInventory.cs | 50 ++ .../WinIIS/IISManager.cs | 24 +- .../ImplementedStoreTypes/WinIIS/Inventory.cs | 191 ++------ .../WinIIS/Management.cs | 266 ++++------- .../WinIIS/ReEnrollment.cs | 207 +-------- .../WinIIS/WinIISInventory.cs | 117 +++++ ...ertificateException.cs => PAMUtilities.cs} | 27 +- IISU/PSHelper.cs | 42 ++ IISU/PowerShellUtilities/CertificateStore.cs | 97 ---- IISU/PowerShellUtilities/PSCommandHelper.cs | 51 --- IISU/Properties/launchSettings.json | 2 +- IISU/WinCertJobTypeBase.cs | 23 + IISU/WindowsCertStore.csproj | 17 +- IISU/manifest.json | 4 + WinCertTestConsole/Program.cs | 128 ++++++ WinCertTestConsole/WinCertTestConsole.csproj | 18 + WindowsCertStore.sln | 8 + images/IISCertStore.png | Bin 0 -> 24867 bytes images/WinCertStore.png | Bin 0 -> 20632 bytes readme_source.md | 95 ++-- 30 files changed, 2096 insertions(+), 841 deletions(-) rename IISU/{PowerShellUtilities => }/Certificate.cs (91%) create mode 100644 IISU/CertificateStore.cs rename IISU/{PowerShellUtilities => }/CertificateStoreException.cs (93%) create mode 100644 IISU/ClientPSCertStoreInventory.cs create mode 100644 IISU/ClientPSCertStoreManager.cs create mode 100644 IISU/ClientPSCertStoreReEnrollment.cs create mode 100644 IISU/ClientPSIIManager.cs create mode 100644 IISU/ImplementedStoreTypes/Win/ReEnrollment.cs create mode 100644 IISU/ImplementedStoreTypes/Win/WinInventory.cs create mode 100644 IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs rename IISU/{PowerShellUtilities/CertificateException.cs => PAMUtilities.cs} (54%) create mode 100644 IISU/PSHelper.cs delete mode 100644 IISU/PowerShellUtilities/CertificateStore.cs delete mode 100644 IISU/PowerShellUtilities/PSCommandHelper.cs create mode 100644 IISU/WinCertJobTypeBase.cs create mode 100644 WinCertTestConsole/Program.cs create mode 100644 WinCertTestConsole/WinCertTestConsole.csproj create mode 100644 images/IISCertStore.png create mode 100644 images/WinCertStore.png diff --git a/IISU/PowerShellUtilities/Certificate.cs b/IISU/Certificate.cs similarity index 91% rename from IISU/PowerShellUtilities/Certificate.cs rename to IISU/Certificate.cs index 905849a..88f3612 100644 --- a/IISU/PowerShellUtilities/Certificate.cs +++ b/IISU/Certificate.cs @@ -14,7 +14,7 @@ using System; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { public class Certificate { diff --git a/IISU/CertificateStore.cs b/IISU/CertificateStore.cs new file mode 100644 index 0000000..25c284a --- /dev/null +++ b/IISU/CertificateStore.cs @@ -0,0 +1,226 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + public class CertificateStore + { + public CertificateStore(string serverName, string storePath, Runspace runSpace) + { + ServerName = serverName; + StorePath = storePath; + RunSpace = runSpace; + Initalize(); + } + + public string ServerName { get; set; } + public string StorePath { get; set; } + public Runspace RunSpace { get; set; } + public List Certificates { get; set; } + + public void RemoveCertificate(string thumbprint) + { + using var ps = PowerShell.Create(); + ps.Runspace = RunSpace; + var removeScript = $@" + $ErrorActionPreference = 'Stop' + $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') + $certStore.Open('MaxAllowed') + $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) + if($certToRemove.Count -gt 0) {{ + $certStore.Remove($certToRemove[0]) + }} + $certStore.Close() + $certStore.Dispose() + "; + + ps.AddScript(removeScript); + + var _ = ps.Invoke(); + if (ps.HadErrors) + throw new CertificateStoreException($"Error removing certificate in {StorePath} store on {ServerName}."); + } + + private void Initalize() + { + Certificates = new List(); + try + { + using var ps = PowerShell.Create(); + ps.Runspace = RunSpace; + + var certStoreScript = $@" + $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') + $certStore.Open('ReadOnly') + $certs = $certStore.Certificates + $certStore.Close() + $certStore.Dispose() + foreach ( $cert in $certs){{ + $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey + }}"; + + ps.AddScript(certStoreScript); + + var certs = ps.Invoke(); + + foreach (var c in certs) + Certificates.Add(new Certificate + { + Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", + HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), + RawData = (byte[])c.Properties["RawData"]?.Value + }); + } + catch (Exception ex) + { + throw new CertificateStoreException( + $"Error listing certificate in {StorePath} store on {ServerName}: {ex.Message}"); + } + } + + private static List PerformGetCertificateInvenotory(Runspace runSpace, string storePath) + { + List myCertificates = new List(); + try + { + using var ps = PowerShell.Create(); + ps.Runspace = runSpace; + + var certStoreScript = $@" + $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') + $certStore.Open('ReadOnly') + $certs = $certStore.Certificates + $certStore.Close() + $certStore.Dispose() + foreach ( $cert in $certs){{ + $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey + }}"; + + ps.AddScript(certStoreScript); + + var certs = ps.Invoke(); + + foreach (var c in certs) + myCertificates.Add(new Certificate + { + Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", + HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), + RawData = (byte[])c.Properties["RawData"]?.Value + }); + + return myCertificates; + } + catch (Exception ex) + { + throw new CertificateStoreException( + $"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}"); + } + + } + + public static List GetCertificatesFromStore(Runspace runSpace, string storePath) + { + return PerformGetCertificateInvenotory(runSpace, storePath); + } + + public static List GetIISBoundCertificates(Runspace runSpace, string storePath) + { + List myCertificates = PerformGetCertificateInvenotory(runSpace, storePath); + List myBoundCerts = new List(); + + using (var ps = PowerShell.Create()) + { + ps.Runspace = runSpace; + + ps.AddCommand("Import-Module") + .AddParameter("Name", "WebAdministration") + .AddStatement(); + + var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; + ps.AddScript(searchScript).AddStatement(); + var iisBindings = ps.Invoke(); // Responsible for getting all bound certificates for each website + + if (ps.HadErrors) + { + var psError = ps.Streams.Error.ReadAll().Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); + } + + if (iisBindings.Count == 0) + { + return myBoundCerts; + } + + //in theory should only be one, but keeping for future update to chance inventory + foreach (var binding in iisBindings) + { + var thumbPrint = $"{binding.Properties["thumbprint"]?.Value}"; + if (string.IsNullOrEmpty(thumbPrint)) continue; + + Certificate foundCert = myCertificates.Find(m => m.Thumbprint.Equals(thumbPrint)); + + if (foundCert == null) continue; + + var sniValue = ""; + switch (Convert.ToInt16(binding.Properties["sniFlg"]?.Value)) + { + case 0: + sniValue = "0 - No SNI"; + break; + case 1: + sniValue = "1 - SNI Enabled"; + break; + case 2: + sniValue = "2 - Non SNI Binding"; + break; + case 3: + sniValue = "3 - SNI Binding"; + break; + } + + var siteSettingsDict = new Dictionary + { + { "SiteName", binding.Properties["Name"]?.Value }, + { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, + { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, + { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, + { "SniFlag", sniValue }, + { "Protocol", binding.Properties["Protocol"]?.Value } + }; + + myBoundCerts.Add( + new CurrentInventoryItem + { + Certificates = new[] { foundCert.CertificateData }, + Alias = thumbPrint, + PrivateKeyEntry = foundCert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = siteSettingsDict + } + ); + } + + return myBoundCerts; + } + } + } +} \ No newline at end of file diff --git a/IISU/PowerShellUtilities/CertificateStoreException.cs b/IISU/CertificateStoreException.cs similarity index 93% rename from IISU/PowerShellUtilities/CertificateStoreException.cs rename to IISU/CertificateStoreException.cs index 4d540a0..b510548 100644 --- a/IISU/PowerShellUtilities/CertificateStoreException.cs +++ b/IISU/CertificateStoreException.cs @@ -15,7 +15,7 @@ using System; using System.Runtime.Serialization; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { [Serializable] internal class CertificateStoreException : Exception diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs new file mode 100644 index 0000000..25875ab --- /dev/null +++ b/IISU/ClientPSCertStoreInventory.cs @@ -0,0 +1,74 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + abstract class ClientPSCertStoreInventory + { + private ILogger _logger; + public ClientPSCertStoreInventory(ILogger logger) + { + _logger = logger; + } + + public List GetCertificatesFromStore(Runspace runSpace, string storePath) + { + List myCertificates = new List(); + try + { + using var ps = PowerShell.Create(); + + _logger.MethodEntry(); + + ps.Runspace = runSpace; + + var certStoreScript = $@" + $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') + $certStore.Open('ReadOnly') + $certs = $certStore.Certificates + $certStore.Close() + $certStore.Dispose() + foreach ( $cert in $certs){{ + $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey + }}"; + + ps.AddScript(certStoreScript); + + var certs = ps.Invoke(); + + foreach (var c in certs) + myCertificates.Add(new Certificate + { + Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", + HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), + RawData = (byte[])c.Properties["RawData"]?.Value + }); + + return myCertificates; + } + catch (Exception ex) + { + throw new CertificateStoreException( + $"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}"); + } + } + } +} diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs new file mode 100644 index 0000000..fdff0b6 --- /dev/null +++ b/IISU/ClientPSCertStoreManager.cs @@ -0,0 +1,168 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + internal class ClientPSCertStoreManager + { + private ILogger _logger; + private Runspace _runspace; + private long _jobNumber = 0; + + private X509Certificate2 x509Cert; + + public X509Certificate2 X509Cert + { + get { return x509Cert; } + } + + + public ClientPSCertStoreManager(ILogger logger, Runspace runSpace, long jobNumber) + { + _logger = logger; + _runspace = runSpace; + _jobNumber = jobNumber; + } + + public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath) + { + try + { + using var ps = PowerShell.Create(); + + _logger.MethodEntry(); + + ps.Runspace = _runspace; + + _logger.LogTrace($"Creating X509 Cert from: {certificateContents}"); + x509Cert = new X509Certificate2 + ( + Convert.FromBase64String(certificateContents), + privateKeyPassword, + X509KeyStorageFlags.MachineKeySet | + X509KeyStorageFlags.PersistKeySet | + X509KeyStorageFlags.Exportable + ); + + _logger.LogTrace($"X509 Cert Created With Subject: {x509Cert.SubjectName}"); + _logger.LogTrace( + $"Begin Add for Cert Store {$@"\\{_runspace.ConnectionInfo.ComputerName}\{storePath}"}"); + + // Add Certificate + var funcScript = @" + $ErrorActionPreference = ""Stop"" + + function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$storeName) { + $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $storeName, ""LocalMachine"" + $certStore.Open(5) + $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $bytes, $password, 18 <# Persist, Machine #> + $certStore.Add($cert) + $certStore.Close(); + }"; + + ps.AddScript(funcScript).AddStatement(); + _logger.LogTrace("InstallPfxToMachineStore Statement Added..."); + + ps.AddCommand("InstallPfxToMachineStore") + .AddParameter("bytes", Convert.FromBase64String(certificateContents)) + .AddParameter("password", privateKeyPassword) + .AddParameter("storeName", $@"\\{_runspace.ConnectionInfo.ComputerName}\{storePath}"); + _logger.LogTrace("InstallPfxToMachineStore Command Added..."); + + foreach (var cmd in ps.Commands.Commands) + { + _logger.LogTrace("Logging PowerShell Command"); + _logger.LogTrace(cmd.CommandText); + } + + _logger.LogTrace("Invoking ps..."); + ps.Invoke(); + _logger.LogTrace("ps Invoked..."); + if (ps.HadErrors) + { + _logger.LogTrace("ps Has Errors"); + var psError = ps.Streams.Error.ReadAll() + .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobNumber, + FailureMessage = + $"Site {storePath} on server {_runspace.ConnectionInfo.ComputerName}: {psError}" + }; + } + } + + _logger.LogTrace("Clearing Commands..."); + ps.Commands.Clear(); + _logger.LogTrace("Commands Cleared.."); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobNumber, + FailureMessage = "" + }; + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobNumber, + FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" + }; + } + } + + public void RemoveCertificate(string thumbprint, string storePath) + { + using var ps = PowerShell.Create(); + + _logger.MethodEntry(); + + ps.Runspace = _runspace; + + var removeScript = $@" + $ErrorActionPreference = 'Stop' + $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') + $certStore.Open('MaxAllowed') + $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) + if($certToRemove.Count -gt 0) {{ + $certStore.Remove($certToRemove[0]) + }} + $certStore.Close() + $certStore.Dispose() + "; + + ps.AddScript(removeScript); + + var _ = ps.Invoke(); + if (ps.HadErrors) + throw new CertificateStoreException($"Error removing certificate in {storePath} store on {_runspace.ConnectionInfo.ComputerName}."); + + } + } +} diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs new file mode 100644 index 0000000..cd9b575 --- /dev/null +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -0,0 +1,274 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS; +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation.Runspaces; +using System.Management.Automation; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Microsoft.Extensions.Logging; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using System.Linq; +using System.IO; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + internal class ClientPSCertStoreReEnrollment + { + private ILogger _logger; + private IPAMSecretResolver _resolver; + + public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver) + { + _logger = logger; + _resolver = resolver; + } + + public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, bool bindCertificate) + { + bool hasError = false; + + try + { + _logger.MethodEntry(); + var serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + var serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + + + // Extract values necessary to create remote PS connection + JobProperties properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, + new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + + WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri($"{properties?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{properties?.WinRmPort}/wsman")); + connectionInfo.IncludePortInSPN = properties.SpnPortFlag; + var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; + _logger.LogTrace($"Credentials: UserName:{serverUserName}"); + + connectionInfo.Credential = new PSCredential(serverUserName, pw); + _logger.LogTrace($"PSCredential Created {pw}"); + + // Establish new remote ps session + _logger.LogTrace("Creating remote PS Workspace"); + using var runSpace = RunspaceFactory.CreateRunspace(connectionInfo); + _logger.LogTrace("Workspace created"); + runSpace.Open(); + _logger.LogTrace("Workspace opened"); + + // NEW + var ps = PowerShell.Create(); + ps.Runspace = runSpace; + + string CSR = string.Empty; + + string storePath = config.CertificateStoreDetails.StorePath; + + var subjectText = config.JobProperties["subjectText"]; + var providerName = config.JobProperties["ProviderName"]; + var keyType = config.JobProperties["keyType"]; + var keySize = config.JobProperties["keySize"]; + var SAN = config.JobProperties["SAN"]; + + Collection results; + + // If the provider name is null, default it to the Microsoft CA + if (providerName == null) providerName = "Microsoft Strong Cryptographic Provider"; + + // Create the script file + ps.AddScript("$infFilename = New-TemporaryFile"); + ps.AddScript("$csrFilename = New-TemporaryFile"); + + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); + + ps.AddScript($"Set-Content $infFilename [NewRequest]"); + ps.AddScript($"Add-Content $infFilename 'Subject = \"{subjectText}\"'"); + ps.AddScript($"Add-Content $infFilename 'ProviderName = \"{providerName}\"'"); + ps.AddScript($"Add-Content $infFilename 'MachineKeySet = True'"); + ps.AddScript($"Add-Content $infFilename 'HashAlgorithm = SHA256'"); + ps.AddScript($"Add-Content $infFilename 'KeyAlgorithm = {keyType}'"); + ps.AddScript($"Add-Content $infFilename 'KeyLength={keySize}'"); + ps.AddScript($"Add-Content $infFilename 'KeySpec = 0'"); + + if (SAN != null) + { + ps.AddScript($"Add-Content $infFilename '[Extensions]'"); + ps.AddScript(@"Add-Content $infFilename '2.5.29.17 = ""{text}""'"); + + foreach (string s in SAN.ToString().Split("&")) + { + ps.AddScript($"Add-Content $infFilename '_continue_ = \"{s + "&"}\"'"); + } + } + + try + { + // Get INF file for debugging + ps.AddScript("$name = $infFilename.FullName"); + ps.AddScript("$name"); + results = ps.Invoke(); + + string fname = results[0].ToString(); + string infContent = File.ReadAllText(fname); + + _logger.LogDebug($"Contents of {fname}:"); + _logger.LogDebug(infContent); + } + catch (Exception) + { + } + + // Execute the -new command + ps.AddScript($"certreq -new -q $infFilename $csrFilename"); + _logger.LogDebug($"Subject Text: {subjectText}"); + _logger.LogDebug($"SAN: {SAN}"); + _logger.LogDebug($"Provider Name: {providerName}"); + _logger.LogDebug($"Key Type: {keyType}"); + _logger.LogDebug($"Key Size: {keySize}"); + _logger.LogTrace("Attempting to create the CSR by Invoking the script."); + + results = ps.Invoke(); + _logger.LogTrace("Completed the attempt in creating the CSR."); + + ps.Commands.Clear(); + + try + { + ps.AddScript($"$CSR = Get-Content $csrFilename"); + _logger.LogTrace("Attempting to get the contents of the CSR file."); + results = ps.Invoke(); + _logger.LogTrace("Finished getting the CSR Contents."); + } + catch (Exception) + { + var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + + hasError = true; + + throw new CertificateStoreException($"Error creating CSR File. {psError}"); + } + finally + { + ps.Commands.Clear(); + + // Delete the temp files + ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); + _logger.LogTrace("Attempt to delete the temporary files."); + results = ps.Invoke(); + + if (hasError) runSpace.Close(); + } + + // Get the byte array + var CSRContent = ps.Runspace.SessionStateProxy.GetVariable("CSR").ToString(); + + // Sign CSR in Keyfactor + _logger.LogTrace("Get the signed CSR from KF."); + X509Certificate2 myCert = submitReenrollment.Invoke(CSRContent); + + if (myCert != null) + { + // Get the cert data into string format + string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); + + _logger.LogTrace("Creating the text version of the certificate."); + + // Write out the cert file + StringBuilder sb = new StringBuilder(); + sb.AppendLine("-----BEGIN CERTIFICATE-----"); + sb.AppendLine(csrData); + sb.AppendLine("-----END CERTIFICATE-----"); + + ps.AddScript("$cerFilename = New-TemporaryFile"); + ps.AddScript($"Set-Content $cerFilename '{sb}'"); + + results = ps.Invoke(); + ps.Commands.Clear(); + + // Accept the signed cert + _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); + + ps.AddScript($"Set-Location -Path Cert:\\localmachine\\{config.CertificateStoreDetails.StorePath}"); + ps.AddScript($"Import-Certificate -Filepath $cerFilename"); + ps.Invoke(); + _logger.LogTrace("Successfully bound the certificate."); + + ps.Commands.Clear(); + + // Delete the temp files + ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); + ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); + _logger.LogTrace("Removing temporary files."); + results = ps.Invoke(); + + ps.Commands.Clear(); + runSpace.Close(); + + JobResult result; + + if (bindCertificate) + { + // Bind the certificate to IIS + ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUserName, serverPassword); + result = iisManager.BindCertificate(myCert); + }else + { + result = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + } + + ps.Commands.Clear(); + runSpace.Close(); + + return result; + } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." + }; + } + + } + catch (Exception ex) + { + var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = failureMessage + }; + } + } + } +} diff --git a/IISU/ClientPSIIManager.cs b/IISU/ClientPSIIManager.cs new file mode 100644 index 0000000..2e08b32 --- /dev/null +++ b/IISU/ClientPSIIManager.cs @@ -0,0 +1,428 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + internal class ClientPSIIManager + { + private string SiteName { get; set; } + private string Port { get; set; } + private string Protocol { get; set; } + private string HostName { get; set; } + private string SniFlag { get; set; } + private string IPAddress { get; set; } + + private string RenewalThumbprint { get; set; } = ""; + + private string CertContents { get; set; } = ""; + + private string PrivateKeyPassword { get; set; } = ""; + + private string ClientMachineName { get; set; } + private string StorePath { get; set; } + + private long JobHistoryID { get; set; } + + private ILogger _logger; + private Runspace _runSpace; + + private PowerShell ps; + + //public ClientPSIIManager(ILogger logger, Runspace runSpace) + //{ + // _logger = logger; + // _runSpace = runSpace; + + // ps = PowerShell.Create(); + // ps.Runspace = _runSpace; + //} + + //public ClientPSIIManager(ILogger logger, Runspace runSpace, PowerShell powerShell) + //{ + // _logger = logger; + // _runSpace = runSpace; + + // ps = powerShell; + //} + + public ClientPSIIManager(ReenrollmentJobConfiguration config, string serverUsername, string serverPassword) + { + _logger = LogHandler.GetClassLogger(); + + try + { + SiteName = config.JobProperties["SiteName"].ToString(); + Port = config.JobProperties["Port"].ToString(); + HostName = config.JobProperties["HostName"]?.ToString(); + Protocol = config.JobProperties["Protocol"].ToString(); + SniFlag = config.JobProperties["SniFlag"].ToString()?.Substring(0, 1); + IPAddress = config.JobProperties["IPAddress"].ToString(); + + PrivateKeyPassword = ""; // A reenrollment does not have a PFX Password + RenewalThumbprint = ""; // A reenrollment will always be empty + CertContents = ""; // Not needed for a reenrollment + + ClientMachineName = config.CertificateStoreDetails.ClientMachine; + StorePath = config.CertificateStoreDetails.StorePath; + + JobHistoryID = config.JobHistoryId; + + // Establish PowerShell Runspace + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string winRmProtocol = jobProperties.WinRmProtocol; + string winRmPort = jobProperties.WinRmPort; + bool includePortInSPN = jobProperties.SpnPortFlag; + + _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); + _runSpace = PSHelper.GetClientPSRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + } + catch (Exception e) + { + throw new Exception($"Error when initiating an IIS ReEnrollment Job: {e.Message}", e.InnerException); + } + } + + public ClientPSIIManager(ManagementJobConfiguration config, string serverUsername, string serverPassword) + { + _logger = LogHandler.GetClassLogger(); + + try + { + SiteName = config.JobProperties["SiteName"].ToString(); + Port = config.JobProperties["Port"].ToString(); + HostName = config.JobProperties["HostName"]?.ToString(); + Protocol = config.JobProperties["Protocol"].ToString(); + SniFlag = config.JobProperties["SniFlag"].ToString()?.Substring(0, 1); + IPAddress = config.JobProperties["IPAddress"].ToString(); + + PrivateKeyPassword = ""; // A reenrollment does not have a PFX Password + RenewalThumbprint = ""; // A reenrollment will always be empty + CertContents = ""; // Not needed for a reenrollment + + ClientMachineName = config.CertificateStoreDetails.ClientMachine; + StorePath = config.CertificateStoreDetails.StorePath; + + JobHistoryID = config.JobHistoryId; + + if (config.JobProperties.ContainsKey("RenewalThumbprint")) + { + RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); + _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); + } + + // Establish PowerShell Runspace + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string winRmProtocol = jobProperties.WinRmProtocol; + string winRmPort = jobProperties.WinRmPort; + bool includePortInSPN = jobProperties.SpnPortFlag; + + _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); + _runSpace = PSHelper.GetClientPSRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + } + catch (Exception e) + { + throw new Exception($"Error when initiating an IIS ReEnrollment Job: {e.Message}", e.InnerException); + } + } + + public JobResult BindCertificate(X509Certificate2 x509Cert) + { + try + { + _logger.MethodEntry(); + + _runSpace.Open(); + ps = PowerShell.Create(); + ps.Runspace = _runSpace; + + //if thumbprint is there it is a renewal so we have to search all the sites for that thumbprint and renew them all + if (RenewalThumbprint?.Length > 0) + { + _logger.LogTrace($"Thumbprint Length > 0 {RenewalThumbprint}"); + ps.AddCommand("Import-Module") + .AddParameter("Name", "WebAdministration") + .AddStatement(); + + _logger.LogTrace("WebAdministration Imported"); + var searchScript = + "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; + ps.AddScript(searchScript).AddStatement(); + _logger.LogTrace($"Search Script: {searchScript}"); + var bindings = ps.Invoke(); + foreach (var binding in bindings) + { + if (binding.Properties["Protocol"].Value.ToString().Contains("http")) + { + _logger.LogTrace("Looping Bindings...."); + var bindingSiteName = binding.Properties["name"].Value.ToString(); + var bindingIpAddress = binding.Properties["Bindings"].Value.ToString()?.Split(':')[0]; + var bindingPort = binding.Properties["Bindings"].Value.ToString()?.Split(':')[1]; + var bindingHostName = binding.Properties["Bindings"].Value.ToString()?.Split(':')[2]; + var bindingProtocol = binding.Properties["Protocol"].Value.ToString(); + var bindingThumbprint = binding.Properties["thumbprint"].Value.ToString(); + var bindingSniFlg = binding.Properties["sniFlg"].Value.ToString(); + + _logger.LogTrace( + $"bindingSiteName: {bindingSiteName}, bindingIpAddress: {bindingIpAddress}, bindingPort: {bindingPort}, bindingHostName: {bindingHostName}, bindingProtocol: {bindingProtocol}, bindingThumbprint: {bindingThumbprint}, bindingSniFlg: {bindingSniFlg}"); + + //if the thumbprint of the renewal request matches the thumbprint of the cert in IIS, then renew it + if (RenewalThumbprint == bindingThumbprint) + { + _logger.LogTrace($"Thumbprint Match {RenewalThumbprint}={bindingThumbprint}"); + var funcScript = string.Format(@" + $ErrorActionPreference = ""Stop"" + + $IISInstalled = Get-Module -ListAvailable | where {{$_.Name -eq ""WebAdministration""}} + if($IISInstalled) {{ + Import-Module WebAdministration + Get-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" | + ForEach-Object {{ Remove-WebBinding -BindingInformation $_.bindingInformation }} + + New-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" -SslFlags ""{7}"" + Get-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" | + ForEach-Object {{ $_.AddSslCertificate(""{5}"", ""{6}"") }} + }}", bindingSiteName, //{0} + bindingIpAddress, //{1} + bindingPort, //{2} + bindingProtocol, //{3} + bindingHostName, //{4} + x509Cert.Thumbprint, //{5} + StorePath, //{6} + bindingSniFlg); //{7} + + _logger.LogTrace($"funcScript {funcScript}"); + ps.AddScript(funcScript); + _logger.LogTrace("funcScript added..."); + ps.Invoke(); + _logger.LogTrace("funcScript Invoked..."); + foreach (var cmd in ps.Commands.Commands) + { + _logger.LogTrace("Logging PowerShell Command"); + _logger.LogTrace(cmd.CommandText); + } + + ps.Commands.Clear(); + _logger.LogTrace("Commands Cleared.."); + } + } + } + } + else + { + var funcScript = string.Format(@" + $ErrorActionPreference = ""Stop"" + + $IISInstalled = Get-Module -ListAvailable | where {{$_.Name -eq ""WebAdministration""}} + if($IISInstalled) {{ + Import-Module WebAdministration + Get-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -Port ""{2}"" -Protocol ""{3}"" -HostHeader ""{4}"" | + ForEach-Object {{ Remove-WebBinding -BindingInformation $_.bindingInformation }} + + New-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" -SslFlags ""{7}"" + Get-WebBinding -Name ""{0}"" -IPAddress ""{1}"" -HostHeader ""{4}"" -Port ""{2}"" -Protocol ""{3}"" | + ForEach-Object {{ $_.AddSslCertificate(""{5}"", ""{6}"") }} + }}", SiteName, //{0} + IPAddress, //{1} + Port, //{2} + Protocol, //{3} + HostName, //{4} + x509Cert.Thumbprint, //{5} + StorePath, //{6} + Convert.ToInt16(SniFlag)); //{7} + foreach (var cmd in ps.Commands.Commands) + { + _logger.LogTrace("Logging PowerShell Command"); + _logger.LogTrace(cmd.CommandText); + } + + _logger.LogTrace($"funcScript {funcScript}"); + ps.AddScript(funcScript); + _logger.LogTrace("funcScript added..."); + ps.Invoke(); + _logger.LogTrace("funcScript Invoked..."); + } + + if (ps.HadErrors) + { + var psError = ps.Streams.Error.ReadAll() + .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryID, + FailureMessage = + $"Site {StorePath} on server {_runSpace.ConnectionInfo.ComputerName}: {psError}" + }; + } + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = JobHistoryID, + FailureMessage = "" + }; + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryID, + FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" + }; + } + finally + { + _runSpace.Close(); + ps.Runspace.Close(); + ps.Dispose(); + } + } + + public JobResult UnBindCertificate() + { + try + { + _logger.MethodEntry(); + + _runSpace.Open(); + ps = PowerShell.Create(); + ps.Runspace = _runSpace; + + ps.AddCommand("Import-Module") + .AddParameter("Name", "WebAdministration") + .AddStatement(); + + _logger.LogTrace("WebAdministration Imported"); + + ps.AddCommand("Get-WebBinding") + .AddParameter("Protocol", Protocol) + .AddParameter("Name", SiteName) + .AddParameter("Port", Port) + .AddParameter("HostHeader", HostName) + .AddParameter("IPAddress", IPAddress) + .AddStatement(); + + _logger.LogTrace("Get-WebBinding Set"); + var foundBindings = ps.Invoke(); + _logger.LogTrace("foundBindings Invoked"); + + if (foundBindings.Count == 0) + { + _logger.LogTrace($"{foundBindings.Count} Bindings Found..."); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryID, + FailureMessage = + $"Site {Protocol} binding for Site {SiteName} on server {_runSpace.ConnectionInfo.ComputerName} not found." + }; + } + + //Log Commands out for debugging purposes + foreach (var cmd in ps.Commands.Commands) + { + _logger.LogTrace("Logging PowerShell Command"); + _logger.LogTrace(cmd.CommandText); + } + + ps.Commands.Clear(); + _logger.LogTrace("Cleared Commands"); + + ps.AddCommand("Import-Module") + .AddParameter("Name", "WebAdministration") + .AddStatement(); + + _logger.LogTrace("Imported WebAdministration Module"); + + foreach (var binding in foundBindings) + { + ps.AddCommand("Remove-WebBinding") + .AddParameter("Name", SiteName) + .AddParameter("BindingInformation", + $"{binding.Properties["bindingInformation"]?.Value}") + .AddStatement(); + + //Log Commands out for debugging purposes + foreach (var cmd in ps.Commands.Commands) + { + _logger.LogTrace("Logging PowerShell Command"); + _logger.LogTrace(cmd.CommandText); + } + + var _ = ps.Invoke(); + _logger.LogTrace("Invoked Remove-WebBinding"); + + if (ps.HadErrors) + { + _logger.LogTrace("PowerShell Had Errors"); + var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryID, + FailureMessage = + $"Failed to remove {Protocol} binding for Site {SiteName} on server {_runSpace.ConnectionInfo.ComputerName} not found, error {psError}" + }; + } + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = JobHistoryID, + FailureMessage = "" + }; + + } + catch (Exception ex) + { + var failureMessage = $"Unbinging for Site '{StorePath}' on server '{_runSpace.ConnectionInfo.ComputerName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = JobHistoryID, + FailureMessage = failureMessage + }; + } + finally + { + _runSpace.Close(); + ps.Runspace.Close(); + ps.Dispose(); + } + } + } +} + diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index dd0df9c..d277ba7 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -18,7 +18,6 @@ using System.Management.Automation.Runspaces; using System.Net; using System.Security.Cryptography.X509Certificates; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -28,128 +27,82 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win { - public class Inventory : IInventoryJobExtension + public class Inventory : WinCertJobTypeBase, IInventoryJobExtension { - public string ExtensionName => "Win"; - private ILogger _logger; - private IPAMSecretResolver _resolver; + public string ExtensionName => string.Empty; - private string ServerUserName { get; set; } - private string ServerPassword { get; set; } + public Inventory() + { + } public Inventory(IPAMSecretResolver resolver) { _resolver = resolver; - - _logger = LogHandler.GetClassLogger(); - _logger.MethodEntry(); } public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) { - return PerformInventory(jobConfiguration, submitInventoryUpdate); - } + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); - public string ResolvePamField(string name, string value) - { - _logger.LogTrace($"Attempting to resolve PAM eligible field {name}"); - return _resolver.Resolve(value); + return PerformInventory(jobConfiguration, submitInventoryUpdate); } private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) { try { - ServerUserName = ResolvePamField("Server UserName", config.ServerUsername); - ServerPassword = ResolvePamField("Server Password", config.ServerPassword); + var inventoryItems = new List(); _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); - var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - var inventoryItems = new List(); + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - // Setup a new connection to the client machine - var connectionInfo = new WSManConnectionInfo(new Uri($"{storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman")); - _logger.LogTrace($"WinRm URL: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); + // Deserialize specific job properties + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string protocol = jobProperties.WinRmProtocol; + string port = jobProperties.WinRmPort; + bool IncludePortInSPN = jobProperties.SpnPortFlag; + string clientMachineName = config.CertificateStoreDetails.ClientMachine; + string storePath = config.CertificateStoreDetails.StorePath; if (storePath != null) { - // Set credentials object - var pw = new NetworkCredential(ServerUserName, ServerPassword).SecurePassword; + _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); + using var myRunspace = PSHelper.GetClientPSRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + myRunspace.Open(); + + _logger.LogTrace("Runspace is now open"); + _logger.LogTrace($"Attempting to read certificates from cert store: {storePath}"); - connectionInfo.Credential = new PSCredential(ServerUserName, pw); + //foreach (Certificate cert in PowerShellUtilities.CertificateStore.GetCertificatesFromStore(myRunspace, storePath)) + WinInventory winInv = new WinInventory(_logger); + inventoryItems = winInv.GetInventoryItems(myRunspace, storePath); - // Create the PowerShell Runspace - using var runSpace = RunspaceFactory.CreateRunspace(connectionInfo); - _logger.LogTrace("runSpace Created"); - runSpace.Open(); - _logger.LogTrace("runSpace Opened"); + _logger.LogTrace($"A total of {inventoryItems.Count} were found"); + _logger.LogTrace("Closing runspace"); + myRunspace.Close(); - using (var ps = PowerShell.Create()) + _logger.LogTrace("Invoking Inventory..."); + submitInventory.Invoke(inventoryItems); + _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); + + return new JobResult { - ps.Runspace = runSpace; - _logger.LogTrace("RunSpace Created"); - - try - { - // Call PowerShell Command to get child items (certs) - _logger.LogTrace($"Attempting to get licenses from cert path: {config.CertificateStoreDetails.StorePath})"); - List myCerts = - PSCommandHelper.GetChildItem(ps, config.CertificateStoreDetails.StorePath); - - if(myCerts.Count > 0) - { - _logger.LogTrace($"Found {myCerts.Count} certificates in path: {config.CertificateStoreDetails.StorePath}"); - foreach (X509Certificate2 thisCert in myCerts) - { - CurrentInventoryItem inventoryItem = new CurrentInventoryItem() - { - Certificates = new[] { thisCert.GetRawCertDataString().ToString() }, - Alias = thisCert.Thumbprint, - PrivateKeyEntry = thisCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = null - }; - - inventoryItems.Add(inventoryItem); - } - - // Get Certificate info and add to list of inventory items to pass back to KF - _logger.LogTrace("Invoking Inventory..."); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Warning, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"No certificates were found in the Certificate Store Path: {config.CertificateStoreDetails.StorePath} on server: {config.CertificateStoreDetails.ClientMachine}" - }; - } - } - catch (CertificateStoreException certEx) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: failed with Error: {certEx.Message}" - }; - } - } + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; } return new JobResult { - Result = OrchestratorJobStatusJobResult.Success, + Result = OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, - FailureMessage = "" + FailureMessage = + $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" }; } catch (CertificateStoreException psEx) diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index 78655d6..fe5d96a 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -1,24 +1,234 @@ -using Keyfactor.Orchestrators.Extensions; +// Copyright 2023 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License.using Keyfactor.Logging; + +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Text; +using System.Management.Automation.Runspaces; +using System.Management.Automation; +using System.Net; +using Keyfactor.Logging; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win { - internal class Management : IManagementJobExtension + public class Management : WinCertJobTypeBase, IManagementJobExtension { - public string ExtensionName => throw new NotImplementedException(); + private ILogger _logger; + + public string ExtensionName => string.Empty; + + private Runspace myRunspace; + + private string _thumbprint = string.Empty; public Management() { + } + + public Management(IPAMSecretResolver resolver) + { + _resolver= resolver; + } + + public JobResult ProcessJob(ManagementJobConfiguration config) + { + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + + try + { + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string protocol = jobProperties.WinRmProtocol; + string port = jobProperties.WinRmPort; + bool IncludePortInSPN = jobProperties.SpnPortFlag; + string clientMachineName = config.CertificateStoreDetails.ClientMachine; + string storePath = config.CertificateStoreDetails.StorePath; + long JobHistoryID = config.JobHistoryId; + + _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); + myRunspace = PSHelper.GetClientPSRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + + var complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = + "Invalid Management Operation" + }; + + switch (config.OperationType) + { + case CertStoreOperationType.Add: + { + myRunspace.Open(); + _logger.LogTrace("runSpace Opened"); + + complete = performAddition(config); + + myRunspace.Close(); + _logger.LogTrace($"RunSpace was closed..."); + + break; + } + case CertStoreOperationType.Remove: + { + myRunspace.Open(); + _logger.LogTrace("runSpace Opened"); + + complete = performRemove(config); + + myRunspace.Close(); + _logger.LogTrace($"RunSpace was closed..."); + + break; + } + } + + return complete; + } + + catch (Exception e) + { + _logger.LogError($"Error Occurred in Management.PerformManagement: {e.Message}"); + throw; + } + } + + //private JobResult PerformManagement(ManagementJobConfiguration config) + //{ + // try + // { + // _logger.MethodEntry(); + + // ServerUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + // ServerPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + // var complete = new JobResult + // { + // Result = OrchestratorJobStatusJobResult.Failure, + // JobHistoryId = config.JobHistoryId, + // FailureMessage = + // "Invalid Management Operation" + // }; + + // switch (config.OperationType) + // { + // case CertStoreOperationType.Add: + // { + // _logger.LogTrace("Adding..."); + // if (config.JobProperties.ContainsKey("RenewalThumbprint")) + // { + // _thumbprint = config.JobProperties["RenewalThumbprint"].ToString(); + // _logger.LogTrace($"Found Thumbprint Will renew all cers with this Thumbprint: {_thumbprint}"); + // } + + // _logger.LogTrace("Before PerformAddition..."); + // complete = performAddition(config); + // _logger.LogTrace("After PerformAddition..."); + + // break; + // } + // case CertStoreOperationType.Remove: + // { + // break; + // } + // } + + // return complete; + // } + + // catch (Exception e) + // { + // _logger.LogError($"Error Occurred in Management.PerformManagement: {e.Message}"); + // throw; + // } + //} + + private JobResult performAddition(ManagementJobConfiguration config) + { + try + { + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string storePath = config.CertificateStoreDetails.StorePath; + long jobNumber = config.JobHistoryId; + + // Setup a new connection to the client machine + //var connectionInfo = new WSManConnectionInfo(new Uri($"{certStoreDetails?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{certStoreDetails?.WinRmPort}/wsman")); + //_logger.LogTrace($"WinRm URL: {certStoreDetails?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{certStoreDetails?.WinRmPort}/wsman"); + + if (storePath != null) + { + _logger.LogTrace($"Attempting to get licenses from cert path: {storePath})"); + + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + + _logger.LogTrace($"Certificate was successfully added to cert store: {storePath})"); + } + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"Management/Add {e.Message}" + }; + } } - public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) + private JobResult performRemove(ManagementJobConfiguration config) { - WinIIS.IISManager mgr = new WinIIS.IISManager(jobConfiguration, "", ""); + try + { + _logger.LogTrace($"Removing Certificate with Alias: {config.JobCertificate.Alias}"); + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, config.JobHistoryId); + manager.RemoveCertificate(config.JobCertificate.Alias, config.CertificateStoreDetails.StorePath); + _logger.LogTrace($"Removed Certificate with Alias: {config.JobCertificate.Alias}"); - throw new NotImplementedException(); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = $"Error Occurred while attempting to remove certificate: {LogHandler.FlattenException(e)}" + }; + } } } } diff --git a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs new file mode 100644 index 0000000..9152fd3 --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs @@ -0,0 +1,41 @@ +// Copyright 2023 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win +{ + public class ReEnrollment : WinCertJobTypeBase, IReenrollmentJobExtension + { + private ILogger _logger; + + public string ExtensionName => string.Empty; + + public ReEnrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollmentUpdate) + { + _logger = LogHandler.GetClassLogger(typeof(ReEnrollment)); + + ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); + return myReEnrollment.PerformReEnrollment(config, submitReenrollmentUpdate, false); + + } + } +} diff --git a/IISU/ImplementedStoreTypes/Win/WinInventory.cs b/IISU/ImplementedStoreTypes/Win/WinInventory.cs new file mode 100644 index 0000000..70ad891 --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/WinInventory.cs @@ -0,0 +1,50 @@ +// Copyright 2023 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Management.Automation.Runspaces; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win +{ + internal class WinInventory : ClientPSCertStoreInventory + { + public WinInventory(ILogger logger) : base(logger) + { + } + + public List GetInventoryItems(Runspace runSpace, string storePath) + { + List inventoryItems = new List(); + + foreach (Certificate cert in base.GetCertificatesFromStore(runSpace, storePath)) + { + inventoryItems.Add(new CurrentInventoryItem + { + Certificates = new[] { cert.CertificateData }, + Alias = cert.Thumbprint, + PrivateKeyEntry = cert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = null + }); + } + + return inventoryItems; + } + } +} diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs b/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs index 36d8b58..6a14735 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs @@ -145,10 +145,15 @@ public JobResult ReEnrollCertificate(X509Certificate2 certificate) try { // Instanciate a new Powershell instance - CreatePowerShellInstance(); - - return BindCertificate(); + //CreatePowerShellInstance(); + //return BindCertificate(); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = JobHistoryId, + FailureMessage = "" + }; } catch (Exception e) { @@ -176,7 +181,7 @@ public JobResult AddCertificate() $"Begin Add for Cert Store {$@"\\{ClientMachine}\{Path}"}"); // Instanciate a new Powershell instance - CreatePowerShellInstance(); + //CreatePowerShellInstance(); // Moved to different method // Add Certificate var funcScript = @" @@ -229,7 +234,14 @@ function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$st Logger.LogTrace("Commands Cleared.."); // Install the certifiacte - return BindCertificate(); + //return BindCertificate(); // Not used any longer; moved to separate method + // The JobResult is necessary because the line above returns the job result. + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = JobHistoryId, + FailureMessage = "" + }; } catch (Exception e) { @@ -258,7 +270,7 @@ private void CreatePowerShellInstance() Logger.LogTrace("RunSpace Opened"); Logger.LogTrace( $"Creating Cert Store with ClientMachine: {ClientMachine}, JobProperties: {Path}"); - var _ = new PowerShellUtilities.CertificateStore( + var _ = new CertificateStore( ClientMachine, Path, runSpace); Logger.LogTrace("Cert Store Created"); diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index c845f94..0919ccf 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -24,181 +24,80 @@ using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { - public class Inventory : IInventoryJobExtension + public class Inventory : WinCertJobTypeBase, IInventoryJobExtension { private ILogger _logger; - private IPAMSecretResolver _resolver; - - private string ServerUserName { get; set; } - private string ServerPassword { get; set; } + public string ExtensionName => string.Empty; public Inventory(IPAMSecretResolver resolver) { _resolver = resolver; } - private string ResolvePamField(string name, string value) + public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) { - _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); - return _resolver.Resolve(value); + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + + return PerformInventory(jobConfiguration, submitInventoryUpdate); } private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) { try { - _logger = LogHandler.GetClassLogger(); - _logger.MethodEntry(); - ServerUserName = ResolvePamField("Server UserName", config.ServerUsername); - ServerPassword = ResolvePamField("Server Password", config.ServerPassword); + var inventoryItems = new List(); _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); - var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - var inventoryItems = new List(); - _logger.LogTrace($"Begin Inventory for Cert Store {$@"\\{config.CertificateStoreDetails.ClientMachine}\{config.CertificateStoreDetails.StorePath}"}"); + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - var connInfo = new WSManConnectionInfo(new Uri($"{storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman")); - _logger.LogTrace($"WinRm Url: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); + // Deserialize specific job properties + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string protocol = jobProperties.WinRmProtocol; + string port = jobProperties.WinRmPort; + bool IncludePortInSPN = jobProperties.SpnPortFlag; + string clientMachineName = config.CertificateStoreDetails.ClientMachine; + string storePath = config.CertificateStoreDetails.StorePath; if (storePath != null) { - var pw = new NetworkCredential(ServerUserName, ServerPassword) - .SecurePassword; - _logger.LogTrace($"Credentials: UserName:{ServerUserName} Password:{ServerPassword}"); - connInfo.Credential = new PSCredential(ServerUserName, pw); - _logger.LogTrace($"PSCredential Created {pw}"); - - using var runSpace = RunspaceFactory.CreateRunspace(connInfo); - _logger.LogTrace("runSpace Created"); - runSpace.Open(); - _logger.LogTrace("runSpace Opened"); - - var psCertStore = new PowerShellUtilities.CertificateStore( - config.CertificateStoreDetails.ClientMachine, config.CertificateStoreDetails.StorePath, - runSpace); - _logger.LogTrace("psCertStore Created"); - - using (var ps = PowerShell.Create()) + _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); + using var myRunspace = PSHelper.GetClientPSRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + myRunspace.Open(); + + _logger.LogTrace("Runspace is now open"); + _logger.LogTrace($"Attempting to read bound IIS certificates from cert store: {storePath}"); + WinIISInventory IISInventory = new WinIISInventory(_logger); + inventoryItems = IISInventory.GetInventoryItems(myRunspace, storePath); + + _logger.LogTrace($"A total of {inventoryItems.Count} were found"); + _logger.LogTrace("Closing runspace..."); + myRunspace.Close(); + + _logger.LogTrace("Invoking Inventory.."); + submitInventory.Invoke(inventoryItems); + _logger.LogTrace($"Inventory Invoked... {inventoryItems.Count} Items"); + + return new JobResult { - ps.Runspace = runSpace; - _logger.LogTrace("RunSpace Created"); - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - _logger.LogTrace("WebAdministration Imported"); - - var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; - _logger.LogTrace($"searchScript {searchScript}"); - ps.AddScript(searchScript).AddStatement(); - _logger.LogTrace("searchScript added..."); - var iisBindings = ps.Invoke(); - _logger.LogTrace("iisBindings Created..."); - - if (ps.HadErrors) - { - _logger.LogTrace("ps Has Errors"); - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: failed with Error: {psError}" - }; - } - - if (iisBindings.Count == 0) - { - _logger.LogTrace("submitInventory About To Be Invoked No Bindings Found"); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace("submitInventory Invoked..."); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Warning, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Inventory on server {config.CertificateStoreDetails.ClientMachine} did not find any bindings." - }; - } - - //in theory should only be one, but keeping for future update to chance inventory - foreach (var binding in iisBindings) - { - _logger.LogTrace("Looping Bindings..."); - var thumbPrint = $"{(binding.Properties["thumbprint"]?.Value)}"; - _logger.LogTrace($"thumbPrint: {thumbPrint}"); - if (string.IsNullOrEmpty(thumbPrint)) - continue; - - var foundCert = psCertStore.Certificates.Find(m => m.Thumbprint.Equals(thumbPrint)); - _logger.LogTrace($"foundCert: {foundCert?.CertificateData}"); - - if (foundCert == null) - continue; - - var sniValue = ""; - switch (Convert.ToInt16(binding.Properties["sniFlg"]?.Value)) - { - case 0: - sniValue = "0 - No SNI"; - break; - case 1: - sniValue = "1 - SNI Enabled"; - break; - case 2: - sniValue = "2 - Non SNI Binding"; - break; - case 3: - sniValue = "3 - SNI Binding"; - break; - - } - - _logger.LogTrace($"bindingSiteName: {binding.Properties["Name"]?.Value}, bindingIpAddress: {binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0]}, bindingPort: {binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1]}, bindingHostName: {binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2]}, bindingProtocol: {binding.Properties["Protocol"]?.Value}, bindingSniFlg: {sniValue}"); - - var siteSettingsDict = new Dictionary - { - { "SiteName", binding.Properties["Name"]?.Value }, - { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, - { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, - { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, - { "SniFlag", sniValue }, - { "Protocol", binding.Properties["Protocol"]?.Value } - }; - - inventoryItems.Add( - new CurrentInventoryItem - { - Certificates = new[] { foundCert.CertificateData }, - Alias = thumbPrint, - PrivateKeyEntry = foundCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = siteSettingsDict - } - ); - } - } - _logger.LogTrace("closing runSpace..."); - runSpace.Close(); - _logger.LogTrace("runSpace closed..."); + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; } - _logger.LogTrace("Invoking Inventory.."); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked... {inventoryItems.Count} Items"); return new JobResult { - Result = OrchestratorJobStatusJobResult.Success, + Result = OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, - FailureMessage = "" + FailureMessage = + $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" }; } catch (CertificateStoreException psEx) @@ -227,11 +126,5 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven }; } } - - public string ExtensionName => "WinIIS"; - public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) - { - return PerformInventory(jobConfiguration, submitInventoryUpdate); - } } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 53fdca1..da0d60f 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -22,210 +22,87 @@ using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.Commands; using Newtonsoft.Json; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { - public class Management : IManagementJobExtension + public class Management : WinCertJobTypeBase, IManagementJobExtension { private ILogger _logger; - private IPAMSecretResolver _resolver; + public string ExtensionName => string.Empty; private string _thumbprint = string.Empty; - private string ServerUserName { get; set; } - private string ServerPassword { get; set; } + private Runspace myRunspace; public Management(IPAMSecretResolver resolver) { _resolver = resolver; } - public string ExtensionName => "IISU"; - - private string ResolvePamField(string name,string value) - { - _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); - return _resolver.Resolve(value); - } - - public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) - { - _logger = LogHandler.GetClassLogger(); - ServerUserName = ResolvePamField("Server UserName", jobConfiguration.ServerUsername); - ServerPassword = ResolvePamField("Server Password", jobConfiguration.ServerPassword); - _logger.MethodEntry(); - _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(jobConfiguration)}"); - var complete = new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - FailureMessage = - "Invalid Management Operation" - }; - - switch (jobConfiguration.OperationType) - { - case CertStoreOperationType.Add: - _logger.LogTrace("Entering Add..."); - if (jobConfiguration.JobProperties.ContainsKey("RenewalThumbprint")) - { - _thumbprint = jobConfiguration.JobProperties["RenewalThumbprint"].ToString(); - _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {_thumbprint}"); - } - _logger.LogTrace("Before PerformAddition..."); - complete = PerformAddition(jobConfiguration); - _logger.LogTrace("After PerformAddition..."); - break; - case CertStoreOperationType.Remove: - _logger.LogTrace("After PerformRemoval..."); - complete = PerformRemoval(jobConfiguration); - _logger.LogTrace("After PerformRemoval..."); - break; - } - _logger.MethodExit(); - return complete; - } - - private JobResult PerformRemoval(ManagementJobConfiguration config) + public JobResult ProcessJob(ManagementJobConfiguration config) { try { + _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); - var siteName = config.JobProperties["SiteName"]; - var port = config.JobProperties["Port"]; - var hostName = config.JobProperties["HostName"]; - var protocol = config.JobProperties["Protocol"]; - var ipAddress = config.JobProperties["IPAddress"].ToString(); - _logger.LogTrace($"Removing Site: {siteName}, Port:{port}, hostName:{hostName}, protocol:{protocol}"); - - var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings {DefaultValueHandling = DefaultValueHandling.Populate}); - _logger.LogTrace( - $"Begin Removal for Cert Store {$@"\\{config.CertificateStoreDetails.ClientMachine}\{config.CertificateStoreDetails.StorePath}"}"); - _logger.LogTrace($"WinRm Url: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); + _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); - var connInfo = - new WSManConnectionInfo( - new Uri($"{storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman")); - if (storePath != null) - { - _logger.LogTrace($"IncludePortInSPN: {storePath.SpnPortFlag}"); - connInfo.IncludePortInSPN = storePath.SpnPortFlag; - var pw = new NetworkCredential(ServerUserName, ServerPassword) - .SecurePassword; - _logger.LogTrace($"Credentials: UserName:{ServerUserName} Password:{ServerPassword}"); - connInfo.Credential = new PSCredential(ServerUserName, pw); - _logger.LogTrace($"PSCredential Created {pw}"); - using var runSpace = RunspaceFactory.CreateRunspace(connInfo); - _logger.LogTrace("runSpace Created"); - runSpace.Open(); - _logger.LogTrace("runSpace Opened"); - var psCertStore = new PowerShellUtilities.CertificateStore( - config.CertificateStoreDetails.ClientMachine, config.CertificateStoreDetails.StorePath, - runSpace); - _logger.LogTrace("psCertStore Created"); - using var ps = PowerShell.Create(); - _logger.LogTrace("ps Created"); - ps.Runspace = runSpace; - _logger.LogTrace("RunSpace Set"); - - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - - _logger.LogTrace("WebAdministration Imported"); + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - ps.AddCommand("Get-WebBinding") - .AddParameter("Protocol", protocol) - .AddParameter("Name", siteName) - .AddParameter("Port", port) - .AddParameter("HostHeader", hostName) - .AddParameter("IPAddress",ipAddress) - .AddStatement(); + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string protocol = jobProperties.WinRmProtocol; + string port = jobProperties.WinRmPort; + bool IncludePortInSPN = jobProperties.SpnPortFlag; + string clientMachineName = config.CertificateStoreDetails.ClientMachine; + string storePath = config.CertificateStoreDetails.StorePath; + long JobHistoryID = config.JobHistoryId; - - _logger.LogTrace("Get-WebBinding Set"); - var foundBindings = ps.Invoke(); - _logger.LogTrace("foundBindings Invoked"); + _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); + myRunspace = PSHelper.GetClientPSRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - if (foundBindings.Count == 0) - { - _logger.LogTrace($"{foundBindings.Count} Bindings Found..."); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Site {protocol} binding for Site {siteName} on server {config.CertificateStoreDetails.ClientMachine} not found." - }; - } - - //Log Commands out for debugging purposes - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - ps.Commands.Clear(); - _logger.LogTrace("Cleared Commands"); + var complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + FailureMessage = + "Invalid Management Operation" + }; - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); + switch (config.OperationType) + { + case CertStoreOperationType.Add: + _logger.LogTrace("Entering Add..."); - _logger.LogTrace("Imported WebAdministration Module"); + myRunspace.Open(); + complete = PerformAddCertificate(config, serverUserName, serverPassword); + myRunspace.Close(); - foreach (var binding in foundBindings) - { - ps.AddCommand("Remove-WebBinding") - .AddParameter("Name", siteName) - .AddParameter("BindingInformation", - $"{binding.Properties["bindingInformation"]?.Value}") - .AddStatement(); - - //Log Commands out for debugging purposes - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } + _logger.LogTrace("After Perform Addition..."); + break; + case CertStoreOperationType.Remove: + _logger.LogTrace("Entering Remove..."); - var _ = ps.Invoke(); - _logger.LogTrace("Invoked Remove-WebBinding"); + _logger.LogTrace("After PerformRemoval..."); + myRunspace.Open(); + complete = PerformRemoveCertificate(config, serverUserName, serverPassword); + myRunspace.Close(); - if (ps.HadErrors) - { - _logger.LogTrace("PowerShell Had Errors"); - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Failed to remove {protocol} binding for Site {siteName} on server {config.CertificateStoreDetails.ClientMachine} not found, error {psError}" - }; - } - } - _logger.LogTrace($"Removing Certificate with Alias: {config.JobCertificate.Alias}"); - psCertStore.RemoveCertificate(config.JobCertificate.Alias); - _logger.LogTrace($"Removed Certificate with Alias: {config.JobCertificate.Alias}"); - runSpace.Close(); - _logger.LogTrace($"RunSpace was closed..."); + _logger.LogTrace("After Perform Removal..."); + break; } - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; + _logger.MethodExit(); + return complete; } catch (Exception ex) { - var failureMessage = $"Remove job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogTrace(LogHandler.FlattenException(ex)); + + var failureMessage = $"Managemenmt job {config.OperationType} failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; _logger.LogWarning(failureMessage); return new JobResult @@ -237,28 +114,53 @@ private JobResult PerformRemoval(ManagementJobConfiguration config) } } - private JobResult PerformAddition(ManagementJobConfiguration config) + private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) { - try + _logger.LogTrace("Before PerformAddition..."); + + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string storePath = config.CertificateStoreDetails.StorePath; + long jobNumber = config.JobHistoryId; + + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + + if (result.Result == OrchestratorJobStatusJobResult.Success) { - _logger.MethodEntry(); - - var iisManager=new IISManager(config,ServerUserName,ServerPassword); - return iisManager.AddCertificate(); - } - catch (Exception ex) + // Bind to IIS + ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); + result = iisManager.BindCertificate(manager.X509Cert); + return result; + } else return result; + } + + private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) + { + _logger.LogTrace("Before Remove Certificate..."); + + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string storePath = config.CertificateStoreDetails.StorePath; + long jobNumber = config.JobHistoryId; + + // First we need to unbind the certificate from IIS before we remove it from the store + ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); + JobResult result = iisManager.UnBindCertificate(); + + if (result.Result == OrchestratorJobStatusJobResult.Success) { - var failureMessage = $"Add job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; - _logger.LogWarning(failureMessage); + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + manager.RemoveCertificate(config.JobCertificate.Alias, storePath); return new JobResult { - Result = OrchestratorJobStatusJobResult.Failure, + Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage + FailureMessage = "" }; } + else return result; } - } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs index e197100..59d2703 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs @@ -12,230 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities; using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS { - public class ReEnrollment:IReenrollmentJobExtension + public class ReEnrollment: WinCertJobTypeBase, IReenrollmentJobExtension { private ILogger _logger; - private IPAMSecretResolver _resolver; public ReEnrollment(IPAMSecretResolver resolver) { _resolver = resolver; } - public string ExtensionName => "IISU"; + public string ExtensionName => string.Empty; - private string ResolvePamField(string name, string value) - { - _logger.LogTrace($"Attempting to resolved PAM eligible field {name}"); - return _resolver.Resolve(value); - } - public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReEnrollmentUpdate) { - _logger = LogHandler.GetClassLogger(); - _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); - var storePath = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - _logger.LogTrace($"WinRm Url: {storePath?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{storePath?.WinRmPort}/wsman"); - - _logger.LogTrace("Entering ReEnrollment..."); - _logger.LogTrace("Before ReEnrollment..."); - return PerformReEnrollment(config, submitReEnrollmentUpdate); - - } - - private JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) - { - try - { - _logger.MethodEntry(); - var serverUserName = ResolvePamField("Server UserName", config.ServerUsername); - var serverPassword = ResolvePamField("Server Password", config.ServerPassword); - - // Extract values necessary to create remote PS connection - JobProperties properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - - WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri($"{properties?.WinRmProtocol}://{config.CertificateStoreDetails.ClientMachine}:{properties?.WinRmPort}/wsman")); - connectionInfo.IncludePortInSPN = properties.SpnPortFlag; - var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; - _logger.LogTrace($"Credentials: UserName:{serverUserName} Password:{serverPassword}"); - - connectionInfo.Credential = new PSCredential(serverUserName, pw); - _logger.LogTrace($"PSCredential Created {pw}"); - - // Establish new remote ps session - _logger.LogTrace("Creating remote PS Workspace"); - using var runSpace = RunspaceFactory.CreateRunspace(connectionInfo); - _logger.LogTrace("Workspace created"); - runSpace.Open(); - _logger.LogTrace("Workspace opened"); - - // NEW - var ps = PowerShell.Create(); - ps.Runspace = runSpace; - - string CSR = string.Empty; - - var subjectText = config.JobProperties["subjectText"]; - var providerName = config.JobProperties["ProviderName"]; - var keyType = config.JobProperties["keyType"]; - var keySize = config.JobProperties["keySize"]; - var SAN = config.JobProperties["SAN"]; - - // If the provider name is null, default it to the Microsoft CA - if (providerName == null) providerName = "Microsoft Strong Cryptographic Provider"; - - // Create the script file - ps.AddScript("$infFilename = New-TemporaryFile"); - ps.AddScript("$csrFilename = New-TemporaryFile"); - - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); - - ps.AddScript($"Set-Content $infFilename [NewRequest]"); - ps.AddScript($"Add-Content $infFilename 'Subject = \"{subjectText}\"'"); - ps.AddScript($"Add-Content $infFilename 'ProviderName = \"{providerName}\"'"); - ps.AddScript($"Add-Content $infFilename 'MachineKeySet = True'"); - ps.AddScript($"Add-Content $infFilename 'HashAlgorithm = SHA256'"); - ps.AddScript($"Add-Content $infFilename 'KeyAlgorithm = {keyType}'"); - ps.AddScript($"Add-Content $infFilename 'KeyLength={keySize}'"); - ps.AddScript($"Add-Content $infFilename 'KeySpec = 0'"); - - if(SAN != null) - { - ps.AddScript($"Add-Content $infFilename '[Extensions]'"); - ps.AddScript(@"Add-Content $infFilename '2.5.29.17 = ""{text}""'"); - - foreach (string s in SAN.ToString().Split("&")) - { - ps.AddScript($"Add-Content $infFilename '_continue_ = \"{s + "&"}\"'"); - } - } - - // Execute the -new command - ps.AddScript($"certreq -new -q $infFilename $csrFilename"); - _logger.LogDebug($"Subject Text: {subjectText}"); - _logger.LogDebug($"SAN: {SAN}"); - _logger.LogDebug($"Provider Name: {providerName}"); - _logger.LogDebug($"Key Type: {keyType}"); - _logger.LogDebug($"Key Size: {keySize}"); - _logger.LogTrace("Attempting to create the CSR by Invoking the script."); - - Collection results = ps.Invoke(); - _logger.LogTrace("Completed the attempt in creating the CSR."); - ps.Commands.Clear(); - - try - { - ps.AddScript($"$CSR = Get-Content $csrFilename"); - _logger.LogTrace("Attempting to get the contents of the CSR file."); - results = ps.Invoke(); - _logger.LogTrace("Finished getting the CSR Contents."); - } - catch (Exception) - { - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - throw new CertificateException($"Error creating CSR File. {psError}"); - } - finally - { - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - _logger.LogTrace("Attempt to delete the temporary files."); - results = ps.Invoke(); - } - - // Get the byte array - var CSRContent = ps.Runspace.SessionStateProxy.GetVariable("CSR").ToString(); - - // Sign CSR in Keyfactor - _logger.LogTrace("Get the signed CSR from KF."); - X509Certificate2 myCert = submitReenrollment.Invoke(CSRContent); - - if (myCert != null) - { - // Get the cert data into string format - string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); - - _logger.LogTrace("Creating the text version of the certificate."); - - // Write out the cert file - StringBuilder sb = new StringBuilder(); - sb.AppendLine("-----BEGIN CERTIFICATE-----"); - sb.AppendLine(csrData); - sb.AppendLine("-----END CERTIFICATE-----"); - - ps.AddScript("$cerFilename = New-TemporaryFile"); - ps.AddScript($"Set-Content $cerFilename '{sb}'"); - - results = ps.Invoke(); - ps.Commands.Clear(); - - // Accept the signed cert - _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); - ps.AddScript("certreq -accept $cerFilename"); - ps.Invoke(); - _logger.LogTrace("Successfully bound the certificate to the HSM."); - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); - _logger.LogTrace("Removing temporary files."); - results = ps.Invoke(); - - ps.Commands.Clear(); - runSpace.Close(); - // Bind the certificate to IIS - var iisManager = new IISManager(config,serverUserName,serverPassword); - return iisManager.ReEnrollCertificate(myCert); - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." - }; - } + _logger = LogHandler.GetClassLogger(typeof(ReEnrollment)); - } - catch (Exception ex) - { - var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; - _logger.LogWarning(failureMessage); + ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); + return myReEnrollment.PerformReEnrollment(config, submitReEnrollmentUpdate, true); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage - }; - } } } } diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs new file mode 100644 index 0000000..23fb0a3 --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs @@ -0,0 +1,117 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Text; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS +{ + internal class WinIISInventory : ClientPSCertStoreInventory + { + public WinIISInventory(ILogger logger) : base(logger) + { + } + + public List GetInventoryItems(Runspace runSpace, string storePath) + { + // Get the raw certificate inventory from cert store + List certificates = base.GetCertificatesFromStore(runSpace, storePath); + + // Contains the inventory items to be sent back to KF + List myBoundCerts = new List(); + + using (var ps = PowerShell.Create()) + { + ps.Runspace = runSpace; + + ps.AddCommand("Import-Module") + .AddParameter("Name", "WebAdministration") + .AddStatement(); + + var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; + ps.AddScript(searchScript).AddStatement(); + var iisBindings = ps.Invoke(); // Responsible for getting all bound certificates for each website + + if (ps.HadErrors) + { + var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + } + + if (iisBindings.Count == 0) + { + return myBoundCerts; + } + + //in theory should only be one, but keeping for future update to chance inventory + foreach (var binding in iisBindings) + { + var thumbPrint = $"{(binding.Properties["thumbprint"]?.Value)}"; + if (string.IsNullOrEmpty(thumbPrint)) continue; + + Certificate foundCert = certificates.Find(m => m.Thumbprint.Equals(thumbPrint)); + + if (foundCert == null) continue; + + var sniValue = ""; + switch (Convert.ToInt16(binding.Properties["sniFlg"]?.Value)) + { + case 0: + sniValue = "0 - No SNI"; + break; + case 1: + sniValue = "1 - SNI Enabled"; + break; + case 2: + sniValue = "2 - Non SNI Binding"; + break; + case 3: + sniValue = "3 - SNI Binding"; + break; + } + + var siteSettingsDict = new Dictionary + { + { "SiteName", binding.Properties["Name"]?.Value }, + { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, + { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, + { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, + { "SniFlag", sniValue }, + { "Protocol", binding.Properties["Protocol"]?.Value } + }; + + myBoundCerts.Add( + new CurrentInventoryItem + { + Certificates = new[] { foundCert.CertificateData }, + Alias = thumbPrint, + PrivateKeyEntry = foundCert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = siteSettingsDict + } + ); + } + } + + return myBoundCerts; + } + } +} diff --git a/IISU/PowerShellUtilities/CertificateException.cs b/IISU/PAMUtilities.cs similarity index 54% rename from IISU/PowerShellUtilities/CertificateException.cs rename to IISU/PAMUtilities.cs index 793674a..bbed644 100644 --- a/IISU/PowerShellUtilities/CertificateException.cs +++ b/IISU/PAMUtilities.cs @@ -12,30 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Runtime.Serialization; using System.Text; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - [Serializable] - internal class CertificateException : Exception + internal class PAMUtilities { - public CertificateException() + internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logger, string name, string key) { - } - - public CertificateException(string message) : base(message) - { - } + if (resolver == null) return key; + else + { + logger.LogDebug($"Attempting to resolve PAM eligible field {name} with key {key}"); + return resolver.Resolve(key); + } - public CertificateException(string message, Exception innerException) : base(message, innerException) - { - } - - protected CertificateException(SerializationInfo info, StreamingContext context) : base(info, context) - { } } } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs new file mode 100644 index 0000000..1d7b5af --- /dev/null +++ b/IISU/PSHelper.cs @@ -0,0 +1,42 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + public class PSHelper + { + private static ILogger _logger; + + public static Runspace GetClientPSRunspace(string winRmProtocol, string clientMachineName, string WinRmPort, bool includePortInSPN, string serverUserName, string serverPassword) + { + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + + var connInfo = new WSManConnectionInfo(new Uri($"{winRmProtocol}://{clientMachineName}:{WinRmPort}/wsman")); + connInfo.IncludePortInSPN = includePortInSPN; + + var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; + connInfo.Credential = new PSCredential(serverUserName, pw); + + return RunspaceFactory.CreateRunspace(connInfo); + } + } +} diff --git a/IISU/PowerShellUtilities/CertificateStore.cs b/IISU/PowerShellUtilities/CertificateStore.cs deleted file mode 100644 index 7f79494..0000000 --- a/IISU/PowerShellUtilities/CertificateStore.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities -{ - internal class CertificateStore - { - public CertificateStore(string serverName, string storePath, Runspace runSpace) - { - ServerName = serverName; - StorePath = storePath; - RunSpace = runSpace; - Initalize(); - } - - public string ServerName { get; set; } - public string StorePath { get; set; } - public Runspace RunSpace { get; set; } - public List Certificates { get; set; } - - public void RemoveCertificate(string thumbprint) - { - using var ps = PowerShell.Create(); - ps.Runspace = RunSpace; - var removeScript = $@" - $ErrorActionPreference = 'Stop' - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') - $certStore.Open('MaxAllowed') - $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) - if($certToRemove.Count -gt 0) {{ - $certStore.Remove($certToRemove[0]) - }} - $certStore.Close() - $certStore.Dispose() - "; - - ps.AddScript(removeScript); - - var _ = ps.Invoke(); - if (ps.HadErrors) - throw new CertificateStoreException($"Error removing certificate in {StorePath} store on {ServerName}."); - } - - private void Initalize() - { - Certificates = new List(); - try - { - using var ps = PowerShell.Create(); - ps.Runspace = RunSpace; - - var certStoreScript = $@" - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') - $certStore.Open('ReadOnly') - $certs = $certStore.Certificates - $certStore.Close() - $certStore.Dispose() - foreach ( $cert in $certs){{ - $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey - }}"; - - ps.AddScript(certStoreScript); - - var certs = ps.Invoke(); - - foreach (var c in certs) - Certificates.Add(new Certificate - { - Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", - HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[]) c.Properties["RawData"]?.Value - }); - } - catch (Exception ex) - { - throw new CertificateStoreException( - $"Error listing certificate in {StorePath} store on {ServerName}: {ex.Message}"); - } - } - } -} \ No newline at end of file diff --git a/IISU/PowerShellUtilities/PSCommandHelper.cs b/IISU/PowerShellUtilities/PSCommandHelper.cs deleted file mode 100644 index fb0b3ef..0000000 --- a/IISU/PowerShellUtilities/PSCommandHelper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Security.Cryptography.X509Certificates; -using System.Text; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities -{ - public class PSCommandHelper - { - /// - /// - /// - /// - /// - /// List - /// - public static List GetChildItem(PowerShell ps, string certStorePath) - { - string output = string.Empty; - string errorMsg = string.Empty; - List certificates = new List(); - - var script = $"Get-ChildItem Cert:{certStorePath}"; - ps.AddScript(script); - - // Establish a Powershell output object - PSDataCollection outputCollection = new PSDataCollection(); - ps.Streams.Error.DataAdded += (object sender, DataAddedEventArgs e) => - { - errorMsg = ((PSDataCollection)sender)[e.Index].ToString(); - }; - - IAsyncResult result = ps.BeginInvoke(null, outputCollection); - ps.EndInvoke(result); - - foreach (var outputItem in outputCollection) - { - X509Certificate2 cert = (X509Certificate2)outputItem.BaseObject; - certificates.Add(cert); - } - - ps.Commands.Clear(); - - if (!string.IsNullOrEmpty(errorMsg)) - throw new CertificateStoreException(errorMsg); - - return certificates; - } - } -} diff --git a/IISU/Properties/launchSettings.json b/IISU/Properties/launchSettings.json index e85a2ae..03eed47 100644 --- a/IISU/Properties/launchSettings.json +++ b/IISU/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "IISU": { + "WindowsCertStore": { "commandName": "Project", "remoteDebugEnabled": true, "authenticationMode": "None", diff --git a/IISU/WinCertJobTypeBase.cs b/IISU/WinCertJobTypeBase.cs new file mode 100644 index 0000000..d6bcd17 --- /dev/null +++ b/IISU/WinCertJobTypeBase.cs @@ -0,0 +1,23 @@ +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Keyfactor.Orchestrators.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + public abstract class WinCertJobTypeBase + { + public IPAMSecretResolver _resolver; + } +} diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index df23cf8..dd80058 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -11,6 +11,15 @@ false + + + + + + + + + @@ -18,7 +27,8 @@ - + + @@ -27,9 +37,4 @@ - - - - - diff --git a/IISU/manifest.json b/IISU/manifest.json index a3efd9b..77c0b2b 100644 --- a/IISU/manifest.json +++ b/IISU/manifest.json @@ -20,6 +20,10 @@ "CertStores.Win.Management": { "assemblypath": "WindowsCertStore.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.Management" + }, + "CertStores.Win.ReEnrollment": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.ReEnrollment" } } } diff --git a/WinCertTestConsole/Program.cs b/WinCertTestConsole/Program.cs new file mode 100644 index 0000000..6a8085f --- /dev/null +++ b/WinCertTestConsole/Program.cs @@ -0,0 +1,128 @@ +using Keyfactor.Orchestrators.Extensions; +//using Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win; +//using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore; +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Moq; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace WinCertTestConsole +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine("Please Select (I)IS cert store or (W)indows cert store"); + var certStoreType = Console.ReadLine().ToUpper().Substring(0,1); + + Console.WriteLine("Please Select (I)nventory or (M)anagement"); + var input = Console.ReadLine(); + + switch (input.ToUpper().Substring(0,1)) + { + case "R": // Done testing + { + //using var myRunspace = PSHelper.GetClientPSRunspace("http", "localhost", "5985", false, "kfadmin", "Wh5G2Tc6VBYjSMpC"); + //myRunspace.Open(); + //List myCerts = Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities.CertificateStore.GetCertificatesFromStore(myRunspace, "My"); + + //Console.WriteLine($"Number of certs found: {myCerts.Count}"); + //Console.ReadKey(); + + //List inventory = Keyfactor.Extensions.Orchestrator.WindowsCertStore.PowerShellUtilities.CertificateStore.GetIISBoundCertificates(myRunspace,"My"); + //myRunspace.Close(); + break; + } + case "I": + { + + //Mock invSecretResolver = new Mock(); + //invSecretResolver.Setup(m => m.Resolve(It.IsAny())).Returns(() => "LUFRPT0xbXlnVU9OL2d1N05zY0NPbDJPaEtzWDhtVWM9RWUzVTk4YmZPajhTRkRtcTNmTnEzNERHVzdRTWZNWmQxNlBFNXl0UDBnOXVDWGU1bFN6NS9FSklKNFduNGV6dA=="); + + //var inv = new Inventory(invSecretResolver.Object); + + //var invJobConfig = GetInventoryJobConfiguration(); + //if (certStoreType == "I") + //{ + // var inv = new Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS.Inventory(); + // SubmitInventoryUpdate sui = GetItems; + // inv.ProcessJob(invJobConfig, sui); + //} + //else if(certStoreType=="W") + //{ + // var inv = new Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.Inventory(); + // SubmitInventoryUpdate sui = GetItems; + // inv.ProcessJob(invJobConfig, sui); + //} + break; + } + case "M": + { + //Console.WriteLine("Select Management (A)dd or (R)emove:"); + //var mgmtInput = Console.ReadLine(); + + //switch (mgmtInput.ToUpper().Substring(0,1)) + //{ + // case "A": + // { + // Console.WriteLine("Enter Private Key Password ikdj3huXRhtZ, Leave Blank if no Private Key"); + // var privateKeyPwd = Console.ReadLine(); + // Console.WriteLine("Overwrite? Enter true or false"); + // var overWrite = Console.ReadLine(); + // Console.WriteLine("Alias Enter Alias Name"); + // var alias = Console.ReadLine(); + // Console.WriteLine("Trusted Root? Enter true or false"); + // var trustedRoot = Console.ReadLine(); + + // Mock mgmtSecretResolver = new Mock(); + // mgmtSecretResolver.Setup(m => m.Resolve(It.IsAny())).Returns(() => "LUFRPT0xbXlnVU9OL2d1N05zY0NPbDJPaEtzWDhtVWM9RWUzVTk4YmZPajhTRkRtcTNmTnEzNERHVzdRTWZNWmQxNlBFNXl0UDBnOXVDWGU1bFN6NS9FSklKNFduNGV6dA=="); + // var mgmt = new Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.Management(mgmtSecretResolver.Object); + + // var jobConfiguration = GetJobConfiguration(privateKeyPwd, overWrite, trustedRoot, alias); + // var result = mgmt.ProcessJob(jobConfiguration); + + // if (result.Result == Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success) + // { + // Console.WriteLine("Add Success"); + // } + // break; + // } + // case "R": + // { + // break; + // } + //} + break; + } + } + } + + public static bool GetItems(IEnumerable items) + { + return true; + } + + public static InventoryJobConfiguration GetInventoryJobConfiguration() + { + var jobConfigString = "{\"LastInventory\":[],\"CertificateStoreDetails\":{\"ClientMachine\":\"localhost\",\"StorePath\":\"My\",\"StorePassword\":\"\",\"Properties\":\"{\\\"CustField1\\\":\\\"\\\",\\\"ServerUsername\\\":\\\"kfadmin\\\",\\\"ServerPassword\\\":\\\"Wh5G2Tc6VBYjSMpC\\\",\\\"ServerUseSsl\\\":\\\"true\\\"}\",\"Type\":103},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":3357,\"RequestStatus\":1,\"ServerUsername\":\"kfadmin\",\"ServerPassword\":\"Wh5G2Tc6VBYjSMpC\",\"UseSSL\":true,\"JobProperties\":null,\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"27eb30f5-f151-4077-acb5-cbc2cc489f7f\",\"Capability\":\"CertStores.Win.Inventory\"}"; + var result = JsonConvert.DeserializeObject(jobConfigString); + + return result; + } + + public static ManagementJobConfiguration GetJobConfiguration(string privateKeyPwd, string overWrite, string trustedRoot, string alias) + { + //var privateKeyConfig = $"{{\"LastInventory\":[],\"CertificateStoreDetails\":{{\"ClientMachine\":\"keyfactorpa.eastus2.cloudapp.azure.com\",\"StorePath\":\"public\",\"StorePassword\":null,\"Properties\":\"{{}}\",\"Type\":5109}},\"OperationType\":2,\"Overwrite\":{overWrite},\"JobCertificate\":{{\"Thumbprint\":null,\"Contents\":\"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\",\"Alias\":\"{alias}\",\"PrivateKeyPassword\":\"{privateKeyPwd}\"}},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":298380,\"RequestStatus\":1,\"ServerUsername\":\"bhill\",\"ServerPassword\":\"LUFRPT0xbXlnVU9OL2d1N05zY0NPbDJPaEtzWDhtVWM9RWUzVTk4YmZPajhTRkRtcTNmTnEzNERHVzdRTWZNWmQxNlBFNXl0UDBnOXVDWGU1bFN6NS9FSklKNFduNGV6dA==\",\"UseSSL\":true,\"JobProperties\":{{\"Trusted Root\":{trustedRoot}}},\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"d9e6e40b-f9cf-4974-a8c3-822d2c4f394f\",\"Capability\":\"CertStores.PaloAlto.Management\"}}"; + //var noPrivateKeyConfig = $"{{\"LastInventory\":[],\"CertificateStoreDetails\":{{\"ClientMachine\":\"keyfactorpa.eastus2.cloudapp.azure.com\",\"StorePath\":\"public\",\"StorePassword\":null,\"Properties\":\"{{}}\",\"Type\":5109}},\"OperationType\":2,\"Overwrite\":{overWrite},\"JobCertificate\":{{\"Thumbprint\":null,\"Contents\":\"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\",\"Alias\":\"{alias}\",\"PrivateKeyPassword\":null}},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":298404,\"RequestStatus\":1,\"ServerUsername\":\"bhill\",\"ServerPassword\":\"LUFRPT0xbXlnVU9OL2d1N05zY0NPbDJPaEtzWDhtVWM9RWUzVTk4YmZPajhTRkRtcTNmTnEzNERHVzdRTWZNWmQxNlBFNXl0UDBnOXVDWGU1bFN6NS9FSklKNFduNGV6dA==\",\"UseSSL\":true,\"JobProperties\":{{\"Trusted Root\":{trustedRoot}}},\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"36a048c2-f051-407d-9f31-a1ec6ab7d913\",\"Capability\":\"CertStores.PaloAlto.Management\"}}"; + var privateKeyConfig = $"{{\"LastInventory\":[],\"CertificateStoreDetails\":{{\"ClientMachine\":\"localhost\",\"StorePath\":\"My\",\"StorePassword\":null,\"Properties\":\"{{\\\"CustField1\\\":\\\"\\\",\\\"ServerUsername\\\":\\\"kfadmin\\\",\\\"ServerPassword\\\":\\\"Wh5G2Tc6VBYjSMpC\\\",\\\"ServerUseSsl\\\":\\\"true\\\"}}\",\"Type\":103}},\"OperationType\":2,\"Overwrite\":{overWrite},\"JobCertificate\":{{\"Thumbprint\":null,\"Contents\":\"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\",\"Alias\":\"{alias}\",\"PrivateKeyPassword\":\"{privateKeyPwd}\"}},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":5028,\"RequestStatus\":1,\"ServerUsername\":\"kfadmin\",\"ServerPassword\":\"Wh5G2Tc6VBYjSMpC\",\"UseSSL\":true,\"JobProperties\":{{\"EntryParam1\":null}},\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"8abad458-5f69-4ca4-b76a-47134649d6d1\",\"Capability\":\"CertStores.Win.Management\"}}"; + + //var jobConfigString = privateKeyPwd.Length > 0 ? privateKeyConfig : noPrivateKeyConfig; + + var result = JsonConvert.DeserializeObject(privateKeyConfig); + return result; + } + } +} diff --git a/WinCertTestConsole/WinCertTestConsole.csproj b/WinCertTestConsole/WinCertTestConsole.csproj new file mode 100644 index 0000000..c0395b3 --- /dev/null +++ b/WinCertTestConsole/WinCertTestConsole.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/WindowsCertStore.sln b/WindowsCertStore.sln index 7b14b13..ed90aa4 100644 --- a/WindowsCertStore.sln +++ b/WindowsCertStore.sln @@ -18,13 +18,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{630203 images\AddCertStore.png = images\AddCertStore.png images\CertStoreType-c.png = images\CertStoreType-c.png images\CertStoreType.png = images\CertStoreType.png + images\IISCertStore.png = images\IISCertStore.png images\ReEnrollment1.png = images\ReEnrollment1.png images\ReEnrollment1a.png = images\ReEnrollment1a.png images\ReEnrollment1b.png = images\ReEnrollment1b.png images\Screen1.png = images\Screen1.png images\Screen2.png = images\Screen2.png + images\WinCertStore.png = images\WinCertStore.png EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinCertTestConsole", "WinCertTestConsole\WinCertTestConsole.csproj", "{D0F4A3CC-5236-4393-9C97-AE55ACE319F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +39,10 @@ Global {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.Build.0 = Debug|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.Build.0 = Release|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/images/IISCertStore.png b/images/IISCertStore.png new file mode 100644 index 0000000000000000000000000000000000000000..a4be6ad2c684118911080b427c4d9d2659511a06 GIT binary patch literal 24867 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�g9#ZB!x2Ch7N0aqPWu8WXP} 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 BMGgP} literal 0 HcmV?d00001 diff --git a/images/WinCertStore.png b/images/WinCertStore.png new file mode 100644 index 0000000000000000000000000000000000000000..d9e9ffec907508a6a87ad18ae6bab3b1033dfed1 GIT binary patch literal 20632 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 Rz|#a7JYD@<);T3K0RXM3{KxBlueprint Allowed - Unchecked
Uses PowerShell - Unchecked Requires Store Password |Determines if a store password is required when configuring an individual store. This must be unchecked. Supports Entry Password |Determined if an individual entry within a store can have a password. This must be unchecked. -Supports Custom Alias |Determines if an individual entry within a store can have a custom Alias. This must be Forbidden. -Uses PowerShell |Unchecked -Store Path Type |Determines what restrictions are applied to the store path field when configuring a new store. This must be Multiple Choice -Store Path Value|A comma separated list of options to select from for the Store Path. This, combined with the hostname, will determine the location used for the certificate store management and inventory. Must be My, WebHosting -Private Keys |This determines if Keyfactor can send the private key associated with a certificate to the store. This is required since IIS will need the private key material to establish TLS connections. -PFX Password Style |This determines how the platform generate passwords to protect a PFX enrollment job that is delivered to the store. This can be either Default (system generated) or Custom (user determined). -Job Types |Inventory, Add, Remove, and Reenrollment are the supported job types. + ![](images/certstoretype.png) **Advanced Settings:** -- **Custom Alias** – Forbidden -- **Private Key Handling** – Required + +CONFIG ELEMENT | DESCRIPTION +------------------|------------------ +Store Path Type |Determines what restrictions are applied to the store path field when configuring a new store. +Store Path Value|When using this as a Windows Cert Store, this option must be freeform, allowing the user to type in a particular store path.
When using this for bound or IIS Certificates, This must be a comma separated list of options to select from for the Store Path. This, combined with the hostname, will determine the location used for the certificate store management and inventory. Must be My, WebHosting +Supports Custom Alias |Determines if an individual entry within a store can have a custom Alias. This must be Forbidden. +Private Keys |This determines if Keyfactor can send the private key associated with a certificate to the store. This is required since IIS will need the private key material to establish TLS connections. +PFX Password Style |This determines how the platform generate passwords to protect a PFX enrollment job that is delivered to the store. This can be either Default (system generated) or Custom (user determined). ![](images/screen1-a.gif) @@ -56,16 +63,18 @@ Job Types |Inventory, Add, Remove, and Reenrollment are the supported job types. Parameter Name|Display Name|Parameter Type|Default Value|Required|Description ---|---|---|---|---|--- -spnwithport|SPN With Port?|Boolean|false|No|An SPN is the name by which a client uniquely identifies an instance of a service -WinRm Protocol|WinRm Protocol|Multiple Choice|http|Yes|Protocol that WinRM Runs on -WinRm Port|WinRm Port|String|5985|Yes|Port that WinRM Runs on +spnwithport\*|SPN With Port?|Boolean|false|No|An SPN is the name by which a client uniquely identifies an instance of a service +WinRm Protocol\*|WinRm Protocol|Multiple Choice|http|Yes|Protocol that WinRM Runs on +WinRm Port\*|WinRm Port|String|5985|Yes|Port that WinRM Runs on ServerUsername|Server Username|Secret||No|The username to log into the IIS Server ServerPassword|Server Password|Secret||No|The password that matches the username to log into the IIS Server ServerUseSsl|Use SSL|Bool|True|Yes|Determine whether the server uses SSL or not +**NOTE: Elements with an asterisk (*) are only required when communicating with a Web Server and bound certificates. ![](images/certstoretype-c.png) + **Entry Parameters:** This section must be configured with binding fields. The parameters will be populated with the appropriate data when creating a new certificate store.
@@ -81,8 +90,8 @@ This section must be configured with binding fields. The parameters will be popu - 1 - SNI Enabled - 2 - Non SNI Binding - 3 - SNI Binding -- **Provider Name** - Optional. Name of the Windows cryptographic provider to use when generating and storing the private key for the certificate being enrolled by a reenrollment job. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be changed when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target IIS server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' in a command shell on the target IIS Server. -- **SAN** - Optional. Specifies Subject Alternative Name (SAN) to be used when performing reenrollment jobs. Certificate templates generally require a SAN that matches the subject of the certificate (per RFC 2818). Format is a list of = entries separated by ampersands. Examples: 'dns=www.mysite.com' for a single SAN or 'dns=www.mysite.com&dns=www.mysite2.com' for multiple SANs. +- **Provider Name\*** - Optional. Name of the Windows cryptographic provider to use when generating and storing the private key for the certificate being enrolled by a reenrollment job. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be changed when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target IIS server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' in a command shell on the target IIS Server. +- **SAN\*** - Optional. Specifies Subject Alternative Name (SAN) to be used when performing reenrollment jobs. Certificate templates generally require a SAN that matches the subject of the certificate (per RFC 2818). Format is a list of = entries separated by ampersands. Examples: 'dns=www.mysite.com' for a single SAN or 'dns=www.mysite.com&dns=www.mysite2.com' for multiple SANs. Parameter Name|Parameter Type|Default Value|Required When ---|---|---|--- @@ -92,19 +101,21 @@ HostName |String|| SiteName |String|Default Web Site|Adding Entry, Removing Entry, Reenrolling an Entry SniFlag |String|0 - No SNI| Protocol |Multiple Choice|https|Adding Entry, Removing Entry, Reenrolling an Entry -ProviderName |String|| -SAN |String||Reenrolling an Entry (if the CA follows RFC 2818 specifications) +ProviderName\* |String|| +SAN\* |String||Reenrolling an Entry (if the CA follows RFC 2818 specifications) + +**NOTE: Elements with an asterisk (*) are only required when not binding certificates to a web server. ![](images/screen2.png) **2. Register the IIS Universal Orchestrator with Keyfactor** See Keyfactor InstallingKeyfactorOrchestrators.pdf Documentation. Get from your Keyfactor contact/representative. -**3. Create an IIS Binding Certificate Store within Keyfactor Command** +**3a. Create an IIS Binding Certificate Store within Keyfactor Command** -In Keyfactor Command create a new Certificate Store similar to the one below, selecting "IISU" as the Category and the parameters as described in "Create the New Certificate Store Type for the New IIS AnyAgent".
+In Keyfactor Command create a new Certificate Store similar to the one below, selecting "WinIIS" as the Category and the parameters as described in "Create the New Certificate Store Type for the New IIS AnyAgent".
-![](images/AddCertStore.png) +![](images/IISCertStore.png) #### STORE CONFIGURATION CONFIG ELEMENT |DESCRIPTION @@ -124,6 +135,24 @@ Use SSL|Determines whether SSL is used ot not Inventory Schedule |The interval that the system will use to report on what certificates are currently in the store. +**3b. Create a Windows Certificate Store within Keyfactor Command** + +In Keyfactor Command create a new Certificate Store similar to the one below, selecting "WinIIS" as the Category and the parameters as described in "Create the New Certificate Store Type for the New IIS AnyAgent".
+ +![](images/WinCertStore.png) + +#### STORE CONFIGURATION +CONFIG ELEMENT |DESCRIPTION +----------------|--------------- +Category |The type of certificate store to be configured. Select category based on the display name configured above. +Container |This is a logical grouping of like stores. This configuration is optional and does not impact the functionality of the store. +Client Machine |The hostname of the server to be managed. The Change Credentials option must be clicked to provide a username and password. This account will be used to manage the remote server via PowerShell. +Credentials |Local or domain admin account that has permissions to manage iis (Has to be admin) +Store Path |Any correctly spelled local machine store path +Orchestrator |This is the orchestrator server registered with the appropriate capabilities to manage this certificate store type. +Server Username|Username to log into the Server +Server Password|Password for the username required to log into the IIS Server +Use SSL|Determines whether SSL is used ot not #### TEST CASES Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot From 507b3972dda388041e097197e8e5e66c303d4a72 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Tue, 28 Feb 2023 20:07:49 +0000 Subject: [PATCH 4/5] Update generated README --- README.md | 121 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 8227e7e..9cc4801 100644 --- a/README.md +++ b/README.md @@ -54,23 +54,51 @@ It is not necessary to implement all of the secrets available to be managed by a Setting up a PAM provider for use involves adding an additional section to the manifest.json file for this extension as well as setting up the PAM provider you will be using. Each of these steps is specific to the PAM provider you will use and are documented in the specific GitHub repo for that provider. For a list of Keyfactor supported PAM providers, please reference the [Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam). +### Register the PAM Provider + +A PAM Provider needs to be registered on the Universal Orchestrator in the same way other extensions are. Create a folder for the specific PAM Provider to be added, and place the contents of the PAM Provider into the folder. There needs to be a manifest.json with the PAM Provider. + +After a manifest.json is added, the final step for configuration is setting the "provider-level" parameters for the PAM Provider. These are also known as the "initialization-level" parameters. These need to be placed in a json file that gets loaded by the Orchestrator by default. + +example manifest.json for MY-PROVIDER-NAME +``` +{ + "extensions": { + "Keyfactor.Platform.Extensions.IPAMProvider": { + "PAMProviders.MY-PROVIDER-NAME.PAMProvider": { + "assemblyPath": "my-pam-provider.dll", + "TypeFullName": "Keyfactor.Extensions.Pam.MyPamProviderClass" + } + } + }, + "Keyfactor:PAMProviders:MY-PROVIDER-NAME:InitializationInfo": { + "InitParam1": "InitValue1", + "InitParam2": "InitValue2" + } +} +``` + + + --- -**IIS Orchestrator Configuration** +**WinCertStore Orchestrator Configuration** **Overview** -The IIS Orchestrator remotely manages certificates in a Windows Server local machine certificate store. -The "Personal" (My) and "Web Hosting" Stores are supported. -Only certificates that are bound to an IIS web site are managed. -Unbound certificates are ignored. +The WinCertStore Orchestrator remotely manages certificates in a Windows Server local machine certificate store. Users are able to determine which store they wish to place certificates in by entering the correct store path. For a complete list of local machine cert stores you can execute the PowerShell command: + + Get-ChildItem Cert:\LocalMachine + +The returned list will contain the actual certificate store name to be used when entering store location. -This agent implements four job types – Inventory, Management Add, Remove and ReEnrollment. Below are the steps necessary to configure this AnyAgent. +By default, most certificates are stored in the “Personal” (My) and “Web Hosting” (WebHosting) stores. -WinRM is used to remotely manage the certificate stores and IIS bindings. WinRM must be properly configured to allow -the server running the orchestrator to manage the server running IIS. +This agent implements four job types: Inventory, Management Add/Remove, and ReEnrollment. + +WinRM is used to remotely manage the certificate stores and IIS bindings. WinRM must be properly configured to allow the orchestrator on the server to manage the certificates. Setting up WinRM is not in the scope of this document. **Note:** In version 2.0 of the IIS Orchestrator, the certificate store type has been renamed and additional parameters have been added. Prior to 2.0 the certificate store type was called “IISBin” and as of 2.0 it is called “IISU”. If you have existing certificate stores of type “IISBin”, you have three options: @@ -80,33 +108,38 @@ In version 2.0 of the IIS Orchestrator, the certificate store type has been rena **Note: There is an additional certificate store type of “IIS” that ships with the Keyfactor platform. Migration of certificate stores from the “IIS” type to either the “IISBin” or “IISU” types is not currently supported.** -**1. Create the New Certificate Store Type for the IIS Orchestrator** +**Note: In version 3.0, the orchestrator has been renamed from IISU to WinCert. There is currently no succession process to update previous certificate store types.** + + +**1. Create the New Certificate Store Type** In Keyfactor Command create a new Certificate Store Type similar to the one below: #### STORE TYPE CONFIGURATION +**Basic Settings:** + CONFIG ELEMENT | DESCRIPTION ------------------|------------------ -Name |Descriptive name for the Store Type -Short Name |The short name that identifies the registered functionality of the orchestrator. Must be IISU -Custom Capability|Store type name orchestrator will register with. Must be "IISU". -Needs Server |Must be checked -Blueprint Allowed |Unchecked +Name |A descriptive name for the extension. Example: WinCert (for general windows cert store), WinIIS (for IIS Webstore cert store) +Short Name |The short name that identifies the registered functionality of the orchestrator. Currently must be either Win or WinIIS +Custom Capability|Store type name orchestrator will register with. Currently must be Win or WinIIS. +Job Types |Inventory (Checked), Add, Remove, and Reenrollment are the supported job types. +General Settings|Needs Server - Checked
Blueprint Allowed - Unchecked
Uses PowerShell - Unchecked Requires Store Password |Determines if a store password is required when configuring an individual store. This must be unchecked. Supports Entry Password |Determined if an individual entry within a store can have a password. This must be unchecked. -Supports Custom Alias |Determines if an individual entry within a store can have a custom Alias. This must be Forbidden. -Uses PowerShell |Unchecked -Store Path Type |Determines what restrictions are applied to the store path field when configuring a new store. This must be Multiple Choice -Store Path Value|A comma separated list of options to select from for the Store Path. This, combined with the hostname, will determine the location used for the certificate store management and inventory. Must be My, WebHosting -Private Keys |This determines if Keyfactor can send the private key associated with a certificate to the store. This is required since IIS will need the private key material to establish TLS connections. -PFX Password Style |This determines how the platform generate passwords to protect a PFX enrollment job that is delivered to the store. This can be either Default (system generated) or Custom (user determined). -Job Types |Inventory, Add, Remove, and Reenrollment are the supported job types. + ![](images/certstoretype.png) **Advanced Settings:** -- **Custom Alias** – Forbidden -- **Private Key Handling** – Required + +CONFIG ELEMENT | DESCRIPTION +------------------|------------------ +Store Path Type |Determines what restrictions are applied to the store path field when configuring a new store. +Store Path Value|When using this as a Windows Cert Store, this option must be freeform, allowing the user to type in a particular store path.
When using this for bound or IIS Certificates, This must be a comma separated list of options to select from for the Store Path. This, combined with the hostname, will determine the location used for the certificate store management and inventory. Must be My, WebHosting +Supports Custom Alias |Determines if an individual entry within a store can have a custom Alias. This must be Forbidden. +Private Keys |This determines if Keyfactor can send the private key associated with a certificate to the store. This is required since IIS will need the private key material to establish TLS connections. +PFX Password Style |This determines how the platform generate passwords to protect a PFX enrollment job that is delivered to the store. This can be either Default (system generated) or Custom (user determined). ![](images/screen1-a.gif) @@ -116,16 +149,18 @@ Job Types |Inventory, Add, Remove, and Reenrollment are the supported job types. Parameter Name|Display Name|Parameter Type|Default Value|Required|Description ---|---|---|---|---|--- -spnwithport|SPN With Port?|Boolean|false|No|An SPN is the name by which a client uniquely identifies an instance of a service -WinRm Protocol|WinRm Protocol|Multiple Choice|http|Yes|Protocol that WinRM Runs on -WinRm Port|WinRm Port|String|5985|Yes|Port that WinRM Runs on +spnwithport\*|SPN With Port?|Boolean|false|No|An SPN is the name by which a client uniquely identifies an instance of a service +WinRm Protocol\*|WinRm Protocol|Multiple Choice|http|Yes|Protocol that WinRM Runs on +WinRm Port\*|WinRm Port|String|5985|Yes|Port that WinRM Runs on ServerUsername|Server Username|Secret||No|The username to log into the IIS Server ServerPassword|Server Password|Secret||No|The password that matches the username to log into the IIS Server ServerUseSsl|Use SSL|Bool|True|Yes|Determine whether the server uses SSL or not +**NOTE: Elements with an asterisk (*) are only required when communicating with a Web Server and bound certificates. ![](images/certstoretype-c.png) + **Entry Parameters:** This section must be configured with binding fields. The parameters will be populated with the appropriate data when creating a new certificate store.
@@ -141,8 +176,8 @@ This section must be configured with binding fields. The parameters will be popu - 1 - SNI Enabled - 2 - Non SNI Binding - 3 - SNI Binding -- **Provider Name** - Optional. Name of the Windows cryptographic provider to use when generating and storing the private key for the certificate being enrolled by a reenrollment job. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be changed when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target IIS server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' in a command shell on the target IIS Server. -- **SAN** - Optional. Specifies Subject Alternative Name (SAN) to be used when performing reenrollment jobs. Certificate templates generally require a SAN that matches the subject of the certificate (per RFC 2818). Format is a list of = entries separated by ampersands. Examples: 'dns=www.mysite.com' for a single SAN or 'dns=www.mysite.com&dns=www.mysite2.com' for multiple SANs. +- **Provider Name\*** - Optional. Name of the Windows cryptographic provider to use when generating and storing the private key for the certificate being enrolled by a reenrollment job. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be changed when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target IIS server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' in a command shell on the target IIS Server. +- **SAN\*** - Optional. Specifies Subject Alternative Name (SAN) to be used when performing reenrollment jobs. Certificate templates generally require a SAN that matches the subject of the certificate (per RFC 2818). Format is a list of = entries separated by ampersands. Examples: 'dns=www.mysite.com' for a single SAN or 'dns=www.mysite.com&dns=www.mysite2.com' for multiple SANs. Parameter Name|Parameter Type|Default Value|Required When ---|---|---|--- @@ -152,19 +187,21 @@ HostName |String|| SiteName |String|Default Web Site|Adding Entry, Removing Entry, Reenrolling an Entry SniFlag |String|0 - No SNI| Protocol |Multiple Choice|https|Adding Entry, Removing Entry, Reenrolling an Entry -ProviderName |String|| -SAN |String||Reenrolling an Entry (if the CA follows RFC 2818 specifications) +ProviderName\* |String|| +SAN\* |String||Reenrolling an Entry (if the CA follows RFC 2818 specifications) + +**NOTE: Elements with an asterisk (*) are only required when not binding certificates to a web server. ![](images/screen2.png) **2. Register the IIS Universal Orchestrator with Keyfactor** See Keyfactor InstallingKeyfactorOrchestrators.pdf Documentation. Get from your Keyfactor contact/representative. -**3. Create an IIS Binding Certificate Store within Keyfactor Command** +**3a. Create an IIS Binding Certificate Store within Keyfactor Command** -In Keyfactor Command create a new Certificate Store similar to the one below, selecting "IISU" as the Category and the parameters as described in "Create the New Certificate Store Type for the New IIS AnyAgent".
+In Keyfactor Command create a new Certificate Store similar to the one below, selecting "WinIIS" as the Category and the parameters as described in "Create the New Certificate Store Type for the New IIS AnyAgent".
-![](images/AddCertStore.png) +![](images/IISCertStore.png) #### STORE CONFIGURATION CONFIG ELEMENT |DESCRIPTION @@ -184,6 +221,24 @@ Use SSL|Determines whether SSL is used ot not Inventory Schedule |The interval that the system will use to report on what certificates are currently in the store. +**3b. Create a Windows Certificate Store within Keyfactor Command** + +In Keyfactor Command create a new Certificate Store similar to the one below, selecting "WinIIS" as the Category and the parameters as described in "Create the New Certificate Store Type for the New IIS AnyAgent".
+ +![](images/WinCertStore.png) + +#### STORE CONFIGURATION +CONFIG ELEMENT |DESCRIPTION +----------------|--------------- +Category |The type of certificate store to be configured. Select category based on the display name configured above. +Container |This is a logical grouping of like stores. This configuration is optional and does not impact the functionality of the store. +Client Machine |The hostname of the server to be managed. The Change Credentials option must be clicked to provide a username and password. This account will be used to manage the remote server via PowerShell. +Credentials |Local or domain admin account that has permissions to manage iis (Has to be admin) +Store Path |Any correctly spelled local machine store path +Orchestrator |This is the orchestrator server registered with the appropriate capabilities to manage this certificate store type. +Server Username|Username to log into the Server +Server Password|Password for the username required to log into the IIS Server +Use SSL|Determines whether SSL is used ot not #### TEST CASES Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot From 1057afeebdb5c48aede565d98d8390e93cc4aa64 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 28 Feb 2023 23:00:05 +0000 Subject: [PATCH 5/5] Fixed a problem adding certs to a cert store that had a space in the name (ie. Remote Desktop) --- IISU/ClientPSCertStoreReEnrollment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index cd9b575..5257f83 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -207,7 +207,7 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit // Accept the signed cert _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); - ps.AddScript($"Set-Location -Path Cert:\\localmachine\\{config.CertificateStoreDetails.StorePath}"); + ps.AddScript($"Set-Location -Path Cert:\\localmachine\\'{config.CertificateStoreDetails.StorePath}'"); ps.AddScript($"Import-Certificate -Filepath $cerFilename"); ps.Invoke(); _logger.LogTrace("Successfully bound the certificate.");