diff --git a/Bundle/Inventory.cs b/Bundle/Inventory.cs index 2af011e..10b1038 100644 --- a/Bundle/Inventory.cs +++ b/Bundle/Inventory.cs @@ -38,7 +38,7 @@ public override JobResult ProcessJob(InventoryJobConfiguration config, SubmitInv try { - base.ParseJobProperties(); + base.ParseStoreProperties(); SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger); F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, null, IgnoreSSLWarning, UseTokenAuth, config.LastInventory); diff --git a/Bundle/Management.cs b/Bundle/Management.cs index 0dfd74a..959df17 100644 --- a/Bundle/Management.cs +++ b/Bundle/Management.cs @@ -45,7 +45,7 @@ public override JobResult ProcessJob(ManagementJobConfiguration config) try { SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger); - base.ParseJobProperties(); + base.ParseStoreProperties(); base.PrimaryNodeActive(); F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, config.JobCertificate.PrivateKeyPassword, IgnoreSSLWarning, UseTokenAuth, config.LastInventory) diff --git a/CHANGELOG.md b/CHANGELOG.md index 938cad0..96a25fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ +v1.10.0 +- Modify SSLProfiles entry parameter (F5-SL-REST store type only) to allow adding one-to-many SSL Profile bindings when adding NEW certificates (ignored for renewals/replacements) +- Update dlls with vulnerability alerts +- Handle inventorying certificates with bag attributes + v1.9.0 -- Added new SSLProfiles READ ONLY entry parameter that will contain a comma delimited list of SSL Profiles a certificate is bound to on the F5 device. +- Added new SSLProfiles READ ONLY entry parameter that will contain a comma delimited list of SSL Profiles a certificate is bound to on the F5 device. (F5-SL-REST store type only) v1.8.1 - Documentation changes including highlighting lack of HA support as well as a correction to the proper StorePath value for F5-CA-REST stores. diff --git a/F5Client.cs b/F5Client.cs index be4d22f..ad400ed 100644 --- a/F5Client.cs +++ b/F5Client.cs @@ -26,6 +26,7 @@ using System.Diagnostics.CodeAnalysis; using static Keyfactor.Orchestrators.Common.OrchestratorConstants; using static Org.BouncyCastle.Math.EC.ECCurve; +using System.Reflection.Metadata; namespace Keyfactor.Extensions.Orchestrator.F5Orchestrator { @@ -205,6 +206,22 @@ public bool CertificateExists(string partition, string crtName) return exists; } + public void BindCertificate(string alias, string sslProfile) + { + LogHandlerCommon.MethodEntry(logger, CertificateStore, "BindCertificate"); + + try + { + F5Binding binding = new F5Binding { cert = $"{alias}", key = $"{alias}", chain = $"{alias}" }; + REST.Patch($"/mgmt/tm/ltm/profile/client-ssl/{sslProfile}", binding); + } + + finally + { + LogHandlerCommon.MethodExit(logger, CertificateStore, "BindCertificate"); + } + } + private void AddCertificate(byte[] entryContents, string partition, string name) { LogHandlerCommon.MethodEntry(logger, CertificateStore, "AddCertificate"); @@ -334,6 +351,7 @@ private X509Certificate2Collection GetCertificateEntry(string path) //PEM(w / headers)-- > L or I case "L": case "I": + case "Q": LogHandlerCommon.Trace(logger, CertificateStore, "Certificate is PEM with headers"); crtBytes = System.Convert.FromBase64String(crt); certificateEntry = System.Text.ASCIIEncoding.ASCII.GetString(crtBytes); diff --git a/F5DataModels.cs b/F5DataModels.cs index 7a3f79e..ef3102b 100644 --- a/F5DataModels.cs +++ b/F5DataModels.cs @@ -136,6 +136,13 @@ internal class F5BundleInclude public string[] includeBundle { get; set; } } + internal class F5Binding + { + public string cert { get; set; } + public string key { get; set; } + public string chain { get; set; } + } + public class F5Transaction { public string transid { get; set; } diff --git a/F5Orchestrator.csproj b/F5Orchestrator.csproj index 1f0df23..af3ce4b 100644 --- a/F5Orchestrator.csproj +++ b/F5Orchestrator.csproj @@ -8,10 +8,12 @@ - - - + + + + + Always diff --git a/InventoryBase.cs b/InventoryBase.cs index 9b136d8..83f16c0 100644 --- a/InventoryBase.cs +++ b/InventoryBase.cs @@ -28,9 +28,9 @@ public abstract class InventoryBase : F5JobBase, IInventoryJobExtension public abstract JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory); - protected void ParseJobProperties() + protected void ParseStoreProperties() { - LogHandlerCommon.MethodEntry(logger, JobConfig.CertificateStoreDetails, "ParseJobProperties"); + LogHandlerCommon.MethodEntry(logger, JobConfig.CertificateStoreDetails, "ParseStoreProperties"); dynamic properties = JsonConvert.DeserializeObject(JobConfig.CertificateStoreDetails.Properties.ToString()); IgnoreSSLWarning = properties.IgnoreSSLWarning == null || string.IsNullOrEmpty(properties.IgnoreSSLWarning.Value) ? false : bool.Parse(properties.IgnoreSSLWarning.Value); diff --git a/ManagementBase.cs b/ManagementBase.cs index cec7484..c80ad2d 100644 --- a/ManagementBase.cs +++ b/ManagementBase.cs @@ -34,14 +34,14 @@ public abstract class ManagementBase : F5JobBase, IManagementJobExtension public abstract JobResult ProcessJob(ManagementJobConfiguration config); - protected void ParseJobProperties() + protected void ParseStoreProperties() { if (logger == null) { logger = Keyfactor.Logging.LogHandler.GetClassLogger(this.GetType()); } - LogHandlerCommon.MethodEntry(logger, JobConfig.CertificateStoreDetails, "ParseJobProperties"); + LogHandlerCommon.MethodEntry(logger, JobConfig.CertificateStoreDetails, "ParseStoreProperties"); dynamic properties = JsonConvert.DeserializeObject(JobConfig.CertificateStoreDetails.Properties.ToString()); PrimaryNodeOnlineRequired = false; PrimaryNode = string.Empty; @@ -71,7 +71,7 @@ protected void ParseJobProperties() int.TryParse(properties.PrimaryNodeCheckRetryWaitSecs.ToString(), out primaryNodeRetryWaitSecs); PrimaryNodeRetryWaitSecs = primaryNodeRetryWaitSecs; LogHandlerCommon.Trace(logger, JobConfig.CertificateStoreDetails, $"Primary node retry wait seconds '{PrimaryNodeRetryWaitSecs}'"); - LogHandlerCommon.MethodExit(logger, JobConfig.CertificateStoreDetails, "ParseJobProperties"); + LogHandlerCommon.MethodExit(logger, JobConfig.CertificateStoreDetails, "ParseStoreProperties"); } else { diff --git a/README.md b/README.md index edac4ca..4bfec5a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ## Overview -The f5-rest-orchestrator orchestrator extension manages various types of certificates on a F5 Big IP device (version 15 or later). TLS certificates, CA bundles, and the TLS certificate bound to the administrative website can all be managed with this integration within the scope described in the sections below. One important note, this integration DOES NOT manage high availability (HA) failover between primary and secondary nodes. If syncing between primary and secondary nodes is desired, this must either be handled within your F5 Big IP instance itself, or you can set up a Keyfactor Command certificate store for each node (primary and secondary) and manage each separately. +The f5-rest-orchestrator orchestrator extension manages various types of certificates on a F5 Big IP device (version 14 or later). TLS certificates, CA bundles, and the TLS certificate bound to the administrative website can all be managed with this integration within the scope described in the sections below. One important note, this integration DOES NOT manage high availability (HA) failover between primary and secondary nodes. If syncing between primary and secondary nodes is desired, this must either be handled within your F5 Big IP instance itself, or you can set up a Keyfactor Command certificate store for each node (primary and secondary) and manage each separately. The F5 Universal Orchestrator extension implements 3 Certificate Store Types. Depending on your use case, you may elect to use one, or all of these Certificate Store Types. Descriptions of each are provided below. @@ -47,9 +47,9 @@ The F5 Universal Orchestrator extension implements 3 Certificate Store Types. De This integration is compatible with Keyfactor Universal Orchestrator version 10.1 and later. ## Support -The F5 Universal Orchestrator extension If you have a support issue, please open a support ticket by either contacting your Keyfactor representative or via the Keyfactor Support Portal at https://support.keyfactor.com. +The F5 Universal Orchestrator extension is supported by Keyfactor. If you require support for any issues or have feature request, please open a support ticket by either contacting your Keyfactor representative or via the Keyfactor Support Portal at https://support.keyfactor.com. -> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab. +> If you want to contribute bug fixes or additional enhancements, use the **[Pull requests](../../pulls)** tab. ## Requirements & Prerequisites @@ -174,7 +174,7 @@ the Keyfactor Command Portal | Name | Display Name | Description | Type | Default Value | Entry has a private key | Adding an entry | Removing an entry | Reenrolling an entry | | ---- | ------------ | ---- | ------------- | ----------------------- | ---------------- | ----------------- | ------------------- | ----------- | - | SSLProfiles | SSL Profiles | One to many comma delimited F5 SSL Profile names the certificate is bound to | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | + | SSLProfiles | SSL Profiles | One to many comma delimited F5 SSL Profiles to bind the certificate to (new certificates ONLY) | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | The Entry Parameters tab should look like this: diff --git a/SSLProfile/Inventory.cs b/SSLProfile/Inventory.cs index f4d1afa..404addd 100644 --- a/SSLProfile/Inventory.cs +++ b/SSLProfile/Inventory.cs @@ -40,7 +40,7 @@ public override JobResult ProcessJob(InventoryJobConfiguration config, SubmitInv try { - base.ParseJobProperties(); + base.ParseStoreProperties(); SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger); F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, null, IgnoreSSLWarning, UseTokenAuth, config.LastInventory); diff --git a/SSLProfile/Management.cs b/SSLProfile/Management.cs index fc3d8d2..d108157 100644 --- a/SSLProfile/Management.cs +++ b/SSLProfile/Management.cs @@ -16,6 +16,7 @@ using Org.BouncyCastle.Security; using System.Text; using System.IO; +using Newtonsoft.Json; namespace Keyfactor.Extensions.Orchestrator.F5Orchestrator.SSLProfile { @@ -48,7 +49,8 @@ public override JobResult ProcessJob(ManagementJobConfiguration config) try { SetPAMSecrets(config.ServerUsername, config.ServerPassword, config.CertificateStoreDetails.StorePassword, logger); - base.ParseJobProperties(); + string sslProfiles = JobConfig.JobProperties.ContainsKey("SSLProfiles") && JobConfig.JobProperties["SSLProfiles"] != null ? JobConfig.JobProperties["SSLProfiles"].ToString() : string.Empty; + base.ParseStoreProperties(); base.PrimaryNodeActive(); F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, config.JobCertificate.PrivateKeyPassword, IgnoreSSLWarning, UseTokenAuth, config.LastInventory) @@ -62,7 +64,9 @@ public override JobResult ProcessJob(ManagementJobConfiguration config) { case CertStoreOperationType.Add: LogHandlerCommon.Debug(logger, config.CertificateStoreDetails, $"Add entry '{config.JobCertificate.Alias}' to '{config.CertificateStoreDetails.StorePath}'"); - PerformAddJob(f5, StorePassword, RemoveChain); + bool certificateExists = PerformAddJob(f5, StorePassword, RemoveChain); + if (!certificateExists && !string.IsNullOrEmpty(sslProfiles)) + BindCertificateToSSLProfiles(f5, config.JobCertificate.Alias, sslProfiles); break; case CertStoreOperationType.Remove: LogHandlerCommon.Trace(logger, config.CertificateStoreDetails, $"Remove entry '{config.JobCertificate.Alias}' from '{config.CertificateStoreDetails.StorePath}'"); @@ -79,6 +83,11 @@ public override JobResult ProcessJob(ManagementJobConfiguration config) LogHandlerCommon.Debug(logger, config.CertificateStoreDetails, "Job complete"); return new JobResult { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } + catch (BindException ex) + { + LogHandlerCommon.Error(logger, config.CertificateStoreDetails, ExceptionHandler.FlattenExceptionMessages(ex, $"Warning performing SSL profile binding: ")); + return new JobResult { Result = OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, FailureMessage = ExceptionHandler.FlattenExceptionMessages(ex, "Certificate successfully added, but one or more SSL profiles could not be bound. ") }; + } catch (Exception ex) { LogHandlerCommon.Error(logger, config.CertificateStoreDetails, ExceptionHandler.FlattenExceptionMessages(ex, $"Error performing Management {config.OperationType.ToString()}")); @@ -90,15 +99,16 @@ public override JobResult ProcessJob(ManagementJobConfiguration config) } } - private void PerformAddJob(F5Client f5, string certificatePassword, bool removeChain) + private bool PerformAddJob(F5Client f5, string certificatePassword, bool removeChain) { LogHandlerCommon.MethodEntry(logger, JobConfig.CertificateStoreDetails, "PerformAddJob"); string name = JobConfig.JobCertificate.Alias; string partition = f5.GetPartitionFromStorePath(); string certContents = !string.IsNullOrEmpty(JobConfig.JobCertificate.PrivateKeyPassword) && removeChain ? RemoveCertificateChainFromPfx() : JobConfig.JobCertificate.Contents; + bool certificateExists = f5.CertificateExists(partition, name); - if (f5.CertificateExists(partition, name)) + if (certificateExists) { if (!JobConfig.Overwrite) { throw new Exception($"An entry named '{name}' exists and 'overwrite' was not selected"); } @@ -111,6 +121,8 @@ private void PerformAddJob(F5Client f5, string certificatePassword, bool removeC f5.AddEntry(partition, name, certContents, certificatePassword); } LogHandlerCommon.MethodExit(logger, JobConfig.CertificateStoreDetails, "PerformAddJob"); + + return certificateExists; } private void PerformRemovalJob(F5Client f5) @@ -171,5 +183,34 @@ private string RemoveCertificateChainFromPfx() return rtnValue; } + + private void BindCertificateToSSLProfiles(F5Client f5, string alias, string sslProfiles) + { + bool hasError = false; + string errorMessages = string.Empty; + + foreach (string sslProfile in sslProfiles.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + try + { + f5.BindCertificate(alias, sslProfile); + } + catch (Exception ex) + { + hasError = true; + errorMessages += ExceptionHandler.FlattenExceptionMessages(ex, $"Error binding {sslProfile}: "); + } + } + + if (hasError) + { + throw new BindException(errorMessages); + } + } + + public class BindException : Exception + { + public BindException(string message) : base(message) { } + } } } diff --git a/WebServer/Inventory.cs b/WebServer/Inventory.cs index 9bbbc49..6fca588 100644 --- a/WebServer/Inventory.cs +++ b/WebServer/Inventory.cs @@ -40,7 +40,7 @@ public override JobResult ProcessJob(InventoryJobConfiguration config, SubmitInv try { LogHandlerCommon.Debug(logger, JobConfig.CertificateStoreDetails, "Processing job parameters"); - base.ParseJobProperties(); + base.ParseStoreProperties(); SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger); F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, null, IgnoreSSLWarning, UseTokenAuth, config.LastInventory); diff --git a/WebServer/Management.cs b/WebServer/Management.cs index 600bd65..d814dee 100644 --- a/WebServer/Management.cs +++ b/WebServer/Management.cs @@ -44,7 +44,7 @@ public override JobResult ProcessJob(ManagementJobConfiguration config) try { SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger); - base.ParseJobProperties(); + base.ParseStoreProperties(); base.PrimaryNodeActive(); F5Client f5 = new F5Client(JobConfig.CertificateStoreDetails, ServerUserName, ServerPassword, JobConfig.UseSSL, JobConfig.JobCertificate.PrivateKeyPassword, IgnoreSSLWarning, UseTokenAuth, JobConfig.LastInventory) diff --git a/docsource/content.md b/docsource/content.md index c5311bf..c1608ec 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -1,6 +1,6 @@ ## Overview -The f5-rest-orchestrator orchestrator extension manages various types of certificates on a F5 Big IP device (version 15 or later). TLS certificates, CA bundles, and the TLS certificate bound to the administrative website can all be managed with this integration within the scope described in the sections below. One important note, this integration DOES NOT manage high availability (HA) failover between primary and secondary nodes. If syncing between primary and secondary nodes is desired, this must either be handled within your F5 Big IP instance itself, or you can set up a Keyfactor Command certificate store for each node (primary and secondary) and manage each separately. +The f5-rest-orchestrator orchestrator extension manages various types of certificates on a F5 Big IP device (version 14 or later). TLS certificates, CA bundles, and the TLS certificate bound to the administrative website can all be managed with this integration within the scope described in the sections below. One important note, this integration DOES NOT manage high availability (HA) failover between primary and secondary nodes. If syncing between primary and secondary nodes is desired, this must either be handled within your F5 Big IP instance itself, or you can set up a Keyfactor Command certificate store for each node (primary and secondary) and manage each separately. ## Requirements diff --git a/docsource/images/F5-SL-REST-advanced-store-type-dialog.png b/docsource/images/F5-SL-REST-advanced-store-type-dialog.png index 11b826b..c0419a9 100644 Binary files a/docsource/images/F5-SL-REST-advanced-store-type-dialog.png and b/docsource/images/F5-SL-REST-advanced-store-type-dialog.png differ diff --git a/docsource/images/F5-SL-REST-basic-store-type-dialog.png b/docsource/images/F5-SL-REST-basic-store-type-dialog.png index 1f2003a..fe41ec1 100644 Binary files a/docsource/images/F5-SL-REST-basic-store-type-dialog.png and b/docsource/images/F5-SL-REST-basic-store-type-dialog.png differ diff --git a/integration-manifest.json b/integration-manifest.json index c2d2add..2f406fc 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -150,7 +150,7 @@ "DependsOn": "", "DefaultValue": "", "Options": "", - "Description": "One to many comma delimited F5 SSL Profile names the certificate is bound to" + "Description": "One to many comma delimited F5 SSL Profiles to bind the certificate to (new certificates ONLY)" } ] },