diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bda5e..d71d6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +2.4.2 +* Correct false positive error when completing an IIS inventory job. +* Revert to specifying the version of PowerShell to use when establishing a local PowerShell Runspace. +* Fixed typo in error message. + 2.4.1 * Modified the CertUtil logic to use the -addstore argument when no password is sent with the certificate information. * Added additional error trapping and trace logs @@ -5,7 +10,7 @@ 2.4.0 * Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. * Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert, WinSQL and IISU extensions. -* Changed how Client Machine Names are handled when a 'localhost' connection is desiered. The new naming convention is: {machineName}|localmachine. This will eliminate the issue of unqiue naming conflicts. +* Changed how Client Machine Names are handled when a 'localhost' connection is desired. The new naming convention is: {machineName}|localmachine. This will eliminate the issue of unique naming conflicts. * Updated the manifest.json to now include WinSQL ReEnrollment. * Updated the integration-manifest.json file for new fields in cert store types. @@ -49,7 +54,7 @@ 2.0.0 * Add support for reenrollment jobs (On Device Key Generation) with the ability to specify a cryptographic provider. Specification of cryptographic provider allows HSM (Hardware Security Module) use. * Local PAM Support added (requires Universal Orchestrator Framework version 10.1) -* Certificate store type changed from IISBin to IISU. See readme for migration notes. +* Certificate store type changed from IISBin to IISU. See README for migration notes. 1.1.3 diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs index 82a6365..0bde6f5 100644 --- a/IISU/ClientPSCertStoreInventory.cs +++ b/IISU/ClientPSCertStoreInventory.cs @@ -64,6 +64,8 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor ps.AddScript(certStoreScript); + _logger.LogTrace($"Executing the following script:\n{certStoreScript}"); + var certs = ps.Invoke(); foreach (var c in certs) @@ -77,11 +79,13 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor SAN = Certificate.Utilities.FormatSAN($"{c.Properties["san"]?.Value}") }); } - + _logger.LogTrace($"found: {myCertificates.Count} certificate(s), exiting GetCertificatesFromStore()"); return myCertificates; } catch (Exception ex) { + _logger.LogTrace($"An error occurred in the WinCert GetCertificatesFromStore method:\n{ex.Message}"); + 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 index df91f38..3a75960 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -133,7 +133,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // If no private key password is provided, import the pfx file directory to the store using addstore argument string script = @" param($pfxFilePath, $storePath) - $output = certutil -addstore $storePath $pfxFilePath 2>&1 + $output = certutil -f -addstore $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" if ($output.GetType().Name -eq ""String"") @@ -156,20 +156,19 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // Use ImportPFX to import the pfx file with private key password to the appropriate cert store string script = @" - param($pfxFilePath, $privateKeyPassword) - $output = certutil -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 + param($pfxFilePath, $privateKeyPassword, $storePath) + $output = certutil -f -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") + + if ($output.GetType().Name -eq ""String"") { - $stuff = @($stuff, $exit_message) + $output = @($output, $exit_message) } else { - $stuff += $exit_message + $output += $exit_message } $output - $stuff "; ps.AddScript(script); @@ -184,20 +183,18 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { string script = @" param($pfxFilePath, $cspName, $storePath) - $output = certutil -csp $cspName -addstore $storePath $pfxFilePath 2>&1 + $output = certutil -f -csp $cspName -addstore $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") + if ($output.GetType().Name -eq ""String"") { - $stuff = @($stuff, $exit_message) + $output = @($output, $exit_message) } else { - $stuff += $exit_message + $output += $exit_message } $output - $stuff "; ps.AddScript(script); @@ -208,21 +205,19 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin else { string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 + param($pfxFilePath, $privateKeyPassword, $cspName, $storePath) + $output = certutil -f -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") + if ($output.GetType().Name -eq ""String"") { - $stuff = @($stuff, $exit_message) + $output = @($output, $exit_message) } else { - $stuff += $exit_message + $output += $exit_message } $output - $stuff "; ps.AddScript(script); @@ -244,9 +239,9 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin lastExitCode = GetLastExitCode(results[^1].ToString()); _logger.LogTrace($"Last exit code: {lastExitCode}"); } - catch (Exception) + catch (Exception ex) { - _logger.LogTrace("Unable to get the last exit code."); + _logger.LogTrace(ex.Message); } diff --git a/IISU/ImplementedStoreTypes/Win/WinInventory.cs b/IISU/ImplementedStoreTypes/Win/WinInventory.cs index 6332e1a..99954e6 100644 --- a/IISU/ImplementedStoreTypes/Win/WinInventory.cs +++ b/IISU/ImplementedStoreTypes/Win/WinInventory.cs @@ -23,12 +23,15 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { internal class WinInventory : ClientPSCertStoreInventory { + private ILogger _logger; public WinInventory(ILogger logger) : base(logger) { + _logger = logger; } public List GetInventoryItems(Runspace runSpace, string storePath) { + _logger.LogTrace("Entering WinCert GetInventoryItems."); List inventoryItems = new List(); foreach (Certificate cert in base.GetCertificatesFromStore(runSpace, storePath)) @@ -50,6 +53,7 @@ public List GetInventoryItems(Runspace runSpace, string st }); } + _logger.LogTrace($"Found {inventoryItems.Count} certificates. Exiting WinCert GetInventoryItems."); return inventoryItems; } } diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 86fffe3..a3855ec 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -75,13 +75,13 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven WinIISInventory IISInventory = new WinIISInventory(_logger); inventoryItems = IISInventory.GetInventoryItems(myRunspace, storePath); - _logger.LogTrace($"A total of {inventoryItems.Count} were found"); + _logger.LogTrace($"A total of {inventoryItems.Count} bound certificate(s) were found"); _logger.LogTrace("Closing runspace..."); myRunspace.Close(); - _logger.LogTrace("Invoking Inventory.."); + _logger.LogTrace("Invoking submitInventory.."); submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked... {inventoryItems.Count} Items"); + _logger.LogTrace($"submitInventory Invoked... {inventoryItems.Count} Items"); return new JobResult { diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 72efe5f..09877c3 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -101,7 +101,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { _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)}'"; + var failureMessage = $"Management 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 diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs index f88f046..2ae070b 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs @@ -27,12 +27,15 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { internal class WinIISInventory : ClientPSCertStoreInventory { + private ILogger _logger; public WinIISInventory(ILogger logger) : base(logger) { + _logger = logger; } public List GetInventoryItems(Runspace runSpace, string storePath) { + _logger.LogTrace("Entering IISU GetInventoryItems"); // Get the raw certificate inventory from cert store List certificates = base.GetCertificatesFromStore(runSpace, storePath); @@ -51,22 +54,36 @@ public List GetInventoryItems(Runspace runSpace, string st } else { - ps2.AddScript("Set-ExecutionPolicy RemoteSigned"); + ps2.AddScript("Set-ExecutionPolicy RemoteSigned -Scope Process -Force"); ps2.AddScript("Import-Module WebAdministration"); - //var result = ps.Invoke(); } 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}}}"; ps2.AddScript(searchScript); - var iisBindings = ps2.Invoke(); // Responsible for getting all bound certificates for each website + + _logger.LogTrace($"Attempting to initiate the following script:\n{searchScript}"); + + var iisBindings = ps2.Invoke(); if (ps2.HadErrors) { - var psError = ps2.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + _logger.LogTrace("The previous script encountered errors. See below for more info."); + string psError = string.Empty; + try + { + psError = ps2.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + (error.ErrorDetails?.Message ?? error.Exception.ToString())); + } + catch + { + } + + if (psError != null) { throw new Exception(psError); } + } if (iisBindings.Count == 0) { + _logger.LogTrace("No binding certificates were found. Exiting IISU GetInventoryItems."); return myBoundCerts; } @@ -123,6 +140,7 @@ public List GetInventoryItems(Runspace runSpace, string st } } + _logger.LogTrace($"Found {myBoundCerts.Count} bound certificates. Exiting IISU GetInventoryItems."); return myBoundCerts; } } diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index e304f1e..f13f7e5 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -96,7 +96,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { _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)}'"; + var failureMessage = $"Management 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 diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 7d2990e..c125751 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -33,7 +33,7 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa _logger.MethodEntry(); // 2.4 - Client Machine Name now follows the naming conventions of {clientMachineName}|{localMachine} - // If the clientMachineName is just 'localhost', it will maintain that as locally only (as previosuly) + // If the clientMachineName is just 'localhost', it will maintain that as locally only (as previously) // If there is no 2nd part to the clientMachineName, a remote PowerShell session will be created // Break the clientMachineName into parts @@ -43,14 +43,18 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa string machineName = parts.Length > 1 ? parts[0] : clientMachineName; string argument = parts.Length > 1 ? parts[1] : null; - // Determine if this is truely a local connection + // Determine if this is truly a local connection bool isLocal = (machineName.ToLower() == "localhost") || (argument != null && argument.ToLower() == "localmachine"); _logger.LogInformation($"Full clientMachineName={clientMachineName} | machineName={machineName} | argument={argument} | isLocal={isLocal}"); if (isLocal) { - return RunspaceFactory.CreateRunspace(); + //return RunspaceFactory.CreateRunspace(); + PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); + Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); + + return rs; } else { diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index d6d1c51..b224ebf 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -31,7 +31,7 @@ - + diff --git a/README.md b/README.md index b2cf34b..038e85e 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,9 @@ Click Save to save the Certificate Store Type. ## Creating New Certificate Stores Once the Certificate Store Types have been created, you need to create the Certificate Stores prior to using the extension. -**Note:** A new naming convention for the Client Machine allows for multiple stores on the same server with different cert store path and cert store types. This convention is \{MachineName\}\|\{[optional]localmachine\}. If the optional value is 'localmachine' (legacy 'localhost' is still supported) is supplied, a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. +### Note Regarding Client Machine +If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), the Client Machine can be entered, OR you can bypass a WinRM connection and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a certificate store that will be accessed without a WinRM connection. + Here are the settings required for each Store Type previously configured.
diff --git a/readme_source.md b/readme_source.md index c5c4a2e..07c1a8c 100644 --- a/readme_source.md +++ b/readme_source.md @@ -264,7 +264,9 @@ Click Save to save the Certificate Store Type. ## Creating New Certificate Stores Once the Certificate Store Types have been created, you need to create the Certificate Stores prior to using the extension. -**Note:** A new naming convention for the Client Machine allows for multiple stores on the same server with different cert store path and cert store types. This convention is \{MachineName\}\|\{[optional]localmachine\}. If the optional value is 'localmachine' (legacy 'localhost' is still supported) is supplied, a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. +### Note Regarding Client Machine +If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), the Client Machine can be entered, OR you can bypass a WinRM connection and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a certificate store that will be accessed without a WinRM connection. + Here are the settings required for each Store Type previously configured.