diff --git a/IISU/PSCertificate.cs b/IISU/Certificate.cs similarity index 90% rename from IISU/PSCertificate.cs rename to IISU/Certificate.cs index 5bfdf7f..88f3612 100644 --- a/IISU/PSCertificate.cs +++ b/IISU/Certificate.cs @@ -14,9 +14,9 @@ using System; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - public class PsCertificate + public class Certificate { public string Thumbprint { get; set; } public byte[] RawData { get; set; } 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/PSCertStoreException.cs b/IISU/CertificateStoreException.cs similarity index 62% rename from IISU/PSCertStoreException.cs rename to IISU/CertificateStoreException.cs index 5afc67b..b510548 100644 --- a/IISU/PSCertStoreException.cs +++ b/IISU/CertificateStoreException.cs @@ -15,24 +15,24 @@ using System; using System.Runtime.Serialization; -namespace Keyfactor.Extensions.Orchestrator.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { [Serializable] - internal class PsCertStoreException : Exception + internal class CertificateStoreException : Exception { - public PsCertStoreException() + public CertificateStoreException() { } - public PsCertStoreException(string message) : base(message) + public CertificateStoreException(string message) : base(message) { } - public PsCertStoreException(string message, Exception innerException) : base(message, innerException) + public CertificateStoreException(string message, Exception innerException) : base(message, innerException) { } - protected PsCertStoreException(SerializationInfo info, StreamingContext context) : base(info, context) + protected CertificateStoreException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 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/Jobs/ReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs similarity index 76% rename from IISU/Jobs/ReEnrollment.cs rename to IISU/ClientPSCertStoreReEnrollment.cs index edb7036..5257f83 100644 --- a/IISU/Jobs/ReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -1,227 +1,261 @@ -// 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 Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS; 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; +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.IISU.Jobs +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - public class ReEnrollment:IReenrollmentJobExtension + internal class ClientPSCertStoreReEnrollment { - private ILogger _logger; - + private ILogger _logger; private IPAMSecretResolver _resolver; - public ReEnrollment(IPAMSecretResolver resolver) + public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver) { + _logger = logger; _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(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) + public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, bool bindCertificate) { + bool hasError = false; + 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 = 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 + "&"}\"'"); + } } - 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." + + 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) { 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 new file mode 100644 index 0000000..d277ba7 --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -0,0 +1,135 @@ +// 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.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using System.Security.Cryptography.X509Certificates; +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 : WinCertJobTypeBase, IInventoryJobExtension + { + private ILogger _logger; + public string ExtensionName => string.Empty; + + public Inventory() + { + } + + public Inventory(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + + public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) + { + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + + return PerformInventory(jobConfiguration, submitInventoryUpdate); + } + + private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) + { + try + { + var inventoryItems = new List(); + + _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); + + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + // 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) + { + _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}"); + + //foreach (Certificate cert in PowerShellUtilities.CertificateStore.GetCertificatesFromStore(myRunspace, storePath)) + WinInventory winInv = new WinInventory(_logger); + inventoryItems = winInv.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 + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Warning, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" + }; + } + 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..fe5d96a --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -0,0 +1,234 @@ +// 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.Management.Automation.Runspaces; +using System.Management.Automation; +using System.Net; +using Keyfactor.Logging; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win +{ + public class Management : WinCertJobTypeBase, IManagementJobExtension + { + 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}" + }; + } + } + + private JobResult performRemove(ManagementJobConfiguration config) + { + 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}"); + + 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/IISManager.cs b/IISU/ImplementedStoreTypes/WinIIS/IISManager.cs similarity index 94% rename from IISU/IISManager.cs rename to IISU/ImplementedStoreTypes/WinIIS/IISManager.cs index 4a96869..6a14735 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,57 @@ 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(); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = JobHistoryId, + FailureMessage = "" + }; + } + 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(); // Moved to different method + + // Add Certificate var funcScript = @" $ErrorActionPreference = ""Stop"" @@ -188,124 +193,131 @@ 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(); // 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) + { + 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 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 +330,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 +370,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/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs new file mode 100644 index 0000000..0919ccf --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -0,0 +1,130 @@ +// 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.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +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 Inventory : WinCertJobTypeBase, IInventoryJobExtension + { + private ILogger _logger; + + public string ExtensionName => string.Empty; + + public Inventory(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + + public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) + { + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + + return PerformInventory(jobConfiguration, submitInventoryUpdate); + } + + private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) + { + try + { + var inventoryItems = new List(); + + _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); + + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + // 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) + { + _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 + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Warning, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" + }; + } + 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 + }; + } + } + } +} \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs new file mode 100644 index 0000000..da0d60f --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -0,0 +1,166 @@ +// 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; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +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 : WinCertJobTypeBase, IManagementJobExtension + { + private ILogger _logger; + + public string ExtensionName => string.Empty; + + private string _thumbprint = string.Empty; + + private Runspace myRunspace; + + public Management(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + + public JobResult ProcessJob(ManagementJobConfiguration config) + { + try + { + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + + _logger.LogTrace($"Job Configuration: {JsonConvert.SerializeObject(config)}"); + + 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, + FailureMessage = + "Invalid Management Operation" + }; + + switch (config.OperationType) + { + case CertStoreOperationType.Add: + _logger.LogTrace("Entering Add..."); + + myRunspace.Open(); + complete = PerformAddCertificate(config, serverUserName, serverPassword); + myRunspace.Close(); + + _logger.LogTrace("After Perform Addition..."); + break; + case CertStoreOperationType.Remove: + _logger.LogTrace("Entering Remove..."); + + _logger.LogTrace("After PerformRemoval..."); + myRunspace.Open(); + complete = PerformRemoveCertificate(config, serverUserName, serverPassword); + myRunspace.Close(); + + _logger.LogTrace("After Perform Removal..."); + break; + } + + _logger.MethodExit(); + return complete; + } + catch (Exception 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 + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = failureMessage + }; + } + } + + private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) + { + _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) + { + // 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) + { + 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; + } + } +} \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs new file mode 100644 index 0000000..59d2703 --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs @@ -0,0 +1,44 @@ +// 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.Extensions; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinIIS +{ + public class ReEnrollment: WinCertJobTypeBase, IReenrollmentJobExtension + { + private ILogger _logger; + + + public ReEnrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + + public string ExtensionName => string.Empty; + + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReEnrollmentUpdate) + { + + _logger = LogHandler.GetClassLogger(typeof(ReEnrollment)); + + ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); + return myReEnrollment.PerformReEnrollment(config, submitReEnrollmentUpdate, true); + + } + } +} 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/Jobs/Inventory.cs b/IISU/Jobs/Inventory.cs deleted file mode 100644 index be8acb5..0000000 --- a/IISU/Jobs/Inventory.cs +++ /dev/null @@ -1,236 +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.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -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.IISU.Jobs -{ - public class Inventory : IInventoryJobExtension - { - 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}"); - return _resolver.Resolve(value); - } - - 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); - - _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}"}"); - - 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"); - - 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 PowerShellCertStore( - config.CertificateStoreDetails.ClientMachine, config.CertificateStoreDetails.StorePath, - runSpace); - _logger.LogTrace("psCertStore Created"); - - using (var ps = PowerShell.Create()) - { - 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..."); - } - _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 (PsCertStoreException 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 - }; - } - } - - public string ExtensionName => "IISU"; - public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) - { - return PerformInventory(jobConfiguration, submitInventoryUpdate); - } - } -} \ No newline at end of file diff --git a/IISU/Jobs/Management.cs b/IISU/Jobs/Management.cs deleted file mode 100644 index 492107b..0000000 --- a/IISU/Jobs/Management.cs +++ /dev/null @@ -1,264 +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.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -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.IISU.Jobs -{ - public class Management : IManagementJobExtension - { - private ILogger _logger; - - private IPAMSecretResolver _resolver; - - private string _thumbprint = string.Empty; - - private string ServerUserName { get; set; } - private string ServerPassword { get; set; } - - 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) - { - try - { - _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"); - - 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 PowerShellCertStore( - 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"); - - 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 = 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"); - - 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 = 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..."); - } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; - } - catch (Exception ex) - { - var failureMessage = $"Remove 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 - }; - } - } - - private JobResult PerformAddition(ManagementJobConfiguration config) - { - try - { - _logger.MethodEntry(); - - var iisManager=new IISManager(config,ServerUserName,ServerPassword); - return iisManager.AddCertificate(); - } - catch (Exception ex) - { - var failureMessage = $"Add 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 - }; - } - } - - } -} \ No newline at end of file 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/PowerShellCertException.cs b/IISU/PAMUtilities.cs similarity index 55% rename from IISU/PowerShellCertException.cs rename to IISU/PAMUtilities.cs index 87d915c..bbed644 100644 --- a/IISU/PowerShellCertException.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.IISU +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - [Serializable] - internal class PowerShellCertException : Exception + internal class PAMUtilities { - public PowerShellCertException() + internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logger, string name, string key) { - } - - public PowerShellCertException(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 PowerShellCertException(string message, Exception innerException) : base(message, innerException) - { - } - - protected PowerShellCertException(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/PowerShellCertStore.cs b/IISU/PowerShellCertStore.cs deleted file mode 100644 index ce9505a..0000000 --- a/IISU/PowerShellCertStore.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.IISU -{ - internal class PowerShellCertStore - { - public PowerShellCertStore(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 PsCertStoreException($"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 PsCertificate - { - Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", - HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[]) c.Properties["RawData"]?.Value - }); - } - catch (Exception ex) - { - throw new PsCertStoreException( - $"Error listing certificate in {StorePath} store on {ServerName}: {ex.Message}"); - } - } - } -} \ No newline at end of file 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/IISU.csproj b/IISU/WindowsCertStore.csproj similarity index 57% rename from IISU/IISU.csproj rename to IISU/WindowsCertStore.csproj index 4ad8110..dd80058 100644 --- a/IISU/IISU.csproj +++ b/IISU/WindowsCertStore.csproj @@ -1,30 +1,40 @@ - - - - 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..77c0b2b 100644 --- a/IISU/manifest.json +++ b/IISU/manifest.json @@ -1,17 +1,29 @@ { "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" + }, + "CertStores.Win.ReEnrollment": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.Win.ReEnrollment" } } } 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 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\":\"MIIQNAIBAzCCD+4GCSqGSIb3DQEHAaCCD98Egg/bMIIP1zCCBYwGCSqGSIb3DQEHAaCCBX0EggV5MIIFdTCCBXEGCyqGSIb3DQEMCgECoIIE+jCCBPYwKAYKKoZIhvcNAQwBAzAaBBToZowff/9eRcA1B3EQRlhwDpkYIgICBAAEggTIyocmman/TgAtU7/Ne9P+f/YfWx5/A03JnrYIJ5M7l1kUkOTXa/r+zgR2UY+LjwcmHQnkK3AA/s9oWL/DjVjXSImILMzg9Izjun2xnmaQJAXQ9qRdLvNYxBWpOVw+4HlYTlp5he9w9qyUGVQ2HiniD/rFpcg0ybA/NiUcDKHh8gWEhFjhR41knYQXJ+efu20QGKSSCTiuF0DBpBCChu5tgnK2sdFE7VPlyQBNXLRsUtaMFEF7qnyvVWCe+Cgh1NY6yhpBfNtlZoJQ6cknRsuSHYWbcvY/O3DOUjI1gCBzMJnAxd4IRAfzKcUSbvwaRrOJIhhyA1ahGq6xhD3lHfB3x+EBx7xtKk1b5FLn6X4OcVfCBIrVFgmDc/Gd7Bs/extROk7OTjg4BejH7MDSBQQznz9vPBWO2BGmMiZeVahMR2n0qOTjvihFGGvrtIK9+3/ETB7qybF4kIi/lHovqt9JA4/VZSSlFND7n4++X2wFmWl7xTj7aO3Zsy3FaoskeEUrhWqpIpwvf7nUjS0XVDQa4kAI087foOI8Sx9E6DTrU7TDdRErDPO2avutvTrnZXhmdkt0m/DqpMYoDTSmZG/8IrImKu0C8zo81f90yUIPeE+rVe8bHbYEb1lHB+yV5pzR+TuRZkIhD+jqUZHYST4CS/gxhUL981RY0Ruly3OyXdVb4O6/tvfaYI3QavV5Sw2FNhs4i5QkLFqbcP1K9ZX1F4yBVrepzhGzWF161jMBg8UeN8YW/56MIIphRmUXVtre7WDDe/6BxdCSmHXd5CGRbLrD1Gi8Ii+fpJEeV9DWJIIc2kqEZUX3kkqTicmz8BHH0S7ipgp4tzPEls+9zsE9NiZTBCuXPMInZR9Ji/uZbt/EevYJ8gNq8CG9OPL0dIkciLTqsPyBtWlrrlltqQRXilfSuvtHPa2BRzRDqdmfK4TlED7C0kcpPSpVvndH+nI4NHXX/BDoQdfs2flwyeNhVqqL5hGQkgbJwp6OTF8mpmZa9t1e+DeAXr4I7IZrdrvKvKEyErb/virGOCyEd5ediEYaL3tmfUZbaIKdIfluB13OXmBUvzE3fWPGq3re15FXbUVa9nw6cWyoYHzkDS92narUHX/zo0ticGC6210RvPMNQ/LUypthNtuq8gGxSGvzrtV/zPosSOOMaTjlGZE2nTryyEzVJDNn14OuLZ/EjDiaRfbjsIv0Lha1WugqrV8OevtawHSJE5gWWFYqruDoDkbQJ+tcm1Qg8NuPhIP3SFwOYVctHKAVxypf19p5OkB314EwlJsuCMp9n7UtMG2WWmlrCaruOVMjQzAJblJuip419clrBJfVzw/6p18+mhOwsm6Tn0rWQzTPonIOza+Zcy2MOTZtPMNv2WEB23jXHMJmn2UCGRT8+mceLSCKNoedEbS4OJdLKCB3OYFFyqmmXtzcOv6K4ZYVxZ24qLXc2l/aKZPCsE4lOCH3WY3Cszs+AprjhbMJKvMVNdxsIfVJ1wcsLrDKdS4KocSYH2Ww9AN5T+llFjC57QTdZCoZQakW+dyzfXpOrwXUraxFHeavTiQVX057BnzXaSmbO+TGts6JNebkYDqdd2aC/j2aoaCLcMHW/E2QiQt58MvcgvtbBsF/8ULpmoOlMWQwIwYJKoZIhvcNAQkVMRYEFEaNcugeJbpKVvjf9gGwRorKgogGMD0GCSqGSIb3DQEJFDEwHi4AdwB3AHcALgB0AGUAcwB0AGUAYQBkAGQAZABsAGEAawBzAGQAZgAuAGMAbwBtMIIKQwYJKoZIhvcNAQcGoIIKNDCCCjACAQAwggopBgkqhkiG9w0BBwEwKAYKKoZIhvcNAQwBBjAaBBT4ls2Db2OhuT5Qh1IF99PwahathQICBACAggnwtRro9j+o2h8p8Li76S6Wc+/3/7et1crIMP1GQsVpI1y5CPfSRNfIacNr17i46kHxj4VTjhaO9tfooH6zYMUTJsV59uczjj464DXh/QxjOumsxuTUL0EHSvhYoka4/tfr1H8uEVEtO6aeOOm5FtvA+ixtdCIZOH9NCDeKRHBnjzUxYRORVLl94NEscg1y++wNmx3HiiJDdG9Rydm/+Bo2iCg9w3konujw2/0XPXPLsoHYGOUxmyx8zqf+1Dz1fp5f75bQ7q6dZmxjenPE/rItfPPf46tvgXsuUCEeXEK4zbIVeyc6Qux3ihCCXOvVC9EM6Blv9nnnwLuv2vPMNLiqcB8cUr2Sb2loaaZQ7AA8h88YQd1R+SKgvH6CnYtiBJqWIeKJpf9VtFITb6C5hVXGm+Ep76F3PrnmkfD79+GLI9Y/y1CVWBZ3FLFM/bZViY49HCEw2St953PTuxjH/lJlvupf1gO2I+UKIDxjm5HfBZv/3CRF81H/wm9lcfaksgdBkGJ9hQzf5aX8DM314+QHHIey5v82SdK2hwWqUJqli4xywoDrngYBepxa2orAyf5bFEYs1yplx87O7p2L2ybTu9yJmq5+E6wNs0KOIsMb7+aDPN/YTjm/Wxv6/49tu9n6VWFb+OPfNo6oV6FnUCzGn2BDXSg9KN2RFZMzL+aSEXhQ8xOfddqvfwAR4Ypd1eE/1rRmbl3VXwNlUFW1bn4CVo0e67fM8d2QvCOFZ4e3SPMCFmjdXwpwxx3L1oK2lG6OzG7jAsSTK9Wl4mR0i3Z2BiyHuDL9vOtjGzJMdTPyn1VbB9d7TOYq7Is38LYUCm0Fv6V3WyVE+lBJoADuACwByZ9s0RjWRp67hTV9/3Qx/djLzWu1VzxrRovUgLF3VNFXzoB3fv0oajpLrWDgJq679j014HTUxhxerosJWl2kX4rLzWPauLwzw9QXdpZWUt0zNoFaNaM/5HX8qvcNkEGrBEOJ+UIlHMSxdkHkOkIP1bgOZCBDURMPx9vdVG0tNDffeGmSDN9Mr1i6vTxwTd8Ghj3FwleYvChUzGRRwj88x1nIlp4egmI/VC9/PsB9ENYKhdHRfYxLF6Z8Qpqex3+30EaGDCaRUdQIIApMuBRmpg4JEW3V4mYH3UTkhvCxgh+vbBXkEi+7AcWBWYvGANB08+N8++u0Oh6X8HQ+tCaevEITSopkCMn37enYcGH4PFxeTnUb8Tk7+pw6GPm9qOhpA69pIvPC4HVsJ3lNmo7NqakoyTXxCQchn27PvuwASbcpnkZK4QAQalcM7hogs1ecuMyI0W1yEzn0+cf8CiLreFr6XHZ95qQlRnuad5uovuFH/94SlWT8nrwGZSBUv8v4DISKKeRuJ+m1jHHd0n5c4hi6qw8Qgn0tmDwo+K4FvpDZ8nEU+ajuyK3BGP4uXIkDIdHJvFVMlcu58UwJrUdT1YB5+7pMfdbA3sHuGLV03Hi/WLaz0MLYer4BuURNiDSj2MQoRoyWnJ7URrq0R6b1i2EY2QpIz4F+c8K5CnWzHsZXz/4S683QWDzAaGxLKBdcv/aFiOu+Ka0vj5ft9rR04tzZIlRCCv7g6fMIevBpdbE8sqg+pKAlwiwHisyc2GqocNwS6t0rUuRZjkVmGAOPU3ZHoy2s12B+rcegwnsRER6xb3Koelq7a66mXQVLSPhMuUfNKJpkHlhJUan5EOJkxFtMFJP9s1/i8b+ynZEm9byK6x9fzvQR7Bg/Chn7TxeeohxiTWGcy0X1+ABztc+IPOElMbMXVusAcAwVVCENSVsxdVJklWUT/PB1ZLuCKaPZ706oFrR4y42nZKYUaPfywqQ+2v1m8onlhrsY5GgtQAqUyUpCnrsQnPpsocx6GAVzamvgE30KMFztpVoKtXPiGumO3wpnM7kYrRSu8sIsWASbSpwyWTyi5x54YdbT2rPQm/NjGUciLwSsiwHdszvd8nWuOQLcoeA9UEhoRgAS8AAPToMRuypQkTmZFc4EFQpTFgqe4lWTn8xaX2sVlpape6ajjcxf0CiqRvTePvEH2IbSVwpEtsS2m5k0692gwN5zQoeV1j/hLcZoKR8/HeMe1P7yztA5DXMvRmPAJDeu8xs3gAx+cJERkNkkk5PhUVplZc5JsyR8P2l8elZ6rL5QbeN5lePLjQ8do0Cpwki39WJ8JrdDzCmTqakqUEjC0Zu/31c8720grSD+VieYApCa9AMEj9obI7YY7YQHVJb+mqXbpVL3W+J4OBvOiXP1wvLmhg5JlYdlqLGmGbSRJEd0/S3Jo+mH9ykkNlCJ3ZjuoeTcf3jZmgL3XEGrs/f7QQ35pSjJMqEBtbKPD522zNZ1wV11NfHEaDIvb53xp1+HaDtVcUNMxpvlaPCZUTKbtajDK9DSzt8pCqm+/hZsUXt/qhEMGd4AAIuOlTbviprU7fFIjfIRzihR08RUt2jVj5ygvBmQDtVcF8GZ3VbEDoznCP+6MXcysIKnnxZ1omK9NYvLUeXjAfnHxO1GSgEJF0I44uPT4rbCmE2m804iTOzuXyGaOaMY7eq5a5KzWIQtG9TOc3JL8gQLNtC3tjv2nxRuG5Y+MOi/GWc/oBAgAYIIu+cunSBaWLTiWORC2H+cuGsX7okiTJQr1TjCGR1E4aA1/y5VGiGqT8OsAFKyg1d8TZV8xQp6JQPS341X58RlIdplemdTAEoqakFVA2RZTkQ1VvXfksb6ne3cfVdswGWDH6Q03HOTyrZKu9awOMkzROSvGo9yZuxjo8DaxgRV5I6sSK2JoqIxNqnHALsDZ8K7GGg1LYhG0jBKHndoCN+aIm5RpV7p+dZ4vt0seiSTBK4L4QKAxg6Gld/8CUkvPaXDySSV4Mc8PAuspT0KLbIccb0NLFz0wJp1HZ3BzTNElZzZ5q1PYzJULc5IXLaFHM10kj1EoF3FzcDz5oYYPpGh0/Yz0xgbLBmpbt6f06zjrc50Iyq0DEztvlgqz+NWT/TG+0plXUdFQVyxGOLvZUsRo2PeqN5hZAM+lXTgdInVPC8hWHPnRNyXNrTiAZJulvHUzv5ZDHksXbDsy/Ci0KnnH3hmYqlrragECOELLjLJGJll3mXHgNW6nfeut4qWki16P42nBNxy+F5et1hcHvJ7tNQRi/UPPL9yWOFq8y+FflsevECwaMH8SKc8Nc6+MBAqx2mxTf0g2jFhQIwrvzZcjXsEJl2bwswxGBIAcojIEHxLi8Ui9fJSgY1DLcDiw5I9GOhbPHcZ2sO7Fe84VFjPZCB1H4VOsJzhVVEU54owLeCHugfGpSAIwLlYZnf80p+54B/CnEw1ntkqjhm4J2cIghEjHQEIBM+LQHyNePlqkkjslGWYcOWIQ+slvNGdp1mddi8x+PLiNV5I4tERbH5otBHvMD0wITAJBgUrDgMCGgUABBRM4ih/Py00W8IYB4C0uucXDYIJjgQUWH+KmgKrv+VEeKDCU7IPTFTs5kYCAgQA\",\"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/IISU.sln b/WindowsCertStore.sln similarity index 71% rename from IISU.sln rename to WindowsCertStore.sln index 50f7736..ed90aa4 100644 --- a/IISU.sln +++ b/WindowsCertStore.sln @@ -1,48 +1,56 @@ - -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\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 + 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 + {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 + 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 diff --git a/images/IISCertStore.png b/images/IISCertStore.png new file mode 100644 index 0000000..a4be6ad Binary files /dev/null and b/images/IISCertStore.png differ diff --git a/images/WinCertStore.png b/images/WinCertStore.png new file mode 100644 index 0000000..d9e9ffe Binary files /dev/null and b/images/WinCertStore.png differ diff --git a/readme_source.md b/readme_source.md index 41b421a..550a643 100644 --- a/readme_source.md +++ b/readme_source.md @@ -1,16 +1,18 @@ -**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: -This agent implements four job types – Inventory, Management Add, Remove and ReEnrollment. Below are the steps necessary to configure this AnyAgent. + Get-ChildItem Cert:\LocalMachine -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. +The returned list will contain the actual certificate store name to be used when entering store location. + +By default, most certificates are stored in the “Personal” (My) and “Web Hosting” (WebHosting) stores. + +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: @@ -20,33 +22,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) @@ -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