From 0a59d741896ffebee255d96d36dbd3d5282ad393 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 23 Oct 2024 19:20:21 -0500 Subject: [PATCH] Updated code for WinCert and WinIIS --- IISU/ImplementedStoreTypes/Win/Inventory.cs | 10 +- IISU/ImplementedStoreTypes/Win/Management.cs | 40 ++- .../WinIIS/IISBindingInfo.cs | 55 ++++ .../ImplementedStoreTypes/WinIIS/Inventory.cs | 5 +- .../WinIIS/Management.cs | 296 ++++++++++-------- IISU/PSHelper.cs | 58 +--- 6 files changed, 272 insertions(+), 192 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index e0dade0..fe8d9f6 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -126,14 +126,16 @@ public List QueryWinCertCertificates(RemoteSettings settin { List Inventory = new(); - string command = string.Empty; - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) { ps.Initialize(); - command = $"Get-KFCertificates -StoreName '{StoreName}'"; - results = ps.ExecuteFunction(command); + var parameters = new Dictionary + { + { "StoreName", StoreName } + }; + + results = ps.ExecutePowerShell("Get-KFCertificates", parameters); // If there are certificates, deserialize the results and send them back to command if (results != null && results.Count > 0) diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index 232695d..f38c3cc 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -23,6 +23,8 @@ using System.Management.Automation; using Keyfactor.Logging; using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Management.Automation.Runspaces; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { @@ -31,7 +33,6 @@ public class Management : WinCertJobTypeBase, IManagementJobExtension public string ExtensionName => "WinCertManagement"; private ILogger _logger; - private string command = string.Empty; private PSHelper _psHelper; private Collection? _results = null; @@ -98,7 +99,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); - complete = AddCertificate(certificateContents, privateKeyPassword, _storePath, cryptoProvider); + complete = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); _logger.LogTrace($"Completed adding the certificate to the store"); break; @@ -107,7 +108,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { string thumbprint = config.JobCertificate.Alias; - complete = RemoveCertificate(thumbprint, _storePath); + complete = RemoveCertificate(thumbprint); _logger.LogTrace($"Completed removing the certificate from the store"); break; @@ -134,7 +135,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } } - public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath, string cryptoProvider) + public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string cryptoProvider) { try { @@ -143,15 +144,26 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas _psHelper.Initialize(); _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - command = $"Add-KFCertificateToStore -Base64Cert '{certificateContents}' -PrivateKeyPassword '{privateKeyPassword}' -StoreName '{storePath}' -CryptoServiceProvider '{cryptoProvider}'"; - _results = _psHelper.ExecuteFunction(command); + + // Manditory parameters + var parameters = new Dictionary + { + { "Base64Cert", certificateContents }, + { "StorePath", _storePath }, + }; + + // Optional parameters + if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } + if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } + + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); // This should return the thumbprint of the certificate if (_results != null && _results.Count > 0) { var thumbprint = _results[0].ToString(); - _logger.LogTrace($"Added certificate to store {storePath}, returned with the thumbprint {thumbprint}"); + _logger.LogTrace($"Added certificate to store {_storePath}, returned with the thumbprint {thumbprint}"); } else { @@ -182,7 +194,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas } } - public JobResult RemoveCertificate(string thumbprint, string storePath) + public JobResult RemoveCertificate(string thumbprint) { try { @@ -190,9 +202,15 @@ public JobResult RemoveCertificate(string thumbprint, string storePath) { _psHelper.Initialize(); - _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {storePath}"); - command = $"Remove-KFCertificateFromStore -Thumbprint '{thumbprint}' -StorePath '{storePath}'"; - _psHelper.ExecuteFunction(command); + _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {_storePath}"); + + var parameters = new Dictionary() + { + { "Thumbprint", thumbprint }, + { "StorePath", _storePath } + }; + + _psHelper.ExecutePowerShell("Remove-KFCertificateFromStore", parameters); _logger.LogTrace("Returned from executing PS function (Remove-KFCertificateFromStore)"); _psHelper.Terminate(); diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs new file mode 100644 index 0000000..9fca009 --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS +{ + public class IISBindingInfo + { + public string SiteName { get; set; } + public string Protocol { get; set; } + public string IPAddress { get; set; } + public string Port { get; set; } + public string HostName { get; set; } + public string SniFlag { get; set; } + + public IISBindingInfo(Dictionary bindingInfo) + { + SiteName = bindingInfo["SiteName"].ToString(); + Protocol = bindingInfo["Protocol"].ToString(); + IPAddress = bindingInfo["IPAddress"].ToString(); + Port = bindingInfo["Port"].ToString(); + HostName = bindingInfo["HostName"].ToString(); + SniFlag = MigrateSNIFlag(bindingInfo["SniFlag"].ToString()); + } + + private string MigrateSNIFlag(string input) + { + // Check if the input is numeric, if so, just return it as an integer + if (int.TryParse(input, out int numericValue)) + { + return numericValue.ToString(); + } + + if (string.IsNullOrEmpty(input)) { throw new ArgumentNullException("SNI/SSL Flag", "The SNI or SSL Flag flag must not be empty or null."); } + + // Handle the string cases + switch (input.ToLower()) + { + case "0 - no sni": + return "0"; + case "1 - sni enabled": + return "1"; + case "2 - non sni binding": + return "2"; + case "3 - sni binding": + return "3"; + default: + throw new ArgumentOutOfRangeException($"Received an invalid value '{input}' for sni/ssl Flag value"); + } + } + } +} diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index da633f2..eb4d8fb 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -123,14 +123,11 @@ public List QueryIISCertificates(RemoteSettings settings) { List Inventory = new(); - string command = string.Empty; - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) { ps.Initialize(); - command = "Get-KFIISBoundCertificates"; - results = ps.ExecuteFunction(command); + results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); // If there are certificates, deserialize the results and send them back to command if (results != null && results.Count > 0) diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index d0199f2..26adfab 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -13,9 +13,11 @@ // limitations under the License. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -30,7 +32,6 @@ public class Management : WinCertJobTypeBase, IManagementJobExtension public string ExtensionName => "WinIISUManagement"; private ILogger _logger; - private string command = string.Empty; private PSHelper _psHelper; private Collection? _results = null; @@ -100,42 +101,67 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); // Add Certificate to Cert Store - using (_psHelper) + try { - - string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, _storePath, cryptoProvider); + string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); _logger.LogTrace($"Completed adding the certificate to the store"); // Bind Certificate to IIS Site if (newThumbprint != null) { - + IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); + BindCertificate(bindingInfo, newThumbprint); + + complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; } - + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; } + _logger.LogTrace($"Completed adding and binding the certificate to the store"); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobHistoryID, - FailureMessage = "" - }; + break; } case CertStoreOperationType.Remove: { + // Removing a certificate involves two steps: UnBind the certificate, then delete the cert from the store + string thumbprint = config.JobCertificate.Alias; + try + { + if (UnBindCertificate(new IISBindingInfo(config.JobProperties))) + { + complete = RemoveCertificate(thumbprint); + } + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; + } - //complete = RemoveCertificate(thumbprint, _storePath); _logger.LogTrace($"Completed removing the certificate from the store"); break; } } - _psHelper.Terminate(); - - _logger.MethodExit(); return complete; } catch (Exception ex) @@ -149,34 +175,44 @@ public JobResult ProcessJob(ManagementJobConfiguration config) FailureMessage = ex.Message }; } + finally + { + _psHelper.Terminate(); + _logger.MethodExit(); + } } - public string AddCertificate(string certificateContents, string privateKeyPassword, string storePath, string cryptoProvider) + public string AddCertificate(string certificateContents, string privateKeyPassword, string cryptoProvider) { try { string newThumbprint = string.Empty; - using (_psHelper) + _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); + + // Manditory parameters + var parameters = new Dictionary { - _psHelper.Initialize(); + { "Base64Cert", certificateContents }, + { "StorePath", _storePath }, + }; - _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - command = $"Add-KFCertificateToStore -Base64Cert '{certificateContents}' -PrivateKeyPassword '{privateKeyPassword}' -StoreName '{storePath}' -CryptoServiceProvider '{cryptoProvider}'"; - _results = _psHelper.ExecuteFunction(command); - _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + // Optional parameters + if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } + if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } - // This should return the thumbprint of the certificate - if (_results != null && _results.Count > 0) - { - newThumbprint= _results[0].ToString(); - _logger.LogTrace($"Added certificate to store {storePath}, returned with the thumbprint {newThumbprint}"); - } - else - { - _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); - } - _psHelper.Terminate(); + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + newThumbprint= _results[0].ToString(); + _logger.LogTrace($"Added certificate to store {_storePath}, returned with the thumbprint {newThumbprint}"); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); } return newThumbprint; @@ -190,107 +226,107 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo } } - public void BindCertificate(string siteName, string protocol, string ipAddress, string port, string sniFlag, string storeName, string thumbPrint, string hostName = "") + public JobResult RemoveCertificate(string thumbprint) { - /* - function New-KFIISSiteBinding - */ + try + { + using (_psHelper) + { + _psHelper.Initialize(); + + _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {_storePath}"); + + var parameters = new Dictionary() + { + { "Thumbprint", thumbprint }, + { "StorePath", _storePath } + }; + _psHelper.ExecutePowerShell("Remove-KFCertificateFromStore", parameters); + _logger.LogTrace("Returned from executing PS function (Remove-KFCertificateFromStore)"); + + _psHelper.Terminate(); + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } + catch (Exception ex) + { + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = failureMessage + }; + } } + public void BindCertificate(IISBindingInfo bindingInfo, string thumbprint) + { + _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); + + // Manditory parameters + var parameters = new Dictionary + { + { "Thumbprint", thumbprint }, + { "WebSite", bindingInfo.SiteName }, + { "Protocol", bindingInfo.Protocol }, + { "IPAddress", bindingInfo.IPAddress }, + { "Port", bindingInfo.Port }, + { "SNIFlag", bindingInfo.SniFlag }, + { "StorePath", _storePath }, + }; + + // Optional parameters + if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + + _results = _psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + } + } + + public bool UnBindCertificate(IISBindingInfo bindingInfo) + { + _logger.LogTrace("Attempting to UnBind and execute PS function (Remove-KFIISBinding)"); + + // Manditory parameters + var parameters = new Dictionary + { + { "SiteName", bindingInfo.SiteName }, + { "IPAddress", bindingInfo.IPAddress }, + { "Port", bindingInfo.Port }, + }; + // Optional parameters + if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } -// private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) -// { -// try -// { -//#nullable enable -// string certificateContents = config.JobCertificate.Contents; -// string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; -// string storePath = config.CertificateStoreDetails.StorePath; -// long jobNumber = config.JobHistoryId; -// string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); -//#nullable disable - -// // If a crypto provider was provided, check to see if it exists -// if (cryptoProvider != null) -// { -// _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); -// if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) -// { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } -// } - -// if (storePath != null) -// { -// _logger.LogInformation($"Attempting to add IISU certificate to cert store: {storePath}"); -// } - -// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - -// // This method is retired -// //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); - -// // Write the certificate contents to a temporary file on the remote computer, returning the filename. -// string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); -// _logger.LogTrace($"{filePath} was created."); - -// // Using certutil on the remote computer, import the pfx file using a supplied csp if any. -// JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); - -// // Delete the temporary file -// manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); - -// if (result.Result == OrchestratorJobStatusJobResult.Success) -// { -// // Bind to IIS -// _logger.LogInformation("Attempting to bind certificate to website."); -// ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); -// result = iisManager.BindCertificate(manager.X509Cert); - -// // Provide logging information -// if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the website."); } -// else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the website. Check the logs for more information."); } - -// return result; -// } -// else return result; -// } -// catch (Exception e) -// { -// return new JobResult -// { -// Result = OrchestratorJobStatusJobResult.Failure, -// JobHistoryId = config.JobHistoryId, -// FailureMessage = -// $"Management/Add {e.Message}" -// }; -// } -// } - -// private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) -// { -// _logger.LogTrace("Before Remove Certificate..."); - -// 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) -// { -// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); -// manager.RemoveCertificate(config.JobCertificate.Alias, storePath); - -// return new JobResult -// { -// Result = OrchestratorJobStatusJobResult.Success, -// JobHistoryId = config.JobHistoryId, -// FailureMessage = "" -// }; -// } -// else return result; -// } + try + { + _results = _psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); + _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); + return true; + } + catch (Exception) + { + return false; + } + } } } \ No newline at end of file diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 27999ac..e7c940f 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -20,6 +20,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Management.Automation; @@ -215,62 +216,33 @@ public void Terminate() return ExecutePowerShell(functionName); } - public Collection? ExecuteScriptBlock(string scriptBlock, Dictionary? parameters = null) + public Collection? ExecutePowerShell(string commandName, Dictionary? parameters = null) { - // THIS IS ONLY FOR TESTING - //using (PS) - //{ - PS.AddCommand(scriptBlock); - foreach (var param in parameters) - { - PS.AddParameter(param.Key, param.Value); - } - - var results = PS.Invoke(); - CheckErrors(); - - // Display the results - foreach (var result in results) - { - Console.WriteLine(result); - } - //} - - return ExecutePowerShell(scriptBlock, parameters); - } - - private Collection? ExecutePowerShell(string scriptBlock, Dictionary? parameters = null) - { - // Add parameters to the script block - var argList = new List(); - if (parameters != null) - { - foreach (var parameter in parameters.Values) - { - argList.Add(parameter); - } - } - try { if (!isLocalMachine) { PS.AddCommand("Invoke-Command") .AddParameter("Session", _PSSession) // send session only when necessary (remote) - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - .AddParameter("ArgumentList", argList.ToArray()); + .AddParameter("ScriptBlock", ScriptBlock.Create(commandName)) + .AddParameter("ArgumentList", parameters?.Values.ToArray()); } else { - PS.AddScript(scriptBlock).AddArgument(argList.ToArray()); - //PS.AddCommand("Invoke-Command") - // .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - // .AddParameter("ArgumentList", argList.ToArray()); + PS.AddCommand(commandName); + + if (parameters != null) + { + foreach (var parameter in parameters) + { + PS.AddParameter(parameter.Key, parameter.Value); + } + } } bool hadErrors = false; string errorList = string.Empty; - _logger.LogTrace($"Script block:\n{scriptBlock}"); + _logger.LogTrace($"Script block:\n{commandName}"); var results = PS.Invoke(); CheckErrors(); @@ -339,7 +311,7 @@ private void CheckErrors() _logger.LogError($"Error: {error}"); } - throw new ApplicationException(errorList); + throw new Exception(errorList); } }